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 }
@@ -226,7 +242,7 @@ fn could_use_elision<'tcx>(
226242 body : Option < BodyId > ,
227243 trait_sig : Option < & [ Ident ] > ,
228244 named_generics : & ' tcx [ GenericParam < ' _ > ] ,
229- ) -> bool {
245+ ) -> Option < Vec < ( LocalDefId , Option < Span > ) > > {
230246 // There are two scenarios where elision works:
231247 // * no output references, all input references have different LT
232248 // * output references, exactly one input reference with same LT
@@ -253,15 +269,15 @@ fn could_use_elision<'tcx>(
253269 }
254270
255271 if input_visitor. abort ( ) || output_visitor. abort ( ) {
256- return false ;
272+ return None ;
257273 }
258274
259275 let input_lts = input_visitor. lts ;
260276 let output_lts = output_visitor. lts ;
261277
262278 if let Some ( trait_sig) = trait_sig {
263279 if explicit_self_type ( cx, func, trait_sig. first ( ) . copied ( ) ) {
264- return false ;
280+ return None ;
265281 }
266282 }
267283
@@ -270,22 +286,22 @@ fn could_use_elision<'tcx>(
270286
271287 let first_ident = body. params . first ( ) . and_then ( |param| param. pat . simple_ident ( ) ) ;
272288 if explicit_self_type ( cx, func, first_ident) {
273- return false ;
289+ return None ;
274290 }
275291
276292 let mut checker = BodyLifetimeChecker {
277293 lifetimes_used_in_body : false ,
278294 } ;
279295 checker. visit_expr ( body. value ) ;
280296 if checker. lifetimes_used_in_body {
281- return false ;
297+ return None ;
282298 }
283299 }
284300
285301 // check for lifetimes from higher scopes
286302 for lt in input_lts. iter ( ) . chain ( output_lts. iter ( ) ) {
287303 if !allowed_lts. contains ( lt) {
288- return false ;
304+ return None ;
289305 }
290306 }
291307
@@ -301,48 +317,45 @@ fn could_use_elision<'tcx>(
301317 for lt in input_visitor. nested_elision_site_lts {
302318 if let RefLt :: Named ( def_id) = lt {
303319 if allowed_lts. contains ( & cx. tcx . item_name ( def_id. to_def_id ( ) ) ) {
304- return false ;
320+ return None ;
305321 }
306322 }
307323 }
308324 for lt in output_visitor. nested_elision_site_lts {
309325 if let RefLt :: Named ( def_id) = lt {
310326 if allowed_lts. contains ( & cx. tcx . item_name ( def_id. to_def_id ( ) ) ) {
311- return false ;
327+ return None ;
312328 }
313329 }
314330 }
315331 }
316332
317- // no input lifetimes? easy case!
318- if input_lts. is_empty ( ) {
319- false
320- } else if output_lts. is_empty ( ) {
321- // no output lifetimes, check distinctness of input lifetimes
333+ // A lifetime can be newly elided if:
334+ // - It occurs only once among the inputs.
335+ // - If there are multiple input lifetimes, then the newly elided lifetime does not occur among the
336+ // outputs (because eliding such an lifetime would create an ambiguity).
337+ let elidable_lts = named_lifetime_occurrences ( & input_lts)
338+ . into_iter ( )
339+ . filter_map ( |( def_id, occurrences) | {
340+ if occurrences == 1 && ( input_lts. len ( ) == 1 || !output_lts. contains ( & RefLt :: Named ( def_id) ) ) {
341+ Some ( (
342+ def_id,
343+ input_visitor
344+ . lifetime_generic_arg_spans
345+ . get ( & def_id)
346+ . or_else ( || output_visitor. lifetime_generic_arg_spans . get ( & def_id) )
347+ . copied ( ) ,
348+ ) )
349+ } else {
350+ None
351+ }
352+ } )
353+ . collect :: < Vec < _ > > ( ) ;
322354
323- // only unnamed and static, ok
324- let unnamed_and_static = input_lts. iter ( ) . all ( |lt| * lt == RefLt :: Unnamed || * lt == RefLt :: Static ) ;
325- if unnamed_and_static {
326- return false ;
327- }
328- // we have no output reference, so we only need all distinct lifetimes
329- input_lts. len ( ) == unique_lifetimes ( & input_lts)
355+ if elidable_lts. is_empty ( ) {
356+ None
330357 } else {
331- // we have output references, so we need one input reference,
332- // and all output lifetimes must be the same
333- if unique_lifetimes ( & output_lts) > 1 {
334- return false ;
335- }
336- if input_lts. len ( ) == 1 {
337- 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 */
342- }
343- } else {
344- false
345- }
358+ Some ( elidable_lts)
346359 }
347360}
348361
@@ -358,10 +371,24 @@ fn allowed_lts_from(tcx: TyCtxt<'_>, named_generics: &[GenericParam<'_>]) -> FxH
358371 allowed_lts
359372}
360373
361- /// Number of unique lifetimes in the given vector.
374+ /// Number of times each named lifetime occurs in the given slice. Returns a vector to preserve
375+ /// relative order.
362376#[ must_use]
363- fn unique_lifetimes ( lts : & [ RefLt ] ) -> usize {
364- lts. iter ( ) . collect :: < FxHashSet < _ > > ( ) . len ( )
377+ fn named_lifetime_occurrences ( lts : & [ RefLt ] ) -> Vec < ( LocalDefId , usize ) > {
378+ let mut occurrences = Vec :: new ( ) ;
379+ for lt in lts {
380+ if let & RefLt :: Named ( curr_def_id) = lt {
381+ if let Some ( pair) = occurrences
382+ . iter_mut ( )
383+ . find ( |( prev_def_id, _) | * prev_def_id == curr_def_id)
384+ {
385+ pair. 1 += 1 ;
386+ } else {
387+ occurrences. push ( ( curr_def_id, 1 ) ) ;
388+ }
389+ }
390+ }
391+ occurrences
365392}
366393
367394const CLOSURE_TRAIT_BOUNDS : [ LangItem ; 3 ] = [ LangItem :: Fn , LangItem :: FnMut , LangItem :: FnOnce ] ;
@@ -370,6 +397,7 @@ const CLOSURE_TRAIT_BOUNDS: [LangItem; 3] = [LangItem::Fn, LangItem::FnMut, Lang
370397struct RefVisitor < ' a , ' tcx > {
371398 cx : & ' a LateContext < ' tcx > ,
372399 lts : Vec < RefLt > ,
400+ lifetime_generic_arg_spans : FxHashMap < LocalDefId , Span > ,
373401 nested_elision_site_lts : Vec < RefLt > ,
374402 unelided_trait_object_lifetime : bool ,
375403}
@@ -379,6 +407,7 @@ impl<'a, 'tcx> RefVisitor<'a, 'tcx> {
379407 Self {
380408 cx,
381409 lts : Vec :: new ( ) ,
410+ lifetime_generic_arg_spans : FxHashMap :: default ( ) ,
382411 nested_elision_site_lts : Vec :: new ( ) ,
383412 unelided_trait_object_lifetime : false ,
384413 }
@@ -472,6 +501,22 @@ impl<'a, 'tcx> Visitor<'tcx> for RefVisitor<'a, 'tcx> {
472501 _ => walk_ty ( self , ty) ,
473502 }
474503 }
504+
505+ fn visit_generic_arg ( & mut self , generic_arg : & ' tcx GenericArg < ' tcx > ) {
506+ if let GenericArg :: Lifetime ( l) = generic_arg
507+ && let LifetimeName :: Param ( def_id, _) = l. name
508+ {
509+ self . lifetime_generic_arg_spans . entry ( def_id) . or_insert ( l. span ) ;
510+ }
511+ // Replace with `walk_generic_arg` if/when https://github.com/rust-lang/rust/pull/103692 lands.
512+ // walk_generic_arg(self, generic_arg);
513+ match generic_arg {
514+ GenericArg :: Lifetime ( lt) => self . visit_lifetime ( lt) ,
515+ GenericArg :: Type ( ty) => self . visit_ty ( ty) ,
516+ GenericArg :: Const ( ct) => self . visit_anon_const ( & ct. value ) ,
517+ GenericArg :: Infer ( inf) => self . visit_infer ( inf) ,
518+ }
519+ }
475520}
476521
477522/// Are any lifetimes mentioned in the `where` clause? If so, we don't try to
0 commit comments