Beispiel #1
0
        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);
                    }
                }
            }
        }
Beispiel #2
0
        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());
        }
Beispiel #3
0
        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);
        }
Beispiel #4
0
        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());
Beispiel #5
0
        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);
        }
Beispiel #7
0
        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());
        }
Beispiel #8
0
        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);