1
- use clippy_utils:: diagnostics:: span_lint;
1
+ use clippy_utils:: diagnostics:: { span_lint, span_lint_and_then } ;
2
2
use clippy_utils:: trait_ref_of_method;
3
3
use rustc_data_structures:: fx:: { FxHashMap , FxHashSet } ;
4
4
use rustc_hir:: intravisit:: nested_filter:: { self as hir_nested_filter, NestedFilter } ;
@@ -151,6 +151,7 @@ fn check_fn_inner<'tcx>(
151
151
. params
152
152
. iter ( )
153
153
. filter ( |param| matches ! ( param. kind, GenericParamKind :: Type { .. } ) ) ;
154
+
154
155
for typ in types {
155
156
for pred in generics. bounds_for_param ( cx. tcx . hir ( ) . local_def_id ( typ. hir_id ) ) {
156
157
if pred. origin == PredicateOrigin :: WhereClause {
@@ -187,15 +188,30 @@ fn check_fn_inner<'tcx>(
187
188
}
188
189
}
189
190
}
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 (
192
203
cx,
193
204
NEEDLESS_LIFETIMES ,
194
205
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
+ } ,
197
212
) ;
198
213
}
214
+
199
215
if report_extra_lifetimes {
200
216
self :: report_extra_lifetimes ( cx, decl, generics) ;
201
217
}
@@ -220,13 +236,14 @@ fn explicit_self_type<'tcx>(cx: &LateContext<'tcx>, func: &FnDecl<'tcx>, ident:
220
236
}
221
237
}
222
238
239
+ #[ expect( clippy:: too_many_lines) ]
223
240
fn could_use_elision < ' tcx > (
224
241
cx : & LateContext < ' tcx > ,
225
242
func : & ' tcx FnDecl < ' _ > ,
226
243
body : Option < BodyId > ,
227
244
trait_sig : Option < & [ Ident ] > ,
228
245
named_generics : & ' tcx [ GenericParam < ' _ > ] ,
229
- ) -> bool {
246
+ ) -> Option < Vec < ( LocalDefId , Option < Span > ) > > {
230
247
// There are two scenarios where elision works:
231
248
// * no output references, all input references have different LT
232
249
// * output references, exactly one input reference with same LT
@@ -253,15 +270,15 @@ fn could_use_elision<'tcx>(
253
270
}
254
271
255
272
if input_visitor. abort ( ) || output_visitor. abort ( ) {
256
- return false ;
273
+ return None ;
257
274
}
258
275
259
276
let input_lts = input_visitor. lts ;
260
277
let output_lts = output_visitor. lts ;
261
278
262
279
if let Some ( trait_sig) = trait_sig {
263
280
if explicit_self_type ( cx, func, trait_sig. first ( ) . copied ( ) ) {
264
- return false ;
281
+ return None ;
265
282
}
266
283
}
267
284
@@ -270,22 +287,22 @@ fn could_use_elision<'tcx>(
270
287
271
288
let first_ident = body. params . first ( ) . and_then ( |param| param. pat . simple_ident ( ) ) ;
272
289
if explicit_self_type ( cx, func, first_ident) {
273
- return false ;
290
+ return None ;
274
291
}
275
292
276
293
let mut checker = BodyLifetimeChecker {
277
294
lifetimes_used_in_body : false ,
278
295
} ;
279
296
checker. visit_expr ( body. value ) ;
280
297
if checker. lifetimes_used_in_body {
281
- return false ;
298
+ return None ;
282
299
}
283
300
}
284
301
285
302
// check for lifetimes from higher scopes
286
303
for lt in input_lts. iter ( ) . chain ( output_lts. iter ( ) ) {
287
304
if !allowed_lts. contains ( lt) {
288
- return false ;
305
+ return None ;
289
306
}
290
307
}
291
308
@@ -301,47 +318,62 @@ fn could_use_elision<'tcx>(
301
318
for lt in input_visitor. nested_elision_site_lts {
302
319
if let RefLt :: Named ( def_id) = lt {
303
320
if allowed_lts. contains ( & cx. tcx . item_name ( def_id. to_def_id ( ) ) ) {
304
- return false ;
321
+ return None ;
305
322
}
306
323
}
307
324
}
308
325
for lt in output_visitor. nested_elision_site_lts {
309
326
if let RefLt :: Named ( def_id) = lt {
310
327
if allowed_lts. contains ( & cx. tcx . item_name ( def_id. to_def_id ( ) ) ) {
311
- return false ;
328
+ return None ;
312
329
}
313
330
}
314
331
}
315
332
}
316
333
317
334
// no input lifetimes? easy case!
318
335
if input_lts. is_empty ( ) {
319
- false
336
+ None
320
337
} else if output_lts. is_empty ( ) {
321
338
// no output lifetimes, check distinctness of input lifetimes
322
339
323
340
// only unnamed and static, ok
324
341
let unnamed_and_static = input_lts. iter ( ) . all ( |lt| * lt == RefLt :: Unnamed || * lt == RefLt :: Static ) ;
325
342
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)
327
360
}
328
- // we have no output reference, so we only need all distinct lifetimes
329
- input_lts. len ( ) == unique_lifetimes ( & input_lts)
330
361
} else {
331
362
// we have output references, so we need one input reference,
332
363
// and all output lifetimes must be the same
333
- if unique_lifetimes ( & output_lts) > 1 {
334
- return false ;
335
- }
336
364
if input_lts. len ( ) == 1 {
337
365
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 */
342
374
}
343
375
} else {
344
- false
376
+ None
345
377
}
346
378
}
347
379
}
@@ -358,10 +390,24 @@ fn allowed_lts_from(tcx: TyCtxt<'_>, named_generics: &[GenericParam<'_>]) -> FxH
358
390
allowed_lts
359
391
}
360
392
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.
362
395
#[ 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
365
411
}
366
412
367
413
const 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
370
416
struct RefVisitor < ' a , ' tcx > {
371
417
cx : & ' a LateContext < ' tcx > ,
372
418
lts : Vec < RefLt > ,
419
+ sample_generic_arg_span : FxHashMap < LocalDefId , Span > ,
373
420
nested_elision_site_lts : Vec < RefLt > ,
374
421
unelided_trait_object_lifetime : bool ,
375
422
}
@@ -379,6 +426,7 @@ impl<'a, 'tcx> RefVisitor<'a, 'tcx> {
379
426
Self {
380
427
cx,
381
428
lts : Vec :: new ( ) ,
429
+ sample_generic_arg_span : FxHashMap :: default ( ) ,
382
430
nested_elision_site_lts : Vec :: new ( ) ,
383
431
unelided_trait_object_lifetime : false ,
384
432
}
@@ -472,6 +520,22 @@ impl<'a, 'tcx> Visitor<'tcx> for RefVisitor<'a, 'tcx> {
472
520
_ => walk_ty ( self , ty) ,
473
521
}
474
522
}
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
+ }
475
539
}
476
540
477
541
/// Are any lifetimes mentioned in the `where` clause? If so, we don't try to
0 commit comments