|
// 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.Diagnostics;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace Microsoft.CodeAnalysis.CSharp
{
internal static class SyntaxNodeExtensions
{
public static TNode WithAnnotations<TNode>(this TNode node, params SyntaxAnnotation[] annotations) where TNode : CSharpSyntaxNode
{
return (TNode)node.Green.SetAnnotations(annotations).CreateRed();
}
public static bool IsAnonymousFunction(this SyntaxNode syntax)
{
Debug.Assert(syntax != null);
switch (syntax.Kind())
{
case SyntaxKind.ParenthesizedLambdaExpression:
case SyntaxKind.SimpleLambdaExpression:
case SyntaxKind.AnonymousMethodExpression:
return true;
default:
return false;
}
}
public static bool IsQuery(this SyntaxNode syntax)
{
Debug.Assert(syntax != null);
switch (syntax.Kind())
{
case SyntaxKind.FromClause:
case SyntaxKind.GroupClause:
case SyntaxKind.JoinClause:
case SyntaxKind.JoinIntoClause:
case SyntaxKind.LetClause:
case SyntaxKind.OrderByClause:
case SyntaxKind.QueryContinuation:
case SyntaxKind.QueryExpression:
case SyntaxKind.SelectClause:
case SyntaxKind.WhereClause:
return true;
default:
return false;
}
}
internal static bool MayBeNameofOperator(this InvocationExpressionSyntax node)
{
if (node.Expression.Kind() == SyntaxKind.IdentifierName &&
((IdentifierNameSyntax)node.Expression).Identifier.ContextualKind() == SyntaxKind.NameOfKeyword &&
node.ArgumentList.Arguments.Count == 1)
{
ArgumentSyntax argument = node.ArgumentList.Arguments[0];
if (argument.NameColon == null && argument.RefOrOutKeyword == default)
{
return true;
}
}
return false;
}
/// <summary>
/// This method is used to keep the code that generates binders in sync
/// with the code that searches for binders. We don't want the searcher
/// to skip over any nodes that could have associated binders, especially
/// if changes are made later.
///
/// "Local binder" is a term that refers to binders that are
/// created by LocalBinderFactory.
/// </summary>
internal static bool CanHaveAssociatedLocalBinder(this SyntaxNode syntax)
{
SyntaxKind kind = syntax.Kind();
switch (kind)
{
case SyntaxKind.InvocationExpression when ((InvocationExpressionSyntax)syntax).MayBeNameofOperator():
return true;
case SyntaxKind.CatchClause:
case SyntaxKind.ParenthesizedLambdaExpression:
case SyntaxKind.SimpleLambdaExpression:
case SyntaxKind.AnonymousMethodExpression:
case SyntaxKind.CatchFilterClause:
case SyntaxKind.SwitchSection:
case SyntaxKind.EqualsValueClause:
case SyntaxKind.Attribute:
case SyntaxKind.ArgumentList:
case SyntaxKind.ArrowExpressionClause:
case SyntaxKind.SwitchExpression:
case SyntaxKind.SwitchExpressionArm:
case SyntaxKind.BaseConstructorInitializer:
case SyntaxKind.ThisConstructorInitializer:
case SyntaxKind.ConstructorDeclaration:
case SyntaxKind.PrimaryConstructorBaseType:
case SyntaxKind.CheckedExpression:
case SyntaxKind.UncheckedExpression:
return true;
case SyntaxKind.RecordStructDeclaration:
return false;
default:
return syntax is StatementSyntax || IsValidScopeDesignator(syntax as ExpressionSyntax);
}
}
internal static bool IsValidScopeDesignator(this ExpressionSyntax? expression)
{
// All these nodes are valid scope designators due to the pattern matching and out vars features.
CSharpSyntaxNode? parent = expression?.Parent;
switch (parent?.Kind())
{
case SyntaxKind.SimpleLambdaExpression:
case SyntaxKind.ParenthesizedLambdaExpression:
return ((LambdaExpressionSyntax)parent).Body == expression;
case SyntaxKind.SwitchStatement:
return ((SwitchStatementSyntax)parent).Expression == expression;
case SyntaxKind.ForStatement:
var forStmt = (ForStatementSyntax)parent;
return forStmt.Condition == expression || forStmt.Incrementors.FirstOrDefault() == expression;
case SyntaxKind.ForEachStatement:
case SyntaxKind.ForEachVariableStatement:
return ((CommonForEachStatementSyntax)parent).Expression == expression;
default:
return false;
}
}
/// <summary>
/// Because the instruction cannot have any values on the stack before CLR execution
/// we limited it to assignments and conditional expressions in C# 7.
/// See https://github.com/dotnet/roslyn/issues/22046.
/// In C# 8 we relaxed
/// that by rewriting the code to move it to the statement level where the stack is empty.
/// </summary>
internal static bool IsLegalCSharp73SpanStackAllocPosition(this SyntaxNode node)
{
Debug.Assert(node != null);
if (node.Parent.IsKind(SyntaxKind.CastExpression))
{
node = node.Parent;
}
while (node.Parent.IsKind(SyntaxKind.ConditionalExpression))
{
node = node.Parent;
}
SyntaxNode? parentNode = node.Parent;
if (parentNode is null)
{
return false;
}
switch (parentNode.Kind())
{
// In case of a declaration of a Span<T> variable
case SyntaxKind.EqualsValueClause:
{
SyntaxNode? variableDeclarator = parentNode.Parent;
return variableDeclarator.IsKind(SyntaxKind.VariableDeclarator) &&
variableDeclarator.Parent.IsKind(SyntaxKind.VariableDeclaration);
}
// In case of reassignment to a Span<T> variable
case SyntaxKind.SimpleAssignmentExpression:
{
return parentNode.Parent.IsKind(SyntaxKind.ExpressionStatement);
}
}
return false;
}
internal static CSharpSyntaxNode AnonymousFunctionBody(this SyntaxNode lambda)
=> ((AnonymousFunctionExpressionSyntax)lambda).Body;
/// <summary>
/// Given an initializer expression infer the name of anonymous property or tuple element.
/// Returns default if unsuccessful
/// </summary>
internal static SyntaxToken ExtractAnonymousTypeMemberName(this ExpressionSyntax input)
{
while (true)
{
switch (input.Kind())
{
case SyntaxKind.IdentifierName:
return ((IdentifierNameSyntax)input).Identifier;
case SyntaxKind.SimpleMemberAccessExpression:
input = ((MemberAccessExpressionSyntax)input).Name;
continue;
case SyntaxKind.ConditionalAccessExpression:
input = ((ConditionalAccessExpressionSyntax)input).WhenNotNull;
if (input.Kind() == SyntaxKind.MemberBindingExpression)
{
return ((MemberBindingExpressionSyntax)input).Name.Identifier;
}
continue;
default:
return default(SyntaxToken);
}
}
}
internal static RefKind GetRefKindInLocalOrReturn(this TypeSyntax syntax, BindingDiagnosticBag diagnostics)
{
syntax.SkipRefInLocalOrReturn(diagnostics, out var refKind);
return refKind;
}
/// <summary>
/// For callers that just want to unwrap a <see cref="RefTypeSyntax"/> and don't care if ref/readonly was there.
/// As these callers don't care about 'ref', they are in scenarios where 'ref' is not legal, and existing code
/// will error out for them. Callers that do want to know what the ref-kind is should use <see
/// cref="SkipRefInLocalOrReturn"/> or <see cref="SkipRefInField"/> depending on which language feature they are
/// asking for.
/// </summary>
internal static TypeSyntax SkipRef(this TypeSyntax syntax)
=> SkipRefWorker(syntax, diagnostics: null, out _);
internal static TypeSyntax SkipRefInField(this TypeSyntax syntax, out RefKind refKind)
{
// Intentionally pass no diagnostics here. This is for ref-fields which handles all its diagnostics itself
// in the field symbol.
return SkipRefWorker(syntax, diagnostics: null, out refKind);
}
internal static TypeSyntax SkipRefInLocalOrReturn(this TypeSyntax syntax, BindingDiagnosticBag? diagnostics, out RefKind refKind)
=> SkipRefWorker(syntax, diagnostics, out refKind);
private static TypeSyntax SkipRefWorker(TypeSyntax syntax, BindingDiagnosticBag? diagnostics, out RefKind refKind)
{
if (syntax.Kind() == SyntaxKind.RefType)
{
var refType = (RefTypeSyntax)syntax;
refKind = refType.ReadOnlyKeyword.Kind() == SyntaxKind.ReadOnlyKeyword
? RefKind.RefReadOnly
: RefKind.Ref;
if (diagnostics != null)
{
#if DEBUG
var current = syntax;
if (current.Parent is ScopedTypeSyntax scopedType)
current = scopedType;
// Should only be called with diagnostics from a location where we're a return-type or local-type.
Debug.Assert(
(current.Parent is ParenthesizedLambdaExpressionSyntax lambda && lambda.ReturnType == current) ||
(current.Parent is LocalFunctionStatementSyntax localFunction && localFunction.ReturnType == current) ||
(current.Parent is MethodDeclarationSyntax method && method.ReturnType == current) ||
(current.Parent is BasePropertyDeclarationSyntax property && property.Type == current) ||
(current.Parent is DelegateDeclarationSyntax delegateDeclaration && delegateDeclaration.ReturnType == current) ||
(current.Parent is VariableDeclarationSyntax { Parent: LocalDeclarationStatementSyntax } variableDeclaration && variableDeclaration.Type == current));
#endif
MessageID.IDS_FeatureRefLocalsReturns.CheckFeatureAvailability(diagnostics, refType.RefKeyword);
if (refType.ReadOnlyKeyword != default)
MessageID.IDS_FeatureReadOnlyReferences.CheckFeatureAvailability(diagnostics, refType.ReadOnlyKeyword);
}
return refType.Type;
}
refKind = RefKind.None;
return syntax;
}
internal static TypeSyntax SkipScoped(this TypeSyntax syntax, out bool isScoped)
{
if (syntax is ScopedTypeSyntax scopedType)
{
isScoped = true;
return scopedType.Type;
}
isScoped = false;
return syntax;
}
internal static SyntaxNode ModifyingScopedOrRefTypeOrSelf(this SyntaxNode syntax)
{
SyntaxNode? parentNode = syntax.Parent;
if (parentNode is RefTypeSyntax refType && refType.Type == syntax)
{
syntax = refType;
parentNode = parentNode.Parent;
}
if (parentNode is ScopedTypeSyntax scopedType && scopedType.Type == syntax)
{
return scopedType;
}
return syntax;
}
internal static ExpressionSyntax? CheckAndUnwrapRefExpression(
this ExpressionSyntax? syntax,
BindingDiagnosticBag diagnostics,
out RefKind refKind)
{
if (syntax is not RefExpressionSyntax { Expression: var expression } refExpression)
{
refKind = RefKind.None;
return syntax;
}
MessageID.IDS_FeatureRefLocalsReturns.CheckFeatureAvailability(diagnostics, refExpression.RefKeyword);
refKind = RefKind.Ref;
expression.CheckDeconstructionCompatibleArgument(diagnostics);
return expression;
}
internal static void CheckDeconstructionCompatibleArgument(this ExpressionSyntax expression, BindingDiagnosticBag diagnostics)
{
if (IsDeconstructionCompatibleArgument(expression))
{
diagnostics.Add(ErrorCode.ERR_VarInvocationLvalueReserved, expression.GetLocation());
}
}
/// <summary>
/// See if the expression is an invocation of a method named 'var',
/// I.e. something like "var(x, y)" or "var(x, (y, z))" or "var(1)".
/// We report an error when such an invocation is used in a certain syntactic contexts that
/// will require an lvalue because we may elect to support deconstruction
/// in the future. We need to ensure that we do not successfully interpret this as an invocation of a
/// ref-returning method named var.
/// </summary>
private static bool IsDeconstructionCompatibleArgument(ExpressionSyntax expression)
{
if (expression.Kind() == SyntaxKind.InvocationExpression)
{
var invocation = (InvocationExpressionSyntax)expression;
var invocationTarget = invocation.Expression;
return invocationTarget.Kind() == SyntaxKind.IdentifierName &&
((IdentifierNameSyntax)invocationTarget).IsVar;
}
return false;
}
internal static SimpleNameSyntax? GetInterceptableNameSyntax(this InvocationExpressionSyntax invocation)
{
// If a qualified name is used as a valid receiver of an invocation syntax at some point,
// we probably want to treat it similarly to a MemberAccessExpression.
// However, we don't expect to encounter it.
Debug.Assert(invocation.Expression is not QualifiedNameSyntax);
return invocation.Expression switch
{
MemberAccessExpressionSyntax memberAccess => memberAccess.Name,
MemberBindingExpressionSyntax memberBinding => memberBinding.Name,
SimpleNameSyntax name => name,
_ => null
};
}
}
}
|