mirror of
https://github.com/ZuneDev/ZuneUIXTools.git
synced 2026-07-27 13:11:59 -07:00
959 lines
37 KiB
C#
959 lines
37 KiB
C#
// 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.Diagnostics.CodeAnalysis;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
|
using Microsoft.Iris.Render.Extensions;
|
|
using Roslyn.Utilities;
|
|
|
|
namespace Microsoft.CodeAnalysis.CSharp.Extensions;
|
|
|
|
internal static class ParenthesizedExpressionSyntaxExtensions
|
|
{
|
|
public static bool IsKind<TNode>([NotNullWhen(true)] this SyntaxNode? node, SyntaxKind kind, [NotNullWhen(true)] out TNode? result)
|
|
where TNode : SyntaxNode
|
|
{
|
|
if (node.IsKind(kind))
|
|
{
|
|
result = (TNode)node;
|
|
return true;
|
|
}
|
|
|
|
result = null;
|
|
return false;
|
|
}
|
|
|
|
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)
|
|
{
|
|
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.Parent!.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;
|
|
|
|
// Cases:
|
|
// {(x)} -> {x}
|
|
if (nodeParent is InitializerExpressionSyntax)
|
|
{
|
|
// `{ ([]) }` can't become `{ [] }` as `[` in an initializer will be parsed as an index assignment.
|
|
if (tokenAfterParen.IsKind(SyntaxKind.OpenBracketToken))
|
|
return false;
|
|
|
|
// Assignment expressions and collection expressions are not allowed in initializers
|
|
// as they are not parsed as expressions, but as more complex constructs
|
|
if (expression is AssignmentExpressionSyntax)
|
|
return false;
|
|
|
|
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))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (semanticModel is not null &&
|
|
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:
|
|
// 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.Parent!.IsKind(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 and not at all in Iris scripts
|
|
//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 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.Parent!.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.
|
|
|
|
Stack<SyntaxNode> stack = new();
|
|
stack.Push(node.Expression);
|
|
|
|
while (stack.Count > 0)
|
|
{
|
|
var expression = stack.Pop();
|
|
foreach (var nodeOrToken in expression.ChildNodesAndTokens())
|
|
{
|
|
// Note: There's no need drill into other parenthesized expressions, since any colons in them would be unambiguous.
|
|
var childNode = nodeOrToken.AsNode();
|
|
if (childNode is not null)
|
|
{
|
|
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).
|
|
var parentBinaryExpressionKind = parentBinaryExpression.Kind();
|
|
if (IsAssociative(parentBinaryExpressionKind) &&
|
|
parentBinaryExpression.Right == node &&
|
|
node.Expression.IsKind(parentBinaryExpressionKind, out BinaryExpressionSyntax? nodeBinary))
|
|
{
|
|
return parentBinaryExpressionKind is not (SyntaxKind.LogicalAndExpression or SyntaxKind.LogicalOrExpression);
|
|
//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 new System.Exception();
|
|
}
|
|
|
|
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.Parent!.IsKind(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 lessThanExpression ||
|
|
!lessThanExpression.IsKind(SyntaxKind.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 greaterThanExpression ||
|
|
!greaterThanExpression.IsKind(SyntaxKind.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;
|
|
}
|
|
|
|
public static OperatorPrecedence GetOperatorPrecedence(this ExpressionSyntax expression)
|
|
{
|
|
switch (expression.Kind())
|
|
{
|
|
case SyntaxKind.SimpleMemberAccessExpression:
|
|
case SyntaxKind.ConditionalAccessExpression:
|
|
case SyntaxKind.InvocationExpression:
|
|
case SyntaxKind.ElementAccessExpression:
|
|
case SyntaxKind.PostIncrementExpression:
|
|
case SyntaxKind.PostDecrementExpression:
|
|
case SyntaxKind.ObjectCreationExpression:
|
|
case SyntaxKind.ImplicitObjectCreationExpression:
|
|
case SyntaxKind.TypeOfExpression:
|
|
case SyntaxKind.DefaultExpression:
|
|
case SyntaxKind.CheckedExpression:
|
|
case SyntaxKind.UncheckedExpression:
|
|
case SyntaxKind.AnonymousMethodExpression:
|
|
case SyntaxKind.SuppressNullableWarningExpression:
|
|
// unsafe code
|
|
case SyntaxKind.SizeOfExpression:
|
|
case SyntaxKind.PointerMemberAccessExpression:
|
|
// From C# spec, 7.3.1:
|
|
// Primary: x.y x?.y x?[y] f(x) a[x] x++ x-- new typeof default checked unchecked delegate x!
|
|
|
|
return OperatorPrecedence.Primary;
|
|
|
|
case SyntaxKind.UnaryPlusExpression:
|
|
case SyntaxKind.UnaryMinusExpression:
|
|
case SyntaxKind.LogicalNotExpression:
|
|
case SyntaxKind.BitwiseNotExpression:
|
|
case SyntaxKind.PreIncrementExpression:
|
|
case SyntaxKind.PreDecrementExpression:
|
|
case SyntaxKind.CastExpression:
|
|
case SyntaxKind.AwaitExpression:
|
|
// unsafe code.
|
|
case SyntaxKind.PointerIndirectionExpression:
|
|
case SyntaxKind.AddressOfExpression:
|
|
|
|
// From C# spec, 7.3.1:
|
|
// Unary: + - ! ~ ++x --x (T)x await Task
|
|
|
|
return OperatorPrecedence.Unary;
|
|
|
|
case SyntaxKind.RangeExpression:
|
|
// From C# spec, https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/ranges.md#systemrange
|
|
// Range: ..
|
|
|
|
return OperatorPrecedence.Range;
|
|
|
|
case SyntaxKind.MultiplyExpression:
|
|
case SyntaxKind.DivideExpression:
|
|
case SyntaxKind.ModuloExpression:
|
|
// From C# spec, 7.3.1:
|
|
// Multiplicative: * / %
|
|
|
|
return OperatorPrecedence.Multiplicative;
|
|
|
|
case SyntaxKind.AddExpression:
|
|
case SyntaxKind.SubtractExpression:
|
|
// From C# spec, 7.3.1:
|
|
// Additive: + -
|
|
|
|
return OperatorPrecedence.Additive;
|
|
|
|
case SyntaxKind.LeftShiftExpression:
|
|
case SyntaxKind.RightShiftExpression:
|
|
// From C# spec, 7.3.1:
|
|
// Shift: << >>
|
|
|
|
return OperatorPrecedence.Shift;
|
|
|
|
case SyntaxKind.LessThanExpression:
|
|
case SyntaxKind.GreaterThanExpression:
|
|
case SyntaxKind.LessThanOrEqualExpression:
|
|
case SyntaxKind.GreaterThanOrEqualExpression:
|
|
case SyntaxKind.IsExpression:
|
|
case SyntaxKind.AsExpression:
|
|
case SyntaxKind.IsPatternExpression:
|
|
// From C# spec, 7.3.1:
|
|
// Relational and type testing: < > <= >= is as
|
|
|
|
return OperatorPrecedence.RelationalAndTypeTesting;
|
|
|
|
case SyntaxKind.EqualsExpression:
|
|
case SyntaxKind.NotEqualsExpression:
|
|
// From C# spec, 7.3.1:
|
|
// Equality: == !=
|
|
|
|
return OperatorPrecedence.Equality;
|
|
|
|
case SyntaxKind.BitwiseAndExpression:
|
|
// From C# spec, 7.3.1:
|
|
// Logical AND: &
|
|
|
|
return OperatorPrecedence.LogicalAnd;
|
|
|
|
case SyntaxKind.ExclusiveOrExpression:
|
|
// From C# spec, 7.3.1:
|
|
// Logical XOR: ^
|
|
|
|
return OperatorPrecedence.LogicalXor;
|
|
|
|
case SyntaxKind.BitwiseOrExpression:
|
|
// From C# spec, 7.3.1:
|
|
// Logical OR: |
|
|
|
|
return OperatorPrecedence.LogicalOr;
|
|
|
|
case SyntaxKind.LogicalAndExpression:
|
|
// From C# spec, 7.3.1:
|
|
// Conditional AND: &&
|
|
|
|
return OperatorPrecedence.ConditionalAnd;
|
|
|
|
case SyntaxKind.LogicalOrExpression:
|
|
// From C# spec, 7.3.1:
|
|
// Conditional AND: ||
|
|
|
|
return OperatorPrecedence.ConditionalOr;
|
|
|
|
case SyntaxKind.CoalesceExpression:
|
|
// From C# spec, 7.3.1:
|
|
// Null coalescing: ??
|
|
|
|
return OperatorPrecedence.NullCoalescing;
|
|
|
|
case SyntaxKind.ConditionalExpression:
|
|
// From C# spec, 7.3.1:
|
|
// Conditional: ?:
|
|
|
|
return OperatorPrecedence.Conditional;
|
|
|
|
case SyntaxKind.SimpleAssignmentExpression:
|
|
case SyntaxKind.MultiplyAssignmentExpression:
|
|
case SyntaxKind.DivideAssignmentExpression:
|
|
case SyntaxKind.ModuloAssignmentExpression:
|
|
case SyntaxKind.AddAssignmentExpression:
|
|
case SyntaxKind.SubtractAssignmentExpression:
|
|
case SyntaxKind.LeftShiftAssignmentExpression:
|
|
case SyntaxKind.RightShiftAssignmentExpression:
|
|
case SyntaxKind.AndAssignmentExpression:
|
|
case SyntaxKind.ExclusiveOrAssignmentExpression:
|
|
case SyntaxKind.OrAssignmentExpression:
|
|
case SyntaxKind.SimpleLambdaExpression:
|
|
case SyntaxKind.ParenthesizedLambdaExpression:
|
|
// From C# spec, 7.3.1:
|
|
// Conditional: ?:
|
|
|
|
return OperatorPrecedence.AssignmentAndLambdaExpression;
|
|
|
|
case SyntaxKind.SwitchExpression:
|
|
return OperatorPrecedence.Switch;
|
|
|
|
default:
|
|
return OperatorPrecedence.None;
|
|
}
|
|
}
|
|
} |