@@ -6,8 +6,8 @@ use rustc_errors::Applicability;
66use rustc_hir:: FnRetTy :: Return ;
77use rustc_hir:: intravisit:: nested_filter:: { self as hir_nested_filter, NestedFilter } ;
88use rustc_hir:: intravisit:: {
9- Visitor , walk_fn_decl, walk_generic_param , walk_generics, walk_impl_item_ref, walk_item, walk_param_bound,
10- walk_poly_trait_ref, walk_trait_ref, walk_ty,
9+ Visitor , walk_fn_decl, walk_generic_arg , walk_generics, walk_impl_item_ref, walk_item, walk_param_bound,
10+ walk_poly_trait_ref, walk_trait_ref, walk_ty, walk_where_predicate ,
1111} ;
1212use rustc_hir:: {
1313 BareFnTy , BodyId , FnDecl , FnSig , GenericArg , GenericBound , GenericParam , GenericParamKind , Generics , Impl ,
@@ -21,7 +21,7 @@ use rustc_middle::lint::in_external_macro;
2121use rustc_session:: declare_lint_pass;
2222use rustc_span:: Span ;
2323use rustc_span:: def_id:: LocalDefId ;
24- use rustc_span:: symbol:: { Ident , Symbol , kw} ;
24+ use rustc_span:: symbol:: { Ident , kw} ;
2525use std:: ops:: ControlFlow ;
2626
2727declare_clippy_lint ! {
@@ -191,52 +191,63 @@ fn check_fn_inner<'tcx>(
191191 if usages. iter ( ) . any ( |usage| !usage. ident . span . eq_ctxt ( span) ) {
192192 return ;
193193 }
194-
195- let lts = elidable_lts
196- . iter ( )
197- // In principle, the result of the call to `Node::ident` could be `unwrap`ped, as `DefId` should refer to a
198- // `Node::GenericParam`.
199- . filter_map ( |& def_id| cx. tcx . hir_node_by_def_id ( def_id) . ident ( ) )
200- . map ( |ident| ident. to_string ( ) )
201- . collect :: < Vec < _ > > ( )
202- . join ( ", " ) ;
203-
204- span_lint_and_then (
205- cx,
206- NEEDLESS_LIFETIMES ,
207- elidable_lts
208- . iter ( )
209- . map ( |& lt| cx. tcx . def_span ( lt) )
210- . chain ( usages. iter ( ) . filter_map ( |usage| {
211- if let LifetimeName :: Param ( def_id) = usage. res
212- && elidable_lts. contains ( & def_id)
213- {
214- return Some ( usage. ident . span ) ;
215- }
216-
217- None
218- } ) )
219- . collect_vec ( ) ,
220- format ! ( "the following explicit lifetimes could be elided: {lts}" ) ,
221- |diag| {
222- if sig. header . is_async ( ) {
223- // async functions have usages whose spans point at the lifetime declaration which messes up
224- // suggestions
225- return ;
226- } ;
227-
228- if let Some ( suggestions) = elision_suggestions ( cx, generics, & elidable_lts, & usages) {
229- diag. multipart_suggestion ( "elide the lifetimes" , suggestions, Applicability :: MachineApplicable ) ;
230- }
231- } ,
232- ) ;
194+ // async functions have usages whose spans point at the lifetime declaration which messes up
195+ // suggestions
196+ let include_suggestions = !sig. header . is_async ( ) ;
197+ report_elidable_lifetimes ( cx, generics, & elidable_lts, & usages, include_suggestions) ;
233198 }
234199
235200 if report_extra_lifetimes {
236201 self :: report_extra_lifetimes ( cx, sig. decl , generics) ;
237202 }
238203}
239204
205+ /// Generate diagnostic messages for elidable lifetimes.
206+ fn report_elidable_lifetimes (
207+ cx : & LateContext < ' _ > ,
208+ generics : & Generics < ' _ > ,
209+ elidable_lts : & [ LocalDefId ] ,
210+ usages : & [ Lifetime ] ,
211+ include_suggestions : bool ,
212+ ) {
213+ let lts = elidable_lts
214+ . iter ( )
215+ // In principle, the result of the call to `Node::ident` could be `unwrap`ped, as `DefId` should refer to a
216+ // `Node::GenericParam`.
217+ . filter_map ( |& def_id| cx. tcx . hir_node_by_def_id ( def_id) . ident ( ) )
218+ . map ( |ident| ident. to_string ( ) )
219+ . collect :: < Vec < _ > > ( )
220+ . join ( ", " ) ;
221+
222+ span_lint_and_then (
223+ cx,
224+ NEEDLESS_LIFETIMES ,
225+ elidable_lts
226+ . iter ( )
227+ . map ( |& lt| cx. tcx . def_span ( lt) )
228+ . chain ( usages. iter ( ) . filter_map ( |usage| {
229+ if let LifetimeName :: Param ( def_id) = usage. res
230+ && elidable_lts. contains ( & def_id)
231+ {
232+ return Some ( usage. ident . span ) ;
233+ }
234+
235+ None
236+ } ) )
237+ . collect_vec ( ) ,
238+ format ! ( "the following explicit lifetimes could be elided: {lts}" ) ,
239+ |diag| {
240+ if !include_suggestions {
241+ return ;
242+ } ;
243+
244+ if let Some ( suggestions) = elision_suggestions ( cx, generics, elidable_lts, usages) {
245+ diag. multipart_suggestion ( "elide the lifetimes" , suggestions, Applicability :: MachineApplicable ) ;
246+ }
247+ } ,
248+ ) ;
249+ }
250+
240251fn elision_suggestions (
241252 cx : & LateContext < ' _ > ,
242253 generics : & Generics < ' _ > ,
@@ -594,23 +605,34 @@ fn has_where_lifetimes<'tcx>(cx: &LateContext<'tcx>, generics: &'tcx Generics<'_
594605 false
595606}
596607
608+ struct Usage {
609+ lifetime : Lifetime ,
610+ in_where_predicate : bool ,
611+ in_generic_arg : bool ,
612+ }
613+
597614struct LifetimeChecker < ' cx , ' tcx , F > {
598615 cx : & ' cx LateContext < ' tcx > ,
599- map : FxHashMap < Symbol , Span > ,
616+ map : FxHashMap < LocalDefId , Vec < Usage > > ,
617+ where_predicate_depth : usize ,
618+ generic_arg_depth : usize ,
600619 phantom : std:: marker:: PhantomData < F > ,
601620}
602621
603622impl < ' cx , ' tcx , F > LifetimeChecker < ' cx , ' tcx , F > {
604- fn new ( cx : & ' cx LateContext < ' tcx > , map : FxHashMap < Symbol , Span > ) -> LifetimeChecker < ' cx , ' tcx , F > {
623+ fn new ( cx : & ' cx LateContext < ' tcx > , def_ids : Vec < LocalDefId > ) -> LifetimeChecker < ' cx , ' tcx , F > {
624+ let map = def_ids. into_iter ( ) . map ( |def_id| ( def_id, Vec :: new ( ) ) ) . collect ( ) ;
605625 Self {
606626 cx,
607627 map,
628+ where_predicate_depth : 0 ,
629+ generic_arg_depth : 0 ,
608630 phantom : std:: marker:: PhantomData ,
609631 }
610632 }
611633}
612634
613- impl < ' cx , ' tcx , F > Visitor < ' tcx > for LifetimeChecker < ' cx , ' tcx , F >
635+ impl < ' tcx , F > Visitor < ' tcx > for LifetimeChecker < ' _ , ' tcx , F >
614636where
615637 F : NestedFilter < ' tcx > ,
616638{
@@ -619,18 +641,27 @@ where
619641
620642 // for lifetimes as parameters of generics
621643 fn visit_lifetime ( & mut self , lifetime : & ' tcx Lifetime ) {
622- self . map . remove ( & lifetime. ident . name ) ;
644+ if let LifetimeName :: Param ( def_id) = lifetime. res
645+ && let Some ( usages) = self . map . get_mut ( & def_id)
646+ {
647+ usages. push ( Usage {
648+ lifetime : * lifetime,
649+ in_where_predicate : self . where_predicate_depth != 0 ,
650+ in_generic_arg : self . generic_arg_depth != 0 ,
651+ } ) ;
652+ }
623653 }
624654
625- fn visit_generic_param ( & mut self , param : & ' tcx GenericParam < ' _ > ) {
626- // don't actually visit `<'a>` or `<'a: 'b>`
627- // we've already visited the `'a` declarations and
628- // don't want to spuriously remove them
629- // `'b` in `'a: 'b` is useless unless used elsewhere in
630- // a non-lifetime bound
631- if let GenericParamKind :: Type { .. } = param. kind {
632- walk_generic_param ( self , param) ;
633- }
655+ fn visit_where_predicate ( & mut self , predicate : & ' tcx WherePredicate < ' tcx > ) {
656+ self . where_predicate_depth += 1 ;
657+ walk_where_predicate ( self , predicate) ;
658+ self . where_predicate_depth -= 1 ;
659+ }
660+
661+ fn visit_generic_arg ( & mut self , generic_arg : & ' tcx GenericArg < ' tcx > ) -> Self :: Result {
662+ self . generic_arg_depth += 1 ;
663+ walk_generic_arg ( self , generic_arg) ;
664+ self . generic_arg_depth -= 1 ;
634665 }
635666
636667 fn nested_visit_map ( & mut self ) -> Self :: Map {
@@ -639,44 +670,49 @@ where
639670}
640671
641672fn report_extra_lifetimes < ' tcx > ( cx : & LateContext < ' tcx > , func : & ' tcx FnDecl < ' _ > , generics : & ' tcx Generics < ' _ > ) {
642- let hs = generics
673+ let def_ids = generics
643674 . params
644675 . iter ( )
645676 . filter_map ( |par| match par. kind {
646677 GenericParamKind :: Lifetime {
647678 kind : LifetimeParamKind :: Explicit ,
648- } => Some ( ( par. name . ident ( ) . name , par . span ) ) ,
679+ } => Some ( par. def_id ) ,
649680 _ => None ,
650681 } )
651682 . collect ( ) ;
652- let mut checker = LifetimeChecker :: < hir_nested_filter:: None > :: new ( cx, hs ) ;
683+ let mut checker = LifetimeChecker :: < hir_nested_filter:: None > :: new ( cx, def_ids ) ;
653684
654685 walk_generics ( & mut checker, generics) ;
655686 walk_fn_decl ( & mut checker, func) ;
656687
657- for & v in checker. map . values ( ) {
658- span_lint (
659- cx,
660- EXTRA_UNUSED_LIFETIMES ,
661- v,
662- "this lifetime isn't used in the function definition" ,
663- ) ;
688+ for ( def_id, usages) in checker. map {
689+ if usages
690+ . iter ( )
691+ . all ( |usage| usage. in_where_predicate && !usage. in_generic_arg )
692+ {
693+ span_lint (
694+ cx,
695+ EXTRA_UNUSED_LIFETIMES ,
696+ cx. tcx . def_span ( def_id) ,
697+ "this lifetime isn't used in the function definition" ,
698+ ) ;
699+ }
664700 }
665701}
666702
667703fn report_extra_impl_lifetimes < ' tcx > ( cx : & LateContext < ' tcx > , impl_ : & ' tcx Impl < ' _ > ) {
668- let hs = impl_
704+ let def_ids = impl_
669705 . generics
670706 . params
671707 . iter ( )
672708 . filter_map ( |par| match par. kind {
673709 GenericParamKind :: Lifetime {
674710 kind : LifetimeParamKind :: Explicit ,
675- } => Some ( ( par. name . ident ( ) . name , par . span ) ) ,
711+ } => Some ( par. def_id ) ,
676712 _ => None ,
677713 } )
678714 . collect ( ) ;
679- let mut checker = LifetimeChecker :: < middle_nested_filter:: All > :: new ( cx, hs ) ;
715+ let mut checker = LifetimeChecker :: < middle_nested_filter:: All > :: new ( cx, def_ids ) ;
680716
681717 walk_generics ( & mut checker, impl_. generics ) ;
682718 if let Some ( ref trait_ref) = impl_. of_trait {
@@ -687,9 +723,62 @@ fn report_extra_impl_lifetimes<'tcx>(cx: &LateContext<'tcx>, impl_: &'tcx Impl<'
687723 walk_impl_item_ref ( & mut checker, item) ;
688724 }
689725
690- for & v in checker. map . values ( ) {
691- span_lint ( cx, EXTRA_UNUSED_LIFETIMES , v, "this lifetime isn't used in the impl" ) ;
726+ for ( & def_id, usages) in & checker. map {
727+ if usages
728+ . iter ( )
729+ . all ( |usage| usage. in_where_predicate && !usage. in_generic_arg )
730+ {
731+ span_lint (
732+ cx,
733+ EXTRA_UNUSED_LIFETIMES ,
734+ cx. tcx . def_span ( def_id) ,
735+ "this lifetime isn't used in the impl" ,
736+ ) ;
737+ }
692738 }
739+
740+ report_elidable_impl_lifetimes ( cx, impl_, & checker. map ) ;
741+ }
742+
743+ // An `impl` lifetime is elidable if it satisfies the following conditions:
744+ // - It is used exactly once.
745+ // - That single use is not in a `GenericArg` in a `WherePredicate`. (Note that a `GenericArg` is
746+ // different from a `GenericParam`.)
747+ fn report_elidable_impl_lifetimes < ' tcx > (
748+ cx : & LateContext < ' tcx > ,
749+ impl_ : & ' tcx Impl < ' _ > ,
750+ map : & FxHashMap < LocalDefId , Vec < Usage > > ,
751+ ) {
752+ let single_usages = map
753+ . iter ( )
754+ . filter_map ( |( def_id, usages) | {
755+ if let [
756+ Usage {
757+ lifetime,
758+ in_where_predicate : false ,
759+ ..
760+ }
761+ | Usage {
762+ lifetime,
763+ in_generic_arg : false ,
764+ ..
765+ } ,
766+ ] = usages. as_slice ( )
767+ {
768+ Some ( ( def_id, lifetime) )
769+ } else {
770+ None
771+ }
772+ } )
773+ . collect :: < Vec < _ > > ( ) ;
774+
775+ if single_usages. is_empty ( ) {
776+ return ;
777+ }
778+
779+ let ( elidable_lts, usages) : ( Vec < _ > , Vec < _ > ) = single_usages. into_iter ( ) . unzip ( ) ;
780+
781+ report_elidable_lifetimes ( cx, impl_. generics , & elidable_lts, & usages, true ) ;
693782}
694783
695784struct BodyLifetimeChecker ;
0 commit comments