1
1
use hir:: GenericParamKind ;
2
+ use rustc_data_structures:: fx:: FxHashSet ;
2
3
use rustc_errors:: {
3
4
codes:: * , Applicability , Diag , DiagMessage , DiagStyledString , EmissionGuarantee , IntoDiagArg ,
4
5
MultiSpan , SubdiagMessageOp , Subdiagnostic ,
5
6
} ;
6
7
use rustc_hir as hir;
8
+ use rustc_hir:: intravisit:: { walk_ty, Visitor } ;
7
9
use rustc_hir:: FnRetTy ;
8
10
use rustc_macros:: { Diagnostic , Subdiagnostic } ;
9
11
use rustc_middle:: ty:: print:: TraitRefPrintOnlyTraitPath ;
@@ -355,31 +357,33 @@ impl Subdiagnostic for AddLifetimeParamsSuggestion<'_> {
355
357
_f : & F ,
356
358
) {
357
359
let mut mk_suggestion = || {
358
- let (
359
- hir:: Ty { kind : hir:: TyKind :: Ref ( lifetime_sub, _) , .. } ,
360
- hir:: Ty { kind : hir:: TyKind :: Ref ( lifetime_sup, _) , .. } ,
361
- ) = ( self . ty_sub , self . ty_sup )
362
- else {
363
- return false ;
364
- } ;
365
-
366
- if !lifetime_sub. is_anonymous ( ) || !lifetime_sup. is_anonymous ( ) {
367
- return false ;
368
- } ;
369
-
370
360
let Some ( anon_reg) = self . tcx . is_suitable_region ( self . sub ) else {
371
361
return false ;
372
362
} ;
373
363
374
364
let node = self . tcx . hir_node_by_def_id ( anon_reg. def_id ) ;
375
365
let is_impl = matches ! ( & node, hir:: Node :: ImplItem ( _) ) ;
376
- let generics = match node {
366
+ let ( generics, parent_generics ) = match node {
377
367
hir:: Node :: Item ( & hir:: Item {
378
368
kind : hir:: ItemKind :: Fn ( _, ref generics, ..) ,
379
369
..
380
370
} )
381
371
| hir:: Node :: TraitItem ( & hir:: TraitItem { ref generics, .. } )
382
- | hir:: Node :: ImplItem ( & hir:: ImplItem { ref generics, .. } ) => generics,
372
+ | hir:: Node :: ImplItem ( & hir:: ImplItem { ref generics, .. } ) => (
373
+ generics,
374
+ match self . tcx . parent_hir_node ( self . tcx . local_def_id_to_hir_id ( anon_reg. def_id ) )
375
+ {
376
+ hir:: Node :: Item ( hir:: Item {
377
+ kind : hir:: ItemKind :: Trait ( _, _, ref generics, ..) ,
378
+ ..
379
+ } )
380
+ | hir:: Node :: Item ( hir:: Item {
381
+ kind : hir:: ItemKind :: Impl ( hir:: Impl { ref generics, .. } ) ,
382
+ ..
383
+ } ) => Some ( generics) ,
384
+ _ => None ,
385
+ } ,
386
+ ) ,
383
387
_ => return false ,
384
388
} ;
385
389
@@ -390,24 +394,112 @@ impl Subdiagnostic for AddLifetimeParamsSuggestion<'_> {
390
394
. map ( |p| p. name . ident ( ) . name )
391
395
. find ( |i| * i != kw:: UnderscoreLifetime ) ;
392
396
let introduce_new = suggestion_param_name. is_none ( ) ;
397
+
398
+ let mut default = "'a" . to_string ( ) ;
399
+ if let Some ( parent_generics) = parent_generics {
400
+ let used: FxHashSet < _ > = parent_generics
401
+ . params
402
+ . iter ( )
403
+ . filter ( |p| matches ! ( p. kind, GenericParamKind :: Lifetime { .. } ) )
404
+ . map ( |p| p. name . ident ( ) . name )
405
+ . filter ( |i| * i != kw:: UnderscoreLifetime )
406
+ . map ( |l| l. to_string ( ) )
407
+ . collect ( ) ;
408
+ if let Some ( lt) =
409
+ ( 'a' ..='z' ) . map ( |it| format ! ( "'{it}" ) ) . find ( |it| !used. contains ( it) )
410
+ {
411
+ // We want a lifetime that *isn't* present in the `trait` or `impl` that assoc
412
+ // `fn` belongs to. We could suggest reusing one of their lifetimes, but it is
413
+ // likely to be an over-constraining lifetime requirement, so we always add a
414
+ // lifetime to the `fn`.
415
+ default = lt;
416
+ }
417
+ }
393
418
let suggestion_param_name =
394
- suggestion_param_name. map ( |n| n. to_string ( ) ) . unwrap_or_else ( || "'a" . to_owned ( ) ) ;
395
-
396
- debug ! ( ?lifetime_sup. ident. span) ;
397
- debug ! ( ?lifetime_sub. ident. span) ;
398
- let make_suggestion = |ident : Ident | {
399
- let sugg = if ident. name == kw:: Empty {
400
- format ! ( "{suggestion_param_name}, " )
401
- } else if ident. name == kw:: UnderscoreLifetime && ident. span . is_empty ( ) {
402
- format ! ( "{suggestion_param_name} " )
403
- } else {
404
- suggestion_param_name. clone ( )
405
- } ;
406
- ( ident. span , sugg)
407
- } ;
408
- let mut suggestions =
409
- vec ! [ make_suggestion( lifetime_sub. ident) , make_suggestion( lifetime_sup. ident) ] ;
419
+ suggestion_param_name. map ( |n| n. to_string ( ) ) . unwrap_or_else ( || default) ;
420
+
421
+ struct ImplicitLifetimeFinder {
422
+ suggestions : Vec < ( Span , String ) > ,
423
+ suggestion_param_name : String ,
424
+ }
410
425
426
+ impl < ' v > Visitor < ' v > for ImplicitLifetimeFinder {
427
+ fn visit_ty ( & mut self , ty : & ' v hir:: Ty < ' v > ) {
428
+ let make_suggestion = |ident : Ident | {
429
+ if ident. name == kw:: Empty && ident. span . is_empty ( ) {
430
+ format ! ( "{}, " , self . suggestion_param_name)
431
+ } else if ident. name == kw:: UnderscoreLifetime && ident. span . is_empty ( ) {
432
+ format ! ( "{} " , self . suggestion_param_name)
433
+ } else {
434
+ self . suggestion_param_name . clone ( )
435
+ }
436
+ } ;
437
+ match ty. kind {
438
+ hir:: TyKind :: Path ( hir:: QPath :: Resolved ( _, path) ) => {
439
+ for segment in path. segments {
440
+ if let Some ( args) = segment. args {
441
+ if args. args . iter ( ) . all ( |arg| {
442
+ matches ! (
443
+ arg,
444
+ hir:: GenericArg :: Lifetime ( lifetime)
445
+ if lifetime. ident. name == kw:: Empty
446
+ )
447
+ } ) {
448
+ self . suggestions . push ( (
449
+ segment. ident . span . shrink_to_hi ( ) ,
450
+ format ! (
451
+ "<{}>" ,
452
+ args. args
453
+ . iter( )
454
+ . map( |_| self . suggestion_param_name. clone( ) )
455
+ . collect:: <Vec <_>>( )
456
+ . join( ", " )
457
+ ) ,
458
+ ) ) ;
459
+ } else {
460
+ for arg in args. args {
461
+ if let hir:: GenericArg :: Lifetime ( lifetime) = arg
462
+ && lifetime. is_anonymous ( )
463
+ {
464
+ self . suggestions . push ( (
465
+ lifetime. ident . span ,
466
+ make_suggestion ( lifetime. ident ) ,
467
+ ) ) ;
468
+ }
469
+ }
470
+ }
471
+ }
472
+ }
473
+ }
474
+ hir:: TyKind :: Ref ( lifetime, ..) if lifetime. is_anonymous ( ) => {
475
+ self . suggestions
476
+ . push ( ( lifetime. ident . span , make_suggestion ( lifetime. ident ) ) ) ;
477
+ }
478
+ _ => { }
479
+ }
480
+ walk_ty ( self , ty) ;
481
+ }
482
+ }
483
+ let mut visitor = ImplicitLifetimeFinder {
484
+ suggestions : vec ! [ ] ,
485
+ suggestion_param_name : suggestion_param_name. clone ( ) ,
486
+ } ;
487
+ if let Some ( fn_decl) = node. fn_decl ( )
488
+ && let hir:: FnRetTy :: Return ( ty) = fn_decl. output
489
+ {
490
+ visitor. visit_ty ( ty) ;
491
+ }
492
+ if visitor. suggestions . is_empty ( ) {
493
+ // Do not suggest constraining the `&self` param, but rather the return type.
494
+ // If that is wrong (because it is not sufficient), a follow up error will tell the
495
+ // user to fix it. This way we lower the chances of *over* constraining, but still
496
+ // get the cake of "correctly" contrained in two steps.
497
+ visitor. visit_ty ( self . ty_sup ) ;
498
+ }
499
+ visitor. visit_ty ( self . ty_sub ) ;
500
+ if visitor. suggestions . is_empty ( ) {
501
+ return false ;
502
+ }
411
503
if introduce_new {
412
504
let new_param_suggestion = if let Some ( first) =
413
505
generics. params . iter ( ) . find ( |p| !p. name . ident ( ) . span . is_empty ( ) )
@@ -417,15 +509,16 @@ impl Subdiagnostic for AddLifetimeParamsSuggestion<'_> {
417
509
( generics. span , format ! ( "<{suggestion_param_name}>" ) )
418
510
} ;
419
511
420
- suggestions. push ( new_param_suggestion) ;
512
+ visitor . suggestions . push ( new_param_suggestion) ;
421
513
}
422
-
423
- diag. multipart_suggestion (
514
+ diag. multipart_suggestion_verbose (
424
515
fluent:: infer_lifetime_param_suggestion,
425
- suggestions,
516
+ visitor . suggestions ,
426
517
Applicability :: MaybeIncorrect ,
427
518
) ;
428
519
diag. arg ( "is_impl" , is_impl) ;
520
+ diag. arg ( "is_reuse" , !introduce_new) ;
521
+
429
522
true
430
523
} ;
431
524
if mk_suggestion ( ) && self . add_note {
0 commit comments