@@ -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 , Inherited } ;
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,188 @@ 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 ion the `rcvr_ty`.
1139
+ let inh = Inherited :: new ( tcx, self . mir_def_id ( ) ) ;
1140
+ let fn_ctxt = FnCtxt :: new ( & inh, 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
+ let mut can_suggest = CanSuggest :: Maybe ;
1178
+ for pred in preds. predicates {
1179
+ match tcx. liberate_late_bound_regions ( self . mir_def_id ( ) . into ( ) , pred. kind ( ) ) {
1180
+ ty:: ClauseKind :: Trait ( pred)
1181
+ if self . infcx . can_eq ( self . param_env , pred. self_ty ( ) , * input)
1182
+ && [
1183
+ tcx. lang_items ( ) . fn_trait ( ) ,
1184
+ tcx. lang_items ( ) . fn_mut_trait ( ) ,
1185
+ tcx. lang_items ( ) . fn_once_trait ( ) ,
1186
+ ]
1187
+ . contains ( & Some ( pred. def_id ( ) ) ) =>
1188
+ {
1189
+ // This predicate is an `Fn*` trait and corresponds to the argument with the
1190
+ // closure that failed the lifetime check. We verify that the arguments will
1191
+ // continue to match (which didn't change, so they should, and this be a no-op).
1192
+ let pred = pred. with_self_ty ( tcx, closure_ty) ;
1193
+ let o = Obligation :: new ( tcx, cause. clone ( ) , self . param_env , pred) ;
1194
+ if !self . infcx . predicate_may_hold ( & o) {
1195
+ // The closure we have doesn't have the right arguments for the trait bound
1196
+ can_suggest = CanSuggest :: No ;
1197
+ } else if let CanSuggest :: Maybe = can_suggest {
1198
+ // The closure has the right arguments
1199
+ can_suggest = CanSuggest :: Yes ;
1200
+ }
1201
+ }
1202
+ ty:: ClauseKind :: Projection ( proj)
1203
+ if self . infcx . can_eq ( self . param_env , proj. projection_ty . self_ty ( ) , * input)
1204
+ && tcx. lang_items ( ) . fn_once_output ( ) == Some ( proj. projection_ty . def_id ) =>
1205
+ {
1206
+ // Verify that `<[closure@...] as FnOnce>::Output` matches the expected
1207
+ // `Output` from the trait bound.
1208
+ let proj = proj. with_self_ty ( tcx, closure_ty) ;
1209
+ let o = Obligation :: new ( tcx, cause. clone ( ) , self . param_env , proj) ;
1210
+ if !self . infcx . predicate_may_hold ( & o) {
1211
+ // Return type doesn't match.
1212
+ can_suggest = CanSuggest :: No ;
1213
+ } else if let CanSuggest :: Maybe = can_suggest {
1214
+ // Return type matches, we can suggest dereferencing the closure's value.
1215
+ can_suggest = CanSuggest :: Yes ;
1216
+ }
1217
+ }
1218
+ _ => { }
1219
+ }
1220
+ }
1221
+ if let CanSuggest :: Yes = can_suggest {
1222
+ diag. span_suggestion_verbose (
1223
+ value. span . shrink_to_lo ( ) ,
1224
+ "dereference the return value" ,
1225
+ "*" . repeat ( count) ,
1226
+ Applicability :: MachineApplicable ,
1227
+ ) ;
1228
+ }
1229
+ }
1230
+
1042
1231
#[ allow( rustc:: diagnostic_outside_of_impl) ]
1043
1232
#[ allow( rustc:: untranslatable_diagnostic) ] // FIXME: make this translatable
1044
1233
fn suggest_move_on_borrowing_closure ( & self , diag : & mut Diag < ' _ > ) {
0 commit comments