1- use clippy_utils:: diagnostics:: span_lint;
1+ use clippy_utils:: diagnostics:: { span_lint, span_lint_and_then } ;
22use clippy_utils:: trait_ref_of_method;
33use rustc_data_structures:: fx:: { FxHashMap , FxHashSet } ;
44use rustc_hir:: intravisit:: nested_filter:: { self as hir_nested_filter, NestedFilter } ;
@@ -151,6 +151,7 @@ fn check_fn_inner<'tcx>(
151151 . params
152152 . iter ( )
153153 . filter ( |param| matches ! ( param. kind, GenericParamKind :: Type { .. } ) ) ;
154+
154155 for typ in types {
155156 for pred in generics. bounds_for_param ( cx. tcx . hir ( ) . local_def_id ( typ. hir_id ) ) {
156157 if pred. origin == PredicateOrigin :: WhereClause {
@@ -187,15 +188,30 @@ fn check_fn_inner<'tcx>(
187188 }
188189 }
189190 }
190- if could_use_elision ( cx, decl, body, trait_sig, generics. params ) {
191- span_lint (
191+
192+ if let Some ( elidable_lts) = could_use_elision ( cx, decl, body, trait_sig, generics. params ) {
193+ let lts = elidable_lts
194+ . iter ( )
195+ // In principle, the result of the call to `Node::ident` could be `unwrap`ped, as `DefId` should refer to a
196+ // `Node::GenericParam`.
197+ . filter_map ( |& ( def_id, _) | cx. tcx . hir ( ) . get_by_def_id ( def_id) . ident ( ) )
198+ . map ( |ident| ident. to_string ( ) )
199+ . collect :: < Vec < _ > > ( )
200+ . join ( ", " ) ;
201+
202+ span_lint_and_then (
192203 cx,
193204 NEEDLESS_LIFETIMES ,
194205 span. with_hi ( decl. output . span ( ) . hi ( ) ) ,
195- "explicit lifetimes given in parameter types where they could be elided \
196- (or replaced with `'_` if needed by type declaration)",
206+ & format ! ( "the following explicit lifetimes could be elided: {lts}" ) ,
207+ |diag| {
208+ if let Some ( span) = elidable_lts. iter ( ) . find_map ( |& ( _, span) | span) {
209+ diag. span_help ( span, "replace with `'_` in generic arguments such as here" ) ;
210+ }
211+ } ,
197212 ) ;
198213 }
214+
199215 if report_extra_lifetimes {
200216 self :: report_extra_lifetimes ( cx, decl, generics) ;
201217 }
@@ -220,13 +236,14 @@ fn explicit_self_type<'tcx>(cx: &LateContext<'tcx>, func: &FnDecl<'tcx>, ident:
220236 }
221237}
222238
239+ #[ expect( clippy:: too_many_lines) ]
223240fn could_use_elision < ' tcx > (
224241 cx : & LateContext < ' tcx > ,
225242 func : & ' tcx FnDecl < ' _ > ,
226243 body : Option < BodyId > ,
227244 trait_sig : Option < & [ Ident ] > ,
228245 named_generics : & ' tcx [ GenericParam < ' _ > ] ,
229- ) -> bool {
246+ ) -> Option < Vec < ( LocalDefId , Option < Span > ) > > {
230247 // There are two scenarios where elision works:
231248 // * no output references, all input references have different LT
232249 // * output references, exactly one input reference with same LT
@@ -253,15 +270,15 @@ fn could_use_elision<'tcx>(
253270 }
254271
255272 if input_visitor. abort ( ) || output_visitor. abort ( ) {
256- return false ;
273+ return None ;
257274 }
258275
259276 let input_lts = input_visitor. lts ;
260277 let output_lts = output_visitor. lts ;
261278
262279 if let Some ( trait_sig) = trait_sig {
263280 if explicit_self_type ( cx, func, trait_sig. first ( ) . copied ( ) ) {
264- return false ;
281+ return None ;
265282 }
266283 }
267284
@@ -270,22 +287,22 @@ fn could_use_elision<'tcx>(
270287
271288 let first_ident = body. params . first ( ) . and_then ( |param| param. pat . simple_ident ( ) ) ;
272289 if explicit_self_type ( cx, func, first_ident) {
273- return false ;
290+ return None ;
274291 }
275292
276293 let mut checker = BodyLifetimeChecker {
277294 lifetimes_used_in_body : false ,
278295 } ;
279296 checker. visit_expr ( body. value ) ;
280297 if checker. lifetimes_used_in_body {
281- return false ;
298+ return None ;
282299 }
283300 }
284301
285302 // check for lifetimes from higher scopes
286303 for lt in input_lts. iter ( ) . chain ( output_lts. iter ( ) ) {
287304 if !allowed_lts. contains ( lt) {
288- return false ;
305+ return None ;
289306 }
290307 }
291308
@@ -301,47 +318,62 @@ fn could_use_elision<'tcx>(
301318 for lt in input_visitor. nested_elision_site_lts {
302319 if let RefLt :: Named ( def_id) = lt {
303320 if allowed_lts. contains ( & cx. tcx . item_name ( def_id. to_def_id ( ) ) ) {
304- return false ;
321+ return None ;
305322 }
306323 }
307324 }
308325 for lt in output_visitor. nested_elision_site_lts {
309326 if let RefLt :: Named ( def_id) = lt {
310327 if allowed_lts. contains ( & cx. tcx . item_name ( def_id. to_def_id ( ) ) ) {
311- return false ;
328+ return None ;
312329 }
313330 }
314331 }
315332 }
316333
317334 // no input lifetimes? easy case!
318335 if input_lts. is_empty ( ) {
319- false
336+ None
320337 } else if output_lts. is_empty ( ) {
321338 // no output lifetimes, check distinctness of input lifetimes
322339
323340 // only unnamed and static, ok
324341 let unnamed_and_static = input_lts. iter ( ) . all ( |lt| * lt == RefLt :: Unnamed || * lt == RefLt :: Static ) ;
325342 if unnamed_and_static {
326- return false ;
343+ return None ;
344+ }
345+ // we have no output reference, so we can elide explicit lifetimes that occur at most once
346+ let elidable_lts = named_lifetime_occurrences ( & input_lts)
347+ . into_iter ( )
348+ . filter_map ( |( def_id, occurrences) | {
349+ if occurrences <= 1 {
350+ Some ( ( def_id, input_visitor. sample_generic_arg_span . get ( & def_id) . copied ( ) ) )
351+ } else {
352+ None
353+ }
354+ } )
355+ . collect :: < Vec < _ > > ( ) ;
356+ if elidable_lts. is_empty ( ) {
357+ None
358+ } else {
359+ Some ( elidable_lts)
327360 }
328- // we have no output reference, so we only need all distinct lifetimes
329- input_lts. len ( ) == unique_lifetimes ( & input_lts)
330361 } else {
331362 // we have output references, so we need one input reference,
332363 // and all output lifetimes must be the same
333- if unique_lifetimes ( & output_lts) > 1 {
334- return false ;
335- }
336364 if input_lts. len ( ) == 1 {
337365 match ( & input_lts[ 0 ] , & output_lts[ 0 ] ) {
338- ( & RefLt :: Named ( n1) , & RefLt :: Named ( n2) ) if n1 == n2 => true ,
339- ( & RefLt :: Named ( _) , & RefLt :: Unnamed ) => true ,
340- _ => false , /* already elided, different named lifetimes
341- * or something static going on */
366+ ( & RefLt :: Named ( n1) , & RefLt :: Named ( n2) ) if n1 == n2 => {
367+ Some ( vec ! [ ( n1, input_visitor. sample_generic_arg_span. get( & n1) . copied( ) ) ] )
368+ } ,
369+ ( & RefLt :: Named ( n) , & RefLt :: Unnamed ) => {
370+ Some ( vec ! [ ( n, input_visitor. sample_generic_arg_span. get( & n) . copied( ) ) ] )
371+ } ,
372+ _ => None , /* already elided, different named lifetimes
373+ * or something static going on */
342374 }
343375 } else {
344- false
376+ None
345377 }
346378 }
347379}
@@ -358,10 +390,24 @@ fn allowed_lts_from(tcx: TyCtxt<'_>, named_generics: &[GenericParam<'_>]) -> FxH
358390 allowed_lts
359391}
360392
361- /// Number of unique lifetimes in the given vector.
393+ /// Number of times each named lifetime occurs in the given slice. Returns a vector to preserve
394+ /// relative order.
362395#[ must_use]
363- fn unique_lifetimes ( lts : & [ RefLt ] ) -> usize {
364- lts. iter ( ) . collect :: < FxHashSet < _ > > ( ) . len ( )
396+ fn named_lifetime_occurrences ( lts : & [ RefLt ] ) -> Vec < ( LocalDefId , usize ) > {
397+ let mut occurrences = Vec :: new ( ) ;
398+ for lt in lts {
399+ if let & RefLt :: Named ( curr_def_id) = lt {
400+ if let Some ( i) = occurrences
401+ . iter ( )
402+ . position ( |& ( prev_def_id, _) | prev_def_id == curr_def_id)
403+ {
404+ occurrences[ i] . 1 += 1 ;
405+ } else {
406+ occurrences. push ( ( curr_def_id, 1 ) ) ;
407+ }
408+ }
409+ }
410+ occurrences
365411}
366412
367413const CLOSURE_TRAIT_BOUNDS : [ LangItem ; 3 ] = [ LangItem :: Fn , LangItem :: FnMut , LangItem :: FnOnce ] ;
@@ -370,6 +416,7 @@ const CLOSURE_TRAIT_BOUNDS: [LangItem; 3] = [LangItem::Fn, LangItem::FnMut, Lang
370416struct RefVisitor < ' a , ' tcx > {
371417 cx : & ' a LateContext < ' tcx > ,
372418 lts : Vec < RefLt > ,
419+ sample_generic_arg_span : FxHashMap < LocalDefId , Span > ,
373420 nested_elision_site_lts : Vec < RefLt > ,
374421 unelided_trait_object_lifetime : bool ,
375422}
@@ -379,6 +426,7 @@ impl<'a, 'tcx> RefVisitor<'a, 'tcx> {
379426 Self {
380427 cx,
381428 lts : Vec :: new ( ) ,
429+ sample_generic_arg_span : FxHashMap :: default ( ) ,
382430 nested_elision_site_lts : Vec :: new ( ) ,
383431 unelided_trait_object_lifetime : false ,
384432 }
@@ -472,6 +520,22 @@ impl<'a, 'tcx> Visitor<'tcx> for RefVisitor<'a, 'tcx> {
472520 _ => walk_ty ( self , ty) ,
473521 }
474522 }
523+
524+ fn visit_generic_arg ( & mut self , generic_arg : & ' tcx GenericArg < ' tcx > ) {
525+ if let GenericArg :: Lifetime ( l) = generic_arg
526+ && let LifetimeName :: Param ( def_id, _) = l. name
527+ {
528+ self . sample_generic_arg_span . entry ( def_id) . or_insert ( l. span ) ;
529+ }
530+ // Replace with `walk_generic_arg` if/when https://github.com/rust-lang/rust/pull/103692 lands.
531+ // walk_generic_arg(self, generic_arg);
532+ match generic_arg {
533+ GenericArg :: Lifetime ( lt) => self . visit_lifetime ( lt) ,
534+ GenericArg :: Type ( ty) => self . visit_ty ( ty) ,
535+ GenericArg :: Const ( ct) => self . visit_anon_const ( & ct. value ) ,
536+ GenericArg :: Infer ( inf) => self . visit_infer ( inf) ,
537+ }
538+ }
475539}
476540
477541/// Are any lifetimes mentioned in the `where` clause? If so, we don't try to
0 commit comments