|
// 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.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.Completion;
using Microsoft.CodeAnalysis.Completion.Providers;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.LanguageService;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers;
[ExportCompletionProvider(nameof(AwaitCompletionProvider), LanguageNames.CSharp), Shared]
[ExtensionOrder(After = nameof(KeywordCompletionProvider))]
[method: ImportingConstructor]
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
internal sealed class AwaitCompletionProvider() : AbstractAwaitCompletionProvider(CSharpSyntaxFacts.Instance)
{
internal override string Language => LanguageNames.CSharp;
public override ImmutableHashSet<char> TriggerCharacters => CompletionUtilities.CommonTriggerCharactersWithArgumentList;
protected override bool IsAwaitKeywordContext(SyntaxContext syntaxContext)
=> base.IsAwaitKeywordContext(syntaxContext);
/// <summary>
/// Gets the span start where async keyword should go.
/// </summary>
protected override int GetSpanStart(SyntaxNode declaration)
{
return declaration switch
{
MethodDeclarationSyntax method => method.ReturnType.SpanStart,
LocalFunctionStatementSyntax local => local.ReturnType.SpanStart,
AnonymousMethodExpressionSyntax anonymous => anonymous.DelegateKeyword.SpanStart,
// If we have an explicit lambda return type, async should go just before it. Otherwise, it should go before parameter list.
// static [|async|] (a) => ....
// static [|async|] ExplicitReturnType (a) => ....
ParenthesizedLambdaExpressionSyntax parenthesizedLambda => (parenthesizedLambda.ReturnType as SyntaxNode ?? parenthesizedLambda.ParameterList).SpanStart,
SimpleLambdaExpressionSyntax simpleLambda => simpleLambda.Parameter.SpanStart,
_ => throw ExceptionUtilities.UnexpectedValue(declaration.Kind())
};
}
protected override SyntaxNode? GetAsyncSupportingDeclaration(SyntaxToken leftToken, int position)
{
// In a case like
// someTask.$$
// await Test();
// someTask.await Test() is parsed as a local function statement.
// We skip this and look further up in the hierarchy.
var parent = leftToken.Parent;
if (parent == null)
return null;
if (parent is NameSyntax { Parent: LocalFunctionStatementSyntax localFunction } name &&
localFunction.ReturnType == name)
{
parent = localFunction.GetRequiredParent();
}
return parent.AncestorsAndSelf().FirstOrDefault(node =>
{
if (!node.IsAsyncSupportingFunctionSyntax())
return false;
// Ensure that if we were outside of the async-supporting-function that we don't return it as the thing to
// make async. We want to make its parent async.
if (position > leftToken.FullSpan.End)
return node.Span.Contains(position);
return node.Span.IntersectsWith(position);
});
}
protected override SyntaxNode? GetExpressionToPlaceAwaitInFrontOf(SyntaxTree syntaxTree, int position, CancellationToken cancellationToken)
{
var dotToken = GetDotTokenLeftOfPosition(syntaxTree, position, cancellationToken);
return dotToken?.Parent switch
{
// Don't support conditional access someTask?.$$ or c?.TaskReturning().$$ because there is no good completion until
// await? is supported by the language https://github.com/dotnet/csharplang/issues/35
MemberAccessExpressionSyntax memberAccess => memberAccess.GetParentConditionalAccessExpression() is null ? memberAccess : null,
// someTask.$$.
RangeExpressionSyntax range => range.LeftOperand,
// special cases, where parsing is misleading. Such cases are handled in GetTypeSymbolOfExpression.
QualifiedNameSyntax qualifiedName => qualifiedName.Left,
_ => null,
};
}
protected override SyntaxToken? GetDotTokenLeftOfPosition(SyntaxTree syntaxTree, int position, CancellationToken cancellationToken)
=> CompletionUtilities.GetDotTokenLeftOfPosition(syntaxTree, position, cancellationToken);
protected override ITypeSymbol? GetTypeSymbolOfExpression(SemanticModel semanticModel, SyntaxNode potentialAwaitableExpression, CancellationToken cancellationToken)
{
if (potentialAwaitableExpression is MemberAccessExpressionSyntax memberAccess)
{
var memberAccessExpression = memberAccess.Expression.WalkDownParentheses();
// In cases like Task.$$ semanticModel.GetTypeInfo returns Task, but
// we don't want to suggest await here. We look up the symbol of the "Task" part
// and return null if it is a NamedType.
var symbol = semanticModel.GetSymbolInfo(memberAccessExpression, cancellationToken).Symbol;
return symbol is ITypeSymbol ? null : semanticModel.GetTypeInfo(memberAccessExpression, cancellationToken).Type;
}
else if (potentialAwaitableExpression is ExpressionSyntax expression &&
expression.ShouldNameExpressionBeTreatedAsExpressionInsteadOfType(semanticModel, out _, out var container))
{
return container;
}
else
{
return semanticModel.GetTypeInfo(potentialAwaitableExpression, cancellationToken).Type;
}
}
}
|