diff --git a/src/Features/Core/Portable/AddImport/SearchScopes/SourceSymbolsProjectSearchScope.cs b/src/Features/Core/Portable/AddImport/SearchScopes/SourceSymbolsProjectSearchScope.cs index 425a4944a4f2c..022ecd693b905 100644 --- a/src/Features/Core/Portable/AddImport/SearchScopes/SourceSymbolsProjectSearchScope.cs +++ b/src/Features/Core/Portable/AddImport/SearchScopes/SourceSymbolsProjectSearchScope.cs @@ -21,11 +21,11 @@ internal abstract partial class AbstractAddImportFeatureService private class SourceSymbolsProjectSearchScope : ProjectSearchScope { - private readonly ConcurrentDictionary> _projectToAssembly; + private readonly ConcurrentDictionary> _projectToAssembly; public SourceSymbolsProjectSearchScope( AbstractAddImportFeatureService provider, - ConcurrentDictionary> projectToAssembly, + ConcurrentDictionary> projectToAssembly, Project project, bool ignoreCase, CancellationToken cancellationToken) : base(provider, project, ignoreCase, cancellationToken) { @@ -53,15 +53,12 @@ protected override async Task> FindDeclarationsAsync( return declarations; - static AsyncLazy CreateLazyAssembly(Project project) - { - return new AsyncLazy( - async c => - { - var compilation = await project.GetRequiredCompilationAsync(c).ConfigureAwait(false); - return compilation.Assembly; - }, cacheResult: true); - } + static AsyncLazy CreateLazyAssembly(Project project) + => new(async c => + { + var compilation = await project.GetRequiredCompilationAsync(c).ConfigureAwait(false); + return compilation.Assembly; + }, cacheResult: true); } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder.cs index 588f3fb7f9046..85c6b1ffa428b 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder.cs @@ -17,25 +17,11 @@ namespace Microsoft.CodeAnalysis.FindSymbols { internal static partial class DeclarationFinder { - private static Task AddCompilationDeclarationsWithNormalQueryAsync( - Project project, SearchQuery query, SymbolFilter filter, - ArrayBuilder list, CancellationToken cancellationToken) - { - Contract.ThrowIfTrue(query.Kind == SearchKind.Custom, "Custom queries are not supported in this API"); - return AddCompilationDeclarationsWithNormalQueryAsync( - project, query, filter, list, - startingCompilation: null, - startingAssembly: null, - cancellationToken: cancellationToken); - } - - private static async Task AddCompilationDeclarationsWithNormalQueryAsync( + private static async Task AddCompilationSourceDeclarationsWithNormalQueryAsync( Project project, SearchQuery query, SymbolFilter filter, ArrayBuilder list, - Compilation? startingCompilation, - IAssemblySymbol? startingAssembly, CancellationToken cancellationToken) { if (!project.SupportsCompilation) @@ -76,23 +62,14 @@ private static async Task AddCompilationDeclarationsWithNormalQueryAsync( var symbolsWithName = symbols.ToImmutableArray(); - if (startingCompilation != null && startingAssembly != null && !Equals(compilation.Assembly, startingAssembly)) - { - // Return symbols from skeleton assembly in this case so that symbols have - // the same language as startingCompilation. - symbolsWithName = symbolsWithName.Select(s => s.GetSymbolKey(cancellationToken).Resolve(startingCompilation, cancellationToken: cancellationToken).Symbol) - .WhereNotNull() - .ToImmutableArray(); - } - list.AddRange(FilterByCriteria(symbolsWithName, filter)); } } private static async Task AddMetadataDeclarationsWithNormalQueryAsync( Project project, - IAssemblySymbol assembly, - PortableExecutableReference? reference, + AsyncLazy lazyAssembly, + PortableExecutableReference reference, SearchQuery query, SymbolFilter filter, ArrayBuilder list, @@ -104,16 +81,13 @@ private static async Task AddMetadataDeclarationsWithNormalQueryAsync( using (Logger.LogBlock(FunctionId.SymbolFinder_Assembly_AddDeclarationsAsync, cancellationToken)) { - if (reference != null) - { - var info = await SymbolTreeInfo.GetInfoForMetadataReferenceAsync( - project.Solution, reference, checksum: null, cancellationToken).ConfigureAwait(false); + var info = await SymbolTreeInfo.GetInfoForMetadataReferenceAsync( + project.Solution, reference, checksum: null, cancellationToken).ConfigureAwait(false); - Contract.ThrowIfNull(info); + Contract.ThrowIfNull(info); - var symbols = await info.FindAsync(query, assembly, filter, cancellationToken).ConfigureAwait(false); - list.AddRange(symbols); - } + var symbols = await info.FindAsync(query, lazyAssembly, filter, cancellationToken).ConfigureAwait(false); + list.AddRange(symbols); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_AllDeclarations.cs b/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_AllDeclarations.cs index 896b7613e04a1..fc5d8e3af64bb 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_AllDeclarations.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_AllDeclarations.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.PooledObjects; @@ -58,51 +59,97 @@ public static async Task> FindAllDeclarationsWithNormalQ internal static async Task> FindAllDeclarationsWithNormalQueryInCurrentProcessAsync( Project project, SearchQuery query, SymbolFilter criteria, CancellationToken cancellationToken) { - using var _1 = ArrayBuilder.GetInstance(out var buffer); - using var _2 = ArrayBuilder.GetInstance(out var result); + using var _ = ArrayBuilder.GetInstance(out var result); + + // Lazily produce the compilation. We don't want to incur any costs (especially source generators) if there + // are no results for this query in this project. + var lazyCompilation = new AsyncLazy(project.GetRequiredCompilationAsync, cacheResult: true); if (project.SupportsCompilation) { - var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); + await SearchCurrentProjectAsync().ConfigureAwait(false); + await SearchProjectReferencesAsync().ConfigureAwait(false); + await SearchMetadataReferencesAsync().ConfigureAwait(false); + } + + return result.ToImmutable(); + + async Task SearchCurrentProjectAsync() + { + // Search in the source symbols for this project first. + using var _ = ArrayBuilder.GetInstance(out var buffer); // get declarations from the compilation's assembly - await AddCompilationDeclarationsWithNormalQueryAsync( + await AddCompilationSourceDeclarationsWithNormalQueryAsync( project, query, criteria, buffer, cancellationToken).ConfigureAwait(false); - // get declarations from directly referenced projects and metadata - foreach (var assembly in compilation.GetReferencedAssemblySymbols()) + // No need to map symbols since we're looking in the starting project. + await AddAllAsync(buffer, mapSymbol: false).ConfigureAwait(false); + } + + async Task SearchProjectReferencesAsync() + { + // get declarations from directly referenced projects + foreach (var projectReference in project.ProjectReferences) { - var assemblyProject = project.Solution.GetProject(assembly, cancellationToken); - if (assemblyProject != null) - { - await AddCompilationDeclarationsWithNormalQueryAsync( - assemblyProject, query, criteria, buffer, - compilation, assembly, cancellationToken).ConfigureAwait(false); - } - else + using var _ = ArrayBuilder.GetInstance(out var buffer); + + var referencedProject = project.Solution.GetProject(projectReference.ProjectId); + if (referencedProject is null) + continue; + + await AddCompilationSourceDeclarationsWithNormalQueryAsync( + referencedProject, query, criteria, buffer, cancellationToken).ConfigureAwait(false); + + // Add all the results. If they're from a different language, attempt to map them back into the + // starting project's language. + await AddAllAsync(buffer, mapSymbol: referencedProject.Language != project.Language).ConfigureAwait(false); + } + } + + async Task SearchMetadataReferencesAsync() + { + // get declarations from directly referenced metadata + foreach (var peReference in project.MetadataReferences.OfType()) + { + using var _ = ArrayBuilder.GetInstance(out var buffer); + + var lazyAssembly = new AsyncLazy(async cancellationToken => { - await AddMetadataDeclarationsWithNormalQueryAsync( - project, assembly, compilation.GetMetadataReference(assembly) as PortableExecutableReference, - query, criteria, buffer, cancellationToken).ConfigureAwait(false); - } + var compilation = await lazyCompilation.GetValueAsync(cancellationToken).ConfigureAwait(false); + var assemblySymbol = compilation.GetAssemblyOrModuleSymbol(peReference) as IAssemblySymbol; + return assemblySymbol; + }, cacheResult: true); + + await AddMetadataDeclarationsWithNormalQueryAsync( + project, lazyAssembly, peReference, + query, criteria, buffer, cancellationToken).ConfigureAwait(false); + + // No need to map symbols since we're looking in metadata. They will always be in the language of our starting project. + await AddAllAsync(buffer, mapSymbol: false).ConfigureAwait(false); } + } + + async Task AddAllAsync(ArrayBuilder buffer, bool mapSymbol) + { + if (buffer.Count == 0) + return; + + var compilation = await lazyCompilation.GetValueAsync(cancellationToken).ConfigureAwait(false); // Make certain all namespace symbols returned by API are from the compilation // for the passed in project. foreach (var symbol in buffer) { - if (symbol is INamespaceSymbol ns) - { - result.AddIfNotNull(compilation.GetCompilationNamespace(ns)); - } - else - { - result.Add(symbol); - } + var mappedSymbol = mapSymbol + ? symbol.GetSymbolKey(cancellationToken).Resolve(compilation, cancellationToken: cancellationToken).Symbol + : symbol; + + result.AddIfNotNull(mappedSymbol is INamespaceSymbol ns + ? compilation.GetCompilationNamespace(ns) + : mappedSymbol); } } - - return result.ToImmutable(); } private static async Task> RehydrateAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_SourceDeclarations.cs b/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_SourceDeclarations.cs index 23fbe41fdcc08..a9a7066a8cd4a 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_SourceDeclarations.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_SourceDeclarations.cs @@ -182,7 +182,7 @@ internal static async Task> FindSourceDeclarationsWithNo using var _ = ArrayBuilder.GetInstance(out var result); foreach (var project in solution.Projects) { - await AddCompilationDeclarationsWithNormalQueryAsync( + await AddCompilationSourceDeclarationsWithNormalQueryAsync( project, query, criteria, result, cancellationToken).ConfigureAwait(false); } @@ -195,7 +195,7 @@ internal static async Task> FindSourceDeclarationsWithNo using var _ = ArrayBuilder.GetInstance(out var result); using var query = SearchQuery.Create(name, ignoreCase); - await AddCompilationDeclarationsWithNormalQueryAsync( + await AddCompilationSourceDeclarationsWithNormalQueryAsync( project, query, filter, result, cancellationToken).ConfigureAwait(false); return result.ToImmutable(); diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.cs index f8635af1c7d95..e3d6678c3d727 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.cs @@ -59,7 +59,7 @@ internal partial class SymbolTreeInfo : IChecksummedObject /// For non-array simple types, the receiver type name would be its metadata name, e.g. "Int32". /// For any array types with simple type as element, the receiver type name would be just "ElementTypeName[]", e.g. "Int32[]" for int[][,] /// For non-array complex types, the receiver type name is "". - /// For any array types with complex type as element, the receier type name is "[]" + /// For any array types with complex type as element, the receiver type name is "[]" /// private readonly MultiDictionary? _receiverTypeNameToExtensionMethodMap; @@ -126,11 +126,11 @@ public Task> FindAsync( Contract.ThrowIfTrue(query.Kind == SearchKind.Custom, "Custom queries are not supported in this API"); return this.FindAsync( - query, new AsyncLazy(assembly), filter, cancellationToken); + query, new AsyncLazy(assembly), filter, cancellationToken); } public async Task> FindAsync( - SearchQuery query, AsyncLazy lazyAssembly, + SearchQuery query, AsyncLazy lazyAssembly, SymbolFilter filter, CancellationToken cancellationToken) { // All entrypoints to this function are Find functions that are only searching @@ -143,7 +143,7 @@ public async Task> FindAsync( } private Task> FindCoreAsync( - SearchQuery query, AsyncLazy lazyAssembly, CancellationToken cancellationToken) + SearchQuery query, AsyncLazy lazyAssembly, CancellationToken cancellationToken) { // All entrypoints to this function are Find functions that are only searching // for specific strings (i.e. they never do a custom search). @@ -168,7 +168,7 @@ private Task> FindCoreAsync( /// Finds symbols in this assembly that match the provided name in a fuzzy manner. /// private async Task> FuzzyFindAsync( - AsyncLazy lazyAssembly, string name, CancellationToken cancellationToken) + AsyncLazy lazyAssembly, string name, CancellationToken cancellationToken) { var similarNames = _spellChecker.FindSimilarWords(name, substringsAreSimilar: false); var result = ArrayBuilder.GetInstance(); @@ -186,7 +186,7 @@ private async Task> FuzzyFindAsync( /// Get all symbols that have a name matching the specified name. /// private async Task> FindAsync( - AsyncLazy lazyAssembly, + AsyncLazy lazyAssembly, string name, bool ignoreCase, CancellationToken cancellationToken) @@ -206,6 +206,9 @@ private async Task> FindAsync( if (ignoreCase || StringComparer.Ordinal.Equals(name, node.Name)) { assemblySymbol ??= await lazyAssembly.GetValueAsync(cancellationToken).ConfigureAwait(false); + if (assemblySymbol is null) + return ImmutableArray.Empty; + Bind(index, assemblySymbol.GlobalNamespace, ref results.AsRef(), cancellationToken); } }