@@ -42,83 +42,53 @@ pub(crate) fn check<'tcx>(
42
42
43
43
match op {
44
44
BinOpKind :: Add | BinOpKind :: BitOr | BinOpKind :: BitXor => {
45
- let _ = check_op (
46
- cx,
47
- left,
48
- 0 ,
49
- expr. span ,
50
- peeled_right_span,
51
- needs_parenthesis ( cx, expr, right) ,
52
- right_is_coerced_to_value,
53
- ) || check_op (
54
- cx,
55
- right,
56
- 0 ,
57
- expr. span ,
58
- peeled_left_span,
59
- Parens :: Unneeded ,
60
- left_is_coerced_to_value,
61
- ) ;
45
+ if is_redundant_op ( cx, left, 0 ) {
46
+ let paren = needs_parenthesis ( cx, expr, right) ;
47
+ span_ineffective_operation ( cx, expr. span , peeled_right_span, paren, right_is_coerced_to_value) ;
48
+ } else if is_redundant_op ( cx, right, 0 ) {
49
+ let paren = needs_parenthesis ( cx, expr, left) ;
50
+ span_ineffective_operation ( cx, expr. span , peeled_left_span, paren, left_is_coerced_to_value) ;
51
+ }
62
52
} ,
63
53
BinOpKind :: Shl | BinOpKind :: Shr | BinOpKind :: Sub => {
64
- let _ = check_op (
65
- cx ,
66
- right ,
67
- 0 ,
68
- expr . span ,
69
- peeled_left_span ,
70
- Parens :: Unneeded ,
71
- left_is_coerced_to_value ,
72
- ) ;
54
+ if is_redundant_op ( cx , right , 0 ) {
55
+ span_ineffective_operation (
56
+ cx ,
57
+ expr . span ,
58
+ peeled_left_span ,
59
+ Parens :: Unneeded ,
60
+ left_is_coerced_to_value ,
61
+ ) ;
62
+ }
73
63
} ,
74
64
BinOpKind :: Mul => {
75
- let _ = check_op (
76
- cx,
77
- left,
78
- 1 ,
79
- expr. span ,
80
- peeled_right_span,
81
- needs_parenthesis ( cx, expr, right) ,
82
- right_is_coerced_to_value,
83
- ) || check_op (
84
- cx,
85
- right,
86
- 1 ,
87
- expr. span ,
88
- peeled_left_span,
89
- Parens :: Unneeded ,
90
- left_is_coerced_to_value,
91
- ) ;
65
+ if is_redundant_op ( cx, left, 1 ) {
66
+ let paren = needs_parenthesis ( cx, expr, right) ;
67
+ span_ineffective_operation ( cx, expr. span , peeled_right_span, paren, right_is_coerced_to_value) ;
68
+ } else if is_redundant_op ( cx, right, 1 ) {
69
+ let paren = needs_parenthesis ( cx, expr, left) ;
70
+ span_ineffective_operation ( cx, expr. span , peeled_left_span, paren, left_is_coerced_to_value) ;
71
+ }
92
72
} ,
93
73
BinOpKind :: Div => {
94
- let _ = check_op (
95
- cx ,
96
- right ,
97
- 1 ,
98
- expr . span ,
99
- peeled_left_span ,
100
- Parens :: Unneeded ,
101
- left_is_coerced_to_value ,
102
- ) ;
74
+ if is_redundant_op ( cx , right , 1 ) {
75
+ span_ineffective_operation (
76
+ cx ,
77
+ expr . span ,
78
+ peeled_left_span ,
79
+ Parens :: Unneeded ,
80
+ left_is_coerced_to_value ,
81
+ ) ;
82
+ }
103
83
} ,
104
84
BinOpKind :: BitAnd => {
105
- let _ = check_op (
106
- cx,
107
- left,
108
- -1 ,
109
- expr. span ,
110
- peeled_right_span,
111
- needs_parenthesis ( cx, expr, right) ,
112
- right_is_coerced_to_value,
113
- ) || check_op (
114
- cx,
115
- right,
116
- -1 ,
117
- expr. span ,
118
- peeled_left_span,
119
- Parens :: Unneeded ,
120
- left_is_coerced_to_value,
121
- ) ;
85
+ if is_redundant_op ( cx, left, -1 ) {
86
+ let paren = needs_parenthesis ( cx, expr, right) ;
87
+ span_ineffective_operation ( cx, expr. span , peeled_right_span, paren, right_is_coerced_to_value) ;
88
+ } else if is_redundant_op ( cx, right, -1 ) {
89
+ let paren = needs_parenthesis ( cx, expr, left) ;
90
+ span_ineffective_operation ( cx, expr. span , peeled_left_span, paren, left_is_coerced_to_value) ;
91
+ }
122
92
} ,
123
93
BinOpKind :: Rem => check_remainder ( cx, left, right, expr. span , left. span ) ,
124
94
_ => ( ) ,
@@ -138,43 +108,75 @@ enum Parens {
138
108
Unneeded ,
139
109
}
140
110
141
- /// Checks if `left op right` needs parenthesis when reduced to `right`
111
+ /// Checks if a binary expression needs parenthesis when reduced to just its
112
+ /// right or left child.
113
+ ///
114
+ /// e.g. `-(x + y + 0)` cannot be reduced to `-x + y`, as the behavior changes silently.
115
+ /// e.g. `1u64 + ((x + y + 0i32) as u64)` cannot be reduced to `1u64 + x + y as u64`, since
116
+ /// the the cast expression will not apply to the same expression.
142
117
/// e.g. `0 + if b { 1 } else { 2 } + if b { 3 } else { 4 }` cannot be reduced
143
118
/// to `if b { 1 } else { 2 } + if b { 3 } else { 4 }` where the `if` could be
144
- /// interpreted as a statement
119
+ /// interpreted as a statement. The same behavior happens for `match`, `loop`,
120
+ /// and blocks.
121
+ /// e.g. `2 * (0 + { a })` can be reduced to `2 * { a }` without the need for parenthesis,
122
+ /// but `1 * ({ a } + 4)` cannot be reduced to `{ a } + 4`, as a block at the start of a line
123
+ /// will be interpreted as a statement instead of an expression.
145
124
///
146
- /// See #8724
147
- fn needs_parenthesis ( cx : & LateContext < ' _ > , binary : & Expr < ' _ > , right : & Expr < ' _ > ) -> Parens {
148
- match right . kind {
125
+ /// See #8724, #13470
126
+ fn needs_parenthesis ( cx : & LateContext < ' _ > , binary : & Expr < ' _ > , child : & Expr < ' _ > ) -> Parens {
127
+ match child . kind {
149
128
ExprKind :: Binary ( _, lhs, _) | ExprKind :: Cast ( lhs, _) => {
150
- // ensure we're checking against the leftmost expression of `right`
151
- //
152
- // ~~~ `lhs`
153
- // 0 + {4} * 2
154
- // ~~~~~~~ `right`
155
- return needs_parenthesis ( cx, binary, lhs) ;
129
+ // For casts and binary expressions, we want to add parenthesis if
130
+ // the parent HIR node is an expression with a different precedence,
131
+ // or if the parent HIR node is a Block or Stmt, and the new left hand side
132
+ // would be treated as a statement rather than an expression.
133
+ if let Some ( ( _, parent) ) = cx. tcx . hir ( ) . parent_iter ( binary. hir_id ) . next ( ) {
134
+ if let Node :: Expr ( expr) = parent {
135
+ if child. precedence ( ) . order ( ) != expr. precedence ( ) . order ( ) {
136
+ return Parens :: Needed ;
137
+ }
138
+ return Parens :: Unneeded ;
139
+ }
140
+ match parent {
141
+ Node :: Block ( _) | Node :: Stmt ( _) => {
142
+ // ensure we're checking against the leftmost expression of `child`
143
+ //
144
+ // ~~~~~~~~~~~ `binary`
145
+ // ~~~ `lhs`
146
+ // 0 + {4} * 2
147
+ // ~~~~~~~ `child`
148
+ return needs_parenthesis ( cx, binary, lhs) ;
149
+ } ,
150
+ _ => return Parens :: Unneeded ,
151
+ }
152
+ }
156
153
} ,
157
- ExprKind :: If ( ..) | ExprKind :: Match ( ..) | ExprKind :: Block ( ..) | ExprKind :: Loop ( ..) => { } ,
158
- _ => return Parens :: Unneeded ,
159
- }
154
+ ExprKind :: If ( ..) | ExprKind :: Match ( ..) | ExprKind :: Block ( ..) | ExprKind :: Loop ( ..) => {
155
+ // For if, match, block, and loop expressions, we want to add parenthesis if
156
+ // the closest ancestor node that is not an expression is a block or statement.
157
+ // This would mean that the rustfix suggestion will appear at the start of a line, which causes
158
+ // these expressions to be interpreted as statements if they do not have parenthesis.
159
+ let mut prev_id = binary. hir_id ;
160
+ for ( _, parent) in cx. tcx . hir ( ) . parent_iter ( binary. hir_id ) {
161
+ if let Node :: Expr ( expr) = parent
162
+ && let ExprKind :: Binary ( _, lhs, _) | ExprKind :: Cast ( lhs, _) | ExprKind :: Unary ( _, lhs) = expr. kind
163
+ && lhs. hir_id == prev_id
164
+ {
165
+ // keep going until we find a node that encompasses left of `binary`
166
+ prev_id = expr. hir_id ;
167
+ continue ;
168
+ }
160
169
161
- let mut prev_id = binary. hir_id ;
162
- for ( _, node) in cx. tcx . hir ( ) . parent_iter ( binary. hir_id ) {
163
- if let Node :: Expr ( expr) = node
164
- && let ExprKind :: Binary ( _, lhs, _) | ExprKind :: Cast ( lhs, _) = expr. kind
165
- && lhs. hir_id == prev_id
166
- {
167
- // keep going until we find a node that encompasses left of `binary`
168
- prev_id = expr. hir_id ;
169
- continue ;
170
- }
171
-
172
- match node {
173
- Node :: Block ( _) | Node :: Stmt ( _) => break ,
174
- _ => return Parens :: Unneeded ,
175
- } ;
170
+ match parent {
171
+ Node :: Block ( _) | Node :: Stmt ( _) => return Parens :: Needed ,
172
+ _ => return Parens :: Unneeded ,
173
+ } ;
174
+ }
175
+ } ,
176
+ _ => {
177
+ return Parens :: Unneeded ;
178
+ } ,
176
179
}
177
-
178
180
Parens :: Needed
179
181
}
180
182
@@ -199,7 +201,7 @@ fn check_remainder(cx: &LateContext<'_>, left: &Expr<'_>, right: &Expr<'_>, span
199
201
}
200
202
}
201
203
202
- fn check_op ( cx : & LateContext < ' _ > , e : & Expr < ' _ > , m : i8 , span : Span , arg : Span , parens : Parens , is_erased : bool ) -> bool {
204
+ fn is_redundant_op ( cx : & LateContext < ' _ > , e : & Expr < ' _ > , m : i8 ) -> bool {
203
205
if let Some ( Constant :: Int ( v) ) = ConstEvalCtxt :: new ( cx) . eval_simple ( e) . map ( Constant :: peel_refs) {
204
206
let check = match * cx. typeck_results ( ) . expr_ty ( e) . peel_refs ( ) . kind ( ) {
205
207
ty:: Int ( ity) => unsext ( cx. tcx , -1_i128 , ity) ,
@@ -212,7 +214,6 @@ fn check_op(cx: &LateContext<'_>, e: &Expr<'_>, m: i8, span: Span, arg: Span, pa
212
214
1 => v == 1 ,
213
215
_ => unreachable ! ( ) ,
214
216
} {
215
- span_ineffective_operation ( cx, span, arg, parens, is_erased) ;
216
217
return true ;
217
218
}
218
219
}
@@ -234,7 +235,13 @@ fn span_ineffective_operation(
234
235
expr_snippet. into_owned ( )
235
236
} ;
236
237
let suggestion = match parens {
237
- Parens :: Needed => format ! ( "({expr_snippet})" ) ,
238
+ Parens :: Needed => {
239
+ if !expr_snippet. starts_with ( '(' ) && !expr_snippet. ends_with ( ')' ) {
240
+ format ! ( "({expr_snippet})" )
241
+ } else {
242
+ expr_snippet
243
+ }
244
+ } ,
238
245
Parens :: Unneeded => expr_snippet,
239
246
} ;
240
247
0 commit comments