1+ use  clippy_config:: Conf ; 
12use  clippy_utils:: diagnostics:: { span_lint,  span_lint_and_sugg,  span_lint_and_then} ; 
3+ use  clippy_utils:: msrvs:: Msrv ; 
24use  clippy_utils:: res:: { MaybeDef ,  MaybeTypeckRes } ; 
35use  clippy_utils:: source:: { SpanRangeExt ,  snippet_with_context} ; 
46use  clippy_utils:: sugg:: { Sugg ,  has_enclosing_paren} ; 
@@ -10,12 +12,12 @@ use rustc_hir::def::Res;
1012use  rustc_hir:: def_id:: { DefId ,  DefIdSet } ; 
1113use  rustc_hir:: { 
1214    BinOpKind ,  Expr ,  ExprKind ,  FnRetTy ,  GenericArg ,  GenericBound ,  HirId ,  ImplItem ,  ImplItemKind ,  ImplicitSelfKind , 
13-     Item ,  ItemKind ,  Mutability ,  Node ,  OpaqueTyOrigin ,  PatExprKind ,  PatKind ,  PathSegment ,  PrimTy ,  QPath ,  TraitItemId , 
14-     TyKind , 
15+     Item ,  ItemKind ,  Mutability ,  Node ,  OpaqueTyOrigin ,  PatExprKind ,  PatKind ,  PathSegment ,  PrimTy ,  QPath ,  RustcVersion , 
16+     StabilityLevel ,   StableSince ,   TraitItemId ,   TyKind , 
1517} ; 
1618use  rustc_lint:: { LateContext ,  LateLintPass } ; 
1719use  rustc_middle:: ty:: { self ,  FnSig ,  Ty } ; 
18- use  rustc_session:: declare_lint_pass ; 
20+ use  rustc_session:: impl_lint_pass ; 
1921use  rustc_span:: source_map:: Spanned ; 
2022use  rustc_span:: symbol:: kw; 
2123use  rustc_span:: { Ident ,  Span ,  Symbol } ; 
@@ -120,7 +122,17 @@ declare_clippy_lint! {
120122    "checking `x == \" \" ` or `x == []` (or similar) when `.is_empty()` could be used instead" 
121123} 
122124
123- declare_lint_pass ! ( LenZero  => [ LEN_ZERO ,  LEN_WITHOUT_IS_EMPTY ,  COMPARISON_TO_EMPTY ] ) ; 
125+ pub  struct  LenZero  { 
126+     msrv :  Msrv , 
127+ } 
128+ 
129+ impl_lint_pass ! ( LenZero  => [ LEN_ZERO ,  LEN_WITHOUT_IS_EMPTY ,  COMPARISON_TO_EMPTY ] ) ; 
130+ 
131+ impl  LenZero  { 
132+     pub  fn  new ( conf :  & ' static  Conf )  -> Self  { 
133+         Self  {  msrv :  conf. msrv  } 
134+     } 
135+ } 
124136
125137impl < ' tcx >  LateLintPass < ' tcx >  for  LenZero  { 
126138    fn  check_item ( & mut  self ,  cx :  & LateContext < ' tcx > ,  item :  & ' tcx  Item < ' _ > )  { 
@@ -184,7 +196,7 @@ impl<'tcx> LateLintPass<'tcx> for LenZero {
184196                _ => false , 
185197            } 
186198            && !expr. span . from_expansion ( ) 
187-             && has_is_empty ( cx,  lt. init ) 
199+             && has_is_empty ( cx,  lt. init ,   self . msrv ) 
188200        { 
189201            let  mut  applicability = Applicability :: MachineApplicable ; 
190202
@@ -206,7 +218,7 @@ impl<'tcx> LateLintPass<'tcx> for LenZero {
206218            && cx. ty_based_def ( expr) . opt_parent ( cx) . is_diag_item ( cx,  sym:: PartialEq ) 
207219            && !expr. span . from_expansion ( ) 
208220        { 
209-             check_empty_expr ( 
221+             self . check_empty_expr ( 
210222                cx, 
211223                expr. span , 
212224                lhs_expr, 
@@ -226,29 +238,110 @@ impl<'tcx> LateLintPass<'tcx> for LenZero {
226238            let  actual_span = span_without_enclosing_paren ( cx,  expr. span ) ; 
227239            match  cmp { 
228240                BinOpKind :: Eq  => { 
229-                     check_cmp ( cx,  actual_span,  left,  right,  "" ,  0 ) ;  // len == 0 
230-                     check_cmp ( cx,  actual_span,  right,  left,  "" ,  0 ) ;  // 0 == len 
241+                     self . check_cmp ( cx,  actual_span,  left,  right,  "" ,  0 ) ;  // len == 0 
242+                     self . check_cmp ( cx,  actual_span,  right,  left,  "" ,  0 ) ;  // 0 == len 
231243                } , 
232244                BinOpKind :: Ne  => { 
233-                     check_cmp ( cx,  actual_span,  left,  right,  "!" ,  0 ) ;  // len != 0 
234-                     check_cmp ( cx,  actual_span,  right,  left,  "!" ,  0 ) ;  // 0 != len 
245+                     self . check_cmp ( cx,  actual_span,  left,  right,  "!" ,  0 ) ;  // len != 0 
246+                     self . check_cmp ( cx,  actual_span,  right,  left,  "!" ,  0 ) ;  // 0 != len 
235247                } , 
236248                BinOpKind :: Gt  => { 
237-                     check_cmp ( cx,  actual_span,  left,  right,  "!" ,  0 ) ;  // len > 0 
238-                     check_cmp ( cx,  actual_span,  right,  left,  "" ,  1 ) ;  // 1 > len 
249+                     self . check_cmp ( cx,  actual_span,  left,  right,  "!" ,  0 ) ;  // len > 0 
250+                     self . check_cmp ( cx,  actual_span,  right,  left,  "" ,  1 ) ;  // 1 > len 
239251                } , 
240252                BinOpKind :: Lt  => { 
241-                     check_cmp ( cx,  actual_span,  left,  right,  "" ,  1 ) ;  // len < 1 
242-                     check_cmp ( cx,  actual_span,  right,  left,  "!" ,  0 ) ;  // 0 < len 
253+                     self . check_cmp ( cx,  actual_span,  left,  right,  "" ,  1 ) ;  // len < 1 
254+                     self . check_cmp ( cx,  actual_span,  right,  left,  "!" ,  0 ) ;  // 0 < len 
243255                } , 
244-                 BinOpKind :: Ge  => check_cmp ( cx,  actual_span,  left,  right,  "!" ,  1 ) ,  // len >= 1 
245-                 BinOpKind :: Le  => check_cmp ( cx,  actual_span,  right,  left,  "!" ,  1 ) ,  // 1 <= len 
256+                 BinOpKind :: Ge  => self . check_cmp ( cx,  actual_span,  left,  right,  "!" ,  1 ) ,  // len >= 1 
257+                 BinOpKind :: Le  => self . check_cmp ( cx,  actual_span,  right,  left,  "!" ,  1 ) ,  // 1 <= len 
246258                _ => ( ) , 
247259            } 
248260        } 
249261    } 
250262} 
251263
264+ impl  LenZero  { 
265+     fn  check_cmp ( 
266+         & self , 
267+         cx :  & LateContext < ' _ > , 
268+         span :  Span , 
269+         method :  & Expr < ' _ > , 
270+         lit :  & Expr < ' _ > , 
271+         op :  & str , 
272+         compare_to :  u32 , 
273+     )  { 
274+         if  method. span . from_expansion ( )  { 
275+             return ; 
276+         } 
277+ 
278+         if  let  ( & ExprKind :: MethodCall ( method_path,  receiver,  [ ] ,  _) ,  ExprKind :: Lit ( lit) )  = ( & method. kind ,  & lit. kind )  { 
279+             // check if we are in an is_empty() method 
280+             if  parent_item_name ( cx,  method)  == Some ( sym:: is_empty)  { 
281+                 return ; 
282+             } 
283+ 
284+             self . check_len ( cx,  span,  method_path. ident . name ,  receiver,  & lit. node ,  op,  compare_to) ; 
285+         }  else  { 
286+             self . check_empty_expr ( cx,  span,  method,  lit,  op) ; 
287+         } 
288+     } 
289+ 
290+     #[ expect( clippy:: too_many_arguments) ]  
291+     fn  check_len ( 
292+         & self , 
293+         cx :  & LateContext < ' _ > , 
294+         span :  Span , 
295+         method_name :  Symbol , 
296+         receiver :  & Expr < ' _ > , 
297+         lit :  & LitKind , 
298+         op :  & str , 
299+         compare_to :  u32 , 
300+     )  { 
301+         if  let  LitKind :: Int ( lit,  _)  = * lit { 
302+             // check if length is compared to the specified number 
303+             if  lit != u128:: from ( compare_to)  { 
304+                 return ; 
305+             } 
306+ 
307+             if  method_name == sym:: len && has_is_empty ( cx,  receiver,  self . msrv )  { 
308+                 let  mut  applicability = Applicability :: MachineApplicable ; 
309+                 span_lint_and_sugg ( 
310+                     cx, 
311+                     LEN_ZERO , 
312+                     span, 
313+                     format ! ( "length comparison to {}" ,  if  compare_to == 0  {  "zero"  }  else {  "one"  } ) , 
314+                     format ! ( "using `{op}is_empty` is clearer and more explicit" ) , 
315+                     format ! ( 
316+                         "{op}{}.is_empty()" , 
317+                         snippet_with_context( cx,  receiver. span,  span. ctxt( ) ,  "_" ,  & mut  applicability) . 0 , 
318+                     ) , 
319+                     applicability, 
320+                 ) ; 
321+             } 
322+         } 
323+     } 
324+ 
325+     fn  check_empty_expr ( & self ,  cx :  & LateContext < ' _ > ,  span :  Span ,  lit1 :  & Expr < ' _ > ,  lit2 :  & Expr < ' _ > ,  op :  & str )  { 
326+         if  ( is_empty_array ( lit2)  || is_empty_string ( lit2) )  && has_is_empty ( cx,  lit1,  self . msrv )  { 
327+             let  mut  applicability = Applicability :: MachineApplicable ; 
328+ 
329+             let  lit1 = peel_ref_operators ( cx,  lit1) ; 
330+             let  lit_str = Sugg :: hir_with_context ( cx,  lit1,  span. ctxt ( ) ,  "_" ,  & mut  applicability) . maybe_paren ( ) ; 
331+ 
332+             span_lint_and_sugg ( 
333+                 cx, 
334+                 COMPARISON_TO_EMPTY , 
335+                 span, 
336+                 "comparison to empty slice" , 
337+                 format ! ( "using `{op}is_empty` is clearer and more explicit" ) , 
338+                 format ! ( "{op}{lit_str}.is_empty()" ) , 
339+                 applicability, 
340+             ) ; 
341+         } 
342+     } 
343+ } 
344+ 
252345fn  span_without_enclosing_paren ( cx :  & LateContext < ' _ > ,  span :  Span )  -> Span  { 
253346    let  Some ( snippet)  = span. get_source_text ( cx)  else  { 
254347        return  span; 
@@ -513,75 +606,6 @@ fn check_for_is_empty(
513606    } 
514607} 
515608
516- fn  check_cmp ( cx :  & LateContext < ' _ > ,  span :  Span ,  method :  & Expr < ' _ > ,  lit :  & Expr < ' _ > ,  op :  & str ,  compare_to :  u32 )  { 
517-     if  method. span . from_expansion ( )  { 
518-         return ; 
519-     } 
520- 
521-     if  let  ( & ExprKind :: MethodCall ( method_path,  receiver,  [ ] ,  _) ,  ExprKind :: Lit ( lit) )  = ( & method. kind ,  & lit. kind )  { 
522-         // check if we are in an is_empty() method 
523-         if  parent_item_name ( cx,  method)  == Some ( sym:: is_empty)  { 
524-             return ; 
525-         } 
526- 
527-         check_len ( cx,  span,  method_path. ident . name ,  receiver,  & lit. node ,  op,  compare_to) ; 
528-     }  else  { 
529-         check_empty_expr ( cx,  span,  method,  lit,  op) ; 
530-     } 
531- } 
532- 
533- fn  check_len ( 
534-     cx :  & LateContext < ' _ > , 
535-     span :  Span , 
536-     method_name :  Symbol , 
537-     receiver :  & Expr < ' _ > , 
538-     lit :  & LitKind , 
539-     op :  & str , 
540-     compare_to :  u32 , 
541- )  { 
542-     if  let  LitKind :: Int ( lit,  _)  = * lit { 
543-         // check if length is compared to the specified number 
544-         if  lit != u128:: from ( compare_to)  { 
545-             return ; 
546-         } 
547- 
548-         if  method_name == sym:: len && has_is_empty ( cx,  receiver)  { 
549-             let  mut  applicability = Applicability :: MachineApplicable ; 
550-             span_lint_and_sugg ( 
551-                 cx, 
552-                 LEN_ZERO , 
553-                 span, 
554-                 format ! ( "length comparison to {}" ,  if  compare_to == 0  {  "zero"  }  else {  "one"  } ) , 
555-                 format ! ( "using `{op}is_empty` is clearer and more explicit" ) , 
556-                 format ! ( 
557-                     "{op}{}.is_empty()" , 
558-                     snippet_with_context( cx,  receiver. span,  span. ctxt( ) ,  "_" ,  & mut  applicability) . 0 , 
559-                 ) , 
560-                 applicability, 
561-             ) ; 
562-         } 
563-     } 
564- } 
565- 
566- fn  check_empty_expr ( cx :  & LateContext < ' _ > ,  span :  Span ,  lit1 :  & Expr < ' _ > ,  lit2 :  & Expr < ' _ > ,  op :  & str )  { 
567-     if  ( is_empty_array ( lit2)  || is_empty_string ( lit2) )  && has_is_empty ( cx,  lit1)  { 
568-         let  mut  applicability = Applicability :: MachineApplicable ; 
569- 
570-         let  lit1 = peel_ref_operators ( cx,  lit1) ; 
571-         let  lit_str = Sugg :: hir_with_context ( cx,  lit1,  span. ctxt ( ) ,  "_" ,  & mut  applicability) . maybe_paren ( ) ; 
572- 
573-         span_lint_and_sugg ( 
574-             cx, 
575-             COMPARISON_TO_EMPTY , 
576-             span, 
577-             "comparison to empty slice" , 
578-             format ! ( "using `{op}is_empty` is clearer and more explicit" ) , 
579-             format ! ( "{op}{lit_str}.is_empty()" ) , 
580-             applicability, 
581-         ) ; 
582-     } 
583- } 
584- 
585609fn  is_empty_string ( expr :  & Expr < ' _ > )  -> bool  { 
586610    if  let  ExprKind :: Lit ( lit)  = expr. kind 
587611        && let  LitKind :: Str ( lit,  _)  = lit. node 
@@ -600,51 +624,65 @@ fn is_empty_array(expr: &Expr<'_>) -> bool {
600624} 
601625
602626/// Checks if this type has an `is_empty` method. 
603- fn  has_is_empty ( cx :  & LateContext < ' _ > ,  expr :  & Expr < ' _ > )  -> bool  { 
627+ fn  has_is_empty ( cx :  & LateContext < ' _ > ,  expr :  & Expr < ' _ > ,   msrv :   Msrv )  -> bool  { 
604628    /// Gets an `AssocItem` and return true if it matches `is_empty(self)`. 
605- fn  is_is_empty ( cx :  & LateContext < ' _ > ,  item :  & ty:: AssocItem )  -> bool  { 
629+ fn  is_is_empty_and_stable ( cx :  & LateContext < ' _ > ,  item :  & ty:: AssocItem ,   msrv :   Msrv )  -> bool  { 
606630        if  item. is_fn ( )  { 
607631            let  sig = cx. tcx . fn_sig ( item. def_id ) . skip_binder ( ) ; 
608632            let  ty = sig. skip_binder ( ) ; 
609633            ty. inputs ( ) . len ( )  == 1 
634+                 && cx. tcx . lookup_stability ( item. def_id ) . is_none_or ( |stability| { 
635+                     if  let  StabilityLevel :: Stable  {  since,  .. }  = stability. level  { 
636+                         let  version = match  since { 
637+                             StableSince :: Version ( version)  => version, 
638+                             StableSince :: Current  => RustcVersion :: CURRENT , 
639+                             StableSince :: Err ( _)  => return  false , 
640+                         } ; 
641+ 
642+                         msrv. meets ( cx,  version) 
643+                     }  else  { 
644+                         // Unstable fn, check if the feature is enabled. 
645+                         cx. tcx . features ( ) . enabled ( stability. feature )  && msrv. current ( cx) . is_none ( ) 
646+                     } 
647+                 } ) 
610648        }  else  { 
611649            false 
612650        } 
613651    } 
614652
615653    /// Checks the inherent impl's items for an `is_empty(self)` method. 
616- fn  has_is_empty_impl ( cx :  & LateContext < ' _ > ,  id :  DefId )  -> bool  { 
654+ fn  has_is_empty_impl ( cx :  & LateContext < ' _ > ,  id :  DefId ,   msrv :   Msrv )  -> bool  { 
617655        cx. tcx . inherent_impls ( id) . iter ( ) . any ( |imp| { 
618656            cx. tcx 
619657                . associated_items ( * imp) 
620658                . filter_by_name_unhygienic ( sym:: is_empty) 
621-                 . any ( |item| is_is_empty ( cx,  item) ) 
659+                 . any ( |item| is_is_empty_and_stable ( cx,  item,  msrv ) ) 
622660        } ) 
623661    } 
624662
625-     fn  ty_has_is_empty < ' tcx > ( cx :  & LateContext < ' tcx > ,  ty :  Ty < ' tcx > ,  depth :  usize )  -> bool  { 
663+     fn  ty_has_is_empty < ' tcx > ( cx :  & LateContext < ' tcx > ,  ty :  Ty < ' tcx > ,  depth :  usize ,   msrv :   Msrv )  -> bool  { 
626664        match  ty. kind ( )  { 
627665            ty:: Dynamic ( tt,  ..)  => tt. principal ( ) . is_some_and ( |principal| { 
628666                cx. tcx 
629667                    . associated_items ( principal. def_id ( ) ) 
630668                    . filter_by_name_unhygienic ( sym:: is_empty) 
631-                     . any ( |item| is_is_empty ( cx,  item) ) 
669+                     . any ( |item| is_is_empty_and_stable ( cx,  item,  msrv ) ) 
632670            } ) , 
633-             ty:: Alias ( ty:: Projection ,  proj)  => has_is_empty_impl ( cx,  proj. def_id ) , 
671+             ty:: Alias ( ty:: Projection ,  proj)  => has_is_empty_impl ( cx,  proj. def_id ,  msrv ) , 
634672            ty:: Adt ( id,  _)  => { 
635-                 has_is_empty_impl ( cx,  id. did ( ) ) 
673+                 has_is_empty_impl ( cx,  id. did ( ) ,  msrv ) 
636674                    || ( cx. tcx . recursion_limit ( ) . value_within_limit ( depth) 
637675                        && cx. tcx . get_diagnostic_item ( sym:: Deref ) . is_some_and ( |deref_id| { 
638676                            implements_trait ( cx,  ty,  deref_id,  & [ ] ) 
639677                                && cx
640678                                    . get_associated_type ( ty,  deref_id,  sym:: Target ) 
641-                                     . is_some_and ( |deref_ty| ty_has_is_empty ( cx,  deref_ty,  depth + 1 ) ) 
679+                                     . is_some_and ( |deref_ty| ty_has_is_empty ( cx,  deref_ty,  depth + 1 ,  msrv ) ) 
642680                        } ) ) 
643681            } , 
644682            ty:: Array ( ..)  | ty:: Slice ( ..)  | ty:: Str  => true , 
645683            _ => false , 
646684        } 
647685    } 
648686
649-     ty_has_is_empty ( cx,  cx. typeck_results ( ) . expr_ty ( expr) . peel_refs ( ) ,  0 ) 
687+     ty_has_is_empty ( cx,  cx. typeck_results ( ) . expr_ty ( expr) . peel_refs ( ) ,  0 ,  msrv ) 
650688} 
0 commit comments