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