AbstractTypeImportCompletionProvider.cs 8.5 KB
Newer Older
G
Gen Lu 已提交
1 2
// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.

3 4
#nullable enable

G
Gen Lu 已提交
5
using System;
6
using System.Collections.Generic;
G
Gen Lu 已提交
7
using System.Collections.Immutable;
8
using System.Linq;
G
Gen Lu 已提交
9 10
using System.Threading;
using System.Threading.Tasks;
11
using Microsoft.CodeAnalysis.Completion.Log;
12
using Microsoft.CodeAnalysis.Completion.Providers.ImportCompletion;
G
Gen Lu 已提交
13
using Microsoft.CodeAnalysis.Internal.Log;
14
using Microsoft.CodeAnalysis.PooledObjects;
G
Gen Lu 已提交
15 16 17 18 19
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery;

namespace Microsoft.CodeAnalysis.Completion.Providers
{
20
    internal abstract class AbstractTypeImportCompletionProvider : AbstractImportCompletionProvider
G
Gen Lu 已提交
21
    {
22 23
        protected override bool ShouldProvideCompletion(Document document, SyntaxContext syntaxContext)
            => syntaxContext.IsTypeContext;
G
Gen Lu 已提交
24

25
        protected override async Task AddCompletionItemsAsync(CompletionContext completionContext, SyntaxContext syntaxContext, HashSet<string> namespacesInScope, bool isExpandedCompletion, CancellationToken cancellationToken)
G
Gen Lu 已提交
26
        {
G
Gen Lu 已提交
27 28
            using var _ = Logger.LogBlock(FunctionId.Completion_TypeImportCompletionProvider_GetCompletionItemsAsync, cancellationToken);
            var telemetryCounter = new TelemetryCounter();
G
Gen Lu 已提交
29

30 31
            var document = completionContext.Document;
            var project = document.Project;
32
            var workspace = project.Solution.Workspace;
G
Gen Lu 已提交
33
            var typeImportCompletionService = document.GetLanguageService<ITypeImportCompletionService>()!;
G
Gen Lu 已提交
34

G
Gen Lu 已提交
35 36
            var tasksToGetCompletionItems = ArrayBuilder<Task<ImmutableArray<CompletionItem>>>.GetInstance();

37
            // Get completion items from current project. 
38
            var compilation = (await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false))!;
G
Gen Lu 已提交
39 40 41 42 43 44 45
            tasksToGetCompletionItems.Add(Task.Run(() => typeImportCompletionService.GetTopLevelTypesAsync(
                project,
                syntaxContext,
                isInternalsVisible: true,
                cancellationToken)));

            // Get declarations from directly referenced projects and PEs.
G
Gen Lu 已提交
46 47 48 49
            // For script compilation, we don't want previous submissions returned as referenced assemblies,
            // there's no need to check for unimported type from them since namespace declaration is not allowed in script.
            var referencedAssemblySymbols = compilation.GetReferencedAssemblySymbols(excludePreviousSubmissions: true);

G
Gen Lu 已提交
50 51 52 53 54
            // This can be parallelized because we don't add items to CompletionContext
            // until all the collected tasks are completed.
            tasksToGetCompletionItems.AddRange(
                referencedAssemblySymbols.Select(symbol => Task.Run(() => HandleReferenceAsync(symbol))));

55 56
            // We want to timebox the operation that might need to traverse all the type symbols and populate the cache. 
            // The idea is not to block completion for too long (likely to happen the first time import completion is triggered).
G
Gen Lu 已提交
57
            // The trade-off is we might not provide unimported types until the cache is warmed up.
58
            var timeoutInMilliseconds = completionContext.Options.GetOption(CompletionServiceOptions.TimeoutInMillisecondsForImportCompletion);
G
Gen Lu 已提交
59
            var combinedTask = Task.WhenAll(tasksToGetCompletionItems.ToImmutableAndFree());
60 61 62

            if (isExpandedCompletion ||
                timeoutInMilliseconds != 0 && await Task.WhenAny(combinedTask, Task.Delay(timeoutInMilliseconds, cancellationToken)).ConfigureAwait(false) == combinedTask)
G
Gen Lu 已提交
63
            {
64 65
                // Either there's no timeout, and we now have all completion items ready,
                // or user asked for unimported type explicitly so we need to wait until they are calculated.
G
Gen Lu 已提交
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
                var completionItemsToAdd = await combinedTask.ConfigureAwait(false);
                foreach (var completionItems in completionItemsToAdd)
                {
                    AddItems(completionItems, completionContext, namespacesInScope, telemetryCounter);
                }
            }
            else
            {
                // If timed out, we don't want to cancel the computation so next time the cache would be populated.
                // We do not keep track if previous compuation for a given project/PE reference is still running. So there's a chance 
                // we queue same computation again later. However, we expect such computation for an individual reference to be relatively 
                // fast so the actual cycles wasted would be insignificant.
                telemetryCounter.TimedOut = true;
            }

            telemetryCounter.ReferenceCount = referencedAssemblySymbols.Length;
G
Gen Lu 已提交
82
            telemetryCounter.Report();
G
Gen Lu 已提交
83 84 85 86

            return;

            async Task<ImmutableArray<CompletionItem>> HandleReferenceAsync(IAssemblySymbol referencedAssemblySymbol)
87 88
            {
                cancellationToken.ThrowIfCancellationRequested();
G
Gen Lu 已提交
89

90
                // Skip reference with only non-global alias.
G
Gen Lu 已提交
91 92
                var metadataReference = compilation.GetMetadataReference(referencedAssemblySymbol);

93 94
                if (metadataReference.Properties.Aliases.IsEmpty ||
                    metadataReference.Properties.Aliases.Any(alias => alias == MetadataReferenceProperties.GlobalAlias))
95
                {
G
Gen Lu 已提交
96
                    var assemblyProject = project.Solution.GetProject(referencedAssemblySymbol, cancellationToken);
97 98
                    if (assemblyProject != null && assemblyProject.SupportsCompilation)
                    {
G
Gen Lu 已提交
99
                        return await typeImportCompletionService.GetTopLevelTypesAsync(
100
                            assemblyProject,
101
                            syntaxContext,
G
Gen Lu 已提交
102
                            isInternalsVisible: compilation.Assembly.IsSameAssemblyOrHasFriendAccessTo(referencedAssemblySymbol),
103 104 105 106
                            cancellationToken).ConfigureAwait(false);
                    }
                    else if (metadataReference is PortableExecutableReference peReference)
                    {
G
Gen Lu 已提交
107
                        return typeImportCompletionService.GetTopLevelTypesFromPEReference(
108 109 110
                            project.Solution,
                            compilation,
                            peReference,
111
                            syntaxContext,
G
Gen Lu 已提交
112
                            isInternalsVisible: compilation.Assembly.IsSameAssemblyOrHasFriendAccessTo(referencedAssemblySymbol),
113 114
                            cancellationToken);
                    }
G
Gen Lu 已提交
115
                }
116

G
Gen Lu 已提交
117 118
                return ImmutableArray<CompletionItem>.Empty;
            }
119

G
Gen Lu 已提交
120
            static void AddItems(ImmutableArray<CompletionItem> items, CompletionContext completionContext, HashSet<string> namespacesInScope, TelemetryCounter counter)
G
Gen Lu 已提交
121
            {
G
Gen Lu 已提交
122
                foreach (var item in items)
G
Gen Lu 已提交
123
                {
124
                    var containingNamespace = ImportCompletionItem.GetContainingNamespace(item);
125 126
                    if (!namespacesInScope.Contains(containingNamespace))
                    {
G
Gen Lu 已提交
127 128 129 130
                        // We can return cached item directly, item's span will be fixed by completion service.
                        // On the other hand, because of this (i.e. mutating the  span of cached item for each run),
                        // the provider can not be used as a service by components that might be run in parallel 
                        // with completion, which would be a race.
131
                        completionContext.AddItem(item);
G
Gen Lu 已提交
132
                        counter.ItemsCount++; ;
133
                    }
G
Gen Lu 已提交
134 135 136 137
                }
            }
        }

G
Gen Lu 已提交
138
        private class TelemetryCounter
G
Gen Lu 已提交
139
        {
140
            protected int Tick { get; }
G
Gen Lu 已提交
141
            public int ItemsCount { get; set; }
142
            public int ReferenceCount { get; set; }
G
Gen Lu 已提交
143 144
            public bool TimedOut { get; set; }

145 146
            public TelemetryCounter()
            {
147
                Tick = Environment.TickCount;
148 149
            }

G
Gen Lu 已提交
150
            public void Report()
G
Gen Lu 已提交
151
            {
152
                var delta = Environment.TickCount - Tick;
153 154 155
                CompletionProvidersLogger.LogTypeImportCompletionTicksDataPoint(delta);
                CompletionProvidersLogger.LogTypeImportCompletionItemCountDataPoint(ItemsCount);
                CompletionProvidersLogger.LogTypeImportCompletionReferenceCountDataPoint(ReferenceCount);
G
Gen Lu 已提交
156 157 158 159 160

                if (TimedOut)
                {
                    CompletionProvidersLogger.LogTypeImportCompletionTimeout();
                }
G
Gen Lu 已提交
161 162
            }
        }
G
Gen Lu 已提交
163 164
    }
}