Skip to content

Teach pattern-matching analyzer to detect and fix additional cases#19907

Merged
CyrusNajmabadi merged 1 commit intodotnet:masterfrom
alrz:smart-as
Jul 25, 2017
Merged

Teach pattern-matching analyzer to detect and fix additional cases#19907
CyrusNajmabadi merged 1 commit intodotnet:masterfrom
alrz:smart-as

Conversation

@alrz
Copy link
Member

@alrz alrz commented May 31, 2017

Fixes #17744

  • null check in while in addition to if
  • inline type check: if ((x = o as T) != null)

/cc @CyrusNajmabadi

@alrz alrz changed the title Teach pattern matching analyzer to detect and fix additional cases Teach pattern-matching analyzer to detect and fix additional cases May 31, 2017
@alrz alrz force-pushed the smart-as branch 2 times, most recently from 579bee5 to a067452 Compare June 1, 2017 10:52
@Pilchie Pilchie added Area-IDE Community The pull request was submitted by a contributor who is not a Microsoft employee. labels Jun 5, 2017
@Pilchie
Copy link
Member

Pilchie commented Jun 5, 2017

Thanks for the contribution - tagging @dotnet/roslyn-ide for review.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

index++ :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

index-- :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be TryFind since this can fail. Maybe say "TryFind..InContainingBlock" as well. That would help explain why we need to hand write this instead of just using the ILocalSymbol and getting the declaration from that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How can this happen?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can't, just to satisfy the compiler. is there an ExceptionUtilities.Unreachable accessible to use?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. It's called "ExceptionUtilities.Unreachable" :D

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's not accessible in feature project. If not could I add it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be accessible. We link that file into the Workspaces layer IIRC. Let me check.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It shoudl be available:

image

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i woudl personally prefer ifOrWhileStatement

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ifOrWhileStatement

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it might be nice to keep this around, so other calls you make could access this directly. your call.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shoudl be declarationStatement. "declaration" makes me think it has type VariableDeclaration.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll need to figure out if we need a new option here. Or if we need to update the option we show to the user here. I think it's ok to have a single option, but we likely need to call out these cases in the preview. What do you think @Pilchie ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally don't think we need a new option here, since this isn't available on the command line yet.

@kuhlenh how do you feel about the preview question?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's unclear to me why so much logic changed in this method. Can you explain what your new code does that's different from teh old way of checking def assignment? Thanks!

Copy link
Member Author

@alrz alrz Jun 5, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

two reasons: in the old code if assigned was false we didn't run the loop body, so we could break out of loop early. and the other reason is that, unlike if, pattern var is scoped only inside while block.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

declarationStatement.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs comments.

@CyrusNajmabadi
Copy link
Contributor

This is awesome. Mostly nits. Haven't reviewed the def assignment stuff yet. Waiting on you for explanation first.

@alrz
Copy link
Member Author

alrz commented Jun 5, 2017

Other cases I want this to handle are:

  • non-leftmost null checks
var x = e as T;
if (condition && x != null && x.Foo)
// gives
if (condition && e is T x && x.Foo)
  • negative null checks
var x = e as T;
if (x == null) return;
// gives
if (!(e is T x)) return;
  • null check in ternary
var x = e as T;
return x != null ? x.Foo : ...;
// gives
return e is T x ? x.Foo : ...;

These all should be able to mix e.g. ternary+negative+inline, while+positive+non-leftmost etc.

Before I do anything about it (probably in another PR?) I'd like to hear your thoughts on those. Thanks.

@CyrusNajmabadi
Copy link
Contributor

Can you give examples of each of those?

@CyrusNajmabadi
Copy link
Contributor

The more i think about this, the more i'm ok with all of this. I think the general feature we're offering is "use the new 'is Type T' pattern matching construct". So wherever reasonable we can see something that can translate into that pattern, we should offer it.

@CyrusNajmabadi
Copy link
Contributor

(and yes, another PR would be good)

@alrz
Copy link
Member Author

alrz commented Jun 5, 2017

Updated my previous comment to include some examples.

@CyrusNajmabadi
Copy link
Contributor

non-leftmost null checks

Will this work with definite assignment?

@CyrusNajmabadi
Copy link
Contributor

CyrusNajmabadi commented Jun 5, 2017

negative null checks

This is iffy. Because some people don't like the if (!( form. We may need a separate option for this. "Use negative pattern matching".

Note: i would personally like this.

@CyrusNajmabadi
Copy link
Contributor

null check in ternary

If it's just "return ternary" then yes please.

@CyrusNajmabadi
Copy link
Contributor

Seperate PR for those though. ideally one PR per new addition.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Generally would prefer all comments to be "Sentence case."

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bad indentation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tagging @mavasani . It seems unfortunate that we will get tons of callbacks for a file and we'll have to do this check and bail out for every node we get called back on. It would be really nice if we could have some sort of conditional way to say "don't even call into us for this syntax tree."

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mavasani RegisterSyntaxNodeAction could take a SyntaxTree predicate that we could potentially use to avoid calling this action if the SyntaxTree didn't match.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fyi, i'm a bit on the fence about this. In general we'd like to do some quick up front checks that don't involve expensive typechecks. That way we can be as low overhead as possible since we're going to be called on every if-statement in the world. so doing IsParentKind(Block) (and doing all the other syntactic checks first) before doing type-tests is generally preferred.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with you but I think it's not just this instance, for example OfType on all nodes to find identifiers or local declarations should be updated as well.

I propose we add bool IsKind<T>(this SyntaxNode, SyntaxKind, out T) where T : SyntaxNode.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wish there was a way to get the corresponding SyntaxKind from the syntax node type

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assignment.

try to wrap at 100col for comments.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might want to comment/give-example why this is ok.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is exactly what the old code does (though not so obviously).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nevertheless, I think CheckDefnitiveAssignment is oversimplified. what if the statement is branched, etc? Could we use control flow analysis instead?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's not clear to me why this is appropriate. do you have an example?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not. when we return true here we won't suggest the fix.

@shyamnamboodiripad shyamnamboodiripad changed the base branch from dev15.6 to master June 29, 2017 22:17
@alrz
Copy link
Member Author

alrz commented Jul 8, 2017

@CyrusNajmabadi I've addressed your comments, Had a few questions. Can you please take a look? thanks.

Copy link
Contributor

@CyrusNajmabadi CyrusNajmabadi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the new extension. If you're feeling up to it, feel free to update the rest of our code to use it :)

@alrz
Copy link
Member Author

alrz commented Jul 10, 2017

We could even have overloads for node types then you dont need to pass the syntax kind,

static bool Is(this SyntaxNode node, out LocalVarDeclStatement result)
// etc

if(node.Is(out LocalVarDeclStatement decl))

We could have it for abstract classes as well. Maybe the syntax generator could produce these helpers.

Is that too many overloads?

@CyrusNajmabadi
Copy link
Contributor

Can't tell if serious. How would that overload work?

@alrz
Copy link
Member Author

alrz commented Jul 10, 2017

The generated code would look like this:

static bool Is(this SyntaxNode node, out LocalDeclarationStatementSyntax result)
  => node.IsKind(SyntaxKind.LocalDeclarationStatement, out result);

static bool Is(this SyntaxNode node, out EnumDeclarationSyntax result)
  => node.IsKind(SyntaxKind.EnumDeclaration, out result);

I'm not sure if we should do this for abstract classes? e.g.

static bool Is(this SyntaxNode node, out TypeDeclarationSyntax result) {
  => node.IsKind(ClassDeclaration, InterfaceDeclaration, StructDeclaration, out result);

@CyrusNajmabadi
Copy link
Contributor

Ah, i see. I missed the part where this would be autogenerated. I would be fine with you making such a helper. But not right now :)

@alrz
Copy link
Member Author

alrz commented Jul 11, 2017

Ok I got it here in this gist, will open a PR at a later time.

@Pilchie
Copy link
Member

Pilchie commented Jul 25, 2017

@CyrusNajmabadi Anything more you are waiting on here? If not, any reason not to merge this?

@CyrusNajmabadi
Copy link
Contributor

I've signed off :)

@Pilchie
Copy link
Member

Pilchie commented Jul 25, 2017

retest this please

@Pilchie
Copy link
Member

Pilchie commented Jul 25, 2017

Test results were fairly stale.

- null check in `while` in addition to `if`
- inline type check e.g. `if ((o as T) != null)`
@CyrusNajmabadi
Copy link
Contributor

Thanks!

@alrz alrz deleted the smart-as branch July 25, 2017 19:26
@sharwell sharwell added Community The pull request was submitted by a contributor who is not a Microsoft employee. and removed Community The pull request was submitted by a contributor who is not a Microsoft employee. labels Aug 31, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area-IDE cla-already-signed Community The pull request was submitted by a contributor who is not a Microsoft employee.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants