diff --git a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptFindUsagesContext.cs b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptFindUsagesContext.cs index fb303bfacba06..32f0eaa82deb8 100644 --- a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptFindUsagesContext.cs +++ b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptFindUsagesContext.cs @@ -26,7 +26,7 @@ public ValueTask OnDefinitionFoundAsync(VSTypeScriptDefinitionItem definition, C => UnderlyingObject.OnDefinitionFoundAsync(definition.UnderlyingObject, cancellationToken); public ValueTask OnReferenceFoundAsync(VSTypeScriptSourceReferenceItem reference, CancellationToken cancellationToken) - => UnderlyingObject.OnReferenceFoundAsync(reference.UnderlyingObject, cancellationToken); + => UnderlyingObject.OnReferencesFoundAsync([reference.UnderlyingObject], cancellationToken); public ValueTask OnCompletedAsync(CancellationToken cancellationToken) => UnderlyingObject.OnCompletedAsync(cancellationToken); diff --git a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptFindUsagesService.cs b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptFindUsagesService.cs index aa1ebbf355ccb..ed1b0e7af0e08 100644 --- a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptFindUsagesService.cs +++ b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptFindUsagesService.cs @@ -46,7 +46,7 @@ public ValueTask OnDefinitionFoundAsync(VSTypeScriptDefinitionItem definition, C => _context.OnDefinitionFoundAsync(definition.UnderlyingObject, cancellationToken); public ValueTask OnReferenceFoundAsync(VSTypeScriptSourceReferenceItem reference, CancellationToken cancellationToken) - => _context.OnReferenceFoundAsync(reference.UnderlyingObject, cancellationToken); + => _context.OnReferencesFoundAsync([reference.UnderlyingObject], cancellationToken); public ValueTask OnCompletedAsync(CancellationToken cancellationToken) => ValueTaskFactory.CompletedTask; diff --git a/src/EditorFeatures/Core/FindUsages/BufferedFindUsagesContext.cs b/src/EditorFeatures/Core/FindUsages/BufferedFindUsagesContext.cs index 16f60a8fca065..f21a42587dae1 100644 --- a/src/EditorFeatures/Core/FindUsages/BufferedFindUsagesContext.cs +++ b/src/EditorFeatures/Core/FindUsages/BufferedFindUsagesContext.cs @@ -201,7 +201,7 @@ async ValueTask IFindUsagesContext.OnDefinitionFoundAsync(DefinitionItem definit } } - ValueTask IFindUsagesContext.OnReferenceFoundAsync(SourceReferenceItem reference, CancellationToken cancellationToken) + ValueTask IFindUsagesContext.OnReferencesFoundAsync(ImmutableArray references, CancellationToken cancellationToken) { // Entirely ignored. These features do not show references. Contract.Fail("GoToImpl/Base should never report a reference."); diff --git a/src/EditorFeatures/Core/Host/IStreamingFindReferencesPresenter.cs b/src/EditorFeatures/Core/Host/IStreamingFindReferencesPresenter.cs index 90a2dc7dace26..1cc8411b32b2c 100644 --- a/src/EditorFeatures/Core/Host/IStreamingFindReferencesPresenter.cs +++ b/src/EditorFeatures/Core/Host/IStreamingFindReferencesPresenter.cs @@ -23,7 +23,7 @@ internal interface IStreamingFindUsagesPresenter /// /// Tells the presenter that a search is starting. The returned /// is used to push information about the search into. i.e. when a reference is found - /// should be called. When the + /// should be called. When the /// search completes should be called. /// etc. etc. /// diff --git a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.vb b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.vb index 519099aa47553..c14cecabf1ee6 100644 --- a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.vb +++ b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.vb @@ -249,9 +249,9 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.FindReferences Return Nothing End Function - Public Overrides Function OnReferenceFoundAsync(reference As SourceReferenceItem, cancellationToken As CancellationToken) As ValueTask + Public Overrides Function OnReferencesFoundAsync(references As ImmutableArray(Of SourceReferenceItem), cancellationToken As CancellationToken) As ValueTask SyncLock gate - References.Add(reference) + Me.References.AddRange(references) End SyncLock Return Nothing diff --git a/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService.DefinitionTrackingContext.cs b/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService.DefinitionTrackingContext.cs index 30c8390088926..c2b710e9c7e2b 100644 --- a/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService.DefinitionTrackingContext.cs +++ b/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService.DefinitionTrackingContext.cs @@ -6,9 +6,6 @@ using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Classification; -using Microsoft.CodeAnalysis.FindUsages; -using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Notification; using Microsoft.CodeAnalysis.Shared.Utilities; @@ -42,8 +39,8 @@ public ValueTask ReportMessageAsync(string message, NotificationSeverity severit public ValueTask SetSearchTitleAsync(string title, CancellationToken cancellationToken) => _underlyingContext.SetSearchTitleAsync(title, cancellationToken); - public ValueTask OnReferenceFoundAsync(SourceReferenceItem reference, CancellationToken cancellationToken) - => _underlyingContext.OnReferenceFoundAsync(reference, cancellationToken); + public ValueTask OnReferencesFoundAsync(ImmutableArray references, CancellationToken cancellationToken) + => _underlyingContext.OnReferencesFoundAsync(references, cancellationToken); public ValueTask OnDefinitionFoundAsync(DefinitionItem definition, CancellationToken cancellationToken) { diff --git a/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService.ProgressAdapter.cs b/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService.ProgressAdapter.cs index 4e3563c936b61..0824e01ddb170 100644 --- a/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService.ProgressAdapter.cs +++ b/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService.ProgressAdapter.cs @@ -13,6 +13,7 @@ using Microsoft.CodeAnalysis.FindUsages; using Microsoft.CodeAnalysis.Navigation; using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -42,8 +43,8 @@ public async ValueTask OnReferenceFoundAsync(Document document, TextSpan span, C var classifiedSpans = await ClassifiedSpansAndHighlightSpanFactory.ClassifyAsync( documentSpan, classifiedSpans: null, options, cancellationToken).ConfigureAwait(false); - await _context.OnReferenceFoundAsync( - new SourceReferenceItem(_definition, documentSpan, classifiedSpans, SymbolUsageInfo.None), cancellationToken).ConfigureAwait(false); + await _context.OnReferencesFoundAsync( + [new SourceReferenceItem(_definition, documentSpan, classifiedSpans, SymbolUsageInfo.None)], cancellationToken).ConfigureAwait(false); } } @@ -51,7 +52,8 @@ await _context.OnReferenceFoundAsync( /// Forwards IFindReferencesProgress calls to an IFindUsagesContext instance. /// private sealed class FindReferencesProgressAdapter( - Solution solution, IFindUsagesContext context, FindReferencesSearchOptions searchOptions, OptionsProvider classificationOptions) : IStreamingFindReferencesProgress + Solution solution, IFindUsagesContext context, FindReferencesSearchOptions searchOptions, OptionsProvider classificationOptions) + : IStreamingFindReferencesProgress { /// /// We will hear about definition symbols many times while performing FAR. We'll @@ -107,20 +109,26 @@ public async ValueTask OnDefinitionFoundAsync(SymbolGroup group, CancellationTok } public async ValueTask OnReferencesFoundAsync( - ImmutableArray<(SymbolGroup group, ISymbol symbol, ReferenceLocation location)> references, CancellationToken cancellationToken) + IAsyncEnumerable<(SymbolGroup group, ISymbol symbol, ReferenceLocation location)> references, CancellationToken cancellationToken) { - 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); - } + await ProducerConsumer.RunParallelAsync( + source: references, + produceItems: static async (tuple, callback, args, cancellationToken) => + { + var (group, _, location) = tuple; + var definitionItem = await args.@this.GetDefinitionItemAsync(group, cancellationToken).ConfigureAwait(false); + var sourceReferenceItem = await location.TryCreateSourceReferenceItemAsync( + args.classificationOptions, + definitionItem, + includeHiddenLocations: false, + cancellationToken).ConfigureAwait(false); + if (sourceReferenceItem != null) + callback(sourceReferenceItem); + }, + consumeItems: static async (items, args, cancellationToken) => + await args.context.OnReferencesFoundAsync(items, cancellationToken).ConfigureAwait(false), + args: (@this: this, context, classificationOptions), + cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Features/Core/Portable/FindUsages/FindUsagesContext.cs b/src/Features/Core/Portable/FindUsages/FindUsagesContext.cs index e6e7096bc20b6..7772d63990a71 100644 --- a/src/Features/Core/Portable/FindUsages/FindUsagesContext.cs +++ b/src/Features/Core/Portable/FindUsages/FindUsagesContext.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.Notification; @@ -28,7 +29,7 @@ protected FindUsagesContext() public virtual ValueTask OnDefinitionFoundAsync(DefinitionItem definition, CancellationToken cancellationToken) => default; - public virtual ValueTask OnReferenceFoundAsync(SourceReferenceItem reference, CancellationToken cancellationToken) => default; + public virtual ValueTask OnReferencesFoundAsync(ImmutableArray references, CancellationToken cancellationToken) => default; protected virtual ValueTask ReportProgressAsync(int current, int maximum, CancellationToken cancellationToken) => default; } diff --git a/src/Features/Core/Portable/FindUsages/IFindUsagesContext.cs b/src/Features/Core/Portable/FindUsages/IFindUsagesContext.cs index 03b0c880ecc80..11c92e3f92e02 100644 --- a/src/Features/Core/Portable/FindUsages/IFindUsagesContext.cs +++ b/src/Features/Core/Portable/FindUsages/IFindUsagesContext.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.Notification; @@ -35,5 +36,5 @@ internal interface IFindUsagesContext ValueTask SetSearchTitleAsync(string title, CancellationToken cancellationToken); ValueTask OnDefinitionFoundAsync(DefinitionItem definition, CancellationToken cancellationToken); - ValueTask OnReferenceFoundAsync(SourceReferenceItem reference, CancellationToken cancellationToken); + ValueTask OnReferencesFoundAsync(ImmutableArray references, CancellationToken cancellationToken); } diff --git a/src/Features/Core/Portable/FindUsages/IRemoteFindUsagesService.cs b/src/Features/Core/Portable/FindUsages/IRemoteFindUsagesService.cs index e4020b4e7f7b7..dfc5c00592067 100644 --- a/src/Features/Core/Portable/FindUsages/IRemoteFindUsagesService.cs +++ b/src/Features/Core/Portable/FindUsages/IRemoteFindUsagesService.cs @@ -10,12 +10,10 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Classification; -using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Notification; -using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -32,7 +30,7 @@ internal interface ICallback : IRemoteOptionsCallback ValueTask ReportInformationalMessageAsync(RemoteServiceCallbackId callbackId, string message, CancellationToken cancellationToken); ValueTask SetSearchTitleAsync(RemoteServiceCallbackId callbackId, string title, CancellationToken cancellationToken); ValueTask OnDefinitionFoundAsync(RemoteServiceCallbackId callbackId, SerializableDefinitionItem definition, CancellationToken cancellationToken); - ValueTask OnReferenceFoundAsync(RemoteServiceCallbackId callbackId, SerializableSourceReferenceItem reference, CancellationToken cancellationToken); + ValueTask OnReferencesFoundAsync(RemoteServiceCallbackId callbackId, ImmutableArray references, CancellationToken cancellationToken); } ValueTask FindReferencesAsync( @@ -73,8 +71,8 @@ public ValueTask ItemsCompletedAsync(RemoteServiceCallbackId callbackId, int cou public ValueTask OnDefinitionFoundAsync(RemoteServiceCallbackId callbackId, SerializableDefinitionItem definition, CancellationToken cancellationToken) => GetCallback(callbackId).OnDefinitionFoundAsync(definition, cancellationToken); - public ValueTask OnReferenceFoundAsync(RemoteServiceCallbackId callbackId, SerializableSourceReferenceItem reference, CancellationToken cancellationToken) - => GetCallback(callbackId).OnReferenceFoundAsync(reference, cancellationToken); + public ValueTask OnReferencesFoundAsync(RemoteServiceCallbackId callbackId, ImmutableArray references, CancellationToken cancellationToken) + => GetCallback(callbackId).OnReferencesFoundAsync(references, cancellationToken); public ValueTask ReportMessageAsync(RemoteServiceCallbackId callbackId, string message, CancellationToken cancellationToken) => GetCallback(callbackId).ReportMessageAsync(message, cancellationToken); @@ -131,12 +129,15 @@ public async ValueTask OnDefinitionFoundAsync(SerializableDefinitionItem definit } } - public async ValueTask OnReferenceFoundAsync(SerializableSourceReferenceItem reference, CancellationToken cancellationToken) + public async ValueTask OnReferencesFoundAsync(ImmutableArray references, CancellationToken cancellationToken) { try { - var rehydrated = await reference.RehydrateAsync(_solution, GetDefinition(reference.DefinitionId), cancellationToken).ConfigureAwait(false); - await _context.OnReferenceFoundAsync(rehydrated, cancellationToken).ConfigureAwait(false); + var result = new FixedSizeArrayBuilder(references.Length); + foreach (var reference in references) + result.Add(await reference.RehydrateAsync(_solution, GetDefinition(reference.DefinitionId), cancellationToken).ConfigureAwait(false)); + + await _context.OnReferencesFoundAsync(result.MoveToImmutable(), cancellationToken).ConfigureAwait(false); } catch (Exception ex) when (FatalError.ReportAndPropagateUnlessCanceled(ex, cancellationToken)) { diff --git a/src/Features/Core/Portable/ValueTracking/ValueTracker.FindReferencesProgress.cs b/src/Features/Core/Portable/ValueTracking/ValueTracker.FindReferencesProgress.cs index c730ff32c5eb8..39e605cd9bb7d 100644 --- a/src/Features/Core/Portable/ValueTracking/ValueTracker.FindReferencesProgress.cs +++ b/src/Features/Core/Portable/ValueTracking/ValueTracker.FindReferencesProgress.cs @@ -2,7 +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.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.FindSymbols; @@ -30,10 +30,10 @@ private class FindReferencesProgress(OperationCollector valueTrackingProgressCol public ValueTask OnDefinitionFoundAsync(SymbolGroup symbolGroup, CancellationToken _) => new(); public async ValueTask OnReferencesFoundAsync( - ImmutableArray<(SymbolGroup group, ISymbol symbol, ReferenceLocation location)> references, + IAsyncEnumerable<(SymbolGroup group, ISymbol symbol, ReferenceLocation location)> references, CancellationToken cancellationToken) { - foreach (var (_, symbol, location) in references) + await foreach (var (_, symbol, location) in references) await OnReferenceFoundAsync(symbol, location, cancellationToken).ConfigureAwait(false); } diff --git a/src/Features/LanguageServer/Protocol/Features/FindUsages/SimpleFindUsagesContext.cs b/src/Features/LanguageServer/Protocol/Features/FindUsages/SimpleFindUsagesContext.cs index 955b3b7eb142d..503e25b01efa6 100644 --- a/src/Features/LanguageServer/Protocol/Features/FindUsages/SimpleFindUsagesContext.cs +++ b/src/Features/LanguageServer/Protocol/Features/FindUsages/SimpleFindUsagesContext.cs @@ -65,11 +65,11 @@ public override ValueTask OnDefinitionFoundAsync(DefinitionItem definition, Canc return default; } - public override ValueTask OnReferenceFoundAsync(SourceReferenceItem reference, CancellationToken cancellationToken) + public override ValueTask OnReferencesFoundAsync(ImmutableArray references, CancellationToken cancellationToken) { lock (_gate) { - _referenceItems.Add(reference); + _referenceItems.AddRange(references); } return default; diff --git a/src/Features/LanguageServer/Protocol/Handler/References/FindUsagesLSPContext.cs b/src/Features/LanguageServer/Protocol/Handler/References/FindUsagesLSPContext.cs index e50932c57f11f..aefb97d12bed0 100644 --- a/src/Features/LanguageServer/Protocol/Handler/References/FindUsagesLSPContext.cs +++ b/src/Features/LanguageServer/Protocol/Handler/References/FindUsagesLSPContext.cs @@ -129,41 +129,44 @@ public override async ValueTask OnDefinitionFoundAsync(DefinitionItem definition } } - public override async ValueTask OnReferenceFoundAsync(SourceReferenceItem reference, CancellationToken cancellationToken) + public override async ValueTask OnReferencesFoundAsync(ImmutableArray references, CancellationToken cancellationToken) { using (await _semaphore.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) { - // Each reference should be associated with a definition. If this somehow isn't the - // case, we bail out early. - if (!_definitionToId.TryGetValue(reference.Definition, out var definitionId)) - return; + foreach (var reference in references) + { + // Each reference should be associated with a definition. If this somehow isn't the + // case, we bail out early. + if (!_definitionToId.TryGetValue(reference.Definition, out var definitionId)) + return; - var documentSpan = reference.SourceSpan; - var document = documentSpan.Document; + var documentSpan = reference.SourceSpan; + var document = documentSpan.Document; - // If this is reference to the same physical location we've already reported, just - // filter this out. it will clutter the UI to show the same places. - if (!_referenceLocations.Add((document.FilePath, reference.SourceSpan.SourceSpan))) - return; + // If this is reference to the same physical location we've already reported, just + // filter this out. it will clutter the UI to show the same places. + if (!_referenceLocations.Add((document.FilePath, reference.SourceSpan.SourceSpan))) + return; - // If the definition hasn't been reported yet, add it to our list of references to report. - if (_definitionsWithoutReference.TryGetValue(definitionId, out var definition)) - { - _workQueue.AddWork(definition); - _definitionsWithoutReference.Remove(definitionId); - } + // If the definition hasn't been reported yet, add it to our list of references to report. + if (_definitionsWithoutReference.TryGetValue(definitionId, out var definition)) + { + _workQueue.AddWork(definition); + _definitionsWithoutReference.Remove(definitionId); + } - // give this reference a fresh id. - _id++; + // give this reference a fresh id. + _id++; - // Creating a new VSReferenceItem for the reference - var referenceItem = await GenerateVSReferenceItemAsync( - definitionId, _id, reference.SourceSpan, - reference.AdditionalProperties, definitionText: null, - definitionGlyph: Glyph.None, reference.SymbolUsageInfo, reference.IsWrittenTo, cancellationToken).ConfigureAwait(false); + // Creating a new VSReferenceItem for the reference + var referenceItem = await GenerateVSReferenceItemAsync( + definitionId, _id, reference.SourceSpan, + reference.AdditionalProperties, definitionText: null, + definitionGlyph: Glyph.None, reference.SymbolUsageInfo, reference.IsWrittenTo, cancellationToken).ConfigureAwait(false); - if (referenceItem != null) - _workQueue.AddWork(referenceItem.Value); + if (referenceItem != null) + _workQueue.AddWork(referenceItem.Value); + } } } diff --git a/src/Tools/ExternalAccess/FSharp/Internal/Editor/FindUsages/FSharpFindUsagesContext.cs b/src/Tools/ExternalAccess/FSharp/Internal/Editor/FindUsages/FSharpFindUsagesContext.cs index 679890068ab2d..b5c4702ba38fe 100644 --- a/src/Tools/ExternalAccess/FSharp/Internal/Editor/FindUsages/FSharpFindUsagesContext.cs +++ b/src/Tools/ExternalAccess/FSharp/Internal/Editor/FindUsages/FSharpFindUsagesContext.cs @@ -29,7 +29,7 @@ public Task OnDefinitionFoundAsync(FSharp.FindUsages.FSharpDefinitionItem defini public Task OnReferenceFoundAsync(FSharp.FindUsages.FSharpSourceReferenceItem reference) { - return _context.OnReferenceFoundAsync(reference.RoslynSourceReferenceItem, _cancellationToken).AsTask(); + return _context.OnReferencesFoundAsync([reference.RoslynSourceReferenceItem], _cancellationToken).AsTask(); } public Task ReportMessageAsync(string message) diff --git a/src/VisualStudio/Core/Def/FindReferences/Contexts/AbstractTableDataSourceFindUsagesContext.cs b/src/VisualStudio/Core/Def/FindReferences/Contexts/AbstractTableDataSourceFindUsagesContext.cs index 79fa5ae84a1e3..3e4fef7dbb2aa 100644 --- a/src/VisualStudio/Core/Def/FindReferences/Contexts/AbstractTableDataSourceFindUsagesContext.cs +++ b/src/VisualStudio/Core/Def/FindReferences/Contexts/AbstractTableDataSourceFindUsagesContext.cs @@ -494,11 +494,12 @@ protected async Task AddDocumentSpanEntriesAsync( return (excerptResult, AbstractDocumentSpanEntry.GetLineContainingPosition(sourceText, sourceSpan.Start)); } - public sealed override async ValueTask OnReferenceFoundAsync(SourceReferenceItem reference, CancellationToken cancellationToken) + public sealed override async ValueTask OnReferencesFoundAsync(ImmutableArray references, CancellationToken cancellationToken) { try { - await OnReferenceFoundWorkerAsync(reference, cancellationToken).ConfigureAwait(false); + foreach (var reference in references) + await OnReferenceFoundWorkerAsync(reference, cancellationToken).ConfigureAwait(false); } catch (Exception ex) when (FatalError.ReportAndPropagateUnlessCanceled(ex, cancellationToken)) { diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs index 1c3de836deb31..9b1910b8bc99d 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs @@ -7,11 +7,14 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.FindSymbols.Finders; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Utilities; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols; @@ -110,19 +113,43 @@ async ValueTask PerformSearchInDocumentWorkerAsync(ISymbol symbol, FindReference async ValueTask DirectSymbolSearchAsync(ISymbol symbol, FindReferencesDocumentState state) { - using var _ = ArrayBuilder.GetInstance(out var referencesForFinder); - foreach (var finder in _finders) - { - finder.FindReferencesInDocument( - symbol, state, StandardCallbacks.AddToArrayBuilder, referencesForFinder, _options, cancellationToken); - } + await ProducerConsumer.RunAsync( + ProducerConsumerOptions.SingleReaderWriterOptions, + static (callback, args, cancellationToken) => + { + // We don't bother calling into the finders in parallel as there's only ever one that applies for a + // particular symbol kind. All the rest bail out immediately after a quick type-check. So there's + // no benefit in forking out to have only one of them end up actually doing work. + foreach (var finder in args.@this._finders) + { + finder.FindReferencesInDocument( + args.symbol, args.state, + static (finderLocation, callback) => callback(finderLocation), + callback, args.@this._options, cancellationToken); + } - if (referencesForFinder.Count > 0) - { - var group = await ReportGroupAsync(symbol, cancellationToken).ConfigureAwait(false); - var references = referencesForFinder.SelectAsArray(r => (group, symbol, r.Location)); + return Task.CompletedTask; + }, + consumeItems: static async (values, args, cancellationToken) => + { + await args.@this._progress.OnReferencesFoundAsync( + ReadAllAsync(args.@this, values, args.symbol, cancellationToken), cancellationToken).ConfigureAwait(false); + }, + args: (@this: this, symbol, state), + cancellationToken).ConfigureAwait(false); + } - await _progress.OnReferencesFoundAsync(references, cancellationToken).ConfigureAwait(false); + static async IAsyncEnumerable<(SymbolGroup group, ISymbol symbol, ReferenceLocation location)> ReadAllAsync( + FindReferencesSearchEngine @this, IAsyncEnumerable locations, ISymbol symbol, [EnumeratorCancellation] CancellationToken cancellationToken) + { + SymbolGroup? group = null; + + // Transform the individual finder-location objects to "group/symbol/location" tuples. + await foreach (var location in locations) + { + // The first time we see the location for a symbol, report its group. + group ??= await @this.ReportGroupAsync(symbol, cancellationToken).ConfigureAwait(false); + yield return (group, symbol, location.Location); } } @@ -144,7 +171,8 @@ async ValueTask InheritanceSymbolSearchAsync(ISymbol symbol, FindReferencesDocum var candidateGroup = await ReportGroupAsync(candidate, cancellationToken).ConfigureAwait(false); var location = AbstractReferenceFinder.CreateReferenceLocation(state, token, candidateReason, cancellationToken); - await _progress.OnReferencesFoundAsync([(candidateGroup, candidate, location)], cancellationToken).ConfigureAwait(false); + await _progress.OnReferencesFoundAsync( + IAsyncEnumerableExtensions.AsAsyncEnumerable([(candidateGroup, candidate, location)]), cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/NoOpStreamingFindReferencesProgress.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/NoOpStreamingFindReferencesProgress.cs index b10cd55dfc0e9..686b33a1b3cdb 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/NoOpStreamingFindReferencesProgress.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/NoOpStreamingFindReferencesProgress.cs @@ -2,7 +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.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Shared.Utilities; @@ -27,7 +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 OnReferencesFoundAsync(ImmutableArray<(SymbolGroup group, ISymbol symbol, ReferenceLocation location)> references, CancellationToken cancellationToken) => default; + public ValueTask OnReferencesFoundAsync(IAsyncEnumerable<(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 32a4ce2080823..a95fdfdb3f0b6 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/StreamingFindReferencesProgress.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/StreamingFindReferencesProgress.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Immutable; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.ErrorReporting; @@ -16,7 +16,7 @@ namespace Microsoft.CodeAnalysis.FindSymbols; /// Wraps an into an /// so it can be used from the new streaming find references APIs. /// -internal class StreamingFindReferencesProgressAdapter : IStreamingFindReferencesProgress +internal sealed class StreamingFindReferencesProgressAdapter : IStreamingFindReferencesProgress { private readonly IFindReferencesProgress _progress; @@ -53,12 +53,10 @@ public ValueTask OnDefinitionFoundAsync(SymbolGroup group, CancellationToken can } } - public ValueTask OnReferencesFoundAsync(ImmutableArray<(SymbolGroup group, ISymbol symbol, ReferenceLocation location)> references, CancellationToken cancellationToken) + public async ValueTask OnReferencesFoundAsync(IAsyncEnumerable<(SymbolGroup group, ISymbol symbol, ReferenceLocation location)> references, CancellationToken cancellationToken) { - foreach (var (_, symbol, location) in references) + await foreach (var (_, symbol, location) in references) _progress.OnReferenceFound(symbol, location); - - return default; } public ValueTask OnStartedAsync(CancellationToken cancellationToken) diff --git a/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs b/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs index 5a84f3f20655e..7aa15611d77ac 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs @@ -3,8 +3,9 @@ // See the LICENSE file in the project root for more information. using System; -using System.Diagnostics; +using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -73,7 +74,7 @@ internal interface IStreamingFindReferencesProgress ValueTask OnCompletedAsync(CancellationToken cancellationToken); ValueTask OnDefinitionFoundAsync(SymbolGroup group, CancellationToken cancellationToken); - ValueTask OnReferencesFoundAsync(ImmutableArray<(SymbolGroup group, ISymbol symbol, ReferenceLocation location)> references, CancellationToken cancellationToken); + ValueTask OnReferencesFoundAsync(IAsyncEnumerable<(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 912ae358bea4b..fb8c31d3eba9c 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/StreamingProgressCollector.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/StreamingProgressCollector.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; @@ -69,15 +70,19 @@ public ValueTask OnDefinitionFoundAsync(SymbolGroup group, CancellationToken can } } - public ValueTask OnReferencesFoundAsync( - ImmutableArray<(SymbolGroup group, ISymbol symbol, ReferenceLocation location)> references, CancellationToken cancellationToken) + public async ValueTask OnReferencesFoundAsync( + IAsyncEnumerable<(SymbolGroup group, ISymbol symbol, ReferenceLocation location)> references, CancellationToken cancellationToken) { - lock (_gate) + // Reading the references from the stream will cause them to be processed. We'll make a copy here so we can + // defer to the underlying progress object with the same data. + using var _ = ArrayBuilder<(SymbolGroup group, ISymbol symbol, ReferenceLocation location)>.GetInstance(out var copy); + await foreach (var tuple in references) { - foreach (var (_, definition, location) in references) - _symbolToLocations[definition].Add(location); + copy.Add(tuple); + lock (_gate) + _symbolToLocations[tuple.symbol].Add(tuple.location); } - return underlyingProgress.OnReferencesFoundAsync(references, cancellationToken); + await underlyingProgress.OnReferencesFoundAsync(copy.AsAsyncEnumerable(), cancellationToken).ConfigureAwait(false); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs index 2830187083028..8d56adc72ba60 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs @@ -2,8 +2,10 @@ // 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 System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.PooledObjects; @@ -64,38 +66,40 @@ public async ValueTask OnDefinitionFoundAsync(SerializableSymbolGroup dehydrated await progress.OnDefinitionFoundAsync(symbolGroup, cancellationToken).ConfigureAwait(false); } - public async ValueTask OnReferencesFoundAsync( + public ValueTask OnReferencesFoundAsync( ImmutableArray<(SerializableSymbolGroup serializableSymbolGroup, SerializableSymbolAndProjectId serializableSymbol, SerializableReferenceLocation reference)> references, CancellationToken cancellationToken) { - using var _ = ArrayBuilder<(SymbolGroup group, ISymbol symbol, ReferenceLocation location)>.GetInstance(references.Length, out var rehydrated); - foreach (var (serializableSymbolGroup, serializableSymbol, reference) in references) + return progress.OnReferencesFoundAsync(ConvertAsync(cancellationToken), cancellationToken); + + async IAsyncEnumerable<(SymbolGroup group, ISymbol symbol, ReferenceLocation location)> ConvertAsync([EnumeratorCancellation] CancellationToken cancellationToken) { - SymbolGroup? symbolGroup; - ISymbol? symbol; - lock (_gate) + 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) { - continue; + // 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); - rehydrated.Add((symbolGroup, symbol, referenceLocation)); - } + var referenceLocation = await reference.RehydrateAsync( + solution, cancellationToken).ConfigureAwait(false); - if (rehydrated.Count > 0) - await progress.OnReferencesFoundAsync(rehydrated.ToImmutableAndClear(), cancellationToken).ConfigureAwait(false); + yield return (symbolGroup, symbol, referenceLocation); + } + } } } } diff --git a/src/Workspaces/Remote/ServiceHub/Services/FindUsages/RemoteFindUsagesService.cs b/src/Workspaces/Remote/ServiceHub/Services/FindUsages/RemoteFindUsagesService.cs index c2a04d3b53ff6..8ef7c042a6975 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/FindUsages/RemoteFindUsagesService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/FindUsages/RemoteFindUsagesService.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; +using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Classification; @@ -127,11 +128,18 @@ private int GetOrAddDefinitionItemId(DefinitionItem item) } } - public ValueTask OnReferenceFoundAsync(SourceReferenceItem reference, CancellationToken cancellationToken) + public ValueTask OnReferencesFoundAsync(ImmutableArray references, CancellationToken cancellationToken) { - var definitionItem = GetOrAddDefinitionItemId(reference.Definition); - var dehydratedReference = SerializableSourceReferenceItem.Dehydrate(definitionItem, reference); - return _callback.InvokeAsync((callback, cancellationToken) => callback.OnReferenceFoundAsync(_callbackId, dehydratedReference, cancellationToken), cancellationToken); + var dehydrated = new FixedSizeArrayBuilder(references.Length); + foreach (var reference in references) + { + var dehydratedReference = SerializableSourceReferenceItem.Dehydrate( + GetOrAddDefinitionItemId(reference.Definition), reference); + dehydrated.Add(dehydratedReference); + } + + var dehydratedReferences = dehydrated.MoveToImmutable(); + return _callback.InvokeAsync((callback, cancellationToken) => callback.OnReferencesFoundAsync(_callbackId, dehydratedReferences, cancellationToken), cancellationToken); } #endregion diff --git a/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs b/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs index 83aef0424bc2b..b2062c3834237 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; @@ -225,18 +226,23 @@ public ValueTask OnDefinitionFoundAsync(SymbolGroup group, CancellationToken can (callback, cancellationToken) => callback.OnDefinitionFoundAsync(_callbackId, dehydratedGroup, cancellationToken), cancellationToken); } - public ValueTask OnReferencesFoundAsync( - ImmutableArray<(SymbolGroup group, ISymbol symbol, ReferenceLocation location)> references, + public async ValueTask OnReferencesFoundAsync( + IAsyncEnumerable<(SymbolGroup group, ISymbol symbol, ReferenceLocation location)> references, CancellationToken cancellationToken) { - var dehydrated = references.SelectAsArray(t => - (SerializableSymbolGroup.Dehydrate(_solution, t.group, cancellationToken), - SerializableSymbolAndProjectId.Dehydrate(_solution, t.symbol, cancellationToken), - SerializableReferenceLocation.Dehydrate(t.location, cancellationToken))); + using var _ = ArrayBuilder<(SerializableSymbolGroup, SerializableSymbolAndProjectId, SerializableReferenceLocation)>.GetInstance(out var result); + await foreach (var (group, symbol, location) in references) + { + result.Add(( + SerializableSymbolGroup.Dehydrate(_solution, group, cancellationToken), + SerializableSymbolAndProjectId.Dehydrate(_solution, symbol, cancellationToken), + SerializableReferenceLocation.Dehydrate(location, cancellationToken))); + } - return _callback.InvokeAsync( + var dehydrated = result.ToImmutableAndClear(); + await _callback.InvokeAsync( (callback, cancellationToken) => callback.OnReferencesFoundAsync( - _callbackId, dehydrated, cancellationToken), cancellationToken); + _callbackId, dehydrated, cancellationToken), cancellationToken).ConfigureAwait(false); } public ValueTask AddItemsAsync(int count, CancellationToken cancellationToken)