diff --git a/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService.ProgressAdapter.cs b/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService.ProgressAdapter.cs index 232b266014dab..4e3563c936b61 100644 --- a/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService.ProgressAdapter.cs +++ b/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService.ProgressAdapter.cs @@ -5,6 +5,7 @@ #nullable disable using System.Collections.Generic; +using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Classification; @@ -73,8 +74,6 @@ public IStreamingProgressTracker ProgressTracker // any of these. public ValueTask OnStartedAsync(CancellationToken cancellationToken) => default; public ValueTask OnCompletedAsync(CancellationToken cancellationToken) => default; - public ValueTask OnFindInDocumentStartedAsync(Document document, CancellationToken cancellationToken) => default; - public ValueTask OnFindInDocumentCompletedAsync(Document document, CancellationToken cancellationToken) => default; // More complicated forwarding functions. These need to map from the symbols // used by the FAR engine to the INavigableItems used by the streaming FAR @@ -107,17 +106,21 @@ public async ValueTask OnDefinitionFoundAsync(SymbolGroup group, CancellationTok await context.OnDefinitionFoundAsync(definitionItem, cancellationToken).ConfigureAwait(false); } - public async ValueTask OnReferenceFoundAsync(SymbolGroup group, ISymbol definition, ReferenceLocation location, CancellationToken cancellationToken) + public async ValueTask OnReferencesFoundAsync( + ImmutableArray<(SymbolGroup group, ISymbol symbol, ReferenceLocation location)> references, CancellationToken cancellationToken) { - var definitionItem = await GetDefinitionItemAsync(group, cancellationToken).ConfigureAwait(false); - var referenceItem = await location.TryCreateSourceReferenceItemAsync( - classificationOptions, - definitionItem, - includeHiddenLocations: false, - cancellationToken).ConfigureAwait(false); - - if (referenceItem != null) - await context.OnReferenceFoundAsync(referenceItem, cancellationToken).ConfigureAwait(false); + foreach (var (group, _, location) in references) + { + var definitionItem = await GetDefinitionItemAsync(group, cancellationToken).ConfigureAwait(false); + var referenceItem = await location.TryCreateSourceReferenceItemAsync( + classificationOptions, + definitionItem, + includeHiddenLocations: false, + cancellationToken).ConfigureAwait(false); + + if (referenceItem != null) + await context.OnReferenceFoundAsync(referenceItem, cancellationToken).ConfigureAwait(false); + } } } } diff --git a/src/Features/Core/Portable/GoToBase/AbstractGoToBaseService.cs b/src/Features/Core/Portable/GoToBase/AbstractGoToBaseService.cs index 28ced7c05cc82..851d23ed45f4c 100644 --- a/src/Features/Core/Portable/GoToBase/AbstractGoToBaseService.cs +++ b/src/Features/Core/Portable/GoToBase/AbstractGoToBaseService.cs @@ -44,7 +44,7 @@ await context.ReportNoResultsAsync( var (symbol, project) = symbolAndProjectOpt.Value; var solution = project.Solution; - var bases = await FindBaseHelpers.FindBasesAsync(symbol, solution, cancellationToken).ConfigureAwait(false); + var bases = FindBaseHelpers.FindBases(symbol, solution, cancellationToken); if (bases.Length == 0 && symbol is IMethodSymbol { MethodKind: MethodKind.Constructor } constructor) { var nextConstructor = await FindNextConstructorInChainAsync(solution, constructor, cancellationToken).ConfigureAwait(false); diff --git a/src/Features/Core/Portable/GoToBase/FindBaseHelpers.cs b/src/Features/Core/Portable/GoToBase/FindBaseHelpers.cs index b7ec56faeae7f..25a40bdc1271f 100644 --- a/src/Features/Core/Portable/GoToBase/FindBaseHelpers.cs +++ b/src/Features/Core/Portable/GoToBase/FindBaseHelpers.cs @@ -14,7 +14,7 @@ namespace Microsoft.CodeAnalysis.GoToBase; internal static class FindBaseHelpers { - public static ValueTask> FindBasesAsync( + public static ImmutableArray FindBases( ISymbol symbol, Solution solution, CancellationToken cancellationToken) { if (symbol is INamedTypeSymbol @@ -23,16 +23,12 @@ public static ValueTask> FindBasesAsync( } namedTypeSymbol) { var result = BaseTypeFinder.FindBaseTypesAndInterfaces(namedTypeSymbol).CastArray(); - return ValueTaskFactory.FromResult(result); + return result; } - if (symbol.Kind is SymbolKind.Property or - SymbolKind.Method or - SymbolKind.Event) - { - return BaseTypeFinder.FindOverriddenAndImplementedMembersAsync(symbol, solution, cancellationToken); - } + if (symbol.Kind is SymbolKind.Property or SymbolKind.Method or SymbolKind.Event) + return BaseTypeFinder.FindOverriddenAndImplementedMembers(symbol, solution, cancellationToken); - return ValueTaskFactory.FromResult(ImmutableArray.Empty); + return []; } } diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs index 95513ef74c32a..faba6677a2c24 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs @@ -14,7 +14,7 @@ using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.PatternMatching; using Microsoft.CodeAnalysis.Remote; -using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Storage; using Roslyn.Utilities; @@ -136,10 +136,8 @@ async ValueTask ProcessSingleProjectGroupAsync( var project = group.Key; // Break the project into high-pri docs and low pri docs, and process in that order. - var highPriDocs = group.Where(priorityDocumentKeysSet.Contains); - var lowPriDocs = group.Where(d => !highPriDocs.Contains(d)); - await ParallelForEachAsync( - highPriDocs.Concat(lowPriDocs), + await RoslynParallel.ForEachAsync( + Prioritize(group, priorityDocumentKeysSet.Contains), cancellationToken, async (documentKey, cancellationToken) => { diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs index e17ff05e12ab7..cc28b997adc84 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.PatternMatching; using Microsoft.CodeAnalysis.Remote; +using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.NavigateTo; @@ -75,7 +76,7 @@ async ValueTask ProcessSingleProjectAsync( // First generate all the source-gen docs. Then handoff to the standard search routine to find matches in them. var sourceGeneratedDocs = await project.GetSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false); - await ParallelForEachAsync( + await RoslynParallel.ForEachAsync( sourceGeneratedDocs, cancellationToken, (document, cancellationToken) => SearchSingleDocumentAsync( diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs index 0a6cd7a549ce5..e76b1cb7aa5cc 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.PatternMatching; using Microsoft.CodeAnalysis.Remote; +using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.NavigateTo; @@ -131,7 +132,7 @@ async ValueTask SearchSingleProjectAsync( { using var _ = GetPooledHashSet(priorityDocuments.Where(d => project == d.Project), out var highPriDocs); - await ParallelForEachAsync( + await RoslynParallel.ForEachAsync( Prioritize(project.Documents, highPriDocs.Contains), cancellationToken, (document, cancellationToken) => SearchSingleDocumentAsync( diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs index 3fc2865d93ed1..3cb8f1ad23fe0 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs @@ -9,6 +9,7 @@ using System.Threading.Channels; using System.Threading.Tasks; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.NavigateTo; @@ -140,34 +141,7 @@ async Task FindAllItemsAndWriteToChannelAsync() } Task PerformSearchAsync(Action onItemFound) - => ParallelForEachAsync( + => RoslynParallel.ForEachAsync( items, cancellationToken, (item, cancellationToken) => callback(item, onItemFound, cancellationToken)); } - -#pragma warning disable CA1068 // CancellationToken parameters must come last - private static async Task ParallelForEachAsync( -#pragma warning restore CA1068 // CancellationToken parameters must come last - IEnumerable source, - CancellationToken cancellationToken, - Func body) - { - if (cancellationToken.IsCancellationRequested) - return; - -#if NET - await Parallel.ForEachAsync(source, cancellationToken, body).ConfigureAwait(false); -#else - using var _ = ArrayBuilder.GetInstance(out var tasks); - - foreach (var item in source) - { - tasks.Add(Task.Run(async () => - { - await body(item, cancellationToken).ConfigureAwait(false); - }, cancellationToken)); - } - - await Task.WhenAll(tasks).ConfigureAwait(false); -#endif - } } diff --git a/src/Features/Core/Portable/ValueTracking/ValueTracker.FindReferencesProgress.cs b/src/Features/Core/Portable/ValueTracking/ValueTracker.FindReferencesProgress.cs index 91ae027a843b9..c730ff32c5eb8 100644 --- a/src/Features/Core/Portable/ValueTracking/ValueTracker.FindReferencesProgress.cs +++ b/src/Features/Core/Portable/ValueTracking/ValueTracker.FindReferencesProgress.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.FindSymbols; @@ -28,11 +29,16 @@ private class FindReferencesProgress(OperationCollector valueTrackingProgressCol public ValueTask OnDefinitionFoundAsync(SymbolGroup symbolGroup, CancellationToken _) => new(); - public ValueTask OnFindInDocumentCompletedAsync(Document document, CancellationToken _) => new(); - - public ValueTask OnFindInDocumentStartedAsync(Document document, CancellationToken _) => new(); + public async ValueTask OnReferencesFoundAsync( + ImmutableArray<(SymbolGroup group, ISymbol symbol, ReferenceLocation location)> references, + CancellationToken cancellationToken) + { + foreach (var (_, symbol, location) in references) + await OnReferenceFoundAsync(symbol, location, cancellationToken).ConfigureAwait(false); + } - public async ValueTask OnReferenceFoundAsync(SymbolGroup _, ISymbol symbol, ReferenceLocation location, CancellationToken cancellationToken) + private async ValueTask OnReferenceFoundAsync( + ISymbol symbol, ReferenceLocation location, CancellationToken cancellationToken) { if (!location.Location.IsInSource) { diff --git a/src/Features/LanguageServer/Protocol/Handler/Symbols/WorkspaceSymbolsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Symbols/WorkspaceSymbolsHandler.cs index 0c05d18f61007..f0ff5255f831b 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Symbols/WorkspaceSymbolsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Symbols/WorkspaceSymbolsHandler.cs @@ -73,10 +73,10 @@ private sealed class LSPNavigateToCallback( public async Task AddResultsAsync(ImmutableArray results, CancellationToken cancellationToken) { Contract.ThrowIfNull(context.Solution); + var solution = context.Solution; foreach (var result in results) { - var solution = context.Solution; var document = await result.NavigableItem.Document.GetRequiredDocumentAsync(solution, cancellationToken).ConfigureAwait(false); var location = await ProtocolConversions.TextSpanToLocationAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/BaseTypeFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/BaseTypeFinder.cs index 5ac66337bfae1..6ece9e7edac9e 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/BaseTypeFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/BaseTypeFinder.cs @@ -15,10 +15,10 @@ internal static partial class BaseTypeFinder public static ImmutableArray FindBaseTypesAndInterfaces(INamedTypeSymbol type) => FindBaseTypes(type).AddRange(type.AllInterfaces); - public static async ValueTask> FindOverriddenAndImplementedMembersAsync( + public static ImmutableArray FindOverriddenAndImplementedMembers( ISymbol symbol, Solution solution, CancellationToken cancellationToken) { - var results = ArrayBuilder.GetInstance(); + using var _ = ArrayBuilder.GetInstance(out var results); // This is called for all: class, struct or interface member. results.AddRange(symbol.ExplicitOrImplicitInterfaceImplementations()); @@ -31,7 +31,7 @@ public static async ValueTask> FindOverriddenAndImplemen cancellationToken.ThrowIfCancellationRequested(); // Add to results overridden members only. Do not add hidden members. - if (await SymbolFinder.IsOverrideAsync(solution, symbol, member, cancellationToken).ConfigureAwait(false)) + if (SymbolFinder.IsOverride(solution, symbol, member)) { results.Add(member); @@ -64,7 +64,8 @@ public static async ValueTask> FindOverriddenAndImplemen } // Remove duplicates from interface implementations before adding their projects. - return results.ToImmutableAndFree().Distinct(); + results.RemoveDuplicates(); + return results.ToImmutableAndClear(); } private static ImmutableArray FindBaseTypes(INamedTypeSymbol type) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs index aaad258c3e4e3..ba3cdc5889a6e 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs @@ -32,17 +32,27 @@ public static async ValueTask GetCacheAsync(Document documen static async Task ComputeCacheAsync(Document document, CancellationToken cancellationToken) { + var text = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false); + // Find-Refs is not impacted by nullable types at all. So get a nullable-disabled semantic model to avoid // unnecessary costs while binding. var model = await document.GetRequiredNullableDisabledSemanticModelAsync(cancellationToken).ConfigureAwait(false); var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - return new(document, model, root); + + // 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 index = await SyntaxTreeIndex.GetRequiredIndexAsync(document, cancellationToken).ConfigureAwait(false); + + return new(document, text, model, root, index); } } public readonly Document Document; + public readonly SourceText Text; public readonly SemanticModel SemanticModel; public readonly SyntaxNode Root; + public readonly ISyntaxFactsService SyntaxFacts; + public readonly SyntaxTreeIndex SyntaxTreeIndex; private readonly ConcurrentDictionary _symbolInfoCache = []; private readonly ConcurrentDictionary> _identifierCache; @@ -50,11 +60,16 @@ static async Task ComputeCacheAsync(Document document, Cance private ImmutableHashSet? _aliasNameSet; private ImmutableArray _constructorInitializerCache; - private FindReferenceCache(Document document, SemanticModel semanticModel, SyntaxNode root) + private FindReferenceCache( + Document document, SourceText text, SemanticModel semanticModel, SyntaxNode root, SyntaxTreeIndex syntaxTreeIndex) { Document = document; + Text = text; SemanticModel = semanticModel; Root = root; + SyntaxTreeIndex = syntaxTreeIndex; + SyntaxFacts = document.GetRequiredLanguageService(); + _identifierCache = new(comparer: semanticModel.Language switch { LanguageNames.VisualBasic => StringComparer.OrdinalIgnoreCase, @@ -81,10 +96,8 @@ public SymbolInfo GetSymbolInfo(SyntaxNode node, CancellationToken cancellationT return null; } - public async ValueTask> FindMatchingIdentifierTokensAsync( - Document document, - string identifier, - CancellationToken cancellationToken) + public ImmutableArray FindMatchingIdentifierTokens( + string identifier, CancellationToken cancellationToken) { if (identifier == "") { @@ -99,103 +112,82 @@ public async ValueTask> FindMatchingIdentifierTokens if (_identifierCache.TryGetValue(identifier, out var result)) return result; - // 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.GetRequiredIndexAsync(document, cancellationToken).ConfigureAwait(false); - // If this document doesn't even contain this identifier (escaped or non-escaped) we don't have to search it at all. - if (!info.ProbablyContainsIdentifier(identifier)) + if (!this.SyntaxTreeIndex.ProbablyContainsIdentifier(identifier)) return []; - return await ComputeAndCacheTokensAsync(this, document, identifier, info, cancellationToken).ConfigureAwait(false); + // If the identifier was escaped in the file then we'll have to do a more involved search that actually + // walks the root and checks all identifier tokens. + // + // otherwise, we can use the text of the document to quickly find candidates and test those directly. + return this.SyntaxTreeIndex.ProbablyContainsEscapedIdentifier(identifier) + ? _identifierCache.GetOrAdd(identifier, identifier => FindMatchingIdentifierTokensFromTree(identifier, cancellationToken)) + : _identifierCache.GetOrAdd(identifier, _ => FindMatchingIdentifierTokensFromText(identifier, cancellationToken)); + } + + private bool IsMatch(string identifier, SyntaxToken token) + => !token.IsMissing && this.SyntaxFacts.IsIdentifier(token) && this.SyntaxFacts.TextMatch(token.ValueText, identifier); + + private ImmutableArray FindMatchingIdentifierTokensFromTree( + string identifier, CancellationToken cancellationToken) + { + using var _ = ArrayBuilder.GetInstance(out var result); + using var obj = SharedPools.Default>().GetPooledObject(); + + var stack = obj.Object; + stack.Push(this.Root); - static async ValueTask> ComputeAndCacheTokensAsync( - FindReferenceCache cache, Document document, string identifier, SyntaxTreeIndex info, CancellationToken cancellationToken) + while (stack.TryPop(out var current)) { - var syntaxFacts = document.GetRequiredLanguageService(); - var root = await cache.SemanticModel.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false); - - // If the identifier was escaped in the file then we'll have to do a more involved search that actually - // walks the root and checks all identifier tokens. - // - // otherwise, we can use the text of the document to quickly find candidates and test those directly. - if (info.ProbablyContainsEscapedIdentifier(identifier)) + cancellationToken.ThrowIfCancellationRequested(); + if (current.IsNode) { - return cache._identifierCache.GetOrAdd( - identifier, _ => FindMatchingIdentifierTokensFromTree(syntaxFacts, identifier, root)); + foreach (var child in current.AsNode()!.ChildNodesAndTokens().Reverse()) + stack.Push(child); } - else + else if (current.IsToken) { - var text = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false); - return cache._identifierCache.GetOrAdd( - identifier, _ => FindMatchingIdentifierTokensFromText(syntaxFacts, identifier, root, text, cancellationToken)); - } - } - - static bool IsMatch(ISyntaxFactsService syntaxFacts, string identifier, SyntaxToken token) - => !token.IsMissing && syntaxFacts.IsIdentifier(token) && syntaxFacts.TextMatch(token.ValueText, identifier); - - static ImmutableArray FindMatchingIdentifierTokensFromTree( - ISyntaxFactsService syntaxFacts, string identifier, SyntaxNode root) - { - using var _ = ArrayBuilder.GetInstance(out var result); - using var obj = SharedPools.Default>().GetPooledObject(); - - var stack = obj.Object; - stack.Push(root); + var token = current.AsToken(); + if (IsMatch(identifier, token)) + result.Add(token); - while (stack.TryPop(out var current)) - { - if (current.IsNode) - { - foreach (var child in current.AsNode()!.ChildNodesAndTokens().Reverse()) - stack.Push(child); - } - else if (current.IsToken) + if (token.HasStructuredTrivia) { - var token = current.AsToken(); - if (IsMatch(syntaxFacts, identifier, token)) - result.Add(token); - - if (token.HasStructuredTrivia) + // structured trivia can only be leading trivia + foreach (var trivia in token.LeadingTrivia) { - // structured trivia can only be leading trivia - foreach (var trivia in token.LeadingTrivia) - { - if (trivia.HasStructure) - stack.Push(trivia.GetStructure()!); - } + if (trivia.HasStructure) + stack.Push(trivia.GetStructure()!); } } } - - return result.ToImmutableAndClear(); } - static ImmutableArray FindMatchingIdentifierTokensFromText( - ISyntaxFactsService syntaxFacts, string identifier, SyntaxNode root, SourceText sourceText, CancellationToken cancellationToken) - { - using var _ = ArrayBuilder.GetInstance(out var result); + return result.ToImmutableAndClear(); + } - var index = 0; - while ((index = sourceText.IndexOf(identifier, index, syntaxFacts.IsCaseSensitive)) >= 0) - { - cancellationToken.ThrowIfCancellationRequested(); + private ImmutableArray FindMatchingIdentifierTokensFromText( + string identifier, CancellationToken cancellationToken) + { + using var _ = ArrayBuilder.GetInstance(out var result); - var token = root.FindToken(index, findInsideTrivia: true); - var span = token.Span; - if (span.Start == index && span.Length == identifier.Length && IsMatch(syntaxFacts, identifier, token)) - result.Add(token); + var index = 0; + while ((index = this.Text.IndexOf(identifier, index, this.SyntaxFacts.IsCaseSensitive)) >= 0) + { + cancellationToken.ThrowIfCancellationRequested(); - var nextIndex = index + identifier.Length; - nextIndex = Math.Max(nextIndex, token.SpanStart); - index = nextIndex; - } + var token = this.Root.FindToken(index, findInsideTrivia: true); + var span = token.Span; + if (span.Start == index && span.Length == identifier.Length && IsMatch(identifier, token)) + result.Add(token); - return result.ToImmutableAndClear(); + var nextIndex = index + identifier.Length; + nextIndex = Math.Max(nextIndex, token.SpanStart); + index = nextIndex; } - } + return result.ToImmutableAndClear(); + } public IEnumerable GetConstructorInitializerTokens( ISyntaxFactsService syntaxFacts, SyntaxNode root, CancellationToken cancellationToken) { diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index eb3a7e932959e..c1c5deeb751e5 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs @@ -7,7 +7,9 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using System.Runtime.CompilerServices; using System.Threading; +using System.Threading.Channels; using System.Threading.Tasks; using Microsoft.CodeAnalysis.FindSymbols.Finders; using Microsoft.CodeAnalysis.Internal.Log; @@ -18,9 +20,12 @@ namespace Microsoft.CodeAnalysis.FindSymbols; +using Reference = (SymbolGroup group, ISymbol symbol, ReferenceLocation location); + internal partial class FindReferencesSearchEngine { private static readonly ObjectPool s_metadataUnifyingSymbolHashSetPool = new(() => []); + private static readonly UnboundedChannelOptions s_channelOptions = new() { SingleReader = true }; private readonly Solution _solution; private readonly IImmutableSet? _documents; @@ -71,12 +76,62 @@ public Task FindReferencesAsync(ISymbol symbol, CancellationToken cancellationTo public async Task FindReferencesAsync( ImmutableArray symbols, CancellationToken cancellationToken) { - var unifiedSymbols = new MetadataUnifyingSymbolHashSet(); - unifiedSymbols.AddRange(symbols); + var channel = Channel.CreateUnbounded(s_channelOptions); await _progress.OnStartedAsync(cancellationToken).ConfigureAwait(false); try { + await Task.WhenAll( + FindAllReferencesAndWriteToChannelAsync(), + ReadReferencesFromChannelAndReportToCallbackAsync()).ConfigureAwait(false); + } + finally + { + await _progress.OnCompletedAsync(cancellationToken).ConfigureAwait(false); + } + + return; + + async Task ReadReferencesFromChannelAndReportToCallbackAsync() + { + await Task.Yield().ConfigureAwait(false); + using var _ = ArrayBuilder.GetInstance(out var references); + + while (await channel.Reader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) + { + // Grab as many items as we can from the channel at once and report in a batch. + while (channel.Reader.TryRead(out var reference)) + references.Add(reference); + + await _progress.OnReferencesFoundAsync(references.ToImmutableAndClear(), cancellationToken).ConfigureAwait(false); + } + } + + async Task FindAllReferencesAndWriteToChannelAsync() + { + Exception? exception = null; + try + { + await Task.Yield().ConfigureAwait(false); + await PerformSearchAsync(item => channel.Writer.TryWrite(item)).ConfigureAwait(false); + } + catch (Exception ex) when ((exception = ex) == null) + { + throw ExceptionUtilities.Unreachable(); + } + finally + { + // No matter what path we take (exceptional or non-exceptional), always complete the channel so the + // writing task knows it's done. + channel.Writer.TryComplete(exception); + } + } + + async ValueTask PerformSearchAsync(Action onReferenceFound) + { + var unifiedSymbols = new MetadataUnifyingSymbolHashSet(); + unifiedSymbols.AddRange(symbols); + var disposable = await _progressTracker.AddSingleItemAsync(cancellationToken).ConfigureAwait(false); await using var _ = disposable.ConfigureAwait(false); @@ -98,11 +153,22 @@ public async Task FindReferencesAsync( // while we're processing each project linearly to update the symbol set we're searching for, we still // then process the projects in parallel once we know the set of symbols we're searching for in that // project. - var dependencyGraph = _solution.GetProjectDependencyGraph(); await _progressTracker.AddItemsAsync(projectsToSearch.Length, cancellationToken).ConfigureAwait(false); - using var _1 = ArrayBuilder.GetInstance(out var tasks); + // Pull off and start searching each project as soon as we can once we've done the inheritance cascade into it. + await RoslynParallel.ForEachAsync( + GetProjectsAndSymbolsToSearchAsync(symbolSet, projectsToSearch, cancellationToken), + cancellationToken, + async (tuple, cancellationToken) => await ProcessProjectAsync( + tuple.project, tuple.allSymbols, onReferenceFound, cancellationToken).ConfigureAwait(false)).ConfigureAwait(false); + } + async IAsyncEnumerable<(Project project, ImmutableArray allSymbols)> GetProjectsAndSymbolsToSearchAsync( + SymbolSet symbolSet, + ImmutableArray projectsToSearch, + [EnumeratorCancellation] CancellationToken cancellationToken) + { + var dependencyGraph = _solution.GetProjectDependencyGraph(); foreach (var projectId in dependencyGraph.GetTopologicallySortedProjects(cancellationToken)) { var currentProject = _solution.GetRequiredProject(projectId); @@ -114,20 +180,13 @@ public async Task FindReferencesAsync( // which is why we do it in this loop and not inside the concurrent project processing that happens // below. await symbolSet.InheritanceCascadeAsync(currentProject, cancellationToken).ConfigureAwait(false); - allSymbols = symbolSet.GetAllSymbols(); + var allSymbols = symbolSet.GetAllSymbols(); // Report any new symbols we've cascaded to to our caller. await ReportGroupsAsync(allSymbols, cancellationToken).ConfigureAwait(false); - tasks.Add(CreateWorkAsync(() => ProcessProjectAsync(currentProject, allSymbols, cancellationToken), cancellationToken)); + yield return (currentProject, allSymbols); } - - // Now, wait for all projects to complete. - await Task.WhenAll(tasks).ConfigureAwait(false); - } - finally - { - await _progress.OnCompletedAsync(cancellationToken).ConfigureAwait(false); } } @@ -184,7 +243,8 @@ private Task> GetProjectsToSearchAsync( return DependentProjectsFinder.GetDependentProjectsAsync(_solution, symbols, projects, cancellationToken); } - private async Task ProcessProjectAsync(Project project, ImmutableArray allSymbols, CancellationToken cancellationToken) + private async ValueTask ProcessProjectAsync( + Project project, ImmutableArray allSymbols, Action onReferenceFound, CancellationToken cancellationToken) { using var _1 = PooledDictionary>.GetInstance(out var symbolToGlobalAliases); using var _2 = PooledDictionary.GetInstance(out var documentToSymbols); @@ -217,14 +277,14 @@ await finder.DetermineDocumentsToSearchAsync( } } - using var _4 = ArrayBuilder.GetInstance(out var tasks); - foreach (var (document, docSymbols) in documentToSymbols) - { - tasks.Add(CreateWorkAsync(() => ProcessDocumentAsync( - document, docSymbols, symbolToGlobalAliases, cancellationToken), cancellationToken)); - } - - await Task.WhenAll(tasks).ConfigureAwait(false); + await RoslynParallel.ForEachAsync( + documentToSymbols, + cancellationToken, + async (kvp, cancellationToken) => + { + var (document, docSymbols) = kvp; + await ProcessDocumentAsync(document, docSymbols, symbolToGlobalAliases, onReferenceFound, cancellationToken).ConfigureAwait(false); + }).ConfigureAwait(false); } finally { @@ -258,38 +318,47 @@ private async Task ProcessDocumentAsync( Document document, MetadataUnifyingSymbolHashSet symbols, Dictionary> symbolToGlobalAliases, + Action onReferenceFound, CancellationToken cancellationToken) { - await _progress.OnFindInDocumentStartedAsync(document, cancellationToken).ConfigureAwait(false); - - try + // We're doing to do all of our processing of this document at once. This will necessitate all the + // appropriate finders checking this document for hits. We know that in the initial pass to determine + // documents, this document was already considered a strong match (e.g. we know it contains the name of + // the symbol being searched for). As such, we're almost certainly going to have to do semantic checks + // to now see if the candidate actually matches the symbol. This will require syntax and semantics. So + // just grab those once here and hold onto them for the lifetime of this call. + var cache = await FindReferenceCache.GetCacheAsync(document, cancellationToken).ConfigureAwait(false); + + // This search almost always involves trying to find the tokens matching the name of the symbol we're looking + // for. Get the cache ready with those tokens so that kicking of N searches to search for each symbol in + // parallel doesn't cause us to compute and cache the same thing concurrently. + + // Note: cascaded symbols will normally have the same name. That's ok. The second call to + // FindMatchingIdentifierTokens with the same name will short circuit since it will already see the result of + // the prior call. + foreach (var symbol in symbols) { - // We're doing to do all of our processing of this document at once. This will necessitate all the - // appropriate finders checking this document for hits. We know that in the initial pass to determine - // documents, this document was already considered a strong match (e.g. we know it contains the name of - // the symbol being searched for). As such, we're almost certainly going to have to do semantic checks - // to now see if the candidate actually matches the symbol. This will require syntax and semantics. So - // just grab those once here and hold onto them for the lifetime of this call. - var cache = await FindReferenceCache.GetCacheAsync(document, cancellationToken).ConfigureAwait(false); - - // scratch array to place results in. Populated/inspected/cleared in inner loop. - using var _ = ArrayBuilder.GetInstance(out var foundReferenceLocations); - - foreach (var symbol in symbols) + if (symbol.CanBeReferencedByName) + cache.FindMatchingIdentifierTokens(symbol.Name, cancellationToken); + } + + await RoslynParallel.ForEachAsync( + symbols, + cancellationToken, + async (symbol, cancellationToken) => { + // symbolToGlobalAliases is safe to read in parallel. It is created fully before this point and is no + // longer mutated. var globalAliases = TryGet(symbolToGlobalAliases, symbol); var state = new FindReferencesDocumentState(cache, globalAliases); - await ProcessDocumentAsync(symbol, state, foundReferenceLocations).ConfigureAwait(false); - } - } - finally - { - await _progress.OnFindInDocumentCompletedAsync(document, cancellationToken).ConfigureAwait(false); - } + await ProcessDocumentAsync(symbol, state, onReferenceFound).ConfigureAwait(false); + }).ConfigureAwait(false); + + return; async Task ProcessDocumentAsync( - ISymbol symbol, FindReferencesDocumentState state, ArrayBuilder foundReferenceLocations) + ISymbol symbol, FindReferencesDocumentState state, Action onReferenceFound) { cancellationToken.ThrowIfCancellationRequested(); @@ -298,14 +367,18 @@ async Task ProcessDocumentAsync( // This is safe to just blindly read. We can only ever get here after the call to ReportGroupsAsync // happened. So there must be a group for this symbol in our map. var group = _symbolToGroup[symbol]; + + // Note: nearly every finder will no-op when passed a in a symbol it's not applicable to. So it's + // simple to just iterate over all of them, knowing that will quickly skip all the irrelevant ones, + // and only do interesting work on the single relevant one. foreach (var finder in _finders) { await finder.FindReferencesInDocumentAsync( - symbol, state, StandardCallbacks.AddToArrayBuilder, foundReferenceLocations, _options, cancellationToken).ConfigureAwait(false); - foreach (var (_, location) in foundReferenceLocations) - await _progress.OnReferenceFoundAsync(group, symbol, location, cancellationToken).ConfigureAwait(false); - - foundReferenceLocations.Clear(); + symbol, state, + (loc, tuple) => tuple.onReferenceFound((tuple.group, tuple.symbol, loc.Location)), + (group, symbol, onReferenceFound), + _options, + cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs index cb31339e8022c..b81e9bb931358 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs @@ -98,33 +98,39 @@ async ValueTask PerformSearchInDocumentAsync( } } - async ValueTask PerformSearchInDocumentWorkerAsync( - ISymbol symbol, FindReferencesDocumentState state) + async ValueTask PerformSearchInDocumentWorkerAsync(ISymbol symbol, FindReferencesDocumentState state) { - // Scratch buffer to place references for each finder. Cleared at the end of every loop iteration. - using var _ = ArrayBuilder.GetInstance(out var referencesForFinder); - // Always perform a normal search, looking for direct references to exactly that symbol. + await DirectSymbolSearchAsync(symbol, state).ConfigureAwait(false); + + // Now, for symbols that could involve inheritance, look for references to the same named entity, and + // see if it's a reference to a symbol that shares an inheritance relationship with that symbol. + await InheritanceSymbolSearchAsync(symbol, state).ConfigureAwait(false); + } + + async ValueTask DirectSymbolSearchAsync(ISymbol symbol, FindReferencesDocumentState state) + { + using var _ = ArrayBuilder.GetInstance(out var referencesForFinder); foreach (var finder in _finders) { await finder.FindReferencesInDocumentAsync( symbol, state, StandardCallbacks.AddToArrayBuilder, referencesForFinder, _options, cancellationToken).ConfigureAwait(false); - foreach (var (_, location) in referencesForFinder) - { - var group = await ReportGroupAsync(symbol, cancellationToken).ConfigureAwait(false); - await _progress.OnReferenceFoundAsync(group, symbol, location, cancellationToken).ConfigureAwait(false); - } - - referencesForFinder.Clear(); } - // Now, for symbols that could involve inheritance, look for references to the same named entity, and - // see if it's a reference to a symbol that shares an inheritance relationship with that symbol. + if (referencesForFinder.Count > 0) + { + var group = await ReportGroupAsync(symbol, cancellationToken).ConfigureAwait(false); + var references = referencesForFinder.SelectAsArray(r => (group, symbol, r.Location)); + await _progress.OnReferencesFoundAsync(references, cancellationToken).ConfigureAwait(false); + } + } + + async ValueTask InheritanceSymbolSearchAsync(ISymbol symbol, FindReferencesDocumentState state) + { if (InvolvesInheritance(symbol)) { - var tokens = await AbstractReferenceFinder.FindMatchingIdentifierTokensAsync( - state, symbol.Name, cancellationToken).ConfigureAwait(false); + var tokens = AbstractReferenceFinder.FindMatchingIdentifierTokens(state, symbol.Name, cancellationToken); foreach (var token in tokens) { @@ -138,7 +144,7 @@ await finder.FindReferencesInDocumentAsync( var candidateGroup = await ReportGroupAsync(candidate, cancellationToken).ConfigureAwait(false); var location = AbstractReferenceFinder.CreateReferenceLocation(state, token, candidateReason, cancellationToken); - await _progress.OnReferenceFoundAsync(candidateGroup, candidate, location, cancellationToken).ConfigureAwait(false); + await _progress.OnReferencesFoundAsync([(candidateGroup, candidate, location)], cancellationToken).ConfigureAwait(false); } } } @@ -180,7 +186,7 @@ async Task ComputeInheritanceRelationshipAsync( // Counter-intuitive, but if these are matching symbols, they do *not* have an inheritance relationship. // We do *not* want to report these as they would have been found in the original call to the finders in // PerformSearchInTextSpanAsync. - if (await SymbolFinder.OriginalSymbolsMatchAsync(_solution, searchSymbol, candidate, cancellationToken).ConfigureAwait(false)) + if (SymbolFinder.OriginalSymbolsMatch(_solution, searchSymbol, candidate)) return false; // walk up the original symbol's inheritance hierarchy to see if we hit the candidate. Don't walk down @@ -189,7 +195,7 @@ async Task ComputeInheritanceRelationshipAsync( this, [searchSymbol], includeImplementationsThroughDerivedTypes: false, cancellationToken).ConfigureAwait(false); foreach (var symbolUp in searchSymbolUpSet.GetAllSymbols()) { - if (await SymbolFinder.OriginalSymbolsMatchAsync(_solution, symbolUp, candidate, cancellationToken).ConfigureAwait(false)) + if (SymbolFinder.OriginalSymbolsMatch(_solution, symbolUp, candidate)) return true; } @@ -199,7 +205,7 @@ async Task ComputeInheritanceRelationshipAsync( this, [candidate], includeImplementationsThroughDerivedTypes: false, cancellationToken).ConfigureAwait(false); foreach (var candidateUp in candidateSymbolUpSet.GetAllSymbols()) { - if (await SymbolFinder.OriginalSymbolsMatchAsync(_solution, searchSymbol, candidateUp, cancellationToken).ConfigureAwait(false)) + if (SymbolFinder.OriginalSymbolsMatch(_solution, searchSymbol, candidateUp)) return true; } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMemberScopedReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMemberScopedReferenceFinder.cs index 574d7b6652cd9..bc902b6dde014 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMemberScopedReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMemberScopedReferenceFinder.cs @@ -49,7 +49,7 @@ protected sealed override Task DetermineDocumentsToSearchAsync( return Task.CompletedTask; } - protected sealed override async ValueTask FindReferencesInDocumentAsync( + protected sealed override ValueTask FindReferencesInDocumentAsync( TSymbol symbol, FindReferencesDocumentState state, Action processResult, @@ -60,13 +60,15 @@ protected sealed override async ValueTask FindReferencesInDocumentAsync( var container = GetContainer(symbol); if (container != null) { - await FindReferencesInContainerAsync(symbol, container, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInContainer(symbol, container, state, processResult, processResultData, cancellationToken); } else if (symbol.ContainingType != null && symbol.ContainingType.IsScriptClass) { - var tokens = await FindMatchingIdentifierTokensAsync(state, symbol.Name, cancellationToken).ConfigureAwait(false); - await FindReferencesInTokensAsync(symbol, state, tokens, processResult, processResultData, cancellationToken).ConfigureAwait(false); + var tokens = FindMatchingIdentifierTokens(state, symbol.Name, cancellationToken); + FindReferencesInTokens(symbol, state, tokens, processResult, processResultData, cancellationToken); } + + return ValueTaskFactory.CompletedTask; } private static ISymbol? GetContainer(ISymbol symbol) @@ -97,7 +99,7 @@ protected sealed override async ValueTask FindReferencesInDocumentAsync( return null; } - private ValueTask FindReferencesInContainerAsync( + private void FindReferencesInContainer( TSymbol symbol, ISymbol container, FindReferencesDocumentState state, @@ -121,6 +123,7 @@ private ValueTask FindReferencesInContainerAsync( } } - return FindReferencesInTokensAsync(symbol, state, tokens.ToImmutable(), processResult, processResultData, cancellationToken); + FindReferencesInTokens( + symbol, state, tokens.ToImmutable(), processResult, processResultData, cancellationToken); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs index 39b19e605551a..aeb052995d490 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs @@ -36,7 +36,7 @@ public abstract Task DetermineDocumentsToSearchAsync( public abstract ValueTask FindReferencesInDocumentAsync( ISymbol symbol, FindReferencesDocumentState state, Action processResult, TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken); - private static ValueTask<(bool matched, CandidateReason reason)> SymbolsMatchAsync( + private static (bool matched, CandidateReason reason) SymbolsMatch( ISymbol symbol, FindReferencesDocumentState state, SyntaxToken token, CancellationToken cancellationToken) { // delegates don't have exposed symbols for their constructors. so when you do `new MyDel()`, that's only a @@ -47,26 +47,25 @@ public abstract ValueTask FindReferencesInDocumentAsync( : state.SyntaxFacts.TryGetBindableParent(token); parent ??= token.Parent!; - return SymbolsMatchAsync(symbol, state, parent, cancellationToken); + return SymbolsMatch(symbol, state, parent, cancellationToken); } - protected static ValueTask<(bool matched, CandidateReason reason)> SymbolsMatchAsync( + protected static (bool matched, CandidateReason reason) SymbolsMatch( ISymbol searchSymbol, FindReferencesDocumentState state, SyntaxNode node, CancellationToken cancellationToken) { var symbolInfo = state.Cache.GetSymbolInfo(node, cancellationToken); - - return MatchesAsync(searchSymbol, state, symbolInfo, cancellationToken); + return Matches(searchSymbol, state, symbolInfo); } - protected static async ValueTask<(bool matched, CandidateReason reason)> MatchesAsync( - ISymbol searchSymbol, FindReferencesDocumentState state, SymbolInfo symbolInfo, CancellationToken cancellationToken) + protected static (bool matched, CandidateReason reason) Matches( + ISymbol searchSymbol, FindReferencesDocumentState state, SymbolInfo symbolInfo) { - if (await SymbolFinder.OriginalSymbolsMatchAsync(state.Solution, searchSymbol, symbolInfo.Symbol, cancellationToken).ConfigureAwait(false)) + if (SymbolFinder.OriginalSymbolsMatch(state.Solution, searchSymbol, symbolInfo.Symbol)) return (matched: true, CandidateReason.None); foreach (var candidate in symbolInfo.CandidateSymbols) { - if (await SymbolFinder.OriginalSymbolsMatchAsync(state.Solution, searchSymbol, candidate, cancellationToken).ConfigureAwait(false)) + if (SymbolFinder.OriginalSymbolsMatch(state.Solution, searchSymbol, candidate)) return (matched: true, symbolInfo.CandidateReason); } @@ -162,8 +161,7 @@ protected static Task FindDocumentsAsync( protected static bool IdentifiersMatch(ISyntaxFactsService syntaxFacts, string name, SyntaxToken token) => syntaxFacts.IsIdentifier(token) && syntaxFacts.TextMatch(token.ValueText, name); - [PerformanceSensitive("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1224834", OftenCompletesSynchronously = true)] - protected static async ValueTask FindReferencesInDocumentUsingIdentifierAsync( + protected static void FindReferencesInDocumentUsingIdentifier( ISymbol symbol, string identifier, FindReferencesDocumentState state, @@ -171,14 +169,14 @@ protected static async ValueTask FindReferencesInDocumentUsingIdentifierAsync> FindMatchingIdentifierTokensAsync(FindReferencesDocumentState state, string identifier, CancellationToken cancellationToken) - => state.Cache.FindMatchingIdentifierTokensAsync(state.Document, identifier, cancellationToken); + public static ImmutableArray FindMatchingIdentifierTokens(FindReferencesDocumentState state, string identifier, CancellationToken cancellationToken) + => state.Cache.FindMatchingIdentifierTokens(identifier, cancellationToken); - protected static async ValueTask FindReferencesInTokensAsync( + protected static void FindReferencesInTokens( ISymbol symbol, FindReferencesDocumentState state, ImmutableArray tokens, @@ -193,12 +191,10 @@ protected static async ValueTask FindReferencesInTokensAsync( { cancellationToken.ThrowIfCancellationRequested(); - var (matched, reason) = await SymbolsMatchAsync( - symbol, state, token, cancellationToken).ConfigureAwait(false); + var (matched, reason) = SymbolsMatch(symbol, state, token, cancellationToken); if (matched) { var finderLocation = CreateFinderLocation(state, token, reason, cancellationToken); - processResult(finderLocation, processResultData); } } @@ -243,7 +239,7 @@ public static ReferenceLocation CreateReferenceLocation(FindReferencesDocumentSt return null; } - protected static async Task FindLocalAliasReferencesAsync( + protected static void FindLocalAliasReferences( ArrayBuilder initialReferences, ISymbol symbol, FindReferencesDocumentState state, @@ -253,10 +249,10 @@ protected static async Task FindLocalAliasReferencesAsync( { var aliasSymbols = GetLocalAliasSymbols(state, initialReferences, cancellationToken); if (!aliasSymbols.IsDefaultOrEmpty) - await FindReferencesThroughLocalAliasSymbolsAsync(symbol, state, aliasSymbols, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesThroughLocalAliasSymbols(symbol, state, aliasSymbols, processResult, processResultData, cancellationToken); } - protected static async Task FindLocalAliasReferencesAsync( + protected static void FindLocalAliasReferences( ArrayBuilder initialReferences, FindReferencesDocumentState state, Action processResult, @@ -265,7 +261,7 @@ protected static async Task FindLocalAliasReferencesAsync( { var aliasSymbols = GetLocalAliasSymbols(state, initialReferences, cancellationToken); if (!aliasSymbols.IsDefaultOrEmpty) - await FindReferencesThroughLocalAliasSymbolsAsync(state, aliasSymbols, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesThroughLocalAliasSymbols(state, aliasSymbols, processResult, processResultData, cancellationToken); } private static ImmutableArray GetLocalAliasSymbols( @@ -284,7 +280,7 @@ private static ImmutableArray GetLocalAliasSymbols( return aliasSymbols.ToImmutableAndClear(); } - private static async Task FindReferencesThroughLocalAliasSymbolsAsync( + private static void FindReferencesThroughLocalAliasSymbols( ISymbol symbol, FindReferencesDocumentState state, ImmutableArray localAliasSymbols, @@ -294,20 +290,20 @@ private static async Task FindReferencesThroughLocalAliasSymbolsAsync( { foreach (var localAliasSymbol in localAliasSymbols) { - await FindReferencesInDocumentUsingIdentifierAsync( - symbol, localAliasSymbol.Name, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInDocumentUsingIdentifier( + symbol, localAliasSymbol.Name, state, processResult, processResultData, cancellationToken); // the alias may reference an attribute and the alias name may end with an "Attribute" suffix. In this case search for the // shortened name as well (e.g. using GooAttribute = MyNamespace.GooAttribute; [Goo] class C1 {}) if (TryGetNameWithoutAttributeSuffix(localAliasSymbol.Name, state.SyntaxFacts, out var simpleName)) { - await FindReferencesInDocumentUsingIdentifierAsync( - symbol, simpleName, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInDocumentUsingIdentifier( + symbol, simpleName, state, processResult, processResultData, cancellationToken); } } } - private static async Task FindReferencesThroughLocalAliasSymbolsAsync( + private static void FindReferencesThroughLocalAliasSymbols( FindReferencesDocumentState state, ImmutableArray localAliasSymbols, Action processResult, @@ -316,15 +312,15 @@ private static async Task FindReferencesThroughLocalAliasSymbolsAsync( { foreach (var aliasSymbol in localAliasSymbols) { - await FindReferencesInDocumentUsingIdentifierAsync( - aliasSymbol, aliasSymbol.Name, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInDocumentUsingIdentifier( + aliasSymbol, aliasSymbol.Name, state, processResult, processResultData, cancellationToken); // the alias may reference an attribute and the alias name may end with an "Attribute" suffix. In this case search for the // shortened name as well (e.g. using GooAttribute = MyNamespace.GooAttribute; [Goo] class C1 {}) if (TryGetNameWithoutAttributeSuffix(aliasSymbol.Name, state.SyntaxFacts, out var simpleName)) { - await FindReferencesInDocumentUsingIdentifierAsync( - aliasSymbol, simpleName, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInDocumentUsingIdentifier( + aliasSymbol, simpleName, state, processResult, processResultData, cancellationToken); } } } @@ -371,7 +367,7 @@ protected static Task FindDocumentsWithForEachStatementsAsync(Project pro protected delegate void CollectMatchingReferences( SyntaxNode node, FindReferencesDocumentState state, Action processResult, TData processResultData); - protected static async Task FindReferencesInDocumentAsync( + protected static void FindReferencesInDocument( FindReferencesDocumentState state, Func isRelevantDocument, CollectMatchingReferences collectMatchingReferences, @@ -379,8 +375,7 @@ protected static async Task FindReferencesInDocumentAsync( TData processResultData, CancellationToken cancellationToken) { - var document = state.Document; - var syntaxTreeInfo = await SyntaxTreeIndex.GetRequiredIndexAsync(document, cancellationToken).ConfigureAwait(false); + var syntaxTreeInfo = state.Cache.SyntaxTreeIndex; if (isRelevantDocument(syntaxTreeInfo)) { foreach (var node in state.Root.DescendantNodesAndSelf()) @@ -391,14 +386,15 @@ protected static async Task FindReferencesInDocumentAsync( } } - protected Task FindReferencesInForEachStatementsAsync( + protected void FindReferencesInForEachStatements( ISymbol symbol, FindReferencesDocumentState state, Action processResult, TData processResultData, CancellationToken cancellationToken) { - return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); + FindReferencesInDocument(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); + return; static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo) => syntaxTreeInfo.ContainsForEachStatement; @@ -429,14 +425,15 @@ void CollectMatchingReferences( } } - protected Task FindReferencesInCollectionInitializerAsync( + protected void FindReferencesInCollectionInitializer( ISymbol symbol, FindReferencesDocumentState state, Action processResult, TData processResultData, CancellationToken cancellationToken) { - return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); + FindReferencesInDocument(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); + return; static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo) => syntaxTreeInfo.ContainsCollectionInitializer; @@ -471,14 +468,15 @@ void CollectMatchingReferences( } } - protected Task FindReferencesInDeconstructionAsync( + protected void FindReferencesInDeconstruction( ISymbol symbol, FindReferencesDocumentState state, Action processResult, TData processResultData, CancellationToken cancellationToken) { - return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); + FindReferencesInDocument(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); + return; static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo) => syntaxTreeInfo.ContainsDeconstruction; @@ -508,14 +506,15 @@ void CollectMatchingReferences( } } - protected Task FindReferencesInAwaitExpressionAsync( + protected void FindReferencesInAwaitExpression( ISymbol symbol, FindReferencesDocumentState state, Action processResult, TData processResultData, CancellationToken cancellationToken) { - return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); + FindReferencesInDocument(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); + return; static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo) => syntaxTreeInfo.ContainsAwait; @@ -538,14 +537,15 @@ void CollectMatchingReferences( } } - protected Task FindReferencesInImplicitObjectCreationExpressionAsync( + protected void FindReferencesInImplicitObjectCreationExpression( ISymbol symbol, FindReferencesDocumentState state, Action processResult, TData processResultData, CancellationToken cancellationToken) { - return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); + FindReferencesInDocument(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); + return; static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo) => syntaxTreeInfo.ContainsImplicitObjectCreation; @@ -905,10 +905,10 @@ protected virtual ValueTask> DetermineCascadedSymbolsAsy return new([]); } - protected static ValueTask FindReferencesInDocumentUsingSymbolNameAsync( + protected static void FindReferencesInDocumentUsingSymbolName( TSymbol symbol, FindReferencesDocumentState state, Action processResult, TData processResultData, CancellationToken cancellationToken) { - return FindReferencesInDocumentUsingIdentifierAsync( + FindReferencesInDocumentUsingIdentifier( symbol, symbol.Name, state, processResult, processResultData, cancellationToken); } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder_GlobalSuppressions.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder_GlobalSuppressions.cs index 13e1de1396ca0..e03b3805db4a8 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder_GlobalSuppressions.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder_GlobalSuppressions.cs @@ -50,7 +50,7 @@ static bool SupportsGlobalSuppression(ISymbol symbol) /// [assembly: SuppressMessage("RuleCategory", "RuleId', Scope = "member", Target = "~F:C.Field")] /// [PerformanceSensitive("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1224834", OftenCompletesSynchronously = true)] - protected static async ValueTask FindReferencesInDocumentInsideGlobalSuppressionsAsync( + protected static void FindReferencesInDocumentInsideGlobalSuppressions( ISymbol symbol, FindReferencesDocumentState state, Action processResult, @@ -61,7 +61,7 @@ protected static async ValueTask FindReferencesInDocumentInsideGlobalSuppression return; // Check if we have any relevant global attributes in this document. - var info = await SyntaxTreeIndex.GetRequiredIndexAsync(state.Document, cancellationToken).ConfigureAwait(false); + var info = state.Cache.SyntaxTreeIndex; if (!info.ContainsGlobalSuppressMessageAttribute) return; @@ -80,7 +80,7 @@ protected static async ValueTask FindReferencesInDocumentInsideGlobalSuppression // We map the positions of documentation ID literals in tree to string literal tokens, // perform semantic checks to ensure these are valid references to the symbol // and if so, add these locations to the computed references. - var root = await semanticModel.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false); + var root = state.Root; foreach (var token in root.DescendantTokens()) { if (IsCandidate(state, token, expectedDocCommentId.Span, suppressMessageAttribute, cancellationToken, out var offsetOfReferenceInToken)) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractTypeParameterSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractTypeParameterSymbolReferenceFinder.cs index 38cc211cfb9ad..b84e1bd7abaf1 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractTypeParameterSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractTypeParameterSymbolReferenceFinder.cs @@ -13,7 +13,7 @@ namespace Microsoft.CodeAnalysis.FindSymbols.Finders; internal abstract class AbstractTypeParameterSymbolReferenceFinder : AbstractReferenceFinder { - protected sealed override async ValueTask FindReferencesInDocumentAsync( + protected sealed override ValueTask FindReferencesInDocumentAsync( ITypeParameterSymbol symbol, FindReferencesDocumentState state, Action processResult, @@ -29,21 +29,21 @@ protected sealed override async ValueTask FindReferencesInDocumentAsync( // T()`). In the former case GetSymbolInfo can be used to bind the symbol and check if it matches this symbol. // in the latter though GetSymbolInfo will fail and we have to directly check if we have the right type info. - var tokens = await FindMatchingIdentifierTokensAsync(state, symbol.Name, cancellationToken).ConfigureAwait(false); + var tokens = FindMatchingIdentifierTokens(state, symbol.Name, cancellationToken); - await FindReferencesInTokensAsync( + FindReferencesInTokens( symbol, state, tokens.WhereAsArray(static (token, state) => !IsObjectCreationToken(token, state), state), processResult, processResultData, - cancellationToken).ConfigureAwait(false); + cancellationToken); GetObjectCreationReferences( tokens.WhereAsArray(static (token, state) => IsObjectCreationToken(token, state), state), processResult, processResultData); - return; + return ValueTaskFactory.CompletedTask; static bool IsObjectCreationToken(SyntaxToken token, FindReferencesDocumentState state) { diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs index 4258a0eca666b..3ee5b2f3a72e4 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs @@ -51,7 +51,7 @@ protected override Task DetermineDocumentsToSearchAsync( }, symbol.ContainingType.Name, processResult, processResultData, cancellationToken); } - protected sealed override async ValueTask FindReferencesInDocumentAsync( + protected sealed override ValueTask FindReferencesInDocumentAsync( IMethodSymbol methodSymbol, FindReferencesDocumentState state, Action processResult, @@ -61,18 +61,14 @@ protected sealed override async ValueTask FindReferencesInDocumentAsync( { var tokens = state.Cache.GetConstructorInitializerTokens(state.SyntaxFacts, state.Root, cancellationToken); if (state.SemanticModel.Language == LanguageNames.VisualBasic) - { - tokens = tokens.Concat(await FindMatchingIdentifierTokensAsync( - state, "New", cancellationToken).ConfigureAwait(false)).Distinct(); - } + tokens = tokens.Concat(FindMatchingIdentifierTokens(state, "New", cancellationToken)).Distinct(); var totalTokens = tokens.WhereAsArray( static (token, tuple) => TokensMatch(tuple.state, token, tuple.methodSymbol.ContainingType.Name, tuple.cancellationToken), (state, methodSymbol, cancellationToken)); - await FindReferencesInTokensAsync(methodSymbol, state, totalTokens, processResult, processResultData, cancellationToken).ConfigureAwait(false); - - return; + FindReferencesInTokens(methodSymbol, state, totalTokens, processResult, processResultData, cancellationToken); + return ValueTaskFactory.CompletedTask; // local functions static bool TokensMatch( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs index 5037d55e9d1ad..9e0d20f947dfd 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs @@ -90,7 +90,7 @@ private static bool IsPotentialReference(PredefinedType predefinedType, ISyntaxF => syntaxFacts.TryGetPredefinedType(token, out var actualType) && predefinedType == actualType; - protected override async ValueTask FindReferencesInDocumentAsync( + protected override ValueTask FindReferencesInDocumentAsync( IMethodSymbol methodSymbol, FindReferencesDocumentState state, Action processResult, @@ -100,8 +100,8 @@ protected override async ValueTask FindReferencesInDocumentAsync( { // First just look for this normal constructor references using the name of it's containing type. var name = methodSymbol.ContainingType.Name; - await AddReferencesInDocumentWorkerAsync( - methodSymbol, name, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + AddReferencesInDocumentWorker( + methodSymbol, name, state, processResult, processResultData, cancellationToken); // Next, look for constructor references through a global alias to our containing type. foreach (var globalAlias in state.GlobalAliases) @@ -112,27 +112,29 @@ await AddReferencesInDocumentWorkerAsync( if (state.SyntaxFacts.StringComparer.Equals(name, globalAlias)) continue; - await AddReferencesInDocumentWorkerAsync( - methodSymbol, globalAlias, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + AddReferencesInDocumentWorker( + methodSymbol, globalAlias, state, processResult, processResultData, cancellationToken); } // Finally, look for constructor references to predefined types (like `new int()`), // implicit object references, and inside global suppression attributes. - await FindPredefinedTypeReferencesAsync( - methodSymbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindPredefinedTypeReferences( + methodSymbol, state, processResult, processResultData, cancellationToken); - await FindReferencesInImplicitObjectCreationExpressionAsync( - methodSymbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInImplicitObjectCreationExpression( + methodSymbol, state, processResult, processResultData, cancellationToken); - await FindReferencesInDocumentInsideGlobalSuppressionsAsync( - methodSymbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInDocumentInsideGlobalSuppressions( + methodSymbol, state, processResult, processResultData, cancellationToken); + + return ValueTaskFactory.CompletedTask; } /// /// Finds references to in this , but only if it referenced /// though (which might be the actual name of the type, or a global alias to it). /// - private static async Task AddReferencesInDocumentWorkerAsync( + private static void AddReferencesInDocumentWorker( IMethodSymbol symbol, string name, FindReferencesDocumentState state, @@ -140,13 +142,13 @@ private static async Task AddReferencesInDocumentWorkerAsync( TData processResultData, CancellationToken cancellationToken) { - await FindOrdinaryReferencesAsync( - symbol, name, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); - await FindAttributeReferencesAsync( - symbol, name, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindOrdinaryReferences( + symbol, name, state, processResult, processResultData, cancellationToken); + FindAttributeReferences( + symbol, name, state, processResult, processResultData, cancellationToken); } - private static ValueTask FindOrdinaryReferencesAsync( + private static void FindOrdinaryReferences( IMethodSymbol symbol, string name, FindReferencesDocumentState state, @@ -154,11 +156,11 @@ private static ValueTask FindOrdinaryReferencesAsync( TData processResultData, CancellationToken cancellationToken) { - return FindReferencesInDocumentUsingIdentifierAsync( + FindReferencesInDocumentUsingIdentifier( symbol, name, state, processResult, processResultData, cancellationToken); } - private static ValueTask FindPredefinedTypeReferencesAsync( + private static void FindPredefinedTypeReferences( IMethodSymbol symbol, FindReferencesDocumentState state, Action processResult, @@ -167,7 +169,7 @@ private static ValueTask FindPredefinedTypeReferencesAsync( { var predefinedType = symbol.ContainingType.SpecialType.ToPredefinedType(); if (predefinedType == PredefinedType.None) - return ValueTaskFactory.CompletedTask; + return; var tokens = state.Root .DescendantTokens(descendIntoTrivia: true) @@ -175,10 +177,10 @@ private static ValueTask FindPredefinedTypeReferencesAsync( static (token, tuple) => IsPotentialReference(tuple.predefinedType, tuple.state.SyntaxFacts, token), (state, predefinedType)); - return FindReferencesInTokensAsync(symbol, state, tokens, processResult, processResultData, cancellationToken); + FindReferencesInTokens(symbol, state, tokens, processResult, processResultData, cancellationToken); } - private static ValueTask FindAttributeReferencesAsync( + private static void FindAttributeReferences( IMethodSymbol symbol, string name, FindReferencesDocumentState state, @@ -186,12 +188,11 @@ private static ValueTask FindAttributeReferencesAsync( TData processResultData, CancellationToken cancellationToken) { - return TryGetNameWithoutAttributeSuffix(name, state.SyntaxFacts, out var simpleName) - ? FindReferencesInDocumentUsingIdentifierAsync(symbol, simpleName, state, processResult, processResultData, cancellationToken) - : ValueTaskFactory.CompletedTask; + if (TryGetNameWithoutAttributeSuffix(name, state.SyntaxFacts, out var simpleName)) + FindReferencesInDocumentUsingIdentifier(symbol, simpleName, state, processResult, processResultData, cancellationToken); } - private Task FindReferencesInImplicitObjectCreationExpressionAsync( + private void FindReferencesInImplicitObjectCreationExpression( IMethodSymbol symbol, FindReferencesDocumentState state, Action processResult, @@ -208,7 +209,8 @@ private Task FindReferencesInImplicitObjectCreationExpressionAsync( ? -1 : symbol.Parameters.Length; - return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); + FindReferencesInDocument(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); + return; static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo) => syntaxTreeInfo.ContainsImplicitObjectCreation; diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/EventSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/EventSymbolReferenceFinder.cs index 8784ccd1b37ef..7b5870acdbde7 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/EventSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/EventSymbolReferenceFinder.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -56,6 +57,7 @@ protected sealed override ValueTask FindReferencesInDocumentAsync( FindReferencesSearchOptions options, CancellationToken cancellationToken) { - return FindReferencesInDocumentUsingSymbolNameAsync(symbol, state, processResult, processResultData, cancellationToken); + FindReferencesInDocumentUsingSymbolName(symbol, state, processResult, processResultData, cancellationToken); + return ValueTaskFactory.CompletedTask; } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitConversionSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitConversionSymbolReferenceFinder.cs index 4078ddcbba83d..dfed302b16041 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitConversionSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitConversionSymbolReferenceFinder.cs @@ -73,7 +73,8 @@ protected sealed override ValueTask FindReferencesInDocumentAsync( static (token, state) => IsPotentialReference(state.SyntaxFacts, token), state); - return FindReferencesInTokensAsync(symbol, state, tokens, processResult, processResultData, cancellationToken); + FindReferencesInTokens(symbol, state, tokens, processResult, processResultData, cancellationToken); + return ValueTaskFactory.CompletedTask; } private static bool IsPotentialReference( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs index 11c23a50ec558..4377ecd8bb5b5 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs @@ -7,6 +7,7 @@ using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -40,7 +41,7 @@ protected override async Task DetermineDocumentsToSearchAsync( await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); } - protected override async ValueTask FindReferencesInDocumentAsync( + protected override ValueTask FindReferencesInDocumentAsync( IFieldSymbol symbol, FindReferencesDocumentState state, Action processResult, @@ -48,9 +49,10 @@ protected override async ValueTask FindReferencesInDocumentAsync( FindReferencesSearchOptions options, CancellationToken cancellationToken) { - await FindReferencesInDocumentUsingSymbolNameAsync( - symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); - await FindReferencesInDocumentInsideGlobalSuppressionsAsync( - symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInDocumentUsingSymbolName( + symbol, state, processResult, processResultData, cancellationToken); + FindReferencesInDocumentInsideGlobalSuppressions( + symbol, state, processResult, processResultData, cancellationToken); + return ValueTaskFactory.CompletedTask; } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs index 35816840c7d6c..0b2e5e08bb88c 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs @@ -105,7 +105,7 @@ private static bool IsPotentialReference( predefinedType == actualType; } - protected override async ValueTask FindReferencesInDocumentAsync( + protected override ValueTask FindReferencesInDocumentAsync( INamedTypeSymbol namedType, FindReferencesDocumentState state, Action processResult, @@ -117,8 +117,8 @@ protected override async ValueTask FindReferencesInDocumentAsync( // First find all references to this type, either with it's actual name, or through potential // global alises to it. - await AddReferencesToTypeOrGlobalAliasToItAsync( - namedType, state, StandardCallbacks.AddToArrayBuilder, initialReferences, cancellationToken).ConfigureAwait(false); + AddReferencesToTypeOrGlobalAliasToIt( + namedType, state, StandardCallbacks.AddToArrayBuilder, initialReferences, cancellationToken); // The items in initialReferences need to be both reported and used later to calculate additional results. foreach (var location in initialReferences) @@ -127,25 +127,27 @@ await AddReferencesToTypeOrGlobalAliasToItAsync( // This named type may end up being locally aliased as well. If so, now find all the references // to the local alias. - await FindLocalAliasReferencesAsync( - initialReferences, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindLocalAliasReferences( + initialReferences, state, processResult, processResultData, cancellationToken); - await FindPredefinedTypeReferencesAsync( - namedType, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindPredefinedTypeReferences( + namedType, state, processResult, processResultData, cancellationToken); - await FindReferencesInDocumentInsideGlobalSuppressionsAsync( - namedType, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInDocumentInsideGlobalSuppressions( + namedType, state, processResult, processResultData, cancellationToken); + + return ValueTaskFactory.CompletedTask; } - internal static async ValueTask AddReferencesToTypeOrGlobalAliasToItAsync( + internal static void AddReferencesToTypeOrGlobalAliasToIt( INamedTypeSymbol namedType, FindReferencesDocumentState state, Action processResult, TData processResultData, CancellationToken cancellationToken) { - await AddNonAliasReferencesAsync( - namedType, namedType.Name, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + AddNonAliasReferences( + namedType, namedType.Name, state, processResult, processResultData, cancellationToken); foreach (var globalAlias in state.GlobalAliases) { @@ -155,8 +157,8 @@ await AddNonAliasReferencesAsync( if (state.SyntaxFacts.StringComparer.Equals(namedType.Name, globalAlias)) continue; - await AddNonAliasReferencesAsync( - namedType, globalAlias, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + AddNonAliasReferences( + namedType, globalAlias, state, processResult, processResultData, cancellationToken); } } @@ -165,7 +167,7 @@ await AddNonAliasReferencesAsync( /// only if it referenced though (which might be the actual name /// of the type, or a global alias to it). /// - private static async ValueTask AddNonAliasReferencesAsync( + private static void AddNonAliasReferences( INamedTypeSymbol symbol, string name, FindReferencesDocumentState state, @@ -173,14 +175,14 @@ private static async ValueTask AddNonAliasReferencesAsync( TData processResultData, CancellationToken cancellationToken) { - await FindOrdinaryReferencesAsync( - symbol, name, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindOrdinaryReferences( + symbol, name, state, processResult, processResultData, cancellationToken); - await FindAttributeReferencesAsync( - symbol, name, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindAttributeReferences( + symbol, name, state, processResult, processResultData, cancellationToken); } - private static ValueTask FindOrdinaryReferencesAsync( + private static void FindOrdinaryReferences( INamedTypeSymbol namedType, string name, FindReferencesDocumentState state, @@ -193,11 +195,11 @@ private static ValueTask FindOrdinaryReferencesAsync( // to the constructor not the type. That's a good thing as we don't want these object-creations to // associate with the type, but rather with the constructor itself. - return FindReferencesInDocumentUsingIdentifierAsync( + FindReferencesInDocumentUsingIdentifier( namedType, name, state, processResult, processResultData, cancellationToken); } - private static ValueTask FindPredefinedTypeReferencesAsync( + private static void FindPredefinedTypeReferences( INamedTypeSymbol symbol, FindReferencesDocumentState state, Action processResult, @@ -206,7 +208,7 @@ private static ValueTask FindPredefinedTypeReferencesAsync( { var predefinedType = symbol.SpecialType.ToPredefinedType(); if (predefinedType == PredefinedType.None) - return ValueTaskFactory.CompletedTask; + return; var tokens = state.Root .DescendantTokens(descendIntoTrivia: true) @@ -214,10 +216,10 @@ private static ValueTask FindPredefinedTypeReferencesAsync( static (token, tuple) => IsPotentialReference(tuple.predefinedType, tuple.state.SyntaxFacts, token), (state, predefinedType)); - return FindReferencesInTokensAsync(symbol, state, tokens, processResult, processResultData, cancellationToken); + FindReferencesInTokens(symbol, state, tokens, processResult, processResultData, cancellationToken); } - private static ValueTask FindAttributeReferencesAsync( + private static void FindAttributeReferences( INamedTypeSymbol namedType, string name, FindReferencesDocumentState state, @@ -225,8 +227,7 @@ private static ValueTask FindAttributeReferencesAsync( TData processResultData, CancellationToken cancellationToken) { - return TryGetNameWithoutAttributeSuffix(name, state.SyntaxFacts, out var nameWithoutSuffix) - ? FindReferencesInDocumentUsingIdentifierAsync(namedType, nameWithoutSuffix, state, processResult, processResultData, cancellationToken) - : ValueTaskFactory.CompletedTask; + if (TryGetNameWithoutAttributeSuffix(name, state.SyntaxFacts, out var nameWithoutSuffix)) + FindReferencesInDocumentUsingIdentifier(namedType, nameWithoutSuffix, state, processResult, processResultData, cancellationToken); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs index e08a522c68576..3ca848269ce6a 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -50,7 +51,7 @@ await FindDocumentsAsync( await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); } - protected override async ValueTask FindReferencesInDocumentAsync( + protected override ValueTask FindReferencesInDocumentAsync( INamespaceSymbol symbol, FindReferencesDocumentState state, Action processResult, @@ -60,16 +61,16 @@ protected override async ValueTask FindReferencesInDocumentAsync( { if (symbol.IsGlobalNamespace) { - await AddGlobalNamespaceReferencesAsync( - symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + AddGlobalNamespaceReferences( + symbol, state, processResult, processResultData, cancellationToken); } else { using var _ = ArrayBuilder.GetInstance(out var initialReferences); var namespaceName = symbol.Name; - await AddNamedReferencesAsync( - symbol, namespaceName, state, StandardCallbacks.AddToArrayBuilder, initialReferences, cancellationToken).ConfigureAwait(false); + AddNamedReferences( + symbol, namespaceName, state, StandardCallbacks.AddToArrayBuilder, initialReferences, cancellationToken); foreach (var globalAlias in state.GlobalAliases) { @@ -79,27 +80,29 @@ await AddNamedReferencesAsync( if (state.SyntaxFacts.StringComparer.Equals(namespaceName, globalAlias)) continue; - await AddNamedReferencesAsync( - symbol, globalAlias, state, StandardCallbacks.AddToArrayBuilder, initialReferences, cancellationToken).ConfigureAwait(false); + AddNamedReferences( + symbol, globalAlias, state, StandardCallbacks.AddToArrayBuilder, initialReferences, cancellationToken); } // The items in initialReferences need to be both reported and used later to calculate additional results. foreach (var location in initialReferences) processResult(location, processResultData); - await FindLocalAliasReferencesAsync( - initialReferences, symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindLocalAliasReferences( + initialReferences, symbol, state, processResult, processResultData, cancellationToken); - await FindReferencesInDocumentInsideGlobalSuppressionsAsync( - symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInDocumentInsideGlobalSuppressions( + symbol, state, processResult, processResultData, cancellationToken); } + + return ValueTaskFactory.CompletedTask; } /// /// Finds references to in this , but only if it referenced /// though (which might be the actual name of the type, or a global alias to it). /// - private static async ValueTask AddNamedReferencesAsync( + private static void AddNamedReferences( INamespaceSymbol symbol, string name, FindReferencesDocumentState state, @@ -107,14 +110,13 @@ private static async ValueTask AddNamedReferencesAsync( TData processResultData, CancellationToken cancellationToken) { - var tokens = await FindMatchingIdentifierTokensAsync( - state, name, cancellationToken).ConfigureAwait(false); + var tokens = FindMatchingIdentifierTokens(state, name, cancellationToken); - await FindReferencesInTokensAsync( - symbol, state, tokens, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInTokens( + symbol, state, tokens, processResult, processResultData, cancellationToken); } - private static async Task AddGlobalNamespaceReferencesAsync( + private static void AddGlobalNamespaceReferences( INamespaceSymbol symbol, FindReferencesDocumentState state, Action processResult, @@ -127,7 +129,7 @@ private static async Task AddGlobalNamespaceReferencesAsync( static (token, state) => state.SyntaxFacts.IsGlobalNamespaceKeyword(token), state); - await FindReferencesInTokensAsync( - symbol, state, tokens, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInTokens( + symbol, state, tokens, processResult, processResultData, cancellationToken); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs index 5b3b92945dc16..a6d5cb8b80c0c 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -47,7 +48,7 @@ private static Task FindDocumentsAsync( project, documents, static (index, op) => index.ContainsPredefinedOperator(op), op, processResult, processResultData, cancellationToken); } - protected sealed override async ValueTask FindReferencesInDocumentAsync( + protected sealed override ValueTask FindReferencesInDocumentAsync( IMethodSymbol symbol, FindReferencesDocumentState state, Action processResult, @@ -62,10 +63,11 @@ protected sealed override async ValueTask FindReferencesInDocumentAsync( static (token, tuple) => IsPotentialReference(tuple.state.SyntaxFacts, tuple.op, token), (state, op)); - await FindReferencesInTokensAsync( - symbol, state, tokens, processResult, processResultData, cancellationToken).ConfigureAwait(false); - await FindReferencesInDocumentInsideGlobalSuppressionsAsync( - symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInTokens( + symbol, state, tokens, processResult, processResultData, cancellationToken); + FindReferencesInDocumentInsideGlobalSuppressions( + symbol, state, processResult, processResultData, cancellationToken); + return ValueTaskFactory.CompletedTask; } private static bool IsPotentialReference( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs index 1131b5489010d..bb1dbb697400a 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs @@ -7,6 +7,7 @@ using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -108,7 +109,7 @@ private static bool IsGetAwaiterMethod(IMethodSymbol methodSymbol) private static bool IsAddMethod(IMethodSymbol methodSymbol) => methodSymbol.Name == WellKnownMemberNames.CollectionInitializerAddMethodName; - protected sealed override async ValueTask FindReferencesInDocumentAsync( + protected sealed override ValueTask FindReferencesInDocumentAsync( IMethodSymbol symbol, FindReferencesDocumentState state, Action processResult, @@ -116,22 +117,24 @@ protected sealed override async ValueTask FindReferencesInDocumentAsync( FindReferencesSearchOptions options, CancellationToken cancellationToken) { - await FindReferencesInDocumentUsingSymbolNameAsync( - symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInDocumentUsingSymbolName( + symbol, state, processResult, processResultData, cancellationToken); if (IsForEachMethod(symbol)) - await FindReferencesInForEachStatementsAsync(symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInForEachStatements(symbol, state, processResult, processResultData, cancellationToken); if (IsDeconstructMethod(symbol)) - await FindReferencesInDeconstructionAsync(symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInDeconstruction(symbol, state, processResult, processResultData, cancellationToken); if (IsGetAwaiterMethod(symbol)) - await FindReferencesInAwaitExpressionAsync(symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInAwaitExpression(symbol, state, processResult, processResultData, cancellationToken); - await FindReferencesInDocumentInsideGlobalSuppressionsAsync( - symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInDocumentInsideGlobalSuppressions( + symbol, state, processResult, processResultData, cancellationToken); if (IsAddMethod(symbol)) - await FindReferencesInCollectionInitializerAsync(symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInCollectionInitializer(symbol, state, processResult, processResultData, cancellationToken); + + return ValueTaskFactory.CompletedTask; } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs index b13c380c6f9c9..adef39983674b 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -46,7 +47,8 @@ protected override ValueTask FindReferencesInDocumentAsync( FindReferencesSearchOptions options, CancellationToken cancellationToken) { - return FindReferencesInDocumentUsingIdentifierAsync(symbol, symbol.Name, state, processResult, processResultData, cancellationToken); + FindReferencesInDocumentUsingIdentifier(symbol, symbol.Name, state, processResult, processResultData, cancellationToken); + return ValueTaskFactory.CompletedTask; } protected override async ValueTask> DetermineCascadedSymbolsAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs index 7c12a31b19e68..cf0a657769c89 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs @@ -68,8 +68,8 @@ protected override async ValueTask FindReferencesInDocumentAsync( FindReferencesSearchOptions options, CancellationToken cancellationToken) { - await FindReferencesInDocumentUsingSymbolNameAsync( - symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInDocumentUsingSymbolName( + symbol, state, processResult, processResultData, cancellationToken); if (symbol.AssociatedSymbol is not IPropertySymbol property || !options.AssociatePropertyReferencesWithSpecificAccessor) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs index cb70c9670db68..d5fed2c94c5aa 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs @@ -13,6 +13,7 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -118,7 +119,7 @@ protected sealed override async Task DetermineDocumentsToSearchAsync( private static bool IsForEachProperty(IPropertySymbol symbol) => symbol.Name == WellKnownMemberNames.CurrentPropertyName; - protected sealed override async ValueTask FindReferencesInDocumentAsync( + protected sealed override ValueTask FindReferencesInDocumentAsync( IPropertySymbol symbol, FindReferencesDocumentState state, Action processResult, @@ -126,7 +127,7 @@ protected sealed override async ValueTask FindReferencesInDocumentAsync( FindReferencesSearchOptions options, CancellationToken cancellationToken) { - await FindReferencesInDocumentUsingSymbolNameAsync( + FindReferencesInDocumentUsingSymbolName( symbol, state, static (loc, data) => @@ -146,16 +147,17 @@ await FindReferencesInDocumentUsingSymbolNameAsync( data.processResult(loc, data.processResultData); }, processResultData: (self: this, symbol, state, processResult, processResultData, options, cancellationToken), - cancellationToken).ConfigureAwait(false); + cancellationToken); if (IsForEachProperty(symbol)) - await FindReferencesInForEachStatementsAsync(symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInForEachStatements(symbol, state, processResult, processResultData, cancellationToken); if (symbol.IsIndexer) - await FindIndexerReferencesAsync(symbol, state, processResult, processResultData, options, cancellationToken).ConfigureAwait(false); + FindIndexerReferences(symbol, state, processResult, processResultData, options, cancellationToken); - await FindReferencesInDocumentInsideGlobalSuppressionsAsync( - symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInDocumentInsideGlobalSuppressions( + symbol, state, processResult, processResultData, cancellationToken); + return ValueTaskFactory.CompletedTask; } private static Task FindDocumentWithExplicitOrImplicitElementAccessExpressionsAsync( @@ -172,7 +174,7 @@ private static Task FindDocumentWithIndexerMemberCrefAsync( project, documents, static index => index.ContainsIndexerMemberCref, processResult, processResultData, cancellationToken); } - private static async Task FindIndexerReferencesAsync( + private static void FindIndexerReferences( IPropertySymbol symbol, FindReferencesDocumentState state, Action processResult, @@ -200,8 +202,7 @@ private static async Task FindIndexerReferencesAsync( { cancellationToken.ThrowIfCancellationRequested(); - var (matched, candidateReason, indexerReference) = await ComputeIndexerInformationAsync( - symbol, state, node, cancellationToken).ConfigureAwait(false); + var (matched, candidateReason, indexerReference) = ComputeIndexerInformation(symbol, state, node, cancellationToken); if (!matched) continue; @@ -217,7 +218,7 @@ private static async Task FindIndexerReferencesAsync( } } - private static ValueTask<(bool matched, CandidateReason reason, SyntaxNode indexerReference)> ComputeIndexerInformationAsync( + private static (bool matched, CandidateReason reason, SyntaxNode indexerReference) ComputeIndexerInformation( IPropertySymbol symbol, FindReferencesDocumentState state, SyntaxNode node, @@ -228,33 +229,33 @@ private static async Task FindIndexerReferencesAsync( if (syntaxFacts.IsElementAccessExpression(node)) { // The indexerReference for an element access expression will not be null - return ComputeElementAccessInformationAsync(symbol, node, state, cancellationToken)!; + return ComputeElementAccessInformation(symbol, node, state, cancellationToken)!; } else if (syntaxFacts.IsImplicitElementAccess(node)) { - return ComputeImplicitElementAccessInformationAsync(symbol, node, state, cancellationToken)!; + return ComputeImplicitElementAccessInformation(symbol, node, state, cancellationToken)!; } else if (syntaxFacts.IsConditionalAccessExpression(node)) { - return ComputeConditionalAccessInformationAsync(symbol, node, state, cancellationToken); + return ComputeConditionalAccessInformation(symbol, node, state, cancellationToken); } else { Debug.Assert(syntaxFacts.IsIndexerMemberCref(node)); - return ComputeIndexerMemberCRefInformationAsync(symbol, state, node, cancellationToken); + return ComputeIndexerMemberCRefInformation(symbol, state, node, cancellationToken); } } - private static async ValueTask<(bool matched, CandidateReason reason, SyntaxNode indexerReference)> ComputeIndexerMemberCRefInformationAsync( + private static (bool matched, CandidateReason reason, SyntaxNode indexerReference) ComputeIndexerMemberCRefInformation( IPropertySymbol symbol, FindReferencesDocumentState state, SyntaxNode node, CancellationToken cancellationToken) { - var (matched, reason) = await SymbolsMatchAsync(symbol, state, node, cancellationToken).ConfigureAwait(false); + var (matched, reason) = SymbolsMatch(symbol, state, node, cancellationToken); // For an IndexerMemberCRef the node itself is the indexer we are looking for. return (matched, reason, node); } - private static async ValueTask<(bool matched, CandidateReason reason, SyntaxNode indexerReference)> ComputeConditionalAccessInformationAsync( + private static (bool matched, CandidateReason reason, SyntaxNode indexerReference) ComputeConditionalAccessInformation( IPropertySymbol symbol, SyntaxNode node, FindReferencesDocumentState state, CancellationToken cancellationToken) { // For a ConditionalAccessExpression the whenNotNull component is the indexer reference we are looking for @@ -269,16 +270,16 @@ private static async Task FindIndexerReferencesAsync( return default; } - var (matched, reason) = await SymbolsMatchAsync(symbol, state, indexerReference, cancellationToken).ConfigureAwait(false); + var (matched, reason) = SymbolsMatch(symbol, state, indexerReference, cancellationToken); return (matched, reason, indexerReference); } - private static async ValueTask<(bool matched, CandidateReason reason, SyntaxNode? indexerReference)> ComputeElementAccessInformationAsync( + private static (bool matched, CandidateReason reason, SyntaxNode? indexerReference) ComputeElementAccessInformation( IPropertySymbol symbol, SyntaxNode node, FindReferencesDocumentState state, CancellationToken cancellationToken) { // For an ElementAccessExpression the indexer we are looking for is the argumentList component. state.SyntaxFacts.GetPartsOfElementAccessExpression(node, out var expression, out var indexerReference); - if (expression != null && (await SymbolsMatchAsync(symbol, state, expression, cancellationToken).ConfigureAwait(false)).matched) + if (expression != null && SymbolsMatch(symbol, state, expression, cancellationToken).matched) { // Element access with explicit member name (allowed in VB). We will have // already added a reference location for the member name identifier, so skip @@ -286,15 +287,15 @@ private static async Task FindIndexerReferencesAsync( return default; } - var (matched, reason) = await SymbolsMatchAsync(symbol, state, node, cancellationToken).ConfigureAwait(false); + var (matched, reason) = SymbolsMatch(symbol, state, node, cancellationToken); return (matched, reason, indexerReference); } - private static async ValueTask<(bool matched, CandidateReason reason, SyntaxNode indexerReference)> ComputeImplicitElementAccessInformationAsync( + private static (bool matched, CandidateReason reason, SyntaxNode indexerReference) ComputeImplicitElementAccessInformation( IPropertySymbol symbol, SyntaxNode node, FindReferencesDocumentState state, CancellationToken cancellationToken) { var argumentList = state.SyntaxFacts.GetArgumentListOfImplicitElementAccess(node); - var (matched, reason) = await SymbolsMatchAsync(symbol, state, node, cancellationToken).ConfigureAwait(false); + var (matched, reason) = SymbolsMatch(symbol, state, node, cancellationToken); return (matched, reason, argumentList); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/NoOpStreamingFindReferencesProgress.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/NoOpStreamingFindReferencesProgress.cs index 511fd7fb42fd7..b10cd55dfc0e9 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/NoOpStreamingFindReferencesProgress.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/NoOpStreamingFindReferencesProgress.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Shared.Utilities; @@ -26,9 +27,7 @@ private NoOpStreamingFindReferencesProgress() public ValueTask OnCompletedAsync(CancellationToken cancellationToken) => default; public ValueTask OnStartedAsync(CancellationToken cancellationToken) => default; public ValueTask OnDefinitionFoundAsync(SymbolGroup group, CancellationToken cancellationToken) => default; - public ValueTask OnReferenceFoundAsync(SymbolGroup group, ISymbol symbol, ReferenceLocation location, CancellationToken cancellationToken) => default; - public ValueTask OnFindInDocumentStartedAsync(Document document, CancellationToken cancellationToken) => default; - public ValueTask OnFindInDocumentCompletedAsync(Document document, CancellationToken cancellationToken) => default; + public ValueTask OnReferencesFoundAsync(ImmutableArray<(SymbolGroup group, ISymbol symbol, ReferenceLocation location)> references, CancellationToken cancellationToken) => default; private class NoOpProgressTracker : IStreamingProgressTracker { diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/StreamingFindReferencesProgress.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/StreamingFindReferencesProgress.cs index 0a13aa5929644..32a4ce2080823 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/StreamingFindReferencesProgress.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/StreamingFindReferencesProgress.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.ErrorReporting; @@ -37,18 +38,6 @@ public ValueTask OnCompletedAsync(CancellationToken cancellationToken) return default; } - public ValueTask OnFindInDocumentCompletedAsync(Document document, CancellationToken cancellationToken) - { - _progress.OnFindInDocumentCompleted(document); - return default; - } - - public ValueTask OnFindInDocumentStartedAsync(Document document, CancellationToken cancellationToken) - { - _progress.OnFindInDocumentStarted(document); - return default; - } - public ValueTask OnDefinitionFoundAsync(SymbolGroup group, CancellationToken cancellationToken) { try @@ -64,9 +53,11 @@ public ValueTask OnDefinitionFoundAsync(SymbolGroup group, CancellationToken can } } - public ValueTask OnReferenceFoundAsync(SymbolGroup group, ISymbol symbol, ReferenceLocation location, CancellationToken cancellationToken) + public ValueTask OnReferencesFoundAsync(ImmutableArray<(SymbolGroup group, ISymbol symbol, ReferenceLocation location)> references, CancellationToken cancellationToken) { - _progress.OnReferenceFound(symbol, location); + foreach (var (_, symbol, location) in references) + _progress.OnReferenceFound(symbol, location); + return default; } diff --git a/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinderService.cs b/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinderService.cs index 1f3b3736c5090..2c95215a67b28 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinderService.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinderService.cs @@ -19,10 +19,8 @@ internal interface ICallback ValueTask ReferenceItemsCompletedAsync(RemoteServiceCallbackId callbackId, int count, CancellationToken cancellationToken); ValueTask OnStartedAsync(RemoteServiceCallbackId callbackId, CancellationToken cancellationToken); ValueTask OnCompletedAsync(RemoteServiceCallbackId callbackId, CancellationToken cancellationToken); - ValueTask OnFindInDocumentStartedAsync(RemoteServiceCallbackId callbackId, DocumentId documentId, CancellationToken cancellationToken); - ValueTask OnFindInDocumentCompletedAsync(RemoteServiceCallbackId callbackId, DocumentId documentId, CancellationToken cancellationToken); ValueTask OnDefinitionFoundAsync(RemoteServiceCallbackId callbackId, SerializableSymbolGroup group, CancellationToken cancellationToken); - ValueTask OnReferenceFoundAsync(RemoteServiceCallbackId callbackId, SerializableSymbolGroup group, SerializableSymbolAndProjectId definition, SerializableReferenceLocation reference, CancellationToken cancellationToken); + ValueTask OnReferencesFoundAsync(RemoteServiceCallbackId callbackId, ImmutableArray<(SerializableSymbolGroup group, SerializableSymbolAndProjectId definition, SerializableReferenceLocation reference)> references, CancellationToken cancellationToken); ValueTask AddLiteralItemsAsync(RemoteServiceCallbackId callbackId, int count, CancellationToken cancellationToken); ValueTask LiteralItemsCompletedAsync(RemoteServiceCallbackId callbackId, int count, CancellationToken cancellationToken); diff --git a/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs b/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs index fa7a34043bc62..5a84f3f20655e 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs @@ -72,11 +72,8 @@ internal interface IStreamingFindReferencesProgress ValueTask OnStartedAsync(CancellationToken cancellationToken); ValueTask OnCompletedAsync(CancellationToken cancellationToken); - ValueTask OnFindInDocumentStartedAsync(Document document, CancellationToken cancellationToken); - ValueTask OnFindInDocumentCompletedAsync(Document document, CancellationToken cancellationToken); - ValueTask OnDefinitionFoundAsync(SymbolGroup group, CancellationToken cancellationToken); - ValueTask OnReferenceFoundAsync(SymbolGroup group, ISymbol symbol, ReferenceLocation location, CancellationToken cancellationToken); + ValueTask OnReferencesFoundAsync(ImmutableArray<(SymbolGroup group, ISymbol symbol, ReferenceLocation location)> references, CancellationToken cancellationToken); } internal interface IStreamingFindLiteralReferencesProgress diff --git a/src/Workspaces/Core/Portable/FindSymbols/StreamingProgressCollector.cs b/src/Workspaces/Core/Portable/FindSymbols/StreamingProgressCollector.cs index 9018ef3297747..912ae358bea4b 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/StreamingProgressCollector.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/StreamingProgressCollector.cs @@ -51,9 +51,6 @@ public ImmutableArray GetReferencedSymbols() public ValueTask OnStartedAsync(CancellationToken cancellationToken) => underlyingProgress.OnStartedAsync(cancellationToken); public ValueTask OnCompletedAsync(CancellationToken cancellationToken) => underlyingProgress.OnCompletedAsync(cancellationToken); - public ValueTask OnFindInDocumentCompletedAsync(Document document, CancellationToken cancellationToken) => underlyingProgress.OnFindInDocumentCompletedAsync(document, cancellationToken); - public ValueTask OnFindInDocumentStartedAsync(Document document, CancellationToken cancellationToken) => underlyingProgress.OnFindInDocumentStartedAsync(document, cancellationToken); - public ValueTask OnDefinitionFoundAsync(SymbolGroup group, CancellationToken cancellationToken) { try @@ -72,13 +69,15 @@ public ValueTask OnDefinitionFoundAsync(SymbolGroup group, CancellationToken can } } - public ValueTask OnReferenceFoundAsync(SymbolGroup group, ISymbol definition, ReferenceLocation location, CancellationToken cancellationToken) + public ValueTask OnReferencesFoundAsync( + ImmutableArray<(SymbolGroup group, ISymbol symbol, ReferenceLocation location)> references, CancellationToken cancellationToken) { lock (_gate) { - _symbolToLocations[definition].Add(location); + foreach (var (_, definition, location) in references) + _symbolToLocations[definition].Add(location); } - return underlyingProgress.OnReferenceFoundAsync(group, definition, location, cancellationToken); + return underlyingProgress.OnReferencesFoundAsync(references, cancellationToken); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.CallbackDispatcher.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.CallbackDispatcher.cs index 1c4eaad54f7a0..84d6270bf7145 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.CallbackDispatcher.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.CallbackDispatcher.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Immutable; using System.Composition; using System.Threading; using System.Threading.Tasks; @@ -43,14 +44,8 @@ public ValueTask OnCompletedAsync(RemoteServiceCallbackId callbackId, Cancellati public ValueTask OnDefinitionFoundAsync(RemoteServiceCallbackId callbackId, SerializableSymbolGroup symbolGroup, CancellationToken cancellationToken) => GetFindReferencesCallback(callbackId).OnDefinitionFoundAsync(symbolGroup, cancellationToken); - public ValueTask OnFindInDocumentCompletedAsync(RemoteServiceCallbackId callbackId, DocumentId documentId, CancellationToken cancellationToken) - => GetFindReferencesCallback(callbackId).OnFindInDocumentCompletedAsync(documentId, cancellationToken); - - public ValueTask OnFindInDocumentStartedAsync(RemoteServiceCallbackId callbackId, DocumentId documentId, CancellationToken cancellationToken) - => GetFindReferencesCallback(callbackId).OnFindInDocumentStartedAsync(documentId, cancellationToken); - - public ValueTask OnReferenceFoundAsync(RemoteServiceCallbackId callbackId, SerializableSymbolGroup symbolGroup, SerializableSymbolAndProjectId definition, SerializableReferenceLocation reference, CancellationToken cancellationToken) - => GetFindReferencesCallback(callbackId).OnReferenceFoundAsync(symbolGroup, definition, reference, cancellationToken); + public ValueTask OnReferencesFoundAsync(RemoteServiceCallbackId callbackId, ImmutableArray<(SerializableSymbolGroup group, SerializableSymbolAndProjectId definition, SerializableReferenceLocation reference)> references, CancellationToken cancellationToken) + => GetFindReferencesCallback(callbackId).OnReferencesFoundAsync(references, cancellationToken); public ValueTask OnStartedAsync(RemoteServiceCallbackId callbackId, CancellationToken cancellationToken) => GetFindReferencesCallback(callbackId).OnStartedAsync(cancellationToken); diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs index 50b7dd3af8c58..2830187083028 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs @@ -8,8 +8,6 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Remote; -using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols; @@ -40,18 +38,6 @@ public ValueTask OnStartedAsync(CancellationToken cancellationToken) public ValueTask OnCompletedAsync(CancellationToken cancellationToken) => progress.OnCompletedAsync(cancellationToken); - public async ValueTask OnFindInDocumentStartedAsync(DocumentId documentId, CancellationToken cancellationToken) - { - var document = await solution.GetRequiredDocumentAsync(documentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); - await progress.OnFindInDocumentStartedAsync(document, cancellationToken).ConfigureAwait(false); - } - - public async ValueTask OnFindInDocumentCompletedAsync(DocumentId documentId, CancellationToken cancellationToken) - { - var document = await solution.GetRequiredDocumentAsync(documentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); - await progress.OnFindInDocumentCompletedAsync(document, cancellationToken).ConfigureAwait(false); - } - public async ValueTask OnDefinitionFoundAsync(SerializableSymbolGroup dehydrated, CancellationToken cancellationToken) { Contract.ThrowIfTrue(dehydrated.Symbols.Count == 0); @@ -78,34 +64,38 @@ public async ValueTask OnDefinitionFoundAsync(SerializableSymbolGroup dehydrated await progress.OnDefinitionFoundAsync(symbolGroup, cancellationToken).ConfigureAwait(false); } - public async ValueTask OnReferenceFoundAsync( - SerializableSymbolGroup serializableSymbolGroup, - SerializableSymbolAndProjectId serializableSymbol, - SerializableReferenceLocation reference, + public async ValueTask OnReferencesFoundAsync( + ImmutableArray<(SerializableSymbolGroup serializableSymbolGroup, SerializableSymbolAndProjectId serializableSymbol, SerializableReferenceLocation reference)> references, CancellationToken cancellationToken) { - SymbolGroup? symbolGroup; - ISymbol? symbol; - lock (_gate) + using var _ = ArrayBuilder<(SymbolGroup group, ISymbol symbol, ReferenceLocation location)>.GetInstance(references.Length, out var rehydrated); + foreach (var (serializableSymbolGroup, serializableSymbol, reference) in references) { - // The definition may not be in the map if we failed to map it over using TryRehydrateAsync in OnDefinitionFoundAsync. - // Just ignore this reference. Note: while this is a degraded experience: - // - // 1. TryRehydrateAsync logs an NFE so we can track down while we're failing to roundtrip the - // definition so we can track down that issue. - // 2. NFE'ing and failing to show a result, is much better than NFE'ing and then crashing - // immediately afterwards. - if (!_groupMap.TryGetValue(serializableSymbolGroup, out symbolGroup) || - !_definitionMap.TryGetValue(serializableSymbol, out symbol)) + SymbolGroup? symbolGroup; + ISymbol? symbol; + lock (_gate) { - return; + // The definition may not be in the map if we failed to map it over using TryRehydrateAsync in OnDefinitionFoundAsync. + // Just ignore this reference. Note: while this is a degraded experience: + // + // 1. TryRehydrateAsync logs an NFE so we can track down while we're failing to roundtrip the + // definition so we can track down that issue. + // 2. NFE'ing and failing to show a result, is much better than NFE'ing and then crashing + // immediately afterwards. + if (!_groupMap.TryGetValue(serializableSymbolGroup, out symbolGroup) || + !_definitionMap.TryGetValue(serializableSymbol, out symbol)) + { + continue; + } } - } - var referenceLocation = await reference.RehydrateAsync( - solution, cancellationToken).ConfigureAwait(false); + var referenceLocation = await reference.RehydrateAsync( + solution, cancellationToken).ConfigureAwait(false); + rehydrated.Add((symbolGroup, symbol, referenceLocation)); + } - await progress.OnReferenceFoundAsync(symbolGroup, symbol, referenceLocation, cancellationToken).ConfigureAwait(false); + if (rehydrated.Count > 0) + await progress.OnReferencesFoundAsync(rehydrated.ToImmutableAndClear(), cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Helpers.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Helpers.cs index 76d59fc83456d..c0d596fa2f7ed 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Helpers.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Helpers.cs @@ -30,11 +30,10 @@ Accessibility.Protected or return true; } - internal static async Task OriginalSymbolsMatchAsync( + internal static bool OriginalSymbolsMatch( Solution solution, ISymbol? searchSymbol, - ISymbol? symbolToMatch, - CancellationToken cancellationToken) + ISymbol? symbolToMatch) { if (ReferenceEquals(searchSymbol, symbolToMatch)) return true; @@ -48,7 +47,7 @@ internal static async Task OriginalSymbolsMatchAsync( if (searchSymbol.Equals(symbolToMatch)) return true; - if (await OriginalSymbolsMatchCoreAsync(solution, searchSymbol, symbolToMatch, cancellationToken).ConfigureAwait(false)) + if (OriginalSymbolsMatchCore(solution, searchSymbol, symbolToMatch)) return true; if (searchSymbol.Kind == SymbolKind.Namespace && symbolToMatch.Kind == SymbolKind.Namespace) @@ -60,8 +59,8 @@ internal static async Task OriginalSymbolsMatchAsync( var namespace2Count = namespace2.ConstituentNamespaces.Length; if (namespace1Count != namespace2Count) { - if ((namespace1Count > 1 && await namespace1.ConstituentNamespaces.AnyAsync(static (n, arg) => NamespaceSymbolsMatchAsync(arg.solution, n, arg.namespace2, arg.cancellationToken), (solution, namespace2, cancellationToken)).ConfigureAwait(false)) || - (namespace2Count > 1 && await namespace2.ConstituentNamespaces.AnyAsync(static (n2, arg) => NamespaceSymbolsMatchAsync(arg.solution, arg.namespace1, n2, arg.cancellationToken), (solution, namespace1, cancellationToken)).ConfigureAwait(false))) + if ((namespace1Count > 1 && namespace1.ConstituentNamespaces.Any(static (n, arg) => OriginalSymbolsMatch(arg.solution, n, arg.namespace2), (solution, namespace2))) || + (namespace2Count > 1 && namespace2.ConstituentNamespaces.Any(static (n2, arg) => OriginalSymbolsMatch(arg.solution, arg.namespace1, n2), (solution, namespace1)))) { return true; } @@ -71,11 +70,8 @@ internal static async Task OriginalSymbolsMatchAsync( return false; } - private static async Task OriginalSymbolsMatchCoreAsync( - Solution solution, - ISymbol searchSymbol, - ISymbol symbolToMatch, - CancellationToken cancellationToken) + private static bool OriginalSymbolsMatchCore( + Solution solution, ISymbol searchSymbol, ISymbol symbolToMatch) { if (searchSymbol == null || symbolToMatch == null) return false; @@ -121,32 +117,22 @@ private static async Task OriginalSymbolsMatchCoreAsync( if (equivalentTypesWithDifferingAssemblies.Count > 0) { // Step 3a) Ensure that all pairs of named types in equivalentTypesWithDifferingAssemblies are indeed equivalent types. - return await VerifyForwardedTypesAsync(solution, equivalentTypesWithDifferingAssemblies, cancellationToken).ConfigureAwait(false); + return VerifyForwardedTypes(solution, equivalentTypesWithDifferingAssemblies); } // 3b) If no such named type pairs were encountered, symbols ARE equivalent. return true; } - private static Task NamespaceSymbolsMatchAsync( - Solution solution, - INamespaceSymbol namespace1, - INamespaceSymbol namespace2, - CancellationToken cancellationToken) - { - return OriginalSymbolsMatchAsync(solution, namespace1, namespace2, cancellationToken); - } - /// /// Verifies that all pairs of named types in equivalentTypesWithDifferingAssemblies are equivalent forwarded types. /// - private static async Task VerifyForwardedTypesAsync( + private static bool VerifyForwardedTypes( Solution solution, - Dictionary equivalentTypesWithDifferingAssemblies, - CancellationToken cancellationToken) + Dictionary equivalentTypesWithDifferingAssemblies) { Contract.ThrowIfNull(equivalentTypesWithDifferingAssemblies); - Contract.ThrowIfTrue(!equivalentTypesWithDifferingAssemblies.Any()); + Contract.ThrowIfTrue(equivalentTypesWithDifferingAssemblies.Count == 0); // Must contain equivalents named types residing in different assemblies. Contract.ThrowIfFalse(equivalentTypesWithDifferingAssemblies.All(kvp => !SymbolEquivalenceComparer.Instance.Equals(kvp.Key.ContainingAssembly, kvp.Value.ContainingAssembly))); @@ -155,16 +141,13 @@ private static async Task VerifyForwardedTypesAsync( Contract.ThrowIfFalse(equivalentTypesWithDifferingAssemblies.All(kvp => kvp.Key.ContainingType == null)); Contract.ThrowIfFalse(equivalentTypesWithDifferingAssemblies.All(kvp => kvp.Value.ContainingType == null)); - // Cache compilations so we avoid recreating any as we walk the pairs of types. - using var _ = PooledHashSet.GetInstance(out var compilationSet); - foreach (var (type1, type2) in equivalentTypesWithDifferingAssemblies) { // Check if type1 was forwarded to type2 in type2's compilation, or if type2 was forwarded to type1 in // type1's compilation. We check both direction as this API is called from higher level comparison APIs // that are unordered. - if (!await VerifyForwardedTypeAsync(solution, candidate: type1, forwardedTo: type2, compilationSet, cancellationToken).ConfigureAwait(false) && - !await VerifyForwardedTypeAsync(solution, candidate: type2, forwardedTo: type1, compilationSet, cancellationToken).ConfigureAwait(false)) + if (!VerifyForwardedType(solution, candidate: type1, forwardedTo: type2) && + !VerifyForwardedType(solution, candidate: type2, forwardedTo: type1)) { return false; } @@ -177,30 +160,20 @@ private static async Task VerifyForwardedTypesAsync( /// Returns if was forwarded to in /// 's . /// - private static async Task VerifyForwardedTypeAsync( + private static bool VerifyForwardedType( Solution solution, INamedTypeSymbol candidate, - INamedTypeSymbol forwardedTo, - HashSet compilationSet, - CancellationToken cancellationToken) + INamedTypeSymbol forwardedTo) { // Only need to operate on original definitions. i.e. List is the type that is forwarded, // not List. candidate = GetOridinalUnderlyingType(candidate); forwardedTo = GetOridinalUnderlyingType(forwardedTo); - var forwardedToOriginatingProject = solution.GetOriginatingProject(forwardedTo); - if (forwardedToOriginatingProject == null) - return false; - - var forwardedToCompilation = await forwardedToOriginatingProject.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); + var forwardedToCompilation = solution.GetOriginatingCompilation(forwardedTo); if (forwardedToCompilation == null) return false; - // Cache the compilation so that if we need it while checking another set of forwarded types, we don't - // expensively throw it away and recreate it. - compilationSet.Add(forwardedToCompilation); - var candidateFullMetadataName = candidate.ContainingNamespace?.IsGlobalNamespace != false ? candidate.MetadataName : $"{candidate.ContainingNamespace.ToDisplayString(SymbolDisplayFormats.SignatureFormat)}.{candidate.MetadataName}"; diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Hierarchy.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Hierarchy.cs index 7bfe7ed73548e..ad70664b74512 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Hierarchy.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Hierarchy.cs @@ -54,10 +54,8 @@ internal static async Task> FindOverridesArrayAsync( var sourceMember = await FindSourceDefinitionAsync(m, solution, cancellationToken).ConfigureAwait(false); var bestMember = sourceMember ?? m; - if (await IsOverrideAsync(solution, bestMember, symbol, cancellationToken).ConfigureAwait(false)) - { + if (IsOverride(solution, bestMember, symbol)) results.Add(bestMember); - } } } } @@ -65,11 +63,11 @@ internal static async Task> FindOverridesArrayAsync( return results.ToImmutableAndFree(); } - internal static async Task IsOverrideAsync(Solution solution, ISymbol member, ISymbol symbol, CancellationToken cancellationToken) + internal static bool IsOverride(Solution solution, ISymbol member, ISymbol symbol) { for (var current = member; current != null; current = current.GetOverriddenMember()) { - if (await OriginalSymbolsMatchAsync(solution, current.GetOverriddenMember(), symbol.OriginalDefinition, cancellationToken).ConfigureAwait(false)) + if (OriginalSymbolsMatch(solution, current.GetOverriddenMember(), symbol.OriginalDefinition)) return true; } @@ -159,8 +157,7 @@ internal static async Task> FindImplementedInterfaceMemb var sourceMethod = await FindSourceDefinitionAsync(interfaceMember, solution, cancellationToken).ConfigureAwait(false); var bestMethod = sourceMethod ?? interfaceMember; - var implementations = await type.FindImplementationsForInterfaceMemberAsync( - bestMethod, solution, cancellationToken).ConfigureAwait(false); + var implementations = type.FindImplementationsForInterfaceMember(bestMethod, solution, cancellationToken); foreach (var implementation in implementations) { if (implementation != null && @@ -361,7 +358,7 @@ internal static async Task> FindMemberImplementationsArr using var _ = ArrayBuilder.GetInstance(out var results); foreach (var t in allTypes) { - var implementations = await t.FindImplementationsForInterfaceMemberAsync(symbol, solution, cancellationToken).ConfigureAwait(false); + var implementations = t.FindImplementationsForInterfaceMember(symbol, solution, cancellationToken); foreach (var implementation in implementations) { var sourceDef = await FindSourceDefinitionAsync(implementation, solution, cancellationToken).ConfigureAwait(false); diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.cs b/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.cs index 6bef4e0d8324e..4153fcdbc4fa3 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.cs @@ -27,7 +27,7 @@ internal static partial class ITypeSymbolExtensions /// interfaceMember, or this type doesn't supply a member that successfully implements /// interfaceMember). /// - public static async Task> FindImplementationsForInterfaceMemberAsync( + public static ImmutableArray FindImplementationsForInterfaceMember( this ITypeSymbol typeSymbol, ISymbol interfaceMember, Solution solution, @@ -97,13 +97,11 @@ not SymbolKind.Method and // OriginalSymbolMatch allows types to be matched across different assemblies if they are considered to // be the same type, which provides a more accurate implementations list for interfaces. var constructedInterfaceMember = - await constructedInterface.GetMembers(interfaceMember.Name).FirstOrDefaultAsync( - typeSymbol => SymbolFinder.OriginalSymbolsMatchAsync(solution, typeSymbol, interfaceMember, cancellationToken)).ConfigureAwait(false); + constructedInterface.GetMembers(interfaceMember.Name).FirstOrDefault( + typeSymbol => SymbolFinder.OriginalSymbolsMatch(solution, typeSymbol, interfaceMember)); if (constructedInterfaceMember == null) - { continue; - } // Now we need to walk the base type chain, but we start at the first type that actually // has the interface directly in its interface hierarchy. diff --git a/src/Workspaces/Core/Portable/Shared/Utilities/RoslynParallel.cs b/src/Workspaces/Core/Portable/Shared/Utilities/RoslynParallel.cs new file mode 100644 index 0000000000000..e0e5860e773de --- /dev/null +++ b/src/Workspaces/Core/Portable/Shared/Utilities/RoslynParallel.cs @@ -0,0 +1,68 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.PooledObjects; + +namespace Microsoft.CodeAnalysis.Shared.Utilities; + +internal static class RoslynParallel +{ +#pragma warning disable CA1068 // CancellationToken parameters must come last + public static async Task ForEachAsync( +#pragma warning restore CA1068 // CancellationToken parameters must come last + IEnumerable source, + CancellationToken cancellationToken, + Func body) + { + if (cancellationToken.IsCancellationRequested) + return; + +#if NET + await Parallel.ForEachAsync(source, cancellationToken, body).ConfigureAwait(false); +#else + using var _ = ArrayBuilder.GetInstance(out var tasks); + + foreach (var item in source) + { + tasks.Add(Task.Run(async () => + { + await body(item, cancellationToken).ConfigureAwait(false); + }, cancellationToken)); + } + + await Task.WhenAll(tasks).ConfigureAwait(false); +#endif + } + +#pragma warning disable CA1068 // CancellationToken parameters must come last + public static async Task ForEachAsync( +#pragma warning restore CA1068 // CancellationToken parameters must come last + IAsyncEnumerable source, + CancellationToken cancellationToken, + Func body) + { + if (cancellationToken.IsCancellationRequested) + return; + +#if NET + await Parallel.ForEachAsync(source, cancellationToken, body).ConfigureAwait(false); +#else + using var _ = ArrayBuilder.GetInstance(out var tasks); + + await foreach (var item in source) + { + tasks.Add(Task.Run(async () => + { + await body(item, cancellationToken).ConfigureAwait(false); + }, cancellationToken)); + } + + await Task.WhenAll(tasks).ConfigureAwait(false); +#endif + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs index 3d6f1098fbd04..df13aee1dcd6a 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs @@ -194,6 +194,14 @@ private static Project CreateProject(ProjectId projectId, Solution solution) internal Project? GetOriginatingProject(ISymbol symbol) => GetProject(GetOriginatingProjectId(symbol)); + /// + /// + /// Returns the that produced the symbol. In the case of a symbol that was retargetted + /// this will be the compilation it was retargtted into, not the original compilation that it was retargetted from. + /// + internal Compilation? GetOriginatingCompilation(ISymbol symbol) + => _compilationState.GetOriginatingProjectInfo(symbol)?.Compilation; + /// /// True if the solution contains the document in one of its projects /// diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.CompilationTrackerState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.CompilationTrackerState.cs index abffc060a6830..347957df7875f 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.CompilationTrackerState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.CompilationTrackerState.cs @@ -147,12 +147,9 @@ private sealed class FinalCompilationTrackerState : CompilationTrackerState public readonly bool HasSuccessfullyLoaded; /// - /// Weak set of the assembly, module and dynamic symbols that this compilation tracker has created. - /// This can be used to determine which project an assembly symbol came from after the fact. This is - /// needed as the compilation an assembly came from can be GC'ed and further requests to get that - /// compilation (or any of it's assemblies) may produce new assembly symbols. + /// Used to determine which project an assembly symbol came from after the fact. /// - public readonly UnrootedSymbolSet UnrootedSymbolSet; + private SingleInitNullable _rootedSymbolSet; /// /// The final compilation, with all references and source generators run. This is distinct from _rootedSymbolSet.Initialize( + static finalCompilationWithGeneratedDocuments => RootedSymbolSet.Create(finalCompilationWithGeneratedDocuments), + this.FinalCompilationWithGeneratedDocuments); + public FinalCompilationTrackerState WithCreationPolicy(CreationPolicy creationPolicy) => creationPolicy == this.CreationPolicy ? this @@ -235,8 +232,7 @@ public FinalCompilationTrackerState WithCreationPolicy(CreationPolicy creationPo FinalCompilationWithGeneratedDocuments, CompilationWithoutGeneratedDocuments, HasSuccessfullyLoaded, - GeneratorInfo, - UnrootedSymbolSet); + GeneratorInfo); private static void RecordAssemblySymbols(ProjectId projectId, Compilation compilation, Dictionary? metadataReferenceToProjectId) { diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.cs index 11b1bd8884d08..5f1b44eaf6799 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.cs @@ -100,23 +100,22 @@ public GeneratorDriver? GeneratorDriver } } - public bool ContainsAssemblyOrModuleOrDynamic(ISymbol symbol, bool primary, out MetadataReferenceInfo? referencedThrough) + public bool ContainsAssemblyOrModuleOrDynamic( + ISymbol symbol, bool primary, + [NotNullWhen(true)] out Compilation? compilation, + out MetadataReferenceInfo? referencedThrough) { - Debug.Assert(symbol.Kind is SymbolKind.Assembly or - SymbolKind.NetModule or - SymbolKind.DynamicType); - var state = this.ReadState(); - - var unrootedSymbolSet = (state as FinalCompilationTrackerState)?.UnrootedSymbolSet; - if (unrootedSymbolSet == null) + Debug.Assert(symbol.Kind is SymbolKind.Assembly or SymbolKind.NetModule or SymbolKind.DynamicType); + if (this.ReadState() is not FinalCompilationTrackerState finalState) { // this was not a tracker that has handed out a compilation (all compilations handed out must be // owned by a 'FinalState'). So this symbol could not be from us. + compilation = null; referencedThrough = null; return false; } - return unrootedSymbolSet.Value.ContainsAssemblyOrModuleOrDynamic(symbol, primary, out referencedThrough); + return finalState.RootedSymbolSet.ContainsAssemblyOrModuleOrDynamic(symbol, primary, out compilation, out referencedThrough); } /// diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.GeneratedFileReplacingCompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.GeneratedFileReplacingCompilationTracker.cs index 83dd3fa0a8ce6..d59b89fe1bcc9 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.GeneratedFileReplacingCompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.GeneratedFileReplacingCompilationTracker.cs @@ -50,18 +50,21 @@ public GeneratedFileReplacingCompilationTracker( _skeletonReferenceCache = underlyingTracker.GetClonedSkeletonReferenceCache(); } - public bool ContainsAssemblyOrModuleOrDynamic(ISymbol symbol, bool primary, out MetadataReferenceInfo? referencedThrough) + public bool ContainsAssemblyOrModuleOrDynamic( + ISymbol symbol, bool primary, + [NotNullWhen(true)] out Compilation? compilation, + out MetadataReferenceInfo? referencedThrough) { if (_compilationWithReplacements == null) { // We don't have a compilation yet, so this couldn't have came from us + compilation = null; referencedThrough = null; return false; } - else - { - return UnrootedSymbolSet.Create(_compilationWithReplacements).ContainsAssemblyOrModuleOrDynamic(symbol, primary, out referencedThrough); - } + + return RootedSymbolSet.Create(_compilationWithReplacements).ContainsAssemblyOrModuleOrDynamic( + symbol, primary, out compilation, out referencedThrough); } public ICompilationTracker Fork(ProjectState newProject, TranslationAction? translate) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.ICompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.ICompilationTracker.cs index 30455ec7cfd49..80ef8db9493ac 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.ICompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.ICompilationTracker.cs @@ -30,7 +30,10 @@ private interface ICompilationTracker /// of the symbols returned by for /// any of the references of the . /// - bool ContainsAssemblyOrModuleOrDynamic(ISymbol symbol, bool primary, out MetadataReferenceInfo? referencedThrough); + bool ContainsAssemblyOrModuleOrDynamic( + ISymbol symbol, bool primary, + [NotNullWhen(true)] out Compilation? compilation, + out MetadataReferenceInfo? referencedThrough); ICompilationTracker Fork(ProjectState newProject, TranslationAction? translate); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.RootedSymbolSet.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.RootedSymbolSet.cs new file mode 100644 index 0000000000000..c33523099f78e --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.RootedSymbolSet.cs @@ -0,0 +1,152 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis.PooledObjects; +using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; + +namespace Microsoft.CodeAnalysis; + +using SecondaryReferencedSymbol = (int hashCode, ISymbol symbol, SolutionCompilationState.MetadataReferenceInfo referenceInfo); + +internal partial class SolutionCompilationState +{ + internal readonly record struct MetadataReferenceInfo(MetadataReferenceProperties Properties, string? FilePath) + { + internal static MetadataReferenceInfo From(MetadataReference reference) + => new(reference.Properties, (reference as PortableExecutableReference)?.FilePath); + } + + /// + /// Information maintained for unrooted symbols. + /// + /// + /// The project the symbol originated from, i.e. the symbol is defined in the project or its metadata reference. + /// + /// + /// The Compilation that produced the symbol. + /// + /// + /// If the symbol is defined in a metadata reference of , information about the + /// reference. + /// + internal sealed record class OriginatingProjectInfo( + ProjectId ProjectId, + Compilation? Compilation, + MetadataReferenceInfo? ReferencedThrough); + + /// + /// A helper type for mapping back to an originating /. + /// + /// + /// In IDE scenarios we have the need to map from an to the that + /// contained a that could have produced that symbol. This is especially needed with OOP + /// scenarios where we have to communicate to OOP from VS (And vice versa) what symbol we are referring to. To do + /// this, we pass along a project where this symbol could be found, and enough information (a ) to resolve that symbol back in that that . + /// + private readonly struct RootedSymbolSet + { + public readonly Compilation Compilation; + + /// + /// The s or s produced through for all the references exposed by . Sorted by the hash code produced by so that it can be binary searched efficiently. + /// + public readonly ImmutableArray SecondaryReferencedSymbols; + + private RootedSymbolSet( + Compilation compilation, + ImmutableArray secondaryReferencedSymbols) + { + Compilation = compilation; + SecondaryReferencedSymbols = secondaryReferencedSymbols; + } + + public static RootedSymbolSet Create(Compilation compilation) + { + // PERF: Preallocate this array so we don't have to resize it as we're adding assembly symbols. + using var _ = ArrayBuilder.GetInstance( + compilation.ExternalReferences.Length + compilation.DirectiveReferences.Length, out var secondarySymbols); + + foreach (var reference in compilation.References) + { + var symbol = compilation.GetAssemblyOrModuleSymbol(reference); + if (symbol == null) + continue; + + secondarySymbols.Add((ReferenceEqualityComparer.GetHashCode(symbol), symbol, MetadataReferenceInfo.From(reference))); + } + + // Sort all the secondary symbols by their hash. This will allow us to easily binary search for them + // afterwards. Note: it is fine for multiple symbols to have the same reference hash. The search algorithm + // will account for that. + secondarySymbols.Sort(static (x, y) => x.hashCode.CompareTo(y.hashCode)); + return new RootedSymbolSet(compilation, secondarySymbols.ToImmutable()); + } + + public bool ContainsAssemblyOrModuleOrDynamic( + ISymbol symbol, bool primary, + [NotNullWhen(true)] out Compilation? compilation, + out MetadataReferenceInfo? referencedThrough) + { + if (primary) + { + if (this.Compilation.Assembly.Equals(symbol)) + { + compilation = this.Compilation; + referencedThrough = null; + return true; + } + + if (this.Compilation.Language == LanguageNames.CSharp && + this.Compilation.DynamicType.Equals(symbol)) + { + compilation = this.Compilation; + referencedThrough = null; + return true; + } + } + else + { + var secondarySymbols = this.SecondaryReferencedSymbols; + + var symbolHash = ReferenceEqualityComparer.GetHashCode(symbol); + + // The secondary symbol array is sorted by the symbols' hash codes. So do a binary search to find + // the location we should start looking at. + var index = secondarySymbols.BinarySearch(symbolHash, static (item, symbolHash) => item.hashCode.CompareTo(symbolHash)); + if (index >= 0) + { + // Could have multiple symbols with the same hash. They will all be placed next to each other, + // so walk backward to hit the first. + while (index > 0 && secondarySymbols[index - 1].hashCode == symbolHash) + index--; + + // Now, walk forward through the stored symbols with the same hash looking to see if any are a reference match. + while (index < secondarySymbols.Length && secondarySymbols[index].hashCode == symbolHash) + { + var cached = secondarySymbols[index]; + if (cached.symbol.Equals(symbol)) + { + referencedThrough = cached.referenceInfo; + compilation = this.Compilation; + return true; + } + + index++; + } + } + } + + compilation = null; + referencedThrough = null; + return false; + } + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SymbolToProjectId.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SymbolToProjectId.cs index 52a13061db566..5e86a295e2ef4 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SymbolToProjectId.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SymbolToProjectId.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Diagnostics; using System.Runtime.CompilerServices; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Shared.Extensions; @@ -172,13 +173,16 @@ internal partial class SolutionCompilationState return projectId; } - else if (symbol.IsKind(SymbolKind.TypeParameter, out ITypeParameterSymbol? typeParameter) && - typeParameter.TypeParameterKind == TypeParameterKind.Cref) + else if (symbol is ITypeParameterSymbol + { + TypeParameterKind: TypeParameterKind.Cref, + Locations: [{ SourceTree: var typeParameterSourceTree }, ..], + }) { // Cref type parameters don't belong to any containing symbol. But we can map them to a doc/project // using the declaring syntax of the type parameter itself. - if (GetDocumentState(typeParameter.Locations[0].SourceTree, projectId: null) is { } document) - return new OriginatingProjectInfo(document.Id.ProjectId, ReferencedThrough: null); + if (GetDocumentState(typeParameterSourceTree, projectId: null) is { } document) + return new OriginatingProjectInfo(document.Id.ProjectId, Compilation: null, ReferencedThrough: null); } return null; @@ -187,8 +191,8 @@ internal partial class SolutionCompilationState { foreach (var (id, tracker) in _projectIdToTrackerMap) { - if (tracker.ContainsAssemblyOrModuleOrDynamic(symbol, primary, out var referencedThrough)) - return new OriginatingProjectInfo(id, referencedThrough); + if (tracker.ContainsAssemblyOrModuleOrDynamic(symbol, primary, out var compilation, out var referencedThrough)) + return new OriginatingProjectInfo(id, compilation, referencedThrough); } return null; diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.UnrootedSymbolSet.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.UnrootedSymbolSet.cs deleted file mode 100644 index 06837aab31a62..0000000000000 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.UnrootedSymbolSet.cs +++ /dev/null @@ -1,156 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using Microsoft.CodeAnalysis.PooledObjects; -using Roslyn.Utilities; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; - -namespace Microsoft.CodeAnalysis; - -using SecondaryReferencedSymbol = (int hashCode, WeakReference symbol, SolutionCompilationState.MetadataReferenceInfo referenceInfo); - -internal partial class SolutionCompilationState -{ - internal readonly record struct MetadataReferenceInfo(MetadataReferenceProperties Properties, string? FilePath) - { - internal static MetadataReferenceInfo From(MetadataReference reference) - => new(reference.Properties, (reference as PortableExecutableReference)?.FilePath); - } - - /// - /// Information maintained for unrooted symbols. - /// - /// - /// The project the symbol originated from, i.e. the symbol is defined in the project or its metadata reference. - /// - /// - /// If the symbol is defined in a metadata reference of , information about the reference. - /// - internal sealed record class OriginatingProjectInfo(ProjectId ProjectId, MetadataReferenceInfo? ReferencedThrough); - - /// - /// A helper type for mapping back to an originating . - /// - /// - /// In IDE scenarios we have the need to map from an to the that - /// contained a that could have produced that symbol. This is especially needed with - /// OOP scenarios where we have to communicate to OOP from VS (And vice versa) what symbol we are referring to. - /// To do this, we pass along a project where this symbol could be found, and enough information (a ) to resolve that symbol back in that that . - /// - /// This is challenging however as symbols do not necessarily have back-pointers to s, - /// and as such, we can't just see which Project produced the that produced that . In other words, the doesn't root the compilation. Because - /// of that we keep track of those symbols per project in a weak fashion. Then, we can later see if a - /// symbol came from a particular project by checking if it is one of those weak symbols. We use weakly held - /// symbols to that a instance doesn't hold symbols alive. But, we know if we are - /// holding the symbol itself, then the weak-ref will stay alive such that we can do this containment check. - /// - /// - private readonly struct UnrootedSymbolSet - { - /// - /// The produced directly by . - /// - public readonly WeakReference PrimaryAssemblySymbol; - - /// - /// The produced directly by . Only - /// valid for . - /// - public readonly WeakReference PrimaryDynamicSymbol; - - /// - /// The s or s produced through for all the references exposed by . Sorted by the hash code produced by so that it can be binary searched efficiently. - /// - public readonly ImmutableArray SecondaryReferencedSymbols; - - private UnrootedSymbolSet( - WeakReference primaryAssemblySymbol, - WeakReference primaryDynamicSymbol, - ImmutableArray secondaryReferencedSymbols) - { - PrimaryAssemblySymbol = primaryAssemblySymbol; - PrimaryDynamicSymbol = primaryDynamicSymbol; - SecondaryReferencedSymbols = secondaryReferencedSymbols; - } - - public static UnrootedSymbolSet Create(Compilation compilation) - { - var primaryAssembly = new WeakReference(compilation.Assembly); - - // The dynamic type is also unrooted (i.e. doesn't point back at the compilation or source - // assembly). So we have to keep track of it so we can get back from it to a project in case the - // underlying compilation is GC'ed. - var primaryDynamic = new WeakReference( - compilation.Language == LanguageNames.CSharp ? compilation.DynamicType : null); - - // PERF: Preallocate this array so we don't have to resize it as we're adding assembly symbols. - using var _ = ArrayBuilder.GetInstance( - compilation.ExternalReferences.Length + compilation.DirectiveReferences.Length, out var secondarySymbols); - - foreach (var reference in compilation.References) - { - var symbol = compilation.GetAssemblyOrModuleSymbol(reference); - if (symbol == null) - continue; - - secondarySymbols.Add((ReferenceEqualityComparer.GetHashCode(symbol), new WeakReference(symbol), MetadataReferenceInfo.From(reference))); - } - - // Sort all the secondary symbols by their hash. This will allow us to easily binary search for - // them afterwards. Note: it is fine for multiple symbols to have the same reference hash. The - // search algorithm will account for that. - secondarySymbols.Sort(static (x, y) => x.hashCode.CompareTo(y.hashCode)); - return new UnrootedSymbolSet(primaryAssembly, primaryDynamic, secondarySymbols.ToImmutable()); - } - - public bool ContainsAssemblyOrModuleOrDynamic(ISymbol symbol, bool primary, out MetadataReferenceInfo? referencedThrough) - { - referencedThrough = null; - - if (primary) - { - return symbol.Equals(this.PrimaryAssemblySymbol.GetTarget()) || - symbol.Equals(this.PrimaryDynamicSymbol.GetTarget()); - } - - var secondarySymbols = this.SecondaryReferencedSymbols; - - var symbolHash = ReferenceEqualityComparer.GetHashCode(symbol); - - // The secondary symbol array is sorted by the symbols' hash codes. So do a binary search to find - // the location we should start looking at. - var index = secondarySymbols.BinarySearch(symbolHash, static (item, symbolHash) => item.hashCode.CompareTo(symbolHash)); - if (index < 0) - return false; - - // Could have multiple symbols with the same hash. They will all be placed next to each other, - // so walk backward to hit the first. - while (index > 0 && secondarySymbols[index - 1].hashCode == symbolHash) - index--; - - // Now, walk forward through the stored symbols with the same hash looking to see if any are a reference match. - while (index < secondarySymbols.Length && secondarySymbols[index].hashCode == symbolHash) - { - var cached = secondarySymbols[index]; - if (cached.symbol.TryGetTarget(out var otherSymbol) && otherSymbol == symbol) - { - referencedThrough = cached.referenceInfo; - return true; - } - - index++; - } - - return false; - } - } -} diff --git a/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs b/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs index 43be04c3b336c..83aef0424bc2b 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs @@ -218,12 +218,6 @@ public ValueTask OnStartedAsync(CancellationToken cancellationToken) public ValueTask OnCompletedAsync(CancellationToken cancellationToken) => _callback.InvokeAsync((callback, cancellationToken) => callback.OnCompletedAsync(_callbackId, cancellationToken), cancellationToken); - public ValueTask OnFindInDocumentStartedAsync(Document document, CancellationToken cancellationToken) - => _callback.InvokeAsync((callback, cancellationToken) => callback.OnFindInDocumentStartedAsync(_callbackId, document.Id, cancellationToken), cancellationToken); - - public ValueTask OnFindInDocumentCompletedAsync(Document document, CancellationToken cancellationToken) - => _callback.InvokeAsync((callback, cancellationToken) => callback.OnFindInDocumentCompletedAsync(_callbackId, document.Id, cancellationToken), cancellationToken); - public ValueTask OnDefinitionFoundAsync(SymbolGroup group, CancellationToken cancellationToken) { var dehydratedGroup = SerializableSymbolGroup.Dehydrate(_solution, group, cancellationToken); @@ -231,15 +225,18 @@ public ValueTask OnDefinitionFoundAsync(SymbolGroup group, CancellationToken can (callback, cancellationToken) => callback.OnDefinitionFoundAsync(_callbackId, dehydratedGroup, cancellationToken), cancellationToken); } - public ValueTask OnReferenceFoundAsync(SymbolGroup group, ISymbol definition, ReferenceLocation reference, CancellationToken cancellationToken) + public ValueTask OnReferencesFoundAsync( + ImmutableArray<(SymbolGroup group, ISymbol symbol, ReferenceLocation location)> references, + CancellationToken cancellationToken) { - var dehydratedGroup = SerializableSymbolGroup.Dehydrate(_solution, group, cancellationToken); - var dehydratedDefinition = SerializableSymbolAndProjectId.Dehydrate(_solution, definition, cancellationToken); - var dehydratedReference = SerializableReferenceLocation.Dehydrate(reference, cancellationToken); + var dehydrated = references.SelectAsArray(t => + (SerializableSymbolGroup.Dehydrate(_solution, t.group, cancellationToken), + SerializableSymbolAndProjectId.Dehydrate(_solution, t.symbol, cancellationToken), + SerializableReferenceLocation.Dehydrate(t.location, cancellationToken))); return _callback.InvokeAsync( - (callback, cancellationToken) => callback.OnReferenceFoundAsync( - _callbackId, dehydratedGroup, dehydratedDefinition, dehydratedReference, cancellationToken), cancellationToken); + (callback, cancellationToken) => callback.OnReferencesFoundAsync( + _callbackId, dehydrated, cancellationToken), cancellationToken); } public ValueTask AddItemsAsync(int count, CancellationToken cancellationToken)