@@ -42,83 +42,53 @@ pub(crate) fn check<'tcx>(
4242
4343 match op {
4444 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+ }
6252 } ,
6353 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+ }
7363 } ,
7464 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+ }
9272 } ,
9373 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+ }
10383 } ,
10484 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+ }
12292 } ,
12393 BinOpKind :: Rem => check_remainder ( cx, left, right, expr. span , left. span ) ,
12494 _ => ( ) ,
@@ -138,43 +108,75 @@ enum Parens {
138108 Unneeded ,
139109}
140110
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.
142117/// e.g. `0 + if b { 1 } else { 2 } + if b { 3 } else { 4 }` cannot be reduced
143118/// 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.
145124///
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 {
149128 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+ }
156153 } ,
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+ }
160169
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+ } ,
176179 }
177-
178180 Parens :: Needed
179181}
180182
@@ -199,7 +201,7 @@ fn check_remainder(cx: &LateContext<'_>, left: &Expr<'_>, right: &Expr<'_>, span
199201 }
200202}
201203
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 {
203205 if let Some ( Constant :: Int ( v) ) = ConstEvalCtxt :: new ( cx) . eval_simple ( e) . map ( Constant :: peel_refs) {
204206 let check = match * cx. typeck_results ( ) . expr_ty ( e) . peel_refs ( ) . kind ( ) {
205207 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
212214 1 => v == 1 ,
213215 _ => unreachable ! ( ) ,
214216 } {
215- span_ineffective_operation ( cx, span, arg, parens, is_erased) ;
216217 return true ;
217218 }
218219 }
@@ -234,7 +235,13 @@ fn span_ineffective_operation(
234235 expr_snippet. into_owned ( )
235236 } ;
236237 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+ } ,
238245 Parens :: Unneeded => expr_snippet,
239246 } ;
240247
0 commit comments