// 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);
}
}
}