// Copyright (c) 2010-2020 AlphaSierraPapa for the SharpDevelop Team // // Permission is hereby granted, free of charge, to any person obtaining a copy of this // software and associated documentation files (the "Software"), to deal in the Software // without restriction, including without limitation the rights to use, copy, modify, merge, // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons // to whom the Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all copies or // substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; using ICSharpCode.Decompiler.CSharp.Syntax; namespace ICSharpCode.Decompiler.CSharp.OutputVisitor { /// /// Inserts the parentheses into the AST that are needed to ensure the AST can be printed correctly. /// For example, if the AST contains /// BinaryOperatorExpresson(2, Mul, BinaryOperatorExpression(1, Add, 1))); printing that AST /// would incorrectly result in "2 * 1 + 1". By running InsertParenthesesVisitor, the necessary /// parentheses are inserted: "2 * (1 + 1)". /// public class InsertParenthesesVisitor : DepthFirstAstVisitor { /// /// Gets/Sets whether the visitor should insert parentheses to make the code better looking. /// If this property is false, it will insert parentheses only where strictly required by the language spec. /// public bool InsertParenthesesForReadability { get; set; } enum PrecedenceLevel { // Higher integer value = higher precedence. Assignment, Conditional, // ?: NullCoalescing, // ?? ConditionalOr, // || ConditionalAnd, // && BitwiseOr, // | ExclusiveOr, // binary ^ BitwiseAnd, // binary & Equality, // == != RelationalAndTypeTesting, // < <= > >= is Shift, // << >> Additive, // binary + - Multiplicative, // * / % Switch, // C# 8 switch expression Range, // .. Unary, QueryOrLambda, NullableRewrap, Primary } /// /// Gets the row number in the C# 4.0 spec operator precedence table. /// static PrecedenceLevel GetPrecedence(Expression expr) { // Note: the operator precedence table on MSDN is incorrect if (expr is QueryExpression || expr is LambdaExpression) { // Not part of the table in the C# spec, but we need to ensure that queries within // primary expressions get parenthesized. return PrecedenceLevel.QueryOrLambda; } if (expr is UnaryOperatorExpression uoe) { switch (uoe.Operator) { case UnaryOperatorType.PostDecrement: case UnaryOperatorType.PostIncrement: case UnaryOperatorType.NullConditional: case UnaryOperatorType.SuppressNullableWarning: return PrecedenceLevel.Primary; case UnaryOperatorType.NullConditionalRewrap: return PrecedenceLevel.NullableRewrap; case UnaryOperatorType.IsTrue: return PrecedenceLevel.Conditional; default: return PrecedenceLevel.Unary; } } if (expr is CastExpression) return PrecedenceLevel.Unary; if (expr is PrimitiveExpression primitive) { var value = primitive.Value; if (value is int i && i < 0) return PrecedenceLevel.Unary; if (value is long l && l < 0) return PrecedenceLevel.Unary; if (value is float f && f < 0) return PrecedenceLevel.Unary; if (value is double d && d < 0) return PrecedenceLevel.Unary; if (value is decimal de && de < 0) return PrecedenceLevel.Unary; return PrecedenceLevel.Primary; } if (expr is BinaryOperatorExpression boe) { switch (boe.Operator) { case BinaryOperatorType.Range: return PrecedenceLevel.Range; case BinaryOperatorType.Multiply: case BinaryOperatorType.Divide: case BinaryOperatorType.Modulus: return PrecedenceLevel.Multiplicative; case BinaryOperatorType.Add: case BinaryOperatorType.Subtract: return PrecedenceLevel.Additive; case BinaryOperatorType.ShiftLeft: case BinaryOperatorType.ShiftRight: case BinaryOperatorType.UnsignedShiftRight: return PrecedenceLevel.Shift; case BinaryOperatorType.GreaterThan: case BinaryOperatorType.GreaterThanOrEqual: case BinaryOperatorType.LessThan: case BinaryOperatorType.LessThanOrEqual: return PrecedenceLevel.RelationalAndTypeTesting; case BinaryOperatorType.Equality: case BinaryOperatorType.InEquality: return PrecedenceLevel.Equality; case BinaryOperatorType.BitwiseAnd: return PrecedenceLevel.BitwiseAnd; case BinaryOperatorType.ExclusiveOr: return PrecedenceLevel.ExclusiveOr; case BinaryOperatorType.BitwiseOr: return PrecedenceLevel.BitwiseOr; case BinaryOperatorType.ConditionalAnd: return PrecedenceLevel.ConditionalAnd; case BinaryOperatorType.ConditionalOr: return PrecedenceLevel.ConditionalOr; case BinaryOperatorType.NullCoalescing: return PrecedenceLevel.NullCoalescing; case BinaryOperatorType.IsPattern: return PrecedenceLevel.RelationalAndTypeTesting; default: throw new NotSupportedException("Invalid value for BinaryOperatorType"); } } if (expr is SwitchExpression) return PrecedenceLevel.Switch; if (expr is IsExpression || expr is AsExpression) return PrecedenceLevel.RelationalAndTypeTesting; if (expr is ConditionalExpression || expr is DirectionExpression) return PrecedenceLevel.Conditional; if (expr is AssignmentExpression) return PrecedenceLevel.Assignment; // anything else: primary expression return PrecedenceLevel.Primary; } /// /// Parenthesizes the expression if it does not have the minimum required precedence. /// static void ParenthesizeIfRequired(Expression expr, PrecedenceLevel minimumPrecedence) { if (GetPrecedence(expr) < minimumPrecedence) { Parenthesize(expr); } } static void Parenthesize(Expression expr) { expr.ReplaceWith(e => new ParenthesizedExpression { Expression = e }); } // Primary expressions public override void VisitMemberReferenceExpression(MemberReferenceExpression memberReferenceExpression) { ParenthesizeIfRequired(memberReferenceExpression.Target, PrecedenceLevel.Primary); base.VisitMemberReferenceExpression(memberReferenceExpression); } public override void VisitPointerReferenceExpression(PointerReferenceExpression pointerReferenceExpression) { ParenthesizeIfRequired(pointerReferenceExpression.Target, PrecedenceLevel.Primary); base.VisitPointerReferenceExpression(pointerReferenceExpression); } public override void VisitInvocationExpression(InvocationExpression invocationExpression) { ParenthesizeIfRequired(invocationExpression.Target, PrecedenceLevel.Primary); base.VisitInvocationExpression(invocationExpression); } public override void VisitIndexerExpression(IndexerExpression indexerExpression) { ParenthesizeIfRequired(indexerExpression.Target, PrecedenceLevel.Primary); switch (indexerExpression.Target) { case ArrayCreateExpression ace when InsertParenthesesForReadability || ace.Initializer.IsNull: // require parentheses for "(new int[1])[0]" Parenthesize(indexerExpression.Target); break; case StackAllocExpression sae when InsertParenthesesForReadability || sae.Initializer.IsNull: // require parentheses for "(stackalloc int[1])[0]" Parenthesize(indexerExpression.Target); break; } base.VisitIndexerExpression(indexerExpression); } // Unary expressions public override void VisitUnaryOperatorExpression(UnaryOperatorExpression unaryOperatorExpression) { ParenthesizeIfRequired(unaryOperatorExpression.Expression, GetPrecedence(unaryOperatorExpression)); UnaryOperatorExpression child = unaryOperatorExpression.Expression as UnaryOperatorExpression; if (child != null && InsertParenthesesForReadability) Parenthesize(child); base.VisitUnaryOperatorExpression(unaryOperatorExpression); } public override void VisitCastExpression(CastExpression castExpression) { // Even in readability mode, don't parenthesize casts of casts. if (!(castExpression.Expression is CastExpression)) { ParenthesizeIfRequired(castExpression.Expression, InsertParenthesesForReadability ? PrecedenceLevel.NullableRewrap : PrecedenceLevel.Unary); } // There's a nasty issue in the C# grammar: cast expressions including certain operators are ambiguous in some cases // "(int)-1" is fine, but "(A)-b" is not a cast. UnaryOperatorExpression uoe = castExpression.Expression as UnaryOperatorExpression; if (uoe != null && !(uoe.Operator == UnaryOperatorType.BitNot || uoe.Operator == UnaryOperatorType.Not)) { if (TypeCanBeMisinterpretedAsExpression(castExpression.Type)) { Parenthesize(castExpression.Expression); } } // The above issue can also happen with PrimitiveExpressions representing negative values: PrimitiveExpression pe = castExpression.Expression as PrimitiveExpression; if (pe != null && pe.Value != null && TypeCanBeMisinterpretedAsExpression(castExpression.Type)) { TypeCode typeCode = Type.GetTypeCode(pe.Value.GetType()); switch (typeCode) { case TypeCode.SByte: if ((sbyte)pe.Value < 0) Parenthesize(castExpression.Expression); break; case TypeCode.Int16: if ((short)pe.Value < 0) Parenthesize(castExpression.Expression); break; case TypeCode.Int32: if ((int)pe.Value < 0) Parenthesize(castExpression.Expression); break; case TypeCode.Int64: if ((long)pe.Value < 0) Parenthesize(castExpression.Expression); break; case TypeCode.Single: if ((float)pe.Value < 0) Parenthesize(castExpression.Expression); break; case TypeCode.Double: if ((double)pe.Value < 0) Parenthesize(castExpression.Expression); break; case TypeCode.Decimal: if ((decimal)pe.Value < 0) Parenthesize(castExpression.Expression); break; } } base.VisitCastExpression(castExpression); } static bool TypeCanBeMisinterpretedAsExpression(AstType type) { // SimpleTypes can always be misinterpreted as IdentifierExpressions // MemberTypes can be misinterpreted as MemberReferenceExpressions if they don't use double colon // PrimitiveTypes or ComposedTypes can never be misinterpreted as expressions. MemberType mt = type as MemberType; if (mt != null) return !mt.IsDoubleColon; else return type is SimpleType; } // Binary Operators public override void VisitBinaryOperatorExpression(BinaryOperatorExpression binaryOperatorExpression) { PrecedenceLevel precedence = GetPrecedence(binaryOperatorExpression); if (binaryOperatorExpression.Operator == BinaryOperatorType.NullCoalescing) { if (InsertParenthesesForReadability) { ParenthesizeIfRequired(binaryOperatorExpression.Left, PrecedenceLevel.NullableRewrap); if (GetBinaryOperatorType(binaryOperatorExpression.Right) == BinaryOperatorType.NullCoalescing) { ParenthesizeIfRequired(binaryOperatorExpression.Right, precedence); } else { ParenthesizeIfRequired(binaryOperatorExpression.Right, PrecedenceLevel.NullableRewrap); } } else { // ?? is right-associative ParenthesizeIfRequired(binaryOperatorExpression.Left, precedence + 1); ParenthesizeIfRequired(binaryOperatorExpression.Right, precedence); } } else { if (InsertParenthesesForReadability && precedence < PrecedenceLevel.Equality) { // In readable mode, boost the priority of the left-hand side if the operator // there isn't the same as the operator on this expression. PrecedenceLevel boostTo = IsBitwise(binaryOperatorExpression.Operator) ? PrecedenceLevel.Unary : PrecedenceLevel.Equality; if (GetBinaryOperatorType(binaryOperatorExpression.Left) == binaryOperatorExpression.Operator) { ParenthesizeIfRequired(binaryOperatorExpression.Left, precedence); } else { ParenthesizeIfRequired(binaryOperatorExpression.Left, boostTo); } ParenthesizeIfRequired(binaryOperatorExpression.Right, boostTo); } else { // all other binary operators are left-associative ParenthesizeIfRequired(binaryOperatorExpression.Left, precedence); ParenthesizeIfRequired(binaryOperatorExpression.Right, precedence + 1); } } base.VisitBinaryOperatorExpression(binaryOperatorExpression); } static bool IsBitwise(BinaryOperatorType op) { return op == BinaryOperatorType.BitwiseAnd || op == BinaryOperatorType.BitwiseOr || op == BinaryOperatorType.ExclusiveOr; } BinaryOperatorType? GetBinaryOperatorType(Expression expr) { BinaryOperatorExpression boe = expr as BinaryOperatorExpression; if (boe != null) return boe.Operator; else return null; } public override void VisitIsExpression(IsExpression isExpression) { if (InsertParenthesesForReadability) { // few people know the precedence of 'is', so always put parentheses in nice-looking mode. ParenthesizeIfRequired(isExpression.Expression, PrecedenceLevel.NullableRewrap); } else { ParenthesizeIfRequired(isExpression.Expression, PrecedenceLevel.RelationalAndTypeTesting); } base.VisitIsExpression(isExpression); } public override void VisitAsExpression(AsExpression asExpression) { if (InsertParenthesesForReadability) { // few people know the precedence of 'as', so always put parentheses in nice-looking mode. ParenthesizeIfRequired(asExpression.Expression, PrecedenceLevel.NullableRewrap); } else { ParenthesizeIfRequired(asExpression.Expression, PrecedenceLevel.RelationalAndTypeTesting); } base.VisitAsExpression(asExpression); } // Conditional operator public override void VisitConditionalExpression(ConditionalExpression conditionalExpression) { // Inside of string interpolation ?: always needs parentheses. if (conditionalExpression.Parent is Interpolation) { Parenthesize(conditionalExpression); } // Associativity here is a bit tricky: // (a ? b : c ? d : e) == (a ? b : (c ? d : e)) // (a ? b ? c : d : e) == (a ? (b ? c : d) : e) // Only ((a ? b : c) ? d : e) strictly needs the additional parentheses if (InsertParenthesesForReadability && !IsConditionalRefExpression(conditionalExpression)) { // Precedence of ?: can be confusing; so always put parentheses in nice-looking mode. ParenthesizeIfRequired(conditionalExpression.Condition, PrecedenceLevel.NullableRewrap); ParenthesizeIfRequired(conditionalExpression.TrueExpression, PrecedenceLevel.NullableRewrap); ParenthesizeIfRequired(conditionalExpression.FalseExpression, PrecedenceLevel.NullableRewrap); } else { ParenthesizeIfRequired(conditionalExpression.Condition, PrecedenceLevel.Conditional + 1); ParenthesizeIfRequired(conditionalExpression.TrueExpression, PrecedenceLevel.Conditional); ParenthesizeIfRequired(conditionalExpression.FalseExpression, PrecedenceLevel.Conditional); } base.VisitConditionalExpression(conditionalExpression); } private bool IsConditionalRefExpression(ConditionalExpression conditionalExpression) { return conditionalExpression.TrueExpression is DirectionExpression || conditionalExpression.FalseExpression is DirectionExpression; } public override void VisitAssignmentExpression(AssignmentExpression assignmentExpression) { // assignment is right-associative ParenthesizeIfRequired(assignmentExpression.Left, PrecedenceLevel.Assignment + 1); HandleAssignmentRHS(assignmentExpression.Right); base.VisitAssignmentExpression(assignmentExpression); } private void HandleAssignmentRHS(Expression right) { if (InsertParenthesesForReadability && !(right is DirectionExpression)) { ParenthesizeIfRequired(right, PrecedenceLevel.Conditional + 1); } else { ParenthesizeIfRequired(right, PrecedenceLevel.Assignment); } } public override void VisitVariableInitializer(VariableInitializer variableInitializer) { if (!variableInitializer.Initializer.IsNull) HandleAssignmentRHS(variableInitializer.Initializer); base.VisitVariableInitializer(variableInitializer); } // don't need to handle lambdas, they have lowest precedence and unambiguous associativity public override void VisitQueryExpression(QueryExpression queryExpression) { // Query expressions are strange beasts: // "var a = -from b in c select d;" is valid, so queries bind stricter than unary expressions. // However, the end of the query is greedy. So their start sort of has a high precedence, // while their end has a very low precedence. We handle this by checking whether a query is used // as left part of a binary operator, and parenthesize it if required. HandleLambdaOrQuery(queryExpression); base.VisitQueryExpression(queryExpression); } public override void VisitLambdaExpression(LambdaExpression lambdaExpression) { // Lambdas are greedy in the same way as query expressions. HandleLambdaOrQuery(lambdaExpression); base.VisitLambdaExpression(lambdaExpression); } void HandleLambdaOrQuery(Expression expr) { if (expr.Role == BinaryOperatorExpression.LeftRole) Parenthesize(expr); if (expr.Parent is IsExpression || expr.Parent is AsExpression) Parenthesize(expr); if (InsertParenthesesForReadability) { // when readability is desired, always parenthesize query expressions within unary or binary operators if (expr.Parent is UnaryOperatorExpression || expr.Parent is BinaryOperatorExpression) Parenthesize(expr); } } public override void VisitNamedExpression(NamedExpression namedExpression) { if (InsertParenthesesForReadability) { ParenthesizeIfRequired(namedExpression.Expression, PrecedenceLevel.RelationalAndTypeTesting + 1); } base.VisitNamedExpression(namedExpression); } public override void VisitSwitchExpression(SwitchExpression switchExpression) { ParenthesizeIfRequired(switchExpression.Expression, PrecedenceLevel.Switch + 1); base.VisitSwitchExpression(switchExpression); } } }