mirror of https://github.com/icsharpcode/ILSpy.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
501 lines
18 KiB
501 lines
18 KiB
// 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 |
|
{ |
|
/// <summary> |
|
/// 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)". |
|
/// </summary> |
|
public class InsertParenthesesVisitor : DepthFirstAstVisitor |
|
{ |
|
/// <summary> |
|
/// 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. |
|
/// </summary> |
|
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 |
|
} |
|
|
|
/// <summary> |
|
/// Gets the row number in the C# 4.0 spec operator precedence table. |
|
/// </summary> |
|
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: |
|
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; |
|
} |
|
|
|
/// <summary> |
|
/// Parenthesizes the expression if it does not have the minimum required precedence. |
|
/// </summary> |
|
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); |
|
} |
|
} |
|
}
|
|
|