提交 5628a26d 编写于 作者: G Gen Lu

Don't show unimported items if there's cache miss

上级 b8940f2a
......@@ -23,16 +23,12 @@ public ExtensionMethodImportCompletionProviderTests(CSharpTestWorkspaceFixture w
private bool? ShowImportCompletionItemsOptionValue { get; set; } = true;
// -1 would disable timebox, whereas 0 means always timeout.
private int TimeoutInMilliseconds { get; set; } = -1;
private bool IsExpandedCompletion { get; set; } = true;
protected override void SetWorkspaceOptions(TestWorkspace workspace)
{
workspace.Options = workspace.Options
.WithChangedOption(CompletionOptions.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, ShowImportCompletionItemsOptionValue)
.WithChangedOption(CompletionServiceOptions.TimeoutInMillisecondsForImportCompletion, TimeoutInMilliseconds)
.WithChangedOption(CompletionServiceOptions.IsExpandedCompletion, IsExpandedCompletion);
}
......
......@@ -3,13 +3,9 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using System.Xml.Linq;
using Microsoft.CodeAnalysis.Completion;
using Microsoft.CodeAnalysis.CSharp.Completion.Providers;
using Microsoft.CodeAnalysis.Editor.Implementation.Interactive;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Editor.UnitTests;
using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces;
using Microsoft.CodeAnalysis.Experiments;
......@@ -33,15 +29,13 @@ internal override CompletionProvider CreateCompletionProvider()
}
private bool? ShowImportCompletionItemsOptionValue { get; set; } = true;
// -1 would disable timebox, whereas 0 means always timeout.
private int TimeoutInMilliseconds { get; set; } = -1;
public object IsExpandedCompletion { get; private set; } = true;
protected override void SetWorkspaceOptions(TestWorkspace workspace)
{
workspace.Options = workspace.Options
.WithChangedOption(CompletionOptions.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, ShowImportCompletionItemsOptionValue)
.WithChangedOption(CompletionServiceOptions.TimeoutInMillisecondsForImportCompletion, TimeoutInMilliseconds);
.WithChangedOption(CompletionServiceOptions.IsExpandedCompletion, IsExpandedCompletion);
}
protected override ExportProvider GetExportProvider()
......@@ -73,6 +67,7 @@ class Bar
public async Task OptionSetToNull_ExpDisabled()
{
ShowImportCompletionItemsOptionValue = null;
IsExpandedCompletion = false;
var markup = @"
class Bar
{
......@@ -90,6 +85,7 @@ public async Task OptionSetToFalse(bool isExperimentEnabled)
SetExperimentOption(WellKnownExperimentNames.TypeImportCompletion, isExperimentEnabled);
ShowImportCompletionItemsOptionValue = false;
IsExpandedCompletion = false;
var markup = @"
class Bar
......@@ -1252,12 +1248,12 @@ namespace Foo
await VerifyCustomCommitProviderAsync(markup, "MyClass", expectedCodeAfterCommit, sourceCodeKind: kind);
}
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
[WorkItem(36624, "https://github.com/dotnet/roslyn/issues/36624")]
public async Task DoNotShowImportItemsIfTimeout()
[InlineData(true)]
[InlineData(false)]
[Theory, Trait(Traits.Feature, Traits.Features.Completion)]
public async Task TestCacheMissNoExpander(bool isProjectReference)
{
// Set timeout to 0 so it always timeout
TimeoutInMilliseconds = 0;
IsExpandedCompletion = false;
var file1 = $@"
namespace NS1
......@@ -1274,7 +1270,7 @@ class C
}
}";
var markup = CreateMarkupForSingleProject(file2, file1, LanguageNames.CSharp);
var markup = GetMarkupWithReference(file2, file1, LanguageNames.CSharp, LanguageNames.CSharp, isProjectReference);
await VerifyTypeImportItemIsAbsentAsync(markup, "Bar", inlineDescription: "NS1");
}
......
......@@ -8,7 +8,6 @@ Imports Microsoft.CodeAnalysis.CSharp
Imports Microsoft.CodeAnalysis.Editor.CSharp.Formatting
Imports Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.AsyncCompletion
Imports Microsoft.CodeAnalysis.Editor.UnitTests.Extensions
Imports Microsoft.CodeAnalysis.Experiments
Imports Microsoft.CodeAnalysis.Options
Imports Microsoft.CodeAnalysis.PooledObjects
Imports Microsoft.CodeAnalysis.Tags
......@@ -5088,59 +5087,6 @@ class C
End Using
End Function
<WorkItem(35614, "https://github.com/dotnet/roslyn/issues/35614")>
<WpfFact, Trait(Traits.Feature, Traits.Features.Completion)>
Public Async Function TestTypeImportCompletion() As Task
Using state = TestStateFactory.CreateCSharpTestState(
<Document><![CDATA[
namespace NS1
{
class C
{
public void Foo()
{
Bar$$
}
}
}
namespace NS2
{
public class Bar { }
}
]]></Document>)
Dim expectedText = "
using NS2;
namespace NS1
{
class C
{
public void Foo()
{
Bar
}
}
}
namespace NS2
{
public class Bar { }
}
"
state.Workspace.Options = state.Workspace.Options _
.WithChangedOption(CompletionOptions.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, True) _
.WithChangedOption(CompletionServiceOptions.TimeoutInMillisecondsForImportCompletion, -1) ' disable timebox for import completion
state.SendInvokeCompletionList()
Await state.AssertSelectedCompletionItem(displayText:="Bar", inlineDescription:="NS2")
state.SendTab()
Assert.Equal(expectedText, state.GetDocumentText())
End Using
End Function
<WpfFact(Skip:="https://github.com/dotnet/roslyn/issues/39070"), Trait(Traits.Feature, Traits.Features.Completion)>
Public Async Function TestExpanderWithImportCompletionDisabled() As Task
Using state = TestStateFactory.CreateCSharpTestState(
......@@ -5219,24 +5165,12 @@ namespace NS2
]]></Document>)
state.Workspace.Options = state.Workspace.Options _
.WithChangedOption(CompletionOptions.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, True) _
.WithChangedOption(CompletionServiceOptions.TimeoutInMillisecondsForImportCompletion, -1) ' disable timebox for import completion
.WithChangedOption(CompletionOptions.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, True)
' trigger completion with import completion enabled
state.SendInvokeCompletionList()
Await state.WaitForUIRenderedAsync()
Await state.AssertSelectedCompletionItem(displayText:="Bar", inlineDescription:="NS2")
state.AssertCompletionItemExpander(isAvailable:=True, isSelected:=True)
' unselect expander
state.SetCompletionItemExpanderState(isSelected:=False)
Await state.WaitForAsynchronousOperationsAsync()
Await state.WaitForUIRenderedAsync()
Await state.AssertCompletionItemsDoNotContainAny({"Bar"})
state.AssertCompletionItemExpander(isAvailable:=True, isSelected:=False)
' select expander
state.SetCompletionItemExpanderState(isSelected:=True)
Await state.WaitForAsynchronousOperationsAsync()
......@@ -5245,7 +5179,7 @@ namespace NS2
Await state.AssertSelectedCompletionItem(displayText:="Bar", inlineDescription:="NS2")
state.AssertCompletionItemExpander(isAvailable:=True, isSelected:=True)
' unselect expander again
' unselect expander
state.SetCompletionItemExpanderState(isSelected:=False)
Await state.WaitForAsynchronousOperationsAsync()
Await state.WaitForUIRenderedAsync()
......@@ -5256,53 +5190,7 @@ namespace NS2
End Function
<WpfFact, Trait(Traits.Feature, Traits.Features.Completion)>
Public Async Function TestExpanderAndTimeboxWithImportCompletionEnabled() As Task
Using state = TestStateFactory.CreateCSharpTestState(
<Document><![CDATA[
namespace NS1
{
class C
{
public void Foo()
{
Bar$$
}
}
}
namespace NS2
{
public class Bar { }
}
]]></Document>)
' Enable import completion and set timeout to 0 (so always timeout)
state.Workspace.Options = state.Workspace.Options _
.WithChangedOption(CompletionOptions.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, True) _
.WithChangedOption(CompletionServiceOptions.TimeoutInMillisecondsForImportCompletion, 0)
' trigger completion with import completion enabled, this should timeout so no unimport types should be shown and expander should be unselected
' (but the caculation should continue in background)
state.SendInvokeCompletionList()
Await state.WaitForUIRenderedAsync()
Await state.AssertCompletionItemsDoNotContainAny({"Bar"})
state.AssertCompletionItemExpander(isAvailable:=True, isSelected:=False)
' select expander
state.SetCompletionItemExpanderState(isSelected:=True)
Await state.WaitForAsynchronousOperationsAsync()
Await state.WaitForUIRenderedAsync()
' timeout is ignored if user asked for unimport types explicitly (via expander)
Await state.AssertSelectedCompletionItem(displayText:="Bar", inlineDescription:="NS2")
state.AssertCompletionItemExpander(isAvailable:=True, isSelected:=True)
End Using
End Function
<WpfFact, Trait(Traits.Feature, Traits.Features.Completion)>
Public Async Function TestExpanderAndTimeboxWithImportCompletionDisabled() As Task
Public Async Function Remove() As Task
Using state = TestStateFactory.CreateCSharpTestState(
<Document><![CDATA[
namespace NS1
......@@ -5332,15 +5220,12 @@ namespace NS2
Await state.AssertCompletionItemsDoNotContainAny({"Bar"})
state.AssertCompletionItemExpander(isAvailable:=True, isSelected:=False)
' set timeout to 0 (always timeout)
state.Workspace.Options = state.Workspace.Options.WithChangedOption(CompletionServiceOptions.TimeoutInMillisecondsForImportCompletion, 0)
' select expander
state.SetCompletionItemExpanderState(isSelected:=True)
Await state.WaitForAsynchronousOperationsAsync()
Await state.WaitForUIRenderedAsync()
' timeout should be ignored since user asked for unimport types explicitly (via expander)
' timeout should be ignored since user asked for unimported types explicitly (via expander)
Await state.AssertSelectedCompletionItem(displayText:="Bar", inlineDescription:="NS2")
state.AssertCompletionItemExpander(isAvailable:=True, isSelected:=True)
......@@ -5762,11 +5647,19 @@ namespace NS2
"
state.Workspace.Options = state.Workspace.Options _
.WithChangedOption(CompletionOptions.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, True) _
.WithChangedOption(CompletionServiceOptions.TimeoutInMillisecondsForImportCompletion, -1)
.WithChangedOption(CompletionOptions.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, True)
state.SendInvokeCompletionList()
Await state.WaitForUIRenderedAsync()
' make sure expander is selected
state.SetCompletionItemExpanderState(isSelected:=True)
Await state.WaitForAsynchronousOperationsAsync()
Await state.WaitForUIRenderedAsync()
state.AssertCompletionItemExpander(isAvailable:=True, isSelected:=True)
Await state.AssertSelectedCompletionItem(displayText:="Bar", displayTextSuffix:="<>")
state.SendTab()
Assert.Equal(expectedText, state.GetDocumentText())
End Using
......@@ -5825,11 +5718,19 @@ namespace NS2
"
state.Workspace.Options = state.Workspace.Options _
.WithChangedOption(CompletionOptions.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, True) _
.WithChangedOption(CompletionServiceOptions.TimeoutInMillisecondsForImportCompletion, -1)
.WithChangedOption(CompletionOptions.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, True)
state.SendInvokeCompletionList()
Await state.WaitForUIRenderedAsync()
' make sure expander is selected
state.SetCompletionItemExpanderState(isSelected:=True)
Await state.WaitForAsynchronousOperationsAsync()
Await state.WaitForUIRenderedAsync()
state.AssertCompletionItemExpander(isAvailable:=True, isSelected:=True)
Await state.AssertSelectedCompletionItem(displayText:="Bar")
state.SendTab()
Assert.Equal(expectedText, state.GetDocumentText())
End Using
......@@ -5888,11 +5789,19 @@ namespace NS2
"
state.Workspace.Options = state.Workspace.Options _
.WithChangedOption(CompletionOptions.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, True) _
.WithChangedOption(CompletionServiceOptions.TimeoutInMillisecondsForImportCompletion, -1)
.WithChangedOption(CompletionOptions.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, True)
state.SendInvokeCompletionList()
Await state.WaitForUIRenderedAsync()
' make sure expander is selected
state.SetCompletionItemExpanderState(isSelected:=True)
Await state.WaitForAsynchronousOperationsAsync()
Await state.WaitForUIRenderedAsync()
state.AssertCompletionItemExpander(isAvailable:=True, isSelected:=True)
Await state.AssertSelectedCompletionItem(displayText:="ABar")
state.SendTab()
Assert.Equal(expectedText, state.GetDocumentText())
End Using
......@@ -5951,10 +5860,17 @@ namespace NS2
"
state.Workspace.Options = state.Workspace.Options _
.WithChangedOption(CompletionOptions.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, True) _
.WithChangedOption(CompletionServiceOptions.TimeoutInMillisecondsForImportCompletion, -1)
.WithChangedOption(CompletionOptions.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, True)
state.SendInvokeCompletionList()
Await state.WaitForUIRenderedAsync()
' make sure expander is selected
state.SetCompletionItemExpanderState(isSelected:=True)
Await state.WaitForAsynchronousOperationsAsync()
Await state.WaitForUIRenderedAsync()
state.AssertCompletionItemExpander(isAvailable:=True, isSelected:=True)
Await state.AssertSelectedCompletionItem(displayText:="Bar", inlineDescription:="")
state.SendTab()
Assert.Equal(expectedText, state.GetDocumentText())
......@@ -6016,11 +5932,19 @@ namespace NS2
"
state.Workspace.Options = state.Workspace.Options _
.WithChangedOption(CompletionOptions.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, True) _
.WithChangedOption(CompletionServiceOptions.TimeoutInMillisecondsForImportCompletion, -1)
.WithChangedOption(CompletionOptions.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, True)
state.SendInvokeCompletionList()
Await state.WaitForUIRenderedAsync()
' make sure expander is selected
state.SetCompletionItemExpanderState(isSelected:=True)
Await state.WaitForAsynchronousOperationsAsync()
Await state.WaitForUIRenderedAsync()
Await state.AssertSelectedCompletionItem(displayText:="Bar", inlineDescription:="NS2")
state.AssertCompletionItemExpander(isAvailable:=True, isSelected:=True)
state.SendTab()
Assert.Equal(expectedText, state.GetDocumentText())
End Using
......
......@@ -25,9 +25,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Completion.Complet
Protected Overrides Sub SetWorkspaceOptions(workspace As TestWorkspace)
workspace.Options = workspace.Options _
.WithChangedOption(CompletionOptions.ShowItemsFromUnimportedNamespaces, LanguageNames.VisualBasic, ShowImportCompletionItemsOptionValue) _
.WithChangedOption(CompletionServiceOptions.TimeoutInMillisecondsForImportCompletion, TimeoutInMilliseconds) _
.WithChangedOption(CompletionServiceOptions.IsExpandedCompletion, IsExpandedCompletion)
.WithChangedOption(CompletionOptions.ShowItemsFromUnimportedNamespaces, LanguageNames.VisualBasic, ShowImportCompletionItemsOptionValue).WithChangedOption(CompletionServiceOptions.IsExpandedCompletion, IsExpandedCompletion)
End Sub
Protected Overrides Function GetExportProvider() As ExportProvider
......
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports Microsoft.CodeAnalysis.Completion
Imports Microsoft.CodeAnalysis.Completion.Providers
Imports Microsoft.CodeAnalysis.Editor.UnitTests
Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces
Imports Microsoft.CodeAnalysis.Experiments
Imports Microsoft.CodeAnalysis.VisualBasic.Completion.Providers
Imports Microsoft.VisualStudio.Composition
......@@ -19,14 +17,12 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Completion.Complet
End Sub
Private Property ShowImportCompletionItemsOptionValue As Boolean = True
' -1 would disable timebox, whereas 0 means always timeout.
Private Property TimeoutInMilliseconds As Integer = -1
Public Property IsExpandedCompletion As Boolean = True
Protected Overrides Sub SetWorkspaceOptions(workspace As TestWorkspace)
workspace.Options = workspace.Options _
.WithChangedOption(CompletionOptions.ShowItemsFromUnimportedNamespaces, LanguageNames.VisualBasic, ShowImportCompletionItemsOptionValue) _
.WithChangedOption(CompletionServiceOptions.TimeoutInMillisecondsForImportCompletion, TimeoutInMilliseconds)
.WithChangedOption(CompletionServiceOptions.IsExpandedCompletion, IsExpandedCompletion)
End Sub
Protected Overrides Function GetExportProvider() As ExportProvider
......
......@@ -11,13 +11,5 @@ internal static class CompletionServiceOptions
/// </summary>
public static readonly Option<bool> IsExpandedCompletion
= new Option<bool>(nameof(CompletionServiceOptions), nameof(IsExpandedCompletion), defaultValue: false);
/// <summary>
/// Timeout value used for time-boxing import completion.
/// Telemetry shows that the average processing time with cache warmed up for 99th percentile is ~700ms,
/// Therefore we set the timeout to 1s to ensure it only applies to the case that cache is cold.
/// </summary>
public static readonly Option<int> TimeoutInMillisecondsForImportCompletion
= new Option<int>(nameof(CompletionServiceOptions), nameof(TimeoutInMillisecondsForImportCompletion), defaultValue: 1000);
}
}
......@@ -5,18 +5,13 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection.Metadata;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Completion.Log;
using Microsoft.CodeAnalysis.Completion.Providers.ImportCompletion;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Completion.Providers
{
......@@ -27,93 +22,47 @@ protected override bool ShouldProvideCompletion(Document document, SyntaxContext
protected override async Task AddCompletionItemsAsync(CompletionContext completionContext, SyntaxContext syntaxContext, HashSet<string> namespacesInScope, bool isExpandedCompletion, CancellationToken cancellationToken)
{
using var _ = Logger.LogBlock(FunctionId.Completion_TypeImportCompletionProvider_GetCompletionItemsAsync, cancellationToken);
var telemetryCounter = new TelemetryCounter();
var document = completionContext.Document;
var project = document.Project;
var workspace = project.Solution.Workspace;
var typeImportCompletionService = document.GetLanguageService<ITypeImportCompletionService>()!;
using var disposer = ArrayBuilder<CompletionItem>.GetInstance(out var itemsBuilder);
var builder = ArrayBuilder<CompletionItem>.GetInstance();
var currentProject = document.Project;
var items = await typeImportCompletionService.GetTopLevelTypesAsync(currentProject,
syntaxContext,
isInternalsVisible: true,
cancellationToken).ConfigureAwait(false);
AddItems(items, completionContext, namespacesInScope, telemetryCounter);
var solution = currentProject.Solution;
var graph = solution.GetProjectDependencyGraph();
var referencedProjects = graph.GetProjectsThatThisProjectTransitivelyDependsOn(currentProject.Id).SelectAsArray(id => solution.GetRequiredProject(id));
var currentCompilation = await currentProject.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false);
foreach (var referencedProject in referencedProjects.Where(p => p.SupportsCompilation))
using (Logger.LogBlock(FunctionId.Completion_TypeImportCompletionProvider_GetCompletionItemsAsync, cancellationToken))
{
var compilation = await referencedProject.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false);
var assembly = SymbolFinder.FindSimilarSymbols(compilation.Assembly, currentCompilation).SingleOrDefault();
var metadataReference = currentCompilation.GetMetadataReference(assembly);
var telemetryCounter = new TelemetryCounter();
var typeImportCompletionService = completionContext.Document.GetRequiredLanguageService<ITypeImportCompletionService>();
if (HasGlobalAlias(metadataReference))
{
items = await typeImportCompletionService.GetTopLevelTypesAsync(
referencedProject,
syntaxContext,
isInternalsVisible: currentCompilation.Assembly.IsSameAssemblyOrHasFriendAccessTo(compilation.Assembly),
cancellationToken).ConfigureAwait(false);
var itemsFromAllAssemblies = await typeImportCompletionService.GetAllTopLevelTypesAsync(
completionContext.Document.Project,
syntaxContext,
forceCacheCreation: isExpandedCompletion,
cancellationToken).ConfigureAwait(false);
AddItems(items, completionContext, namespacesInScope, telemetryCounter);
if (itemsFromAllAssemblies == null)
{
telemetryCounter.TimedOut = true;
}
telemetryCounter.ReferenceCount++;
}
foreach (var peReference in currentProject.MetadataReferences.OfType<PortableExecutableReference>())
{
if (HasGlobalAlias(peReference))
else
{
if (currentCompilation.GetAssemblyOrModuleSymbol(peReference) is IAssemblySymbol assembly)
foreach (var items in itemsFromAllAssemblies)
{
items = typeImportCompletionService.GetTopLevelTypesFromPEReference(
solution,
currentCompilation,
peReference,
syntaxContext,
isInternalsVisible: currentCompilation.Assembly.IsSameAssemblyOrHasFriendAccessTo(assembly),
cancellationToken);
AddItems(items, completionContext, namespacesInScope, telemetryCounter);
}
}
telemetryCounter.ReferenceCount++;
telemetryCounter.Report();
}
}
telemetryCounter.Report();
return;
static bool HasGlobalAlias(MetadataReference metadataReference)
=> metadataReference != null && (metadataReference.Properties.Aliases.IsEmpty || metadataReference.Properties.Aliases.Any(alias => alias == MetadataReferenceProperties.GlobalAlias));
static void AddItems(ImmutableArray<CompletionItem> items, CompletionContext completionContext, HashSet<string> namespacesInScope, TelemetryCounter counter)
private static void AddItems(ImmutableArray<CompletionItem> items, CompletionContext completionContext, HashSet<string> namespacesInScope, TelemetryCounter counter)
{
counter.ReferenceCount++;
foreach (var item in items)
{
foreach (var item in items)
var containingNamespace = ImportCompletionItem.GetContainingNamespace(item);
if (!namespacesInScope.Contains(containingNamespace))
{
var containingNamespace = ImportCompletionItem.GetContainingNamespace(item);
if (!namespacesInScope.Contains(containingNamespace))
{
// 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.
completionContext.AddItem(item);
counter.ItemsCount++; ;
}
// 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.
completionContext.AddItem(item);
counter.ItemsCount++; ;
}
}
}
......
......@@ -5,17 +5,24 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Completion.Providers.ImportCompletion
{
internal abstract partial class AbstractTypeImportCompletionService : ITypeImportCompletionService
{
private static readonly object s_gate = new object();
private static Task s_cachingTask = Task.CompletedTask;
private IImportCompletionCacheService<CacheEntry, CacheEntry> CacheService { get; }
protected abstract string GenericTypeSuffix { get; }
......@@ -27,108 +34,207 @@ internal AbstractTypeImportCompletionService(Workspace workspace)
CacheService = workspace.Services.GetRequiredService<IImportCompletionCacheService<CacheEntry, CacheEntry>>();
}
public async Task<ImmutableArray<CompletionItem>> GetTopLevelTypesAsync(
Project project,
public async Task<ImmutableArray<ImmutableArray<CompletionItem>>?> GetAllTopLevelTypesAsync(
Project currentProject,
SyntaxContext syntaxContext,
bool isInternalsVisible,
bool forceCacheCreation,
CancellationToken cancellationToken)
{
if (!project.SupportsCompilation)
var getCacheResults = await GetCacheEntries(currentProject, syntaxContext, forceCacheCreation, cancellationToken).ConfigureAwait(false);
if (getCacheResults == null)
{
throw new ArgumentException(nameof(project));
// 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 doing.
lock (s_gate)
{
if (s_cachingTask.IsCompleted)
{
s_cachingTask = Task.Run(() => GetCacheEntries(currentProject, syntaxContext, forceCacheCreation: true, CancellationToken.None));
}
}
return null;
}
var currentCompilation = await currentProject.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false);
return getCacheResults.Value.SelectAsArray(GetItemsFromCacheResult);
ImmutableArray<CompletionItem> GetItemsFromCacheResult(GetCacheResult cacheResult)
{
return cacheResult.Entry.GetItemsForContext(
syntaxContext.SemanticModel.Language,
GenericTypeSuffix,
currentCompilation.Assembly.IsSameAssemblyOrHasFriendAccessTo(cacheResult.Assembly),
syntaxContext.IsAttributeNameContext,
IsCaseSensitive);
}
}
private async Task<ImmutableArray<GetCacheResult>?> GetCacheEntries(Project currentProject, SyntaxContext syntaxContext, bool forceCacheCreation, CancellationToken cancellationToken)
{
var _ = ArrayBuilder<GetCacheResult>.GetInstance(out var builder);
var cacheResult = await GetCacheForProject(currentProject, syntaxContext, forceCacheCreation: true, cancellationToken).ConfigureAwait(false);
// We always force create cache for current project.
Debug.Assert(cacheResult.HasValue);
builder.Add(cacheResult!.Value);
var solution = currentProject.Solution;
var graph = solution.GetProjectDependencyGraph();
var referencedProjects = graph.GetProjectsThatThisProjectTransitivelyDependsOn(currentProject.Id).SelectAsArray(id => solution.GetRequiredProject(id));
var currentCompilation = await currentProject.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false);
foreach (var referencedProject in referencedProjects.Where(p => p.SupportsCompilation))
{
var compilation = await referencedProject.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false);
var assembly = SymbolFinder.FindSimilarSymbols(compilation.Assembly, currentCompilation).SingleOrDefault();
var metadataReference = currentCompilation.GetMetadataReference(assembly);
if (HasGlobalAlias(metadataReference))
{
cacheResult = await GetCacheForProject(
referencedProject,
syntaxContext,
forceCacheCreation,
cancellationToken).ConfigureAwait(false);
if (cacheResult.HasValue)
{
builder.Add(cacheResult.Value);
}
else
{
// If there's cache miss, we just don't return any item.
// This way, we will not block completion building our cache.
return null;
}
}
}
foreach (var peReference in currentProject.MetadataReferences.OfType<PortableExecutableReference>())
{
if (HasGlobalAlias(peReference) &&
currentCompilation.GetAssemblyOrModuleSymbol(peReference) is IAssemblySymbol assembly &&
TryGetCacheForPEReference(solution, currentCompilation, peReference, syntaxContext, forceCacheCreation, cancellationToken, out cacheResult))
{
if (cacheResult.HasValue)
{
builder.Add(cacheResult.Value);
}
else
{
// If there's cache miss, we just don't return any item.
// This way, we will not block completion building our cache.
return null;
}
}
}
return builder.ToImmutable();
static bool HasGlobalAlias(MetadataReference metadataReference)
=> metadataReference != null && (metadataReference.Properties.Aliases.IsEmpty || metadataReference.Properties.Aliases.Any(alias => alias == MetadataReferenceProperties.GlobalAlias));
}
/// <summary>
/// Get appropriate completion items for all the visible top level types from given project.
/// This method is intended to be used for getting types from source only, so the project must support compilation.
/// For getting types from PE, use <see cref="TryGetCacheForPEReference"/>.
/// </summary>
private async Task<GetCacheResult?> GetCacheForProject(
Project project,
SyntaxContext syntaxContext,
bool forceCacheCreation,
CancellationToken cancellationToken)
{
var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false);
// Since we only need top level types from source, therefore we only care if source symbol checksum changes.
var checksum = await SymbolTreeInfo.GetSourceSymbolsChecksumAsync(project, cancellationToken).ConfigureAwait(false);
return GetAccessibleTopLevelTypesWorker(
return GetCacheWorker(
project.Id,
compilation.Assembly,
checksum,
syntaxContext,
isInternalsVisible,
forceCacheCreation,
CacheService.ProjectItemsCache,
cancellationToken);
}
public ImmutableArray<CompletionItem> GetTopLevelTypesFromPEReference(
/// <summary>
/// Get appropriate completion items for all the visible top level types from given PE reference.
/// </summary>
private bool TryGetCacheForPEReference(
Solution solution,
Compilation compilation,
PortableExecutableReference peReference,
SyntaxContext syntaxContext,
bool isInternalsVisible,
CancellationToken cancellationToken)
bool forceCacheCreation,
CancellationToken cancellationToken,
out GetCacheResult? result)
{
var key = GetReferenceKey(peReference);
var key = peReference.FilePath ?? peReference.Display;
if (key == null)
{
// Can't cache items for reference with null key. We don't want risk potential perf regression by
// making those items repeatedly, so simply not returning anything from this assembly, until
// we have a better understanding on this sceanrio.
// we have a better understanding on this scenario.
// TODO: Add telemetry
return ImmutableArray<CompletionItem>.Empty;
result = default;
return false;
}
if (!(compilation.GetAssemblyOrModuleSymbol(peReference) is IAssemblySymbol assemblySymbol))
{
return ImmutableArray<CompletionItem>.Empty;
result = default;
return false;
}
var checksum = SymbolTreeInfo.GetMetadataChecksum(solution, peReference, cancellationToken);
return GetAccessibleTopLevelTypesWorker(
result = GetCacheWorker(
key,
assemblySymbol,
checksum,
syntaxContext,
isInternalsVisible,
forceCacheCreation,
CacheService.PEItemsCache,
cancellationToken);
static string GetReferenceKey(PortableExecutableReference reference)
=> reference.FilePath ?? reference.Display;
return true;
}
private ImmutableArray<CompletionItem> GetAccessibleTopLevelTypesWorker<TKey>(
TKey key,
IAssemblySymbol assembly,
Checksum checksum,
SyntaxContext syntaxContext,
bool isInternalsVisible,
IDictionary<TKey, CacheEntry> cache,
CancellationToken cancellationToken)
{
var cacheEntry = GetCacheEntry(key, assembly, checksum, syntaxContext, cache, cancellationToken);
return cacheEntry.GetItemsForContext(
syntaxContext.SemanticModel.Language,
GenericTypeSuffix,
isInternalsVisible,
syntaxContext.IsAttributeNameContext,
IsCaseSensitive);
}
private CacheEntry GetCacheEntry<TKey>(
private GetCacheResult? GetCacheWorker<TKey>(
TKey key,
IAssemblySymbol assembly,
Checksum checksum,
SyntaxContext syntaxContext,
bool forceCacheCreation,
IDictionary<TKey, CacheEntry> cache,
CancellationToken cancellationToken)
{
var language = syntaxContext.SemanticModel.Language;
// Cache miss, create all requested items.
if (!cache.TryGetValue(key, out var cacheEntry) ||
cacheEntry.Checksum != checksum)
// Cache hit
if (cache.TryGetValue(key, out var cacheEntry) && cacheEntry.Checksum == checksum)
{
return new GetCacheResult(cacheEntry, assembly);
}
// Cache miss, create all items only when asked.
if (forceCacheCreation)
{
using var builder = new CacheEntry.Builder(checksum, language, GenericTypeSuffix);
GetCompletionItemsForTopLevelTypeDeclarations(assembly.GlobalNamespace, builder, cancellationToken);
cacheEntry = builder.ToReferenceCacheEntry();
cache[key] = cacheEntry;
return new GetCacheResult(cacheEntry, assembly);
}
return cacheEntry;
return null;
}
private static void GetCompletionItemsForTopLevelTypeDeclarations(
......@@ -231,6 +337,18 @@ public TypeOverloadInfo Aggregate(INamedTypeSymbol type)
}
}
private readonly struct GetCacheResult
{
public CacheEntry Entry { get; }
public IAssemblySymbol Assembly { get; }
public GetCacheResult(CacheEntry entry, IAssemblySymbol assembly)
{
Entry = entry;
Assembly = assembly;
}
}
private readonly struct TypeImportCompletionItemInfo
{
private readonly ItemPropertyKind _properties;
......
......@@ -13,25 +13,18 @@ namespace Microsoft.CodeAnalysis.Completion.Providers.ImportCompletion
internal interface ITypeImportCompletionService : ILanguageService
{
/// <summary>
/// Get approperiate completion items for all the visible top level types from given project.
/// This method is intended to be used for getting types from source only, so the project must support compilation.
/// For getting types from PE, use <see cref="GetTopLevelTypesFromPEReference"/>.
/// Get completion items for all the accessible top level types from the given project and all its references.
/// Each array returned contains all items from one of the reachable entities (i.e. projects and PE references.)
/// Returns null if we don't have all the items cached and <paramref name="forceCacheCreation"/> is false.
/// </summary>
Task<ImmutableArray<CompletionItem>> GetTopLevelTypesAsync(
/// <remarks>
/// Because items from each entity are cached as a separate array, we simply return them as is instead of an
/// aggregated array to avoid unnecessary allocations.
/// </remarks>
Task<ImmutableArray<ImmutableArray<CompletionItem>>?> GetAllTopLevelTypesAsync(
Project project,
SyntaxContext syntaxContext,
bool isInternalsVisible,
CancellationToken cancellationToken);
/// <summary>
/// Get approperiate completion items for all the visible top level types from given PE reference.
/// </summary>
ImmutableArray<CompletionItem> GetTopLevelTypesFromPEReference(
Solution solution,
Compilation compilation,
PortableExecutableReference peReference,
SyntaxContext syntaxContext,
bool isInternalsVisible,
bool forceCacheCreation,
CancellationToken cancellationToken);
}
}
......@@ -1384,6 +1384,16 @@ internal class WorkspacesResources {
}
}
/// <summary>
/// Looks up a localized string similar to Project of ID {0} is required to accomplish the task but is not available from the solution.
/// </summary>
internal static string Project_of_ID_0_is_required_to_accomplish_the_task_but_is_not_available_from_the_solution {
get {
return ResourceManager.GetString("Project_of_ID_0_is_required_to_accomplish_the_task_but_is_not_available_from_the_" +
"solution", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Property.
/// </summary>
......
......@@ -1485,4 +1485,7 @@ Zero-width positive lookbehind assertions are typically used at the beginning of
<data name="SyntaxTree_is_required_to_accomplish_the_task_but_is_not_supported_by_document_0" xml:space="preserve">
<value>Syntax tree is required to accomplish the task but is not supported by document {0}.</value>
</data>
<data name="Project_of_ID_0_is_required_to_accomplish_the_task_but_is_not_available_from_the_solution" xml:space="preserve">
<value>Project of ID {0} is required to accomplish the task but is not available from the solution</value>
</data>
</root>
\ No newline at end of file
......@@ -107,6 +107,11 @@
<target state="translated">Předpona {0} se neočekávala.</target>
<note />
</trans-unit>
<trans-unit id="Project_of_ID_0_is_required_to_accomplish_the_task_but_is_not_available_from_the_solution">
<source>Project of ID {0} is required to accomplish the task but is not available from the solution</source>
<target state="new">Project of ID {0} is required to accomplish the task but is not available from the solution</target>
<note />
</trans-unit>
<trans-unit id="Refactoring_Only">
<source>Refactoring Only</source>
<target state="translated">Pouze refaktoring</target>
......
......@@ -107,6 +107,11 @@
<target state="translated">Das Präfix "{0}" wurde nicht erwartet.</target>
<note />
</trans-unit>
<trans-unit id="Project_of_ID_0_is_required_to_accomplish_the_task_but_is_not_available_from_the_solution">
<source>Project of ID {0} is required to accomplish the task but is not available from the solution</source>
<target state="new">Project of ID {0} is required to accomplish the task but is not available from the solution</target>
<note />
</trans-unit>
<trans-unit id="Refactoring_Only">
<source>Refactoring Only</source>
<target state="translated">Nur Refactoring</target>
......
......@@ -107,6 +107,11 @@
<target state="translated">No se espera el prefijo "{0}"</target>
<note />
</trans-unit>
<trans-unit id="Project_of_ID_0_is_required_to_accomplish_the_task_but_is_not_available_from_the_solution">
<source>Project of ID {0} is required to accomplish the task but is not available from the solution</source>
<target state="new">Project of ID {0} is required to accomplish the task but is not available from the solution</target>
<note />
</trans-unit>
<trans-unit id="Refactoring_Only">
<source>Refactoring Only</source>
<target state="translated">Solo refactorización</target>
......
......@@ -107,6 +107,11 @@
<target state="translated">Le préfixe « {0} » n'est pas attendu</target>
<note />
</trans-unit>
<trans-unit id="Project_of_ID_0_is_required_to_accomplish_the_task_but_is_not_available_from_the_solution">
<source>Project of ID {0} is required to accomplish the task but is not available from the solution</source>
<target state="new">Project of ID {0} is required to accomplish the task but is not available from the solution</target>
<note />
</trans-unit>
<trans-unit id="Refactoring_Only">
<source>Refactoring Only</source>
<target state="translated">Refactorisation uniquement</target>
......
......@@ -107,6 +107,11 @@
<target state="translated">Il prefisso '{0}' non è previsto</target>
<note />
</trans-unit>
<trans-unit id="Project_of_ID_0_is_required_to_accomplish_the_task_but_is_not_available_from_the_solution">
<source>Project of ID {0} is required to accomplish the task but is not available from the solution</source>
<target state="new">Project of ID {0} is required to accomplish the task but is not available from the solution</target>
<note />
</trans-unit>
<trans-unit id="Refactoring_Only">
<source>Refactoring Only</source>
<target state="translated">Solo refactoring</target>
......
......@@ -107,6 +107,11 @@
<target state="translated">プレフィックス '{0}' は予期されていません</target>
<note />
</trans-unit>
<trans-unit id="Project_of_ID_0_is_required_to_accomplish_the_task_but_is_not_available_from_the_solution">
<source>Project of ID {0} is required to accomplish the task but is not available from the solution</source>
<target state="new">Project of ID {0} is required to accomplish the task but is not available from the solution</target>
<note />
</trans-unit>
<trans-unit id="Refactoring_Only">
<source>Refactoring Only</source>
<target state="translated">リファクタリングのみ</target>
......
......@@ -107,6 +107,11 @@
<target state="translated">접두사 '{0}'은(는) 사용할 수 없습니다.</target>
<note />
</trans-unit>
<trans-unit id="Project_of_ID_0_is_required_to_accomplish_the_task_but_is_not_available_from_the_solution">
<source>Project of ID {0} is required to accomplish the task but is not available from the solution</source>
<target state="new">Project of ID {0} is required to accomplish the task but is not available from the solution</target>
<note />
</trans-unit>
<trans-unit id="Refactoring_Only">
<source>Refactoring Only</source>
<target state="translated">리팩터링만</target>
......
......@@ -107,6 +107,11 @@
<target state="translated">Nieoczekiwany prefiks „{0}”</target>
<note />
</trans-unit>
<trans-unit id="Project_of_ID_0_is_required_to_accomplish_the_task_but_is_not_available_from_the_solution">
<source>Project of ID {0} is required to accomplish the task but is not available from the solution</source>
<target state="new">Project of ID {0} is required to accomplish the task but is not available from the solution</target>
<note />
</trans-unit>
<trans-unit id="Refactoring_Only">
<source>Refactoring Only</source>
<target state="translated">Tylko refaktoryzacja</target>
......
......@@ -107,6 +107,11 @@
<target state="translated">O prefixo '{0}' não é esperado</target>
<note />
</trans-unit>
<trans-unit id="Project_of_ID_0_is_required_to_accomplish_the_task_but_is_not_available_from_the_solution">
<source>Project of ID {0} is required to accomplish the task but is not available from the solution</source>
<target state="new">Project of ID {0} is required to accomplish the task but is not available from the solution</target>
<note />
</trans-unit>
<trans-unit id="Refactoring_Only">
<source>Refactoring Only</source>
<target state="translated">Somente Refatoração</target>
......
......@@ -107,6 +107,11 @@
<target state="translated">Префикс "{0}" является недопустимым</target>
<note />
</trans-unit>
<trans-unit id="Project_of_ID_0_is_required_to_accomplish_the_task_but_is_not_available_from_the_solution">
<source>Project of ID {0} is required to accomplish the task but is not available from the solution</source>
<target state="new">Project of ID {0} is required to accomplish the task but is not available from the solution</target>
<note />
</trans-unit>
<trans-unit id="Refactoring_Only">
<source>Refactoring Only</source>
<target state="translated">Только рефакторинг</target>
......
......@@ -107,6 +107,11 @@
<target state="translated">'{0}' öneki beklenmez</target>
<note />
</trans-unit>
<trans-unit id="Project_of_ID_0_is_required_to_accomplish_the_task_but_is_not_available_from_the_solution">
<source>Project of ID {0} is required to accomplish the task but is not available from the solution</source>
<target state="new">Project of ID {0} is required to accomplish the task but is not available from the solution</target>
<note />
</trans-unit>
<trans-unit id="Refactoring_Only">
<source>Refactoring Only</source>
<target state="translated">Sadece Yeniden Düzenlenme</target>
......
......@@ -107,6 +107,11 @@
<target state="translated">前缀“{0}”不是预期的</target>
<note />
</trans-unit>
<trans-unit id="Project_of_ID_0_is_required_to_accomplish_the_task_but_is_not_available_from_the_solution">
<source>Project of ID {0} is required to accomplish the task but is not available from the solution</source>
<target state="new">Project of ID {0} is required to accomplish the task but is not available from the solution</target>
<note />
</trans-unit>
<trans-unit id="Refactoring_Only">
<source>Refactoring Only</source>
<target state="translated">仅重构</target>
......
......@@ -107,6 +107,11 @@
<target state="translated">不應為前置詞 '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Project_of_ID_0_is_required_to_accomplish_the_task_but_is_not_available_from_the_solution">
<source>Project of ID {0} is required to accomplish the task but is not available from the solution</source>
<target state="new">Project of ID {0} is required to accomplish the task but is not available from the solution</target>
<note />
</trans-unit>
<trans-unit id="Refactoring_Only">
<source>Refactoring Only</source>
<target state="translated">僅重構</target>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册