1- use clippy_utils:: consts:: { constant_with_source , Constant } ;
1+ use clippy_utils:: consts:: { constant , Constant } ;
22use clippy_utils:: diagnostics:: span_lint_and_then;
3- use clippy_utils:: get_item_name;
43use 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 ;
57use 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 } ;
710use rustc_lint:: LateContext ;
8- use rustc_middle:: ty;
11+ use rustc_middle:: ty:: { self , Ty , TypeFlags , TypeVisitableExt } ;
912
10- use super :: { FLOAT_CMP , FLOAT_CMP_CONST } ;
13+ use super :: { FloatCmpConfig , FLOAT_CMP } ;
1114
1215pub ( crate ) fn check < ' tcx > (
1316 cx : & LateContext < ' tcx > ,
17+ config : FloatCmpConfig ,
1418 expr : & ' tcx Expr < ' _ > ,
1519 op : BinOpKind ,
1620 left : & ' tcx Expr < ' _ > ,
1721 right : & ' tcx Expr < ' _ > ,
1822) {
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+ } ;
3027
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 ( _) ) )
3134 // 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+ {
3369 return ;
3470 }
3571
3672 if let Some ( name) = get_item_name ( cx, expr) {
3773 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" ) {
3975 return ;
4076 }
4177 }
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| {
4585 let lhs = Sugg :: hir ( cx, left, ".." ) ;
4686 let rhs = Sugg :: hir ( cx, right, ".." ) ;
4787
@@ -61,54 +101,105 @@ pub(crate) fn check<'tcx>(
61101 }
62102}
63103
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-
86104fn is_allowed ( val : & Constant < ' _ > ) -> bool {
87105 match val {
88106 // FIXME(f16_f128): add when equality check is available on all platforms
107+ Constant :: Ref ( val) => is_allowed ( val) ,
89108 & Constant :: F32 ( f) => f == 0.0 || f. is_infinite ( ) ,
90109 & 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 ( ) ,
94113 _ => false ,
95114 } ) ,
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+ } ,
96120 _ => false ,
97121 }
98122}
99123
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 ,
105164 }
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+ }
106178
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 {
107199 if let ExprKind :: MethodCall ( method_name, self_arg, ..) = expr. kind
108200 && 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)
111201 {
202+ // Check that the receiver of the signum() is a float
112203 return is_float ( cx, self_arg) ;
113204 }
114205 false
0 commit comments