private static void GetItemsFromConflictingTypes( INamespaceSymbol containingNamespaceSymbol, ConflictNameNode conflictTypeNodes, ArrayBuilder <SerializableImportCompletionItem> builder, ITypeSymbol receiverTypeSymbol, SemanticModel semanticModel, int position, StatisticCounter counter) { Debug.Assert(!conflictTypeNodes.NamespaceAndMethodNames.HasValue); foreach (var child in conflictTypeNodes.Children.Values) { if (child.NamespaceAndMethodNames == null) { var childNamespace = containingNamespaceSymbol.GetMembers(child.Name).OfType <INamespaceSymbol>().FirstOrDefault(); if (childNamespace != null) { GetItemsFromConflictingTypes(childNamespace, child, builder, receiverTypeSymbol, semanticModel, position, counter); } } else { var types = containingNamespaceSymbol.GetMembers(child.Name).OfType <INamedTypeSymbol>(); foreach (var type in types) { var(namespaceName, methodNames) = child.NamespaceAndMethodNames.Value; GetItemsFromTypeContainsPotentialMatches(type, namespaceName, methodNames, receiverTypeSymbol, semanticModel, position, counter, builder); } } } }
private static ImmutableArray <SerializableImportCompletionItem> GetExtensionMethodItems( INamespaceSymbol rootNamespaceSymbol, ITypeSymbol receiverTypeSymbol, SemanticModel semanticModel, int position, ISet <string> namespaceFilter, MultiDictionary <string, string> methodNameFilter, StatisticCounter counter, CancellationToken cancellationToken) { var compilation = semanticModel.Compilation; using var _ = ArrayBuilder <SerializableImportCompletionItem> .GetInstance(out var builder); using var conflictTypeRootNode = new ConflictNameNode(name: string.Empty); foreach (var(fullyQualifiedContainerName, methodNames) in methodNameFilter) { cancellationToken.ThrowIfCancellationRequested(); var indexOfLastDot = fullyQualifiedContainerName.LastIndexOf('.'); var qualifiedNamespaceName = indexOfLastDot > 0 ? fullyQualifiedContainerName.Substring(0, indexOfLastDot) : string.Empty; if (namespaceFilter.Contains(qualifiedNamespaceName)) { continue; } // Container of extension method (static class in C# and Module in VB) can't be generic or nested. // Note that we might incorrectly ignore valid types, because, for example, calling `GetTypeByMetadataName` // would return null if we have multiple definitions of a type even though only one is accessible from here // (e.g. an internal type declared inside a shared document). In this case, we have to handle the conflicted // types explicitly here. // // TODO: // Alternatively, we can try to get symbols out of each assembly, instead of the compilation (which includes all references), // to avoid such conflict. We should give this approach a try and see if any perf improvement can be archieved. var containerSymbol = compilation.GetTypeByMetadataName(fullyQualifiedContainerName); if (containerSymbol != null) { GetItemsFromTypeContainsPotentialMatches(containerSymbol, qualifiedNamespaceName, methodNames, receiverTypeSymbol, semanticModel, position, counter, builder); } else { conflictTypeRootNode.Add(fullyQualifiedContainerName, (qualifiedNamespaceName, methodNames)); } } var ticks = Environment.TickCount; GetItemsFromConflictingTypes(rootNamespaceSymbol, conflictTypeRootNode, builder, receiverTypeSymbol, semanticModel, position, counter); counter.GetSymbolExtraTicks = Environment.TickCount - ticks; return(builder.ToImmutable()); }
public static async Task <(ImmutableArray <SerializableImportCompletionItem>, StatisticCounter)> GetUnimportedExtensionMethodsInCurrentProcessAsync( Document document, int position, ITypeSymbol receiverTypeSymbol, ISet <string> namespaceInScope, bool forceIndexCreation, CancellationToken cancellationToken) { var counter = new StatisticCounter(); var ticks = Environment.TickCount; // Get the metadata name of all the base types and interfaces this type derived from. using var _ = PooledHashSet <string> .GetInstance(out var allTypeNamesBuilder); allTypeNamesBuilder.Add(receiverTypeSymbol.MetadataName); allTypeNamesBuilder.AddRange(receiverTypeSymbol.GetBaseTypes().Select(t => t.MetadataName)); allTypeNamesBuilder.AddRange(receiverTypeSymbol.GetAllInterfacesIncludingThis().Select(t => t.MetadataName)); // interface doesn't inherit from object, but is implicitly convertible to object type. if (receiverTypeSymbol.IsInterfaceType()) { allTypeNamesBuilder.Add(nameof(Object)); } var allTypeNames = allTypeNamesBuilder.ToImmutableArray(); var indicesResult = await TryGetIndicesAsync( document.Project, forceIndexCreation, cancellationToken).ConfigureAwait(false); // Don't show unimported extension methods if the index isn't ready. if (!indicesResult.HasResult) { // We use a very simple approach to build the cache in the background: // queue a new task only if the previous task is completed, regardless of what // that task is. lock (s_gate) { if (s_indexingTask.IsCompleted) { s_indexingTask = Task.Run(() => TryGetIndicesAsync(document.Project, forceIndexCreation: true, CancellationToken.None)); } } return(ImmutableArray <SerializableImportCompletionItem> .Empty, counter); } counter.GetFilterTicks = Environment.TickCount - ticks; counter.NoFilter = !indicesResult.HasResult; ticks = Environment.TickCount; var items = await GetExtensionMethodItemsAsync(document, receiverTypeSymbol, allTypeNames, indicesResult, position, namespaceInScope, counter, cancellationToken).ConfigureAwait(false); counter.GetSymbolTicks = Environment.TickCount - ticks; return(items, counter); }
private static ImmutableArray <IMethodSymbol> GetPotentialMatchingSymbolsFromAssembly( IAssemblySymbol assembly, MultiDictionary <string, string> extensionMethodFilter, ISet <string> namespaceFilter, bool internalsVisible, StatisticCounter counter, CancellationToken cancellationToken) { using var _ = ArrayBuilder <IMethodSymbol> .GetInstance(out var builder); foreach (var(fullyQualifiedContainerName, methodNames) in extensionMethodFilter) { cancellationToken.ThrowIfCancellationRequested(); // First try to filter out types from already imported namespaces var indexOfLastDot = fullyQualifiedContainerName.LastIndexOf('.'); var qualifiedNamespaceName = indexOfLastDot > 0 ? fullyQualifiedContainerName.Substring(0, indexOfLastDot) : string.Empty; if (namespaceFilter.Contains(qualifiedNamespaceName)) { continue; } counter.TotalTypesChecked++; // Container of extension method (static class in C# and Module in VB) can't be generic or nested. var containerSymbol = assembly.GetTypeByMetadataName(fullyQualifiedContainerName); if (containerSymbol == null || !containerSymbol.MightContainExtensionMethods || !IsAccessible(containerSymbol, internalsVisible)) { continue; } foreach (var methodName in methodNames) { var methodSymbols = containerSymbol.GetMembers(methodName).OfType <IMethodSymbol>(); foreach (var methodSymbol in methodSymbols) { counter.TotalExtensionMethodsChecked++; if (methodSymbol.IsExtensionMethod && IsAccessible(methodSymbol, internalsVisible)) { // Find a potential match. builder.Add(methodSymbol); } } } } return(builder.ToImmutable());
private static void GetItemsFromTypeContainsPotentialMatches( INamedTypeSymbol containerSymbol, string qualifiedNamespaceName, MultiDictionary <string, string> .ValueSet methodNames, ITypeSymbol receiverTypeSymbol, SemanticModel semanticModel, int position, StatisticCounter counter, ArrayBuilder <SerializableImportCompletionItem> builder) { counter.TotalTypesChecked++; if (containerSymbol == null || !containerSymbol.MightContainExtensionMethods || !IsSymbolAccessible(containerSymbol, position, semanticModel)) { return; } foreach (var methodName in methodNames) { var methodSymbols = containerSymbol.GetMembers(methodName).OfType <IMethodSymbol>(); foreach (var methodSymbol in methodSymbols) { counter.TotalExtensionMethodsChecked++; IMethodSymbol?reducedMethodSymbol = null; if (methodSymbol.IsExtensionMethod && IsSymbolAccessible(methodSymbol, position, semanticModel)) { reducedMethodSymbol = methodSymbol.ReduceExtensionMethod(receiverTypeSymbol); } if (reducedMethodSymbol != null) { var symbolKeyData = SymbolKey.CreateString(reducedMethodSymbol); builder.Add(new SerializableImportCompletionItem( symbolKeyData, reducedMethodSymbol.Name, reducedMethodSymbol.Arity, reducedMethodSymbol.GetGlyph(), qualifiedNamespaceName)); } } } }
public static async Task <(ImmutableArray <SerializableImportCompletionItem>, StatisticCounter)> GetUnimportedExtensionMethodsInCurrentProcessAsync( Document document, int position, ITypeSymbol receiverTypeSymbol, ISet <string> namespaceInScope, bool forceIndexCreation, CancellationToken cancellationToken) { var counter = new StatisticCounter(); var ticks = Environment.TickCount; // First find symbols of all applicable extension methods. // Workspace's syntax/symbol index is used to avoid iterating every method symbols in the solution. var symbolComputer = await ExtensionMethodSymbolComputer.CreateAsync( document, position, receiverTypeSymbol, namespaceInScope, cancellationToken).ConfigureAwait(false); var(extentsionMethodSymbols, isPartialResult) = await symbolComputer.GetExtensionMethodSymbolsAsync(forceIndexCreation, cancellationToken).ConfigureAwait(false); counter.GetSymbolsTicks = Environment.TickCount - ticks; ticks = Environment.TickCount; var items = ConvertSymbolsToCompletionItems(extentsionMethodSymbols, cancellationToken); // If we don't have all the indices available already, queue a backgrounds task to create them. if (isPartialResult) { lock (s_gate) { // We use a very simple approach to build the cache in the background: // queue a new task only if the previous task is completed. This is to avoid // queueing calculation for the same set of references repeatedly while // index is being constrcuted, which might take some time. if (s_indexingTask.IsCompleted) { s_indexingTask = symbolComputer.PopulateIndicesAsync(CancellationToken.None); } } counter.PartialResult = true; } counter.CreateItemsTicks = Environment.TickCount - ticks; return(items, counter); }
private static async Task <ImmutableArray <SerializableImportCompletionItem> > GetExtensionMethodItemsAsync( Document document, ITypeSymbol receiverTypeSymbol, ImmutableArray <string> targetTypeNames, GetIndicesResult indices, int position, ISet <string> namespaceFilter, StatisticCounter counter, CancellationToken cancellationToken) { if (!indices.HasResult) { return(ImmutableArray <SerializableImportCompletionItem> .Empty); } var currentProject = document.Project; var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var currentCompilation = semanticModel.Compilation; var currentAssembly = currentCompilation.Assembly; using var _1 = ArrayBuilder <SerializableImportCompletionItem> .GetInstance(out var builder); using var _2 = PooledDictionary <INamespaceSymbol, string> .GetInstance(out var namespaceNameCache); // Get extension method items from source foreach (var(project, syntaxIndex) in indices.SyntaxIndices !) { var filter = CreateAggregatedFilter(targetTypeNames, syntaxIndex); var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); var assembly = compilation.Assembly; var internalsVisible = currentAssembly.IsSameAssemblyOrHasFriendAccessTo(assembly); var matchingMethodSymbols = GetPotentialMatchingSymbolsFromAssembly( compilation.Assembly, filter, namespaceFilter, internalsVisible, counter, cancellationToken); var isSymbolFromCurrentCompilation = project == currentProject; GetExtensionMethodItemsWorker(position, semanticModel, receiverTypeSymbol, matchingMethodSymbols, isSymbolFromCurrentCompilation, builder, namespaceNameCache); } // Get extension method items from PE foreach (var(peReference, symbolInfo) in indices.SymbolInfos !) { var filter = CreateAggregatedFilter(targetTypeNames, symbolInfo); if (currentCompilation.GetAssemblyOrModuleSymbol(peReference) is IAssemblySymbol assembly) { var internalsVisible = currentAssembly.IsSameAssemblyOrHasFriendAccessTo(assembly); var matchingMethodSymbols = GetPotentialMatchingSymbolsFromAssembly( assembly, filter, namespaceFilter, internalsVisible, counter, cancellationToken); GetExtensionMethodItemsWorker(position, semanticModel, receiverTypeSymbol, matchingMethodSymbols, isSymbolFromCurrentCompilation: false, builder, namespaceNameCache); } } return(builder.ToImmutable()); }
public static async Task <(ImmutableArray <SerializableImportCompletionItem>, StatisticCounter)> GetUnimportedExtensionMethodsInCurrentProcessAsync( Document document, int position, ITypeSymbol receiverTypeSymbol, ISet <string> namespaceInScope, bool forceIndexCreation, CancellationToken cancellationToken) { var counter = new StatisticCounter(); var ticks = Environment.TickCount; // First find symbols of all applicable extension methods. // Workspace's syntax/symbol index is used to avoid iterating every method symbols in the solution. var results = await GetExtensionMethodSymbolsAsync( document, position, receiverTypeSymbol, namespaceInScope, forceIndexCreation, cancellationToken).ConfigureAwait(false); counter.GetSymbolsTicks = Environment.TickCount - ticks; ticks = Environment.TickCount; var indicesComplete = true; using var _1 = PooledDictionary <INamespaceSymbol, string> .GetInstance(out var namespaceNameCache); using var _2 = PooledDictionary <(string containingNamespace, string methodName, bool isGeneric), (IMethodSymbol bestSymbol, int overloadCount)> .GetInstance(out var overloadMap); // Aggregate overloads foreach (var result in results) { // `null` indicates we don't have the index ready for the corresponding project/PE, // we will queue a background task to force creating them. Meanwhile, returns // what we do have even it means we only show partial results. if (result == null) { indicesComplete = false; continue; } foreach (var symbol in result) { IMethodSymbol bestSymbol; int overloadCount; var containingNamespacename = GetFullyQualifiedNamespaceName(symbol.ContainingNamespace, namespaceNameCache); var overloadKey = (containingNamespacename, symbol.Name, isGeneric : symbol.Arity > 0); // Select the overload with minimum number of parameters to display if (overloadMap.TryGetValue(overloadKey, out var currentValue)) { bestSymbol = currentValue.bestSymbol.Parameters.Length > symbol.Parameters.Length ? symbol : currentValue.bestSymbol; overloadCount = currentValue.overloadCount + 1; } else { bestSymbol = symbol; overloadCount = 1; } overloadMap[overloadKey] = (bestSymbol, overloadCount); } } // Then convert symbols into completion items using var _3 = ArrayBuilder <SerializableImportCompletionItem> .GetInstance(out var itemsBuilder); foreach (var((containingNamespace, _, _), (bestSymbol, overloadCount)) in overloadMap) { // To display the count of of additional overloads, we need to substract total by 1. var item = CreateItem(bestSymbol, containingNamespace, additionalOverloadCount: overloadCount - 1, cancellationToken); itemsBuilder.Add(item); } // If we don't have all the indices available already, queue a backgrounds task to create them. if (!indicesComplete) { lock (s_gate) { // We use a very simple approach to build the cache in the background: // queue a new task only if the previous task is completed. This is to avoid // queueing calculation for the same set of references repeatedly while // index is being constrcuted, which might take some time. if (s_indexingTask.IsCompleted) { s_indexingTask = PopulateIndicesAsync(document.Project, CancellationToken.None); } } counter.PartialResult = true; } counter.CreateItemsTicks = Environment.TickCount - ticks; return(itemsBuilder.ToImmutable(), counter);