1- use clippy_utils:: get_parent_expr;
2- use clippy_utils:: source:: snippet;
3- use rustc_hir:: { BinOp , BinOpKind , Expr , ExprKind } ;
1+ use clippy_utils:: consts:: { constant_full_int, constant_simple, Constant , FullInt } ;
2+ use clippy_utils:: diagnostics:: span_lint_and_sugg;
3+ use clippy_utils:: source:: snippet_with_applicability;
4+ use clippy_utils:: { clip, unsext} ;
5+ use rustc_errors:: Applicability ;
6+ use rustc_hir:: { BinOp , BinOpKind , Expr , ExprKind , Node } ;
47use rustc_lint:: { LateContext , LateLintPass } ;
58use rustc_middle:: ty;
69use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
710use rustc_span:: source_map:: Span ;
811
9- use clippy_utils:: consts:: { constant_full_int, constant_simple, Constant , FullInt } ;
10- use clippy_utils:: diagnostics:: span_lint;
11- use clippy_utils:: { clip, unsext} ;
12-
1312declare_clippy_lint ! {
1413 /// ### What it does
1514 /// Checks for identity operations, e.g., `x + 0`.
@@ -23,11 +22,6 @@ declare_clippy_lint! {
2322 /// # let x = 1;
2423 /// x / 1 + 0 * 1 - 0 | 0;
2524 /// ```
26- ///
27- /// ### Known problems
28- /// False negatives: `f(0 + if b { 1 } else { 2 } + 3);` is reducible to
29- /// `f(if b { 1 } else { 2 } + 3);`. But the lint doesn't trigger for the code.
30- /// See [#8724](https://github.com/rust-lang/rust-clippy/issues/8724)
3125 #[ clippy:: version = "pre 1.29.0" ]
3226 pub IDENTITY_OP ,
3327 complexity,
@@ -45,56 +39,73 @@ impl<'tcx> LateLintPass<'tcx> for IdentityOp {
4539 if !is_allowed ( cx, * cmp, left, right) {
4640 match cmp. node {
4741 BinOpKind :: Add | BinOpKind :: BitOr | BinOpKind :: BitXor => {
48- if reducible_to_right ( cx, expr, right) {
49- check ( cx, left, 0 , expr. span , right. span ) ;
50- }
51- check ( cx, right, 0 , expr. span , left. span ) ;
42+ check ( cx, left, 0 , expr. span , right. span , needs_parenthesis ( cx, expr, right) ) ;
43+ check ( cx, right, 0 , expr. span , left. span , Parens :: Unneeded ) ;
5244 } ,
5345 BinOpKind :: Shl | BinOpKind :: Shr | BinOpKind :: Sub => {
54- check ( cx, right, 0 , expr. span , left. span ) ;
46+ check ( cx, right, 0 , expr. span , left. span , Parens :: Unneeded ) ;
5547 } ,
5648 BinOpKind :: Mul => {
57- if reducible_to_right ( cx, expr, right) {
58- check ( cx, left, 1 , expr. span , right. span ) ;
59- }
60- check ( cx, right, 1 , expr. span , left. span ) ;
49+ check ( cx, left, 1 , expr. span , right. span , needs_parenthesis ( cx, expr, right) ) ;
50+ check ( cx, right, 1 , expr. span , left. span , Parens :: Unneeded ) ;
6151 } ,
62- BinOpKind :: Div => check ( cx, right, 1 , expr. span , left. span ) ,
52+ BinOpKind :: Div => check ( cx, right, 1 , expr. span , left. span , Parens :: Unneeded ) ,
6353 BinOpKind :: BitAnd => {
64- if reducible_to_right ( cx, expr, right) {
65- check ( cx, left, -1 , expr. span , right. span ) ;
66- }
67- check ( cx, right, -1 , expr. span , left. span ) ;
68- } ,
69- BinOpKind :: Rem => {
70- // Don't call reducible_to_right because N % N is always reducible to 1
71- check_remainder ( cx, left, right, expr. span , left. span ) ;
54+ check ( cx, left, -1 , expr. span , right. span , needs_parenthesis ( cx, expr, right) ) ;
55+ check ( cx, right, -1 , expr. span , left. span , Parens :: Unneeded ) ;
7256 } ,
57+ BinOpKind :: Rem => check_remainder ( cx, left, right, expr. span , left. span ) ,
7358 _ => ( ) ,
7459 }
7560 }
7661 }
7762 }
7863}
7964
80- /// Checks if `left op ..right` can be actually reduced to `right`
81- /// e.g. `0 + if b { 1 } else { 2 } + if b { 3 } else { 4 }`
82- /// cannot be reduced to `if b { 1 } else { 2 } + if b { 3 } else { 4 }`
65+ #[ derive( Copy , Clone ) ]
66+ enum Parens {
67+ Needed ,
68+ Unneeded ,
69+ }
70+
71+ /// Checks if `left op right` needs parenthesis when reduced to `right`
72+ /// e.g. `0 + if b { 1 } else { 2 } + if b { 3 } else { 4 }` cannot be reduced
73+ /// to `if b { 1 } else { 2 } + if b { 3 } else { 4 }` where the `if` could be
74+ /// interpreted as a statement
75+ ///
8376/// See #8724
84- fn reducible_to_right ( cx : & LateContext < ' _ > , binary : & Expr < ' _ > , right : & Expr < ' _ > ) -> bool {
85- if let ExprKind :: If ( ..) | ExprKind :: Match ( ..) | ExprKind :: Block ( ..) | ExprKind :: Loop ( ..) = right. kind {
86- is_toplevel_binary ( cx, binary)
87- } else {
88- true
77+ fn needs_parenthesis ( cx : & LateContext < ' _ > , binary : & Expr < ' _ > , right : & Expr < ' _ > ) -> Parens {
78+ match right. kind {
79+ ExprKind :: Binary ( _, lhs, _) | ExprKind :: Cast ( lhs, _) => {
80+ // ensure we're checking against the leftmost expression of `right`
81+ //
82+ // ~~~ `lhs`
83+ // 0 + {4} * 2
84+ // ~~~~~~~ `right`
85+ return needs_parenthesis ( cx, binary, lhs) ;
86+ } ,
87+ ExprKind :: If ( ..) | ExprKind :: Match ( ..) | ExprKind :: Block ( ..) | ExprKind :: Loop ( ..) => { } ,
88+ _ => return Parens :: Unneeded ,
8989 }
90- }
9190
92- fn is_toplevel_binary ( cx : & LateContext < ' _ > , must_be_binary : & Expr < ' _ > ) -> bool {
93- if let Some ( parent) = get_parent_expr ( cx, must_be_binary) && let ExprKind :: Binary ( ..) = & parent. kind {
94- false
95- } else {
96- true
91+ let mut prev_id = binary. hir_id ;
92+ for ( _, node) in cx. tcx . hir ( ) . parent_iter ( binary. hir_id ) {
93+ if let Node :: Expr ( expr) = node
94+ && let ExprKind :: Binary ( _, lhs, _) | ExprKind :: Cast ( lhs, _) = expr. kind
95+ && lhs. hir_id == prev_id
96+ {
97+ // keep going until we find a node that encompasses left of `binary`
98+ prev_id = expr. hir_id ;
99+ continue ;
100+ }
101+
102+ match node {
103+ Node :: Block ( _) | Node :: Stmt ( _) => break ,
104+ _ => return Parens :: Unneeded ,
105+ } ;
97106 }
107+
108+ Parens :: Needed
98109}
99110
100111fn is_allowed ( cx : & LateContext < ' _ > , cmp : BinOp , left : & Expr < ' _ > , right : & Expr < ' _ > ) -> bool {
@@ -115,11 +126,11 @@ fn check_remainder(cx: &LateContext<'_>, left: &Expr<'_>, right: &Expr<'_>, span
115126 ( Some ( FullInt :: U ( lv) ) , Some ( FullInt :: U ( rv) ) ) => lv < rv,
116127 _ => return ,
117128 } {
118- span_ineffective_operation ( cx, span, arg) ;
129+ span_ineffective_operation ( cx, span, arg, Parens :: Unneeded ) ;
119130 }
120131}
121132
122- fn check ( cx : & LateContext < ' _ > , e : & Expr < ' _ > , m : i8 , span : Span , arg : Span ) {
133+ fn check ( cx : & LateContext < ' _ > , e : & Expr < ' _ > , m : i8 , span : Span , arg : Span , parens : Parens ) {
123134 if let Some ( Constant :: Int ( v) ) = constant_simple ( cx, cx. typeck_results ( ) , e) . map ( Constant :: peel_refs) {
124135 let check = match * cx. typeck_results ( ) . expr_ty ( e) . peel_refs ( ) . kind ( ) {
125136 ty:: Int ( ity) => unsext ( cx. tcx , -1_i128 , ity) ,
@@ -132,19 +143,27 @@ fn check(cx: &LateContext<'_>, e: &Expr<'_>, m: i8, span: Span, arg: Span) {
132143 1 => v == 1 ,
133144 _ => unreachable ! ( ) ,
134145 } {
135- span_ineffective_operation ( cx, span, arg) ;
146+ span_ineffective_operation ( cx, span, arg, parens ) ;
136147 }
137148 }
138149}
139150
140- fn span_ineffective_operation ( cx : & LateContext < ' _ > , span : Span , arg : Span ) {
141- span_lint (
151+ fn span_ineffective_operation ( cx : & LateContext < ' _ > , span : Span , arg : Span , parens : Parens ) {
152+ let mut applicability = Applicability :: MachineApplicable ;
153+ let expr_snippet = snippet_with_applicability ( cx, arg, ".." , & mut applicability) ;
154+
155+ let suggestion = match parens {
156+ Parens :: Needed => format ! ( "({expr_snippet})" ) ,
157+ Parens :: Unneeded => expr_snippet. into_owned ( ) ,
158+ } ;
159+
160+ span_lint_and_sugg (
142161 cx,
143162 IDENTITY_OP ,
144163 span,
145- & format ! (
146- "the operation is ineffective. Consider reducing it to `{}` ",
147- snippet ( cx , arg , ".." )
148- ) ,
164+ "this operation has no effect" ,
165+ "consider reducing it to",
166+ suggestion ,
167+ applicability ,
149168 ) ;
150169}
0 commit comments