diff --git a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.ParameterSymbol.vb b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.ParameterSymbol.vb index 09db7620f9455..3d0823dfdff91 100644 --- a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.ParameterSymbol.vb +++ b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.ParameterSymbol.vb @@ -727,5 +727,173 @@ end class Await TestAPIAndFeature(input, kind, host) End Function + + + Public Async Function TestRecordParameter1(kind As TestKind, host As TestHost) As Task + Dim input = + + + +using System; + +record Goo(int x, int {|Definition:$$y|}) +{ + +} + +class P +{ + static void Main() + { + var f = new Goo(0, [|y|]: 1); + Console.WriteLine(f.[|y|]); + } +} + + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + Public Async Function TestRecordParameter2(kind As TestKind, host As TestHost) As Task + Dim input = + + + +using System; + +record Goo(int x, int {|Definition:y|}) +{ + +} + +class P +{ + static void Main() + { + var f = new Goo(0, [|$$y|]: 1); + Console.WriteLine(f.[|y|]); + } +} + + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + Public Async Function TestRecordParameterWithExplicitProperty1(kind As TestKind, host As TestHost) As Task + Dim input = + + + +using System; + +record Goo(int x, int {|Definition:$$y|}) +{ + public int y { get; } = [|y|]; +} + +class P +{ + static void Main() + { + var f = new Goo(0, [|y|]: 1); + Console.WriteLine(f.y); + } +} + + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + Public Async Function TestRecordParameterWithExplicitProperty2(kind As TestKind, host As TestHost) As Task + Dim input = + + + +using System; + +record Goo(int x, int {|Definition:y|}) +{ + public int y { get; } = [|$$y|]; +} + +class P +{ + static void Main() + { + var f = new Goo(0, [|y|]: 1); + Console.WriteLine(f.y); + } +} + + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + Public Async Function TestRecordParameterWithExplicitProperty3(kind As TestKind, host As TestHost) As Task + Dim input = + + + +using System; + +record Goo(int x, int {|Definition:y|}) +{ + public int y { get; } = [|y|]; +} + +class P +{ + static void Main() + { + var f = new Goo(0, [|$$y|]: 1); + Console.WriteLine(f.y); + } +} + + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + Public Async Function TestRecordParameterWithNotPrimaryConstructor(kind As TestKind, host As TestHost) As Task + Dim input = + + + +using System; + +record Goo(int x, int y) +{ + public Goo(int {|Definition:$$y|}) { } +} + +class P +{ + static void Main() + { + var f = new Goo([|y|]: 1); + Console.WriteLine(f.y); + } +} + + + + + Await TestAPIAndFeature(input, kind, host) + End Function End Class End Namespace diff --git a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.PropertySymbols.vb b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.PropertySymbols.vb index 9faa9028e087d..8294de72ce16d 100644 --- a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.PropertySymbols.vb +++ b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.PropertySymbols.vb @@ -1214,5 +1214,207 @@ class C2_2 : I2 Await TestAPI(input, host) End Function + + + Public Async Function TestRecordProperty1(kind As TestKind, host As TestHost) As Task + Dim input = + + + +using System; + +record Goo(int x, int {|Definition:$$y|}) +{ + +} + +class P +{ + static void Main() + { + var f = new Goo(0, [|y|]: 1); + Console.WriteLine(f.[|y|]); + } +} + + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + Public Async Function TestRecordProperty2(kind As TestKind, host As TestHost) As Task + Dim input = + + + +using System; + +record Goo(int x, int {|Definition:y|}) +{ + +} + +class P +{ + static void Main() + { + var f = new Goo(0, [|$$y|]: 1); + Console.WriteLine(f.[|y|]); + } +} + + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + Public Async Function TestRecordProperty3(kind As TestKind, host As TestHost) As Task + Dim input = + + + +using System; + +record Goo(int x, int {|Definition:y|}) +{ + +} + +class P +{ + static void Main() + { + var f = new Goo(0, [|y|]: 1); + Console.WriteLine(f.[|$$y|]); + } +} + + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + Public Async Function TestRecordPropertyWithExplicitProperty1(kind As TestKind, host As TestHost) As Task + Dim input = + + + +using System; + +record Goo(int x, int y) +{ + public int {|Definition:$$y|} { get; } = y; +} + +class P +{ + static void Main() + { + var f = new Goo(0, y: 1); + Console.WriteLine(f.[|y|]); + } +} + + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + Public Async Function TestRecordPropertyWithExplicitProperty2(kind As TestKind, host As TestHost) As Task + Dim input = + + + +using System; + +record Goo(int x, int y) +{ + public int {|Definition:y|} { get; } = y; +} + +class P +{ + static void Main() + { + var f = new Goo(0, y: 1); + Console.WriteLine(f.[|$$y|]); + } +} + + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + Public Async Function TestRecordPropertyWithNotPrimaryConstructor1(kind As TestKind, host As TestHost) As Task + Dim input = + + + +using System; + +record Goo(int x, int y) +{ + public Goo(int {|Definition:$$y|}) + { + this.y = [|y|]; + } +} + +class P +{ + static void Main() + { + var f = new Goo([|y|]: 1); + Console.WriteLine(f.y); + } +} + + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + Public Async Function TestRecordPropertyWithNotPrimaryConstructor2(kind As TestKind, host As TestHost) As Task + Dim input = + + + +using System; + +record Goo(int x, int {|Definition:$$y|}) +{ + public Goo(int y) + { + this.[|y|] = y; + } +} + +class P +{ + static void Main() + { + var f = new Goo(y: 1); + Console.WriteLine(f.[|y|]); + } +} + + + + + Await TestAPIAndFeature(input, kind, host) + End Function End Class End Namespace diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs index 69e78cc5ac64f..bc11bafee15fd 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs @@ -102,20 +102,25 @@ protected override async Task> DetermineCascadedSymbolsA CancellationToken cancellationToken) { if (parameter.IsThis) - { return ImmutableArray.Empty; - } - using var _1 = ArrayBuilder.GetInstance(out var symbols); + 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); return symbols.ToImmutable(); } + private static void CascadeBetweenPrimaryConstructorParameterAndProperties( + IParameterSymbol parameter, ArrayBuilder symbols, CancellationToken cancellationToken) + { + symbols.AddIfNotNull(parameter.GetAssociatedSynthesizedRecordProperty(cancellationToken)); + } + private static async Task CascadeBetweenAnonymousFunctionParametersAsync( Solution solution, IParameterSymbol parameter, diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs index db1694b7514c2..9fc819d149526 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs @@ -29,20 +29,68 @@ protected override Task> DetermineCascadedSymbolsAsync( FindReferencesSearchOptions options, CancellationToken cancellationToken) { - var backingFields = symbol.ContainingType.GetMembers() - .OfType() - .Where(f => symbol.Equals(f.AssociatedSymbol)) - .ToImmutableArray(); + using var _ = ArrayBuilder.GetInstance(out var result); - var result = backingFields; + CascadeToBackingFields(symbol, result); + CascadeToAccessors(symbol, result); + CascadeToPrimaryConstructorParameters(symbol, result, cancellationToken); - if (symbol.GetMethod != null) - result = result.Add(symbol.GetMethod); + return Task.FromResult(result.ToImmutable()); + } + + private static void CascadeToBackingFields(IPropertySymbol symbol, ArrayBuilder result) + { + foreach (var member in symbol.ContainingType.GetMembers()) + { + if (member is IFieldSymbol field && + symbol.Equals(field.AssociatedSymbol)) + { + result.Add(field); + } + } + } - if (symbol.SetMethod != null) - result = result.Add(symbol.SetMethod); + private static void CascadeToAccessors(IPropertySymbol symbol, ArrayBuilder result) + { + result.AddIfNotNull(symbol.GetMethod); + result.AddIfNotNull(symbol.SetMethod); + } - return Task.FromResult(result); + private static void CascadeToPrimaryConstructorParameters(IPropertySymbol property, ArrayBuilder result, CancellationToken cancellationToken) + { + if (property is + { + IsStatic: false, + DeclaringSyntaxReferences.Length: > 0, + ContainingType: + { + IsRecord: true, + DeclaringSyntaxReferences.Length: > 0, + } containingType, + }) + { + // OK, we have a property in a record. See if we can find a primary constructor that has a parameter that synthesized this + var containingTypeSyntaxes = containingType.DeclaringSyntaxReferences.SelectAsArray(r => r.GetSyntax(cancellationToken)); + foreach (var constructor in containingType.Constructors) + { + if (constructor.DeclaringSyntaxReferences.Length > 0) + { + var constructorSyntax = constructor.DeclaringSyntaxReferences[0].GetSyntax(cancellationToken); + if (containingTypeSyntaxes.Contains(constructorSyntax)) + { + // OK found the primary construct. Try to find a parameter that corresponds to this property. + foreach (var parameter in constructor.Parameters) + { + if (property.Name.Equals(parameter.Name) && + property.Equals(parameter.GetAssociatedSynthesizedRecordProperty(cancellationToken))) + { + result.Add(parameter); + } + } + } + } + } + } } protected sealed override async Task> DetermineDocumentsToSearchAsync( diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/IParameterSymbolExtensions.cs b/src/Workspaces/Core/Portable/Shared/Extensions/IParameterSymbolExtensions.cs index 7d0f451f2f2e7..80b98652bf77f 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/IParameterSymbolExtensions.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/IParameterSymbolExtensions.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using System.Threading; using Microsoft.CodeAnalysis.CodeGeneration; using Microsoft.CodeAnalysis.PooledObjects; @@ -52,5 +53,38 @@ public static ImmutableArray RenameParameters(this IList 0, + ContainingSymbol: IMethodSymbol + { + MethodKind: MethodKind.Constructor, + DeclaringSyntaxReferences.Length: > 0, + ContainingType: { IsRecord: true } containingType, + } constructor, + }) + { + // ok, we have a record constructor. This might be the primary constructor or not. + var parameterSyntax = parameter.DeclaringSyntaxReferences[0].GetSyntax(cancellationToken); + var constructorSyntax = constructor.DeclaringSyntaxReferences[0].GetSyntax(cancellationToken); + if (containingType.DeclaringSyntaxReferences.Any(r => r.GetSyntax(cancellationToken) == constructorSyntax)) + { + // this was a primary constructor. see if we can map this parameter to a corresponding synthesized property + foreach (var member in containingType.GetMembers(parameter.Name)) + { + if (member is IPropertySymbol { DeclaringSyntaxReferences.Length: > 0 } property && + property.DeclaringSyntaxReferences[0].GetSyntax(cancellationToken) == parameterSyntax) + { + return property; + } + } + } + } + + return null; + } } }