1
+ use std:: iter;
2
+
1
3
use either:: Either ;
2
4
use rustc_data_structures:: captures:: Captures ;
3
5
use rustc_data_structures:: fx:: FxIndexSet ;
@@ -10,25 +12,25 @@ use rustc_hir::intravisit::{walk_block, walk_expr, Visitor};
10
12
use rustc_hir:: { AsyncGeneratorKind , GeneratorKind , LangItem } ;
11
13
use rustc_infer:: infer:: TyCtxtInferExt ;
12
14
use rustc_infer:: traits:: ObligationCause ;
15
+ use rustc_middle:: hir:: nested_filter:: OnlyBodies ;
13
16
use rustc_middle:: mir:: tcx:: PlaceTy ;
14
17
use rustc_middle:: mir:: {
15
18
self , AggregateKind , BindingForm , BorrowKind , ClearCrossCrate , ConstraintCategory ,
16
19
FakeReadCause , LocalDecl , LocalInfo , LocalKind , Location , Operand , Place , PlaceRef ,
17
20
ProjectionElem , Rvalue , Statement , StatementKind , Terminator , TerminatorKind , VarBindingForm ,
18
21
} ;
19
- use rustc_middle:: ty:: { self , suggest_constraining_type_params, PredicateKind , Ty } ;
22
+ use rustc_middle:: ty:: { self , suggest_constraining_type_params, PredicateKind , Ty , TypeckResults } ;
20
23
use rustc_middle:: util:: CallKind ;
21
24
use rustc_mir_dataflow:: move_paths:: { InitKind , MoveOutIndex , MovePathIndex } ;
22
25
use rustc_span:: def_id:: LocalDefId ;
23
26
use rustc_span:: hygiene:: DesugaringKind ;
24
- use rustc_span:: symbol:: { kw, sym} ;
27
+ use rustc_span:: symbol:: { kw, sym, Ident } ;
25
28
use rustc_span:: { BytePos , Span , Symbol } ;
26
29
use rustc_trait_selection:: infer:: InferCtxtExt ;
27
30
use rustc_trait_selection:: traits:: ObligationCtxt ;
28
31
29
32
use crate :: borrow_set:: TwoPhaseActivation ;
30
33
use crate :: borrowck_errors;
31
-
32
34
use crate :: diagnostics:: conflict_errors:: StorageDeadOrDrop :: LocalStorageDead ;
33
35
use crate :: diagnostics:: { find_all_local_uses, CapturedMessageOpt } ;
34
36
use crate :: {
@@ -959,6 +961,11 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
959
961
None ,
960
962
) ;
961
963
self . suggest_binding_for_closure_capture_self ( & mut err, & issued_spans) ;
964
+ self . suggest_using_closure_argument_instead_of_capture (
965
+ & mut err,
966
+ issued_borrow. borrowed_place ,
967
+ & issued_spans,
968
+ ) ;
962
969
err
963
970
}
964
971
@@ -977,6 +984,11 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
977
984
place,
978
985
issued_borrow. borrowed_place ,
979
986
) ;
987
+ self . suggest_using_closure_argument_instead_of_capture (
988
+ & mut err,
989
+ issued_borrow. borrowed_place ,
990
+ & issued_spans,
991
+ ) ;
980
992
err
981
993
}
982
994
@@ -1263,6 +1275,173 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
1263
1275
}
1264
1276
}
1265
1277
1278
+ /// Suggest using closure argument instead of capture.
1279
+ ///
1280
+ /// For example:
1281
+ /// ```ignore (illustrative)
1282
+ /// struct S;
1283
+ ///
1284
+ /// impl S {
1285
+ /// fn call(&mut self, f: impl Fn(&mut Self)) { /* ... */ }
1286
+ /// fn x(&self) {}
1287
+ /// }
1288
+ ///
1289
+ /// let mut v = S;
1290
+ /// v.call(|this: &mut S| v.x());
1291
+ /// // ^\ ^-- help: try using the closure argument: `this`
1292
+ /// // *-- error: cannot borrow `v` as mutable because it is also borrowed as immutable
1293
+ /// ```
1294
+ fn suggest_using_closure_argument_instead_of_capture (
1295
+ & self ,
1296
+ err : & mut Diagnostic ,
1297
+ borrowed_place : Place < ' tcx > ,
1298
+ issued_spans : & UseSpans < ' tcx > ,
1299
+ ) {
1300
+ let & UseSpans :: ClosureUse { capture_kind_span, .. } = issued_spans else { return } ;
1301
+ let tcx = self . infcx . tcx ;
1302
+ let hir = tcx. hir ( ) ;
1303
+
1304
+ // Get the type of the local that we are trying to borrow
1305
+ let local = borrowed_place. local ;
1306
+ let local_ty = self . body . local_decls [ local] . ty ;
1307
+
1308
+ // Get the body the error happens in
1309
+ let & body_id =
1310
+ if let hir:: Node :: Item ( hir:: Item { kind, .. } ) = hir. get ( self . mir_hir_id ( ) )
1311
+ && let hir:: ItemKind :: Static ( _, _, body_id)
1312
+ | hir:: ItemKind :: Const ( _, body_id)
1313
+ | hir:: ItemKind :: Fn ( _, _, body_id) = kind
1314
+ {
1315
+ body_id
1316
+ } else if let hir:: Node :: TraitItem ( hir:: TraitItem { kind, .. } ) = hir. get ( self . mir_hir_id ( ) )
1317
+ && let hir:: TraitItemKind :: Const ( _, Some ( body_id) )
1318
+ | hir:: TraitItemKind :: Fn ( _, hir:: TraitFn :: Provided ( body_id) ) = kind
1319
+ {
1320
+ body_id
1321
+ } else if let hir:: Node :: ImplItem ( hir:: ImplItem { kind, .. } ) = hir. get ( self . mir_hir_id ( ) )
1322
+ && let hir:: ImplItemKind :: Const ( _, body_id)
1323
+ | hir:: ImplItemKind :: Fn ( _, body_id) = kind
1324
+ {
1325
+ body_id
1326
+ } else {
1327
+ return
1328
+ } ;
1329
+
1330
+ let body_expr = hir. body ( body_id) . value ;
1331
+
1332
+ struct ClosureFinder < ' hir > {
1333
+ hir : rustc_middle:: hir:: map:: Map < ' hir > ,
1334
+ borrow_span : Span ,
1335
+ res : Option < ( & ' hir hir:: Expr < ' hir > , & ' hir hir:: Closure < ' hir > ) > ,
1336
+ /// The path expression with the `borrow_span` span
1337
+ error_path : Option < ( & ' hir hir:: Expr < ' hir > , & ' hir hir:: QPath < ' hir > ) > ,
1338
+ }
1339
+ impl < ' hir > Visitor < ' hir > for ClosureFinder < ' hir > {
1340
+ type NestedFilter = OnlyBodies ;
1341
+
1342
+ fn nested_visit_map ( & mut self ) -> Self :: Map {
1343
+ self . hir
1344
+ }
1345
+
1346
+ fn visit_expr ( & mut self , ex : & ' hir hir:: Expr < ' hir > ) {
1347
+ if let hir:: ExprKind :: Path ( qpath) = & ex. kind
1348
+ && ex. span == self . borrow_span
1349
+ {
1350
+ self . error_path = Some ( ( ex, qpath) ) ;
1351
+ }
1352
+
1353
+ if let hir:: ExprKind :: Closure ( closure) = ex. kind
1354
+ && ex. span . contains ( self . borrow_span )
1355
+ // To support cases like `|| { v.call(|this| v.get()) }`
1356
+ // FIXME: actually support such cases (need to figure out how to move from the capture place to original local)
1357
+ && self . res . as_ref ( ) . map_or ( true , |( prev_res, _) | prev_res. span . contains ( ex. span ) )
1358
+ {
1359
+ self . res = Some ( ( ex, closure) ) ;
1360
+ }
1361
+
1362
+ hir:: intravisit:: walk_expr ( self , ex) ;
1363
+ }
1364
+ }
1365
+
1366
+ // Find the closure that most tightly wraps `capture_kind_span`
1367
+ let mut finder =
1368
+ ClosureFinder { hir, borrow_span : capture_kind_span, res : None , error_path : None } ;
1369
+ finder. visit_expr ( body_expr) ;
1370
+ let Some ( ( closure_expr, closure) ) = finder. res else { return } ;
1371
+
1372
+ let typeck_results: & TypeckResults < ' _ > =
1373
+ tcx. typeck_opt_const_arg ( self . body . source . with_opt_param ( ) . as_local ( ) . unwrap ( ) ) ;
1374
+
1375
+ // Check that the parent of the closure is a method call,
1376
+ // with receiver matching with local's type (modulo refs)
1377
+ let parent = hir. parent_id ( closure_expr. hir_id ) ;
1378
+ if let hir:: Node :: Expr ( parent) = hir. get ( parent) {
1379
+ if let hir:: ExprKind :: MethodCall ( _, recv, ..) = parent. kind {
1380
+ let recv_ty = typeck_results. expr_ty ( recv) ;
1381
+
1382
+ if recv_ty. peel_refs ( ) != local_ty {
1383
+ return ;
1384
+ }
1385
+ }
1386
+ }
1387
+
1388
+ // Get closure's arguments
1389
+ let ty:: Closure ( _, substs) = typeck_results. expr_ty ( closure_expr) . kind ( ) else { unreachable ! ( ) } ;
1390
+ let sig = substs. as_closure ( ) . sig ( ) ;
1391
+ let tupled_params =
1392
+ tcx. erase_late_bound_regions ( sig. inputs ( ) . iter ( ) . next ( ) . unwrap ( ) . map_bound ( |& b| b) ) ;
1393
+ let ty:: Tuple ( params) = tupled_params. kind ( ) else { return } ;
1394
+
1395
+ // Find the first argument with a matching type, get its name
1396
+ let Some ( ( _, this_name) ) = params
1397
+ . iter ( )
1398
+ . zip ( hir. body_param_names ( closure. body ) )
1399
+ . find ( |( param_ty, name) |{
1400
+ // FIXME: also support deref for stuff like `Rc` arguments
1401
+ param_ty. peel_refs ( ) == local_ty && name != & Ident :: empty ( )
1402
+ } )
1403
+ else { return } ;
1404
+
1405
+ let spans;
1406
+ if let Some ( ( _path_expr, qpath) ) = finder. error_path
1407
+ && let hir:: QPath :: Resolved ( _, path) = qpath
1408
+ && let hir:: def:: Res :: Local ( local_id) = path. res
1409
+ {
1410
+ // Find all references to the problematic variable in this closure body
1411
+
1412
+ struct VariableUseFinder {
1413
+ local_id : hir:: HirId ,
1414
+ spans : Vec < Span > ,
1415
+ }
1416
+ impl < ' hir > Visitor < ' hir > for VariableUseFinder {
1417
+ fn visit_expr ( & mut self , ex : & ' hir hir:: Expr < ' hir > ) {
1418
+ if let hir:: ExprKind :: Path ( qpath) = & ex. kind
1419
+ && let hir:: QPath :: Resolved ( _, path) = qpath
1420
+ && let hir:: def:: Res :: Local ( local_id) = path. res
1421
+ && local_id == self . local_id
1422
+ {
1423
+ self . spans . push ( ex. span ) ;
1424
+ }
1425
+
1426
+ hir:: intravisit:: walk_expr ( self , ex) ;
1427
+ }
1428
+ }
1429
+
1430
+ let mut finder = VariableUseFinder { local_id, spans : Vec :: new ( ) } ;
1431
+ finder. visit_expr ( hir. body ( closure. body ) . value ) ;
1432
+
1433
+ spans = finder. spans ;
1434
+ } else {
1435
+ spans = vec ! [ capture_kind_span] ;
1436
+ }
1437
+
1438
+ err. multipart_suggestion (
1439
+ "try using the closure argument" ,
1440
+ iter:: zip ( spans, iter:: repeat ( this_name. to_string ( ) ) ) . collect ( ) ,
1441
+ Applicability :: MaybeIncorrect ,
1442
+ ) ;
1443
+ }
1444
+
1266
1445
fn suggest_binding_for_closure_capture_self (
1267
1446
& self ,
1268
1447
err : & mut Diagnostic ,
0 commit comments