提交 10c7d437 编写于 作者: G Gen Lu

Don't provide unimported items if filter is not already created

上级 4fd3e8e7
......@@ -26,11 +26,14 @@ public ExtensionMethodImportCompletionProviderTests(CSharpTestWorkspaceFixture w
// -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.TimeoutInMillisecondsForImportCompletion, TimeoutInMilliseconds)
.WithChangedOption(CompletionServiceOptions.IsExpandedCompletion, IsExpandedCompletion);
}
protected override ExportProvider GetExportProvider()
......@@ -818,6 +821,42 @@ public void M(int x)
inlineDescription: "");
}
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
public async Task ShouldNotProvideItemsIfIndexIsNotReady()
{
var file1 = $@"
using System;
namespace Foo
{{
public static class ExtensionClass
{{
public static bool ExtentionMethod(this int x)
=> true;
}}
}}";
var file2 = $@"
using System;
namespace Baz
{{
public class Bat
{{
public void M(int x)
{{
x.$$
}}
}}
}}";
IsExpandedCompletion = false;
var markup = GetMarkup(file2, file1, ReferenceType.None);
await VerifyTypeImportItemIsAbsentAsync(
markup,
"ExtentionMethod",
inlineDescription: "Foo");
}
private Task VerifyTypeImportItemExistsAsync(string markup, string expectedItem, int glyph, string inlineDescription, string displayTextSuffix = null, string expectedDescriptionOrNull = null)
{
return VerifyItemExistsAsync(markup, expectedItem, displayTextSuffix: displayTextSuffix, glyph: glyph, inlineDescription: inlineDescription, expectedDescriptionOrNull: expectedDescriptionOrNull);
......
......@@ -38,7 +38,6 @@ protected override bool ShouldProvideCompletion(Document document, SyntaxContext
{
var project = completionContext.Document.Project;
var tick = Environment.TickCount;
using var allTypeNamesDisposer = PooledHashSet<string>.GetInstance(out var allTypeNames);
allTypeNames.Add(receiverTypeSymbol.MetadataName);
allTypeNames.AddRange(receiverTypeSymbol.GetBaseTypes().Concat(receiverTypeSymbol.GetAllInterfacesIncludingThis()).Select(s => s.MetadataName));
......@@ -49,21 +48,140 @@ protected override bool ShouldProvideCompletion(Document document, SyntaxContext
allTypeNames.Add("Object");
}
var matchedMethods = await GetPossibleExtensionMethodMatchesAsync(project, allTypeNames, cancellationToken).ConfigureAwait(false);
telemetryCounter.TotalTypesChecked = Environment.TickCount - tick;
// We don't want to wait for creating indices from scratch unless user explicitly asked for unimported items (via expander).
var matchedMethods = await GetPossibleExtensionMethodMatchesAsync(project, allTypeNames, loadOnly: !isExpandedCompletion, cancellationToken).ConfigureAwait(false);
if (matchedMethods == null)
{
return;
}
var assembly = (await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false))!;
using var itemsDisposer = ArrayBuilder<CompletionItem>.GetInstance(out var items);
tick = Environment.TickCount;
VisitNamespaceSymbol(assembly.GlobalNamespace, string.Empty, receiverTypeSymbol,
syntaxContext.SemanticModel, syntaxContext.Position, namespaceInScope, matchedMethods, items, telemetryCounter,
cancellationToken);
telemetryCounter.TotalExtensionMethodsChecked = Environment.TickCount - tick;
completionContext.AddItems(items);
}
}
/// <summary>
/// Returns a multi-dictionary of mappings "FQN of containing type" => "extension method names"
/// </summary>
private static async Task<MultiDictionary<string, string>?> GetPossibleExtensionMethodMatchesAsync(
Project currentProject,
ISet<string> targetTypeNames,
bool loadOnly,
CancellationToken cancellationToken)
{
var solution = currentProject.Solution;
var graph = currentProject.Solution.GetProjectDependencyGraph();
var relevantProjectIds = graph.GetProjectsThatThisProjectTransitivelyDependsOn(currentProject.Id)
.Concat(currentProject.Id);
using var syntaxDisposer = ArrayBuilder<SyntaxTreeIndex>.GetInstance(out var syntaxTreeIndexBuilder);
using var symbolDisposer = ArrayBuilder<SymbolTreeInfo>.GetInstance(out var symbolTreeInfoBuilder);
var peReferences = PooledHashSet<PortableExecutableReference>.GetInstance();
telemetryCounter.TotalExtensionMethodsProvided = items.Count;
try
{
foreach (var projectId in relevantProjectIds.Concat(currentProject.Id))
{
// Alway create indices for documents in current project if they don't exist.
loadOnly &= projectId != currentProject.Id;
var project = solution.GetProject(projectId);
if (project == null || !project.SupportsCompilation)
{
continue;
}
// Transitively get all the PE references
peReferences.AddRange(project.MetadataReferences.OfType<PortableExecutableReference>());
foreach (var document in project.Documents)
{
var info = await document.GetSyntaxTreeIndexAsync(loadOnly, cancellationToken).ConfigureAwait(false);
// Don't provide anyting if we don't have all the required SyntaxTreeIndex created.
if (info == null)
{
return null;
}
if (info.ContainsExtensionMethod)
{
syntaxTreeIndexBuilder.Add(info);
}
}
}
foreach (var peReference in peReferences)
{
var info = await SymbolTreeInfo.GetInfoForMetadataReferenceAsync(
solution, peReference, loadOnly, cancellationToken).ConfigureAwait(false);
// Don't provide anyting if we don't have all the required SymbolTreeInfo created.
if (info == null)
{
return null;
}
if (info.ContainsExtensionMethod)
{
symbolTreeInfoBuilder.Add(info);
}
}
var results = new MultiDictionary<string, string>();
// Find matching extension methods from source.
foreach (var info in syntaxTreeIndexBuilder)
{
// Add simple extension methods with matching target type name
foreach (var targetTypeName in targetTypeNames)
{
if (!info.SimpleExtensionMethodInfo.TryGetValue(targetTypeName, out var methodInfoIndices))
{
continue;
}
foreach (var index in methodInfoIndices)
{
if (info.TryGetDeclaredSymbolInfo(index, out var methodInfo))
{
results.Add(methodInfo.FullyQualifiedContainerName, methodInfo.Name);
}
}
}
// Add all complex extension methods, we will need to completely rely on symbols to match them.
foreach (var index in info.ComplexExtensionMethodInfo)
{
if (info.TryGetDeclaredSymbolInfo(index, out var methodInfo))
{
results.Add(methodInfo.FullyQualifiedContainerName, methodInfo.Name);
}
}
}
// Find matching extension methods from metadata
foreach (var info in symbolTreeInfoBuilder)
{
foreach (var targetTypeName in targetTypeNames)
{
foreach (var methodInfo in info.GetMatchingExtensionMethodInfo(targetTypeName))
{
results.Add(methodInfo.FullyQualifiedContainerName, methodInfo.Name);
}
}
}
return results;
}
finally
{
peReferences.Free();
}
}
......@@ -151,101 +269,6 @@ protected override bool ShouldProvideCompletion(Document document, SyntaxContext
return reducedMethodSymbol != null;
}
/// <summary>
/// Returns a multi-dictionary of mappings "FQN of containing type" => "extension method names"
/// </summary>
private static async Task<MultiDictionary<string, string>> GetPossibleExtensionMethodMatchesAsync(
Project currentProject,
ISet<string> targetTypeNames,
CancellationToken cancellationToken)
{
var solution = currentProject.Solution;
var graph = currentProject.Solution.GetProjectDependencyGraph();
var dependencies = graph.GetProjectsThatThisProjectTransitivelyDependsOn(currentProject.Id);
#nullable restore
var relevantProjects = dependencies.Select(solution.GetProject)
.Where(p => p.SupportsCompilation)
.Concat(currentProject);
using var syntaxDisposer = ArrayBuilder<SyntaxTreeIndex>.GetInstance(out var syntaxTreeIndexBuilder);
using var symbolDisposer = ArrayBuilder<SymbolTreeInfo>.GetInstance(out var symbolTreeInfoBuilder);
var peReferences = PooledHashSet<PortableExecutableReference>.GetInstance();
foreach (var project in relevantProjects)
{
// Transitively get all the PE references
peReferences.AddRange(project.MetadataReferences.OfType<PortableExecutableReference>());
foreach (var document in project.Documents)
{
var info = await document.GetSyntaxTreeIndexAsync(cancellationToken).ConfigureAwait(false);
if (info.ContainsExtensionMethod)
{
syntaxTreeIndexBuilder.Add(info);
}
}
}
#nullable enable
foreach (var peReference in peReferences)
{
var info = await SymbolTreeInfo.GetInfoForMetadataReferenceAsync(
solution, peReference, loadOnly: true, cancellationToken).ConfigureAwait(false);
if (info.ContainsExtensionMethod)
{
symbolTreeInfoBuilder.Add(info);
}
}
peReferences.Free();
var results = new MultiDictionary<string, string>();
// Find matching extension methods from source.
foreach (var info in syntaxTreeIndexBuilder)
{
// Add simple extension methods with matching target type name
foreach (var targetTypeName in targetTypeNames)
{
if (!info.SimpleExtensionMethodInfo.TryGetValue(targetTypeName, out var methodInfoIndices))
{
continue;
}
foreach (var index in methodInfoIndices)
{
if (info.TryGetDeclaredSymbolInfo(index, out var methodInfo))
{
results.Add(methodInfo.FullyQualifiedContainerName, methodInfo.Name);
}
}
}
// Add all complex extension methods, we will need to completely rely on symbols to match them.
foreach (var index in info.ComplexExtensionMethodInfo)
{
if (info.TryGetDeclaredSymbolInfo(index, out var methodInfo))
{
results.Add(methodInfo.FullyQualifiedContainerName, methodInfo.Name);
}
}
}
// Find matching extension methods from metadata
foreach (var info in symbolTreeInfoBuilder)
{
foreach (var targetTypeName in targetTypeNames)
{
foreach (var methodInfo in info.GetMatchingExtensionMethodInfo(targetTypeName))
{
results.Add(methodInfo.FullyQualifiedContainerName, methodInfo.Name);
}
}
}
return results;
}
private class TelemetryCounter : IDisposable
{
public int TotalTypesChecked { get; set; }
......
......@@ -386,7 +386,7 @@ private static async Task AddDocumentsToUpdateForProjectAsync(Project project, A
{
foreach (var document in project.Documents)
{
var info = await document.GetSyntaxTreeIndexAsync(cancellationToken).ConfigureAwait(false);
var info = await document.GetSyntaxTreeIndexAsync(loadOnly: false, cancellationToken).ConfigureAwait(false);
if (info.ContainsTupleExpressionOrTupleType &&
InfoProbablyContainsTupleFieldNames(info, tupleFieldNames))
{
......
......@@ -185,7 +185,7 @@ internal abstract partial class AbstractNavigateToSearchService
}
cancellationToken.ThrowIfCancellationRequested();
var declarationInfo = await document.GetSyntaxTreeIndexAsync(cancellationToken).ConfigureAwait(false);
var declarationInfo = await document.GetSyntaxTreeIndexAsync(loadOnly: false, cancellationToken).ConfigureAwait(false);
foreach (var declaredSymbolInfo in declarationInfo.DeclaredSymbolInfos)
{
......
......@@ -32,7 +32,7 @@ public static async Task<IEnumerable<SyntaxToken>> GetConstructorInitializerToke
{
// It's very costly to walk an entire tree. So if the tree is simple and doesn't contain
// any unicode escapes in it, then we do simple string matching to find the tokens.
var info = await SyntaxTreeIndex.GetIndexAsync(document, cancellationToken).ConfigureAwait(false);
var info = await SyntaxTreeIndex.GetIndexAsync(document, loadOnly: false, cancellationToken).ConfigureAwait(false);
if (!info.ProbablyContainsIdentifier(identifier))
{
return ImmutableArray<SyntaxToken>.Empty;
......
......@@ -123,7 +123,7 @@ private async Task ProcessDocumentAsync(Document document)
private async Task ProcessDocumentWorkerAsync(Document document)
{
var index = await SyntaxTreeIndex.GetIndexAsync(
document, _cancellationToken).ConfigureAwait(false);
document, loadOnly: false, _cancellationToken).ConfigureAwait(false);
if (_searchKind == SearchKind.StringLiterals)
{
......
......@@ -56,7 +56,7 @@ private static async Task<ProjectIndex> CreateIndexAsync(Project project, Cancel
foreach (var document in project.Documents)
{
var syntaxTreeIndex = await document.GetSyntaxTreeIndexAsync(cancellationToken).ConfigureAwait(false);
var syntaxTreeIndex = await document.GetSyntaxTreeIndexAsync(loadOnly: false, cancellationToken).ConfigureAwait(false);
foreach (var info in syntaxTreeIndex.DeclaredSymbolInfos)
{
switch (info.Kind)
......
......@@ -80,7 +80,7 @@ protected Task<ImmutableArray<Document>> FindDocumentsAsync(Project project, IIm
{
return FindDocumentsAsync(project, documents, async (d, c) =>
{
var info = await SyntaxTreeIndex.GetIndexAsync(d, c).ConfigureAwait(false);
var info = await SyntaxTreeIndex.GetIndexAsync(d, loadOnly: false, c).ConfigureAwait(false);
foreach (var value in values)
{
if (!info.ProbablyContainsIdentifier(value))
......@@ -106,7 +106,7 @@ protected Task<ImmutableArray<Document>> FindDocumentsAsync(Project project, IIm
return FindDocumentsAsync(project, documents, async (d, c) =>
{
var info = await SyntaxTreeIndex.GetIndexAsync(d, c).ConfigureAwait(false);
var info = await SyntaxTreeIndex.GetIndexAsync(d, loadOnly: false, c).ConfigureAwait(false);
return info.ContainsPredefinedType(predefinedType);
}, cancellationToken);
}
......@@ -124,7 +124,7 @@ protected Task<ImmutableArray<Document>> FindDocumentsAsync(Project project, IIm
return FindDocumentsAsync(project, documents, async (d, c) =>
{
var info = await SyntaxTreeIndex.GetIndexAsync(d, c).ConfigureAwait(false);
var info = await SyntaxTreeIndex.GetIndexAsync(d, loadOnly: false, c).ConfigureAwait(false);
return info.ContainsPredefinedOperator(op);
}, cancellationToken);
}
......@@ -395,7 +395,7 @@ protected Task<ImmutableArray<Document>> FindDocumentsWithPredicateAsync(Project
{
return FindDocumentsAsync(project, documents, async (d, c) =>
{
var info = await SyntaxTreeIndex.GetIndexAsync(d, c).ConfigureAwait(false);
var info = await SyntaxTreeIndex.GetIndexAsync(d, loadOnly: false, c).ConfigureAwait(false);
return predicate(info);
}, cancellationToken);
}
......@@ -422,7 +422,7 @@ protected Task<ImmutableArray<Document>> FindDocumentsWithAwaitExpressionAsync(P
CollectMatchingReferences collectMatchingReferences,
CancellationToken cancellationToken)
{
var syntaxTreeInfo = await SyntaxTreeIndex.GetIndexAsync(document, cancellationToken).ConfigureAwait(false);
var syntaxTreeInfo = await SyntaxTreeIndex.GetIndexAsync(document, loadOnly: false, cancellationToken).ConfigureAwait(false);
if (isRelevantDocument(syntaxTreeInfo))
{
var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
......
......@@ -26,7 +26,7 @@ protected override bool CanFind(IMethodSymbol symbol)
{
return FindDocumentsAsync(project, documents, async (d, c) =>
{
var index = await SyntaxTreeIndex.GetIndexAsync(d, c).ConfigureAwait(false);
var index = await SyntaxTreeIndex.GetIndexAsync(d, loadOnly: false, c).ConfigureAwait(false);
if (index.ContainsBaseConstructorInitializer)
{
return true;
......
......@@ -6,6 +6,7 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.FindSymbols
{
......@@ -61,13 +62,20 @@ public static async Task PrecalculateAsync(Document document, CancellationToken
public static async Task<SyntaxTreeIndex> GetIndexAsync(
Document document,
bool loadOnly,
CancellationToken cancellationToken)
{
// See if we already cached an index with this direct document index. If so we can just
// return it with no additional work.
if (!s_documentToIndex.TryGetValue(document, out var index))
{
index = await GetIndexWorkerAsync(document, cancellationToken).ConfigureAwait(false);
index = await GetIndexWorkerAsync(document, loadOnly, cancellationToken).ConfigureAwait(false);
Contract.ThrowIfFalse(index != null || loadOnly == true, "Result can only be null if 'loadOnly: true' was passed.");
if (index == null && loadOnly)
{
return null;
}
// Populate our caches with this data.
s_documentToIndex.GetValue(document, _ => index);
......@@ -79,6 +87,7 @@ public static async Task PrecalculateAsync(Document document, CancellationToken
private static async Task<SyntaxTreeIndex> GetIndexWorkerAsync(
Document document,
bool loadOnly,
CancellationToken cancellationToken)
{
var checksum = await GetChecksumAsync(document, cancellationToken).ConfigureAwait(false);
......@@ -95,7 +104,7 @@ public static async Task PrecalculateAsync(Document document, CancellationToken
// What we have in memory isn't valid. Try to load from the persistence service.
index = await LoadAsync(document, checksum, cancellationToken).ConfigureAwait(false);
if (index != null)
if (index != null || loadOnly)
{
return index;
}
......
......@@ -706,7 +706,7 @@ private async Task AddDocumentsWithPotentialConflicts(IEnumerable<Document> docu
continue;
}
var info = await SyntaxTreeIndex.GetIndexAsync(document, CancellationToken.None).ConfigureAwait(false);
var info = await SyntaxTreeIndex.GetIndexAsync(document, loadOnly: false, CancellationToken.None).ConfigureAwait(false);
if (info.ProbablyContainsEscapedIdentifier(_originalText))
{
_documentsIdsToBeCheckedForConflict.Add(document.Id);
......
......@@ -173,8 +173,8 @@ public static async Task<bool> IsForkedDocumentWithSyntaxChangesAsync(this Docum
}
}
public static Task<SyntaxTreeIndex> GetSyntaxTreeIndexAsync(this Document document, CancellationToken cancellationToken)
=> SyntaxTreeIndex.GetIndexAsync(document, cancellationToken);
public static Task<SyntaxTreeIndex> GetSyntaxTreeIndexAsync(this Document document, bool loadOnly, CancellationToken cancellationToken)
=> SyntaxTreeIndex.GetIndexAsync(document, loadOnly, cancellationToken);
/// <summary>
/// Returns the semantic model for this document that may be produced from partial semantics. The semantic model
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册