1+ use std:: iter;
2+
13use either:: Either ;
24use rustc_data_structures:: captures:: Captures ;
35use rustc_data_structures:: fx:: FxIndexSet ;
@@ -10,25 +12,25 @@ use rustc_hir::intravisit::{walk_block, walk_expr, Visitor};
1012use rustc_hir:: { AsyncGeneratorKind , GeneratorKind , LangItem } ;
1113use rustc_infer:: infer:: TyCtxtInferExt ;
1214use rustc_infer:: traits:: ObligationCause ;
15+ use rustc_middle:: hir:: nested_filter:: OnlyBodies ;
1316use rustc_middle:: mir:: tcx:: PlaceTy ;
1417use rustc_middle:: mir:: {
1518 self , AggregateKind , BindingForm , BorrowKind , ClearCrossCrate , ConstraintCategory ,
1619 FakeReadCause , LocalDecl , LocalInfo , LocalKind , Location , Operand , Place , PlaceRef ,
1720 ProjectionElem , Rvalue , Statement , StatementKind , Terminator , TerminatorKind , VarBindingForm ,
1821} ;
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 } ;
2023use rustc_middle:: util:: CallKind ;
2124use rustc_mir_dataflow:: move_paths:: { InitKind , MoveOutIndex , MovePathIndex } ;
2225use rustc_span:: def_id:: LocalDefId ;
2326use rustc_span:: hygiene:: DesugaringKind ;
24- use rustc_span:: symbol:: { kw, sym} ;
27+ use rustc_span:: symbol:: { kw, sym, Ident } ;
2528use rustc_span:: { BytePos , Span , Symbol } ;
2629use rustc_trait_selection:: infer:: InferCtxtExt ;
2730use rustc_trait_selection:: traits:: ObligationCtxt ;
2831
2932use crate :: borrow_set:: TwoPhaseActivation ;
3033use crate :: borrowck_errors;
31-
3234use crate :: diagnostics:: conflict_errors:: StorageDeadOrDrop :: LocalStorageDead ;
3335use crate :: diagnostics:: { find_all_local_uses, CapturedMessageOpt } ;
3436use crate :: {
@@ -959,6 +961,11 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
959961 None ,
960962 ) ;
961963 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+ ) ;
962969 err
963970 }
964971
@@ -977,6 +984,11 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
977984 place,
978985 issued_borrow. borrowed_place ,
979986 ) ;
987+ self . suggest_using_closure_argument_instead_of_capture (
988+ & mut err,
989+ issued_borrow. borrowed_place ,
990+ & issued_spans,
991+ ) ;
980992 err
981993 }
982994
@@ -1263,6 +1275,173 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
12631275 }
12641276 }
12651277
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+
12661445 fn suggest_binding_for_closure_capture_self (
12671446 & self ,
12681447 err : & mut Diagnostic ,
0 commit comments