-
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
Fix reference finding for tuple fields and anonymous type properties #69804
base: main
Are you sure you want to change the base?
Changes from 5 commits
29de8bb
1e5132f
2b9c62a
08fcd62
071e032
e72fae9
d951355
1d2622a
6c3cfe7
51b9253
cf79756
38309f1
1c3dfd1
70754f3
5adca8f
dbad8fb
8ba66b3
7b40bc8
48efc1f
df8c23e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -409,6 +409,225 @@ partial class Program | |
]]> | ||
</DocumentFromSourceGenerator> | ||
</Project> | ||
</Workspace> | ||
Await TestAPIAndFeature(input, kind, host) | ||
End Function | ||
|
||
<WorkItem("https://github.com/dotnet/roslyn/issues/52621")> | ||
<WpfTheory, CombinatorialData> | ||
Public Async Function TestImplicitlyNamedTuples(kind As TestKind, host As TestHost) As Task | ||
Dim input = | ||
<Workspace> | ||
<Project Language="C#" CommonReferencesNetCoreApp="true"> | ||
<Document><![CDATA[ | ||
using System; | ||
|
||
class Program | ||
{ | ||
static void Main() | ||
{ | ||
int {|Definition:x|} = 4, y = 5; | ||
var z = ($$[|x|], y); | ||
Rekkonnect marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
]]> | ||
</Document> | ||
</Project> | ||
</Workspace> | ||
Await TestAPIAndFeature(input, kind, host) | ||
End Function | ||
|
||
<WorkItem("https://github.com/dotnet/roslyn/issues/52621")> | ||
<WpfTheory, CombinatorialData> | ||
Public Async Function TestImplicitTupleSwitchStatement(kind As TestKind, host As TestHost) As Task | ||
Dim input = | ||
<Workspace> | ||
<Project Language="C#" CommonReferencesNetCoreApp="true"> | ||
<Document><![CDATA[ | ||
using System; | ||
|
||
class Program | ||
{ | ||
static void Main() | ||
{ | ||
int {|Definition:x|} = 4, y = 5; | ||
int z = 3; | ||
switch ($$[|x|], y) | ||
{ | ||
case (1, 0): | ||
z += [|x|]; | ||
break; | ||
case (1, 1): | ||
z += [|x|]; | ||
break; | ||
default: | ||
break; | ||
} | ||
} | ||
} | ||
]]> | ||
</Document> | ||
</Project> | ||
</Workspace> | ||
Await TestAPIAndFeature(input, kind, host) | ||
End Function | ||
|
||
<WorkItem("https://github.com/dotnet/roslyn/issues/52621")> | ||
<WpfTheory, CombinatorialData> | ||
Public Async Function TestTupleDeconstruction(kind As TestKind, host As TestHost) As Task | ||
Dim input = | ||
<Workspace> | ||
<Project Language="C#" CommonReferencesNetCoreApp="true"> | ||
<Document><![CDATA[ | ||
using System; | ||
|
||
class C | ||
{ | ||
(int, int) M() | ||
{ | ||
return (1, 1); | ||
} | ||
|
||
void M2() | ||
{ | ||
int {|Definition:x|}; | ||
int y; | ||
|
||
($$[|x|], y) = M(); | ||
|
||
[|x|] = 0; | ||
} | ||
} | ||
]]> | ||
</Document> | ||
</Project> | ||
</Workspace> | ||
Await TestAPIAndFeature(input, kind, host) | ||
End Function | ||
|
||
<WorkItem("https://github.com/dotnet/roslyn/issues/52621")> | ||
<WpfTheory, CombinatorialData> | ||
Public Async Function TestTupleSwappedFields(kind As TestKind, host As TestHost) As Task | ||
Dim input = | ||
<Workspace> | ||
<Project Language="C#" CommonReferencesNetCoreApp="true"> | ||
<Document><![CDATA[ | ||
using System; | ||
|
||
class C | ||
{ | ||
void M(int {|Definition:left|}, int right) | ||
{ | ||
var r = ($$[|left|], right); | ||
r = (right, [|left|]); | ||
} | ||
} | ||
]]> | ||
</Document> | ||
</Project> | ||
</Workspace> | ||
Await TestAPIAndFeature(input, kind, host) | ||
End Function | ||
|
||
<WorkItem("https://github.com/dotnet/roslyn/issues/52621")> | ||
<WpfTheory, CombinatorialData> | ||
Public Async Function TestTupleNonLocal(kind As TestKind, host As TestHost) As Task | ||
Dim input = | ||
<Workspace> | ||
<Project Language="C#" CommonReferencesNetCoreApp="true"> | ||
<Document><![CDATA[ | ||
using System; | ||
|
||
class C | ||
{ | ||
int {|Definition:Property|} { get; set; } | ||
|
||
void M(string a) | ||
{ | ||
var r = (a.Length, $$[|Property|]); | ||
r = ([|Property|], Property: [|Property|]); | ||
} | ||
} | ||
]]> | ||
</Document> | ||
</Project> | ||
</Workspace> | ||
Await TestAPIAndFeature(input, kind, host) | ||
End Function | ||
|
||
<WorkItem("https://github.com/dotnet/roslyn/issues/52621")> | ||
<WpfTheory, CombinatorialData> | ||
Public Async Function TestTupleExplicitNames(kind As TestKind, host As TestHost) As Task | ||
Dim input = | ||
<Workspace> | ||
<Project Language="C#" CommonReferencesNetCoreApp="true"> | ||
<Document><![CDATA[ | ||
using System; | ||
|
||
class C | ||
{ | ||
void M(int a, int b) | ||
{ | ||
var t = ($$[|{|Definition:x|}|]: a, y: b); | ||
t = ([|x|]: b, y: a); | ||
b = t.[|x|]; | ||
} | ||
} | ||
]]> | ||
</Document> | ||
</Project> | ||
</Workspace> | ||
Await TestAPIAndFeature(input, kind, host) | ||
End Function | ||
|
||
<WorkItem("https://github.com/dotnet/roslyn/issues/52621")> | ||
<WpfTheory, CombinatorialData> | ||
Public Async Function TestTupleExplicitNamesSameAsLocals01(kind As TestKind, host As TestHost) As Task | ||
Dim input = | ||
<Workspace> | ||
<Project Language="C#" CommonReferencesNetCoreApp="true"> | ||
<Document><![CDATA[ | ||
using System; | ||
|
||
class C | ||
{ | ||
void M(int x, int y) | ||
{ | ||
var t = ($$[|{|Definition:x|}|]: x, y: y); | ||
t = ([|x|]: y, y: x); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. test of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add test where there is also an implicit name. i.e. |
||
t = ([|x|]: x, y: y); | ||
b = t.[|x|]; | ||
} | ||
} | ||
]]> | ||
</Document> | ||
</Project> | ||
</Workspace> | ||
Await TestAPIAndFeature(input, kind, host) | ||
End Function | ||
|
||
<WorkItem("https://github.com/dotnet/roslyn/issues/52621")> | ||
<WpfTheory, CombinatorialData> | ||
Public Async Function TestTupleExplicitNamesSameAsLocals02(kind As TestKind, host As TestHost) As Task | ||
Dim input = | ||
<Workspace> | ||
<Project Language="C#" CommonReferencesNetCoreApp="true"> | ||
<Document><![CDATA[ | ||
using System; | ||
|
||
class C | ||
{ | ||
void M(int {|Definition:x|}, int y) | ||
{ | ||
var t = (x: $$[|x|], y: y); | ||
t = (x: y, y: [|x|]); | ||
t = (x: [|x|], y: y); | ||
b = t.x; | ||
} | ||
} | ||
]]> | ||
</Document> | ||
</Project> | ||
</Workspace> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i'm finding the tests hard to totally understand because they don't show both implicit and explicit named-props in anonymous-types, and implicit and explicit arguments for tuples. |
||
Await TestAPIAndFeature(input, kind, host) | ||
End Function | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -57,23 +57,54 @@ public bool CanReplaceWithRValue(SemanticModel semanticModel, [NotNullWhen(true) | |
{ | ||
var location = token.GetLocation(); | ||
|
||
foreach (var ancestor in token.GetAncestors<SyntaxNode>()) | ||
var tokenParent = token.Parent; | ||
var sourceAncestor = tokenParent; | ||
Rekkonnect marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// Skip the tuple expression if the identifier is part of the simple identifier expression inside a tuple, | ||
// but not the identifier of the field | ||
// For example, we are evaluating `x` in `(x, y)`, or the expression after the colon in `(x: x, y: y)` | ||
// but not the name of the field | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this doesn't explain the 'why' this is important. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added comment ptal |
||
if (tokenParent is IdentifierNameSyntax { Parent: ArgumentSyntax { Parent: TupleExpressionSyntax parentTuple } }) | ||
{ | ||
sourceAncestor = parentTuple.Parent; | ||
} | ||
|
||
if (sourceAncestor is null) | ||
return null; | ||
|
||
foreach (var ancestor in sourceAncestor.AncestorsAndSelf()) | ||
{ | ||
var symbol = semanticModel.GetDeclaredSymbol(ancestor, cancellationToken); | ||
if (symbol != null) | ||
{ | ||
if (symbol is IMethodSymbol { MethodKind: MethodKind.Conversion }) | ||
{ | ||
// The token may be part of a larger name (for example, `int` in `public static operator int[](Goo g);`. | ||
// So check if the symbol's location encompasses the span of the token we're asking about. | ||
if (symbol.Locations.Any(static (loc, location) => loc.SourceTree == location.SourceTree && loc.SourceSpan.Contains(location.SourceSpan), location)) | ||
return symbol; | ||
} | ||
else | ||
switch (symbol) | ||
{ | ||
// For any other symbols, we only care if the name directly matches the span of the token | ||
if (symbol.Locations.Contains(location)) | ||
return symbol; | ||
case IMethodSymbol { MethodKind: MethodKind.Conversion }: | ||
{ | ||
// The token may be part of a larger name (for example, `int` in `public static operator int[](Goo g);`. | ||
// So check if the symbol's location encompasses the span of the token we're asking about. | ||
if (symbol.Locations.Any(static (loc, location) => loc.SourceTree == location.SourceTree && loc.SourceSpan.Contains(location.SourceSpan), location)) | ||
return symbol; | ||
break; | ||
} | ||
|
||
case IPropertySymbol { ContainingType: ITypeSymbol { IsAnonymousType: true } }: | ||
{ | ||
// If the span is part of the property's assignment expression, it's not our target result | ||
// For example, we are evaluating `a.Length` in `new { a.Length }`, or in `new { Length = a.Length }` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this doesn't say 'why' though. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added comment ptal |
||
if (ancestor is AnonymousObjectMemberDeclaratorSyntax declarator && | ||
declarator.Expression.Span.Contains(location.SourceSpan)) | ||
{ | ||
break; | ||
} | ||
|
||
return symbol; | ||
} | ||
|
||
default: | ||
// For any other symbols, we only care if the name directly matches the span of the token | ||
if (symbol.Locations.Contains(location)) | ||
return symbol; | ||
break; | ||
} | ||
|
||
// We found some symbol, but it defined something else. We're not going to have a higher node defining _another_ symbol with this token, so we can stop now. | ||
|
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.
add an example where there is a named-arg for 'a'. like
new { a = "", ...}
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.
add test where the $$ is on the defintion. add test where $$ is on the
a
innew { a = "", ...