@@ -8,8 +8,12 @@ use rustc_errors::{Applicability, DiagnosticBuilder};
88use rustc_hir as hir;
99use rustc_hir:: def:: { CtorOf , DefKind } ;
1010use 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+ } ;
1215use rustc_infer:: infer:: { self , TyCtxtInferExt } ;
16+
1317use rustc_middle:: lint:: in_external_macro;
1418use rustc_middle:: ty:: { self , Binder , Ty } ;
1519use rustc_span:: symbol:: { kw, sym} ;
@@ -559,13 +563,123 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
559563 let ty = self . tcx . erase_late_bound_regions ( ty) ;
560564 if self . can_coerce ( expected, ty) {
561565 err. span_label ( sp, format ! ( "expected `{}` because of return type" , expected) ) ;
566+ self . try_suggest_return_impl_trait ( err, expected, ty, fn_id) ;
562567 return true ;
563568 }
564569 false
565570 }
566571 }
567572 }
568573
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+
569683 pub ( in super :: super ) fn suggest_missing_break_or_return_expr (
570684 & self ,
571685 err : & mut DiagnosticBuilder < ' _ > ,
0 commit comments