Skip to content

Commit 102c0af

Browse files
committed
Add suggestion to use a closure arg instead of a capture on bck error
1 parent cfe5c3c commit 102c0af

File tree

4 files changed

+237
-8
lines changed

4 files changed

+237
-8
lines changed

compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs

Lines changed: 182 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::iter;
2+
13
use either::Either;
24
use rustc_data_structures::captures::Captures;
35
use rustc_data_structures::fx::FxIndexSet;
@@ -10,25 +12,25 @@ use rustc_hir::intravisit::{walk_block, walk_expr, Visitor};
1012
use rustc_hir::{AsyncGeneratorKind, GeneratorKind, LangItem};
1113
use rustc_infer::infer::TyCtxtInferExt;
1214
use rustc_infer::traits::ObligationCause;
15+
use rustc_middle::hir::nested_filter::OnlyBodies;
1316
use rustc_middle::mir::tcx::PlaceTy;
1417
use 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};
2023
use rustc_middle::util::CallKind;
2124
use rustc_mir_dataflow::move_paths::{InitKind, MoveOutIndex, MovePathIndex};
2225
use rustc_span::def_id::LocalDefId;
2326
use rustc_span::hygiene::DesugaringKind;
24-
use rustc_span::symbol::{kw, sym};
27+
use rustc_span::symbol::{kw, sym, Ident};
2528
use rustc_span::{BytePos, Span, Symbol};
2629
use rustc_trait_selection::infer::InferCtxtExt;
2730
use rustc_trait_selection::traits::ObligationCtxt;
2831

2932
use crate::borrow_set::TwoPhaseActivation;
3033
use crate::borrowck_errors;
31-
3234
use crate::diagnostics::conflict_errors::StorageDeadOrDrop::LocalStorageDead;
3335
use crate::diagnostics::{find_all_local_uses, CapturedMessageOpt};
3436
use 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,
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// run-rustfix
2+
#![allow(unused)]
3+
struct S;
4+
5+
impl S {
6+
fn call(&mut self, f: impl FnOnce((), &mut Self)) {
7+
// change state or something ...
8+
f((), self);
9+
// change state or something ...
10+
}
11+
12+
fn get(&self) {}
13+
fn set(&mut self) {}
14+
}
15+
16+
fn main() {
17+
let mut v = S;
18+
19+
v.call(|(), this: &mut S| this.get());
20+
//~^ error: cannot borrow `v` as mutable because it is also borrowed as immutable
21+
v.call(|(), this: &mut S| this.set());
22+
//~^ error: cannot borrow `v` as mutable more than once at a time
23+
//~| error: cannot borrow `v` as mutable more than once at a time
24+
25+
v.call(|(), this: &mut S| {
26+
//~^ error: cannot borrow `v` as mutable more than once at a time
27+
//~| error: cannot borrow `v` as mutable more than once at a time
28+
29+
_ = this;
30+
this.set();
31+
this.get();
32+
S::get(&this);
33+
34+
use std::ops::Add;
35+
let v = 0u32;
36+
_ = v + v;
37+
_ = v.add(3);
38+
});
39+
}

tests/ui/borrowck/issue-109271-pass-self-into-closure.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// run-rustfix
12
#![allow(unused)]
23
struct S;
34

tests/ui/borrowck/issue-109271-pass-self-into-closure.stderr

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,29 @@
11
error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
2-
--> $DIR/issue-109271-pass-self-into-closure.rs:18:5
2+
--> $DIR/issue-109271-pass-self-into-closure.rs:19:5
33
|
44
LL | v.call(|(), this: &mut S| v.get());
55
| ^^----^------------------^-^^^^^^^
66
| | | | |
77
| | | | first borrow occurs due to use of `v` in closure
8+
| | | | help: try using the closure argument: `this`
89
| | | immutable borrow occurs here
910
| | immutable borrow later used by call
1011
| mutable borrow occurs here
1112

1213
error[E0499]: cannot borrow `v` as mutable more than once at a time
13-
--> $DIR/issue-109271-pass-self-into-closure.rs:20:5
14+
--> $DIR/issue-109271-pass-self-into-closure.rs:21:5
1415
|
1516
LL | v.call(|(), this: &mut S| v.set());
1617
| ^^----^------------------^-^^^^^^^
1718
| | | | |
1819
| | | | first borrow occurs due to use of `v` in closure
20+
| | | | help: try using the closure argument: `this`
1921
| | | first mutable borrow occurs here
2022
| | first borrow later used by call
2123
| second mutable borrow occurs here
2224

2325
error[E0499]: cannot borrow `v` as mutable more than once at a time
24-
--> $DIR/issue-109271-pass-self-into-closure.rs:20:12
26+
--> $DIR/issue-109271-pass-self-into-closure.rs:21:12
2527
|
2628
LL | v.call(|(), this: &mut S| v.set());
2729
| -------^^^^^^^^^^^^^^^^^^---------
@@ -32,7 +34,7 @@ LL | v.call(|(), this: &mut S| v.set());
3234
| first mutable borrow occurs here
3335

3436
error[E0499]: cannot borrow `v` as mutable more than once at a time
35-
--> $DIR/issue-109271-pass-self-into-closure.rs:24:5
37+
--> $DIR/issue-109271-pass-self-into-closure.rs:25:5
3638
|
3739
LL | v.call(|(), this: &mut S| {
3840
| ^ ---- ------------------ first mutable borrow occurs here
@@ -49,9 +51,17 @@ LL | | v.set();
4951
LL | | _ = v.add(3);
5052
LL | | });
5153
| |______^ second mutable borrow occurs here
54+
|
55+
help: try using the closure argument
56+
|
57+
LL ~ _ = this;
58+
LL ~ this.set();
59+
LL ~ this.get();
60+
LL ~ S::get(&this);
61+
|
5262

5363
error[E0499]: cannot borrow `v` as mutable more than once at a time
54-
--> $DIR/issue-109271-pass-self-into-closure.rs:24:12
64+
--> $DIR/issue-109271-pass-self-into-closure.rs:25:12
5565
|
5666
LL | v.call(|(), this: &mut S| {
5767
| - ---- ^^^^^^^^^^^^^^^^^^ second mutable borrow occurs here

0 commit comments

Comments
 (0)