|
// 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.Diagnostics;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.Extensions;
internal static class ParenthesizedExpressionSyntaxExtensions
{
public static bool CanRemoveParentheses(
this ParenthesizedExpressionSyntax node, SemanticModel semanticModel, CancellationToken cancellationToken)
{
if (node.OpenParenToken.IsMissing || node.CloseParenToken.IsMissing)
{
// int x = (3;
return false;
}
var nodeParent = node.Parent;
if (nodeParent == null)
return false;
var expression = node.Expression;
// The 'direct' expression that contains this parenthesized node. Note: in the case
// of code like: ```x is (y)``` there is an intermediary 'no-syntax' 'ConstantPattern'
// node between the 'is-pattern' node and the parenthesized expression. So we manually
// jump past that as, for all intents and purposes, we want to consider the 'is' expression
// as the parent expression of the (y) expression.
var parentExpression = nodeParent.IsKind(SyntaxKind.ConstantPattern)
? nodeParent.Parent as ExpressionSyntax
: nodeParent as ExpressionSyntax;
// Have to be careful if we would remove parens and cause a + and a + to become a ++.
// (same with - as well).
var tokenBeforeParen = node.GetFirstToken().GetPreviousToken();
var tokenAfterParen = node.Expression.GetFirstToken();
var previousChar = tokenBeforeParen.Text.LastOrDefault();
var nextChar = tokenAfterParen.Text.FirstOrDefault();
if ((previousChar == '+' && nextChar == '+') ||
(previousChar == '-' && nextChar == '-'))
{
return false;
}
// Simplest cases:
// ((x)) -> (x)
if (expression.IsKind(SyntaxKind.ParenthesizedExpression) ||
parentExpression.IsKind(SyntaxKind.ParenthesizedExpression))
{
return true;
}
if (expression is StackAllocArrayCreationExpressionSyntax or ImplicitStackAllocArrayCreationExpressionSyntax)
{
// var span = (stackalloc byte[8]);
// https://github.com/dotnet/roslyn/issues/44629
// The code semantics changes if the parenthesis removed.
// With parenthesis: variable span is of type `Span<byte>`.
// Without parenthesis: variable span is of type `byte*` which can only be used in unsafe context.
if (nodeParent is EqualsValueClauseSyntax { Parent: VariableDeclaratorSyntax { Parent: VariableDeclarationSyntax varDecl } })
{
// We have either `var x = (stackalloc byte[8])` or `Span<byte> x = (stackalloc byte[8])`. The former
// is not safe to remove. the latter is.
if (semanticModel.GetTypeInfo(varDecl.Type, cancellationToken).Type.IsSpanOrReadOnlySpan())
return !varDecl.Type.IsVar;
}
return false;
}
// Don't remove parentheses around `<` and `>` if there's a reasonable chance that it might
// pair with the opposite form, causing them to be reinterpreted as generic syntax. See
// https://github.com/dotnet/roslyn/issues/43934 for examples.
if (expression.Kind() is SyntaxKind.GreaterThanExpression or SyntaxKind.LessThanExpression &&
nodeParent is ArgumentSyntax)
{
var opposite = expression.IsKind(SyntaxKind.GreaterThanExpression) ? SyntaxKind.LessThanExpression : SyntaxKind.GreaterThanExpression;
if (nodeParent.GetRequiredParent().ChildNodes().OfType<ArgumentSyntax>().Any(a => a.Expression.IsKind(opposite)))
return false;
}
// (throw ...) -> throw ...
if (expression.IsKind(SyntaxKind.ThrowExpression))
return true;
// (x); -> x;
if (nodeParent.IsKind(SyntaxKind.ExpressionStatement))
return true;
// => (x) -> => x
if (nodeParent.IsKind(SyntaxKind.ArrowExpressionClause))
return true;
// checked((x)) -> checked(x)
if (nodeParent.Kind() is SyntaxKind.CheckedExpression or SyntaxKind.UncheckedExpression)
return true;
// ((x, y)) -> (x, y)
if (expression.IsKind(SyntaxKind.TupleExpression))
return true;
// ([...]) -> [...]
if (expression.IsKind(SyntaxKind.CollectionExpression))
return true;
// int Prop => (x); -> int Prop => x;
if (nodeParent is ArrowExpressionClauseSyntax arrowExpressionClause && arrowExpressionClause.Expression == node)
{
return true;
}
// Easy statement-level cases:
// var y = (x); -> var y = x;
// var (y, z) = (x); -> var (y, z) = x;
// if ((x)) -> if (x)
// return (x); -> return x;
// yield return (x); -> yield return x;
// throw (x); -> throw x;
// switch ((x)) -> switch (x)
// while ((x)) -> while (x)
// do { } while ((x)) -> do { } while (x)
// for(;(x);) -> for(;x;)
// foreach (var y in (x)) -> foreach (var y in x)
// lock ((x)) -> lock (x)
// using ((x)) -> using (x)
// catch when ((x)) -> catch when (x)
if ((nodeParent is EqualsValueClauseSyntax equalsValue && equalsValue.Value == node) ||
(nodeParent is IfStatementSyntax ifStatement && ifStatement.Condition == node) ||
(nodeParent is ReturnStatementSyntax returnStatement && returnStatement.Expression == node) ||
(nodeParent is YieldStatementSyntax(SyntaxKind.YieldReturnStatement) yieldStatement && yieldStatement.Expression == node) ||
(nodeParent is ThrowStatementSyntax throwStatement && throwStatement.Expression == node) ||
(nodeParent is SwitchStatementSyntax switchStatement && switchStatement.Expression == node) ||
(nodeParent is WhileStatementSyntax whileStatement && whileStatement.Condition == node) ||
(nodeParent is DoStatementSyntax doStatement && doStatement.Condition == node) ||
(nodeParent is ForStatementSyntax forStatement && forStatement.Condition == node) ||
(nodeParent is CommonForEachStatementSyntax forEachStatement && forEachStatement.Expression == node) ||
(nodeParent is LockStatementSyntax lockStatement && lockStatement.Expression == node) ||
(nodeParent is UsingStatementSyntax usingStatement && usingStatement.Expression == node) ||
(nodeParent is CatchFilterClauseSyntax catchFilter && catchFilter.FilterExpression == node))
{
return true;
}
// Handle expression-level ambiguities
if (RemovalMayIntroduceCastAmbiguity(node) ||
RemovalMayIntroduceCommaListAmbiguity(node) ||
RemovalMayIntroduceInterpolationAmbiguity(node) ||
RemovalWouldChangeConstantReferenceToTypeReference(node, expression, semanticModel, cancellationToken))
{
return false;
}
// Cases:
// (C)(this) -> (C)this
if (nodeParent.IsKind(SyntaxKind.CastExpression) && expression.IsKind(SyntaxKind.ThisExpression))
return true;
// Cases:
// y((x)) -> y(x)
if (nodeParent is ArgumentSyntax argument && argument.Expression == node)
return true;
// Cases:
// $"{(x)}" -> $"{x}"
if (nodeParent.IsKind(SyntaxKind.Interpolation))
return true;
// Cases:
// ($"{x}") -> $"{x}"
if (expression.IsKind(SyntaxKind.InterpolatedStringExpression))
return true;
// Cases:
// {(x)} -> {x}
if (nodeParent is InitializerExpressionSyntax)
{
// Assignment expressions and collection expressions are not allowed in initializers
// as they are not parsed as expressions, but as more complex constructs
return expression is not AssignmentExpressionSyntax and not CollectionExpressionSyntax;
}
// Cases:
// new {(x)} -> {x}
// new { a = (x)} -> { a = x }
// new { a = (x = c)} -> { a = x = c }
if (nodeParent is AnonymousObjectMemberDeclaratorSyntax anonymousDeclarator)
{
// Assignment expressions are not allowed unless member is named
if (anonymousDeclarator.NameEquals == null && expression is AssignmentExpressionSyntax)
return false;
return true;
}
// Cases:
// where (x + 1 > 14) -> where x + 1 > 14
if (nodeParent is QueryClauseSyntax)
return true;
// Cases:
// (x) -> x
// (x.y) -> x.y
if (IsSimpleOrDottedName(expression))
return true;
// Cases:
// ('') -> ''
// ("") -> ""
// (false) -> false
// (true) -> true
// (null) -> null
// (default) -> default;
// (1) -> 1
if (expression is LiteralExpressionSyntax)
return true;
// (typeof(int)) -> typeof(int)
// (default(int)) -> default(int)
// (checked(1)) -> checked(1)
// (unchecked(1)) -> unchecked(1)
// (sizeof(int)) -> sizeof(int)
if (expression is TypeOfExpressionSyntax or DefaultExpressionSyntax or CheckedExpressionSyntax or SizeOfExpressionSyntax)
return true;
// (this) -> this
if (expression.IsKind(SyntaxKind.ThisExpression))
return true;
// x ?? (throw ...) -> x ?? throw ...
if (expression.IsKind(SyntaxKind.ThrowExpression) &&
nodeParent is BinaryExpressionSyntax(SyntaxKind.CoalesceExpression) binary &&
binary.Right == node)
{
return true;
}
// case (x): -> case x:
if (nodeParent.IsKind(SyntaxKind.CaseSwitchLabel))
return true;
// case (x) when y: -> case x when y:
if (nodeParent.IsKind(SyntaxKind.ConstantPattern) &&
nodeParent.IsParentKind(SyntaxKind.CasePatternSwitchLabel))
{
return true;
}
// case x when (y): -> case x when y:
if (nodeParent.IsKind(SyntaxKind.WhenClause))
{
// Subtle case, `when (x?[] ...):`. Can't remove the parentheses here as it can the conditional access
// become a conditional expression.
for (var current = expression; current != null; current = current.ChildNodes().FirstOrDefault() as ExpressionSyntax)
{
if (current is ConditionalAccessExpressionSyntax)
return false;
}
return true;
}
// #if (x) -> #if x
if (nodeParent is DirectiveTriviaSyntax)
return true;
// Switch expression arm
// x => (y)
if (nodeParent is SwitchExpressionArmSyntax arm && arm.Expression == node)
return true;
// [.. (expr)] -> [.. expr]
//
// Note: There is no precedence with `..` it's always just part of the collection expr, with the expr being
// parsed independently of it. That's why no parens are ever needed here.
if (nodeParent is SpreadElementSyntax)
return true;
// If we have: (X)(++x) or (X)(--x), we don't want to remove the parens. doing so can
// make the ++/-- now associate with the previous part of the cast expression.
if (parentExpression.IsKind(SyntaxKind.CastExpression) &&
expression.Kind() is SyntaxKind.PreIncrementExpression or SyntaxKind.PreDecrementExpression)
{
return false;
}
// (condition ? ref a : ref b ) = SomeValue, parenthesis can't be removed for when conditional expression appears at left
// This syntax is only allowed since C# 7.2
if (expression.IsKind(SyntaxKind.ConditionalExpression) &&
node.IsLeftSideOfAnyAssignExpression())
{
return false;
}
// Don't change (x?.Count)... to x?.Count...
//
// It very much changes the semantics to have code that always executed (outside the
// parenthesized expression) now only conditionally run depending on if 'x' is null or
// not.
if (expression.IsKind(SyntaxKind.ConditionalAccessExpression))
return false;
// Operator precedence cases:
// - If the parent is not an expression, do not remove parentheses
// - Otherwise, parentheses may be removed if doing so does not change operator associations.
return parentExpression != null && !RemovalChangesAssociation(node, parentExpression, semanticModel);
}
private static bool RemovalWouldChangeConstantReferenceToTypeReference(
ParenthesizedExpressionSyntax node, ExpressionSyntax expression,
SemanticModel semanticModel, CancellationToken cancellationToken)
{
// With cases like: `if (x is (Y))` then we cannot remove the parens if it would make Y now bind to a type
// instead of a constant.
if (node.Parent is not ConstantPatternSyntax { Parent: IsPatternExpressionSyntax })
return false;
var exprSymbol = semanticModel.GetSymbolInfo(expression, cancellationToken).Symbol;
if (exprSymbol is not IFieldSymbol { IsConst: true })
return false;
// See if interpreting the same expression as a type in this location binds.
var potentialType = semanticModel.GetSpeculativeTypeInfo(expression.SpanStart, expression, SpeculativeBindingOption.BindAsTypeOrNamespace).Type;
return potentialType is not (null or IErrorTypeSymbol);
}
private static readonly ObjectPool<Stack<SyntaxNode>> s_nodeStackPool = SharedPools.Default<Stack<SyntaxNode>>();
private static bool RemovalMayIntroduceInterpolationAmbiguity(ParenthesizedExpressionSyntax node)
{
// First, find the parenting interpolation. If we find a parenthesize expression first,
// we can bail out early.
InterpolationSyntax? interpolation = null;
foreach (var ancestor in node.GetRequiredParent().AncestorsAndSelf())
{
if (ancestor.IsKind(SyntaxKind.ParenthesizedExpression))
return false;
if (ancestor.IsKind(SyntaxKind.Interpolation, out interpolation))
break;
}
if (interpolation == null)
return false;
// In order determine whether removing this parenthesized expression will introduce a
// parsing ambiguity, we must dig into the child tokens and nodes to determine whether
// they include any : or :: tokens. If they do, we can't remove the parentheses because
// the parser would assume that the first : would begin the format clause of the interpolation.
using var _ = s_nodeStackPool.GetPooledObject(out var stack);
stack.Push(node.Expression);
while (stack.TryPop(out var expression))
{
foreach (var nodeOrToken in expression.ChildNodesAndTokens())
{
// Note: There's no need drill into other parenthesized expressions, since any colons in them would be unambiguous.
if (nodeOrToken.AsNode(out var childNode))
{
if (!childNode.IsKind(SyntaxKind.ParenthesizedExpression))
stack.Push(childNode);
}
else if (nodeOrToken.IsToken)
{
if (nodeOrToken.Kind() is SyntaxKind.ColonToken or SyntaxKind.ColonColonToken)
{
return true;
}
}
}
}
return false;
}
private static bool RemovalChangesAssociation(
ParenthesizedExpressionSyntax node, ExpressionSyntax parentExpression, SemanticModel semanticModel)
{
var expression = node.Expression;
var precedence = expression.GetOperatorPrecedence();
var parentPrecedence = parentExpression.GetOperatorPrecedence();
if (precedence == OperatorPrecedence.None || parentPrecedence == OperatorPrecedence.None)
{
// Be conservative if the expression or its parent has no precedence.
return true;
}
if (precedence > parentPrecedence)
{
// Association never changes if the expression's precedence is higher than its parent.
return false;
}
else if (precedence < parentPrecedence)
{
// Association always changes if the expression's precedence is lower that its parent.
return true;
}
else if (precedence == parentPrecedence)
{
// If the expression's precedence is the same as its parent, and both are binary expressions,
// check for associativity and commutability.
if (expression is not (BinaryExpressionSyntax or AssignmentExpressionSyntax))
{
// If the expression is not a binary expression, association never changes.
return false;
}
if (parentExpression is BinaryExpressionSyntax parentBinaryExpression)
{
// If both the expression and its parent are binary expressions and their kinds
// are the same, and the parenthesized expression is on hte right and the
// operation is associative, it can sometimes be safe to remove these parens.
//
// i.e. if you have "a && (b && c)" it can be converted to "a && b && c"
// as that new interpretation "(a && b) && c" operates the exact same way at
// runtime.
//
// Specifically:
// 1) the operands are still executed in the same order: a, b, then c.
// So even if they have side effects, it will not matter.
// 2) the same shortcircuiting happens.
// 3) for logical operators the result will always be the same (there are
// additional conditions that are checked for non-logical operators).
if (IsAssociative(parentBinaryExpression.Kind()) &&
parentBinaryExpression.Right == node &&
node.Expression.IsKind(parentBinaryExpression.Kind(), out BinaryExpressionSyntax? nodeBinary))
{
return !CSharpSemanticFacts.Instance.IsSafeToChangeAssociativity(
nodeBinary, parentBinaryExpression, semanticModel);
}
// Null-coalescing is right associative; removing parens from the LHS changes the association.
if (parentExpression.IsKind(SyntaxKind.CoalesceExpression))
{
return parentBinaryExpression.Left == node;
}
// All other binary operators are left associative; removing parens from the RHS changes the association.
return parentBinaryExpression.Right == node;
}
if (parentExpression is AssignmentExpressionSyntax parentAssignmentExpression)
{
// Assignment expressions are right associative; removing parens from the LHS changes the association.
return parentAssignmentExpression.Left == node;
}
// If the parent is not a binary expression, association never changes.
return false;
}
throw ExceptionUtilities.Unreachable();
}
private static bool IsAssociative(SyntaxKind kind)
{
switch (kind)
{
case SyntaxKind.AddExpression:
case SyntaxKind.MultiplyExpression:
case SyntaxKind.BitwiseOrExpression:
case SyntaxKind.ExclusiveOrExpression:
case SyntaxKind.LogicalOrExpression:
case SyntaxKind.BitwiseAndExpression:
case SyntaxKind.LogicalAndExpression:
return true;
}
return false;
}
private static bool RemovalMayIntroduceCastAmbiguity(ParenthesizedExpressionSyntax node)
{
// Be careful not to break the special case around (x)(-y)
// as defined in section 7.7.6 of the C# language specification.
//
// cases we can't remove the parens for are:
//
// (x)(+y)
// (x)(-y)
// (x)(&y) // unsafe code
// (x)(*y) // unsafe code
//
// Note: we can remove the parens if the (x) part is unambiguously a type.
// i.e. if it something like:
//
// (int)(...)
// (x[])(...)
// (X*)(...)
// (X?)(...)
// (global::X)(...)
if (node?.Parent is CastExpressionSyntax castExpression)
{
if (castExpression.Type.Kind() is
SyntaxKind.PredefinedType or
SyntaxKind.ArrayType or
SyntaxKind.PointerType or
SyntaxKind.NullableType)
{
return false;
}
if (castExpression.Type is NameSyntax name && StartsWithAlias(name))
{
return false;
}
var expression = node.Expression;
if (expression.Kind() is
SyntaxKind.UnaryMinusExpression or
SyntaxKind.UnaryPlusExpression or
SyntaxKind.PointerIndirectionExpression or
SyntaxKind.AddressOfExpression)
{
return true;
}
}
return false;
}
private static bool StartsWithAlias(NameSyntax name)
{
if (name.IsKind(SyntaxKind.AliasQualifiedName))
{
return true;
}
if (name is QualifiedNameSyntax qualifiedName)
{
return StartsWithAlias(qualifiedName.Left);
}
return false;
}
private static bool RemovalMayIntroduceCommaListAmbiguity(ParenthesizedExpressionSyntax node)
{
if (IsSimpleOrDottedName(node.Expression))
{
// We can't remove parentheses from an identifier name in the following cases:
// F((x) < x, x > (1 + 2))
// F(x < (x), x > (1 + 2))
// F(x < x, (x) > (1 + 2))
// {(x) < x, x > (1 + 2)}
// {x < (x), x > (1 + 2)}
// {x < x, (x) > (1 + 2)}
if (node.Parent is BinaryExpressionSyntax binaryExpression &&
binaryExpression.Kind() is SyntaxKind.LessThanExpression or SyntaxKind.GreaterThanExpression &&
(binaryExpression.IsParentKind(SyntaxKind.Argument) || binaryExpression.Parent is InitializerExpressionSyntax))
{
if (binaryExpression.IsKind(SyntaxKind.LessThanExpression))
{
if ((binaryExpression.Left == node && IsSimpleOrDottedName(binaryExpression.Right)) ||
(binaryExpression.Right == node && IsSimpleOrDottedName(binaryExpression.Left)))
{
if (IsNextExpressionPotentiallyAmbiguous(binaryExpression))
{
return true;
}
}
return false;
}
else if (binaryExpression.IsKind(SyntaxKind.GreaterThanExpression))
{
if (binaryExpression.Left == node &&
binaryExpression.Right.Kind() is SyntaxKind.ParenthesizedExpression or SyntaxKind.CastExpression)
{
if (IsPreviousExpressionPotentiallyAmbiguous(binaryExpression))
{
return true;
}
}
return false;
}
}
}
else if (node.Expression.IsKind(SyntaxKind.LessThanExpression))
{
// We can't remove parentheses from a less-than expression in the following cases:
// F((x < x), x > (1 + 2))
// {(x < x), x > (1 + 2)}
return IsNextExpressionPotentiallyAmbiguous(node);
}
else if (node.Expression.IsKind(SyntaxKind.GreaterThanExpression))
{
// We can't remove parentheses from a greater-than expression in the following cases:
// F(x < x, (x > (1 + 2)))
// {x < x, (x > (1 + 2))}
return IsPreviousExpressionPotentiallyAmbiguous(node);
}
return false;
}
private static bool IsPreviousExpressionPotentiallyAmbiguous(ExpressionSyntax node)
{
ExpressionSyntax? previousExpression = null;
if (node.Parent is ArgumentSyntax argument)
{
if (argument.Parent is ArgumentListSyntax argumentList)
{
var argumentIndex = argumentList.Arguments.IndexOf(argument);
if (argumentIndex > 0)
{
previousExpression = argumentList.Arguments[argumentIndex - 1].Expression;
}
}
}
else if (node.Parent is InitializerExpressionSyntax initializer)
{
var expressionIndex = initializer.Expressions.IndexOf(node);
if (expressionIndex > 0)
{
previousExpression = initializer.Expressions[expressionIndex - 1];
}
}
if (previousExpression == null ||
previousExpression is not BinaryExpressionSyntax(SyntaxKind.LessThanExpression) lessThanExpression)
{
return false;
}
return (IsSimpleOrDottedName(lessThanExpression.Left)
|| lessThanExpression.Left.IsKind(SyntaxKind.CastExpression))
&& IsSimpleOrDottedName(lessThanExpression.Right);
}
private static bool IsNextExpressionPotentiallyAmbiguous(ExpressionSyntax node)
{
ExpressionSyntax? nextExpression = null;
if (node.Parent is ArgumentSyntax argument)
{
if (argument.Parent is ArgumentListSyntax argumentList)
{
var argumentIndex = argumentList.Arguments.IndexOf(argument);
if (argumentIndex >= 0 && argumentIndex < argumentList.Arguments.Count - 1)
{
nextExpression = argumentList.Arguments[argumentIndex + 1].Expression;
}
}
}
else if (node.Parent is InitializerExpressionSyntax initializer)
{
var expressionIndex = initializer.Expressions.IndexOf(node);
if (expressionIndex >= 0 && expressionIndex < initializer.Expressions.Count - 1)
{
nextExpression = initializer.Expressions[expressionIndex + 1];
}
}
if (nextExpression == null ||
nextExpression is not BinaryExpressionSyntax(SyntaxKind.GreaterThanExpression) greaterThanExpression)
{
return false;
}
return IsSimpleOrDottedName(greaterThanExpression.Left)
&& greaterThanExpression.Right.Kind() is SyntaxKind.ParenthesizedExpression or SyntaxKind.CastExpression;
}
private static bool IsSimpleOrDottedName(ExpressionSyntax expression)
=> expression.Kind() is SyntaxKind.IdentifierName or SyntaxKind.QualifiedName or SyntaxKind.SimpleMemberAccessExpression;
public static bool CanRemoveParentheses(this ParenthesizedPatternSyntax node)
{
if (node.OpenParenToken.IsMissing || node.CloseParenToken.IsMissing)
{
// int x = (3;
return false;
}
var pattern = node.Pattern;
// We wrap a parenthesized pattern and we're parenthesized. We can remove our parens.
if (pattern is ParenthesizedPatternSyntax)
return true;
// We're parenthesized discard pattern. We cannot remove parens.
// x is (_)
if (pattern is DiscardPatternSyntax && node.Parent is IsPatternExpressionSyntax)
return false;
// (not ...) -> not ...
//
// this is safe because unary patterns have the highest precedence, so even if you had:
// (not ...) or (not ...)
//
// you can safely convert to `not ... or not ...`
var patternPrecedence = pattern.GetOperatorPrecedence();
if (patternPrecedence is OperatorPrecedence.Primary or OperatorPrecedence.Unary)
return true;
// We're parenthesized and are inside a parenthesized pattern. We can remove our parens.
// ((x)) -> (x)
if (node.Parent is ParenthesizedPatternSyntax)
return true;
// x is (...) -> x is ...
if (node.Parent is IsPatternExpressionSyntax)
return true;
// (x or y) => ... -> x or y => ...
if (node.Parent is SwitchExpressionArmSyntax)
return true;
// X: (y or z) -> X: y or z
if (node.Parent is SubpatternSyntax)
return true;
// case (x or y): -> case x or y:
if (node.Parent is CasePatternSwitchLabelSyntax)
return true;
// Operator precedence cases:
// - If the parent is not an expression, do not remove parentheses
// - Otherwise, parentheses may be removed if doing so does not change operator associations.
return node.Parent is PatternSyntax patternParent &&
!RemovalChangesAssociation(node, patternParent);
}
private static bool RemovalChangesAssociation(
ParenthesizedPatternSyntax node, PatternSyntax parentPattern)
{
var pattern = node.Pattern;
var precedence = pattern.GetOperatorPrecedence();
var parentPrecedence = parentPattern.GetOperatorPrecedence();
if (precedence == OperatorPrecedence.None || parentPrecedence == OperatorPrecedence.None)
{
// Be conservative if the expression or its parent has no precedence.
return true;
}
// Association always changes if the expression's precedence is lower that its parent.
return precedence < parentPrecedence;
}
public static OperatorPrecedence GetOperatorPrecedence(this PatternSyntax pattern)
{
switch (pattern)
{
case ConstantPatternSyntax:
case DiscardPatternSyntax:
case DeclarationPatternSyntax:
case RecursivePatternSyntax:
case TypePatternSyntax:
case VarPatternSyntax:
return OperatorPrecedence.Primary;
case UnaryPatternSyntax:
case RelationalPatternSyntax:
return OperatorPrecedence.Unary;
case BinaryPatternSyntax binaryPattern:
if (binaryPattern.IsKind(SyntaxKind.AndPattern))
return OperatorPrecedence.ConditionalAnd;
if (binaryPattern.IsKind(SyntaxKind.OrPattern))
return OperatorPrecedence.ConditionalOr;
break;
}
Debug.Fail("Unhandled pattern type");
return OperatorPrecedence.None;
}
}
|