@@ -20,12 +20,17 @@ use rustc_infer::infer::{
20
20
} ;
21
21
use rustc_middle:: hir:: place:: PlaceBase ;
22
22
use rustc_middle:: mir:: { ConstraintCategory , ReturnConstraint } ;
23
+ use rustc_middle:: traits:: ObligationCause ;
23
24
use rustc_middle:: ty:: GenericArgs ;
24
25
use rustc_middle:: ty:: TypeVisitor ;
25
26
use rustc_middle:: ty:: { self , RegionVid , Ty } ;
26
27
use rustc_middle:: ty:: { Region , TyCtxt } ;
27
28
use rustc_span:: symbol:: { kw, Ident } ;
28
29
use 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 ;
29
34
30
35
use crate :: borrowck_errors;
31
36
use crate :: session_diagnostics:: {
@@ -810,6 +815,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
810
815
self . add_static_impl_trait_suggestion ( & mut diag, * fr, fr_name, * outlived_fr) ;
811
816
self . suggest_adding_lifetime_params ( & mut diag, * fr, * outlived_fr) ;
812
817
self . suggest_move_on_borrowing_closure ( & mut diag) ;
818
+ self . suggest_deref_closure_value ( & mut diag) ;
813
819
814
820
diag
815
821
}
@@ -1039,6 +1045,202 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
1039
1045
suggest_adding_lifetime_params ( self . infcx . tcx , sub, ty_sup, ty_sub, diag) ;
1040
1046
}
1041
1047
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
+
1042
1244
#[ allow( rustc:: diagnostic_outside_of_impl) ]
1043
1245
#[ allow( rustc:: untranslatable_diagnostic) ] // FIXME: make this translatable
1044
1246
fn suggest_move_on_borrowing_closure ( & self , diag : & mut Diag < ' _ > ) {
0 commit comments