1
- use clippy_utils:: consts:: { constant_with_source , Constant } ;
1
+ use clippy_utils:: consts:: { constant , Constant } ;
2
2
use clippy_utils:: diagnostics:: span_lint_and_then;
3
- use clippy_utils:: get_item_name;
4
3
use clippy_utils:: sugg:: Sugg ;
4
+ use clippy_utils:: visitors:: { for_each_expr_without_closures, is_const_evaluatable} ;
5
+ use clippy_utils:: { get_item_name, is_expr_named_const, path_res, peel_hir_expr_while, SpanlessEq } ;
6
+ use core:: ops:: ControlFlow ;
5
7
use rustc_errors:: Applicability ;
6
- use rustc_hir:: { BinOpKind , Expr , ExprKind , UnOp } ;
8
+ use rustc_hir:: def:: Res ;
9
+ use rustc_hir:: { BinOpKind , BorrowKind , Expr , ExprKind , Safety , UnOp } ;
7
10
use rustc_lint:: LateContext ;
8
- use rustc_middle:: ty;
11
+ use rustc_middle:: ty:: { self , Ty , TypeFlags , TypeVisitableExt } ;
9
12
10
- use super :: { FLOAT_CMP , FLOAT_CMP_CONST } ;
13
+ use super :: { FloatCmpConfig , FLOAT_CMP } ;
11
14
12
15
pub ( crate ) fn check < ' tcx > (
13
16
cx : & LateContext < ' tcx > ,
17
+ config : FloatCmpConfig ,
14
18
expr : & ' tcx Expr < ' _ > ,
15
19
op : BinOpKind ,
16
20
left : & ' tcx Expr < ' _ > ,
17
21
right : & ' tcx Expr < ' _ > ,
18
22
) {
19
- if ( op == BinOpKind :: Eq || op == BinOpKind :: Ne ) && is_float ( cx, left) {
20
- let left_is_local = match constant_with_source ( cx, cx. typeck_results ( ) , left) {
21
- Some ( ( c, s) ) if !is_allowed ( & c) => s. is_local ( ) ,
22
- Some ( _) => return ,
23
- None => true ,
24
- } ;
25
- let right_is_local = match constant_with_source ( cx, cx. typeck_results ( ) , right) {
26
- Some ( ( c, s) ) if !is_allowed ( & c) => s. is_local ( ) ,
27
- Some ( _) => return ,
28
- None => true ,
29
- } ;
23
+ let peel_expr = |e : & ' tcx Expr < ' tcx > | match e. kind {
24
+ ExprKind :: Cast ( e, _) | ExprKind :: AddrOf ( BorrowKind :: Ref , _, e) | ExprKind :: Unary ( UnOp :: Neg , e) => Some ( e) ,
25
+ _ => None ,
26
+ } ;
30
27
28
+ if matches ! ( op, BinOpKind :: Eq | BinOpKind :: Ne )
29
+ && let left_reduced = peel_hir_expr_while ( left, peel_expr)
30
+ && let right_reduced = peel_hir_expr_while ( right, peel_expr)
31
+ && is_float ( cx, left_reduced)
32
+ // Don't lint literal comparisons
33
+ && !( matches ! ( left_reduced. kind, ExprKind :: Lit ( _) ) && matches ! ( right_reduced. kind, ExprKind :: Lit ( _) ) )
31
34
// Allow comparing the results of signum()
32
- if is_signum ( cx, left) && is_signum ( cx, right) {
35
+ && !( is_signum ( cx, left_reduced) && is_signum ( cx, right_reduced) )
36
+ && match ( path_res ( cx, left_reduced) , path_res ( cx, right_reduced) ) {
37
+ ( Res :: Err , _) | ( _, Res :: Err ) => true ,
38
+ ( left, right) => left != right,
39
+ }
40
+ {
41
+ let left_c = constant ( cx, cx. typeck_results ( ) , left_reduced) ;
42
+ let is_left_const = left_c. is_some ( ) ;
43
+ if left_c. is_some_and ( |c| is_allowed ( & c) ) {
44
+ return ;
45
+ }
46
+ let right_c = constant ( cx, cx. typeck_results ( ) , right_reduced) ;
47
+ let is_right_const = right_c. is_some ( ) ;
48
+ if right_c. is_some_and ( |c| is_allowed ( & c) ) {
49
+ return ;
50
+ }
51
+
52
+ if config. ignore_constant_comparisons
53
+ && ( is_left_const || is_const_evaluatable ( cx, left_reduced) )
54
+ && ( is_right_const || is_const_evaluatable ( cx, right_reduced) )
55
+ {
56
+ return ;
57
+ }
58
+
59
+ if config. ignore_named_constants
60
+ && ( is_expr_named_const ( cx, left_reduced) || is_expr_named_const ( cx, right_reduced) )
61
+ {
62
+ return ;
63
+ }
64
+
65
+ if config. ignore_change_detection
66
+ && ( ( is_pure_expr ( cx, left_reduced) && contains_expr ( cx, right, left) )
67
+ || ( is_pure_expr ( cx, right_reduced) && contains_expr ( cx, left, right) ) )
68
+ {
33
69
return ;
34
70
}
35
71
36
72
if let Some ( name) = get_item_name ( cx, expr) {
37
73
let name = name. as_str ( ) ;
38
- if name == "eq" || name == "ne" || name == "is_nan" || name . starts_with ( "eq_" ) || name. ends_with ( "_eq" ) {
74
+ if name == "eq" || name == "ne" || name. starts_with ( "eq_" ) || name. ends_with ( "_eq" ) {
39
75
return ;
40
76
}
41
77
}
42
- let is_comparing_arrays = is_array ( cx, left) || is_array ( cx, right) ;
43
- let ( lint, msg) = get_lint_and_message ( left_is_local && right_is_local, is_comparing_arrays) ;
44
- span_lint_and_then ( cx, lint, expr. span , msg, |diag| {
78
+ let is_comparing_arrays = is_array ( cx, left_reduced) || is_array ( cx, right_reduced) ;
79
+ let msg = if is_comparing_arrays {
80
+ "strict comparison of `f32` or `f64` arrays"
81
+ } else {
82
+ "strict comparison of `f32` or `f64`"
83
+ } ;
84
+ span_lint_and_then ( cx, FLOAT_CMP , expr. span , msg, |diag| {
45
85
let lhs = Sugg :: hir ( cx, left, ".." ) ;
46
86
let rhs = Sugg :: hir ( cx, right, ".." ) ;
47
87
@@ -61,54 +101,105 @@ pub(crate) fn check<'tcx>(
61
101
}
62
102
}
63
103
64
- fn get_lint_and_message ( is_local : bool , is_comparing_arrays : bool ) -> ( & ' static rustc_lint:: Lint , & ' static str ) {
65
- if is_local {
66
- (
67
- FLOAT_CMP ,
68
- if is_comparing_arrays {
69
- "strict comparison of `f32` or `f64` arrays"
70
- } else {
71
- "strict comparison of `f32` or `f64`"
72
- } ,
73
- )
74
- } else {
75
- (
76
- FLOAT_CMP_CONST ,
77
- if is_comparing_arrays {
78
- "strict comparison of `f32` or `f64` constant arrays"
79
- } else {
80
- "strict comparison of `f32` or `f64` constant"
81
- } ,
82
- )
83
- }
84
- }
85
-
86
104
fn is_allowed ( val : & Constant < ' _ > ) -> bool {
87
105
match val {
88
106
// FIXME(f16_f128): add when equality check is available on all platforms
107
+ Constant :: Ref ( val) => is_allowed ( val) ,
89
108
& Constant :: F32 ( f) => f == 0.0 || f. is_infinite ( ) ,
90
109
& Constant :: F64 ( f) => f == 0.0 || f. is_infinite ( ) ,
91
- Constant :: Vec ( vec) => vec. iter ( ) . all ( |f| match f {
92
- Constant :: F32 ( f) => * f == 0.0 || ( * f ) . is_infinite ( ) ,
93
- Constant :: F64 ( f) => * f == 0.0 || ( * f ) . is_infinite ( ) ,
110
+ Constant :: Vec ( vec) => vec. iter ( ) . all ( |f| match * f {
111
+ Constant :: F32 ( f) => f == 0.0 || f . is_infinite ( ) ,
112
+ Constant :: F64 ( f) => f == 0.0 || f . is_infinite ( ) ,
94
113
_ => false ,
95
114
} ) ,
115
+ Constant :: Repeat ( val, _) => match * * val {
116
+ Constant :: F32 ( f) => f == 0.0 || f. is_infinite ( ) ,
117
+ Constant :: F64 ( f) => f == 0.0 || f. is_infinite ( ) ,
118
+ _ => false ,
119
+ } ,
96
120
_ => false ,
97
121
}
98
122
}
99
123
100
- // Return true if `expr` is the result of `signum()` invoked on a float value.
101
- fn is_signum ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > ) -> bool {
102
- // The negation of a signum is still a signum
103
- if let ExprKind :: Unary ( UnOp :: Neg , child_expr) = expr. kind {
104
- return is_signum ( cx, child_expr) ;
124
+ // This is a best effort guess and may have false positives and negatives.
125
+ fn is_pure_expr < ' tcx > ( cx : & LateContext < ' tcx > , e : & ' tcx Expr < ' _ > ) -> bool {
126
+ match e. kind {
127
+ ExprKind :: Path ( _) | ExprKind :: Lit ( _) => true ,
128
+ ExprKind :: Field ( e, _) | ExprKind :: Cast ( e, _) | ExprKind :: Repeat ( e, _) => is_pure_expr ( cx, e) ,
129
+ ExprKind :: Tup ( args) => args. iter ( ) . all ( |arg| is_pure_expr ( cx, arg) ) ,
130
+ ExprKind :: Struct ( _, fields, base) => {
131
+ base. map_or ( true , |base| is_pure_expr ( cx, base) ) && fields. iter ( ) . all ( |f| is_pure_expr ( cx, f. expr ) )
132
+ } ,
133
+
134
+ // Since rust doesn't actually have the concept of a pure function we
135
+ // have to guess whether it's likely pure from the signature of the
136
+ // function.
137
+ ExprKind :: Unary ( _, e) => is_pure_arg_ty ( cx, cx. typeck_results ( ) . expr_ty_adjusted ( e) ) && is_pure_expr ( cx, e) ,
138
+ ExprKind :: Binary ( _, x, y) | ExprKind :: Index ( x, y, _) => {
139
+ is_pure_arg_ty ( cx, cx. typeck_results ( ) . expr_ty_adjusted ( x) )
140
+ && is_pure_arg_ty ( cx, cx. typeck_results ( ) . expr_ty_adjusted ( y) )
141
+ && is_pure_expr ( cx, x)
142
+ && is_pure_expr ( cx, y)
143
+ } ,
144
+ ExprKind :: MethodCall ( _, recv, args, _) => {
145
+ is_pure_arg_ty ( cx, cx. typeck_results ( ) . expr_ty_adjusted ( recv) )
146
+ && is_pure_expr ( cx, recv)
147
+ && cx
148
+ . typeck_results ( )
149
+ . type_dependent_def_id ( e. hir_id )
150
+ . is_some_and ( |did| matches ! ( cx. tcx. fn_sig( did) . skip_binder( ) . skip_binder( ) . safety, Safety :: Safe ) )
151
+ && args
152
+ . iter ( )
153
+ . all ( |arg| is_pure_arg_ty ( cx, cx. typeck_results ( ) . expr_ty_adjusted ( arg) ) && is_pure_expr ( cx, arg) )
154
+ } ,
155
+ ExprKind :: Call ( f, args @ [ _, ..] ) => {
156
+ is_pure_expr ( cx, f)
157
+ && is_pure_fn_ty ( cx, cx. typeck_results ( ) . expr_ty_adjusted ( f) )
158
+ && args
159
+ . iter ( )
160
+ . all ( |arg| is_pure_arg_ty ( cx, cx. typeck_results ( ) . expr_ty_adjusted ( arg) ) && is_pure_expr ( cx, arg) )
161
+ } ,
162
+
163
+ _ => false ,
105
164
}
165
+ }
166
+
167
+ fn is_pure_fn_ty < ' tcx > ( cx : & LateContext < ' tcx > , ty : Ty < ' tcx > ) -> bool {
168
+ let sig = match * ty. peel_refs ( ) . kind ( ) {
169
+ ty:: FnDef ( did, _) => cx. tcx . fn_sig ( did) . skip_binder ( ) ,
170
+ ty:: FnPtr ( sig) => sig,
171
+ ty:: Closure ( _, args) => {
172
+ return args. as_closure ( ) . upvar_tys ( ) . iter ( ) . all ( |ty| is_pure_arg_ty ( cx, ty) ) ;
173
+ } ,
174
+ _ => return false ,
175
+ } ;
176
+ matches ! ( sig. skip_binder( ) . safety, Safety :: Safe )
177
+ }
106
178
179
+ fn is_pure_arg_ty < ' tcx > ( cx : & LateContext < ' tcx > , ty : Ty < ' tcx > ) -> bool {
180
+ !ty. is_mutable_ptr ( )
181
+ && ty. is_copy_modulo_regions ( cx. tcx , cx. param_env )
182
+ && ( ty. peel_refs ( ) . is_freeze ( cx. tcx , cx. param_env )
183
+ || !ty. has_type_flags ( TypeFlags :: HAS_FREE_REGIONS | TypeFlags :: HAS_RE_ERASED | TypeFlags :: HAS_RE_BOUND ) )
184
+ }
185
+
186
+ fn contains_expr < ' tcx > ( cx : & LateContext < ' tcx > , corpus : & ' tcx Expr < ' tcx > , e : & ' tcx Expr < ' tcx > ) -> bool {
187
+ for_each_expr_without_closures ( corpus, |corpus| {
188
+ if SpanlessEq :: new ( cx) . eq_expr ( corpus, e) {
189
+ ControlFlow :: Break ( ( ) )
190
+ } else {
191
+ ControlFlow :: Continue ( ( ) )
192
+ }
193
+ } )
194
+ . is_some ( )
195
+ }
196
+
197
+ // Return true if `expr` is the result of `signum()` invoked on a float value.
198
+ fn is_signum ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > ) -> bool {
107
199
if let ExprKind :: MethodCall ( method_name, self_arg, ..) = expr. kind
108
200
&& sym ! ( signum) == method_name. ident . name
109
- // Check that the receiver of the signum() is a float (expressions[0] is the receiver of
110
- // the method call)
111
201
{
202
+ // Check that the receiver of the signum() is a float
112
203
return is_float ( cx, self_arg) ;
113
204
}
114
205
false
0 commit comments