Skip to content

Commit

Permalink
Merge pull request #65655 from CyrusNajmabadi/addUsing
Browse files Browse the repository at this point in the history
Avoid producing compilations in Add-Import when we can prove there are no results in that project.
  • Loading branch information
CyrusNajmabadi committed Nov 29, 2022
2 parents f38dbf8 + 3b2153c commit 2c896d2
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 81 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ internal abstract partial class AbstractAddImportFeatureService<TSimpleNameSynta
/// </summary>
private class SourceSymbolsProjectSearchScope : ProjectSearchScope
{
private readonly ConcurrentDictionary<Project, AsyncLazy<IAssemblySymbol>> _projectToAssembly;
private readonly ConcurrentDictionary<Project, AsyncLazy<IAssemblySymbol?>> _projectToAssembly;

public SourceSymbolsProjectSearchScope(
AbstractAddImportFeatureService<TSimpleNameSyntax> provider,
ConcurrentDictionary<Project, AsyncLazy<IAssemblySymbol>> projectToAssembly,
ConcurrentDictionary<Project, AsyncLazy<IAssemblySymbol?>> projectToAssembly,
Project project, bool ignoreCase, CancellationToken cancellationToken)
: base(provider, project, ignoreCase, cancellationToken)
{
Expand Down Expand Up @@ -53,15 +53,12 @@ protected override async Task<ImmutableArray<ISymbol>> FindDeclarationsAsync(

return declarations;

static AsyncLazy<IAssemblySymbol> CreateLazyAssembly(Project project)
{
return new AsyncLazy<IAssemblySymbol>(
async c =>
{
var compilation = await project.GetRequiredCompilationAsync(c).ConfigureAwait(false);
return compilation.Assembly;
}, cacheResult: true);
}
static AsyncLazy<IAssemblySymbol?> CreateLazyAssembly(Project project)
=> new(async c =>
{
var compilation = await project.GetRequiredCompilationAsync(c).ConfigureAwait(false);
return compilation.Assembly;
}, cacheResult: true);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,11 @@ namespace Microsoft.CodeAnalysis.FindSymbols
{
internal static partial class DeclarationFinder
{
private static Task AddCompilationDeclarationsWithNormalQueryAsync(
Project project, SearchQuery query, SymbolFilter filter,
ArrayBuilder<ISymbol> 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<ISymbol> list,
Compilation? startingCompilation,
IAssemblySymbol? startingAssembly,
CancellationToken cancellationToken)
{
if (!project.SupportsCompilation)
Expand Down Expand Up @@ -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<IAssemblySymbol?> lazyAssembly,
PortableExecutableReference reference,
SearchQuery query,
SymbolFilter filter,
ArrayBuilder<ISymbol> list,
Expand All @@ -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);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -58,51 +59,97 @@ public static async Task<ImmutableArray<ISymbol>> FindAllDeclarationsWithNormalQ
internal static async Task<ImmutableArray<ISymbol>> FindAllDeclarationsWithNormalQueryInCurrentProcessAsync(
Project project, SearchQuery query, SymbolFilter criteria, CancellationToken cancellationToken)
{
using var _1 = ArrayBuilder<ISymbol>.GetInstance(out var buffer);
using var _2 = ArrayBuilder<ISymbol>.GetInstance(out var result);
using var _ = ArrayBuilder<ISymbol>.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<Compilation>(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<ISymbol>.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<ISymbol>.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<PortableExecutableReference>())
{
using var _ = ArrayBuilder<ISymbol>.GetInstance(out var buffer);

var lazyAssembly = new AsyncLazy<IAssemblySymbol?>(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<ISymbol> 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<ImmutableArray<ISymbol>> RehydrateAsync(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ internal static async Task<ImmutableArray<ISymbol>> FindSourceDeclarationsWithNo
using var _ = ArrayBuilder<ISymbol>.GetInstance(out var result);
foreach (var project in solution.Projects)
{
await AddCompilationDeclarationsWithNormalQueryAsync(
await AddCompilationSourceDeclarationsWithNormalQueryAsync(
project, query, criteria, result, cancellationToken).ConfigureAwait(false);
}

Expand All @@ -195,7 +195,7 @@ internal static async Task<ImmutableArray<ISymbol>> FindSourceDeclarationsWithNo
using var _ = ArrayBuilder<ISymbol>.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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 "[]"
/// </summary>
private readonly MultiDictionary<string, ExtensionMethodInfo>? _receiverTypeNameToExtensionMethodMap;

Expand Down Expand Up @@ -126,11 +126,11 @@ public Task<ImmutableArray<ISymbol>> FindAsync(
Contract.ThrowIfTrue(query.Kind == SearchKind.Custom, "Custom queries are not supported in this API");

return this.FindAsync(
query, new AsyncLazy<IAssemblySymbol>(assembly), filter, cancellationToken);
query, new AsyncLazy<IAssemblySymbol?>(assembly), filter, cancellationToken);
}

public async Task<ImmutableArray<ISymbol>> FindAsync(
SearchQuery query, AsyncLazy<IAssemblySymbol> lazyAssembly,
SearchQuery query, AsyncLazy<IAssemblySymbol?> lazyAssembly,
SymbolFilter filter, CancellationToken cancellationToken)
{
// All entrypoints to this function are Find functions that are only searching
Expand All @@ -143,7 +143,7 @@ public async Task<ImmutableArray<ISymbol>> FindAsync(
}

private Task<ImmutableArray<ISymbol>> FindCoreAsync(
SearchQuery query, AsyncLazy<IAssemblySymbol> lazyAssembly, CancellationToken cancellationToken)
SearchQuery query, AsyncLazy<IAssemblySymbol?> 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).
Expand All @@ -168,7 +168,7 @@ private Task<ImmutableArray<ISymbol>> FindCoreAsync(
/// Finds symbols in this assembly that match the provided name in a fuzzy manner.
/// </summary>
private async Task<ImmutableArray<ISymbol>> FuzzyFindAsync(
AsyncLazy<IAssemblySymbol> lazyAssembly, string name, CancellationToken cancellationToken)
AsyncLazy<IAssemblySymbol?> lazyAssembly, string name, CancellationToken cancellationToken)
{
var similarNames = _spellChecker.FindSimilarWords(name, substringsAreSimilar: false);
var result = ArrayBuilder<ISymbol>.GetInstance();
Expand All @@ -186,7 +186,7 @@ private async Task<ImmutableArray<ISymbol>> FuzzyFindAsync(
/// Get all symbols that have a name matching the specified name.
/// </summary>
private async Task<ImmutableArray<ISymbol>> FindAsync(
AsyncLazy<IAssemblySymbol> lazyAssembly,
AsyncLazy<IAssemblySymbol?> lazyAssembly,
string name,
bool ignoreCase,
CancellationToken cancellationToken)
Expand All @@ -206,6 +206,9 @@ private async Task<ImmutableArray<ISymbol>> FindAsync(
if (ignoreCase || StringComparer.Ordinal.Equals(name, node.Name))
{
assemblySymbol ??= await lazyAssembly.GetValueAsync(cancellationToken).ConfigureAwait(false);
if (assemblySymbol is null)
return ImmutableArray<ISymbol>.Empty;

Bind(index, assemblySymbol.GlobalNamespace, ref results.AsRef(), cancellationToken);
}
}
Expand Down

0 comments on commit 2c896d2

Please sign in to comment.