11use  clippy_utils:: consts:: { constant,  Constant } ; 
22use  clippy_utils:: diagnostics:: span_lint_and_then; 
33use  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 ; 
67use  rustc_errors:: Applicability ; 
7- use  rustc_hir:: { BinOpKind ,  BorrowKind ,  Expr ,  ExprKind ,  UnOp } ; 
8+ use  rustc_hir:: { BinOpKind ,  BorrowKind ,  Expr ,  ExprKind ,  UnOp ,   Unsafety } ; 
89use  rustc_lint:: LateContext ; 
9- use  rustc_middle:: ty; 
10+ use  rustc_middle:: ty:: { self ,   Ty ,   TypeFlags ,   TypeVisitableExt } ; 
1011
1112use  super :: { FloatCmpConfig ,  FLOAT_CMP } ; 
1213
@@ -24,33 +25,40 @@ pub(crate) fn check<'tcx>(
2425    } ; 
2526
2627    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 ) 
3031        // 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 ( _) ) ) 
3233        // 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 ) ) 
3435    { 
35-         let  left_c = constant ( cx,  cx. typeck_results ( ) ,  left ) ; 
36+         let  left_c = constant ( cx,  cx. typeck_results ( ) ,  left_red ) ; 
3637        let  is_left_const = left_c. is_some ( ) ; 
3738        if  left_c. is_some_and ( |c| is_allowed ( & c) )  { 
3839            return ; 
3940        } 
40-         let  right_c = constant ( cx,  cx. typeck_results ( ) ,  right ) ; 
41+         let  right_c = constant ( cx,  cx. typeck_results ( ) ,  right_red ) ; 
4142        let  is_right_const = right_c. is_some ( ) ; 
4243        if  right_c. is_some_and ( |c| is_allowed ( & c) )  { 
4344            return ; 
4445        } 
4546
4647        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 ) ) 
4950        { 
5051            return ; 
5152        } 
5253
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+         { 
5462            return ; 
5563        } 
5664
@@ -60,7 +68,7 @@ pub(crate) fn check<'tcx>(
6068                return ; 
6169            } 
6270        } 
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 ) ; 
6472        let  msg = if  is_comparing_arrays { 
6573            "strict comparison of `f32` or `f64` arrays" 
6674        }  else  { 
@@ -105,6 +113,78 @@ fn is_allowed(val: &Constant<'_>) -> bool {
105113    } 
106114} 
107115
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+ 
108188// Return true if `expr` is the result of `signum()` invoked on a float value. 
109189fn  is_signum ( cx :  & LateContext < ' _ > ,  expr :  & Expr < ' _ > )  -> bool  { 
110190    if  let  ExprKind :: MethodCall ( method_name,  self_arg,  ..)  = expr. kind 
0 commit comments