@@ -20,12 +20,17 @@ use rustc_infer::infer::{
2020} ;
2121use rustc_middle:: hir:: place:: PlaceBase ;
2222use rustc_middle:: mir:: { ConstraintCategory , ReturnConstraint } ;
23+ use rustc_middle:: traits:: ObligationCause ;
2324use rustc_middle:: ty:: GenericArgs ;
2425use rustc_middle:: ty:: TypeVisitor ;
2526use rustc_middle:: ty:: { self , RegionVid , Ty } ;
2627use rustc_middle:: ty:: { Region , TyCtxt } ;
2728use rustc_span:: symbol:: { kw, Ident } ;
2829use rustc_span:: Span ;
30+ use rustc_trait_selection:: infer:: type_variable:: { TypeVariableOrigin , TypeVariableOriginKind } ;
31+ use rustc_trait_selection:: infer:: InferCtxtExt ;
32+ use rustc_trait_selection:: traits:: query:: evaluate_obligation:: InferCtxtExt as _;
33+ use rustc_trait_selection:: traits:: Obligation ;
2934
3035use crate :: borrowck_errors;
3136use crate :: session_diagnostics:: {
@@ -810,6 +815,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
810815 self . add_static_impl_trait_suggestion ( & mut diag, * fr, fr_name, * outlived_fr) ;
811816 self . suggest_adding_lifetime_params ( & mut diag, * fr, * outlived_fr) ;
812817 self . suggest_move_on_borrowing_closure ( & mut diag) ;
818+ self . suggest_deref_closure_value ( & mut diag) ;
813819
814820 diag
815821 }
@@ -1039,6 +1045,202 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
10391045 suggest_adding_lifetime_params ( self . infcx . tcx , sub, ty_sup, ty_sub, diag) ;
10401046 }
10411047
1048+ #[ allow( rustc:: diagnostic_outside_of_impl) ]
1049+ #[ allow( rustc:: untranslatable_diagnostic) ] // FIXME: make this translatable
1050+ /// When encountering a lifetime error caused by the return type of a closure, check the
1051+ /// corresponding trait bound and see if dereferencing the closure return value would satisfy
1052+ /// them. If so, we produce a structured suggestion.
1053+ fn suggest_deref_closure_value ( & self , diag : & mut Diag < ' _ > ) {
1054+ let tcx = self . infcx . tcx ;
1055+ let map = tcx. hir ( ) ;
1056+
1057+ // Get the closure return value and type.
1058+ let body_id = map. body_owned_by ( self . mir_def_id ( ) ) ;
1059+ let body = & map. body ( body_id) ;
1060+ let value = & body. value . peel_blocks ( ) ;
1061+ let hir:: Node :: Expr ( closure_expr) = tcx. hir_node_by_def_id ( self . mir_def_id ( ) ) else {
1062+ return ;
1063+ } ;
1064+ let fn_call_id = tcx. parent_hir_id ( self . mir_hir_id ( ) ) ;
1065+ let hir:: Node :: Expr ( expr) = tcx. hir_node ( fn_call_id) else { return } ;
1066+ let def_id = map. enclosing_body_owner ( fn_call_id) ;
1067+ let tables = tcx. typeck ( def_id) ;
1068+ let Some ( return_value_ty) = tables. node_type_opt ( value. hir_id ) else { return } ;
1069+ let return_value_ty = self . infcx . resolve_vars_if_possible ( return_value_ty) ;
1070+
1071+ // We don't use `ty.peel_refs()` to get the number of `*`s needed to get the root type.
1072+ let mut ty = return_value_ty;
1073+ let mut count = 0 ;
1074+ while let ty:: Ref ( _, t, _) = ty. kind ( ) {
1075+ ty = * t;
1076+ count += 1 ;
1077+ }
1078+ if !self . infcx . type_is_copy_modulo_regions ( self . param_env , ty) {
1079+ return ;
1080+ }
1081+
1082+ // Build a new closure where the return type is an owned value, instead of a ref.
1083+ let Some ( ty:: Closure ( did, args) ) =
1084+ tables. node_type_opt ( closure_expr. hir_id ) . as_ref ( ) . map ( |ty| ty. kind ( ) )
1085+ else {
1086+ return ;
1087+ } ;
1088+ let sig = args. as_closure ( ) . sig ( ) ;
1089+ let closure_sig_as_fn_ptr_ty = Ty :: new_fn_ptr (
1090+ tcx,
1091+ sig. map_bound ( |s| {
1092+ let unsafety = hir:: Unsafety :: Normal ;
1093+ use rustc_target:: spec:: abi;
1094+ tcx. mk_fn_sig (
1095+ [ s. inputs ( ) [ 0 ] ] ,
1096+ s. output ( ) . peel_refs ( ) ,
1097+ s. c_variadic ,
1098+ unsafety,
1099+ abi:: Abi :: Rust ,
1100+ )
1101+ } ) ,
1102+ ) ;
1103+ let parent_args = GenericArgs :: identity_for_item (
1104+ tcx,
1105+ tcx. typeck_root_def_id ( self . mir_def_id ( ) . to_def_id ( ) ) ,
1106+ ) ;
1107+ let closure_kind = args. as_closure ( ) . kind ( ) ;
1108+ let closure_kind_ty = Ty :: from_closure_kind ( tcx, closure_kind) ;
1109+ let tupled_upvars_ty = self . infcx . next_ty_var ( TypeVariableOrigin {
1110+ kind : TypeVariableOriginKind :: ClosureSynthetic ,
1111+ span : closure_expr. span ,
1112+ } ) ;
1113+ let closure_args = ty:: ClosureArgs :: new (
1114+ tcx,
1115+ ty:: ClosureArgsParts {
1116+ parent_args,
1117+ closure_kind_ty,
1118+ closure_sig_as_fn_ptr_ty,
1119+ tupled_upvars_ty,
1120+ } ,
1121+ ) ;
1122+ let closure_ty = Ty :: new_closure ( tcx, * did, closure_args. args ) ;
1123+ let closure_ty = tcx. erase_regions ( closure_ty) ;
1124+
1125+ let hir:: ExprKind :: MethodCall ( _, rcvr, args, _) = expr. kind else { return } ;
1126+ let Some ( pos) = args
1127+ . iter ( )
1128+ . enumerate ( )
1129+ . find ( |( _, arg) | arg. hir_id == closure_expr. hir_id )
1130+ . map ( |( i, _) | i)
1131+ else {
1132+ return ;
1133+ } ;
1134+ // The found `Self` type of the method call.
1135+ let Some ( possible_rcvr_ty) = tables. node_type_opt ( rcvr. hir_id ) else { return } ;
1136+
1137+ // The `MethodCall` expression is `Res::Err`, so we search for the method on the `rcvr_ty`.
1138+ let Some ( method) = tcx. lookup_method_for_diagnostic ( ( self . mir_def_id ( ) , expr. hir_id ) )
1139+ else {
1140+ return ;
1141+ } ;
1142+
1143+ // Get the arguments for the found method, only specifying that `Self` is the receiver type.
1144+ let args = GenericArgs :: for_item ( tcx, method, |param, _| {
1145+ if param. index == 0 {
1146+ possible_rcvr_ty. into ( )
1147+ } else {
1148+ self . infcx . var_for_def ( expr. span , param)
1149+ }
1150+ } ) ;
1151+
1152+ let preds = tcx. predicates_of ( method) . instantiate ( tcx, args) ;
1153+ // Get the type for the parameter corresponding to the argument the closure with the
1154+ // lifetime error we had.
1155+ let Some ( input) = tcx
1156+ . fn_sig ( method)
1157+ . instantiate_identity ( )
1158+ . inputs ( )
1159+ . skip_binder ( )
1160+ // Methods have a `self` arg, so `pos` is actually `+ 1` to match the method call arg.
1161+ . get ( pos + 1 )
1162+ else {
1163+ return ;
1164+ } ;
1165+
1166+ let cause = ObligationCause :: misc ( expr. span , self . mir_def_id ( ) ) ;
1167+
1168+ enum CanSuggest {
1169+ Yes ,
1170+ No ,
1171+ Maybe ,
1172+ }
1173+
1174+ // Ok, the following is a HACK. We go over every predicate in the `fn` looking for the ones
1175+ // referencing the argument at hand, which is a closure with some bounds. In those, we
1176+ // re-verify that the closure we synthesized still matches the closure bound on the argument
1177+ // (this is likely unneeded) but *more importantly*, we look at the
1178+ // `<ClosureTy as FnOnce>::Output = ClosureRetTy` to confirm that the closure type we
1179+ // synthesized above *will* be accepted by the `where` bound corresponding to this
1180+ // argument. Put a different way, given `counts.iter().max_by_key(|(_, v)| v)`, we check
1181+ // that a new `ClosureTy` of `|(_, v)| { **v }` will be accepted by this method signature:
1182+ // ```
1183+ // fn max_by_key<B: Ord, F>(self, f: F) -> Option<Self::Item>
1184+ // where
1185+ // Self: Sized,
1186+ // F: FnMut(&Self::Item) -> B,
1187+ // ```
1188+ // Sadly, we can't use `ObligationCtxt` to do this, we need to modify things in place.
1189+ let mut can_suggest = CanSuggest :: Maybe ;
1190+ for pred in preds. predicates {
1191+ match tcx. liberate_late_bound_regions ( self . mir_def_id ( ) . into ( ) , pred. kind ( ) ) {
1192+ ty:: ClauseKind :: Trait ( pred)
1193+ if self . infcx . can_eq ( self . param_env , pred. self_ty ( ) , * input)
1194+ && [
1195+ tcx. lang_items ( ) . fn_trait ( ) ,
1196+ tcx. lang_items ( ) . fn_mut_trait ( ) ,
1197+ tcx. lang_items ( ) . fn_once_trait ( ) ,
1198+ ]
1199+ . contains ( & Some ( pred. def_id ( ) ) ) =>
1200+ {
1201+ // This predicate is an `Fn*` trait and corresponds to the argument with the
1202+ // closure that failed the lifetime check. We verify that the arguments will
1203+ // continue to match (which didn't change, so they should, and this be a no-op).
1204+ let pred = pred. with_self_ty ( tcx, closure_ty) ;
1205+ let o = Obligation :: new ( tcx, cause. clone ( ) , self . param_env , pred) ;
1206+ if !self . infcx . predicate_may_hold ( & o) {
1207+ // The closure we have doesn't have the right arguments for the trait bound
1208+ can_suggest = CanSuggest :: No ;
1209+ } else if let CanSuggest :: Maybe = can_suggest {
1210+ // The closure has the right arguments
1211+ can_suggest = CanSuggest :: Yes ;
1212+ }
1213+ }
1214+ ty:: ClauseKind :: Projection ( proj)
1215+ if self . infcx . can_eq ( self . param_env , proj. projection_ty . self_ty ( ) , * input)
1216+ && tcx. lang_items ( ) . fn_once_output ( ) == Some ( proj. projection_ty . def_id ) =>
1217+ {
1218+ // Verify that `<[closure@...] as FnOnce>::Output` matches the expected
1219+ // `Output` from the trait bound on the function called with the `[closure@...]`
1220+ // as argument.
1221+ let proj = proj. with_self_ty ( tcx, closure_ty) ;
1222+ let o = Obligation :: new ( tcx, cause. clone ( ) , self . param_env , proj) ;
1223+ if !self . infcx . predicate_may_hold ( & o) {
1224+ // Return type doesn't match.
1225+ can_suggest = CanSuggest :: No ;
1226+ } else if let CanSuggest :: Maybe = can_suggest {
1227+ // Return type matches, we can suggest dereferencing the closure's value.
1228+ can_suggest = CanSuggest :: Yes ;
1229+ }
1230+ }
1231+ _ => { }
1232+ }
1233+ }
1234+ if let CanSuggest :: Yes = can_suggest {
1235+ diag. span_suggestion_verbose (
1236+ value. span . shrink_to_lo ( ) ,
1237+ "dereference the return value" ,
1238+ "*" . repeat ( count) ,
1239+ Applicability :: MachineApplicable ,
1240+ ) ;
1241+ }
1242+ }
1243+
10421244 #[ allow( rustc:: diagnostic_outside_of_impl) ]
10431245 #[ allow( rustc:: untranslatable_diagnostic) ] // FIXME: make this translatable
10441246 fn suggest_move_on_borrowing_closure ( & self , diag : & mut Diag < ' _ > ) {
0 commit comments