1
ļ»æ// Copyright (c) AlphaSierraPapa for the SharpDevelop Team
3
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
4
// software and associated documentation files (the "Software"), to deal in the Software
5
// without restriction, including without limitation the rights to use, copy, modify, merge,
6
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
7
// to whom the Software is furnished to do so, subject to the following conditions:
9
// The above copyright notice and this permission notice shall be included in all copies or
10
// substantial portions of the Software.
12
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
13
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
15
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
17
// DEALINGS IN THE SOFTWARE.
21
namespace ICSharpCode.NRefactory.CSharp
24
/// Inserts the parentheses into the AST that are needed to ensure the AST can be printed correctly.
25
/// For example, if the AST contains
26
/// BinaryOperatorExpresson(2, Mul, BinaryOperatorExpression(1, Add, 1))); printing that AST
27
/// would incorrectly result in "2 * 1 + 1". By running InsertParenthesesVisitor, the necessary
28
/// parentheses are inserted: "2 * (1 + 1)".
30
public class InsertParenthesesVisitor : DepthFirstAstVisitor
33
/// Gets/Sets whether the visitor should insert parentheses to make the code better looking.
34
/// If this property is false, it will insert parentheses only where strictly required by the language spec.
36
public bool InsertParenthesesForReadability { get; set; }
38
const int Primary = 16;
39
const int QueryOrLambda = 15;
41
const int RelationalAndTypeTesting = 10;
42
const int Equality = 9;
43
const int Conditional = 2;
44
const int Assignment = 1;
47
/// Gets the row number in the C# 4.0 spec operator precedence table.
49
static int GetPrecedence(Expression expr)
51
// Note: the operator precedence table on MSDN is incorrect
52
if (expr is QueryExpression) {
53
// Not part of the table in the C# spec, but we need to ensure that queries within
54
// primary expressions get parenthesized.
57
UnaryOperatorExpression uoe = expr as UnaryOperatorExpression;
59
if (uoe.Operator == UnaryOperatorType.PostDecrement || uoe.Operator == UnaryOperatorType.PostIncrement)
64
if (expr is CastExpression)
66
BinaryOperatorExpression boe = expr as BinaryOperatorExpression;
68
switch (boe.Operator) {
69
case BinaryOperatorType.Multiply:
70
case BinaryOperatorType.Divide:
71
case BinaryOperatorType.Modulus:
72
return 13; // multiplicative
73
case BinaryOperatorType.Add:
74
case BinaryOperatorType.Subtract:
75
return 12; // additive
76
case BinaryOperatorType.ShiftLeft:
77
case BinaryOperatorType.ShiftRight:
79
case BinaryOperatorType.GreaterThan:
80
case BinaryOperatorType.GreaterThanOrEqual:
81
case BinaryOperatorType.LessThan:
82
case BinaryOperatorType.LessThanOrEqual:
83
return RelationalAndTypeTesting;
84
case BinaryOperatorType.Equality:
85
case BinaryOperatorType.InEquality:
87
case BinaryOperatorType.BitwiseAnd:
89
case BinaryOperatorType.ExclusiveOr:
91
case BinaryOperatorType.BitwiseOr:
93
case BinaryOperatorType.ConditionalAnd:
95
case BinaryOperatorType.ConditionalOr:
97
case BinaryOperatorType.NullCoalescing:
100
throw new NotSupportedException("Invalid value for BinaryOperatorType");
103
if (expr is IsExpression || expr is AsExpression)
104
return RelationalAndTypeTesting;
105
if (expr is ConditionalExpression)
107
if (expr is AssignmentExpression || expr is LambdaExpression)
109
// anything else: primary expression
114
/// Parenthesizes the expression if it does not have the minimum required precedence.
116
static void ParenthesizeIfRequired(Expression expr, int minimumPrecedence)
118
if (GetPrecedence(expr) < minimumPrecedence) {
123
static void Parenthesize(Expression expr)
125
expr.ReplaceWith(e => new ParenthesizedExpression { Expression = e });
128
// Primary expressions
129
public override void VisitMemberReferenceExpression(MemberReferenceExpression memberReferenceExpression)
131
ParenthesizeIfRequired(memberReferenceExpression.Target, Primary);
132
base.VisitMemberReferenceExpression(memberReferenceExpression);
135
public override void VisitPointerReferenceExpression(PointerReferenceExpression pointerReferenceExpression)
137
ParenthesizeIfRequired(pointerReferenceExpression.Target, Primary);
138
base.VisitPointerReferenceExpression(pointerReferenceExpression);
141
public override void VisitInvocationExpression(InvocationExpression invocationExpression)
143
ParenthesizeIfRequired(invocationExpression.Target, Primary);
144
base.VisitInvocationExpression(invocationExpression);
147
public override void VisitIndexerExpression(IndexerExpression indexerExpression)
149
ParenthesizeIfRequired(indexerExpression.Target, Primary);
150
ArrayCreateExpression ace = indexerExpression.Target as ArrayCreateExpression;
151
if (ace != null && (InsertParenthesesForReadability || ace.Initializer.IsNull)) {
152
// require parentheses for "(new int[1])[0]"
153
Parenthesize(indexerExpression.Target);
155
base.VisitIndexerExpression(indexerExpression);
159
public override void VisitUnaryOperatorExpression(UnaryOperatorExpression unaryOperatorExpression)
161
ParenthesizeIfRequired(unaryOperatorExpression.Expression, GetPrecedence(unaryOperatorExpression));
162
UnaryOperatorExpression child = unaryOperatorExpression.Expression as UnaryOperatorExpression;
163
if (child != null && InsertParenthesesForReadability)
165
base.VisitUnaryOperatorExpression(unaryOperatorExpression);
168
public override void VisitCastExpression(CastExpression castExpression)
170
ParenthesizeIfRequired(castExpression.Expression, InsertParenthesesForReadability ? Primary : Unary);
171
// There's a nasty issue in the C# grammar: cast expressions including certain operators are ambiguous in some cases
172
// "(int)-1" is fine, but "(A)-b" is not a cast.
173
UnaryOperatorExpression uoe = castExpression.Expression as UnaryOperatorExpression;
174
if (uoe != null && !(uoe.Operator == UnaryOperatorType.BitNot || uoe.Operator == UnaryOperatorType.Not)) {
175
if (TypeCanBeMisinterpretedAsExpression(castExpression.Type)) {
176
Parenthesize(castExpression.Expression);
179
// The above issue can also happen with PrimitiveExpressions representing negative values:
180
PrimitiveExpression pe = castExpression.Expression as PrimitiveExpression;
181
if (pe != null && pe.Value != null && TypeCanBeMisinterpretedAsExpression(castExpression.Type)) {
182
TypeCode typeCode = Type.GetTypeCode(pe.Value.GetType());
185
if ((sbyte)pe.Value < 0)
186
Parenthesize(castExpression.Expression);
189
if ((short)pe.Value < 0)
190
Parenthesize(castExpression.Expression);
193
if ((int)pe.Value < 0)
194
Parenthesize(castExpression.Expression);
197
if ((long)pe.Value < 0)
198
Parenthesize(castExpression.Expression);
200
case TypeCode.Single:
201
if ((float)pe.Value < 0)
202
Parenthesize(castExpression.Expression);
204
case TypeCode.Double:
205
if ((double)pe.Value < 0)
206
Parenthesize(castExpression.Expression);
208
case TypeCode.Decimal:
209
if ((decimal)pe.Value < 0)
210
Parenthesize(castExpression.Expression);
214
base.VisitCastExpression(castExpression);
217
static bool TypeCanBeMisinterpretedAsExpression(AstType type)
219
// SimpleTypes can always be misinterpreted as IdentifierExpressions
220
// MemberTypes can be misinterpreted as MemberReferenceExpressions if they don't use double colon
221
// PrimitiveTypes or ComposedTypes can never be misinterpreted as expressions.
222
MemberType mt = type as MemberType;
224
return !mt.IsDoubleColon;
226
return type is SimpleType;
230
public override void VisitBinaryOperatorExpression(BinaryOperatorExpression binaryOperatorExpression)
232
int precedence = GetPrecedence(binaryOperatorExpression);
233
if (binaryOperatorExpression.Operator == BinaryOperatorType.NullCoalescing) {
234
if (InsertParenthesesForReadability) {
235
ParenthesizeIfRequired(binaryOperatorExpression.Left, Primary);
236
ParenthesizeIfRequired(binaryOperatorExpression.Right, Primary);
238
// ?? is right-associate
239
ParenthesizeIfRequired(binaryOperatorExpression.Left, precedence + 1);
240
ParenthesizeIfRequired(binaryOperatorExpression.Right, precedence);
243
if (InsertParenthesesForReadability && precedence < Equality) {
244
// In readable mode, boost the priority of the left-hand side if the operator
245
// there isn't the same as the operator on this expression.
246
if (GetBinaryOperatorType(binaryOperatorExpression.Left) == binaryOperatorExpression.Operator) {
247
ParenthesizeIfRequired(binaryOperatorExpression.Left, precedence);
249
ParenthesizeIfRequired(binaryOperatorExpression.Left, Equality);
251
ParenthesizeIfRequired(binaryOperatorExpression.Right, Equality);
253
// all other binary operators are left-associative
254
ParenthesizeIfRequired(binaryOperatorExpression.Left, precedence);
255
ParenthesizeIfRequired(binaryOperatorExpression.Right, precedence + 1);
258
base.VisitBinaryOperatorExpression(binaryOperatorExpression);
261
BinaryOperatorType? GetBinaryOperatorType(Expression expr)
263
BinaryOperatorExpression boe = expr as BinaryOperatorExpression;
270
public override void VisitIsExpression(IsExpression isExpression)
272
if (InsertParenthesesForReadability) {
273
// few people know the precedence of 'is', so always put parentheses in nice-looking mode.
274
ParenthesizeIfRequired(isExpression.Expression, Primary);
276
ParenthesizeIfRequired(isExpression.Expression, RelationalAndTypeTesting);
278
base.VisitIsExpression(isExpression);
281
public override void VisitAsExpression(AsExpression asExpression)
283
if (InsertParenthesesForReadability) {
284
// few people know the precedence of 'as', so always put parentheses in nice-looking mode.
285
ParenthesizeIfRequired(asExpression.Expression, Primary);
287
ParenthesizeIfRequired(asExpression.Expression, RelationalAndTypeTesting);
289
base.VisitAsExpression(asExpression);
292
// Conditional operator
293
public override void VisitConditionalExpression(ConditionalExpression conditionalExpression)
295
// Associativity here is a bit tricky:
296
// (a ? b : c ? d : e) == (a ? b : (c ? d : e))
297
// (a ? b ? c : d : e) == (a ? (b ? c : d) : e)
298
// Only ((a ? b : c) ? d : e) strictly needs the additional parentheses
299
if (InsertParenthesesForReadability) {
300
// Precedence of ?: can be confusing; so always put parentheses in nice-looking mode.
301
ParenthesizeIfRequired(conditionalExpression.Condition, Primary);
302
ParenthesizeIfRequired(conditionalExpression.TrueExpression, Primary);
303
ParenthesizeIfRequired(conditionalExpression.FalseExpression, Primary);
305
ParenthesizeIfRequired(conditionalExpression.Condition, Conditional + 1);
306
ParenthesizeIfRequired(conditionalExpression.TrueExpression, Conditional);
307
ParenthesizeIfRequired(conditionalExpression.FalseExpression, Conditional);
309
base.VisitConditionalExpression(conditionalExpression);
312
public override void VisitAssignmentExpression(AssignmentExpression assignmentExpression)
314
// assignment is right-associative
315
ParenthesizeIfRequired(assignmentExpression.Left, Assignment + 1);
316
if (InsertParenthesesForReadability) {
317
ParenthesizeIfRequired(assignmentExpression.Right, RelationalAndTypeTesting + 1);
319
ParenthesizeIfRequired(assignmentExpression.Right, Assignment);
321
base.VisitAssignmentExpression(assignmentExpression);
324
// don't need to handle lambdas, they have lowest precedence and unambiguous associativity
326
public override void VisitQueryExpression(QueryExpression queryExpression)
328
// Query expressions are strange beasts:
329
// "var a = -from b in c select d;" is valid, so queries bind stricter than unary expressions.
330
// However, the end of the query is greedy. So their start sort of has a high precedence,
331
// while their end has a very low precedence. We handle this by checking whether a query is used
332
// as left part of a binary operator, and parenthesize it if required.
333
if (queryExpression.Role == BinaryOperatorExpression.LeftRole)
334
Parenthesize(queryExpression);
335
if (queryExpression.Parent is IsExpression || queryExpression.Parent is AsExpression)
336
Parenthesize(queryExpression);
337
if (InsertParenthesesForReadability) {
338
// when readability is desired, always parenthesize query expressions within unary or binary operators
339
if (queryExpression.Parent is UnaryOperatorExpression || queryExpression.Parent is BinaryOperatorExpression)
340
Parenthesize(queryExpression);
342
base.VisitQueryExpression(queryExpression);