File: SignatureHelp\InvocationExpressionSignatureHelpProvider.cs
Web Access
Project: src\src\Features\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.Features.csproj (Microsoft.CodeAnalysis.CSharp.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.
 
using System;
using System.Collections.Immutable;
using System.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.DocumentationComments;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.SignatureHelp;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp.SignatureHelp;
 
[ExportSignatureHelpProvider("InvocationExpressionSignatureHelpProvider", LanguageNames.CSharp), Shared]
internal sealed class InvocationExpressionSignatureHelpProvider : InvocationExpressionSignatureHelpProviderBase
{
    [ImportingConstructor]
    [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
    public InvocationExpressionSignatureHelpProvider()
    {
    }
}
 
internal partial class InvocationExpressionSignatureHelpProviderBase : AbstractOrdinaryMethodSignatureHelpProvider
{
    public override bool IsTriggerCharacter(char ch)
        => ch is '(' or ',';
 
    public override bool IsRetriggerCharacter(char ch)
        => ch == ')';
 
    private async Task<InvocationExpressionSyntax?> TryGetInvocationExpressionAsync(Document document, int position, SignatureHelpTriggerReason triggerReason, CancellationToken cancellationToken)
    {
        var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
        var syntaxFacts = document.GetRequiredLanguageService<ISyntaxFactsService>();
 
        if (!CommonSignatureHelpUtilities.TryGetSyntax(
                root, position, syntaxFacts, triggerReason, IsTriggerToken, IsArgumentListToken, cancellationToken, out InvocationExpressionSyntax? expression))
        {
            return null;
        }
 
        if (expression.ArgumentList is null)
            return null;
 
        return expression;
    }
 
    private bool IsTriggerToken(SyntaxToken token)
        => SignatureHelpUtilities.IsTriggerParenOrComma<InvocationExpressionSyntax>(token, IsTriggerCharacter);
 
    private static bool IsArgumentListToken(InvocationExpressionSyntax expression, SyntaxToken token)
    {
        return expression.ArgumentList.Span.Contains(token.SpanStart) &&
            token != expression.ArgumentList.CloseParenToken;
    }
 
    protected override async Task<SignatureHelpItems?> GetItemsWorkerAsync(
        Document document,
        int position,
        SignatureHelpTriggerInfo triggerInfo,
        MemberDisplayOptions options,
        CancellationToken cancellationToken)
    {
        var invocationExpression = await TryGetInvocationExpressionAsync(
            document, position, triggerInfo.TriggerReason, cancellationToken).ConfigureAwait(false);
        if (invocationExpression is null)
            return null;
 
        var semanticModel = await document.ReuseExistingSpeculativeModelAsync(invocationExpression, cancellationToken).ConfigureAwait(false);
        var within = semanticModel.GetEnclosingNamedTypeOrAssembly(position, cancellationToken);
        if (within == null)
            return null;
 
        var invokedType = semanticModel.GetTypeInfo(invocationExpression.Expression, cancellationToken).Type;
        if (invokedType is INamedTypeSymbol { TypeKind: TypeKind.Delegate } or IFunctionPointerTypeSymbol)
        {
            return await GetItemsWorkerForDelegateOrFunctionPointerAsync(document, position, invocationExpression, within, cancellationToken).ConfigureAwait(false);
        }
 
        // get the candidate methods
        var symbolDisplayService = document.GetLanguageService<ISymbolDisplayService>();
        var methods = semanticModel
            .GetMemberGroup(invocationExpression.Expression, cancellationToken)
            .OfType<IMethodSymbol>()
            .ToImmutableArray()
            .FilterToVisibleAndBrowsableSymbols(options.HideAdvancedMembers, semanticModel.Compilation);
        methods = GetAccessibleMethods(invocationExpression, semanticModel, within, methods, cancellationToken);
        methods = methods.Sort(semanticModel, invocationExpression.SpanStart);
 
        if (methods.Length == 0)
            return null;
 
        // guess the best candidate if needed and determine parameter index
        var symbolInfo = semanticModel.GetSymbolInfo(invocationExpression, cancellationToken);
        var (currentSymbol, parameterIndexOverride) = new LightweightOverloadResolution(semanticModel, position, invocationExpression.ArgumentList.Arguments)
            .RefineOverloadAndPickParameter(symbolInfo, methods);
 
        // if the symbol could be bound, replace that item in the symbol list
        if (currentSymbol?.IsGenericMethod == true)
            methods = methods.SelectAsArray(m => Equals(currentSymbol.OriginalDefinition, m) ? currentSymbol : m);
 
        // present items and select
        var (items, selectedItem) = await GetMethodGroupItemsAndSelectionAsync(
            methods, document, invocationExpression, semanticModel, symbolInfo, currentSymbol, cancellationToken).ConfigureAwait(false);
 
        var textSpan = SignatureHelpUtilities.GetSignatureHelpSpan(invocationExpression.ArgumentList);
        var argumentState = await GetCurrentArgumentStateAsync(
            document, position, textSpan, cancellationToken).ConfigureAwait(false);
        return CreateSignatureHelpItems(items, textSpan, argumentState, selectedItem, parameterIndexOverride);
    }
 
    protected async Task<SignatureHelpItems?> GetItemsWorkerForDelegateOrFunctionPointerAsync(
        Document document,
        int position,
        InvocationExpressionSyntax invocationExpression,
        ISymbol within,
        CancellationToken cancellationToken)
    {
        var semanticModel = await document.ReuseExistingSpeculativeModelAsync(invocationExpression, cancellationToken).ConfigureAwait(false);
 
        var invokedType = semanticModel.GetTypeInfo(invocationExpression.Expression, cancellationToken).Type;
        IMethodSymbol? currentSymbol;
        if (invokedType is INamedTypeSymbol { TypeKind: TypeKind.Delegate } expressionType)
        {
            currentSymbol = GetDelegateInvokeMethod(invocationExpression, semanticModel, within, expressionType, cancellationToken);
        }
        else if (invokedType is IFunctionPointerTypeSymbol functionPointerType)
        {
            currentSymbol = functionPointerType.Signature;
        }
        else
        {
            throw ExceptionUtilities.Unreachable();
        }
 
        if (currentSymbol is null)
        {
            return null;
        }
 
        // determine parameter index
        var parameterIndexOverride = new LightweightOverloadResolution(semanticModel, position, invocationExpression.ArgumentList.Arguments)
            .FindParameterIndexIfCompatibleMethod(currentSymbol);
 
        // present item and select
        var structuralTypeDisplayService = document.Project.Services.GetRequiredService<IStructuralTypeDisplayService>();
        var documentationCommentFormattingService = document.GetRequiredLanguageService<IDocumentationCommentFormattingService>();
 
        var items = GetDelegateOrFunctionPointerInvokeItems(invocationExpression, currentSymbol,
            semanticModel, structuralTypeDisplayService, documentationCommentFormattingService, out var selectedItem, cancellationToken);
 
        var textSpan = SignatureHelpUtilities.GetSignatureHelpSpan(invocationExpression.ArgumentList);
        var argumentState = await GetCurrentArgumentStateAsync(
            document, position, textSpan, cancellationToken).ConfigureAwait(false);
        return CreateSignatureHelpItems(items, textSpan, argumentState, selectedItem, parameterIndexOverride);
    }
 
    private async Task<SignatureHelpState?> GetCurrentArgumentStateAsync(
        Document document,
        int position,
        TextSpan currentSpan,
        CancellationToken cancellationToken)
    {
        var expression = await TryGetInvocationExpressionAsync(
            document, position, SignatureHelpTriggerReason.InvokeSignatureHelpCommand, cancellationToken).ConfigureAwait(false);
        if (expression is { ArgumentList: not null } &&
            currentSpan.Start == SignatureHelpUtilities.GetSignatureHelpSpan(expression.ArgumentList).Start)
        {
            return SignatureHelpUtilities.GetSignatureHelpState(expression.ArgumentList, position);
        }
 
        return null;
    }
}