Exemple #1
0
        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
                    }
                })
        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));
        }