Skip to content

Commit

Permalink
Merge pull request #63449 from CyrusNajmabadi/betterAddUsing
Browse files Browse the repository at this point in the history
Improve cross-project add-using results
  • Loading branch information
CyrusNajmabadi authored Aug 23, 2022
2 parents 7ecb817 + 4ed3696 commit a128904
Show file tree
Hide file tree
Showing 13 changed files with 211 additions and 244 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,12 @@ protected override async Task<ImmutableArray<ISymbol>> FindDeclarationsAsync(
SymbolFilter filter, SearchQuery searchQuery)
{
var service = _solution.Services.GetService<SymbolTreeInfoCacheService>();
var info = await service.TryGetMetadataSymbolTreeInfoAsync(_solution, _metadataReference, CancellationToken).ConfigureAwait(false);
var info = await service.TryGetPotentiallyStaleMetadataSymbolTreeInfoAsync(_solution, _metadataReference, CancellationToken).ConfigureAwait(false);
if (info == null)
{
return ImmutableArray<ISymbol>.Empty;
}

var declarations = await info.FindAsync(
searchQuery, _assembly,
filter, CancellationToken).ConfigureAwait(false);
searchQuery, _assembly, filter, CancellationToken).ConfigureAwait(false);

return declarations;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ protected override async Task<ImmutableArray<ISymbol>> FindDeclarationsAsync(
SymbolFilter filter, SearchQuery searchQuery)
{
var service = _project.Solution.Services.GetService<SymbolTreeInfoCacheService>();
var info = await service.TryGetSourceSymbolTreeInfoAsync(_project, CancellationToken).ConfigureAwait(false);
var info = await service.TryGetPotentiallyStaleSourceSymbolTreeInfoAsync(_project, CancellationToken).ConfigureAwait(false);
if (info == null)
{
// Looks like there was nothing in the cache. Return no results for now.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ private bool TryGetUpToDateCacheForPEReference(
cacheEntry = CreateCacheWorker(
GetPEReferenceCacheKey(peReference)!,
assemblySymbol,
checksum: SymbolTreeInfo.GetMetadataChecksum(solution, peReference, cancellationToken),
checksum: SymbolTreeInfo.GetMetadataChecksum(solution.Services, peReference, cancellationToken),
CacheService.PEItemsCache,
editorBrowsableInfo,
cancellationToken);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public static async ValueTask UpdateCacheAsync(Project project, CancellationToke
await GetUpToDateCacheEntryAsync(relevantProject, cacheService, cancellationToken).ConfigureAwait(false);

foreach (var peReference in GetAllRelevantPeReferences(project))
await SymbolTreeInfo.GetInfoForMetadataReferenceAsync(project.Solution, peReference, loadOnly: false, cancellationToken).ConfigureAwait(false);
await SymbolTreeInfo.GetInfoForMetadataReferenceAsync(project.Solution, peReference, cancellationToken).ConfigureAwait(false);
}

public async Task<(ImmutableArray<IMethodSymbol> symbols, bool isPartialResult)> GetExtensionMethodSymbolsAsync(bool forceCacheCreation, bool hideAdvancedMembers, CancellationToken cancellationToken)
Expand Down Expand Up @@ -199,7 +199,7 @@ private static ImmutableArray<PortableExecutableReference> GetAllRelevantPeRefer
if (forceCacheCreation)
{
symbolInfo = await SymbolTreeInfo.GetInfoForMetadataReferenceAsync(
_originatingDocument.Project.Solution, peReference, loadOnly: false, cancellationToken).ConfigureAwait(false);
_originatingDocument.Project.Solution, peReference, cancellationToken).ConfigureAwait(false);
}
else
{
Expand Down
2 changes: 1 addition & 1 deletion src/Tools/AnalyzerRunner/IncrementalAnalyzerRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public async Task RunAsync(CancellationToken cancellationToken)
{
case nameof(SymbolTreeInfoIncrementalAnalyzerProvider):
var symbolTreeInfoCacheService = _workspace.Services.GetRequiredService<SymbolTreeInfoCacheService>();
var symbolTreeInfo = await symbolTreeInfoCacheService.TryGetSourceSymbolTreeInfoAsync(_workspace.CurrentSolution.Projects.First(), cancellationToken).ConfigureAwait(false);
var symbolTreeInfo = await symbolTreeInfoCacheService.TryGetPotentiallyStaleSourceSymbolTreeInfoAsync(_workspace.CurrentSolution.Projects.First(), cancellationToken).ConfigureAwait(false);
if (symbolTreeInfo is null)
{
throw new InvalidOperationException("Benchmark failed to calculate symbol tree info.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,8 @@ private static async Task AddMetadataDeclarationsWithNormalQueryAsync(
if (reference != null)
{
var info = await SymbolTreeInfo.GetInfoForMetadataReferenceAsync(
project.Solution, reference, loadOnly: false, cancellationToken).ConfigureAwait(false);
project.Solution, reference, cancellationToken).ConfigureAwait(false);

// We must have an index since we passed in loadOnly: false here.
Contract.ThrowIfNull(info);

var symbols = await info.FindAsync(query, assembly, filter, cancellationToken).ConfigureAwait(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -407,15 +407,13 @@ private static async Task AddMatchingMetadataTypesInMetadataReferenceAsync(
{
cancellationToken.ThrowIfCancellationRequested();

// We store an index in SymbolTreeInfo of the *simple* metadata type name
// to the names of the all the types that either immediately derive or
// implement that type. Because the mapping is from the simple name
// we might get false positives. But that's fine as we still use
// 'tpeMatches' to make sure the match is correct.
// We store an index in SymbolTreeInfo of the *simple* metadata type name to the names of the all the types
// that either immediately derive or implement that type. Because the mapping is from the simple name we
// might get false positives. But that's fine as we still use 'tpeMatches' to make sure the match is
// correct.
var symbolTreeInfo = await SymbolTreeInfo.GetInfoForMetadataReferenceAsync(
project.Solution, reference, loadOnly: false, cancellationToken: cancellationToken).ConfigureAwait(false);
project.Solution, reference, cancellationToken).ConfigureAwait(false);

// This will always be non-null since we pass loadOnly: false above.
Contract.ThrowIfNull(symbolTreeInfo);

// For each type we care about, see if we can find any derived types
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@

namespace Microsoft.CodeAnalysis.FindSymbols
{
/// <summary>
/// Represents a tree of names of the namespaces, types (and members within those types) within a <see
/// cref="Project"/> or <see cref="PortableExecutableReference"/>. This tree can be used to quickly determine if
/// there is a name match, and can provide the named path to that named entity. This path can then be used to
/// produce a corresponding <see cref="ISymbol"/> that can be used by a feature. The primary purpose of this index
/// is to allow features to quickly determine that there is <em>no</em> name match, so that acquiring symbols is not
/// necessary. The secondary purpose is to generate a minimal set of symbols when there is a match, though that
/// will still incur a heavy cost (for example, getting the <see cref="IAssemblySymbol"/> root symbol for a
/// particular project).
/// </summary>
internal partial class SymbolTreeInfo : IChecksummedObject
{
public Checksum Checksum { get; }
Expand Down Expand Up @@ -62,17 +72,7 @@ public MultiDictionary<string, ExtensionMethodInfo>.ValueSet GetExtensionMethodI

public bool ContainsExtensionMethod => _receiverTypeNameToExtensionMethodMap?.Count > 0;

/// <summary>
/// The task that produces the spell checker we use for fuzzy match queries.
/// We use a task so that we can generate the <see cref="SymbolTreeInfo"/>
/// without having to wait for the spell checker construction to finish.
///
/// Features that don't need fuzzy matching don't want to incur the cost of
/// the creation of this value. And the only feature which does want fuzzy
/// matching (add-using) doesn't want to block waiting for the value to be
/// created.
/// </summary>
private readonly Task<SpellChecker> _spellCheckerTask;
private readonly SpellChecker _spellChecker;

private static readonly StringSliceComparer s_caseInsensitiveComparer =
StringSliceComparer.OrdinalIgnoreCase;
Expand All @@ -96,10 +96,10 @@ public MultiDictionary<string, ExtensionMethodInfo>.ValueSet GetExtensionMethodI
private SymbolTreeInfo(
Checksum checksum,
ImmutableArray<Node> sortedNodes,
Task<SpellChecker> spellCheckerTask,
SpellChecker spellChecker,
OrderPreservingMultiDictionary<string, string> inheritanceMap,
MultiDictionary<string, ExtensionMethodInfo>? receiverTypeNameToExtensionMethodMap)
: this(checksum, sortedNodes, spellCheckerTask,
: this(checksum, sortedNodes, spellChecker,
CreateIndexBasedInheritanceMap(sortedNodes, inheritanceMap),
receiverTypeNameToExtensionMethodMap)
{
Expand All @@ -108,13 +108,13 @@ private SymbolTreeInfo(
private SymbolTreeInfo(
Checksum checksum,
ImmutableArray<Node> sortedNodes,
Task<SpellChecker> spellCheckerTask,
SpellChecker spellChecker,
OrderPreservingMultiDictionary<int, int> inheritanceMap,
MultiDictionary<string, ExtensionMethodInfo>? receiverTypeNameToExtensionMethodMap)
{
Checksum = checksum;
_nodes = sortedNodes;
_spellCheckerTask = spellCheckerTask;
_spellChecker = spellChecker;
_inheritanceMap = inheritanceMap;
_receiverTypeNameToExtensionMethodMap = receiverTypeNameToExtensionMethodMap;
}
Expand All @@ -125,15 +125,15 @@ public static SymbolTreeInfo CreateEmpty(Checksum checksum)
SortNodes(unsortedNodes, out var sortedNodes);

return new SymbolTreeInfo(checksum, sortedNodes,
CreateSpellCheckerAsync(checksum, sortedNodes),
CreateSpellChecker(checksum, sortedNodes),
new OrderPreservingMultiDictionary<string, string>(),
new MultiDictionary<string, ExtensionMethodInfo>());
}

public SymbolTreeInfo WithChecksum(Checksum checksum)
{
return new SymbolTreeInfo(
checksum, _nodes, _spellCheckerTask, _inheritanceMap, _receiverTypeNameToExtensionMethodMap);
checksum, _nodes, _spellChecker, _inheritanceMap, _receiverTypeNameToExtensionMethodMap);
}

public Task<ImmutableArray<ISymbol>> FindAsync(
Expand Down Expand Up @@ -188,14 +188,7 @@ private Task<ImmutableArray<ISymbol>> FindCoreAsync(
private async Task<ImmutableArray<ISymbol>> FuzzyFindAsync(
AsyncLazy<IAssemblySymbol> lazyAssembly, string name, CancellationToken cancellationToken)
{
if (_spellCheckerTask.Status != TaskStatus.RanToCompletion)
{
// Spell checker isn't ready. Just return immediately.
return ImmutableArray<ISymbol>.Empty;
}

var spellChecker = await _spellCheckerTask.ConfigureAwait(false);
var similarNames = spellChecker.FindSimilarWords(name, substringsAreSimilar: false);
var similarNames = _spellChecker.FindSimilarWords(name, substringsAreSimilar: false);
var result = ArrayBuilder<ISymbol>.GetInstance();

foreach (var similarName in similarNames)
Expand Down Expand Up @@ -333,22 +326,8 @@ private static int BinarySearch(ImmutableArray<Node> nodes, string name)
/// </summary>
private static readonly ConditionalWeakTable<MetadataId, AsyncLazy<SymbolTreeInfo>> s_metadataIdToInfo = new();

private static Task<SpellChecker> GetSpellCheckerAsync(
SolutionServices services, SolutionKey solutionKey, Checksum checksum, string filePath, ImmutableArray<Node> sortedNodes)
{
// Create a new task to attempt to load or create the spell checker for this
// SymbolTreeInfo. This way the SymbolTreeInfo will be ready immediately
// for non-fuzzy searches, and soon afterwards it will be able to perform
// fuzzy searches as well.
return Task.Run(() => LoadOrCreateSpellCheckerAsync(services, solutionKey, checksum, filePath, sortedNodes));
}

private static Task<SpellChecker> CreateSpellCheckerAsync(
Checksum checksum, ImmutableArray<Node> sortedNodes)
{
return Task.FromResult(new SpellChecker(
checksum, sortedNodes.Select(n => n.Name.AsMemory())));
}
private static SpellChecker CreateSpellChecker(Checksum checksum, ImmutableArray<Node> sortedNodes)
=> new(checksum, sortedNodes.Select(n => n.Name.AsMemory()));

private static void SortNodes(
ImmutableArray<BuilderNode> unsortedNodes,
Expand Down Expand Up @@ -497,17 +476,16 @@ internal void AssertEquivalentTo(SymbolTreeInfo other)
}

private static SymbolTreeInfo CreateSymbolTreeInfo(
SolutionServices services, SolutionKey solutionKey, Checksum checksum,
string filePath, ImmutableArray<BuilderNode> unsortedNodes,
Checksum checksum,
ImmutableArray<BuilderNode> unsortedNodes,
OrderPreservingMultiDictionary<string, string> inheritanceMap,
MultiDictionary<string, ExtensionMethodInfo>? receiverTypeNameToExtensionMethodMap)
{
SortNodes(unsortedNodes, out var sortedNodes);
var createSpellCheckerTask = GetSpellCheckerAsync(
services, solutionKey, checksum, filePath, sortedNodes);
var spellChecker = CreateSpellChecker(checksum, sortedNodes);

return new SymbolTreeInfo(
checksum, sortedNodes, createSpellCheckerTask, inheritanceMap, receiverTypeNameToExtensionMethodMap);
checksum, sortedNodes, spellChecker, inheritanceMap, receiverTypeNameToExtensionMethodMap);
}

private static OrderPreservingMultiDictionary<int, int> CreateIndexBasedInheritanceMap(
Expand Down
Loading

0 comments on commit a128904

Please sign in to comment.