@@ -8,8 +8,12 @@ use rustc_errors::{Applicability, DiagnosticBuilder};
8
8
use rustc_hir as hir;
9
9
use rustc_hir:: def:: { CtorOf , DefKind } ;
10
10
use rustc_hir:: lang_items:: LangItem ;
11
- use rustc_hir:: { Expr , ExprKind , ItemKind , Node , Path , QPath , Stmt , StmtKind , TyKind } ;
11
+ use rustc_hir:: {
12
+ Expr , ExprKind , GenericBound , ItemKind , Node , Path , QPath , Stmt , StmtKind , TyKind ,
13
+ WherePredicate ,
14
+ } ;
12
15
use rustc_infer:: infer:: { self , TyCtxtInferExt } ;
16
+
13
17
use rustc_middle:: lint:: in_external_macro;
14
18
use rustc_middle:: ty:: { self , Binder , Ty } ;
15
19
use rustc_span:: symbol:: { kw, sym} ;
@@ -559,13 +563,123 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
559
563
let ty = self . tcx . erase_late_bound_regions ( ty) ;
560
564
if self . can_coerce ( expected, ty) {
561
565
err. span_label ( sp, format ! ( "expected `{}` because of return type" , expected) ) ;
566
+ self . try_suggest_return_impl_trait ( err, expected, ty, fn_id) ;
562
567
return true ;
563
568
}
564
569
false
565
570
}
566
571
}
567
572
}
568
573
574
+ /// check whether the return type is a generic type with a trait bound
575
+ /// only suggest this if the generic param is not present in the arguments
576
+ /// if this is true, hint them towards changing the return type to `impl Trait`
577
+ /// ```
578
+ /// fn cant_name_it<T: Fn() -> u32>() -> T {
579
+ /// || 3
580
+ /// }
581
+ /// ```
582
+ fn try_suggest_return_impl_trait (
583
+ & self ,
584
+ err : & mut DiagnosticBuilder < ' _ > ,
585
+ expected : Ty < ' tcx > ,
586
+ found : Ty < ' tcx > ,
587
+ fn_id : hir:: HirId ,
588
+ ) {
589
+ // Only apply the suggestion if:
590
+ // - the return type is a generic parameter
591
+ // - the generic param is not used as a fn param
592
+ // - the generic param has at least one bound
593
+ // - the generic param doesn't appear in any other bounds where it's not the Self type
594
+ // Suggest:
595
+ // - Changing the return type to be `impl <all bounds>`
596
+
597
+ debug ! ( "try_suggest_return_impl_trait, expected = {:?}, found = {:?}" , expected, found) ;
598
+
599
+ let ty:: Param ( expected_ty_as_param) = expected. kind ( ) else { return } ;
600
+
601
+ let fn_node = self . tcx . hir ( ) . find ( fn_id) ;
602
+
603
+ let Some ( hir:: Node :: Item ( hir:: Item {
604
+ kind :
605
+ hir:: ItemKind :: Fn (
606
+ hir:: FnSig { decl : hir:: FnDecl { inputs : fn_parameters, output : fn_return, .. } , .. } ,
607
+ hir:: Generics { params, where_clause, .. } ,
608
+ _body_id,
609
+ ) ,
610
+ ..
611
+ } ) ) = fn_node else { return } ;
612
+
613
+ let Some ( expected_generic_param) = params. get ( expected_ty_as_param. index as usize ) else { return } ;
614
+
615
+ // get all where BoundPredicates here, because they are used in to cases below
616
+ let where_predicates = where_clause
617
+ . predicates
618
+ . iter ( )
619
+ . filter_map ( |p| match p {
620
+ WherePredicate :: BoundPredicate ( hir:: WhereBoundPredicate {
621
+ bounds,
622
+ bounded_ty,
623
+ ..
624
+ } ) => {
625
+ // FIXME: Maybe these calls to `ast_ty_to_ty` can be removed (and the ones below)
626
+ let ty = <dyn AstConv < ' _ > >:: ast_ty_to_ty ( self , bounded_ty) ;
627
+ Some ( ( ty, bounds) )
628
+ }
629
+ _ => None ,
630
+ } )
631
+ . map ( |( ty, bounds) | match ty. kind ( ) {
632
+ ty:: Param ( param_ty) if param_ty == expected_ty_as_param => Ok ( Some ( bounds) ) ,
633
+ // check whether there is any predicate that contains our `T`, like `Option<T>: Send`
634
+ _ => match ty. contains ( expected) {
635
+ true => Err ( ( ) ) ,
636
+ false => Ok ( None ) ,
637
+ } ,
638
+ } )
639
+ . collect :: < Result < Vec < _ > , _ > > ( ) ;
640
+
641
+ let Ok ( where_predicates) = where_predicates else { return } ;
642
+
643
+ // now get all predicates in the same types as the where bounds, so we can chain them
644
+ let predicates_from_where =
645
+ where_predicates. iter ( ) . flatten ( ) . map ( |bounds| bounds. iter ( ) ) . flatten ( ) ;
646
+
647
+ // extract all bounds from the source code using their spans
648
+ let all_matching_bounds_strs = expected_generic_param
649
+ . bounds
650
+ . iter ( )
651
+ . chain ( predicates_from_where)
652
+ . filter_map ( |bound| match bound {
653
+ GenericBound :: Trait ( _, _) => {
654
+ self . tcx . sess . source_map ( ) . span_to_snippet ( bound. span ( ) ) . ok ( )
655
+ }
656
+ _ => None ,
657
+ } )
658
+ . collect :: < Vec < String > > ( ) ;
659
+
660
+ if all_matching_bounds_strs. len ( ) == 0 {
661
+ return ;
662
+ }
663
+
664
+ let all_bounds_str = all_matching_bounds_strs. join ( " + " ) ;
665
+
666
+ let ty_param_used_in_fn_params = fn_parameters. iter ( ) . any ( |param| {
667
+ let ty = <dyn AstConv < ' _ > >:: ast_ty_to_ty ( self , param) ;
668
+ matches ! ( ty. kind( ) , ty:: Param ( fn_param_ty_param) if expected_ty_as_param == fn_param_ty_param)
669
+ } ) ;
670
+
671
+ if ty_param_used_in_fn_params {
672
+ return ;
673
+ }
674
+
675
+ err. span_suggestion (
676
+ fn_return. span ( ) ,
677
+ "consider using an impl return type" ,
678
+ format ! ( "impl {}" , all_bounds_str) ,
679
+ Applicability :: MaybeIncorrect ,
680
+ ) ;
681
+ }
682
+
569
683
pub ( in super :: super ) fn suggest_missing_break_or_return_expr (
570
684
& self ,
571
685
err : & mut DiagnosticBuilder < ' _ > ,
0 commit comments