1
1
use clippy_utils:: consts:: { constant, Constant } ;
2
2
use clippy_utils:: diagnostics:: span_lint_and_then;
3
3
use clippy_utils:: sugg:: Sugg ;
4
- use clippy_utils:: visitors:: is_const_evaluatable;
5
- use clippy_utils:: { get_item_name, is_expr_named_const, peel_hir_expr_while} ;
4
+ use clippy_utils:: visitors:: { for_each_expr, is_const_evaluatable} ;
5
+ use clippy_utils:: { get_item_name, is_expr_named_const, peel_hir_expr_while, SpanlessEq } ;
6
+ use core:: ops:: ControlFlow ;
6
7
use rustc_errors:: Applicability ;
7
- use rustc_hir:: { BinOpKind , BorrowKind , Expr , ExprKind , UnOp } ;
8
+ use rustc_hir:: { BinOpKind , BorrowKind , Expr , ExprKind , UnOp , Unsafety } ;
8
9
use rustc_lint:: LateContext ;
9
- use rustc_middle:: ty;
10
+ use rustc_middle:: ty:: { self , Ty , TypeFlags , TypeVisitableExt } ;
10
11
11
12
use super :: { FloatCmpConfig , FLOAT_CMP } ;
12
13
@@ -24,33 +25,40 @@ pub(crate) fn check<'tcx>(
24
25
} ;
25
26
26
27
if matches ! ( op, BinOpKind :: Eq | BinOpKind :: Ne )
27
- && let left = peel_hir_expr_while ( left, peel_expr)
28
- && let right = peel_hir_expr_while ( right, peel_expr)
29
- && is_float ( cx, left )
28
+ && let left_red = peel_hir_expr_while ( left, peel_expr)
29
+ && let right_red = peel_hir_expr_while ( right, peel_expr)
30
+ && is_float ( cx, left_red )
30
31
// Don't lint literal comparisons
31
- && !( matches ! ( left . kind, ExprKind :: Lit ( _) ) && matches ! ( right . kind, ExprKind :: Lit ( _) ) )
32
+ && !( matches ! ( left_red . kind, ExprKind :: Lit ( _) ) && matches ! ( right_red . kind, ExprKind :: Lit ( _) ) )
32
33
// Allow comparing the results of signum()
33
- && !( is_signum ( cx, left ) && is_signum ( cx, right ) )
34
+ && !( is_signum ( cx, left_red ) && is_signum ( cx, right_red ) )
34
35
{
35
- let left_c = constant ( cx, cx. typeck_results ( ) , left ) ;
36
+ let left_c = constant ( cx, cx. typeck_results ( ) , left_red ) ;
36
37
let is_left_const = left_c. is_some ( ) ;
37
38
if left_c. is_some_and ( |c| is_allowed ( & c) ) {
38
39
return ;
39
40
}
40
- let right_c = constant ( cx, cx. typeck_results ( ) , right ) ;
41
+ let right_c = constant ( cx, cx. typeck_results ( ) , right_red ) ;
41
42
let is_right_const = right_c. is_some ( ) ;
42
43
if right_c. is_some_and ( |c| is_allowed ( & c) ) {
43
44
return ;
44
45
}
45
46
46
47
if config. ignore_constant_comparisons
47
- && ( is_left_const || is_const_evaluatable ( cx, left ) )
48
- && ( is_right_const || is_const_evaluatable ( cx, right ) )
48
+ && ( is_left_const || is_const_evaluatable ( cx, left_red ) )
49
+ && ( is_right_const || is_const_evaluatable ( cx, right_red ) )
49
50
{
50
51
return ;
51
52
}
52
53
53
- if config. ignore_named_constants && ( is_expr_named_const ( cx, left) || is_expr_named_const ( cx, right) ) {
54
+ if config. ignore_named_constants && ( is_expr_named_const ( cx, left_red) || is_expr_named_const ( cx, right_red) ) {
55
+ return ;
56
+ }
57
+
58
+ if config. ignore_change_detection
59
+ && ( ( is_pure_expr ( cx, left_red) && contains_expr ( cx, right, left) )
60
+ || ( is_pure_expr ( cx, right_red) && contains_expr ( cx, left, right) ) )
61
+ {
54
62
return ;
55
63
}
56
64
@@ -60,7 +68,7 @@ pub(crate) fn check<'tcx>(
60
68
return ;
61
69
}
62
70
}
63
- let is_comparing_arrays = is_array ( cx, left ) || is_array ( cx, right ) ;
71
+ let is_comparing_arrays = is_array ( cx, left_red ) || is_array ( cx, right_red ) ;
64
72
let msg = if is_comparing_arrays {
65
73
"strict comparison of `f32` or `f64` arrays"
66
74
} else {
@@ -105,6 +113,78 @@ fn is_allowed(val: &Constant<'_>) -> bool {
105
113
}
106
114
}
107
115
116
+ // This is a best effort guess and may have false positives and negatives.
117
+ fn is_pure_expr < ' tcx > ( cx : & LateContext < ' tcx > , e : & ' tcx Expr < ' _ > ) -> bool {
118
+ match e. kind {
119
+ ExprKind :: Path ( _) | ExprKind :: Lit ( _) => true ,
120
+ ExprKind :: Field ( e, _) | ExprKind :: Cast ( e, _) | ExprKind :: Repeat ( e, _) => is_pure_expr ( cx, e) ,
121
+ ExprKind :: Tup ( args) => args. iter ( ) . all ( |arg| is_pure_expr ( cx, arg) ) ,
122
+ ExprKind :: Struct ( _, fields, base) => {
123
+ base. map_or ( true , |base| is_pure_expr ( cx, base) ) && fields. iter ( ) . all ( |f| is_pure_expr ( cx, f. expr ) )
124
+ } ,
125
+
126
+ // Since rust doesn't actually have the concept of a pure function we
127
+ // have to guess whether it's likely pure from the signature of the
128
+ // function.
129
+ ExprKind :: Unary ( _, e) => is_pure_arg_ty ( cx, cx. typeck_results ( ) . expr_ty_adjusted ( e) ) && is_pure_expr ( cx, e) ,
130
+ ExprKind :: Binary ( _, x, y) | ExprKind :: Index ( x, y, _) => {
131
+ is_pure_arg_ty ( cx, cx. typeck_results ( ) . expr_ty_adjusted ( x) )
132
+ && is_pure_arg_ty ( cx, cx. typeck_results ( ) . expr_ty_adjusted ( y) )
133
+ && is_pure_expr ( cx, x)
134
+ && is_pure_expr ( cx, y)
135
+ } ,
136
+ ExprKind :: MethodCall ( _, recv, args, _) => {
137
+ is_pure_arg_ty ( cx, cx. typeck_results ( ) . expr_ty_adjusted ( recv) )
138
+ && is_pure_expr ( cx, recv)
139
+ && cx. typeck_results ( ) . type_dependent_def_id ( e. hir_id ) . is_some_and ( |did| {
140
+ matches ! (
141
+ cx. tcx. fn_sig( did) . skip_binder( ) . skip_binder( ) . unsafety,
142
+ Unsafety :: Normal
143
+ )
144
+ } )
145
+ && args
146
+ . iter ( )
147
+ . all ( |arg| is_pure_arg_ty ( cx, cx. typeck_results ( ) . expr_ty_adjusted ( arg) ) && is_pure_expr ( cx, arg) )
148
+ } ,
149
+ ExprKind :: Call ( f, args @ [ _, ..] ) => {
150
+ is_pure_expr ( cx, f)
151
+ && is_safe_fn ( cx, f)
152
+ && args
153
+ . iter ( )
154
+ . all ( |arg| is_pure_arg_ty ( cx, cx. typeck_results ( ) . expr_ty_adjusted ( arg) ) && is_pure_expr ( cx, arg) )
155
+ } ,
156
+
157
+ _ => false ,
158
+ }
159
+ }
160
+
161
+ fn is_safe_fn < ' tcx > ( cx : & LateContext < ' tcx > , e : & Expr < ' tcx > ) -> bool {
162
+ let sig = match * cx. typeck_results ( ) . expr_ty ( e) . kind ( ) {
163
+ ty:: FnDef ( did, _) => cx. tcx . fn_sig ( did) . skip_binder ( ) ,
164
+ ty:: FnPtr ( sig) => sig,
165
+ _ => return true ,
166
+ } ;
167
+ matches ! ( sig. skip_binder( ) . unsafety, Unsafety :: Normal )
168
+ }
169
+
170
+ fn is_pure_arg_ty < ' tcx > ( cx : & LateContext < ' tcx > , ty : Ty < ' tcx > ) -> bool {
171
+ !ty. is_mutable_ptr ( )
172
+ && ty. is_copy_modulo_regions ( cx. tcx , cx. param_env )
173
+ && ( ty. peel_refs ( ) . is_freeze ( cx. tcx , cx. param_env )
174
+ || !ty. has_type_flags ( TypeFlags :: HAS_FREE_REGIONS | TypeFlags :: HAS_RE_ERASED | TypeFlags :: HAS_RE_BOUND ) )
175
+ }
176
+
177
+ fn contains_expr < ' tcx > ( cx : & LateContext < ' tcx > , corpus : & ' tcx Expr < ' tcx > , e : & ' tcx Expr < ' tcx > ) -> bool {
178
+ for_each_expr ( corpus, |corpus| {
179
+ if SpanlessEq :: new ( cx) . eq_expr ( corpus, e) {
180
+ ControlFlow :: Break ( ( ) )
181
+ } else {
182
+ ControlFlow :: Continue ( ( ) )
183
+ }
184
+ } )
185
+ . is_some ( )
186
+ }
187
+
108
188
// Return true if `expr` is the result of `signum()` invoked on a float value.
109
189
fn is_signum ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > ) -> bool {
110
190
if let ExprKind :: MethodCall ( method_name, self_arg, ..) = expr. kind
0 commit comments