From 29de8bb6447698a42d9a36772a8b9299eba3e5fa Mon Sep 17 00:00:00 2001 From: Rekkonnect Date: Sun, 3 Sep 2023 00:19:35 +0300 Subject: [PATCH 01/12] Fix FAR in simple expr implying tuple name --- .../FindReferencesTests.Tuples.vb | 167 ++++++++++++++++++ .../SemanticFacts/CSharpSemanticFacts.cs | 15 +- 2 files changed, 181 insertions(+), 1 deletion(-) diff --git a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.Tuples.vb b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.Tuples.vb index 0aa066f41665c..5f5dd80ee1caf 100644 --- a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.Tuples.vb +++ b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.Tuples.vb @@ -409,6 +409,173 @@ partial class Program ]]> + + Await TestAPIAndFeature(input, kind, host) + End Function + + + + Public Async Function TestImplicitlyNamedTuples(kind As TestKind, host As TestHost) As Task + Dim input = + + + + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + + Public Async Function TestImplicitTupleSwitchStatement(kind As TestKind, host As TestHost) As Task + Dim input = + + + + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + + Public Async Function TestTupleDeconstruction(kind As TestKind, host As TestHost) As Task + Dim input = + + + + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + + Public Async Function TestTupleSwappedFields(kind As TestKind, host As TestHost) As Task + Dim input = + + + + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + + Public Async Function TestTupleNonLocal(kind As TestKind, host As TestHost) As Task + Dim input = + + + + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + + Public Async Function TestTupleExplicitNames(kind As TestKind, host As TestHost) As Task + Dim input = + + + + + Await TestAPIAndFeature(input, kind, host) End Function diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs index bd7f3d78bd169..ccfcdc04d3e36 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs @@ -57,7 +57,20 @@ public bool CanReplaceWithRValue(SemanticModel semanticModel, [NotNullWhen(true) { var location = token.GetLocation(); - foreach (var ancestor in token.GetAncestors()) + var tokenParent = token.Parent; + if (tokenParent is null) + { + return null; + } + + var sourceAncestor = tokenParent; + // Skip the tuple expression if the identifier implies the name of the tuple field + if (tokenParent is IdentifierNameSyntax { Parent: ArgumentSyntax { Parent: TupleExpressionSyntax parentTuple } }) + { + sourceAncestor = parentTuple.Parent; + } + + foreach (var ancestor in sourceAncestor.AncestorsAndSelf()) { var symbol = semanticModel.GetDeclaredSymbol(ancestor, cancellationToken); if (symbol != null) From 1e5132f5ff1609e60cfbc512355cf12105725b4c Mon Sep 17 00:00:00 2001 From: Rekkonnect Date: Sun, 3 Sep 2023 01:33:39 +0300 Subject: [PATCH 02/12] Fix anonymous type property name references --- ...indReferencesTests.AnonymousTypeSymbols.vb | 53 +++++++++++++++++++ .../SemanticFacts/CSharpSemanticFacts.cs | 47 ++++++++++------ 2 files changed, 84 insertions(+), 16 deletions(-) diff --git a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.AnonymousTypeSymbols.vb b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.AnonymousTypeSymbols.vb index cd189d520a5f1..05b621ffde1a5 100644 --- a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.AnonymousTypeSymbols.vb +++ b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.AnonymousTypeSymbols.vb @@ -8,6 +8,7 @@ Imports Microsoft.CodeAnalysis.Remote.Testing Namespace Microsoft.CodeAnalysis.Editor.UnitTests.FindReferences Partial Public Class FindReferencesTests +#Region "Visual Basic" Public Async Function TestAnonymousType1(kind As TestKind, host As TestHost) As Task @@ -165,5 +166,57 @@ End Class Await TestAPIAndFeature(input, kind, host) End Function +#End Region + +#Region "C#" + + + Public Async Function TestAnonymousTypesCSharp_SimpleExpression(kind As TestKind, host As TestHost) As Task + Dim input = + + + + + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + + Public Async Function TestAnonymousTypesCSharp_PropertyAccess(kind As TestKind, host As TestHost) As Task + Dim input = + + + + + + + + Await TestAPIAndFeature(input, kind, host) + End Function +#End Region End Class End Namespace diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs index ccfcdc04d3e36..c3284b02355b3 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs @@ -58,11 +58,6 @@ public bool CanReplaceWithRValue(SemanticModel semanticModel, [NotNullWhen(true) var location = token.GetLocation(); var tokenParent = token.Parent; - if (tokenParent is null) - { - return null; - } - var sourceAncestor = tokenParent; // Skip the tuple expression if the identifier implies the name of the tuple field if (tokenParent is IdentifierNameSyntax { Parent: ArgumentSyntax { Parent: TupleExpressionSyntax parentTuple } }) @@ -70,23 +65,43 @@ public bool CanReplaceWithRValue(SemanticModel semanticModel, [NotNullWhen(true) 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 } }: + { + Debug.Assert(ancestor is AnonymousObjectMemberDeclaratorSyntax); + var declarator = (AnonymousObjectMemberDeclaratorSyntax)ancestor; + // If the span is not part of the property's explicit name, it's not our target result + if (declarator.NameEquals is null || !declarator.NameEquals.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. From 2b9c62ac21e0ec50843191b2e1c05cd4be7e8de0 Mon Sep 17 00:00:00 2001 From: Rekkonnect Date: Sun, 3 Sep 2023 09:38:20 +0300 Subject: [PATCH 03/12] Review comments + one more test --- .../FindReferencesTests.Tuples.vb | 25 +++++++++++++++++++ .../SemanticFacts/CSharpSemanticFacts.cs | 7 +++--- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.Tuples.vb b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.Tuples.vb index 5f5dd80ee1caf..2edcd73ab2e77 100644 --- a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.Tuples.vb +++ b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.Tuples.vb @@ -573,6 +573,31 @@ class C b = t.[|x|]; } } +]]> + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + + Public Async Function TestTupleExplicitNamesSameAsLocals(kind As TestKind, host As TestHost) As Task + Dim input = + + + diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs index c3284b02355b3..7f3847997d370 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs @@ -60,6 +60,7 @@ public bool CanReplaceWithRValue(SemanticModel semanticModel, [NotNullWhen(true) var tokenParent = token.Parent; var sourceAncestor = tokenParent; // Skip the tuple expression if the identifier implies the name of the tuple field + // For example, we are evaluating `x` in `(x, y)`, but not in `(x: x, y: y)` if (tokenParent is IdentifierNameSyntax { Parent: ArgumentSyntax { Parent: TupleExpressionSyntax parentTuple } }) { sourceAncestor = parentTuple.Parent; @@ -86,10 +87,10 @@ public bool CanReplaceWithRValue(SemanticModel semanticModel, [NotNullWhen(true) case IPropertySymbol { ContainingType: ITypeSymbol { IsAnonymousType: true } }: { - Debug.Assert(ancestor is AnonymousObjectMemberDeclaratorSyntax); - var declarator = (AnonymousObjectMemberDeclaratorSyntax)ancestor; // If the span is not part of the property's explicit name, it's not our target result - if (declarator.NameEquals is null || !declarator.NameEquals.Span.Contains(location.SourceSpan)) + // For example, we are evaluating `a.Length` in `new { a.Length }` + if (ancestor is AnonymousObjectMemberDeclaratorSyntax declarator && + (declarator.NameEquals is null || !declarator.NameEquals.Span.Contains(location.SourceSpan))) { break; } From 08fcd62a88f6575ef51b983adc43a5d6605fad6c Mon Sep 17 00:00:00 2001 From: Rekkonnect Date: Sun, 3 Sep 2023 21:24:50 +0300 Subject: [PATCH 04/12] Review comments + improve tests --- .../FindReferencesTests.Tuples.vb | 29 ++++++++++++++++++- .../SemanticFacts/CSharpSemanticFacts.cs | 12 ++++---- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.Tuples.vb b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.Tuples.vb index 2edcd73ab2e77..a707009485e9e 100644 --- a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.Tuples.vb +++ b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.Tuples.vb @@ -582,7 +582,7 @@ class C - Public Async Function TestTupleExplicitNamesSameAsLocals(kind As TestKind, host As TestHost) As Task + Public Async Function TestTupleExplicitNamesSameAsLocals01(kind As TestKind, host As TestHost) As Task Dim input = @@ -595,9 +595,36 @@ class C { var t = ($$[|{|Definition:x|}|]: x, y: y); t = ([|x|]: y, y: x); + t = ([|x|]: x, y: y); b = t.[|x|]; } } +]]> + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + + Public Async Function TestTupleExplicitNamesSameAsLocals02(kind As TestKind, host As TestHost) As Task + Dim input = + + + diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs index 7f3847997d370..9bd5bacccf2af 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs @@ -59,8 +59,10 @@ public bool CanReplaceWithRValue(SemanticModel semanticModel, [NotNullWhen(true) var tokenParent = token.Parent; var sourceAncestor = tokenParent; - // Skip the tuple expression if the identifier implies the name of the tuple field - // For example, we are evaluating `x` in `(x, y)`, but not in `(x: x, y: y)` + // 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 if (tokenParent is IdentifierNameSyntax { Parent: ArgumentSyntax { Parent: TupleExpressionSyntax parentTuple } }) { sourceAncestor = parentTuple.Parent; @@ -87,10 +89,10 @@ public bool CanReplaceWithRValue(SemanticModel semanticModel, [NotNullWhen(true) case IPropertySymbol { ContainingType: ITypeSymbol { IsAnonymousType: true } }: { - // If the span is not part of the property's explicit name, it's not our target result - // For example, we are evaluating `a.Length` in `new { a.Length }` + // 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 }` if (ancestor is AnonymousObjectMemberDeclaratorSyntax declarator && - (declarator.NameEquals is null || !declarator.NameEquals.Span.Contains(location.SourceSpan))) + (declarator.Expression.Span.Contains(location.SourceSpan))) { break; } From 071e032a4f159b38965862f2d6b9a7de68e72d3e Mon Sep 17 00:00:00 2001 From: Rekkonnect Date: Sun, 3 Sep 2023 21:25:29 +0300 Subject: [PATCH 05/12] Remove extra parentheses --- .../CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs index 9bd5bacccf2af..377b1d9f87694 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs @@ -92,7 +92,7 @@ public bool CanReplaceWithRValue(SemanticModel semanticModel, [NotNullWhen(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 }` if (ancestor is AnonymousObjectMemberDeclaratorSyntax declarator && - (declarator.Expression.Span.Contains(location.SourceSpan))) + declarator.Expression.Span.Contains(location.SourceSpan)) { break; } From d951355665ef4390c564c858092ff434752ac1bc Mon Sep 17 00:00:00 2001 From: Rekkonnect Date: Tue, 19 Sep 2023 12:44:09 +0300 Subject: [PATCH 06/12] Adjust tests and finding references --- ...indReferencesTests.AnonymousTypeSymbols.vb | 84 ++++++++++++++++++- .../FindReferencesTests.Tuples.vb | 4 +- .../SemanticFacts/CSharpSemanticFacts.cs | 61 ++++++++++++-- 3 files changed, 139 insertions(+), 10 deletions(-) diff --git a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.AnonymousTypeSymbols.vb b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.AnonymousTypeSymbols.vb index 05b621ffde1a5..35c5ea9c0eeaf 100644 --- a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.AnonymousTypeSymbols.vb +++ b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.AnonymousTypeSymbols.vb @@ -171,7 +171,7 @@ End Class #Region "C#" - Public Async Function TestAnonymousTypesCSharp_SimpleExpression(kind As TestKind, host As TestHost) As Task + Public Async Function TestAnonymousTypesCSharp_SimpleExpressionHoverNameImplication(kind As TestKind, host As TestHost) As Task Dim input = @@ -184,6 +184,7 @@ class C int length = [|a|].Length; var r = new { [|a|], Length = 1 }; r = new { $$[|a|], [|a|].Length }; + r = new { [|a|] = string.Empty, Length = [|a|].Length }; } } ]]> @@ -195,7 +196,57 @@ class C - Public Async Function TestAnonymousTypesCSharp_PropertyAccess(kind As TestKind, host As TestHost) As Task + Public Async Function TestAnonymousTypesCSharp_SimpleExpressionHoverParameterDeclaration(kind As TestKind, host As TestHost) As Task + Dim input = + + + + + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + + Public Async Function TestAnonymousTypesCSharp_SimpleExpressionHoverAnonymousTypeProperty(kind As TestKind, host As TestHost) As Task + Dim input = + + + + + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + + Public Async Function TestAnonymousTypesCSharp_PropertyAccessHoverAccessedProperty(kind As TestKind, host As TestHost) As Task Dim input = @@ -206,9 +257,34 @@ class C void M(string a) { int length = a.[|Length|]; - var r = new { a, Length = 1 }; + var r = new { a, [|Length|] = 1 }; r = new { a, a.$$[|Length|] }; - r = new { a, Length = a.[|Length|] }; + r = new { a, [|Length|] = a.[|Length|] }; + } +} +]]> + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + + Public Async Function TestAnonymousTypesCSharp_PropertyAccessHoverAnonymousTypeProperty(kind As TestKind, host As TestHost) As Task + Dim input = + + + + diff --git a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.Tuples.vb b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.Tuples.vb index a707009485e9e..1873c810f1d62 100644 --- a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.Tuples.vb +++ b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.Tuples.vb @@ -428,6 +428,8 @@ class Program { int {|Definition:x|} = 4, y = 5; var z = ($$[|x|], y); + z = ([|x|]: 0, y: 1); + z = ([|x|]: y, y: [|x|]); } } ]]> @@ -545,7 +547,7 @@ class C void M(string a) { var r = (a.Length, $$[|Property|]); - r = ([|Property|], Property: [|Property|]); + r = ([|Property|], [|Property|]: [|Property|]); } } ]]> diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs index 377b1d9f87694..1126aae5f4ec4 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs @@ -57,13 +57,13 @@ public bool CanReplaceWithRValue(SemanticModel semanticModel, [NotNullWhen(true) { var location = token.GetLocation(); - var tokenParent = token.Parent; - var sourceAncestor = tokenParent; + var sourceAncestor = token.Parent; // 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 - if (tokenParent is IdentifierNameSyntax { Parent: ArgumentSyntax { Parent: TupleExpressionSyntax parentTuple } }) + // We want to handle the implicit declaration of the tuple field when finding its references, not via its name implication + if (sourceAncestor is IdentifierNameSyntax { Parent: ArgumentSyntax { Parent: TupleExpressionSyntax parentTuple } }) { sourceAncestor = parentTuple.Parent; } @@ -91,6 +91,7 @@ public bool CanReplaceWithRValue(SemanticModel semanticModel, [NotNullWhen(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 }` + // We want to handle the implicit declaration of the property when finding its references, not via its name implication if (ancestor is AnonymousObjectMemberDeclaratorSyntax declarator && declarator.Expression.Span.Contains(location.SourceSpan)) { @@ -372,7 +373,56 @@ private static ImmutableArray GetSymbolInfo(SemanticModel semanticModel return semanticModel.GetSymbolInfo(baseType, cancellationToken).GetBestOrAllSymbols(); } - //Only in the orderby clause a comma can bind to a symbol. + // In the following cases, we want to return the tuple field or the anonymous object property too, as we imply its name + var impliedSymbols = ImmutableArray.Empty; + if (node is IdentifierNameSyntax identifier) + { + var implyingNameNode = node; + + if (identifier.Parent is MemberAccessExpressionSyntax(SyntaxKind.SimpleMemberAccessExpression) simpleMemberAccessExpression + && simpleMemberAccessExpression.Name == node) + { + implyingNameNode = simpleMemberAccessExpression; + } + + var implyingNameNodeParent = implyingNameNode.Parent; + + switch (implyingNameNodeParent) + { + case ArgumentSyntax { NameColon: null, Parent: TupleExpressionSyntax tuple }: + { + var tupleType = semanticModel.GetTypeInfo(tuple, cancellationToken).Type; + if (tupleType is not null) + { + var field = tupleType.GetMembers().FirstOrDefault(s => s.Name == token.ValueText); + if (field is IFieldSymbol) + { + impliedSymbols = ImmutableArray.Create(field); + } + } + + break; + } + + case AnonymousObjectMemberDeclaratorSyntax { NameEquals: null, Parent: AnonymousObjectCreationExpressionSyntax anonymousObject }: + { + var anonymousObjectConstructorSymbol = semanticModel.GetSymbolInfo(anonymousObject, cancellationToken).Symbol; + if (anonymousObjectConstructorSymbol is IMethodSymbol anonymousObjectConstructor) + { + var anonymousType = anonymousObjectConstructorSymbol.ContainingType; + var property = anonymousType.GetMembers().FirstOrDefault(s => s.Name == token.ValueText); + if (property is IPropertySymbol) + { + impliedSymbols = ImmutableArray.Create(property); + } + } + + break; + } + } + } + + // Only in the orderby clause a comma can bind to a symbol. if (token.IsKind(SyntaxKind.CommaToken)) return ImmutableArray.Empty; @@ -390,7 +440,8 @@ private static ImmutableArray GetSymbolInfo(SemanticModel semanticModel } } - return semanticModel.GetSymbolInfo(node, cancellationToken).GetBestOrAllSymbols(); + return semanticModel.GetSymbolInfo(node, cancellationToken).GetBestOrAllSymbols() + .Concat(impliedSymbols); } public bool IsInsideNameOfExpression(SemanticModel semanticModel, [NotNullWhen(true)] SyntaxNode? node, CancellationToken cancellationToken) From 1d2622a8da7cd262c817c1bf35cac0aae3bbe619 Mon Sep 17 00:00:00 2001 From: Rekkonnect Date: Tue, 19 Sep 2023 16:02:57 +0300 Subject: [PATCH 07/12] Adjust implementation using cascade --- ...indReferencesTests.AnonymousTypeSymbols.vb | 18 +-- .../FindReferencesTests.Tuples.vb | 12 +- .../Finders/AbstractReferenceFinder.cs | 139 ++++++++++++++++++ .../Finders/EventSymbolReferenceFinder.cs | 17 ++- .../Finders/FieldSymbolReferenceFinder.cs | 15 +- .../Finders/LocalSymbolReferenceFinder.cs | 12 ++ .../Finders/ParameterSymbolReferenceFinder.cs | 20 +-- .../Finders/PropertySymbolReferenceFinder.cs | 6 +- .../Core/Portable/FindSymbols/SymbolFinder.cs | 12 ++ .../Services/SyntaxFacts/CSharpSyntaxFacts.cs | 19 +++ .../Core/Services/SyntaxFacts/ISyntaxFacts.cs | 9 +- .../SyntaxFacts/VisualBasicSyntaxFacts.vb | 15 ++ 12 files changed, 255 insertions(+), 39 deletions(-) diff --git a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.AnonymousTypeSymbols.vb b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.AnonymousTypeSymbols.vb index 35c5ea9c0eeaf..d777bdfb26016 100644 --- a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.AnonymousTypeSymbols.vb +++ b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.AnonymousTypeSymbols.vb @@ -182,7 +182,7 @@ class C void M(string {|Definition:a|}) { int length = [|a|].Length; - var r = new { [|a|], Length = 1 }; + var r = new { [|{|Definition:a|}|], Length = 1 }; r = new { $$[|a|], [|a|].Length }; r = new { [|a|] = string.Empty, Length = [|a|].Length }; } @@ -207,9 +207,9 @@ class C void M(string $${|Definition:a|}) { int length = [|a|].Length; - var r = new { [|a|], Length = 1 }; + var r = new { [|{|Definition:a|}|], Length = 1 }; r = new { [|a|], [|a|].Length }; - r = new { a = string.Empty, Length = [|a|].Length }; + r = new { [|a|] = string.Empty, Length = [|a|].Length }; } } ]]> @@ -232,9 +232,9 @@ class C void M(string a) { int length = a.Length; - var r = new { [|a|], Length = 1 }; - r = new { [|a|], a.Length }; - r = new { $$[|a|] = string.Empty, Length = a.Length }; + var r = new { a, Length = 1 }; + r = new { a, a.Length }; + r = new { $$[|{|Definition:a|}|] = string.Empty, Length = a.Length }; } } ]]> @@ -258,7 +258,7 @@ class C { int length = a.[|Length|]; var r = new { a, [|Length|] = 1 }; - r = new { a, a.$$[|Length|] }; + r = new { a, a.$$[|{|Definition:Length|}|] }; r = new { a, [|Length|] = a.[|Length|] }; } } @@ -283,8 +283,8 @@ class C { int length = a.Length; var r = new { a, [|Length|] = 1 }; - r = new { a, a.[|Length|] }; - r = new { a, $$[|Length|] = a.Length }; + r = new { a, a.Length }; + r = new { a, $$[|{|Definition:Length|}|] = a.Length }; } } ]]> diff --git a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.Tuples.vb b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.Tuples.vb index 1873c810f1d62..c731ae748607e 100644 --- a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.Tuples.vb +++ b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.Tuples.vb @@ -427,7 +427,7 @@ class Program static void Main() { int {|Definition:x|} = 4, y = 5; - var z = ($$[|x|], y); + var z = ($$[|{|Definition:x|}|], y); z = ([|x|]: 0, y: 1); z = ([|x|]: y, y: [|x|]); } @@ -454,7 +454,7 @@ class Program { int {|Definition:x|} = 4, y = 5; int z = 3; - switch ($$[|x|], y) + switch ($$[|{|Definition:x|}|], y) { case (1, 0): z += [|x|]; @@ -495,7 +495,7 @@ class C int {|Definition:x|}; int y; - ($$[|x|], y) = M(); + ($$[|{|Definition:x|}|], y) = M(); [|x|] = 0; } @@ -520,8 +520,8 @@ class C { void M(int {|Definition:left|}, int right) { - var r = ($$[|left|], right); - r = (right, [|left|]); + var r = ($$[|{|Definition:left|}|], right); + r = (right, [|{|Definition:left|}|]); } } ]]> @@ -546,7 +546,7 @@ class C void M(string a) { - var r = (a.Length, $$[|Property|]); + var r = (a.Length, $$[|{|Definition:Property|}|]); r = ([|Property|], [|Property|]: [|Property|]); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs index bccf81f86abe8..356356f7b457f 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs @@ -3,12 +3,15 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Operations; using Microsoft.CodeAnalysis.PooledObjects; @@ -902,5 +905,141 @@ protected static async Task> GetAllMatchingGlobalAliasNam return result.ToImmutableAndClear(); } + + protected static async ValueTask DiscoverImpliedSymbolsAsync( + TSymbol symbol, Solution solution, ArrayBuilder symbolBuilder, CancellationToken cancellationToken) + { + var name = symbol.Name; + + var symbolSet = PooledHashSet.GetInstance(); + var documentQueue = new ConcurrentQueue(); + Task? documentProcessingTask = null; + + foreach (var project in solution.Projects) + { + var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); + + if (compilation is null) + continue; + + var allDocuments = await project.GetAllRegularAndSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); + + foreach (var document in allDocuments) + { + if (!document.SupportsSemanticModel) + continue; + + var treeIndex = await document.GetSyntaxTreeIndexAsync(cancellationToken).ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); + + if (treeIndex is null) + continue; + + if (!treeIndex.ProbablyContainsIdentifier(name)) + continue; + + AskProcessDocumentForSymbols(document); + } + } + + if (documentProcessingTask is not null) + await documentProcessingTask.ConfigureAwait(false); + + symbolBuilder.AddRange(symbolSet); + symbolSet.Free(); + + void AskProcessDocumentForSymbols(Document document) + { + var trigger = documentQueue.IsEmpty; + documentQueue.Enqueue(document); + if (trigger) + { + documentProcessingTask = Task.Run(ProcessQueuedDocumentsAsync, cancellationToken); + } + } + + async ValueTask ProcessQueuedDocumentsAsync() + { + while (!documentQueue.IsEmpty) + { + var dequeued = documentQueue.TryDequeue(out var document); + if (!dequeued) + break; + + var semanticModel = await document!.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + if (cancellationToken.IsCancellationRequested) + return; + + Debug.Assert(semanticModel is not null); + + var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + if (root is null) + continue; + + var syntaxFacts = document.GetRequiredLanguageService(); + // Instead of getting all symbols from the hash set, invoke this semantic operation in every node we encounter + + var allDocumentSymbols = semanticModel.GetAllDeclaredSymbols(root, cancellationToken); + foreach (var descendantNode in root.DescendantNodes()) + { + var documentSymbol = semanticModel.GetDeclaredSymbol(descendantNode, cancellationToken); + if (documentSymbol is null) + continue; + + if (documentSymbol is not ITypeSymbol documentTypeSymbol) + continue; + + if (documentTypeSymbol.IsAnonymousType) + { + foreach (var property in documentTypeSymbol.GetValidAnonymousTypeProperties()) + { + if (symbol.Name != property.Name) + continue; + + var propertyDeclarationSyntax = await property.DeclaringSyntaxReferences.First()!.GetSyntaxAsync(cancellationToken).ConfigureAwait(false); + if (!syntaxFacts.IsInferredAnonymousObjectMemberDeclarator(propertyDeclarationSyntax)) + continue; + + var assignedExpression = syntaxFacts.GetAssignedExpressionForAnonymousTypeDeclarator(propertyDeclarationSyntax); + if (assignedExpression is null) + continue; + + var symbolInfo = semanticModel.GetSymbolInfo(assignedExpression, cancellationToken); + var nestedSymbol = symbolInfo.Symbol; + if (symbol.Equals(nestedSymbol)) + { + symbolSet.Add(property); + } + } + } + else if (documentTypeSymbol.IsTupleType) + { + foreach (var tupleMember in documentTypeSymbol.GetMembers(symbol.Name)) + { + if (tupleMember is not IFieldSymbol) + continue; + + var tupleArgumentDeclarationSyntax = await tupleMember.DeclaringSyntaxReferences.First()!.GetSyntaxAsync(cancellationToken).ConfigureAwait(false); + if (!syntaxFacts.IsInferredTupleMemberDeclarator(tupleArgumentDeclarationSyntax)) + continue; + + var assignedExpression = syntaxFacts.GetAssignedExpressionForTupleMemberDeclarator(tupleArgumentDeclarationSyntax); + if (assignedExpression is null) + continue; + + var symbolInfo = semanticModel.GetSymbolInfo(assignedExpression, cancellationToken); + var nestedSymbol = symbolInfo.Symbol; + if (symbol.Equals(nestedSymbol)) + { + symbolSet.Add(tupleMember); + } + } + } + } + } + } + } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/EventSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/EventSymbolReferenceFinder.cs index 5158e9be2a8be..943039564f018 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/EventSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/EventSymbolReferenceFinder.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.PooledObjects; namespace Microsoft.CodeAnalysis.FindSymbols.Finders { @@ -15,22 +16,26 @@ internal class EventSymbolReferenceFinder : AbstractMethodOrPropertyOrEventSymbo protected override bool CanFind(IEventSymbol symbol) => true; - protected sealed override ValueTask> DetermineCascadedSymbolsAsync( + protected sealed override async ValueTask> DetermineCascadedSymbolsAsync( IEventSymbol symbol, Solution solution, FindReferencesSearchOptions options, CancellationToken cancellationToken) { + using var _ = ArrayBuilder.GetInstance(out var symbols); + + await DiscoverImpliedSymbolsAsync(symbol, solution, symbols, cancellationToken).ConfigureAwait(false); + var backingFields = symbol.ContainingType.GetMembers() .OfType() - .Where(f => symbol.Equals(f.AssociatedSymbol)) - .ToImmutableArray(); + .Where(f => symbol.Equals(f.AssociatedSymbol)); + symbols.AddRange(backingFields); var associatedNamedTypes = symbol.ContainingType.GetTypeMembers() - .WhereAsArray(n => symbol.Equals(n.AssociatedSymbol)) - .CastArray(); + .Where(n => symbol.Equals(n.AssociatedSymbol)); + symbols.AddRange(associatedNamedTypes); - return new(backingFields.Concat(associatedNamedTypes)); + return symbols.ToImmutable(); } protected sealed override async Task> DetermineDocumentsToSearchAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs index 709b4619390ae..4d7583a186f5d 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs @@ -6,7 +6,7 @@ using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; -using Roslyn.Utilities; +using Microsoft.CodeAnalysis.PooledObjects; namespace Microsoft.CodeAnalysis.FindSymbols.Finders { @@ -15,15 +15,20 @@ internal sealed class FieldSymbolReferenceFinder : AbstractReferenceFinder true; - protected override ValueTask> DetermineCascadedSymbolsAsync( + protected override async ValueTask> DetermineCascadedSymbolsAsync( IFieldSymbol symbol, Solution solution, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - return symbol.AssociatedSymbol != null - ? new(ImmutableArray.Create(symbol.AssociatedSymbol)) - : new(ImmutableArray.Empty); + using var _ = ArrayBuilder.GetInstance(out var symbols); + + await DiscoverImpliedSymbolsAsync(symbol, solution, symbols, cancellationToken).ConfigureAwait(false); + + if (symbol.AssociatedSymbol != null) + symbols.Add(symbol.AssociatedSymbol); + + return symbols.ToImmutable(); } protected override async Task> DetermineDocumentsToSearchAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/LocalSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/LocalSymbolReferenceFinder.cs index 07b3d35c90c37..7377ac7697d98 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/LocalSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/LocalSymbolReferenceFinder.cs @@ -2,11 +2,23 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.PooledObjects; + namespace Microsoft.CodeAnalysis.FindSymbols.Finders { internal sealed class LocalSymbolReferenceFinder : AbstractMemberScopedReferenceFinder { protected override bool TokensMatch(FindReferencesDocumentState state, SyntaxToken token, string name) => IdentifiersMatch(state.SyntaxFacts, name, token); + + protected override async ValueTask> DetermineCascadedSymbolsAsync(ILocalSymbol symbol, Solution solution, FindReferencesSearchOptions options, CancellationToken cancellationToken) + { + using var _ = ArrayBuilder.GetInstance(out var symbols); + await DiscoverImpliedSymbolsAsync(symbol, solution, symbols, cancellationToken).ConfigureAwait(false); + return symbols.ToImmutable(); + } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs index 315d1ef38314e..5b71a36b596e3 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs @@ -51,17 +51,19 @@ protected override async ValueTask> DetermineCascadedSym FindReferencesSearchOptions options, CancellationToken cancellationToken) { - if (parameter.IsThis) - return ImmutableArray.Empty; - using var _ = ArrayBuilder.GetInstance(out var symbols); - await CascadeBetweenAnonymousFunctionParametersAsync(solution, parameter, symbols, cancellationToken).ConfigureAwait(false); - CascadeBetweenPropertyAndAccessorParameters(parameter, symbols); - CascadeBetweenDelegateMethodParameters(parameter, symbols); - CascadeBetweenPartialMethodParameters(parameter, symbols); - CascadeBetweenPrimaryConstructorParameterAndProperties(parameter, symbols, cancellationToken); - CascadeBetweenAnonymousDelegateParameters(parameter, symbols); + await DiscoverImpliedSymbolsAsync(parameter, solution, symbols, cancellationToken).ConfigureAwait(false); + + if (!parameter.IsThis) + { + await CascadeBetweenAnonymousFunctionParametersAsync(solution, parameter, symbols, cancellationToken).ConfigureAwait(false); + CascadeBetweenPropertyAndAccessorParameters(parameter, symbols); + CascadeBetweenDelegateMethodParameters(parameter, symbols); + CascadeBetweenPartialMethodParameters(parameter, symbols); + CascadeBetweenPrimaryConstructorParameterAndProperties(parameter, symbols, cancellationToken); + CascadeBetweenAnonymousDelegateParameters(parameter, symbols); + } return symbols.ToImmutable(); } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs index 3ad79a3c5bfc8..508e892c4ba5c 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs @@ -21,7 +21,7 @@ internal sealed class PropertySymbolReferenceFinder : AbstractMethodOrPropertyOr protected override bool CanFind(IPropertySymbol symbol) => true; - protected override ValueTask> DetermineCascadedSymbolsAsync( + protected override async ValueTask> DetermineCascadedSymbolsAsync( IPropertySymbol symbol, Solution solution, FindReferencesSearchOptions options, @@ -29,11 +29,13 @@ protected override ValueTask> DetermineCascadedSymbolsAs { using var _ = ArrayBuilder.GetInstance(out var result); + await DiscoverImpliedSymbolsAsync(symbol, solution, result, cancellationToken).ConfigureAwait(false); + CascadeToBackingFields(symbol, result); CascadeToAccessors(symbol, result); CascadeToPrimaryConstructorParameters(symbol, result, cancellationToken); - return new(result.ToImmutable()); + return result.ToImmutable(); } private static void CascadeToBackingFields(IPropertySymbol symbol, ArrayBuilder result) diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.cs index d7165ae6d80a2..aca61c9564906 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.cs @@ -114,6 +114,18 @@ public static async Task FindSymbolAtPositionAsync( return await FindSymbolAtPositionAsync(semanticModel, position, document.Project.Solution.Services, cancellationToken).ConfigureAwait(false); } + internal static async Task GetSemanticInfoAtPositionAsync( + Document document, + int position, + CancellationToken cancellationToken = default) + { + if (document is null) + throw new ArgumentNullException(nameof(document)); + + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + return await GetSemanticInfoAtPositionAsync(semanticModel, position, document.Project.Solution.Services, cancellationToken).ConfigureAwait(false); + } + /// /// Finds the definition symbol declared in source code for a corresponding reference symbol. /// Returns null if no such symbol can be found in the specified solution. diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs index 62aabbdd8d05f..556cb81ddc2d6 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs @@ -1167,6 +1167,25 @@ public bool IsInferredAnonymousObjectMemberDeclarator([NotNullWhen(true)] Syntax => node is AnonymousObjectMemberDeclaratorSyntax anonObject && anonObject.NameEquals == null; + public bool IsInferredTupleMemberDeclarator([NotNullWhen(true)] SyntaxNode? node) + => node is not null + && (node is ArgumentSyntax { Parent: TupleExpressionSyntax, NameColon: null } + || IsInferredTupleMemberDeclarator(node.Parent)); + + public SyntaxNode? GetAssignedExpressionForAnonymousTypeDeclarator(SyntaxNode? node) + => (node as AnonymousObjectMemberDeclaratorSyntax)?.Expression; + + public SyntaxNode? GetAssignedExpressionForTupleMemberDeclarator(SyntaxNode? node) + { + if (node is ExpressionSyntax) + return node; + + if (node is ArgumentSyntax argument) + return GetExpressionOfArgument(argument); + + return null; + } + public bool IsOperandOfIncrementExpression([NotNullWhen(true)] SyntaxNode? node) => node?.Parent?.Kind() is SyntaxKind.PostIncrementExpression or SyntaxKind.PreIncrementExpression; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs index c71f1225a6df9..6b06caf8f4133 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs @@ -220,6 +220,11 @@ void GetPartsOfTupleExpression(SyntaxNode node, SyntaxNode GetRightHandSideOfAssignment(SyntaxNode node); bool IsInferredAnonymousObjectMemberDeclarator([NotNullWhen(true)] SyntaxNode? node); + bool IsInferredTupleMemberDeclarator([NotNullWhen(true)] SyntaxNode? node); + + SyntaxNode? GetAssignedExpressionForAnonymousTypeDeclarator([NotNullWhen(true)] SyntaxNode? node); + SyntaxNode? GetAssignedExpressionForTupleMemberDeclarator([NotNullWhen(true)] SyntaxNode? node); + bool IsOperandOfIncrementExpression([NotNullWhen(true)] SyntaxNode? node); bool IsOperandOfIncrementOrDecrementExpression([NotNullWhen(true)] SyntaxNode? node); @@ -234,8 +239,8 @@ void GetPartsOfTupleExpression(SyntaxNode node, /// following forms: /// 1) new With { .a = 1, .b = .a .a refers to the anonymous type /// 2) With obj : .m .m refers to the obj type - /// 3) new T() With { .a = 1, .b = .a 'a refers to the T type - /// If `allowImplicitTarget` is set to true, the returned node will be set to approperiate node, otherwise, it will return null. + /// 3) new T() With { .a = 1, .b = .a .a refers to the T type + /// If `allowImplicitTarget` is set to true, the returned node will be set to appropriate node, otherwise, it will return null. /// This parameter has no affect on C# node. /// SyntaxNode? GetLeftSideOfDot(SyntaxNode? node, bool allowImplicitTarget = false); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb index e39797f051f14..2d908411c0660 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb @@ -1197,6 +1197,21 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.LanguageService Return node.IsKind(SyntaxKind.InferredFieldInitializer) End Function + Public Function IsInferredTupleMemberDeclarator(node As SyntaxNode) As Boolean Implements ISyntaxFacts.IsInferredTupleMemberDeclarator + ' TODO: Evaluate if we need any further processing + Return node.IsKind(SyntaxKind.TypedTupleElement) + End Function + + Public Function GetAssignedExpressionForAnonymousTypeDeclarator(node As SyntaxNode) As SyntaxNode Implements ISyntaxFacts.GetAssignedExpressionForAnonymousTypeDeclarator + ' TODO: Implement + Return Nothing + End Function + + Public Function GetAssignedExpressionForTupleMemberDeclarator(node As SyntaxNode) As SyntaxNode Implements ISyntaxFacts.GetAssignedExpressionForTupleMemberDeclarator + ' TODO: Implement + Return Nothing + End Function + Public Function IsOperandOfIncrementExpression(node As SyntaxNode) As Boolean Implements ISyntaxFacts.IsOperandOfIncrementExpression Return False End Function From 6c3cfe79c39e8f98b71f95481efab017ddd478ea Mon Sep 17 00:00:00 2001 From: Rekkonnect Date: Thu, 28 Sep 2023 12:39:51 +0300 Subject: [PATCH 08/12] Adjust test side effect --- .../RemoveUnusedValueAssignmentTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Analyzers/CSharp/Tests/RemoveUnusedParametersAndValues/RemoveUnusedValueAssignmentTests.cs b/src/Analyzers/CSharp/Tests/RemoveUnusedParametersAndValues/RemoveUnusedValueAssignmentTests.cs index 6689d880a521b..af1ddec6e9806 100644 --- a/src/Analyzers/CSharp/Tests/RemoveUnusedParametersAndValues/RemoveUnusedValueAssignmentTests.cs +++ b/src/Analyzers/CSharp/Tests/RemoveUnusedParametersAndValues/RemoveUnusedValueAssignmentTests.cs @@ -1641,8 +1641,8 @@ C M(C y, C z) {{ C M(C y, C z) {{ - {fix} = M2(); C x; + {fix} = M2(); (x, y) = (y ?? z, z); return x; }} From 51b9253933546890dad78d4dbf17e046126f6b1a Mon Sep 17 00:00:00 2001 From: Rekkonnect Date: Thu, 28 Sep 2023 15:04:01 +0300 Subject: [PATCH 09/12] Fix property with methods replacer for anon type props --- ...opertyWithMethodsCodeRefactoringProvider.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Features/Core/Portable/ReplacePropertyWithMethods/ReplacePropertyWithMethodsCodeRefactoringProvider.cs b/src/Features/Core/Portable/ReplacePropertyWithMethods/ReplacePropertyWithMethodsCodeRefactoringProvider.cs index 9c8edcdf5771f..7749d0b35fb68 100644 --- a/src/Features/Core/Portable/ReplacePropertyWithMethods/ReplacePropertyWithMethodsCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/ReplacePropertyWithMethods/ReplacePropertyWithMethodsCodeRefactoringProvider.cs @@ -94,7 +94,7 @@ private async Task ReplacePropertyWithMethodsAsync( var definitionToBackingField = CreateDefinitionToBackingFieldMap(propertyReferences); var q = from r in propertyReferences - where r.Definition is IPropertySymbol + where IsReplaceablePropertyReference(r, out _) from loc in r.Locations select (property: (IPropertySymbol)r.Definition, location: loc); @@ -123,7 +123,7 @@ from loc in r.Locations foreach (var reference in propertyReferences) { - if (reference.Definition is IPropertySymbol property) + if (IsReplaceablePropertyReference(reference, out var property)) { var backingField = GetBackingField(property); definitionToBackingField[property] = backingField; @@ -133,6 +133,14 @@ from loc in r.Locations return definitionToBackingField.ToImmutable(); } + private static bool IsReplaceablePropertyReference(ReferencedSymbol reference, [NotNullWhen(true)] out IPropertySymbol? property) + { + property = null; + if (reference.Definition is IPropertySymbol { ContainingType.IsAnonymousType: false } prop) + property = prop; + return property is not null; + } + private static bool HasAnyMatchingGetOrSetMethods(IPropertySymbol property, string name) { return HasAnyMatchingGetMethods(property, name) || @@ -320,10 +328,12 @@ private static async Task> GetDefin var result = new MultiDictionary(); foreach (var referencedSymbol in referencedSymbols) { + if (!IsReplaceablePropertyReference(referencedSymbol, out var definition)) + continue; + cancellationToken.ThrowIfCancellationRequested(); - var definition = referencedSymbol.Definition as IPropertySymbol; - if (definition?.DeclaringSyntaxReferences.Length > 0) + if (definition.DeclaringSyntaxReferences.Length > 0) { var syntax = await definition.DeclaringSyntaxReferences[0].GetSyntaxAsync(cancellationToken).ConfigureAwait(false); if (syntax != null) From 38309f1418c98ccfe263055bf58417aac887fe83 Mon Sep 17 00:00:00 2001 From: Rekkonnect <8298332+Rekkonnect@users.noreply.github.com> Date: Wed, 28 Aug 2024 20:29:16 +0300 Subject: [PATCH 10/12] Fix outdated code --- .../FindReferences/Finders/AbstractReferenceFinder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs index dce5ad5c248b4..d0e50cb5b5e4f 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs @@ -900,10 +900,10 @@ protected static async ValueTask DiscoverImpliedSymbolsAsync( if (compilation is null) continue; - var allDocuments = await project.GetAllRegularAndSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false); + var allDocuments = project.GetAllRegularAndSourceGeneratedDocumentsAsync(cancellationToken); cancellationToken.ThrowIfCancellationRequested(); - foreach (var document in allDocuments) + await foreach (var document in allDocuments) { if (!document.SupportsSemanticModel) continue; From 1c3dfd1e333fdb5dfde24182ec09ea422747a1d7 Mon Sep 17 00:00:00 2001 From: Rekkonnect <8298332+Rekkonnect@users.noreply.github.com> Date: Wed, 28 Aug 2024 23:15:12 +0300 Subject: [PATCH 11/12] Fix formatting --- ...pertyWithMethodsCodeRefactoringProvider.cs | 74 +++++++++---------- .../Finders/LocalSymbolReferenceFinder.cs | 2 +- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/src/Features/Core/Portable/ReplacePropertyWithMethods/ReplacePropertyWithMethodsCodeRefactoringProvider.cs b/src/Features/Core/Portable/ReplacePropertyWithMethods/ReplacePropertyWithMethodsCodeRefactoringProvider.cs index 339faf7923fc7..c6c18739bb1ec 100644 --- a/src/Features/Core/Portable/ReplacePropertyWithMethods/ReplacePropertyWithMethodsCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/ReplacePropertyWithMethods/ReplacePropertyWithMethodsCodeRefactoringProvider.cs @@ -92,10 +92,10 @@ private async Task ReplacePropertyWithMethodsAsync( var definitionWarning = GetDefinitionIssues(propertyReferences); var definitionToBackingField = CreateDefinitionToBackingFieldMap(propertyReferences); - var q = from r in propertyReferences - where IsReplaceablePropertyReference(r, out _) - from loc in r.Locations - select (property: (IPropertySymbol)r.Definition, location: loc); + var q = from r in propertyReferences + where IsReplaceablePropertyReference(r, out _) + from loc in r.Locations + select (property: (IPropertySymbol)r.Definition, location: loc); var referencesByDocument = q.ToLookup(t => t.location.Document); @@ -120,25 +120,25 @@ from loc in r.Locations { var definitionToBackingField = ImmutableDictionary.CreateBuilder(SymbolEquivalenceComparer.Instance); - foreach (var reference in propertyReferences) + foreach (var reference in propertyReferences) + { + if (IsReplaceablePropertyReference(reference, out var property)) { - if (IsReplaceablePropertyReference(reference, out var property)) - { - var backingField = GetBackingField(property); - definitionToBackingField[property] = backingField; - } + var backingField = GetBackingField(property); + definitionToBackingField[property] = backingField; } - - return definitionToBackingField.ToImmutable(); } - private static bool IsReplaceablePropertyReference(ReferencedSymbol reference, [NotNullWhen(true)] out IPropertySymbol? property) - { - property = null; - if (reference.Definition is IPropertySymbol { ContainingType.IsAnonymousType: false } prop) - property = prop; - return property is not null; - } + return definitionToBackingField.ToImmutable(); + } + + private static bool IsReplaceablePropertyReference(ReferencedSymbol reference, [NotNullWhen(true)] out IPropertySymbol? property) + { + property = null; + if (reference.Definition is IPropertySymbol { ContainingType.IsAnonymousType: false } prop) + property = prop; + return property is not null; + } private static bool HasAnyMatchingGetOrSetMethods(IPropertySymbol property, string name) { @@ -318,32 +318,32 @@ private static async Task ReplaceDefinitionsWithMethodsAsync( return updatedSolution; } - private static async Task> GetDefinitionsByDocumentIdAsync( - Solution originalSolution, - IEnumerable referencedSymbols, - CancellationToken cancellationToken) + private static async Task> GetDefinitionsByDocumentIdAsync( + Solution originalSolution, + IEnumerable referencedSymbols, + CancellationToken cancellationToken) + { + var result = new MultiDictionary(); + foreach (var referencedSymbol in referencedSymbols) { - var result = new MultiDictionary(); - foreach (var referencedSymbol in referencedSymbols) - { - if (!IsReplaceablePropertyReference(referencedSymbol, out var definition)) - continue; + if (!IsReplaceablePropertyReference(referencedSymbol, out var definition)) + continue; - cancellationToken.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); - if (definition.DeclaringSyntaxReferences.Length > 0) + if (definition.DeclaringSyntaxReferences.Length > 0) + { + var syntax = await definition.DeclaringSyntaxReferences[0].GetSyntaxAsync(cancellationToken).ConfigureAwait(false); + if (syntax != null) { - var syntax = await definition.DeclaringSyntaxReferences[0].GetSyntaxAsync(cancellationToken).ConfigureAwait(false); - if (syntax != null) + var document = originalSolution.GetDocument(syntax.SyntaxTree); + if (document != null) { - var document = originalSolution.GetDocument(syntax.SyntaxTree); - if (document != null) - { - result.Add(document.Id, definition); - } + result.Add(document.Id, definition); } } } + } return result; } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/LocalSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/LocalSymbolReferenceFinder.cs index 8f0aeb692dc57..1fc002de5e1d9 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/LocalSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/LocalSymbolReferenceFinder.cs @@ -13,7 +13,7 @@ internal sealed class LocalSymbolReferenceFinder : AbstractMemberScopedReference { protected override bool TokensMatch(FindReferencesDocumentState state, SyntaxToken token, string name) => IdentifiersMatch(state.SyntaxFacts, name, token); - + protected override async ValueTask> DetermineCascadedSymbolsAsync(ILocalSymbol symbol, Solution solution, FindReferencesSearchOptions options, CancellationToken cancellationToken) { using var _ = ArrayBuilder.GetInstance(out var symbols); From 70754f323591fd1b178497e0378141fbf35c1190 Mon Sep 17 00:00:00 2001 From: Rekkonnect <8298332+Rekkonnect@users.noreply.github.com> Date: Wed, 28 Aug 2024 23:19:34 +0300 Subject: [PATCH 12/12] Fix broken test --- .../RemoveUnusedValueAssignmentTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Analyzers/CSharp/Tests/RemoveUnusedParametersAndValues/RemoveUnusedValueAssignmentTests.cs b/src/Analyzers/CSharp/Tests/RemoveUnusedParametersAndValues/RemoveUnusedValueAssignmentTests.cs index c80e2492244f8..cd926b3fe6959 100644 --- a/src/Analyzers/CSharp/Tests/RemoveUnusedParametersAndValues/RemoveUnusedValueAssignmentTests.cs +++ b/src/Analyzers/CSharp/Tests/RemoveUnusedParametersAndValues/RemoveUnusedValueAssignmentTests.cs @@ -1791,8 +1791,8 @@ class C { C M(C y, C z) { - {{fix}} = M2(); C x; + {{fix}} = M2(); (x, y) = (y ?? z, z); return x; }