File: AddImport\SymbolReferenceFinder.cs
Web Access
Project: src\src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj (Microsoft.CodeAnalysis.Features)
// 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.
 
#nullable disable
 
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Packaging;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.SymbolSearch;
using Roslyn.Utilities;
using static Microsoft.CodeAnalysis.Shared.Utilities.EditorBrowsableHelpers;
 
namespace Microsoft.CodeAnalysis.AddImport;
 
internal abstract partial class AbstractAddImportFeatureService<TSimpleNameSyntax>
{
    private sealed partial class SymbolReferenceFinder
    {
        private const string AttributeSuffix = nameof(Attribute);
 
        private readonly string _diagnosticId;
        private readonly Document _document;
        private readonly SemanticModel _semanticModel;
 
        private readonly ISet<INamespaceSymbol> _namespacesInScope;
        private readonly ISyntaxFactsService _syntaxFacts;
        private readonly AbstractAddImportFeatureService<TSimpleNameSyntax> _owner;
 
        private readonly SyntaxNode _node;
        private readonly ISymbolSearchService _symbolSearchService;
        private readonly AddImportOptions _options;
        private readonly ImmutableArray<PackageSource> _packageSources;
 
        /// <summary>
        /// If the search is being conducted inside of a `using/import` directive.
        /// </summary>
        private readonly bool _isWithinImport;
 
        public SymbolReferenceFinder(
            AbstractAddImportFeatureService<TSimpleNameSyntax> owner,
            Document document,
            SemanticModel semanticModel,
            string diagnosticId,
            SyntaxNode node,
            ISymbolSearchService symbolSearchService,
            AddImportOptions options,
            ImmutableArray<PackageSource> packageSources,
            CancellationToken cancellationToken)
        {
            _owner = owner;
            _document = document;
            _semanticModel = semanticModel;
            _diagnosticId = diagnosticId;
            _node = node;
 
            _symbolSearchService = symbolSearchService;
            _options = options;
            _packageSources = packageSources;
            _syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
 
            _namespacesInScope = GetNamespacesInScope(cancellationToken);
            _isWithinImport = owner.IsWithinImport(node);
        }
 
        private ISet<INamespaceSymbol> GetNamespacesInScope(CancellationToken cancellationToken)
        {
            // Add all the namespaces brought in by imports/usings.
            var set = _owner.GetImportNamespacesInScope(_semanticModel, _node, cancellationToken);
 
            // Also add all the namespaces we're contained in.  We don't want
            // to add imports for these namespaces either.
            for (var containingNamespace = _semanticModel.GetEnclosingNamespace(_node.SpanStart, cancellationToken);
                 containingNamespace != null;
                 containingNamespace = containingNamespace.ContainingNamespace)
            {
                set.Add(MapToCompilationNamespaceIfPossible(containingNamespace));
            }
 
            return set;
        }
 
        private INamespaceSymbol MapToCompilationNamespaceIfPossible(INamespaceSymbol containingNamespace)
            => _semanticModel.Compilation.GetCompilationNamespace(containingNamespace) ?? containingNamespace;
 
        internal Task<ImmutableArray<SymbolReference>> FindInAllSymbolsInStartingProjectAsync(bool exact, CancellationToken cancellationToken)
            => DoAsync(new AllSymbolsProjectSearchScope(_owner, _document.Project, exact), cancellationToken);
 
        internal Task<ImmutableArray<SymbolReference>> FindInSourceSymbolsInProjectAsync(ConcurrentDictionary<Project, AsyncLazy<IAssemblySymbol>> projectToAssembly, Project project, bool exact, CancellationToken cancellationToken)
            => DoAsync(new SourceSymbolsProjectSearchScope(_owner, projectToAssembly, project, exact), cancellationToken);
 
        internal Task<ImmutableArray<SymbolReference>> FindInMetadataSymbolsAsync(IAssemblySymbol assembly, Project assemblyProject, PortableExecutableReference metadataReference, bool exact, CancellationToken cancellationToken)
            => DoAsync(new MetadataSymbolsSearchScope(_owner, assemblyProject, assembly, metadataReference, exact), cancellationToken);
 
        private async Task<ImmutableArray<SymbolReference>> DoAsync(SearchScope searchScope, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
 
            // Within a using/import directive, we don't want to offer to add other imports.  We only want to offer to
            // add nuget packages.  So bail out immediately on all symbolic searches.  The only searches we aactually
            // perform are done in FindNugetOrReferenceAssemblyReferencesAsync.
            if (_isWithinImport)
                return [];
 
            // Spin off tasks to do all our searching in parallel
            using var _1 = ArrayBuilder<Task<ImmutableArray<SymbolReference>>>.GetInstance(out var tasks);
            tasks.Add(GetReferencesForMatchingTypesAsync(searchScope, cancellationToken));
            tasks.Add(GetReferencesForMatchingNamespacesAsync(searchScope, cancellationToken));
            tasks.Add(GetReferencesForMatchingFieldsAndPropertiesAsync(searchScope, cancellationToken));
            tasks.Add(GetReferencesForMatchingExtensionMethodsAsync(searchScope, cancellationToken));
 
            // Searching for things like "Add" (for collection initializers) and "Select"
            // (for extension methods) should only be done when doing an 'exact' search.
            // We should not do fuzzy searches for these names.  In this case it's not
            // like the user was writing Add or Select, but instead we're looking for
            // viable symbols with those names to make a collection initializer or 
            // query expression valid.
            if (searchScope.Exact)
            {
                tasks.Add(GetReferencesForCollectionInitializerMethodsAsync(searchScope, cancellationToken));
                tasks.Add(GetReferencesForQueryPatternsAsync(searchScope, cancellationToken));
                tasks.Add(GetReferencesForDeconstructAsync(searchScope, cancellationToken));
                tasks.Add(GetReferencesForGetAwaiterAsync(searchScope, cancellationToken));
                tasks.Add(GetReferencesForGetEnumeratorAsync(searchScope, cancellationToken));
                tasks.Add(GetReferencesForGetAsyncEnumeratorAsync(searchScope, cancellationToken));
            }
 
            await Task.WhenAll(tasks).ConfigureAwait(false);
            cancellationToken.ThrowIfCancellationRequested();
 
            using var _2 = ArrayBuilder<SymbolReference>.GetInstance(out var allReferences);
            foreach (var task in tasks)
            {
                var taskResult = await task.ConfigureAwait(false);
                allReferences.AddRange(taskResult);
            }
 
            return DeDupeAndSortReferences(allReferences.ToImmutable());
        }
 
        private ImmutableArray<SymbolReference> DeDupeAndSortReferences(ImmutableArray<SymbolReference> allReferences)
        {
            return [.. allReferences
                .Distinct()
                .Where(NotNull)
                .Where(NotGlobalNamespace)
                .OrderBy((r1, r2) => r1.CompareTo(_document, r2))];
        }
 
        private static void CalculateContext(
            TSimpleNameSyntax nameNode, ISyntaxFactsService syntaxFacts, out string name, out int arity,
            out bool inAttributeContext, out bool hasIncompleteParentMember, out bool looksGeneric)
        {
            // Has to be a simple identifier or generic name.
            syntaxFacts.GetNameAndArityOfSimpleName(nameNode, out name, out arity);
 
            inAttributeContext = syntaxFacts.IsNameOfAttribute(nameNode);
            hasIncompleteParentMember = nameNode?.Parent?.RawKind == syntaxFacts.SyntaxKinds.IncompleteMember;
            looksGeneric = syntaxFacts.LooksGeneric(nameNode);
        }
 
        /// <summary>
        /// Searches for types that match the name the user has written.  Returns <see cref="SymbolReference"/>s
        /// to the <see cref="INamespaceSymbol"/>s or <see cref="INamedTypeSymbol"/>s those types are
        /// contained in.
        /// </summary>
        private async Task<ImmutableArray<SymbolReference>> GetReferencesForMatchingTypesAsync(
            SearchScope searchScope, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            if (!_owner.CanAddImportForTypeOrNamespace(_diagnosticId, _node, out var nameNode))
                return [];
 
            CalculateContext(
                nameNode, _syntaxFacts,
                out var name, out var arity, out var inAttributeContext,
                out var hasIncompleteParentMember, out var looksGeneric);
 
            if (ExpressionBinds(nameNode, checkForExtensionMethods: false, cancellationToken: cancellationToken))
            {
                // If the expression bound, there's nothing to do.
                return [];
            }
 
            var symbols = await searchScope.FindDeclarationsAsync(name, nameNode, SymbolFilter.Type, cancellationToken).ConfigureAwait(false);
 
            // also lookup type symbols with the "Attribute" suffix if necessary.
            if (inAttributeContext)
            {
                var attributeSymbols = await searchScope.FindDeclarationsAsync(
                    name + AttributeSuffix, nameNode, SymbolFilter.Type, cancellationToken).ConfigureAwait(false);
 
                symbols = symbols.AddRange(
                    attributeSymbols.Select(r => r.WithDesiredName(r.DesiredName.GetWithoutAttributeSuffix(isCaseSensitive: false))));
            }
 
            var typeSymbols = OfType<ITypeSymbol>(symbols);
 
            var editorBrowserInfo = new EditorBrowsableInfo(_semanticModel.Compilation);
 
            // Only keep symbols which are accessible from the current location and that are allowed by the current
            // editor browsable rules.
            var accessibleTypeSymbols = typeSymbols.WhereAsArray(
                s => ArityAccessibilityAndAttributeContextAreCorrect(s.Symbol, arity, inAttributeContext, hasIncompleteParentMember, looksGeneric) &&
                     s.Symbol.IsEditorBrowsable(_options.MemberDisplayOptions.HideAdvancedMembers, _semanticModel.Compilation, editorBrowserInfo));
 
            // These types may be contained within namespaces, or they may be nested 
            // inside generic types.  Record these namespaces/types if it would be 
            // legal to add imports for them.
 
            var typesContainedDirectlyInNamespaces = accessibleTypeSymbols.WhereAsArray(s => s.Symbol.ContainingSymbol is INamespaceSymbol);
            var typesContainedDirectlyInTypes = accessibleTypeSymbols.WhereAsArray(s => s.Symbol.ContainingType != null);
 
            var namespaceReferences = GetNamespaceSymbolReferences(searchScope,
                typesContainedDirectlyInNamespaces.SelectAsArray(r => r.WithSymbol(r.Symbol.ContainingNamespace)));
 
            var typeReferences = typesContainedDirectlyInTypes.SelectAsArray(
                r => searchScope.CreateReference(r.WithSymbol(r.Symbol.ContainingType)));
 
            return namespaceReferences.Concat(typeReferences);
        }
 
        private bool ArityAccessibilityAndAttributeContextAreCorrect(
            ITypeSymbol symbol,
            int arity,
            bool inAttributeContext,
            bool hasIncompleteParentMember,
            bool looksGeneric)
        {
            if (inAttributeContext && !symbol.IsAttribute())
            {
                return false;
            }
 
            if (!symbol.IsAccessibleWithin(_semanticModel.Compilation.Assembly))
            {
                return false;
            }
 
            if (looksGeneric && symbol.GetTypeArguments().Length == 0)
            {
                return false;
            }
 
            return arity == 0 || symbol.GetArity() == arity || hasIncompleteParentMember;
        }
 
        /// <summary>
        /// Searches for namespaces that match the name the user has written.  Returns <see cref="SymbolReference"/>s
        /// to the <see cref="INamespaceSymbol"/>s those namespaces are contained in.
        /// </summary>
        private async Task<ImmutableArray<SymbolReference>> GetReferencesForMatchingNamespacesAsync(
            SearchScope searchScope, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            if (_owner.CanAddImportForNamespace(_diagnosticId, _node, out var nameNode))
            {
                _syntaxFacts.GetNameAndArityOfSimpleName(nameNode, out var name, out var arity);
 
                if (arity == 0 &&
                    !ExpressionBinds(nameNode, checkForExtensionMethods: false, cancellationToken))
                {
                    var symbols = await searchScope.FindDeclarationsAsync(name, nameNode, SymbolFilter.Namespace, cancellationToken).ConfigureAwait(false);
                    var namespaceSymbols = OfType<INamespaceSymbol>(symbols);
                    var containingNamespaceSymbols = OfType<INamespaceSymbol>(symbols).SelectAsArray(s => s.WithSymbol(s.Symbol.ContainingNamespace));
 
                    return GetNamespaceSymbolReferences(searchScope, containingNamespaceSymbols);
                }
            }
 
            return [];
        }
 
        /// <summary>
        /// Specialized finder for the "Color Color" case.  Used when we have "Color.Black" and "Color"
        /// bound to a Field/Property, but not a type.  In this case, we want to look for namespaces
        /// containing 'Color' as if we import them it can resolve this issue.
        /// </summary>
        private async Task<ImmutableArray<SymbolReference>> GetReferencesForMatchingFieldsAndPropertiesAsync(
            SearchScope searchScope, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            if (_owner.CanAddImportForMethod(_diagnosticId, _syntaxFacts, _node, out var nameNode) &&
                nameNode != null)
            {
                // We have code like "Color.Black".  "Color" bound to a 'Color Color' property, and
                // 'Black' did not bind.  We want to find a type called 'Color' that will actually
                // allow 'Black' to bind.
                var syntaxFacts = _document.GetLanguageService<ISyntaxFactsService>();
                if (syntaxFacts.IsNameOfSimpleMemberAccessExpression(nameNode) ||
                    syntaxFacts.IsNameOfMemberBindingExpression(nameNode))
                {
                    var expression = syntaxFacts.IsNameOfSimpleMemberAccessExpression(nameNode)
                        ? syntaxFacts.GetExpressionOfMemberAccessExpression(nameNode.Parent, allowImplicitTarget: true)
                        : syntaxFacts.GetTargetOfMemberBinding(nameNode.Parent);
                    if (expression is TSimpleNameSyntax simpleName)
                    {
                        // Check if the expression before the dot binds to a property or field.
                        var symbol = _semanticModel.GetSymbolInfo(expression, cancellationToken).GetAnySymbol();
                        if (symbol?.Kind is SymbolKind.Property or SymbolKind.Field)
                        {
                            // Check if we have the 'Color Color' case.
                            var propertyOrFieldType = symbol.GetSymbolType();
                            if (propertyOrFieldType is INamedTypeSymbol propertyType &&
                                Equals(propertyType.Name, symbol.Name))
                            {
                                // Try to look up 'Color' as a type.
                                var symbolResults = await searchScope.FindDeclarationsAsync(
                                    symbol.Name, simpleName, SymbolFilter.Type, cancellationToken).ConfigureAwait(false);
 
                                // Return results that have accessible members.
                                var namedTypeSymbols = OfType<INamedTypeSymbol>(symbolResults);
                                var name = nameNode.GetFirstToken().ValueText;
                                var namespaceResults =
                                    namedTypeSymbols.WhereAsArray(sr => HasAccessibleStaticFieldOrProperty(sr.Symbol, name))
                                                    .SelectAsArray(sr => sr.WithSymbol(sr.Symbol.ContainingNamespace));
 
                                return GetNamespaceSymbolReferences(searchScope, namespaceResults);
                            }
                        }
                    }
                }
            }
 
            return [];
        }
 
        private bool HasAccessibleStaticFieldOrProperty(INamedTypeSymbol namedType, string fieldOrPropertyName)
        {
            return namedType.GetMembers(fieldOrPropertyName)
                            .Any(static (m, self) => (m is IFieldSymbol || m is IPropertySymbol) &&
                                      m.IsStatic &&
                                      m.IsAccessibleWithin(self._semanticModel.Compilation.Assembly), this);
        }
 
        /// <summary>
        /// Searches for extension methods that match the name the user has written.  Returns
        /// <see cref="SymbolReference"/>s to the <see cref="INamespaceSymbol"/>s that contain
        /// the static classes that those extension methods are contained in.
        /// </summary>
        private async Task<ImmutableArray<SymbolReference>> GetReferencesForMatchingExtensionMethodsAsync(
            SearchScope searchScope, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            if (_owner.CanAddImportForMethod(_diagnosticId, _syntaxFacts, _node, out var nameNode) &&
                nameNode != null)
            {
                cancellationToken.ThrowIfCancellationRequested();
 
                // See if the name binds.  If it does, there's nothing further we need to do.
                if (!ExpressionBinds(nameNode, checkForExtensionMethods: true, cancellationToken))
                {
                    _syntaxFacts.GetNameAndArityOfSimpleName(nameNode, out var name, out var arity);
                    if (name != null)
                    {
                        var symbols = await searchScope.FindDeclarationsAsync(name, nameNode, SymbolFilter.Member, cancellationToken).ConfigureAwait(false);
 
                        var methodSymbols = OfType<IMethodSymbol>(symbols);
 
                        var extensionMethodSymbols = GetViableExtensionMethods(
                            methodSymbols, nameNode.Parent, cancellationToken);
 
                        var namespaceSymbols = extensionMethodSymbols.SelectAsArray(s => s.WithSymbol(s.Symbol.ContainingNamespace));
                        return GetNamespaceSymbolReferences(searchScope, namespaceSymbols);
                    }
                }
            }
 
            return [];
        }
 
        private ImmutableArray<SymbolResult<IMethodSymbol>> GetViableExtensionMethods(
            ImmutableArray<SymbolResult<IMethodSymbol>> methodSymbols,
            SyntaxNode expression, CancellationToken cancellationToken)
        {
            return GetViableExtensionMethodsWorker(methodSymbols).WhereAsArray(
                s => _owner.IsViableExtensionMethod(s.Symbol, expression, _semanticModel, _syntaxFacts, cancellationToken));
        }
 
        private ImmutableArray<SymbolResult<IMethodSymbol>> GetViableExtensionMethods(
            ImmutableArray<SymbolResult<IMethodSymbol>> methodSymbols, ITypeSymbol typeSymbol)
        {
            return GetViableExtensionMethodsWorker(methodSymbols).WhereAsArray(
                s => IsViableExtensionMethod(s.Symbol, typeSymbol));
        }
 
        private ImmutableArray<SymbolResult<IMethodSymbol>> GetViableExtensionMethodsWorker(
            ImmutableArray<SymbolResult<IMethodSymbol>> methodSymbols)
        {
            return methodSymbols.WhereAsArray(
                s => s.Symbol.IsExtensionMethod &&
                     s.Symbol.IsAccessibleWithin(_semanticModel.Compilation.Assembly));
        }
 
        /// <summary>
        /// Searches for extension methods exactly called 'Add'.  Returns
        /// <see cref="SymbolReference"/>s to the <see cref="INamespaceSymbol"/>s that contain
        /// the static classes that those extension methods are contained in.
        /// </summary>
        private async Task<ImmutableArray<SymbolReference>> GetReferencesForCollectionInitializerMethodsAsync(
            SearchScope searchScope, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            if (_owner.CanAddImportForMethod(_diagnosticId, _syntaxFacts, _node, out _) &&
                !_syntaxFacts.IsSimpleName(_node) &&
                _owner.IsAddMethodContext(_node, _semanticModel))
            {
                var symbols = await searchScope.FindDeclarationsAsync(
                    nameof(IList.Add), nameNode: null, filter: SymbolFilter.Member, cancellationToken).ConfigureAwait(false);
 
                // Note: there is no desiredName for these search results.  We're searching for
                // extension methods called "Add", but we have no intention of renaming any 
                // of the existing user code to that name.
                var methodSymbols = OfType<IMethodSymbol>(symbols).SelectAsArray(s => s.WithDesiredName(null));
 
                var viableMethods = GetViableExtensionMethods(
                    methodSymbols, _node.Parent, cancellationToken);
 
                return GetNamespaceSymbolReferences(searchScope,
                    viableMethods.SelectAsArray(m => m.WithSymbol(m.Symbol.ContainingNamespace)));
            }
 
            return [];
        }
 
        /// <summary>
        /// Searches for extension methods exactly called 'Select'.  Returns
        /// <see cref="SymbolReference"/>s to the <see cref="INamespaceSymbol"/>s that contain
        /// the static classes that those extension methods are contained in.
        /// </summary>
        private async Task<ImmutableArray<SymbolReference>> GetReferencesForQueryPatternsAsync(
            SearchScope searchScope, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
 
            if (_owner.CanAddImportForQuery(_diagnosticId, _node))
            {
                var type = _owner.GetQueryClauseInfo(_semanticModel, _node, cancellationToken);
                if (type != null)
                {
                    // find extension methods named "Select"
                    return await GetReferencesForExtensionMethodAsync(
                        searchScope, nameof(Enumerable.Select), type, predicate: null, cancellationToken).ConfigureAwait(false);
                }
            }
 
            return [];
        }
 
        /// <summary>
        /// Searches for extension methods exactly called 'GetAwaiter'.  Returns
        /// <see cref="SymbolReference"/>s to the <see cref="INamespaceSymbol"/>s that contain
        /// the static classes that those extension methods are contained in.
        /// </summary>
        private async Task<ImmutableArray<SymbolReference>> GetReferencesForGetAwaiterAsync(
            SearchScope searchScope, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
 
            if (_owner.CanAddImportForGetAwaiter(_diagnosticId, _syntaxFacts, _node))
            {
                var type = GetAwaitInfo(_semanticModel, _syntaxFacts, _node);
                if (type != null)
                {
                    return await GetReferencesForExtensionMethodAsync(
                        searchScope, WellKnownMemberNames.GetAwaiter, type,
                        static m => m.IsValidGetAwaiter(),
                        cancellationToken).ConfigureAwait(false);
                }
            }
 
            return [];
        }
 
        /// <summary>
        /// Searches for extension methods exactly called 'GetEnumerator'.  Returns
        /// <see cref="SymbolReference"/>s to the <see cref="INamespaceSymbol"/>s that contain
        /// the static classes that those extension methods are contained in.
        /// </summary>
        private async Task<ImmutableArray<SymbolReference>> GetReferencesForGetEnumeratorAsync(
            SearchScope searchScope, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
 
            if (_owner.CanAddImportForGetEnumerator(_diagnosticId, _syntaxFacts, _node))
            {
                var type = GetCollectionExpressionType(_semanticModel, _syntaxFacts, _node);
                if (type != null)
                {
                    return await GetReferencesForExtensionMethodAsync(
                        searchScope, WellKnownMemberNames.GetEnumeratorMethodName, type,
                        static m => m.IsValidGetEnumerator(),
                        cancellationToken).ConfigureAwait(false);
                }
            }
 
            return [];
        }
 
        /// <summary>
        /// Searches for extension methods exactly called 'GetAsyncEnumerator'.  Returns
        /// <see cref="SymbolReference"/>s to the <see cref="INamespaceSymbol"/>s that contain
        /// the static classes that those extension methods are contained in.
        /// </summary>
        private async Task<ImmutableArray<SymbolReference>> GetReferencesForGetAsyncEnumeratorAsync(
            SearchScope searchScope, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
 
            if (_owner.CanAddImportForGetAsyncEnumerator(_diagnosticId, _syntaxFacts, _node))
            {
                var type = GetCollectionExpressionType(_semanticModel, _syntaxFacts, _node);
                if (type != null)
                {
                    return await GetReferencesForExtensionMethodAsync(
                        searchScope, WellKnownMemberNames.GetAsyncEnumeratorMethodName, type,
                        static m => m.IsValidGetAsyncEnumerator(),
                        cancellationToken).ConfigureAwait(false);
                }
            }
 
            return [];
        }
 
        /// <summary>
        /// Searches for extension methods exactly called 'Deconstruct'.  Returns
        /// <see cref="SymbolReference"/>s to the <see cref="INamespaceSymbol"/>s that contain
        /// the static classes that those extension methods are contained in.
        /// </summary>
        private async Task<ImmutableArray<SymbolReference>> GetReferencesForDeconstructAsync(
            SearchScope searchScope, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
 
            if (_owner.CanAddImportForDeconstruct(_diagnosticId, _node))
            {
                var type = _owner.GetDeconstructInfo(_semanticModel, _node, cancellationToken);
                if (type != null)
                {
                    // Note: we could check that the extension methods have the right number of out-params.  
                    // But that would involve figuring out what we're trying to deconstruct into.  For now
                    // we'll just be permissive, with the assumption that there won't be that many matching
                    // 'Deconstruct' extension methods for the type of node that we're on.
                    return await GetReferencesForExtensionMethodAsync(
                        searchScope, "Deconstruct", type, static m => m.ReturnsVoid, cancellationToken).ConfigureAwait(false);
                }
            }
 
            return [];
        }
 
        private async Task<ImmutableArray<SymbolReference>> GetReferencesForExtensionMethodAsync(
            SearchScope searchScope, string name, ITypeSymbol type, Func<IMethodSymbol, bool> predicate, CancellationToken cancellationToken)
        {
            var symbols = await searchScope.FindDeclarationsAsync(
                name, nameNode: null, filter: SymbolFilter.Member, cancellationToken).ConfigureAwait(false);
 
            // Note: there is no "desiredName" when doing this.  We're not going to do any
            // renames of the user code.  We're just looking for an extension method called 
            // "Select", but that name has no bearing on the code in question that we're
            // trying to fix up.
            var methodSymbols = OfType<IMethodSymbol>(symbols).SelectAsArray(s => s.WithDesiredName(null));
            var viableExtensionMethods = GetViableExtensionMethods(methodSymbols, type);
 
            if (predicate != null)
            {
                viableExtensionMethods = viableExtensionMethods.WhereAsArray(s => predicate(s.Symbol));
            }
 
            var namespaceSymbols = viableExtensionMethods.SelectAsArray(s => s.WithSymbol(s.Symbol.ContainingNamespace));
 
            return GetNamespaceSymbolReferences(searchScope, namespaceSymbols);
        }
 
        private bool ExpressionBinds(
            TSimpleNameSyntax nameNode, bool checkForExtensionMethods, CancellationToken cancellationToken)
        {
            // See if the name binds to something other then the error type. If it does, there's nothing further we need to do.
            // For extension methods, however, we will continue to search if there exists any better matched method.
            cancellationToken.ThrowIfCancellationRequested();
            var symbolInfo = _semanticModel.GetSymbolInfo(nameNode, cancellationToken);
            if (symbolInfo.CandidateReason == CandidateReason.OverloadResolutionFailure && !checkForExtensionMethods)
            {
                return true;
            }
 
            return symbolInfo.Symbol != null;
        }
 
        private ImmutableArray<SymbolReference> GetNamespaceSymbolReferences(
            SearchScope scope, ImmutableArray<SymbolResult<INamespaceSymbol>> namespaces)
        {
            using var _ = ArrayBuilder<SymbolReference>.GetInstance(out var references);
 
            foreach (var namespaceResult in namespaces)
            {
                var symbol = namespaceResult.Symbol;
                var mappedResult = namespaceResult.WithSymbol(MapToCompilationNamespaceIfPossible(namespaceResult.Symbol));
                var namespaceIsInScope = _namespacesInScope.Contains(mappedResult.Symbol);
                if (!symbol.IsGlobalNamespace && !namespaceIsInScope)
                    references.Add(scope.CreateReference(mappedResult));
            }
 
            return references.ToImmutableAndClear();
        }
 
        private static ImmutableArray<SymbolResult<T>> OfType<T>(ImmutableArray<SymbolResult<ISymbol>> symbols) where T : ISymbol
        {
            return symbols.WhereAsArray(s => s.Symbol is T)
                          .SelectAsArray(s => s.WithSymbol((T)s.Symbol));
        }
    }
}