@@ -6,8 +6,8 @@ use rustc_errors::Applicability;
6
6
use rustc_hir:: FnRetTy :: Return ;
7
7
use rustc_hir:: intravisit:: nested_filter:: { self as hir_nested_filter, NestedFilter } ;
8
8
use 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 ,
11
11
} ;
12
12
use rustc_hir:: {
13
13
BareFnTy , BodyId , FnDecl , FnSig , GenericArg , GenericBound , GenericParam , GenericParamKind , Generics , Impl ,
@@ -21,7 +21,7 @@ use rustc_middle::lint::in_external_macro;
21
21
use rustc_session:: declare_lint_pass;
22
22
use rustc_span:: Span ;
23
23
use rustc_span:: def_id:: LocalDefId ;
24
- use rustc_span:: symbol:: { Ident , Symbol , kw} ;
24
+ use rustc_span:: symbol:: { Ident , kw} ;
25
25
use std:: ops:: ControlFlow ;
26
26
27
27
declare_clippy_lint ! {
@@ -191,52 +191,63 @@ fn check_fn_inner<'tcx>(
191
191
if usages. iter ( ) . any ( |usage| !usage. ident . span . eq_ctxt ( span) ) {
192
192
return ;
193
193
}
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) ;
233
198
}
234
199
235
200
if report_extra_lifetimes {
236
201
self :: report_extra_lifetimes ( cx, sig. decl , generics) ;
237
202
}
238
203
}
239
204
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
+
240
251
fn elision_suggestions (
241
252
cx : & LateContext < ' _ > ,
242
253
generics : & Generics < ' _ > ,
@@ -594,23 +605,34 @@ fn has_where_lifetimes<'tcx>(cx: &LateContext<'tcx>, generics: &'tcx Generics<'_
594
605
false
595
606
}
596
607
608
+ struct Usage {
609
+ lifetime : Lifetime ,
610
+ in_where_predicate : bool ,
611
+ in_generic_arg : bool ,
612
+ }
613
+
597
614
struct LifetimeChecker < ' cx , ' tcx , F > {
598
615
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 ,
600
619
phantom : std:: marker:: PhantomData < F > ,
601
620
}
602
621
603
622
impl < ' 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 ( ) ;
605
625
Self {
606
626
cx,
607
627
map,
628
+ where_predicate_depth : 0 ,
629
+ generic_arg_depth : 0 ,
608
630
phantom : std:: marker:: PhantomData ,
609
631
}
610
632
}
611
633
}
612
634
613
- impl < ' cx , ' tcx , F > Visitor < ' tcx > for LifetimeChecker < ' cx , ' tcx , F >
635
+ impl < ' tcx , F > Visitor < ' tcx > for LifetimeChecker < ' _ , ' tcx , F >
614
636
where
615
637
F : NestedFilter < ' tcx > ,
616
638
{
@@ -619,18 +641,27 @@ where
619
641
620
642
// for lifetimes as parameters of generics
621
643
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
+ }
623
653
}
624
654
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 ;
634
665
}
635
666
636
667
fn nested_visit_map ( & mut self ) -> Self :: Map {
@@ -639,44 +670,49 @@ where
639
670
}
640
671
641
672
fn report_extra_lifetimes < ' tcx > ( cx : & LateContext < ' tcx > , func : & ' tcx FnDecl < ' _ > , generics : & ' tcx Generics < ' _ > ) {
642
- let hs = generics
673
+ let def_ids = generics
643
674
. params
644
675
. iter ( )
645
676
. filter_map ( |par| match par. kind {
646
677
GenericParamKind :: Lifetime {
647
678
kind : LifetimeParamKind :: Explicit ,
648
- } => Some ( ( par. name . ident ( ) . name , par . span ) ) ,
679
+ } => Some ( par. def_id ) ,
649
680
_ => None ,
650
681
} )
651
682
. 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 ) ;
653
684
654
685
walk_generics ( & mut checker, generics) ;
655
686
walk_fn_decl ( & mut checker, func) ;
656
687
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
+ }
664
700
}
665
701
}
666
702
667
703
fn report_extra_impl_lifetimes < ' tcx > ( cx : & LateContext < ' tcx > , impl_ : & ' tcx Impl < ' _ > ) {
668
- let hs = impl_
704
+ let def_ids = impl_
669
705
. generics
670
706
. params
671
707
. iter ( )
672
708
. filter_map ( |par| match par. kind {
673
709
GenericParamKind :: Lifetime {
674
710
kind : LifetimeParamKind :: Explicit ,
675
- } => Some ( ( par. name . ident ( ) . name , par . span ) ) ,
711
+ } => Some ( par. def_id ) ,
676
712
_ => None ,
677
713
} )
678
714
. 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 ) ;
680
716
681
717
walk_generics ( & mut checker, impl_. generics ) ;
682
718
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<'
687
723
walk_impl_item_ref ( & mut checker, item) ;
688
724
}
689
725
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
+ }
692
738
}
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 ) ;
693
782
}
694
783
695
784
struct BodyLifetimeChecker ;
0 commit comments