File: FindSymbols\FindReferences\Finders\ParameterSymbolReferenceFinder.cs
Web Access
Project: src\src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.Workspaces)
// 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.LanguageService;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.FindSymbols.Finders;
 
internal sealed class ParameterSymbolReferenceFinder : AbstractReferenceFinder<IParameterSymbol>
{
    protected override bool CanFind(IParameterSymbol symbol)
        => true;
 
    protected override Task DetermineDocumentsToSearchAsync<TData>(
        IParameterSymbol symbol,
        HashSet<string>? globalAliases,
        Project project,
        IImmutableSet<Document>? documents,
        Action<Document, TData> processResult,
        TData processResultData,
        FindReferencesSearchOptions options,
        CancellationToken cancellationToken)
    {
        // TODO(cyrusn): We can be smarter with parameters.  They will either be found
        // within the method that they were declared on, or they will referenced
        // elsewhere as "paramName:" or "paramName:=".  We can narrow the search by
        // filtering down to matches of that form.  For now we just return any document
        // that references something with this name.
        return FindDocumentsAsync(project, documents, processResult, processResultData, cancellationToken, symbol.Name);
    }
 
    protected override void FindReferencesInDocument<TData>(
        IParameterSymbol symbol,
        FindReferencesDocumentState state,
        Action<FinderLocation, TData> processResult,
        TData processResultData,
        FindReferencesSearchOptions options,
        CancellationToken cancellationToken)
    {
        FindReferencesInDocumentUsingIdentifier(symbol, symbol.Name, state, processResult, processResultData, cancellationToken);
    }
 
    protected override async ValueTask<ImmutableArray<ISymbol>> DetermineCascadedSymbolsAsync(
        IParameterSymbol parameter,
        Solution solution,
        FindReferencesSearchOptions options,
        CancellationToken cancellationToken)
    {
        if (parameter.IsThis)
            return [];
 
        using var _ = ArrayBuilder<ISymbol>.GetInstance(out var symbols);
 
        await CascadeBetweenAnonymousFunctionParametersAsync(solution, parameter, symbols, cancellationToken).ConfigureAwait(false);
        CascadeBetweenPropertyAndAccessorParameters(parameter, symbols);
        CascadeBetweenDelegateMethodParameters(parameter, symbols);
        CascadeBetweenPartialMethodParameters(parameter, symbols);
        CascadeBetweenPrimaryConstructorParameterAndProperties(parameter, symbols, cancellationToken);
        CascadeBetweenAnonymousDelegateParameters(parameter, symbols);
 
        return symbols.ToImmutableAndClear();
    }
 
    private static void CascadeBetweenAnonymousDelegateParameters(IParameterSymbol parameter, ArrayBuilder<ISymbol> symbols)
    {
        if (parameter.ContainingSymbol is IMethodSymbol { AssociatedAnonymousDelegate.DelegateInvokeMethod: { } invokeMethod } &&
            parameter.Ordinal < invokeMethod.Parameters.Length)
        {
            symbols.Add(invokeMethod.Parameters[parameter.Ordinal]);
        }
    }
 
    private static void CascadeBetweenPrimaryConstructorParameterAndProperties(
        IParameterSymbol parameter, ArrayBuilder<ISymbol> symbols, CancellationToken cancellationToken)
    {
        symbols.AddIfNotNull(parameter.GetAssociatedSynthesizedRecordProperty(cancellationToken));
    }
 
    private static async ValueTask CascadeBetweenAnonymousFunctionParametersAsync(
        Solution solution,
        IParameterSymbol parameter,
        ArrayBuilder<ISymbol> results,
        CancellationToken cancellationToken)
    {
        if (parameter.ContainingSymbol.IsAnonymousFunction())
        {
            var parameterNode = parameter.DeclaringSyntaxReferences.Select(r => r.GetSyntax(cancellationToken)).FirstOrDefault();
            if (parameterNode != null)
            {
                var document = solution.GetDocument(parameterNode.SyntaxTree);
                if (document != null)
                {
                    var semanticFacts = document.GetRequiredLanguageService<ISemanticFactsService>();
                    if (semanticFacts.ExposesAnonymousFunctionParameterNames)
                    {
                        var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
 
                        var lambdaNode = parameter.ContainingSymbol.DeclaringSyntaxReferences.Select(r => r.GetSyntax(cancellationToken)).First();
                        var convertedType = semanticModel.GetTypeInfo(lambdaNode, cancellationToken).ConvertedType;
 
                        if (convertedType != null)
                        {
                            var syntaxFacts = document.GetRequiredLanguageService<ISyntaxFactsService>();
                            var container = GetContainer(semanticModel, parameterNode, syntaxFacts);
                            if (container != null)
                            {
                                CascadeBetweenAnonymousFunctionParameters(
                                    document, semanticModel, container, parameter,
                                    convertedType, results, cancellationToken);
                            }
                        }
                    }
                }
            }
        }
    }
 
    private static void CascadeBetweenAnonymousFunctionParameters(
        Document document,
        SemanticModel semanticModel,
        SyntaxNode container,
        IParameterSymbol parameter,
        ITypeSymbol convertedType1,
        ArrayBuilder<ISymbol> results,
        CancellationToken cancellationToken)
    {
        var syntaxFacts = document.GetRequiredLanguageService<ISyntaxFactsService>();
        foreach (var token in container.DescendantTokens())
        {
            if (IdentifiersMatch(syntaxFacts, parameter.Name, token))
            {
                var symbol = semanticModel.GetDeclaredSymbol(token.GetRequiredParent(), cancellationToken);
                if (symbol is IParameterSymbol &&
                    symbol.ContainingSymbol.IsAnonymousFunction() &&
                    SignatureComparer.Instance.HaveSameSignatureAndConstraintsAndReturnTypeAndAccessors(parameter.ContainingSymbol, symbol.ContainingSymbol, syntaxFacts.IsCaseSensitive) &&
                    ParameterNamesMatch(syntaxFacts, (IMethodSymbol)parameter.ContainingSymbol, (IMethodSymbol)symbol.ContainingSymbol))
                {
                    var lambdaNode = symbol.ContainingSymbol.DeclaringSyntaxReferences.Select(r => r.GetSyntax(cancellationToken)).First();
                    var convertedType2 = semanticModel.GetTypeInfo(lambdaNode, cancellationToken).ConvertedType;
 
                    if (convertedType1.Equals(convertedType2))
                        results.Add(symbol);
                }
            }
        }
    }
 
    private static bool ParameterNamesMatch(ISyntaxFactsService syntaxFacts, IMethodSymbol methodSymbol1, IMethodSymbol methodSymbol2)
    {
        for (int i = 0, n = methodSymbol1.Parameters.Length; i < n; i++)
        {
            if (!syntaxFacts.TextMatch(methodSymbol1.Parameters[i].Name, methodSymbol2.Parameters[i].Name))
                return false;
        }
 
        return true;
    }
 
    private static SyntaxNode? GetContainer(SemanticModel semanticModel, SyntaxNode parameterNode, ISyntaxFactsService syntaxFactsService)
    {
        for (var current = parameterNode; current != null; current = current.Parent)
        {
            var declaredSymbol = semanticModel.GetDeclaredSymbol(current);
 
            if (declaredSymbol is IMethodSymbol { MethodKind: not MethodKind.AnonymousFunction })
                return current;
        }
 
        return syntaxFactsService.GetContainingVariableDeclaratorOfFieldDeclaration(parameterNode);
    }
 
    private static void CascadeBetweenPropertyAndAccessorParameters(
        IParameterSymbol parameter,
        ArrayBuilder<ISymbol> results)
    {
        var ordinal = parameter.Ordinal;
        var containingSymbol = parameter.ContainingSymbol;
        if (containingSymbol is IMethodSymbol containingMethod)
        {
            if (containingMethod.AssociatedSymbol is IPropertySymbol property)
                AddParameterAtIndex(results, ordinal, property.Parameters);
        }
        else if (containingSymbol is IPropertySymbol containingProperty)
        {
            if (containingProperty.GetMethod != null && ordinal < containingProperty.GetMethod.Parameters.Length)
                results.Add(containingProperty.GetMethod.Parameters[ordinal]);
 
            if (containingProperty.SetMethod != null && ordinal < containingProperty.SetMethod.Parameters.Length)
                results.Add(containingProperty.SetMethod.Parameters[ordinal]);
        }
    }
 
    private static void CascadeBetweenDelegateMethodParameters(
        IParameterSymbol parameter,
        ArrayBuilder<ISymbol> results)
    {
        var ordinal = parameter.Ordinal;
        if (parameter.ContainingSymbol is IMethodSymbol containingMethod)
        {
            var containingType = containingMethod.ContainingType;
            if (containingType.IsDelegateType())
            {
                if (containingMethod.MethodKind == MethodKind.DelegateInvoke)
                {
                    // cascade to the corresponding parameter in the BeginInvoke method.
                    var beginInvokeMethod = containingType.GetMembers(WellKnownMemberNames.DelegateBeginInvokeName)
                                                          .OfType<IMethodSymbol>()
                                                          .FirstOrDefault();
                    AddParameterAtIndex(results, ordinal, beginInvokeMethod?.Parameters);
                }
                else if (containingMethod.ContainingType.IsDelegateType() &&
                         containingMethod.Name == WellKnownMemberNames.DelegateBeginInvokeName)
                {
                    // cascade to the corresponding parameter in the Invoke method.
                    AddParameterAtIndex(results, ordinal, containingType.DelegateInvokeMethod?.Parameters);
                }
            }
        }
    }
 
    private static void AddParameterAtIndex(
        ArrayBuilder<ISymbol> results,
        int ordinal,
        ImmutableArray<IParameterSymbol>? parameters)
    {
        if (parameters != null && ordinal < parameters.Value.Length)
            results.Add(parameters.Value[ordinal]);
    }
 
    private static void CascadeBetweenPartialMethodParameters(
        IParameterSymbol parameter,
        ArrayBuilder<ISymbol> results)
    {
        // https://github.com/dotnet/roslyn/issues/73772: also cascade partial indexer parameters
        if (parameter.ContainingSymbol is IMethodSymbol method)
        {
            var ordinal = parameter.Ordinal;
            if (ordinal < method.PartialDefinitionPart?.Parameters.Length)
                results.Add(method.PartialDefinitionPart.Parameters[ordinal]);
 
            if (ordinal < method.PartialImplementationPart?.Parameters.Length)
                results.Add(method.PartialImplementationPart.Parameters[ordinal]);
        }
    }
}