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 } ;
4
7
use rustc_lint:: { LateContext , LateLintPass } ;
5
8
use rustc_middle:: ty;
6
9
use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
7
10
use rustc_span:: source_map:: Span ;
8
11
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
-
13
12
declare_clippy_lint ! {
14
13
/// ### What it does
15
14
/// Checks for identity operations, e.g., `x + 0`.
@@ -23,11 +22,6 @@ declare_clippy_lint! {
23
22
/// # let x = 1;
24
23
/// x / 1 + 0 * 1 - 0 | 0;
25
24
/// ```
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)
31
25
#[ clippy:: version = "pre 1.29.0" ]
32
26
pub IDENTITY_OP ,
33
27
complexity,
@@ -45,56 +39,73 @@ impl<'tcx> LateLintPass<'tcx> for IdentityOp {
45
39
if !is_allowed ( cx, * cmp, left, right) {
46
40
match cmp. node {
47
41
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 ) ;
52
44
} ,
53
45
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 ) ;
55
47
} ,
56
48
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 ) ;
61
51
} ,
62
- BinOpKind :: Div => check ( cx, right, 1 , expr. span , left. span ) ,
52
+ BinOpKind :: Div => check ( cx, right, 1 , expr. span , left. span , Parens :: Unneeded ) ,
63
53
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 ) ;
72
56
} ,
57
+ BinOpKind :: Rem => check_remainder ( cx, left, right, expr. span , left. span ) ,
73
58
_ => ( ) ,
74
59
}
75
60
}
76
61
}
77
62
}
78
63
}
79
64
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
+ ///
83
76
/// 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 ,
89
89
}
90
- }
91
90
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
+ } ;
97
106
}
107
+
108
+ Parens :: Needed
98
109
}
99
110
100
111
fn 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
115
126
( Some ( FullInt :: U ( lv) ) , Some ( FullInt :: U ( rv) ) ) => lv < rv,
116
127
_ => return ,
117
128
} {
118
- span_ineffective_operation ( cx, span, arg) ;
129
+ span_ineffective_operation ( cx, span, arg, Parens :: Unneeded ) ;
119
130
}
120
131
}
121
132
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 ) {
123
134
if let Some ( Constant :: Int ( v) ) = constant_simple ( cx, cx. typeck_results ( ) , e) . map ( Constant :: peel_refs) {
124
135
let check = match * cx. typeck_results ( ) . expr_ty ( e) . peel_refs ( ) . kind ( ) {
125
136
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) {
132
143
1 => v == 1 ,
133
144
_ => unreachable ! ( ) ,
134
145
} {
135
- span_ineffective_operation ( cx, span, arg) ;
146
+ span_ineffective_operation ( cx, span, arg, parens ) ;
136
147
}
137
148
}
138
149
}
139
150
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 (
142
161
cx,
143
162
IDENTITY_OP ,
144
163
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 ,
149
168
) ;
150
169
}
0 commit comments