diff --git a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.ExtensionSymbols.vb b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.ExtensionSymbols.vb index 195ad461e2fec..5fb1e547c049e 100644 --- a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.ExtensionSymbols.vb +++ b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.ExtensionSymbols.vb @@ -155,5 +155,538 @@ public static class MyExtension Await TestAPIAndFeature(input, kind, host) End Function + + + + Public Async Function FindReferences_ExtensionBlockMethod(kind As TestKind, host As TestHost) As Task + ' Find references identifies both kinds of calls sites to an extension method: + ' 1) extension invocation `42.M()` + ' 2) static implementation method invocation `E.M(42)` + Dim input = + + + +class C +{ + void Test() + { + 42.[|M|](); + E.[|M|](43); + } +} + +public static class E +{ + extension(int i) + { + public void {|Definition:$$M|}() { } + } +} + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + + Public Async Function FindReferences_ExtensionBlockMethod_Generic(kind As TestKind, host As TestHost) As Task + Dim input = + + + +class C +{ + void Test() + { + 42.[|M|](""); + E.[|M|](43, ""); + } +} + +public static class E +{ + extension<T>(T t) + { + public void {|Definition:$$M|}<U>(U u) { } + } +} + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + + Public Async Function FindReferences_ExtensionBlockMethod_Static(kind As TestKind, host As TestHost) As Task + Dim input = + + + +class C +{ + void Test() + { + int.[|M|](); + E.[|M|](); + } +} + +public static class E +{ + extension(int) + { + public static void {|Definition:$$M|}() { } + } +} + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + + Public Async Function FindReferences_ExtensionBlockProperty(kind As TestKind, host As TestHost) As Task + ' Find references identifies both kinds of usages of an extension property: + ' 1) extension access of different kinds (member access like `42.P`, property pattern, object initializer) + ' 2) static implementation method invocation `E.get_P(42)`/`E.set_P(42, value)` + Dim input = + + + +class C +{ + void Test() + { + var c = new C(); + _ = c.[|P|]; + c.[|P|] = 1; + + E.[|get_P|](c); + E.[|set_P|](c, 1); + + _ = c is { [|P|]: 1 }; + _ = new C() { [|P|] = 1 }; + } +} + +public static class E +{ + extension(C c) + { + public int {|Definition:$$P|} { {|Definition:get|} => i; {|Definition:set|} { } } + } +} + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + + Public Async Function FindReferences_ExtensionBlockProperty_Generic(kind As TestKind, host As TestHost) As Task + Dim input = + + + +class C +{ + void Test() + { + var c = new C(); + _ = c.[|P|]; + c.[|P|] = 1; + + E.[|get_P|](c); + E.[|set_P|](c, 1); + + _ = c is { [|P|]: 1 }; + _ = new C() { [|P|] = 1 }; + } +} + +public static class E +{ + extension<T>(T t) + { + public int {|Definition:$$P|} { {|Definition:get|} => i; {|Definition:set|} { } } + } +} + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + + Public Async Function FindReferences_ExtensionBlockProperty_FromAccess(kind As TestKind, host As TestHost) As Task + Dim input = + + + +class C +{ + void Test() + { + var c = new C(); + _ = c.[|$$P|]; + c.[|P|] = 1; + + E.[|get_P|](c); + E.[|set_P|](c, 1); + + _ = c is { [|P|]: 1 }; + _ = new C() { [|P|] = 1 }; + } +} + +public static class E +{ + extension(C c) + { + public int {|Definition:P|} { {|Definition:get|} => i; {|Definition:set|} { } } + } +} + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + + Public Async Function FindReferences_ExtensionBlockProperty_FromAccess_MultiFile(kind As TestKind, host As TestHost) As Task + Dim input = + + + +class C +{ + void Test() + { + E.[|get_P|](c); + } +} + + +class C2 +{ + void Test() + { + var c = new C(); + _ = c.[|$$P|]; + } +} + + +class C3 +{ + void Test() + { + var c = new C(); + c.[|P|] = 1; + } +} + + +class C4 +{ + void Test() + { + var c = new C(); + E.[|get_P|](c); + } +} + + +class C5 +{ + void Test() + { + var c = new C(); + E.[|set_P|](c, 1); + } +} + + +class C6 +{ + void Test() + { + var c = new C(); + _ = c is { [|P|]: 1 }; + } +} + + +class C7 +{ + void Test() + { + _ = new C() { [|P|] = 1 }; + } +} + + +public static class E +{ + extension(C c) + { + public int {|Definition:P|} { {|Definition:get|} => i; {|Definition:set|} { } } + } +} + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + + Public Async Function FindReferences_ExtensionBlockProperty_FromImplementationMethodInvocation(kind As TestKind, host As TestHost) As Task + Dim input = + + + +class C +{ + void Test() + { + var c = new C(); + _ = c.[|P|]; + c.[|P|] = 1; + + E.[|$$get_P|](c); + E.[|set_P|](c, 1); + + _ = c is { [|P|]: 1 }; + _ = new C() { [|P|] = 1 }; + } +} + +public static class E +{ + extension(C c) + { + public int {|Definition:P|} { {|Definition:get|} => i; {|Definition:set|} { } } + } +} + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + + Public Async Function FindReferences_ExtensionBlockProperty_FromImplementationMethodInvocation_MultiFile(kind As TestKind, host As TestHost) As Task + Dim input = + + + +class C +{ + void Test() + { + E.[|$$get_P|](c); + } +} + + +class C2 +{ + void Test() + { + var c = new C(); + _ = c.[|P|]; + } +} + + +class C3 +{ + void Test() + { + var c = new C(); + c.[|P|] = 1; + } +} + + +class C4 +{ + void Test() + { + var c = new C(); + E.[|get_P|](c); + } +} + + +class C5 +{ + void Test() + { + var c = new C(); + E.[|set_P|](c, 1); + } +} + + +class C6 +{ + void Test() + { + var c = new C(); + _ = c is { [|P|]: 1 }; + } +} + + +class C7 +{ + void Test() + { + _ = new C() { [|P|] = 1 }; + } +} + + +public static class E +{ + extension(C c) + { + public int {|Definition:P|} { {|Definition:get|} => i; {|Definition:set|} { } } + } +} + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + + Public Async Function FindReferences_ExtensionBlockProperty_Static(kind As TestKind, host As TestHost) As Task + Dim input = + + + +class C +{ + void Test() + { + _ = C.[|P|]; + C.[|P|] = 1; + + E.[|get_P|](); + E.[|set_P|](1); + } +} + +public static class E +{ + extension(C c) + { + public static int {|Definition:$$P|} { {|Definition:get|} => i; {|Definition:set|} { } } + } +} + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + + Public Async Function FindReferences_ExtensionBlockOperator_FromExtensionUsage(kind As TestKind, host As TestHost) As Task + Dim input = + + + +class C +{ + static void Test(C c1, C c2) + { + _ = c1 $$[|+|] c2; + E.[|op_Addition|](c1, c2); + } +} + +public static class E +{ + extension(C) + { + public static C operator {|Definition:+|}(C c1, C c2) => throw null; + } +} + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + + Public Async Function FindReferences_ExtensionBlockOperator_FromDisambiguationUsage(kind As TestKind, host As TestHost) As Task + Dim input = + + + +class C +{ + static void Test(C c1, C c2) + { + _ = c1 [|+|] c2; + E.[|$$op_Addition|](c1, c2); + } +} + +public static class E +{ + extension(C) + { + public static C operator {|Definition:+|}(C c1, C c2) => throw null; + } +} + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + + Public Async Function FindReferences_ExtensionBlockOperator_FromDefinition(kind As TestKind, host As TestHost) As Task + Dim input = + + + +class C +{ + static void Test(C c1, C c2) + { + _ = c1 [|+|] c2; + E.[|op_Addition|](c1, c2); + } +} + +public static class E +{ + extension(C) + { + public static C operator $${|Definition:+|}(C c1, C c2) => throw null; + } +} + + + + Await TestAPIAndFeature(input, kind, host) + End Function + End Class End Namespace diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs index 869ff256c1691..a7ec78f9f8280 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs @@ -76,4 +76,16 @@ private static bool IsPotentialReference( { return syntaxFacts.TryGetPredefinedOperator(token, out var actualOperator) && actualOperator == op; } + + protected override ValueTask> DetermineCascadedSymbolsAsync( + IMethodSymbol symbol, + Solution solution, + FindReferencesSearchOptions options, + CancellationToken cancellationToken) + { + // If given an extension operator `E.extension(...).operator+`, we cascade to the its implementation method `E.op_Addition(...)` + return symbol.AssociatedExtensionImplementation is { } associatedExtensionMethod + ? new([associatedExtensionMethod]) + : new([]); + } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs index e7fbb5e2347ee..eb3ae5d47aa1a 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs @@ -7,6 +7,7 @@ using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.PooledObjects; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -27,9 +28,51 @@ protected override ValueTask> DetermineCascadedSymbolsAs { // If it's a delegate method, then cascade to the type as well. These guys are // practically equivalent for users. - return symbol.ContainingType.TypeKind == TypeKind.Delegate - ? new(ImmutableArray.Create(symbol.ContainingType)) - : new(GetOtherPartsOfPartial(symbol)); + if (symbol.ContainingType.TypeKind == TypeKind.Delegate) + return new([symbol.ContainingType]); + + using var _ = ArrayBuilder.GetInstance(out var result); + + result.AddRange(GetOtherPartsOfPartial(symbol)); + + // If the given symbol is an extension member, cascade to its implementation method + result.AddIfNotNull(symbol.AssociatedExtensionImplementation); + + CascadeFromExtensionImplementation(symbol, result); + + return new(result.ToImmutableAndClear()); + } + + private static void CascadeFromExtensionImplementation(IMethodSymbol symbol, ArrayBuilder result) + { + // If the given symbol is an implementation method of an extension member, cascade to the extension member itself + var containingType = symbol.ContainingType; + if (symbol is not { IsStatic: true, IsImplicitlyDeclared: true, ContainingType.MightContainExtensionMethods: true }) + return; + + // Having a compiler API to go from implementation method back to its corresponding extension member would be useful + // Tracked by https://github.com/dotnet/roslyn/issues/81686 + + foreach (var nestedType in containingType.GetTypeMembers()) + { + if (!nestedType.IsExtension || nestedType.ExtensionParameter is null) + continue; + + foreach (var member in nestedType.GetMembers()) + { + if (member is IMethodSymbol method) + { + var associated = method.AssociatedExtensionImplementation; + if (associated is null) + continue; + if (!Equals(associated, symbol)) + continue; + + result.Add(method); + return; + } + } + } } private static ImmutableArray GetOtherPartsOfPartial(IMethodSymbol symbol) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs index 9dad8f072eb4d..3799f7231bfaa 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs @@ -7,6 +7,7 @@ using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -22,11 +23,17 @@ protected override ValueTask> DetermineCascadedSymbolsAs FindReferencesSearchOptions options, CancellationToken cancellationToken) { + using var _ = ArrayBuilder.GetInstance(out var result); + // If we've been asked to search for specific accessors, then do not cascade. // We don't want to produce results for the associated property. - return options.AssociatePropertyReferencesWithSpecificAccessor || symbol.AssociatedSymbol == null - ? new(ImmutableArray.Empty) - : new(ImmutableArray.Create(symbol.AssociatedSymbol)); + if (!options.AssociatePropertyReferencesWithSpecificAccessor) + result.AddIfNotNull(symbol.AssociatedSymbol); + + // If the given symbol is an extension accessor, cascade to its implementation method + result.AddIfNotNull(symbol.AssociatedExtensionImplementation); + + return new(result.ToImmutableAndClear()); } protected override async Task DetermineDocumentsToSearchAsync(