public static bool CanRemoveParentheses(this ParenthesizedExpressionSyntax node, SemanticModel semanticModel) { if (node.OpenParenToken.IsMissing || node.CloseParenToken.IsMissing) { // int x = (3; 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 = node.IsParentKind(SyntaxKind.ConstantPattern) ? node.Parent.Parent as ExpressionSyntax : node.Parent 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); } // (x); -> x; if (node.IsParentKind(SyntaxKind.ExpressionStatement)) { return(true); } // => (x) -> => x if (node.IsParentKind(SyntaxKind.ArrowExpressionClause)) { return(true); } // checked((x)) -> checked(x) if (node.IsParentKind(SyntaxKind.CheckedExpression) || node.IsParentKind(SyntaxKind.UncheckedExpression)) { return(true); } // ((x, y)) -> (x, y) if (expression.IsKind(SyntaxKind.TupleExpression)) { return(true); } // int Prop => (x); -> int Prop => x; if (node.Parent is ArrowExpressionClauseSyntax arrowExpressionClause && arrowExpressionClause.Expression == node) { return(true); } // Don't change (x?.Count).GetValueOrDefault() to x?.Count.GetValueOrDefault() if (expression.IsKind(SyntaxKind.ConditionalAccessExpression) && parentExpression is MemberAccessExpressionSyntax) { return(false); } // 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 ((node.IsParentKind(SyntaxKind.EqualsValueClause) && ((EqualsValueClauseSyntax)node.Parent).Value == node) || (node.IsParentKind(SyntaxKind.IfStatement) && ((IfStatementSyntax)node.Parent).Condition == node) || (node.IsParentKind(SyntaxKind.ReturnStatement) && ((ReturnStatementSyntax)node.Parent).Expression == node) || (node.IsParentKind(SyntaxKind.YieldReturnStatement) && ((YieldStatementSyntax)node.Parent).Expression == node) || (node.IsParentKind(SyntaxKind.ThrowStatement) && ((ThrowStatementSyntax)node.Parent).Expression == node) || (node.IsParentKind(SyntaxKind.SwitchStatement) && ((SwitchStatementSyntax)node.Parent).Expression == node) || (node.IsParentKind(SyntaxKind.WhileStatement) && ((WhileStatementSyntax)node.Parent).Condition == node) || (node.IsParentKind(SyntaxKind.DoStatement) && ((DoStatementSyntax)node.Parent).Condition == node) || (node.IsParentKind(SyntaxKind.ForStatement) && ((ForStatementSyntax)node.Parent).Condition == node) || (node.IsParentKind(SyntaxKind.ForEachStatement, SyntaxKind.ForEachVariableStatement) && ((CommonForEachStatementSyntax)node.Parent).Expression == node) || (node.IsParentKind(SyntaxKind.LockStatement) && ((LockStatementSyntax)node.Parent).Expression == node) || (node.IsParentKind(SyntaxKind.UsingStatement) && ((UsingStatementSyntax)node.Parent).Expression == node) || (node.IsParentKind(SyntaxKind.CatchFilterClause) && ((CatchFilterClauseSyntax)node.Parent).FilterExpression == node)) { return(true); } // Handle expression-level ambiguities if (RemovalMayIntroduceCastAmbiguity(node) || RemovalMayIntroduceCommaListAmbiguity(node) || RemovalMayIntroduceInterpolationAmbiguity(node)) { return(false); } // Cases: // (C)(this) -> (C)this if (node.IsParentKind(SyntaxKind.CastExpression) && expression.IsKind(SyntaxKind.ThisExpression)) { return(true); } // Cases: // y((x)) -> y(x) if (node.IsParentKind(SyntaxKind.Argument) && ((ArgumentSyntax)node.Parent).Expression == node) { return(true); } // Cases: // $"{(x)}" -> $"{x}" if (node.IsParentKind(SyntaxKind.Interpolation)) { return(true); } // Cases: // ($"{x}") -> $"{x}" if (expression.IsKind(SyntaxKind.InterpolatedStringExpression)) { return(true); } // Cases: // {(x)} -> {x} if (node.Parent is InitializerExpressionSyntax) { // Assignment expressions are not allowed in initializers if (expression.IsAnyAssignExpression()) { return(false); } return(true); } // Cases: // new {(x)} -> {x} // new { a = (x)} -> { a = x } // new { a = (x = c)} -> { a = x = c } if (node.Parent is AnonymousObjectMemberDeclaratorSyntax anonymousDeclarator) { // Assignment expressions are not allowed unless member is named if (anonymousDeclarator.NameEquals == null && expression.IsAnyAssignExpression()) { return(false); } return(true); } // Cases: // where (x + 1 > 14) -> where x + 1 > 14 if (node.Parent is QueryClauseSyntax) { return(true); } // Cases: // (x) -> x // (x.y) -> x.y if (IsSimpleOrDottedName(expression)) { return(true); } // Cases: // ('') -> '' // ("") -> "" // (false) -> false // (true) -> true // (null) -> null // (1) -> 1 if (expression.IsAnyLiteralExpression()) { return(true); } // x ?? (throw ...) -> x ?? throw ... if (expression.IsKind(SyntaxKind.ThrowExpression) && node.IsParentKind(SyntaxKind.CoalesceExpression) && ((BinaryExpressionSyntax)node.Parent).Right == node) { return(true); } // case (x): -> case x: if (node.IsParentKind(SyntaxKind.CaseSwitchLabel)) { return(true); } // case (x) when y: -> case x when y: if (node.IsParentKind(SyntaxKind.ConstantPattern) && node.Parent.IsParentKind(SyntaxKind.CasePatternSwitchLabel)) { return(true); } // case x when (y): -> case x when y: if (node.IsParentKind(SyntaxKind.WhenClause)) { return(true); } // #if (x) -> #if x if (node.Parent is DirectiveTriviaSyntax) { 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)) { if (expression.IsKind(SyntaxKind.PreIncrementExpression) || expression.IsKind(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); } // 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)); }
public static bool CanRemoveParentheses( this ParenthesizedExpressionSyntax node, SemanticModel semanticModel, CancellationToken cancellationToken) { if (node.OpenParenToken.IsMissing || node.CloseParenToken.IsMissing) { // int x = (3; 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 = node.IsParentKind(SyntaxKind.ConstantPattern) ? node.Parent.Parent as ExpressionSyntax : node.Parent 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 || expression is 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. return(false); } // (throw ...) -> throw ... if (expression.IsKind(SyntaxKind.ThrowExpression)) { return(true); } // (x); -> x; if (node.IsParentKind(SyntaxKind.ExpressionStatement)) { return(true); } // => (x) -> => x if (node.IsParentKind(SyntaxKind.ArrowExpressionClause)) { return(true); } // checked((x)) -> checked(x) if (node.IsParentKind(SyntaxKind.CheckedExpression) || node.IsParentKind(SyntaxKind.UncheckedExpression)) { return(true); } // ((x, y)) -> (x, y) if (expression.IsKind(SyntaxKind.TupleExpression)) { return(true); } // int Prop => (x); -> int Prop => x; if (node.Parent 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 ((node.IsParentKind(SyntaxKind.EqualsValueClause, out EqualsValueClauseSyntax equalsValue) && equalsValue.Value == node) || (node.IsParentKind(SyntaxKind.IfStatement, out IfStatementSyntax ifStatement) && ifStatement.Condition == node) || (node.IsParentKind(SyntaxKind.ReturnStatement, out ReturnStatementSyntax returnStatement) && returnStatement.Expression == node) || (node.IsParentKind(SyntaxKind.YieldReturnStatement, out YieldStatementSyntax yieldStatement) && yieldStatement.Expression == node) || (node.IsParentKind(SyntaxKind.ThrowStatement, out ThrowStatementSyntax throwStatement) && throwStatement.Expression == node) || (node.IsParentKind(SyntaxKind.SwitchStatement, out SwitchStatementSyntax switchStatement) && switchStatement.Expression == node) || (node.IsParentKind(SyntaxKind.WhileStatement, out WhileStatementSyntax whileStatement) && whileStatement.Condition == node) || (node.IsParentKind(SyntaxKind.DoStatement, out DoStatementSyntax doStatement) && doStatement.Condition == node) || (node.IsParentKind(SyntaxKind.ForStatement, out ForStatementSyntax forStatement) && forStatement.Condition == node) || (node.IsParentKind(SyntaxKind.ForEachStatement, SyntaxKind.ForEachVariableStatement) && ((CommonForEachStatementSyntax)node.Parent).Expression == node) || (node.IsParentKind(SyntaxKind.LockStatement, out LockStatementSyntax lockStatement) && lockStatement.Expression == node) || (node.IsParentKind(SyntaxKind.UsingStatement, out UsingStatementSyntax usingStatement) && usingStatement.Expression == node) || (node.IsParentKind(SyntaxKind.CatchFilterClause, out 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 (node.IsParentKind(SyntaxKind.CastExpression) && expression.IsKind(SyntaxKind.ThisExpression)) { return(true); } // Cases: // y((x)) -> y(x) if (node.IsParentKind(SyntaxKind.Argument, out ArgumentSyntax argument) && argument.Expression == node) { return(true); } // Cases: // $"{(x)}" -> $"{x}" if (node.IsParentKind(SyntaxKind.Interpolation)) { return(true); } // Cases: // ($"{x}") -> $"{x}" if (expression.IsKind(SyntaxKind.InterpolatedStringExpression)) { return(true); } // Cases: // {(x)} -> {x} if (node.Parent is InitializerExpressionSyntax) { // Assignment expressions are not allowed in initializers if (expression.IsAnyAssignExpression()) { return(false); } return(true); } // Cases: // new {(x)} -> {x} // new { a = (x)} -> { a = x } // new { a = (x = c)} -> { a = x = c } if (node.Parent is AnonymousObjectMemberDeclaratorSyntax anonymousDeclarator) { // Assignment expressions are not allowed unless member is named if (anonymousDeclarator.NameEquals == null && expression.IsAnyAssignExpression()) { return(false); } return(true); } // Cases: // where (x + 1 > 14) -> where x + 1 > 14 if (node.Parent 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.IsAnyLiteralExpression()) { return(true); } // (this) -> this if (expression.IsKind(SyntaxKind.ThisExpression)) { return(true); } // x ?? (throw ...) -> x ?? throw ... if (expression.IsKind(SyntaxKind.ThrowExpression) && node.IsParentKind(SyntaxKind.CoalesceExpression, out BinaryExpressionSyntax binary) && binary.Right == node) { return(true); } // case (x): -> case x: if (node.IsParentKind(SyntaxKind.CaseSwitchLabel)) { return(true); } // case (x) when y: -> case x when y: if (node.IsParentKind(SyntaxKind.ConstantPattern) && node.Parent.IsParentKind(SyntaxKind.CasePatternSwitchLabel)) { return(true); } // case x when (y): -> case x when y: if (node.IsParentKind(SyntaxKind.WhenClause)) { return(true); } // #if (x) -> #if x if (node.Parent is DirectiveTriviaSyntax) { return(true); } // Switch expression arm // x => (y) if (node.Parent is SwitchExpressionArmSyntax arm && arm.Expression == node) { 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)) { if (expression.IsKind(SyntaxKind.PreIncrementExpression) || expression.IsKind(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)); }