diff --git a/src/Features/Core/Portable/ChangeSignature/DelegateInvokeMethodReferenceFinder.cs b/src/Features/Core/Portable/ChangeSignature/DelegateInvokeMethodReferenceFinder.cs index b5db419837dab..fe4c1faececc1 100644 --- a/src/Features/Core/Portable/ChangeSignature/DelegateInvokeMethodReferenceFinder.cs +++ b/src/Features/Core/Portable/ChangeSignature/DelegateInvokeMethodReferenceFinder.cs @@ -33,19 +33,17 @@ internal class DelegateInvokeMethodReferenceFinder : AbstractReferenceFinder symbol.MethodKind == MethodKind.DelegateInvoke; - protected override async Task> DetermineCascadedSymbolsAsync( + protected override async Task> DetermineCascadedSymbolsAsync( IMethodSymbol symbol, Solution solution, - IImmutableSet projects, FindReferencesSearchOptions options, - FindReferencesCascadeDirection cascadeDirection, CancellationToken cancellationToken) { - using var _ = ArrayBuilder<(ISymbol symbol, FindReferencesCascadeDirection cascadeDirection)>.GetInstance(out var result); + using var _ = ArrayBuilder.GetInstance(out var result); var beginInvoke = symbol.ContainingType.GetMembers(WellKnownMemberNames.DelegateBeginInvokeName).FirstOrDefault(); if (beginInvoke != null) - result.Add((beginInvoke, cascadeDirection)); + result.Add(beginInvoke); // All method group references foreach (var project in solution.Projects) @@ -55,7 +53,7 @@ protected override bool CanFind(IMethodSymbol symbol) var changeSignatureService = document.GetLanguageService(); var cascaded = await changeSignatureService.DetermineCascadedSymbolsFromDelegateInvokeAsync( symbol, document, cancellationToken).ConfigureAwait(false); - result.AddRange(cascaded.SelectAsArray(s => (s, cascadeDirection))); + result.AddRange(cascaded); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs index da2756f64d686..73610c56a6648 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs @@ -83,20 +83,33 @@ private static async Task> DescendInheritanceTr // are passed in. There is no need to check D as there's no way it could // contribute an intermediate type that affects A or C. We only need to check // A, B and C + // + // An exception to the above rule is if we're just searching a single project. + // in that case there can be no intermediary projects that could add types. + // So we can just limit ourselves to that single project. // First find all the projects that could potentially reference this type. - var projectsThatCouldReferenceType = await GetProjectsThatCouldReferenceTypeAsync( - type, solution, searchInMetadata, cancellationToken).ConfigureAwait(false); + List orderedProjectsToExamine; - // Now, based on the list of projects that could actually reference the type, - // and the list of projects the caller wants to search, find the actual list of - // projects we need to search through. - // - // This list of projects is properly topologically ordered. Because of this we - // can just process them in order from first to last because we know no project - // in this list could affect a prior project. - var orderedProjectsToExamine = GetOrderedProjectsToExamine( - solution, projects, projectsThatCouldReferenceType); + if (projects.Count == 1) + { + orderedProjectsToExamine = projects.ToList(); + } + else + { + var projectsThatCouldReferenceType = await GetProjectsThatCouldReferenceTypeAsync( + type, solution, searchInMetadata, cancellationToken).ConfigureAwait(false); + + // Now, based on the list of projects that could actually reference the type, + // and the list of projects the caller wants to search, find the actual list of + // projects we need to search through. + // + // This list of projects is properly topologically ordered. Because of this we + // can just process them in order from first to last because we know no project + // in this list could affect a prior project. + orderedProjectsToExamine = GetOrderedProjectsToExamine( + solution, projects, projectsThatCouldReferenceType); + } // The final set of results we'll be returning. using var _1 = GetSymbolSet(out var result); @@ -278,7 +291,7 @@ private static IEnumerable GetProjectsThatCouldReferenceType( { // Get all the projects that depend on 'project' as well as 'project' itself. return dependencyGraph.GetProjectsThatTransitivelyDependOnThisProject(project.Id) - .Concat(project.Id); + .Concat(project.Id); } private static List GetOrderedProjectsToExamine( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesCascadeDirection.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesCascadeDirection.cs deleted file mode 100644 index 3cc61554b7206..0000000000000 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesCascadeDirection.cs +++ /dev/null @@ -1,33 +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; - -namespace Microsoft.CodeAnalysis.FindSymbols -{ - [Flags] - internal enum FindReferencesCascadeDirection - { - /// - /// Cascade up the inheritance hierarchy. - /// - Up = 1, - - /// - /// Cascade down the inheritance hierarchy. - /// - Down = 2, - - /// - /// Cascade in both directions. - /// - UpAndDown = Up | Down, - } - - internal static class FindReferencesCascadeDirectionExtensions - { - public static bool HasFlag(this FindReferencesCascadeDirection value, FindReferencesCascadeDirection flag) - => (value & flag) == flag; - } -} diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.BidirectionalSymbolSet.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.BidirectionalSymbolSet.cs new file mode 100644 index 0000000000000..32c55213ad128 --- /dev/null +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.BidirectionalSymbolSet.cs @@ -0,0 +1,62 @@ +// 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.Generic; +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.FindSymbols +{ + internal partial class FindReferencesSearchEngine + { + /// + /// Symbol set used when is . This symbol set will cascade up *and* down the inheritance hierarchy for all symbols we + /// are searching for. This is the symbol set used for features like 'Rename', where all cascaded symbols must + /// be updated in order to keep the code compiling. + /// + private sealed class BidirectionalSymbolSet : SymbolSet + { + /// + /// When we're cascading in both direction, we can just keep all symbols in a single set. We'll always be + /// examining all of them to go in both up and down directions in every project we process. Any time we + /// add a new symbol to it we'll continue to cascade in both directions looking for more. + /// + private readonly HashSet _allSymbols = new(); + + public BidirectionalSymbolSet(FindReferencesSearchEngine engine, HashSet initialSymbols, HashSet upSymbols) + : base(engine) + { + _allSymbols.AddRange(initialSymbols); + _allSymbols.AddRange(upSymbols); + } + + public override ImmutableArray GetAllSymbols() + => _allSymbols.ToImmutableArray(); + + public override async Task InheritanceCascadeAsync(Project project, CancellationToken cancellationToken) + { + // Start searching using the current set of symbols built up so far. + var workQueue = new Stack(); + workQueue.Push(_allSymbols); + + var projects = ImmutableHashSet.Create(project); + + while (workQueue.Count > 0) + { + var current = workQueue.Pop(); + + // For each symbol we're examining try to walk both up and down from it to see if we discover any + // new symbols in this project. As long as we keep finding symbols, we'll keep searching from them + // in both directions. + await AddDownSymbolsAsync(this.Engine, current, _allSymbols, workQueue, projects, cancellationToken).ConfigureAwait(false); + await AddUpSymbolsAsync(this.Engine, current, _allSymbols, workQueue, projects, cancellationToken).ConfigureAwait(false); + } + } + } + } +} diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.NonCascadingSymbolSet.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.NonCascadingSymbolSet.cs new file mode 100644 index 0000000000000..ede3c3df2bb7b --- /dev/null +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.NonCascadingSymbolSet.cs @@ -0,0 +1,37 @@ +// 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.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.FindSymbols +{ + internal partial class FindReferencesSearchEngine + { + /// + /// A symbol set used when the find refs caller does not want cascading. This is a trivial impl that basically + /// just wraps the initial symbol provided and doesn't need to do anything beyond that. + /// + private sealed class NonCascadingSymbolSet : SymbolSet + { + private readonly ImmutableArray _symbols; + + public NonCascadingSymbolSet(FindReferencesSearchEngine engine, ISymbol searchSymbol) : base(engine) + { + _symbols = ImmutableArray.Create(searchSymbol); + } + + public override ImmutableArray GetAllSymbols() + => _symbols; + + public override Task InheritanceCascadeAsync(Project project, CancellationToken cancellationToken) + { + // Nothing to do here. We're in a non-cascading scenario, so even as we encounter a new project we + // don't have to figure out what new symbols may be found. + return Task.CompletedTask; + } + } + } +} diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.SymbolSet.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.SymbolSet.cs new file mode 100644 index 0000000000000..4379e9d7c19bf --- /dev/null +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.SymbolSet.cs @@ -0,0 +1,270 @@ +// 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.Generic; +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.FindSymbols.Finders; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.FindSymbols +{ + internal partial class FindReferencesSearchEngine + { + /// + /// Represents the set of symbols that the engine is searching for. While the find-refs engine is passed an + /// initial symbol to find results for, the engine will often have to 'cascade' that symbol to many more symbols + /// that clients will also need. This includes: + /// + /// Cascading to all linked symbols for the requested symbol. This ensures a unified set of results for a + /// particular symbol, regardless of what project context it was originally found in. + /// Symbol specific cascading. For example, when searching for a named type, references to that named + /// type will be found through its constructors. + /// Cascading up and down the inheritance hierarchy for members (e.g. methods, properties, events). This + /// is controllable through the + /// option. + /// + /// + private abstract class SymbolSet + { + protected readonly FindReferencesSearchEngine Engine; + + protected SymbolSet(FindReferencesSearchEngine engine) + { + Engine = engine; + } + + protected Solution Solution => Engine._solution; + + /// + /// Get a copy of all the symbols in the set. Cannot be called concurrently with + /// + public abstract ImmutableArray GetAllSymbols(); + + /// + /// Update the set of symbols in this set with any appropriate symbols in the inheritance hierarchy brought + /// in within . For example, given a project 'A' with interface interface IGoo + /// { void Goo(); }, and a project 'B' with class class Goo : IGoo { public void Goo() { } }, + /// then initially the symbol set will only contain IGoo.Goo. However, when project 'B' is processed, this + /// will add Goo.Goo is added to the set as well so that references to it can be found. + /// + /// + /// This method is non threadsafe as it mutates the symbol set instance. As such, it should only be + /// called serially. should not be called concurrently with this. + /// + public abstract Task InheritanceCascadeAsync(Project project, CancellationToken cancellationToken); + + private static bool InvolvesInheritance(ISymbol symbol) + => symbol.Kind is SymbolKind.Method or SymbolKind.Property or SymbolKind.Event; + + public static async Task CreateAsync( + FindReferencesSearchEngine engine, ISymbol symbol, CancellationToken cancellationToken) + { + var solution = engine._solution; + var options = engine._options; + + // Start by mapping the initial symbol to the appropriate source symbol in originating project if possible. + var searchSymbol = await MapToAppropriateSymbolAsync(solution, symbol, cancellationToken).ConfigureAwait(false); + + // If the caller doesn't want any cascading then just return an appropriate set that will just point at + // only the search symbol and won't cascade to any related symbols, linked symbols, or inheritance + // symbols. + if (!options.Cascade) + return new NonCascadingSymbolSet(engine, searchSymbol); + + // Keep track of the initial symbol group corresponding to search-symbol. Any references to this group + // will always be reported. + // + // Depending on what type of search we're doing, return an appropriate set that will have those + // inheritance cascading semantics. + var initialSymbols = await DetermineInitialSearchSymbolsAsync(engine, searchSymbol, cancellationToken).ConfigureAwait(false); + + // Walk and find all the symbols above the starting symbol set. + var upSymbols = await DetermineInitialUpSymbolsAsync(engine, initialSymbols, cancellationToken).ConfigureAwait(false); + + return options.UnidirectionalHierarchyCascade + ? new UnidirectionalSymbolSet(engine, initialSymbols, upSymbols) + : new BidirectionalSymbolSet(engine, initialSymbols, upSymbols); + } + + private static async Task MapToAppropriateSymbolAsync( + Solution solution, ISymbol symbol, CancellationToken cancellationToken) + { + // Never search for an alias. Always search for it's target. Note: if the caller was + // actually searching for an alias, they can always get that information out in the end + // by checking the ReferenceLocations that are returned. + var searchSymbol = symbol; + + if (searchSymbol is IAliasSymbol aliasSymbol) + searchSymbol = aliasSymbol.Target; + + searchSymbol = searchSymbol.GetOriginalUnreducedDefinition(); + + // If they're searching for a delegate constructor, then just search for the delegate + // itself. They're practically interchangeable for consumers. + if (searchSymbol.IsConstructor() && searchSymbol.ContainingType.TypeKind == TypeKind.Delegate) + searchSymbol = symbol.ContainingType; + + Contract.ThrowIfNull(searchSymbol); + + // Attempt to map this symbol back to a source symbol if possible as we always prefer the original + // source definition as the 'truth' of a symbol versus seeing it projected into dependent cross language + // projects as a metadata symbol. If there is no source symbol, then continue to just use the metadata + // symbol as the one to be looking for. + var sourceSymbol = await SymbolFinder.FindSourceDefinitionAsync(searchSymbol, solution, cancellationToken).ConfigureAwait(false); + return sourceSymbol ?? searchSymbol; + } + + /// + /// Determines the initial set of symbols that we should actually be finding references for given a request + /// to find refs to . This will include any symbols that a specific cascades to, as well as all the linked symbols to those across any + /// multi-targetting/shared-project documents. This will not include symbols up or down the inheritance + /// hierarchy. + /// + private static async Task> DetermineInitialSearchSymbolsAsync( + FindReferencesSearchEngine engine, ISymbol symbol, CancellationToken cancellationToken) + { + var result = new HashSet(); + var workQueue = new Stack(); + + // Start with the initial symbol we're searching for. + workQueue.Push(symbol); + + // As long as there's work in the queue, keep going. + while (workQueue.Count > 0) + { + var currentSymbol = workQueue.Pop(); + await AddCascadedAndLinkedSymbolsToAsync(engine, currentSymbol, result, workQueue, cancellationToken).ConfigureAwait(false); + } + + return result; + } + + private static async Task> DetermineInitialUpSymbolsAsync( + FindReferencesSearchEngine engine, HashSet initialSymbols, CancellationToken cancellationToken) + { + var upSymbols = new HashSet(); + var workQueue = new Stack(); + workQueue.Push(initialSymbols); + + var solution = engine._solution; + var allProjects = solution.Projects.ToImmutableHashSet(); + while (workQueue.Count > 0) + { + var currentSymbol = workQueue.Pop(); + await AddUpSymbolsAsync(engine, currentSymbol, upSymbols, workQueue, allProjects, cancellationToken).ConfigureAwait(false); + } + + return upSymbols; + } + + protected static async Task AddCascadedAndLinkedSymbolsToAsync( + FindReferencesSearchEngine engine, ImmutableArray symbols, HashSet seenSymbols, Stack workQueue, CancellationToken cancellationToken) + { + foreach (var symbol in symbols) + await AddCascadedAndLinkedSymbolsToAsync(engine, symbol, seenSymbols, workQueue, cancellationToken).ConfigureAwait(false); + } + + protected static async Task AddCascadedAndLinkedSymbolsToAsync( + FindReferencesSearchEngine engine, ISymbol symbol, HashSet seenSymbols, Stack workQueue, CancellationToken cancellationToken) + { + var solution = engine._solution; + symbol = await MapAndAddLinkedSymbolsAsync(symbol).ConfigureAwait(false); + + foreach (var finder in engine._finders) + { + var cascaded = await finder.DetermineCascadedSymbolsAsync(symbol, solution, engine._options, cancellationToken).ConfigureAwait(false); + foreach (var cascade in cascaded) + await MapAndAddLinkedSymbolsAsync(cascade).ConfigureAwait(false); + } + + return; + + async Task MapAndAddLinkedSymbolsAsync(ISymbol symbol) + { + symbol = await MapToAppropriateSymbolAsync(solution, symbol, cancellationToken).ConfigureAwait(false); + foreach (var linked in await SymbolFinder.FindLinkedSymbolsAsync(symbol, solution, cancellationToken).ConfigureAwait(false)) + { + if (seenSymbols.Add(linked)) + workQueue.Push(linked); + } + + return symbol; + } + } + + /// + /// Finds all the symbols 'down' the inheritance hierarchy of in the given + /// project. The symbols found are added to . If did not + /// contain that symbol, then it is also added to to allow fixed point + /// algorithms to continue. + /// + /// will always be a single project. We just pass this in as a set to + /// avoid allocating a fresh set every time this calls into FindMemberImplementationsArrayAsync. + /// + protected static async Task AddDownSymbolsAsync( + FindReferencesSearchEngine engine, ISymbol symbol, + HashSet seenSymbols, Stack workQueue, + ImmutableHashSet projects, CancellationToken cancellationToken) + { + Contract.ThrowIfFalse(projects.Count == 1, "Only a single project should be passed in"); + + // Don't bother on symbols that aren't even involved in inheritance computations. + if (!InvolvesInheritance(symbol)) + return; + + var solution = engine._solution; + if (symbol.IsImplementableMember()) + { + var implementations = await SymbolFinder.FindMemberImplementationsArrayAsync( + symbol, solution, projects, cancellationToken).ConfigureAwait(false); + + await AddCascadedAndLinkedSymbolsToAsync(engine, implementations, seenSymbols, workQueue, cancellationToken).ConfigureAwait(false); + } + else + { + var overrrides = await SymbolFinder.FindOverridesArrayAsync( + symbol, solution, projects, cancellationToken).ConfigureAwait(false); + + await AddCascadedAndLinkedSymbolsToAsync(engine, overrrides, seenSymbols, workQueue, cancellationToken).ConfigureAwait(false); + } + } + + /// + /// Finds all the symbols 'up' the inheritance hierarchy of in the solution. The + /// symbols found are added to . If did not contain that symbol, + /// then it is also added to to allow fixed point algorithms to continue. + /// + protected static async Task AddUpSymbolsAsync( + FindReferencesSearchEngine engine, ISymbol symbol, + HashSet seenSymbols, Stack workQueue, + ImmutableHashSet projects, CancellationToken cancellationToken) + { + if (!InvolvesInheritance(symbol)) + return; + + var solution = engine._solution; + var originatingProject = solution.GetOriginatingProject(symbol); + if (originatingProject != null) + { + // We have a normal method. Find any interface methods up the inheritance hierarchy that it implicitly + // or explicitly implements and cascade to those. + foreach (var match in await SymbolFinder.FindImplementedInterfaceMembersArrayAsync(symbol, solution, projects, cancellationToken).ConfigureAwait(false)) + await AddCascadedAndLinkedSymbolsToAsync(engine, match, seenSymbols, workQueue, cancellationToken).ConfigureAwait(false); + } + + // If we're overriding a member, then add it to the up-set + if (symbol.GetOverriddenMember() is ISymbol overriddenMember) + await AddCascadedAndLinkedSymbolsToAsync(engine, overriddenMember, seenSymbols, workQueue, cancellationToken).ConfigureAwait(false); + + // An explicit interface method will cascade to all the methods that it implements in the up direction. + await AddCascadedAndLinkedSymbolsToAsync(engine, symbol.ExplicitInterfaceImplementations(), seenSymbols, workQueue, cancellationToken).ConfigureAwait(false); + } + } + } +} diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.UnidirectionalSymbolSet.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.UnidirectionalSymbolSet.cs new file mode 100644 index 0000000000000..ed5de36ba2f6c --- /dev/null +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.UnidirectionalSymbolSet.cs @@ -0,0 +1,69 @@ +// 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.Generic; +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; + +namespace Microsoft.CodeAnalysis.FindSymbols +{ + internal partial class FindReferencesSearchEngine + { + /// + /// Symbol set used when is . This symbol set will only cascade in a uniform direction once it walks either up or down + /// from the initial set of symbols. This is the symbol set used for features like 'Find Refs', where we only + /// want to return location results for members that could feasible actually end up calling into that member at + /// runtime. See the docs of for more + /// information on this. + /// + private sealed class UnidirectionalSymbolSet : SymbolSet + { + private readonly HashSet _initialAndDownSymbols; + + /// + /// When we're doing a unidirectional find-references, the initial set of up-symbols can never change. + /// That's because we have computed the up set entirely up front, and no down symbols can produce new + /// up-symbols (as going down then up would not be unidirectional). + /// + private readonly ImmutableHashSet _upSymbols; + + public UnidirectionalSymbolSet(FindReferencesSearchEngine engine, HashSet initialSymbols, HashSet upSymbols) + : base(engine) + { + _initialAndDownSymbols = initialSymbols; + _upSymbols = upSymbols.ToImmutableHashSet(); + } + + public override ImmutableArray GetAllSymbols() + { + using var _ = ArrayBuilder.GetInstance(_upSymbols.Count + _initialAndDownSymbols.Count, out var result); + result.AddRange(_upSymbols); + result.AddRange(_initialAndDownSymbols); + result.RemoveDuplicates(); + return result.ToImmutable(); + } + + public override async Task InheritanceCascadeAsync(Project project, CancellationToken cancellationToken) + { + // Start searching using the existing set of symbols found at the start (or anything found below that). + var workQueue = new Stack(); + workQueue.Push(_initialAndDownSymbols); + + var projects = ImmutableHashSet.Create(project); + + while (workQueue.Count > 0) + { + var current = workQueue.Pop(); + + // Keep adding symbols downwards in this project as long as we keep finding new symbols. + await AddDownSymbolsAsync(this.Engine, current, _initialAndDownSymbols, workQueue, projects, cancellationToken).ConfigureAwait(false); + } + } + } + } +} diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index 3c47abb899ef1..e4261031a2a5e 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs @@ -2,23 +2,22 @@ // 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.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; -using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.FindSymbols.Finders; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols { - using ProjectToDocumentMap = Dictionary>>; - internal partial class FindReferencesSearchEngine { private readonly Solution _solution; @@ -66,15 +65,50 @@ public async Task FindReferencesAsync(ISymbol symbol, CancellationToken cancella { await using var _ = await _progressTracker.AddSingleItemAsync(cancellationToken).ConfigureAwait(false); - // For the starting symbol, always cascade up and down the inheritance hierarchy. - var symbols = await DetermineAllSymbolsAsync( - symbol, FindReferencesCascadeDirection.UpAndDown, cancellationToken).ConfigureAwait(false); + // Create the initial set of symbols to search for. As we walk the appropriate projects in the solution + // we'll expand this set as we dicover new symbols to search for in each project. + var symbolSet = await SymbolSet.CreateAsync(this, symbol, cancellationToken).ConfigureAwait(false); + + // Report the initial set of symbols to the caller. + var allSymbols = symbolSet.GetAllSymbols(); + await ReportGroupsAsync(allSymbols, cancellationToken).ConfigureAwait(false); + + // Determine the set of projects we actually have to walk to find results in. If the caller provided a + // set of documents to search, we only bother with those. + var projectsToSearch = await GetProjectIdsToSearchAsync(symbolSet.GetAllSymbols(), cancellationToken).ConfigureAwait(false); + + // We need to process projects in order when updating our symbol set. Say we have three projects (A, B + // and C), we cannot necessarily find inherited symbols in C until we have searched B. Importantly, + // 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.Count, cancellationToken).ConfigureAwait(false); + + using var _1 = ArrayBuilder.GetInstance(out var tasks); + + foreach (var projectId in dependencyGraph.GetTopologicallySortedProjects(cancellationToken)) + { + if (!projectsToSearch.Contains(projectId)) + continue; - var projectMap = await CreateProjectMapAsync(symbols, cancellationToken).ConfigureAwait(false); - var projectToDocumentMap = await CreateProjectToDocumentMapAsync(projectMap, cancellationToken).ConfigureAwait(false); - ValidateProjectToDocumentMap(projectToDocumentMap); + var currentProject = _solution.GetRequiredProject(projectId); - await ProcessAsync(projectToDocumentMap, cancellationToken).ConfigureAwait(false); + // As we walk each project, attempt to grow the search set appropriately up and down the inheritance + // hierarchy and grab a copy of the symbols to be processed. Note: this has to happen serially + // 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(); + + // 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)); + } + + // Now, wait for all projects to complete. + await Task.WhenAll(tasks).ConfigureAwait(false); } finally { @@ -82,76 +116,124 @@ public async Task FindReferencesAsync(ISymbol symbol, CancellationToken cancella } } - private async Task ProcessAsync(ProjectToDocumentMap projectToDocumentMap, CancellationToken cancellationToken) + public Task CreateWorkAsync(Func createWorkAsync, CancellationToken cancellationToken) + => Task.Factory.StartNew(createWorkAsync, cancellationToken, TaskCreationOptions.None, _scheduler).Unwrap(); + + /// + /// Notify the caller of the engine about the definitions we've found that we're looking for. We'll only notify + /// them once per symbol group, but we may have to notify about new symbols each time we expand our symbol set + /// when we walk into a new project. + /// + private async Task ReportGroupsAsync(ImmutableArray symbols, CancellationToken cancellationToken) { - using (Logger.LogBlock(FunctionId.FindReference_ProcessAsync, cancellationToken)) + foreach (var symbol in symbols) { - // quick exit - if (projectToDocumentMap.Count == 0) + // See if this is the first time we're running across this symbol. Note: no locks are needed + // here betwen checking and then adding because this is only ever called serially from within + // FindReferencesAsync above (though we still need a ConcurrentDictionary as reads of these + // symbols will happen later in ProcessDocumentAsync. However, those reads will only happen + // after the dependent symbol values were written in, so it will be safe to blindly read them + // out. + if (!_symbolToGroup.ContainsKey(symbol)) { - return; - } + var linkedSymbols = await SymbolFinder.FindLinkedSymbolsAsync(symbol, _solution, cancellationToken).ConfigureAwait(false); + var group = new SymbolGroup(linkedSymbols); - // Add a progress item for each (document, symbol, finder) set that we will execute. - // We'll mark the item as completed in "ProcessDocumentAsync". - var totalFindCount = projectToDocumentMap.Sum( - kvp1 => kvp1.Value.Sum(kvp2 => kvp2.Value.Count)); - await _progressTracker.AddItemsAsync(totalFindCount, cancellationToken).ConfigureAwait(false); + foreach (var groupSymbol in group.Symbols) + _symbolToGroup.TryAdd(groupSymbol, group); + + await _progress.OnDefinitionFoundAsync(group, cancellationToken).ConfigureAwait(false); + } + } + } - using var _ = ArrayBuilder.GetInstance(out var tasks); + private async Task> GetProjectIdsToSearchAsync( + ImmutableArray symbols, CancellationToken cancellationToken) + { + var projects = _documents != null + ? _documents.Select(d => d.Project).ToImmutableHashSet() + : _solution.Projects.ToImmutableHashSet(); - foreach (var (project, documentMap) in projectToDocumentMap) - tasks.Add(Task.Factory.StartNew(() => ProcessProjectAsync(project, documentMap, cancellationToken), cancellationToken, TaskCreationOptions.None, _scheduler).Unwrap()); + var result = new HashSet(); - await Task.WhenAll(tasks).ConfigureAwait(false); + foreach (var symbol in symbols) + { + var dependentProjects = await DependentProjectsFinder.GetDependentProjectsAsync( + _solution, symbol, projects, cancellationToken).ConfigureAwait(false); + foreach (var project in dependentProjects) + result.Add(project.Id); } + + return result; } - [Conditional("DEBUG")] - private static void ValidateProjectToDocumentMap( - ProjectToDocumentMap projectToDocumentMap) + private async Task ProcessProjectAsync(Project project, ImmutableArray allSymbols, CancellationToken cancellationToken) { - var set = new HashSet(); - - foreach (var documentMap in projectToDocumentMap.Values) + try { - foreach (var documentToFinderList in documentMap) + using var _1 = PooledHashSet.GetInstance(out var allDocuments); + foreach (var symbol in allSymbols) { - set.Clear(); - - foreach (var tuple in documentToFinderList.Value) - Debug.Assert(set.Add(tuple)); + foreach (var finder in _finders) + { + var documents = await finder.DetermineDocumentsToSearchAsync( + symbol, project, _documents, _options, cancellationToken).ConfigureAwait(false); + allDocuments.AddRange(documents); + } } + + using var _2 = ArrayBuilder.GetInstance(out var tasks); + foreach (var document in allDocuments) + tasks.Add(CreateWorkAsync(() => ProcessDocumentAsync(document, allSymbols, cancellationToken), cancellationToken)); + + await Task.WhenAll(tasks).ConfigureAwait(false); + } + finally + { + await _progressTracker.ItemCompletedAsync(cancellationToken).ConfigureAwait(false); } } - private async ValueTask HandleLocationAsync(ISymbol symbol, ReferenceLocation location, CancellationToken cancellationToken) + private async Task ProcessDocumentAsync( + Document document, ImmutableArray symbols, CancellationToken cancellationToken) { - var group = await GetOrCreateSymbolGroupAsync(symbol, cancellationToken).ConfigureAwait(false); - await _progress.OnReferenceFoundAsync(group, symbol, location, cancellationToken).ConfigureAwait(false); + await _progress.OnFindInDocumentStartedAsync(document, cancellationToken).ConfigureAwait(false); + + SemanticModel? model = null; + try + { + model = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + + // start cache for this semantic model + FindReferenceCache.Start(model); + + foreach (var symbol in symbols) + await ProcessDocumentAsync(document, model, symbol, cancellationToken).ConfigureAwait(false); + } + finally + { + FindReferenceCache.Stop(model); + + await _progress.OnFindInDocumentCompletedAsync(document, cancellationToken).ConfigureAwait(false); + } } - private async ValueTask GetOrCreateSymbolGroupAsync(ISymbol symbol, CancellationToken cancellationToken) + private async Task ProcessDocumentAsync( + Document document, SemanticModel semanticModel, ISymbol symbol, CancellationToken cancellationToken) { - // See if this symbol is already associated with a symbol group. - if (!_symbolToGroup.TryGetValue(symbol, out var group)) + using (Logger.LogBlock(FunctionId.FindReference_ProcessDocumentAsync, cancellationToken)) { - // If not, compute the group it should be associated with. - group = await DetermineSymbolGroupAsync(symbol, cancellationToken).ConfigureAwait(false); - - // now try to update our mapping. - lock (_symbolToGroup) + // This is safe to just blindly read. We can only ever get here after the call to ReportGroupsAsync + // happened. So tehre must be a group for this symbol in our map. + var group = _symbolToGroup[symbol]; + foreach (var finder in _finders) { - // Another thread may have beat us, so only do this if we're actually the first to get here. - if (!_symbolToGroup.TryGetValue(symbol, out _)) - { - foreach (var groupSymbol in group.Symbols) - Contract.ThrowIfFalse(_symbolToGroup.TryAdd(groupSymbol, group)); - } + var references = await finder.FindReferencesInDocumentAsync( + symbol, document, semanticModel, _options, cancellationToken).ConfigureAwait(false); + foreach (var (_, location) in references) + await _progress.OnReferenceFoundAsync(group, symbol, location, cancellationToken).ConfigureAwait(false); } } - - return group; } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_DocumentProcessing.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_DocumentProcessing.cs deleted file mode 100644 index 7c570a68dbe33..0000000000000 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_DocumentProcessing.cs +++ /dev/null @@ -1,73 +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.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.FindSymbols.Finders; -using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.CodeAnalysis.Shared.Extensions; - -namespace Microsoft.CodeAnalysis.FindSymbols -{ - internal partial class FindReferencesSearchEngine - { - private async Task ProcessDocumentQueueAsync( - Document document, - HashSet documentQueue, - CancellationToken cancellationToken) - { - await _progress.OnFindInDocumentStartedAsync(document, cancellationToken).ConfigureAwait(false); - - SemanticModel? model = null; - try - { - model = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); - - // start cache for this semantic model - FindReferenceCache.Start(model); - - foreach (var symbol in documentQueue) - await ProcessDocumentAsync(document, model, symbol, cancellationToken).ConfigureAwait(false); - } - finally - { - FindReferenceCache.Stop(model); - - await _progress.OnFindInDocumentCompletedAsync(document, cancellationToken).ConfigureAwait(false); - } - } - - private static readonly Func s_logDocument = (d, s) => - { - return (d.Name != null && s.Name != null) ? string.Format("{0} - {1}", d.Name, s.Name) : string.Empty; - }; - - private async Task ProcessDocumentAsync( - Document document, - SemanticModel semanticModel, - ISymbol symbol, - CancellationToken cancellationToken) - { - using (Logger.LogBlock(FunctionId.FindReference_ProcessDocumentAsync, s_logDocument, document, symbol, cancellationToken)) - { - try - { - foreach (var finder in _finders) - { - var references = await finder.FindReferencesInDocumentAsync( - symbol, document, semanticModel, _options, cancellationToken).ConfigureAwait(false); - foreach (var (_, location) in references) - await HandleLocationAsync(symbol, location, cancellationToken).ConfigureAwait(false); - } - } - finally - { - await _progressTracker.ItemCompletedAsync(cancellationToken).ConfigureAwait(false); - } - } - } - } -} diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_MapCreation.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_MapCreation.cs deleted file mode 100644 index b04db155d06db..0000000000000 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_MapCreation.cs +++ /dev/null @@ -1,264 +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 System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.FindSymbols.Finders; -using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Shared.Extensions; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.FindSymbols -{ - using DocumentMap = Dictionary>; - using ProjectMap = Dictionary>; - using ProjectToDocumentMap = Dictionary>>; - - internal partial class FindReferencesSearchEngine - { - private static readonly Func s_createDocumentMap = _ => new DocumentMap(); - - private async Task CreateProjectToDocumentMapAsync(ProjectMap projectMap, CancellationToken cancellationToken) - { - using (Logger.LogBlock(FunctionId.FindReference_CreateDocumentMapAsync, cancellationToken)) - { - using var _ = ArrayBuilder, ISymbol)>>.GetInstance(out var tasks); - - foreach (var (project, projectQueue) in projectMap) - { - foreach (var symbol in projectQueue) - { - tasks.Add(Task.Factory.StartNew(() => - DetermineDocumentsToSearchAsync(project, symbol, cancellationToken), cancellationToken, TaskCreationOptions.None, _scheduler).Unwrap()); - } - } - - var results = await Task.WhenAll(tasks).ConfigureAwait(false); - - var finalMap = new ProjectToDocumentMap(); - foreach (var (documents, symbol) in results) - { - foreach (var document in documents) - { - finalMap.GetOrAdd(document.Project, s_createDocumentMap) - .MultiAdd(document, symbol); - } - } - -#if DEBUG - foreach (var (project, documentMap) in finalMap) - { - Contract.ThrowIfTrue(documentMap.Any(kvp1 => kvp1.Value.Count != kvp1.Value.ToSet().Count)); - } -#endif - - return finalMap; - } - } - - private async Task<(ImmutableArray, ISymbol)> DetermineDocumentsToSearchAsync( - Project project, ISymbol symbol, CancellationToken cancellationToken) - { - using var _ = ArrayBuilder.GetInstance(out var result); - - foreach (var finder in _finders) - { - var documents = await finder.DetermineDocumentsToSearchAsync( - symbol, project, _documents, _options, cancellationToken).ConfigureAwait(false); - - foreach (var document in documents) - { - if (_documents == null || _documents.Contains(document)) - result.Add(document); - } - } - - result.RemoveDuplicates(); - return (result.ToImmutable(), symbol); - } - - private async Task CreateProjectMapAsync(ConcurrentSet symbolGroups, CancellationToken cancellationToken) - { - using (Logger.LogBlock(FunctionId.FindReference_CreateProjectMapAsync, cancellationToken)) - { - var projectMap = new ProjectMap(); - - var scope = _documents?.Select(d => d.Project).ToImmutableHashSet(); - foreach (var symbolGroup in symbolGroups) - { - foreach (var symbol in symbolGroup.Symbols) - { - cancellationToken.ThrowIfCancellationRequested(); - var projects = await DependentProjectsFinder.GetDependentProjectsAsync( - _solution, symbol, scope, cancellationToken).ConfigureAwait(false); - - foreach (var project in projects.Distinct().WhereNotNull()) - { - if (scope == null || scope.Contains(project)) - projectMap.MultiAdd(project, symbol); - } - } - } - - Contract.ThrowIfTrue(projectMap.Any(kvp => kvp.Value.Count != kvp.Value.ToSet().Count)); - return projectMap; - } - } - - private async Task> DetermineAllSymbolsAsync( - ISymbol symbol, FindReferencesCascadeDirection cascadeDirection, CancellationToken cancellationToken) - { - using (Logger.LogBlock(FunctionId.FindReference_DetermineAllSymbolsAsync, cancellationToken)) - { - var result = new ConcurrentSet(); - await DetermineAllSymbolsCoreAsync(symbol, cascadeDirection, result, cancellationToken).ConfigureAwait(false); - return result; - } - } - - private async Task DetermineAllSymbolsCoreAsync( - ISymbol symbol, FindReferencesCascadeDirection cascadeDirection, - ConcurrentSet result, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - var searchSymbol = MapToAppropriateSymbol(symbol); - - // 2) Try to map this back to source symbol if this was a metadata symbol. - var sourceSymbol = await SymbolFinder.FindSourceDefinitionAsync(searchSymbol, _solution, cancellationToken).ConfigureAwait(false); - if (sourceSymbol != null) - searchSymbol = sourceSymbol; - - Contract.ThrowIfNull(searchSymbol); - - var group = await DetermineSymbolGroupAsync(searchSymbol, cancellationToken).ConfigureAwait(false); - if (result.Add(group)) - { - await _progress.OnDefinitionFoundAsync(group, cancellationToken).ConfigureAwait(false); - - // get project to search - var projects = GetProjectScope(); - - cancellationToken.ThrowIfCancellationRequested(); - - using var _ = ArrayBuilder.GetInstance(out var finderTasks); - foreach (var f in _finders) - { - finderTasks.Add(Task.Factory.StartNew(async () => - { - using var _ = ArrayBuilder.GetInstance(out var symbolTasks); - - var symbols = await f.DetermineCascadedSymbolsAsync( - searchSymbol, _solution, projects, _options, cascadeDirection, cancellationToken).ConfigureAwait(false); - AddSymbolTasks(result, symbols, symbolTasks, cancellationToken); - - // Defer to the language to see if it wants to cascade here in some special way. - var symbolProject = _solution.GetProject(searchSymbol.ContainingAssembly); - if (symbolProject?.LanguageServices.GetService() is { } service) - { - symbols = await service.DetermineCascadedSymbolsAsync( - searchSymbol, symbolProject, cascadeDirection, cancellationToken).ConfigureAwait(false); - AddSymbolTasks(result, symbols, symbolTasks, cancellationToken); - } - - cancellationToken.ThrowIfCancellationRequested(); - - await Task.WhenAll(symbolTasks).ConfigureAwait(false); - }, cancellationToken, TaskCreationOptions.None, _scheduler).Unwrap()); - } - - await Task.WhenAll(finderTasks).ConfigureAwait(false); - } - } - - private async Task DetermineSymbolGroupAsync(ISymbol searchSymbol, CancellationToken cancellationToken) - { - if (!_options.Cascade) - return new SymbolGroup(ImmutableArray.Create(searchSymbol)); - - return new SymbolGroup( - await SymbolFinder.FindLinkedSymbolsAsync(searchSymbol, _solution, cancellationToken).ConfigureAwait(false)); - } - - private void AddSymbolTasks( - ConcurrentSet result, - ImmutableArray<(ISymbol symbol, FindReferencesCascadeDirection cascadeDirection)> symbols, - ArrayBuilder symbolTasks, - CancellationToken cancellationToken) - { - if (!symbols.IsDefault) - { - foreach (var (symbol, cascadeDirection) in symbols) - { - Contract.ThrowIfNull(symbol); - - // If we're cascading unidirectionally, then keep going in the direction this symbol was found in. - // Otherwise, if we're not unidirectional, then continue to cascade in both directions with this - // symbol. - var finalDirection = _options.UnidirectionalHierarchyCascade - ? cascadeDirection - : FindReferencesCascadeDirection.UpAndDown; - symbolTasks.Add(Task.Factory.StartNew( - () => DetermineAllSymbolsCoreAsync(symbol, finalDirection, result, cancellationToken), cancellationToken, TaskCreationOptions.None, _scheduler).Unwrap()); - } - } - } - - private ImmutableHashSet? GetProjectScope() - { - if (_documents == null) - { - return null; - } - - var builder = ImmutableHashSet.CreateBuilder(); - foreach (var document in _documents) - { - builder.Add(document.Project); - - foreach (var reference in document.Project.ProjectReferences) - { - var referenceProject = document.Project.Solution.GetProject(reference.ProjectId); - if (referenceProject != null) - { - builder.Add(referenceProject); - } - } - } - - return builder.ToImmutable(); - } - - private static ISymbol MapToAppropriateSymbol(ISymbol symbol) - { - // Never search for an alias. Always search for it's target. Note: if the caller was - // actually searching for an alias, they can always get that information out in the end - // by checking the ReferenceLocations that are returned. - var searchSymbol = symbol; - - if (searchSymbol is IAliasSymbol) - { - searchSymbol = ((IAliasSymbol)searchSymbol).Target; - } - - searchSymbol = searchSymbol.GetOriginalUnreducedDefinition(); - - // If they're searching for a delegate constructor, then just search for the delegate - // itself. They're practically interchangeable for consumers. - if (searchSymbol.IsConstructor() && searchSymbol.ContainingType.TypeKind == TypeKind.Delegate) - { - searchSymbol = symbol.ContainingType; - } - - Contract.ThrowIfNull(searchSymbol); - return searchSymbol; - } - } -} diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_ProjectProcessing.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_ProjectProcessing.cs deleted file mode 100644 index a6b0c28a10bd6..0000000000000 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_ProjectProcessing.cs +++ /dev/null @@ -1,45 +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.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.CodeAnalysis.PooledObjects; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.FindSymbols -{ - using DocumentMap = Dictionary>; - - internal partial class FindReferencesSearchEngine - { - private async Task ProcessProjectAsync( - Project project, - DocumentMap documentMap, - CancellationToken cancellationToken) - { - using (Logger.LogBlock(FunctionId.FindReference_ProcessProjectAsync, project.Name, cancellationToken)) - { - if (project.SupportsCompilation) - { - // make sure we hold onto compilation while we search documents belong to this project - var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); - - using var _ = ArrayBuilder.GetInstance(out var documentTasks); - foreach (var (document, documentQueue) in documentMap) - { - if (document.Project == project) - documentTasks.Add(Task.Factory.StartNew(() => ProcessDocumentQueueAsync(document, documentQueue, cancellationToken), cancellationToken, TaskCreationOptions.None, _scheduler).Unwrap()); - } - - await Task.WhenAll(documentTasks).ConfigureAwait(false); - - GC.KeepAlive(compilation); - } - } - } - } -} diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMemberScopedReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMemberScopedReferenceFinder.cs index 982397e804992..c3a8d69a4e0f0 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMemberScopedReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMemberScopedReferenceFinder.cs @@ -117,8 +117,7 @@ protected static ValueTask> FindReferencesInToken CancellationToken cancellationToken) { return FindReferencesInTokensWithSymbolNameAsync( - symbol, document, semanticModel, tokens, - findParentNode: null, cancellationToken: cancellationToken); + symbol, document, semanticModel, tokens, findParentNode: null, cancellationToken); } protected static ValueTask> FindReferencesInTokensWithSymbolNameAsync( @@ -150,8 +149,7 @@ private ValueTask> FindReferencesInContainerAsync CancellationToken cancellationToken) { return FindReferencesInContainerAsync( - symbol, container, document, semanticModel, - findParentNode: null, cancellationToken: cancellationToken); + symbol, container, document, semanticModel, findParentNode: null, cancellationToken); } private ValueTask> FindReferencesInContainerAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMethodOrPropertyOrEventSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMethodOrPropertyOrEventSymbolReferenceFinder.cs index 29e40ca4a9817..769556a104cc2 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMethodOrPropertyOrEventSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMethodOrPropertyOrEventSymbolReferenceFinder.cs @@ -18,52 +18,6 @@ protected AbstractMethodOrPropertyOrEventSymbolReferenceFinder() { } - protected override async Task> DetermineCascadedSymbolsAsync( - TSymbol symbol, - Solution solution, - IImmutableSet? projects, - FindReferencesSearchOptions options, - FindReferencesCascadeDirection cascadeDirection, - CancellationToken cancellationToken) - { - if (symbol.IsImplementableMember()) - { - // We have an interface method. Walk down the inheritance hierarchy and find all implementations of - // that method and cascade to them. - var result = cascadeDirection.HasFlag(FindReferencesCascadeDirection.Down) - ? await SymbolFinder.FindMemberImplementationsArrayAsync(symbol, solution, projects, cancellationToken).ConfigureAwait(false) - : ImmutableArray.Empty; - return result.SelectAsArray(s => (s, FindReferencesCascadeDirection.Down)); - } - else - { - // We have a normal method. Find any interface methods up the inheritance hierarchy that it implicitly - // or explicitly implements and cascade to those. - var interfaceMembersImplemented = cascadeDirection.HasFlag(FindReferencesCascadeDirection.Up) - ? await SymbolFinder.FindImplementedInterfaceMembersArrayAsync(symbol, solution, projects, cancellationToken).ConfigureAwait(false) - : ImmutableArray.Empty; - - // Finally, methods can cascade through virtual/override inheritance. NOTE(cyrusn): - // We only need to go up or down one level. Then, when we're finding references on - // those members, we'll end up traversing the entire hierarchy. - var overrides = cascadeDirection.HasFlag(FindReferencesCascadeDirection.Down) - ? await SymbolFinder.FindOverridesArrayAsync(symbol, solution, projects, cancellationToken).ConfigureAwait(false) - : ImmutableArray.Empty; - - var overriddenMember = cascadeDirection.HasFlag(FindReferencesCascadeDirection.Up) - ? symbol.GetOverriddenMember() - : null; - - var interfaceMembersImplementedWithDirection = interfaceMembersImplemented.SelectAsArray(s => (s, FindReferencesCascadeDirection.Up)); - var overridesWithDirection = overrides.SelectAsArray(s => (s, FindReferencesCascadeDirection.Down)); - var overriddenMemberWithDirection = (overriddenMember!, FindReferencesCascadeDirection.Up); - - return overriddenMember == null - ? interfaceMembersImplementedWithDirection.Concat(overridesWithDirection) - : interfaceMembersImplementedWithDirection.Concat(overridesWithDirection).Concat(overriddenMemberWithDirection); - } - } - protected static ImmutableArray GetReferencedAccessorSymbols( ISyntaxFactsService syntaxFacts, ISemanticFactsService semanticFacts, SemanticModel model, IPropertySymbol property, SyntaxNode node, CancellationToken cancellationToken) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs index 8fbffac24712f..0d2e891844c2e 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs @@ -26,10 +26,8 @@ internal abstract partial class AbstractReferenceFinder : IReferenceFinder public const string ContainingTypeInfoPropertyName = "ContainingTypeInfo"; public const string ContainingMemberInfoPropertyName = "ContainingMemberInfo"; - public abstract Task> DetermineCascadedSymbolsAsync( - ISymbol symbol, Solution solution, IImmutableSet? projects, - FindReferencesSearchOptions options, FindReferencesCascadeDirection cascadeDirection, - CancellationToken cancellationToken); + public abstract Task> DetermineCascadedSymbolsAsync( + ISymbol symbol, Solution solution, FindReferencesSearchOptions options, CancellationToken cancellationToken); public abstract Task> DetermineDocumentsToSearchAsync( ISymbol symbol, Project project, IImmutableSet? documents, FindReferencesSearchOptions options, CancellationToken cancellationToken); @@ -162,8 +160,7 @@ protected static ValueTask> FindReferencesInDocum CancellationToken cancellationToken) { return FindReferencesInDocumentUsingIdentifierAsync( - symbol, identifier, document, semanticModel, findParentNode: null, - cancellationToken: cancellationToken); + symbol, identifier, document, semanticModel, findParentNode: null, cancellationToken); } [PerformanceSensitive("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1224834", OftenCompletesSynchronously = true)] @@ -337,8 +334,7 @@ protected static Task> FindAliasReferencesAsync( CancellationToken cancellationToken) { return FindAliasReferencesAsync( - nonAliasReferences, symbol, document, semanticModel, - findParentNode: null, cancellationToken: cancellationToken); + nonAliasReferences, symbol, document, semanticModel, findParentNode: null, cancellationToken); } protected static async Task> FindAliasReferencesAsync( @@ -468,7 +464,6 @@ protected delegate void CollectMatchingReferences( SyntaxNode node, ISyntaxFactsService syntaxFacts, ISemanticFactsService semanticFacts, ArrayBuilder locations); protected static async Task> FindReferencesInDocumentAsync( - ISymbol _, Document document, Func isRelevantDocument, CollectMatchingReferences collectMatchingReferences, @@ -481,7 +476,7 @@ protected static async Task> FindReferencesInDocu var semanticFacts = document.GetRequiredLanguageService(); var syntaxRoot = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - using var _1 = ArrayBuilder.GetInstance(out var locations); + using var _ = ArrayBuilder.GetInstance(out var locations); foreach (var node in syntaxRoot.DescendantNodesAndSelf()) { @@ -501,7 +496,7 @@ protected Task> FindReferencesInForEachStatements SemanticModel semanticModel, CancellationToken cancellationToken) { - return FindReferencesInDocumentAsync(symbol, document, IsRelevantDocument, CollectMatchingReferences, cancellationToken); + return FindReferencesInDocumentAsync(document, IsRelevantDocument, CollectMatchingReferences, cancellationToken); static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo) => syntaxTreeInfo.ContainsForEachStatement; @@ -537,7 +532,7 @@ protected Task> FindReferencesInDeconstructionAsy SemanticModel semanticModel, CancellationToken cancellationToken) { - return FindReferencesInDocumentAsync(symbol, document, IsRelevantDocument, CollectMatchingReferences, cancellationToken); + return FindReferencesInDocumentAsync(document, IsRelevantDocument, CollectMatchingReferences, cancellationToken); static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo) => syntaxTreeInfo.ContainsDeconstruction; @@ -569,7 +564,7 @@ protected Task> FindReferencesInAwaitExpressionAs SemanticModel semanticModel, CancellationToken cancellationToken) { - return FindReferencesInDocumentAsync(symbol, document, IsRelevantDocument, CollectMatchingReferences, cancellationToken); + return FindReferencesInDocumentAsync(document, IsRelevantDocument, CollectMatchingReferences, cancellationToken); static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo) => syntaxTreeInfo.ContainsAwait; @@ -596,7 +591,7 @@ protected Task> FindReferencesInImplicitObjectCre SemanticModel semanticModel, CancellationToken cancellationToken) { - return FindReferencesInDocumentAsync(symbol, document, IsRelevantDocument, CollectMatchingReferences, cancellationToken); + return FindReferencesInDocumentAsync(document, IsRelevantDocument, CollectMatchingReferences, cancellationToken); static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo) => syntaxTreeInfo.ContainsImplicitObjectCreation; @@ -897,28 +892,26 @@ public override ValueTask> FindReferencesInDocume : new ValueTask>(ImmutableArray.Empty); } - public override Task> DetermineCascadedSymbolsAsync( - ISymbol symbol, Solution solution, IImmutableSet? projects, - FindReferencesSearchOptions options, FindReferencesCascadeDirection cascadeDirection, - CancellationToken cancellationToken) + public sealed override Task> DetermineCascadedSymbolsAsync( + ISymbol symbol, Solution solution, FindReferencesSearchOptions options, CancellationToken cancellationToken) { if (options.Cascade && symbol is TSymbol typedSymbol && CanFind(typedSymbol)) { - return DetermineCascadedSymbolsAsync( - typedSymbol, solution, projects, options, cascadeDirection, cancellationToken); + return DetermineCascadedSymbolsAsync(typedSymbol, solution, options, cancellationToken); } - return SpecializedTasks.EmptyImmutableArray<(ISymbol symbol, FindReferencesCascadeDirection cascadeDirection)>(); + return SpecializedTasks.EmptyImmutableArray(); } - protected virtual Task> DetermineCascadedSymbolsAsync( - TSymbol symbol, Solution solution, IImmutableSet? projects, - FindReferencesSearchOptions options, FindReferencesCascadeDirection cascadeDirection, + protected virtual Task> DetermineCascadedSymbolsAsync( + TSymbol symbol, + Solution solution, + FindReferencesSearchOptions options, CancellationToken cancellationToken) { - return SpecializedTasks.EmptyImmutableArray<(ISymbol symbol, FindReferencesCascadeDirection cascadeDirection)>(); + return SpecializedTasks.EmptyImmutableArray(); } protected static ValueTask> FindReferencesInDocumentUsingSymbolNameAsync( @@ -928,7 +921,7 @@ protected static ValueTask> FindReferencesInDocum CancellationToken cancellationToken) { return FindReferencesInDocumentUsingIdentifierAsync( - symbol, symbol.Name, document, semanticModel, cancellationToken: cancellationToken); + symbol, symbol.Name, document, semanticModel, cancellationToken); } protected static ValueTask> FindReferencesInTokensAsync( @@ -940,8 +933,7 @@ protected static ValueTask> FindReferencesInToken CancellationToken cancellationToken) { return FindReferencesInTokensAsync( - symbol, document, semanticModel, tokens, tokensMatch, - findParentNode: null, cancellationToken: cancellationToken); + symbol, document, semanticModel, tokens, tokensMatch, findParentNode: null, cancellationToken); } protected static ValueTask> FindReferencesInTokensAsync( @@ -972,8 +964,7 @@ protected static ValueTask> FindReferencesInDocum CancellationToken cancellationToken) { return FindReferencesInDocumentAsync( - symbol, document, semanticModel, tokensMatch, - findParentNode: null, cancellationToken: cancellationToken); + symbol, document, semanticModel, tokensMatch, findParentNode: null, cancellationToken); } protected static ValueTask> FindReferencesInDocumentAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs index 56d67ff697fc7..2e6fe92da6ce6 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs @@ -67,12 +67,7 @@ protected override async ValueTask> FindReference } return await FindReferencesInTokensAsync( - methodSymbol, - document, - semanticModel, - tokens, - TokensMatch, - cancellationToken).ConfigureAwait(false); + methodSymbol, document, semanticModel, tokens, TokensMatch, cancellationToken).ConfigureAwait(false); // local functions bool TokensMatch(SyntaxToken t) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs index 992adf5d97b37..c0f58c606a425 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs @@ -86,8 +86,7 @@ internal async ValueTask> FindAllReferencesInDocu var nonAliasTypeReferences = await NamedTypeSymbolReferenceFinder.FindNonAliasReferencesAsync(methodSymbol.ContainingType, document, semanticModel, cancellationToken).ConfigureAwait(false); var aliasReferences = await FindAliasReferencesAsync( - nonAliasTypeReferences, methodSymbol, document, semanticModel, - findParentNode, cancellationToken).ConfigureAwait(false); + nonAliasTypeReferences, methodSymbol, document, semanticModel, findParentNode, cancellationToken).ConfigureAwait(false); var suppressionReferences = await FindReferencesInDocumentInsideGlobalSuppressionsAsync(document, semanticModel, methodSymbol, cancellationToken).ConfigureAwait(false); return normalReferences.Concat(aliasReferences, suppressionReferences); @@ -167,7 +166,7 @@ private Task> FindReferencesInImplicitObjectCreat ? -1 : symbol.Parameters.Length; - return FindReferencesInDocumentAsync(symbol, document, IsRelevantDocument, CollectMatchingReferences, cancellationToken); + return FindReferencesInDocumentAsync(document, IsRelevantDocument, CollectMatchingReferences, cancellationToken); 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 f9fb71287efab..c99897acf3bb9 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/EventSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/EventSymbolReferenceFinder.cs @@ -14,27 +14,22 @@ internal class EventSymbolReferenceFinder : AbstractMethodOrPropertyOrEventSymbo protected override bool CanFind(IEventSymbol symbol) => true; - protected override async Task> DetermineCascadedSymbolsAsync( + protected override Task> DetermineCascadedSymbolsAsync( IEventSymbol symbol, Solution solution, - IImmutableSet? projects, FindReferencesSearchOptions options, - FindReferencesCascadeDirection cascadeDirection, CancellationToken cancellationToken) { - var baseSymbols = await base.DetermineCascadedSymbolsAsync( - symbol, solution, projects, options, cascadeDirection, cancellationToken).ConfigureAwait(false); - var backingFields = symbol.ContainingType.GetMembers() .OfType() .Where(f => symbol.Equals(f.AssociatedSymbol)) - .ToImmutableArray(); + .ToImmutableArray(); var associatedNamedTypes = symbol.ContainingType.GetTypeMembers() - .WhereAsArray(n => symbol.Equals(n.AssociatedSymbol)); + .WhereAsArray(n => symbol.Equals(n.AssociatedSymbol)) + .CastArray(); - return baseSymbols.Concat(backingFields.SelectAsArray(f => ((ISymbol)f, cascadeDirection))) - .Concat(associatedNamedTypes.SelectAsArray(n => ((ISymbol)n, cascadeDirection))); + return Task.FromResult(backingFields.Concat(associatedNamedTypes)); } protected override async Task> DetermineDocumentsToSearchAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitInterfaceMethodReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitInterfaceMethodReferenceFinder.cs index 533c4227ff5a3..ca77742c7c537 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitInterfaceMethodReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitInterfaceMethodReferenceFinder.cs @@ -14,22 +14,6 @@ internal class ExplicitInterfaceMethodReferenceFinder : AbstractReferenceFinder< protected override bool CanFind(IMethodSymbol symbol) => symbol.MethodKind == MethodKind.ExplicitInterfaceImplementation; - protected override Task> DetermineCascadedSymbolsAsync( - IMethodSymbol symbol, - Solution solution, - IImmutableSet? projects, - FindReferencesSearchOptions options, - FindReferencesCascadeDirection cascadeDirection, - CancellationToken cancellationToken) - { - if (!cascadeDirection.HasFlag(FindReferencesCascadeDirection.Up)) - return SpecializedTasks.EmptyImmutableArray<(ISymbol symbol, FindReferencesCascadeDirection cascadeDirection)>(); - - // An explicit interface method will cascade to all the methods that it implements in the up direction. - return Task.FromResult( - symbol.ExplicitInterfaceImplementations.SelectAsArray(m => ((ISymbol)m, FindReferencesCascadeDirection.Up))); - } - protected override Task> DetermineDocumentsToSearchAsync( IMethodSymbol symbol, Project project, diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs index ccc563fb70130..6e437b0c33ad8 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs @@ -14,17 +14,15 @@ internal class FieldSymbolReferenceFinder : AbstractReferenceFinder true; - protected override Task> DetermineCascadedSymbolsAsync( + protected override Task> DetermineCascadedSymbolsAsync( IFieldSymbol symbol, Solution solution, - IImmutableSet? projects, FindReferencesSearchOptions options, - FindReferencesCascadeDirection cascadeDirection, CancellationToken cancellationToken) { return symbol.AssociatedSymbol != null - ? Task.FromResult(ImmutableArray.Create((symbol.AssociatedSymbol, cascadeDirection))) - : SpecializedTasks.EmptyImmutableArray<(ISymbol symbol, FindReferencesCascadeDirection cascadeDirection)>(); + ? Task.FromResult(ImmutableArray.Create(symbol.AssociatedSymbol)) + : SpecializedTasks.EmptyImmutableArray(); } protected override async Task> DetermineDocumentsToSearchAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ILanguageServiceReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ILanguageServiceReferenceFinder.cs index aec22cca0ea43..737e35400968d 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ILanguageServiceReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ILanguageServiceReferenceFinder.cs @@ -16,7 +16,7 @@ namespace Microsoft.CodeAnalysis.FindSymbols.Finders /// internal interface ILanguageServiceReferenceFinder : ILanguageService { - Task> DetermineCascadedSymbolsAsync( - ISymbol symbol, Project project, FindReferencesCascadeDirection cascadeDirection, CancellationToken cancellationToken); + Task> DetermineCascadedSymbolsAsync( + ISymbol symbol, Project project, CancellationToken cancellationToken); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/IReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/IReferenceFinder.cs index 6284cab2e7785..0ebe51ba8f0e7 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/IReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/IReferenceFinder.cs @@ -24,10 +24,8 @@ internal interface IReferenceFinder /// /// Implementations of this method must be thread-safe. /// - Task> DetermineCascadedSymbolsAsync( - ISymbol symbol, Solution solution, IImmutableSet? projects, - FindReferencesSearchOptions options, FindReferencesCascadeDirection cascadeDirection, - CancellationToken cancellationToken); + Task> DetermineCascadedSymbolsAsync( + ISymbol symbol, Solution solution, FindReferencesSearchOptions options, CancellationToken cancellationToken); /// /// Called by the find references search engine to determine which documents in the supplied diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/MethodTypeParameterSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/MethodTypeParameterSymbolReferenceFinder.cs index 5a412595189c7..f570fadc2c624 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/MethodTypeParameterSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/MethodTypeParameterSymbolReferenceFinder.cs @@ -14,12 +14,10 @@ internal class MethodTypeParameterSymbolReferenceFinder : AbstractReferenceFinde protected override bool CanFind(ITypeParameterSymbol symbol) => symbol.TypeParameterKind == TypeParameterKind.Method; - protected override Task> DetermineCascadedSymbolsAsync( + protected override Task> DetermineCascadedSymbolsAsync( ITypeParameterSymbol symbol, Solution solution, - IImmutableSet? projects, FindReferencesSearchOptions options, - FindReferencesCascadeDirection cascadeDirection, CancellationToken cancellationToken) { var method = (IMethodSymbol)symbol.ContainingSymbol; @@ -28,19 +26,13 @@ protected override bool CanFind(ITypeParameterSymbol symbol) if (ordinal >= 0) { if (method.PartialDefinitionPart != null && ordinal < method.PartialDefinitionPart.TypeParameters.Length) - { - return Task.FromResult(ImmutableArray.Create( - ((ISymbol)method.PartialDefinitionPart.TypeParameters[ordinal], cascadeDirection))); - } + return Task.FromResult(ImmutableArray.Create(method.PartialDefinitionPart.TypeParameters[ordinal])); if (method.PartialImplementationPart != null && ordinal < method.PartialImplementationPart.TypeParameters.Length) - { - return Task.FromResult(ImmutableArray.Create( - ((ISymbol)method.PartialImplementationPart.TypeParameters[ordinal], cascadeDirection))); - } + return Task.FromResult(ImmutableArray.Create(method.PartialImplementationPart.TypeParameters[ordinal])); } - return SpecializedTasks.EmptyImmutableArray<(ISymbol symbol, FindReferencesCascadeDirection cascadeDirection)>(); + return SpecializedTasks.EmptyImmutableArray(); } protected override Task> DetermineDocumentsToSearchAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs index 80aa784e30f5d..222c617c95e52 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs @@ -18,12 +18,10 @@ internal class NamedTypeSymbolReferenceFinder : AbstractReferenceFinder symbol.TypeKind != TypeKind.Error; - protected override Task> DetermineCascadedSymbolsAsync( + protected override Task> DetermineCascadedSymbolsAsync( INamedTypeSymbol symbol, Solution solution, - IImmutableSet? projects, FindReferencesSearchOptions options, - FindReferencesCascadeDirection cascadeDirection, CancellationToken cancellationToken) { using var _ = ArrayBuilder.GetInstance(out var result); @@ -37,7 +35,7 @@ protected override bool CanFind(INamedTypeSymbol symbol) // cascade to destructor Add(result, symbol.GetMembers(WellKnownMemberNames.DestructorName)); - return Task.FromResult(result.SelectAsArray(s => (s, cascadeDirection))); + return Task.FromResult(result.ToImmutable()); } private static void Add(ArrayBuilder result, ImmutableArray enumerable) where TSymbol : ISymbol @@ -83,7 +81,7 @@ protected override async ValueTask> FindReference CancellationToken cancellationToken) { var nonAliasReferences = await FindNonAliasReferencesAsync(namedType, document, semanticModel, cancellationToken).ConfigureAwait(false); - var symbolsMatch = GetStandardSymbolsMatchFunction(namedType, null, document.Project.Solution, cancellationToken); + var symbolsMatch = GetStandardSymbolsMatchFunction(namedType, findParentNode: null, document.Project.Solution, cancellationToken); var aliasReferences = await FindAliasReferencesAsync(nonAliasReferences, document, semanticModel, symbolsMatch, cancellationToken).ConfigureAwait(false); var suppressionReferences = await FindReferencesInDocumentInsideGlobalSuppressionsAsync(document, semanticModel, namedType, cancellationToken).ConfigureAwait(false); return nonAliasReferences.Concat(aliasReferences, suppressionReferences); @@ -143,7 +141,7 @@ private static ValueTask> FindAttributeReferences SemanticModel semanticModel, CancellationToken cancellationToken) { - var symbolsMatch = GetStandardSymbolsMatchFunction(namedType, null, document.Project.Solution, cancellationToken); + var symbolsMatch = GetStandardSymbolsMatchFunction(namedType, findParentNode: null, document.Project.Solution, cancellationToken); var syntaxFacts = document.GetRequiredLanguageService(); return TryGetNameWithoutAttributeSuffix(namedType.Name, syntaxFacts, out var simpleName) ? FindReferencesInDocumentUsingIdentifierAsync(namedType, simpleName, document, semanticModel, symbolsMatch, cancellationToken) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs index f4c5df42ff47b..b90757014fbf3 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs @@ -48,7 +48,8 @@ protected override async ValueTask> FindReference var tokens = await GetIdentifierOrGlobalNamespaceTokensWithTextAsync( document, semanticModel, identifierName, cancellationToken).ConfigureAwait(false); - var nonAliasReferences = await FindReferencesInTokensAsync(symbol, + var nonAliasReferences = await FindReferencesInTokensAsync( + symbol, document, semanticModel, tokens, diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs index 277c7e024977d..bd6ee9ad9ba4b 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs @@ -20,44 +20,31 @@ protected override bool CanFind(IMethodSymbol symbol) symbol.MethodKind == MethodKind.LocalFunction; } - protected override async Task> DetermineCascadedSymbolsAsync( + protected override Task> DetermineCascadedSymbolsAsync( IMethodSymbol symbol, Solution solution, - IImmutableSet? projects, FindReferencesSearchOptions options, - FindReferencesCascadeDirection cascadeDirection, CancellationToken cancellationToken) { // If it's a delegate method, then cascade to the type as well. These guys are // practically equivalent for users. if (symbol.ContainingType.TypeKind == TypeKind.Delegate) { - return ImmutableArray.Create(((ISymbol)symbol.ContainingType, cascadeDirection)); + return Task.FromResult(ImmutableArray.Create(symbol.ContainingType)); } else { - var otherPartsOfPartial = GetOtherPartsOfPartial(symbol); - var baseCascadedSymbols = await base.DetermineCascadedSymbolsAsync( - symbol, solution, projects, options, cascadeDirection, cancellationToken).ConfigureAwait(false); - - if (otherPartsOfPartial == null && baseCascadedSymbols == null) - return ImmutableArray<(ISymbol symbol, FindReferencesCascadeDirection cascadeDirection)>.Empty; - - return otherPartsOfPartial.SelectAsArray(m => (m, cascadeDirection)).Concat(baseCascadedSymbols); + return Task.FromResult(GetOtherPartsOfPartial(symbol)); } } private static ImmutableArray GetOtherPartsOfPartial(IMethodSymbol symbol) { if (symbol.PartialDefinitionPart != null) - { return ImmutableArray.Create(symbol.PartialDefinitionPart); - } if (symbol.PartialImplementationPart != null) - { return ImmutableArray.Create(symbol.PartialImplementationPart); - } return ImmutableArray.Empty; } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs index 2c3b49348348c..4b1427dfccc36 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs @@ -41,8 +41,7 @@ protected override ValueTask> FindReferencesInDoc FindReferencesSearchOptions options, CancellationToken cancellationToken) { - var symbolsMatchAsync = GetParameterSymbolsMatchFunction( - symbol, document.Project.Solution, cancellationToken); + var symbolsMatchAsync = GetParameterSymbolsMatchFunction(symbol, document.Project.Solution, cancellationToken); return FindReferencesInDocumentUsingIdentifierAsync( symbol, symbol.Name, document, semanticModel, symbolsMatchAsync, cancellationToken); @@ -53,8 +52,7 @@ protected override ValueTask> FindReferencesInDoc { // Get the standard function for comparing parameters. This function will just // directly compare the parameter symbols for SymbolEquivalence. - var standardFunction = GetStandardSymbolsMatchFunction( - parameter, findParentNode: null, solution: solution, cancellationToken: cancellationToken); + var standardFunction = GetStandardSymbolsMatchFunction(parameter, findParentNode: null, solution, cancellationToken); // HOwever, we also want to consider parameter symbols them same if they unify across // VB's synthesized AnonymousDelegate parameters. @@ -77,8 +75,7 @@ protected override ValueTask> FindReferencesInDoc // anonymous-delegate's invoke method. So get he symbol match function that will chec // for equivalence with that parameter. var anonymousDelegateParameter = invokeMethod.Parameters[ordinal]; - var anonParameterFunc = GetStandardSymbolsMatchFunction( - anonymousDelegateParameter, findParentNode: null, solution: solution, cancellationToken: cancellationToken); + var anonParameterFunc = GetStandardSymbolsMatchFunction(anonymousDelegateParameter, findParentNode: null, solution, cancellationToken); // Return a new function which is a compound of the two functions we have. return async (token, model) => @@ -95,17 +92,15 @@ protected override ValueTask> FindReferencesInDoc }; } - protected override async Task> DetermineCascadedSymbolsAsync( + protected override async Task> DetermineCascadedSymbolsAsync( IParameterSymbol parameter, Solution solution, - IImmutableSet? projects, FindReferencesSearchOptions options, - FindReferencesCascadeDirection cascadeDirection, CancellationToken cancellationToken) { if (parameter.IsThis) { - return ImmutableArray<(ISymbol symbol, FindReferencesCascadeDirection cascadeDirection)>.Empty; + return ImmutableArray.Empty; } using var _1 = ArrayBuilder.GetInstance(out var symbols); @@ -115,11 +110,7 @@ protected override ValueTask> FindReferencesInDoc CascadeBetweenDelegateMethodParameters(parameter, symbols); CascadeBetweenPartialMethodParameters(parameter, symbols); - using var _2 = ArrayBuilder<(ISymbol symbol, FindReferencesCascadeDirection cascadeDirection)>.GetInstance(symbols.Count, out var result); - foreach (var symbol in symbols) - result.Add((symbol, cascadeDirection)); - - return result.ToImmutable(); + return symbols.ToImmutable(); } private static async Task CascadeBetweenAnonymousFunctionParametersAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs index 7c6cf9cdc3a13..0094eaa3cb5cf 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders { @@ -15,23 +16,17 @@ internal class PropertyAccessorSymbolReferenceFinder : AbstractMethodOrPropertyO protected override bool CanFind(IMethodSymbol symbol) => symbol.MethodKind.IsPropertyAccessor(); - protected override async Task> DetermineCascadedSymbolsAsync( + protected override Task> DetermineCascadedSymbolsAsync( IMethodSymbol symbol, Solution solution, - IImmutableSet? projects, FindReferencesSearchOptions options, - FindReferencesCascadeDirection cascadeDirection, CancellationToken cancellationToken) { - var result = await base.DetermineCascadedSymbolsAsync( - symbol, solution, projects, options, cascadeDirection, cancellationToken).ConfigureAwait(false); - // If we've been asked to search for specific accessors, then do not cascade. // We don't want to produce results for the associated property. - if (!options.AssociatePropertyReferencesWithSpecificAccessor && symbol.AssociatedSymbol != null) - result = result.Add((symbol.AssociatedSymbol, cascadeDirection)); - - return result; + return options.AssociatePropertyReferencesWithSpecificAccessor || symbol.AssociatedSymbol == null + ? SpecializedTasks.EmptyImmutableArray() + : Task.FromResult(ImmutableArray.Create(symbol.AssociatedSymbol)); } protected override async Task> DetermineDocumentsToSearchAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs index 63f3f140c535a..287656f5f12b8 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs @@ -22,32 +22,26 @@ internal class PropertySymbolReferenceFinder : AbstractMethodOrPropertyOrEventSy protected override bool CanFind(IPropertySymbol symbol) => true; - protected override async Task> DetermineCascadedSymbolsAsync( + protected override Task> DetermineCascadedSymbolsAsync( IPropertySymbol symbol, Solution solution, - IImmutableSet? projects, FindReferencesSearchOptions options, - FindReferencesCascadeDirection cascadeDirection, CancellationToken cancellationToken) { - var baseSymbols = await base.DetermineCascadedSymbolsAsync( - symbol, solution, projects, options, cascadeDirection, cancellationToken).ConfigureAwait(false); - var backingFields = symbol.ContainingType.GetMembers() .OfType() .Where(f => symbol.Equals(f.AssociatedSymbol)) - .Select(f => ((ISymbol)f, cascadeDirection)) - .ToImmutableArray(); + .ToImmutableArray(); - var result = baseSymbols.Concat(backingFields); + var result = backingFields; if (symbol.GetMethod != null) - result = result.Add((symbol.GetMethod, cascadeDirection)); + result = result.Add(symbol.GetMethod); if (symbol.SetMethod != null) - result = result.Add((symbol.SetMethod, cascadeDirection)); + result = result.Add(symbol.SetMethod); - return result; + return Task.FromResult(result); } protected override async Task> DetermineDocumentsToSearchAsync( @@ -153,7 +147,7 @@ private static async Task> FindIndexerReferencesA { cancellationToken.ThrowIfCancellationRequested(); - (var matched, var candidateReason, var indexerReference) = await ComputeIndexerInformationAsync( + var (matched, candidateReason, indexerReference) = await ComputeIndexerInformationAsync( symbol, document, semanticModel, node, cancellationToken).ConfigureAwait(false); if (!matched) continue; @@ -209,8 +203,7 @@ private static async Task> FindIndexerReferencesA } private static async ValueTask<(bool matched, CandidateReason reason, SyntaxNode indexerReference)> ComputeConditionalAccessInformationAsync( - SemanticModel semanticModel, SyntaxNode node, - ISyntaxFactsService syntaxFacts, Func> symbolsMatchAsync) + SemanticModel semanticModel, SyntaxNode node, ISyntaxFactsService syntaxFacts, SymbolsMatchAsync symbolsMatchAsync) { // For a ConditionalAccessExpression the whenNotNull component is the indexer reference we are looking for syntaxFacts.GetPartsOfConditionalAccessExpression(node, out _, out var indexerReference); diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Helpers.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Helpers.cs index ef5123ffb4935..9075f9556ff5f 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Helpers.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Helpers.cs @@ -42,6 +42,12 @@ internal static async Task OriginalSymbolsMatchAsync( if (searchSymbol == null || symbolToMatch == null) return false; + // Avoid the expensive checks if we can fast path when the compiler just says these are equal. Also, for the + // purposes of symbol finding nullability of symbols doesn't affect things, so just use the default + // comparison. + if (searchSymbol.Equals(symbolToMatch)) + return true; + if (await OriginalSymbolsMatchCoreAsync(solution, searchSymbol, symbolToMatch, cancellationToken).ConfigureAwait(false)) return true; @@ -72,13 +78,17 @@ private static async Task OriginalSymbolsMatchCoreAsync( CancellationToken cancellationToken) { if (searchSymbol == null || symbolToMatch == null) - { return false; - } searchSymbol = searchSymbol.GetOriginalUnreducedDefinition(); symbolToMatch = symbolToMatch.GetOriginalUnreducedDefinition(); + // Avoid the expensive checks if we can fast path when the compiler just says these are equal. Also, for the + // purposes of symbol finding nullability of symbols doesn't affect things, so just use the default + // comparison. + if (searchSymbol.Equals(symbolToMatch, SymbolEqualityComparer.Default)) + return true; + // We compare the given searchSymbol and symbolToMatch for equivalence using SymbolEquivalenceComparer // as follows: // 1) We compare the given symbols using the SymbolEquivalenceComparer.IgnoreAssembliesInstance, diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Hierarchy.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Hierarchy.cs index e9e2640818a1e..8db349f2014aa 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Hierarchy.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Hierarchy.cs @@ -86,12 +86,18 @@ public static async Task> FindImplementedInterfaceMembersAs return await FindImplementedInterfaceMembersArrayAsync(symbol, solution, projects, cancellationToken).ConfigureAwait(false); } + internal static Task> FindImplementedInterfaceMembersArrayAsync( + ISymbol symbol, Solution solution, CancellationToken cancellationToken) + { + return FindImplementedInterfaceMembersArrayAsync(symbol, solution, projects: null, cancellationToken); + } + /// /// /// Use this overload to avoid boxing the result into an . /// internal static async Task> FindImplementedInterfaceMembersArrayAsync( - ISymbol symbol, Solution solution, IImmutableSet projects = null, CancellationToken cancellationToken = default) + ISymbol symbol, Solution solution, IImmutableSet projects, CancellationToken cancellationToken) { // Member can only implement interface members if it is an explicit member, or if it is // public diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs index 63e17623c5e5a..f49884c398117 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs @@ -129,7 +129,7 @@ private static Project CreateProject(ProjectId projectId, Solution solution) /// /// Given a returns the of the it came - /// from. Returns if does not come from . + /// from. Returns if does not come from any project in this solution. /// /// /// This function differs from in terms of how it diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/StackExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/StackExtensions.cs index d35916489069c..0120615e39add 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/StackExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/StackExtensions.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 Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Shared.Extensions @@ -12,9 +13,19 @@ internal static class StackExtensions public static void Push(this Stack stack, IEnumerable values) { foreach (var v in values) - { stack.Push(v); - } + } + + public static void Push(this Stack stack, HashSet values) + { + foreach (var v in values) + stack.Push(v); + } + + public static void Push(this Stack stack, ImmutableArray values) + { + foreach (var v in values) + stack.Push(v); } internal static void PushReverse(this Stack stack, IList range) diff --git a/src/Workspaces/VisualBasic/Portable/FindSymbols/VisualBasicReferenceFinder.vb b/src/Workspaces/VisualBasic/Portable/FindSymbols/VisualBasicReferenceFinder.vb index eaf724e56be67..37bf617330aca 100644 --- a/src/Workspaces/VisualBasic/Portable/FindSymbols/VisualBasicReferenceFinder.vb +++ b/src/Workspaces/VisualBasic/Portable/FindSymbols/VisualBasicReferenceFinder.vb @@ -22,36 +22,33 @@ Namespace Microsoft.CodeAnalysis.FindSymbols Public Function DetermineCascadedSymbolsAsync( symbol As ISymbol, project As Project, - cascadeDirection As FindReferencesCascadeDirection, - cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of (symbol As ISymbol, cascadeDirection As FindReferencesCascadeDirection))) Implements ILanguageServiceReferenceFinder.DetermineCascadedSymbolsAsync + cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of ISymbol)) Implements ILanguageServiceReferenceFinder.DetermineCascadedSymbolsAsync If symbol.Kind = SymbolKind.Property Then - Return DetermineCascadedSymbolsAsync(DirectCast(symbol, IPropertySymbol), project, cascadeDirection, cancellationToken) + Return DetermineCascadedSymbolsAsync(DirectCast(symbol, IPropertySymbol), project, cancellationToken) ElseIf symbol.Kind = SymbolKind.NamedType Then - Return DetermineCascadedSymbolsAsync(DirectCast(symbol, INamedTypeSymbol), project, cascadeDirection, cancellationToken) + Return DetermineCascadedSymbolsAsync(DirectCast(symbol, INamedTypeSymbol), project, cancellationToken) Else - Return SpecializedTasks.EmptyImmutableArray(Of (symbol As ISymbol, cascadeDirection As FindReferencesCascadeDirection))() + Return SpecializedTasks.EmptyImmutableArray(Of ISymbol) End If End Function Private Shared Async Function DetermineCascadedSymbolsAsync( [property] As IPropertySymbol, project As Project, - cascadeDirection As FindReferencesCascadeDirection, - cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of (symbol As ISymbol, cascadeDirection As FindReferencesCascadeDirection))) + cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of ISymbol)) Dim compilation = Await project.GetCompilationAsync(cancellationToken).ConfigureAwait(False) Dim relatedSymbol = [property].FindRelatedExplicitlyDeclaredSymbol(compilation) Return If([property].Equals(relatedSymbol), - ImmutableArray(Of (symbol As ISymbol, cascadeDirection As FindReferencesCascadeDirection)).Empty, - ImmutableArray.Create((relatedSymbol, cascadeDirection))) + ImmutableArray(Of ISymbol).Empty, + ImmutableArray.Create(relatedSymbol)) End Function Private Shared Async Function DetermineCascadedSymbolsAsync( namedType As INamedTypeSymbol, project As Project, - cascadeDirection As FindReferencesCascadeDirection, - cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of (symbol As ISymbol, cascadeDirection As FindReferencesCascadeDirection))) + cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of ISymbol)) Dim compilation = Await project.GetCompilationAsync(cancellationToken).ConfigureAwait(False) @@ -66,7 +63,7 @@ Namespace Microsoft.CodeAnalysis.FindSymbols Where type.Name = "MyForms" From childProperty In type.GetMembers().OfType(Of IPropertySymbol) Where childProperty.IsImplicitlyDeclared AndAlso childProperty.Type.Equals(namedType) - Select (DirectCast(childProperty, ISymbol), cascadeDirection) + Select DirectCast(childProperty, ISymbol) Return matchingMyPropertySymbols.Distinct().ToImmutableArray() End Function