-
Notifications
You must be signed in to change notification settings - Fork 4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement FindReferences for deconstructions #22934
Conversation
|
||
foreach (var nested in deconstruction.Nested) | ||
{ | ||
foreach (var m in getMethods(nested)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📝 this is a bit unfortunate, but I don't expect deeply nested deconstructions. #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer an immutable array builder. we end up tending to have to avoid IEnumreables in pathological cases. #Resolved
@dotnet/roslyn-compiler for review. This PR provides a public API for accessing deconstruction information. See description for more details. Thanks #Resolved |
@dotnet/roslyn-ide for review as well. This PR implements FindReferences for |
@@ -227,6 +227,38 @@ public ForEachSymbols GetForEachSymbols(SemanticModel semanticModel, SyntaxNode | |||
} | |||
} | |||
|
|||
public ImmutableArray<IMethodSymbol> GetDeconstructionSymbols(SemanticModel semanticModel, SyntaxNode node) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe GetDeconstructionMethods? #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also... on a personal level... i don't like having a single helper method that works on all these types. I think i'd prefer a GetAssignmentDeconstructionSymbols and GetForEachStatementDeconstructionSymbols. #Resolved
|
||
foreach (var nested in deconstruction.Nested) | ||
{ | ||
foreach (var m in getMethods(nested)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
avoid single letter foreach variable names. #Closed
@@ -106,6 +106,11 @@ private bool IsForEachMethod(IMethodSymbol methodSymbol) | |||
methodSymbol.Name == WellKnownMemberNames.MoveNextMethodName; | |||
} | |||
|
|||
private bool IsDeconstructMethod(IMethodSymbol methodSymbol) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: => #Resolved
@@ -24,7 +24,8 @@ private struct ContextInfo | |||
bool containsThisConstructorInitializer, | |||
bool containsBaseConstructorInitializer, | |||
bool containsElementAccessExpression, | |||
bool containsIndexerMemberCref) : | |||
bool containsIndexerMemberCref, | |||
bool containsDeconstruction) : |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
need to update the 'version' of SyntaxTreeINdex. Otherwise, existing indices will not be recreated and it will look like no files contain deconstructions. #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -268,6 +268,68 @@ class B : C, A | |||
Assert.Empty(expectedMatchedLines); | |||
} | |||
|
|||
[Fact, WorkItem(18963, "https://github.com/dotnet/roslyn/issues/18963")] | |||
public async Task FindReferences_Deconstruction() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wrong location for these tests. Look for the more declarative FindReferencesTests written in VB. #Resolved
@dotnet/roslyn-compiler for review as well (adding public API to get information about deconstructions, for IDE scenario). Thanks #Resolved |
You're welcome :) Can't wait to have this. It'll be super useful! #Resolved |
@@ -246,6 +246,16 @@ public bool IsForEachStatement(SyntaxNode node) | |||
return node is ForEachStatementSyntax; | |||
} | |||
|
|||
public bool IsDeconstruction(SyntaxNode node) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here. SyntaxFacts isn't for merging multiple nodes from the same language into the same concept. It's for providing a uniform syntactic API over VB and C#. I would prefer IsDeconstructionAssignment and IsDeconstructionForEachStatement #Resolved
@@ -91,6 +92,7 @@ internal sealed partial class SyntaxTreeIndex | |||
containsQueryExpression = containsQueryExpression || syntaxFacts.IsQueryExpression(node); | |||
containsElementAccess = containsElementAccess || syntaxFacts.IsElementAccessExpression(node); | |||
containsIndexerMemberCref = containsIndexerMemberCref || syntaxFacts.IsIndexerMemberCRef(node); | |||
containsDeconstruction = containsDeconstruction || syntaxFacts.IsDeconstruction(node); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this would then become containsDestruction = containsDeconstruction || (syntaxFacts.IsDeconstructionAssignment || syntaxFacts.IsDeconstructionForEachStatement);
#Resolved
427522d
to
914c168
Compare
public void {|Definition:Decons$$truct|}(out int x1, out int x2) { x1 = 1; x2 = 2; } | ||
public void M() | ||
{ | ||
[|var (x1, x2) =|] this; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fasscinating. This is the resultant span? I guess i'm ok with that. though i think it's strange. #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I think something a bit narrower might be wise. Consider if there are comments in the middle of that somewhere... #Resolved
public void M() | ||
{ | ||
[|var (x1, x2) =|] this; | ||
[|foreach (var (y1, y2)|] in new[] { this }) { } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here.. i think i would prefer either "var (x1, x2)" to be the span, or "(x1, x2)" to be hte span. or "=" to be hte span. for the foreach case, the same (except replacing "in" for "="). #Resolved
return; | ||
} | ||
|
||
foreach (var nested in deconstruction.Nested) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so just to be sure. You can' thave both .Method and .Nested available at the same time? #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. There is a bug here. Actually, if there is a .Method
, there will be a .Nested
. #Resolved
@@ -246,6 +246,32 @@ public bool IsForEachStatement(SyntaxNode node) | |||
return node is ForEachStatementSyntax; | |||
} | |||
|
|||
public bool IsDeconstructionForEachStatement(SyntaxNode node) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: => #Resolved
return Location.Create(tree, TextSpan.FromBounds(assignment.SpanStart, assignment.OperatorToken.Span.End)); | ||
|
||
case ForEachVariableStatementSyntax @foreach: | ||
return Location.Create(tree, TextSpan.FromBounds(@foreach.SpanStart, @foreach.Variable.Span.End)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this location seems absolutely wacky :D #Resolved
switch (node) | ||
{ | ||
case AssignmentExpressionSyntax assignment: | ||
return Location.Create(tree, TextSpan.FromBounds(assignment.SpanStart, assignment.OperatorToken.Span.End)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am... barely ok with this :D i think it would be better to just select hte =, and the 'in' token though. But i would be open for arguments otherwise. #Resolved
var deconstructMethods = semanticFacts.GetDeconstructionAssignmentMethods(semanticModel, node); | ||
if (deconstructMethods.IsEmpty) | ||
{ | ||
deconstructMethods = semanticFacts.GetDeconstructionForEachMethods(semanticModel, node); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
don't you always want to do this? i don't understand why you only do htis if you didn't find any deconstruction assignments. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh........... i see. yes. this is checking per node. so you couldn't have both. cute :) n/m then. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still worth adding a comment. I think what's throwing me off is the GetDeconstruction..Methods* name, since plural, implies that it might be doing recursive itself. Which it's not. #Resolved
Filed #23037 for updating ChangeSignature to handle Deconstruct methods. #Resolved |
@dotnet/roslyn-compiler for review of the compiler portion. Thanks #Resolved |
if (!underlyingConversions.IsDefault) | ||
{ | ||
result = Hash.Combine(result, Hash.CombineValues(underlyingConversions.SelectAsArray(c => c.GetHashCode()))); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hash.CombineValues
calls GetHashCode()
on the ImmutableArray<T>
and handles IsDefault
. #Resolved
/// Its second nested node has a Method (Deconstructable1.Deconstruct), no Conversion, and two Nested nodes. | ||
/// Those last two nested nodes have no Method, but each have a Conversion (ImplicitNumeric, from int to long). | ||
/// </summary> | ||
public struct DeconstructionInfo : IEquatable<DeconstructionInfo> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are IEquatable<DeconstructionInfo>
and Equals
needed? #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did this for consistency with ForEachDeconstructionInfo
, which begs the question.
I'll check with @gafter on public API design, and if nobody objects, I'll remove IEquatable
. #Closed
From discussion with Chuck and Neal, we don't need IEquatable on DeconstructionInfo yet (we're not sure why ForEachStatementInfo needed it). Removed that portion and its tendrils. #Resolved |
@@ -4403,6 +4403,18 @@ private static ImmutableArray<Symbol> CreateReducedExtensionMethodIfPossible(Bou | |||
public abstract ForEachStatementInfo GetForEachStatementInfo(CommonForEachStatementSyntax node); | |||
|
|||
/// <summary> | |||
/// Gets for deconstruction info. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gets assignment deconstruction info
? #Resolved
public abstract DeconstructionInfo GetDeconstructionInfo(AssignmentExpressionSyntax node); | ||
|
||
/// <summary> | ||
/// Gets for deconstruction info. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gets foreach variable deconstruction info
? #Resolved
@@ -837,6 +837,34 @@ public override ForEachStatementInfo GetForEachStatementInfo(CommonForEachStatem | |||
enumeratorInfoOpt.CurrentConversion); | |||
} | |||
|
|||
public override DeconstructionInfo GetDeconstructionInfo(AssignmentExpressionSyntax node) | |||
{ | |||
var boundDeconstruction = (BoundDeconstructionAssignmentOperator)GetUpperBoundNode(node); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this throw a cast exception if the caller passes an assignment that is not a deconstruction assignment? Is that the desired behavior? #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question. It's the same behavior as GetForEachStatementInfo
, but does seem wrong.
For comparison, GetAwaitExpressionInfo
returns default
in such cases. I'll change foreach and deconstruction methods to do the same.
In reply to: 149514253 [](ancestors = 149514253)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hum, actually GetForEachStatementInfo
is fine. The problem is just with assignment (which may or may not be a deconstruction).
In reply to: 149516672 [](ancestors = 149516672,149514253)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Compiler changes LGTM.
Thanks Chuck! |
|
||
/// <summary> | ||
/// The representation of a deconstruction as a tree of Deconstruct methods and conversions. | ||
/// Methods only appear in non-terminal nodes. All terminal nodes have a Conversion. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cref Conversion if that means it's a reference to the property.
/// | ||
/// The top-level node has no Method and no Conversion, but has two Nested nodes. | ||
/// Its first nested node has no Method, but has a Conversion (Identity). | ||
/// Its second nested node has a Method (Deconstructable1.Deconstruct), no Conversion, and two Nested nodes. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment implies that across the summation of all the nodes, there is one node that has a Method, but I would expect two since two methods are being invoked.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Argh, sorry about that :-(
@@ -996,5 +996,31 @@ public static ConditionalAccessExpressionSyntax GetInnerMostConditionalAccessExp | |||
return to.WithPrependedLeadingTrivia(finalTrivia) | |||
.WithAdditionalAnnotations(Formatter.Annotation); | |||
} | |||
|
|||
public static SyntaxNode IsInDeconstructionLeft(this SyntaxNode node) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an Is* method returning non-Boolean. Please revise the name. #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good other than one specific question about doc comments that look like they have a typo which makes it really confusing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
API and compiler changes look good to me.
Nice, thanks! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The docs are so clear, even a IDE developer can understand it. Thanks!
You mean when the docs don't say the opposite of what they should ;-) |
46f66cf
to
5ae6e7b
Compare
Implements "Find All References" for
Deconstruct
methods. It is similar to how FAR works on methods that participate inforeach
.Fixes #18963
From the compiler side, the change is to expose information about deconstructions (assignment/declarations and foreach) via new public APIs. Since deconstructions are not conversions in the language, but they are implemented using nested conversions (like tuple conversions) with a deconstruction method, I'm using a wrapper class around deconstruction conversion objects to abstract from the implementation.
The existing
DeconstructionInfo
class (which is internal) was renamed toDeconstructMethodInfo
.From the IDE side, we now keep track of whether a document uses any deconstructions (just like we do for
foreach
) and we can scan through a document to find all theDeconstruct
method symbols that are references. That's the core of the "Find All References" implementation. (thanks Cyrus for the tips)Note: this fix was incomplete, it was supplemented by #24223