@@ -26,6 +26,9 @@ use rustc_middle::ty::{self, RegionVid, Ty};
2626use rustc_middle:: ty:: { Region , TyCtxt } ;
2727use rustc_span:: symbol:: { kw, Ident } ;
2828use rustc_span:: Span ;
29+ use rustc_trait_selection:: infer:: type_variable:: { TypeVariableOrigin , TypeVariableOriginKind } ;
30+ use rustc_trait_selection:: infer:: InferCtxtExt ;
31+ use rustc_trait_selection:: traits:: { Obligation , ObligationCtxt } ;
2932
3033use crate :: borrowck_errors;
3134use crate :: session_diagnostics:: {
@@ -810,6 +813,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
810813 self . add_static_impl_trait_suggestion ( & mut diag, * fr, fr_name, * outlived_fr) ;
811814 self . suggest_adding_lifetime_params ( & mut diag, * fr, * outlived_fr) ;
812815 self . suggest_move_on_borrowing_closure ( & mut diag) ;
816+ self . suggest_deref_closure_value ( & mut diag) ;
813817
814818 diag
815819 }
@@ -1039,6 +1043,147 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
10391043 suggest_adding_lifetime_params ( self . infcx . tcx , sub, ty_sup, ty_sub, diag) ;
10401044 }
10411045
1046+ #[ allow( rustc:: diagnostic_outside_of_impl) ]
1047+ #[ allow( rustc:: untranslatable_diagnostic) ] // FIXME: make this translatable
1048+ /// When encountering a lifetime error caused by the return type of a closure, check the
1049+ /// corresponding trait bound and see if dereferencing the closure return value would satisfy
1050+ /// them. If so, we produce a structured suggestion.
1051+ fn suggest_deref_closure_value ( & self , diag : & mut Diag < ' _ > ) {
1052+ let tcx = self . infcx . tcx ;
1053+ let map = tcx. hir ( ) ;
1054+
1055+ // Get the closure return value and type.
1056+ let body_id = map. body_owned_by ( self . mir_def_id ( ) ) ;
1057+ let body = & map. body ( body_id) ;
1058+ let value = & body. value . peel_blocks ( ) ;
1059+ let hir:: Node :: Expr ( closure_expr) = tcx. hir_node_by_def_id ( self . mir_def_id ( ) ) else {
1060+ return ;
1061+ } ;
1062+ let fn_call_id = tcx. parent_hir_id ( self . mir_hir_id ( ) ) ;
1063+ let hir:: Node :: Expr ( expr) = tcx. hir_node ( fn_call_id) else { return } ;
1064+ let def_id = map. enclosing_body_owner ( fn_call_id) ;
1065+ let tables = tcx. typeck ( def_id) ;
1066+ let Some ( return_value_ty) = tables. node_type_opt ( value. hir_id ) else { return } ;
1067+ let return_value_ty = self . infcx . resolve_vars_if_possible ( return_value_ty) ;
1068+
1069+ // We don't use `ty.peel_refs()` to get the number of `*`s needed to get the root type.
1070+ let mut ty = return_value_ty;
1071+ let mut count = 0 ;
1072+ while let ty:: Ref ( _, t, _) = ty. kind ( ) {
1073+ ty = * t;
1074+ count += 1 ;
1075+ }
1076+ if !self . infcx . type_is_copy_modulo_regions ( self . param_env , ty) {
1077+ return ;
1078+ }
1079+
1080+ // Build a new closure where the return type is an owned value, instead of a ref.
1081+ let Some ( ty:: Closure ( did, args) ) =
1082+ tables. node_type_opt ( closure_expr. hir_id ) . as_ref ( ) . map ( |ty| ty. kind ( ) )
1083+ else {
1084+ return ;
1085+ } ;
1086+ let sig = args. as_closure ( ) . sig ( ) ;
1087+ let closure_sig_as_fn_ptr_ty = Ty :: new_fn_ptr (
1088+ tcx,
1089+ sig. map_bound ( |s| {
1090+ let unsafety = hir:: Unsafety :: Normal ;
1091+ use rustc_target:: spec:: abi;
1092+ tcx. mk_fn_sig (
1093+ [ s. inputs ( ) [ 0 ] ] ,
1094+ s. output ( ) . peel_refs ( ) ,
1095+ s. c_variadic ,
1096+ unsafety,
1097+ abi:: Abi :: Rust ,
1098+ )
1099+ } ) ,
1100+ ) ;
1101+ let parent_args = GenericArgs :: identity_for_item (
1102+ tcx,
1103+ tcx. typeck_root_def_id ( self . mir_def_id ( ) . to_def_id ( ) ) ,
1104+ ) ;
1105+ let closure_kind = args. as_closure ( ) . kind ( ) ;
1106+ let closure_kind_ty = Ty :: from_closure_kind ( tcx, closure_kind) ;
1107+ let tupled_upvars_ty = self . infcx . next_ty_var ( TypeVariableOrigin {
1108+ kind : TypeVariableOriginKind :: ClosureSynthetic ,
1109+ span : closure_expr. span ,
1110+ } ) ;
1111+ let closure_args = ty:: ClosureArgs :: new (
1112+ tcx,
1113+ ty:: ClosureArgsParts {
1114+ parent_args,
1115+ closure_kind_ty,
1116+ closure_sig_as_fn_ptr_ty,
1117+ tupled_upvars_ty,
1118+ } ,
1119+ ) ;
1120+ let closure_ty = Ty :: new_closure ( tcx, * did, closure_args. args ) ;
1121+ let closure_ty = tcx. erase_regions ( closure_ty) ;
1122+
1123+ let hir:: ExprKind :: MethodCall ( _, rcvr, args, _) = expr. kind else { return } ;
1124+ let Some ( pos) = args
1125+ . iter ( )
1126+ . enumerate ( )
1127+ . find ( |( _, arg) | arg. hir_id == closure_expr. hir_id )
1128+ . map ( |( i, _) | i)
1129+ else {
1130+ return ;
1131+ } ;
1132+ // The found `Self` type of the method call.
1133+ let Some ( possible_rcvr_ty) = tables. node_type_opt ( rcvr. hir_id ) else { return } ;
1134+
1135+ // The `MethodCall` expression is `Res::Err`, so we search for the method on the `rcvr_ty`.
1136+ let Some ( method) = tcx. lookup_method_for_diagnostic ( ( self . mir_def_id ( ) , expr. hir_id ) )
1137+ else {
1138+ return ;
1139+ } ;
1140+
1141+ // Get the type for the parameter corresponding to the argument the closure with the
1142+ // lifetime error we had.
1143+ let Some ( input) = tcx
1144+ . fn_sig ( method)
1145+ . instantiate_identity ( )
1146+ . inputs ( )
1147+ . skip_binder ( )
1148+ // Methods have a `self` arg, so `pos` is actually `+ 1` to match the method call arg.
1149+ . get ( pos + 1 )
1150+ else {
1151+ return ;
1152+ } ;
1153+
1154+ trace ! ( ?input) ;
1155+
1156+ let ty:: Param ( closure_param) = input. kind ( ) else { return } ;
1157+
1158+ // Get the arguments for the found method, only specifying that `Self` is the receiver type.
1159+ let args = GenericArgs :: for_item ( tcx, method, |param, _| {
1160+ if param. index == 0 {
1161+ possible_rcvr_ty. into ( )
1162+ } else if param. index == closure_param. index {
1163+ closure_ty. into ( )
1164+ } else {
1165+ self . infcx . var_for_def ( expr. span , param)
1166+ }
1167+ } ) ;
1168+
1169+ let preds = tcx. predicates_of ( method) . instantiate ( tcx, args) ;
1170+
1171+ let ocx = ObligationCtxt :: new ( & self . infcx ) ;
1172+ ocx. register_obligations ( preds. iter ( ) . map ( |( pred, span) | {
1173+ trace ! ( ?pred) ;
1174+ Obligation :: misc ( tcx, span, self . mir_def_id ( ) , self . param_env , pred)
1175+ } ) ) ;
1176+
1177+ if ocx. select_all_or_error ( ) . is_empty ( ) {
1178+ diag. span_suggestion_verbose (
1179+ value. span . shrink_to_lo ( ) ,
1180+ "dereference the return value" ,
1181+ "*" . repeat ( count) ,
1182+ Applicability :: MachineApplicable ,
1183+ ) ;
1184+ }
1185+ }
1186+
10421187 #[ allow( rustc:: diagnostic_outside_of_impl) ]
10431188 #[ allow( rustc:: untranslatable_diagnostic) ] // FIXME: make this translatable
10441189 fn suggest_move_on_borrowing_closure ( & self , diag : & mut Diag < ' _ > ) {
0 commit comments