diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ExtensionMethodImportCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ExtensionMethodImportCompletionProviderTests.cs index 698fabbad287d38972090f65732b601fe47224bb..77dec2762e556a9f87c93b9d944db6e11c5114b9 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ExtensionMethodImportCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ExtensionMethodImportCompletionProviderTests.cs @@ -1623,7 +1623,6 @@ public void M({tupleType} x) inlineDescription: "NS2"); } - [InlineData(ReferenceType.Project)] [InlineData(ReferenceType.Metadata)] [Theory, Trait(Traits.Feature, Traits.Features.Completion)] diff --git a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.cs b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.cs index 6351e7937652684cf23eb81999f646e0b9edbc13..4c10497c8334c122194642aad2230ca818acdc56 100644 --- a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.cs +++ b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -216,10 +217,18 @@ private static string GetReceiverTypeName(ITypeSymbol typeSymbol) compilation.Assembly, filter, namespaceFilter, internalsVisible, counter, cancellationToken); - var isSymbolFromCurrentCompilation = project == currentProject; - GetExtensionMethodItemsWorker( - position, semanticModel, receiverTypeSymbol, matchingMethodSymbols, isSymbolFromCurrentCompilation, - completionItemsbuilder, namespaceNameCache, checkedReceiverTypes, cancellationToken); + if (project == currentProject) + { + GetExtensionMethodItemsForSymbolsFromSameCompilation( + position, semanticModel, receiverTypeSymbol, matchingMethodSymbols, + completionItemsbuilder, namespaceNameCache, checkedReceiverTypes, cancellationToken); + } + else + { + GetExtensionMethodItemsForSymbolsFromDifferentCompilation( + position, semanticModel, receiverTypeSymbol, matchingMethodSymbols, + completionItemsbuilder, namespaceNameCache, checkedReceiverTypes, cancellationToken); + } } // Get extension method items from PE @@ -234,9 +243,9 @@ private static string GetReceiverTypeName(ITypeSymbol typeSymbol) assembly, filter, namespaceFilter, internalsVisible, counter, cancellationToken); - GetExtensionMethodItemsWorker( + GetExtensionMethodItemsForSymbolsFromSameCompilation( position, semanticModel, receiverTypeSymbol, matchingMethodSymbols, - isSymbolFromCurrentCompilation: true, completionItemsbuilder, namespaceNameCache, + completionItemsbuilder, namespaceNameCache, checkedReceiverTypes, cancellationToken); } } @@ -256,83 +265,120 @@ static ImmutableArray AttachComplexTypes(ImmutableArray receiver } } - private static void GetExtensionMethodItemsWorker( + private static void GetExtensionMethodItemsForSymbolsFromDifferentCompilation( int position, SemanticModel semanticModel, ITypeSymbol receiverTypeSymbol, MultiDictionary matchingMethodSymbols, - bool isSymbolFromCurrentCompilation, ArrayBuilder builder, Dictionary stringCache, Dictionary checkedReceiverTypes, CancellationToken cancellationToken) { // Matching extension method symbols are grouped based on their receiver type. - foreach (var (receiverType, methodSymbols) in matchingMethodSymbols) + foreach (var (declaredReceiverType, methodSymbols) in matchingMethodSymbols) { cancellationToken.ThrowIfCancellationRequested(); - var receiverTypeInCurrentCompilation = isSymbolFromCurrentCompilation - ? receiverType - : SymbolFinder.FindSimilarSymbols(receiverType, semanticModel.Compilation).OfType().FirstOrDefault(); + var declaredReceiverTypeInCurrentCompilation = SymbolFinder.FindSimilarSymbols(declaredReceiverType, semanticModel.Compilation, ignoreAssemblyKey: true, cancellationToken).FirstOrDefault(); + if (declaredReceiverTypeInCurrentCompilation == null) + { + if (!Debugger.IsAttached) + Debugger.Launch(); + continue; + } - // If we already checked an extension method with same receiver type before, and we know it can't be applied - // to the receiverTypeSymbol, then no need to proceed further. - if (checkedReceiverTypes.TryGetValue(receiverTypeInCurrentCompilation, out var cachedResult) && !cachedResult) + if (checkedReceiverTypes.TryGetValue(declaredReceiverTypeInCurrentCompilation, out var cachedResult) && !cachedResult) { + // If we already checked an extension method with same receiver type before, and we know it can't be applied + // to the receiverTypeSymbol, then no need to proceed methods from this group.. continue; } - if (isSymbolFromCurrentCompilation) + var methodsInCurrentCompilation = methodSymbols.Select(s => SymbolFinder.FindSimilarSymbols(s, semanticModel.Compilation, ignoreAssemblyKey: false, cancellationToken).FirstOrDefault()).WhereNotNull(); + + var enumerator = methodsInCurrentCompilation.GetEnumerator(); + if (!enumerator.MoveNext()) { - // We haven't seen this type yet. Try to check by reducing one extension method - // to the given receiver type and save the result. - if (!cachedResult) - { - var reducedMethodSymbol = methodSymbols.First().ReduceExtensionMethod(receiverTypeSymbol); - cachedResult = reducedMethodSymbol != null; - checkedReceiverTypes[receiverType] = cachedResult; - } + continue; + } + + // Get the first method so we can check if the receiver type applies. + var methodInCurrentCompilation = enumerator.Current; + + // We haven't seen this type yet. Try to check by reducing one extension method + // to the given receiver type and save the result. + if (!cachedResult) + { + // If this is the first symbol we retrived from current compilation, + // try to check if we can apply it to given receiver type, and save result to our cache. + var reducedMethodSymbol = methodInCurrentCompilation.ReduceExtensionMethod(receiverTypeSymbol); + cachedResult = reducedMethodSymbol != null; + checkedReceiverTypes[declaredReceiverTypeInCurrentCompilation] = cachedResult; + } + + // Now, cachedResult being false means the receiver type doesn't match, + // stop processing methods from this group. + if (!cachedResult) + { + continue; + } + + // Add first method to the item list. + if (semanticModel.IsAccessible(position, methodInCurrentCompilation)) + { + CreateAndAddItem(methodInCurrentCompilation, builder, stringCache); + } - // Receiver type matches the receiver type of the extension method declaration. - // We can add accessible ones to the item builder. - if (cachedResult) + // Then add the rest to item list. + while (enumerator.MoveNext()) + { + methodInCurrentCompilation = enumerator.Current; + if (semanticModel.IsAccessible(position, methodInCurrentCompilation)) { - foreach (var methodSymbol in methodSymbols) - { - if (semanticModel.IsAccessible(position, methodSymbol)) - { - CreateAndAddItem(methodSymbol, builder, stringCache); - } - } + CreateAndAddItem(methodInCurrentCompilation, builder, stringCache); } } - else + } + } + + private static void GetExtensionMethodItemsForSymbolsFromSameCompilation( + int position, + SemanticModel semanticModel, + ITypeSymbol receiverTypeSymbol, + MultiDictionary matchingMethodSymbols, + ArrayBuilder builder, + Dictionary stringCache, + Dictionary checkedReceiverTypes, + CancellationToken cancellationToken) + { + // Matching extension method symbols are grouped based on their receiver type. + foreach (var (receiverType, methodSymbols) in matchingMethodSymbols) + { + cancellationToken.ThrowIfCancellationRequested(); + + // If we already checked an extension method with same receiver type before, and we know it can't be applied + // to the receiverTypeSymbol, then no need to proceed further. + if (checkedReceiverTypes.TryGetValue(receiverType, out var cachedResult) && !cachedResult) { - // Symbols could be from a different compilation. - // Need to find the matching one in current compilation before any further checks is done. - foreach (var methodSymbol in methodSymbols.Select(s => SymbolFinder.FindSimilarSymbols(s, semanticModel.Compilation).FirstOrDefault()).WhereNotNull()) - { - // We haven't seen this type yet. Try to check by reducing one extension method - // to the given receiver type and save the result. - // Note we will only hit this condition at most once, which is the first time we find a non-null - // similar symbol from current compilation. - if (!cachedResult) - { - // If this is the first symbol we retrived from current compilation, - // try to check if we can apply it to given receiver type, and save result to our cache. - var reducedMethodSymbol = methodSymbol.ReduceExtensionMethod(receiverTypeSymbol); - cachedResult = reducedMethodSymbol != null; - checkedReceiverTypes[receiverType] = cachedResult; - - // Now, cachedResult being false means the receiver type doesn't match, - // stop processing any more methods. - if (!cachedResult) - { - break; - } - } + continue; + } + // We haven't seen this type yet. Try to check by reducing one extension method + // to the given receiver type and save the result. + if (!cachedResult) + { + var reducedMethodSymbol = methodSymbols.First().ReduceExtensionMethod(receiverTypeSymbol); + cachedResult = reducedMethodSymbol != null; + checkedReceiverTypes[receiverType] = cachedResult; + } + + // Receiver type matches the receiver type of the extension method declaration. + // We can add accessible ones to the item builder. + if (cachedResult) + { + foreach (var methodSymbol in methodSymbols) + { if (semanticModel.IsAccessible(position, methodSymbol)) { CreateAndAddItem(methodSymbol, builder, stringCache); @@ -340,16 +386,16 @@ static ImmutableArray AttachComplexTypes(ImmutableArray receiver } } } - - static void CreateAndAddItem(IMethodSymbol methodSymbol, ArrayBuilder builder, Dictionary stringCache) - => builder.Add(new SerializableImportCompletionItem( - SymbolKey.CreateString(methodSymbol), - methodSymbol.Name, - methodSymbol.Arity, - methodSymbol.GetGlyph(), - GetFullyQualifiedNamespaceName(methodSymbol.ContainingNamespace, stringCache))); } + private static void CreateAndAddItem(IMethodSymbol methodSymbol, ArrayBuilder builder, Dictionary stringCache) + => builder.Add(new SerializableImportCompletionItem( + SymbolKey.CreateString(methodSymbol), + methodSymbol.Name, + methodSymbol.Arity, + methodSymbol.GetGlyph(), + GetFullyQualifiedNamespaceName(methodSymbol.ContainingNamespace, stringCache))); + private static string GetFullyQualifiedNamespaceName(INamespaceSymbol symbol, Dictionary stringCache) { if (symbol.ContainingNamespace == null || symbol.ContainingNamespace.IsGlobalNamespace) diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.cs index 2b5e8e8ae44233a7b5c2bae2c960a5c85b8a65ad..357b94ffae3c7bb9a63eade463862bb4f73c61c1 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.cs @@ -198,6 +198,10 @@ private static bool InSource(ISymbol symbol) /// public static IEnumerable FindSimilarSymbols(TSymbol symbol, Compilation compilation, CancellationToken cancellationToken = default) where TSymbol : ISymbol + => FindSimilarSymbols(symbol, compilation, ignoreAssemblyKey: false, cancellationToken); + + internal static IEnumerable FindSimilarSymbols(TSymbol symbol, Compilation compilation, bool ignoreAssemblyKey, CancellationToken cancellationToken = default) + where TSymbol : ISymbol { if (symbol == null) { @@ -213,7 +217,7 @@ public static IEnumerable FindSimilarSymbols(TSymbol symbol, C // We may be talking about different compilations. So do not try to resolve locations. var result = new HashSet(); - var resolution = key.Resolve(compilation, cancellationToken: cancellationToken); + var resolution = key.Resolve(compilation, ignoreAssemblyKey, cancellationToken); foreach (var current in resolution.OfType()) { result.Add(current);