Skip to content

Commit b9fd498

Browse files
committed
Auto merge of rust-lang#110061 - WaffleLapkin:duality_of_myself_and_this, r=cjgillot
Add suggestion to use closure argument instead of a capture on borrowck error Fixes rust-lang#109271 r? `@compiler-errors` This should probably be refined a bit, but opening a PR so that I don't forget anything.
2 parents 39c6804 + cac143f commit b9fd498

24 files changed

+425
-83
lines changed

compiler/rustc_borrowck/src/borrowck_errors.rs

+1
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,7 @@ impl<'cx, 'tcx> crate::MirBorrowckCtxt<'cx, 'tcx> {
469469
}
470470

471471
#[rustc_lint_diagnostics]
472+
#[track_caller]
472473
pub(crate) fn struct_span_err_with_code<S: Into<MultiSpan>>(
473474
&self,
474475
sp: S,

compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs

+160-14
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,27 +12,26 @@ 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;
33-
use crate::diagnostics::mutability_errors::mut_borrow_of_mutable_ref;
3435
use crate::diagnostics::{find_all_local_uses, CapturedMessageOpt};
3536
use crate::{
3637
borrow_set::BorrowData, diagnostics::Instance, prefixes::IsPrefixOf,
@@ -959,7 +960,8 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
959960
&msg_borrow,
960961
None,
961962
);
962-
self.suggest_binding_for_closure_capture_self(
963+
self.suggest_binding_for_closure_capture_self(&mut err, &issued_spans);
964+
self.suggest_using_closure_argument_instead_of_capture(
963965
&mut err,
964966
issued_borrow.borrowed_place,
965967
&issued_spans,
@@ -982,6 +984,11 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
982984
place,
983985
issued_borrow.borrowed_place,
984986
);
987+
self.suggest_using_closure_argument_instead_of_capture(
988+
&mut err,
989+
issued_borrow.borrowed_place,
990+
&issued_spans,
991+
);
985992
err
986993
}
987994

@@ -1268,22 +1275,161 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
12681275
}
12691276
}
12701277

1271-
fn suggest_binding_for_closure_capture_self(
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(
12721295
&self,
12731296
err: &mut Diagnostic,
12741297
borrowed_place: Place<'tcx>,
12751298
issued_spans: &UseSpans<'tcx>,
12761299
) {
1277-
let UseSpans::ClosureUse { capture_kind_span, .. } = issued_spans else { return };
1278-
let hir = self.infcx.tcx.hir();
1300+
let &UseSpans::ClosureUse { capture_kind_span, .. } = issued_spans else { return };
1301+
let tcx = self.infcx.tcx;
1302+
let hir = tcx.hir();
12791303

1280-
// check whether the borrowed place is capturing `self` by mut reference
1304+
// Get the type of the local that we are trying to borrow
12811305
let local = borrowed_place.local;
1282-
let Some(_) = self
1283-
.body
1284-
.local_decls
1285-
.get(local)
1286-
.map(|l| mut_borrow_of_mutable_ref(l, self.local_names[local])) else { return };
1306+
let local_ty = self.body.local_decls[local].ty;
1307+
1308+
// Get the body the error happens in
1309+
let Some(body_id) = hir.get(self.mir_hir_id()).body_id() else { return };
1310+
1311+
let body_expr = hir.body(body_id).value;
1312+
1313+
struct ClosureFinder<'hir> {
1314+
hir: rustc_middle::hir::map::Map<'hir>,
1315+
borrow_span: Span,
1316+
res: Option<(&'hir hir::Expr<'hir>, &'hir hir::Closure<'hir>)>,
1317+
/// The path expression with the `borrow_span` span
1318+
error_path: Option<(&'hir hir::Expr<'hir>, &'hir hir::QPath<'hir>)>,
1319+
}
1320+
impl<'hir> Visitor<'hir> for ClosureFinder<'hir> {
1321+
type NestedFilter = OnlyBodies;
1322+
1323+
fn nested_visit_map(&mut self) -> Self::Map {
1324+
self.hir
1325+
}
1326+
1327+
fn visit_expr(&mut self, ex: &'hir hir::Expr<'hir>) {
1328+
if let hir::ExprKind::Path(qpath) = &ex.kind
1329+
&& ex.span == self.borrow_span
1330+
{
1331+
self.error_path = Some((ex, qpath));
1332+
}
1333+
1334+
if let hir::ExprKind::Closure(closure) = ex.kind
1335+
&& ex.span.contains(self.borrow_span)
1336+
// To support cases like `|| { v.call(|this| v.get()) }`
1337+
// FIXME: actually support such cases (need to figure out how to move from the capture place to original local)
1338+
&& self.res.as_ref().map_or(true, |(prev_res, _)| prev_res.span.contains(ex.span))
1339+
{
1340+
self.res = Some((ex, closure));
1341+
}
1342+
1343+
hir::intravisit::walk_expr(self, ex);
1344+
}
1345+
}
1346+
1347+
// Find the closure that most tightly wraps `capture_kind_span`
1348+
let mut finder =
1349+
ClosureFinder { hir, borrow_span: capture_kind_span, res: None, error_path: None };
1350+
finder.visit_expr(body_expr);
1351+
let Some((closure_expr, closure)) = finder.res else { return };
1352+
1353+
let typeck_results: &TypeckResults<'_> =
1354+
tcx.typeck_opt_const_arg(self.body.source.with_opt_param().as_local().unwrap());
1355+
1356+
// Check that the parent of the closure is a method call,
1357+
// with receiver matching with local's type (modulo refs)
1358+
let parent = hir.parent_id(closure_expr.hir_id);
1359+
if let hir::Node::Expr(parent) = hir.get(parent) {
1360+
if let hir::ExprKind::MethodCall(_, recv, ..) = parent.kind {
1361+
let recv_ty = typeck_results.expr_ty(recv);
1362+
1363+
if recv_ty.peel_refs() != local_ty {
1364+
return;
1365+
}
1366+
}
1367+
}
1368+
1369+
// Get closure's arguments
1370+
let ty::Closure(_, substs) = typeck_results.expr_ty(closure_expr).kind() else { unreachable!() };
1371+
let sig = substs.as_closure().sig();
1372+
let tupled_params =
1373+
tcx.erase_late_bound_regions(sig.inputs().iter().next().unwrap().map_bound(|&b| b));
1374+
let ty::Tuple(params) = tupled_params.kind() else { return };
1375+
1376+
// Find the first argument with a matching type, get its name
1377+
let Some((_, this_name)) = params
1378+
.iter()
1379+
.zip(hir.body_param_names(closure.body))
1380+
.find(|(param_ty, name)|{
1381+
// FIXME: also support deref for stuff like `Rc` arguments
1382+
param_ty.peel_refs() == local_ty && name != &Ident::empty()
1383+
})
1384+
else { return };
1385+
1386+
let spans;
1387+
if let Some((_path_expr, qpath)) = finder.error_path
1388+
&& let hir::QPath::Resolved(_, path) = qpath
1389+
&& let hir::def::Res::Local(local_id) = path.res
1390+
{
1391+
// Find all references to the problematic variable in this closure body
1392+
1393+
struct VariableUseFinder {
1394+
local_id: hir::HirId,
1395+
spans: Vec<Span>,
1396+
}
1397+
impl<'hir> Visitor<'hir> for VariableUseFinder {
1398+
fn visit_expr(&mut self, ex: &'hir hir::Expr<'hir>) {
1399+
if let hir::ExprKind::Path(qpath) = &ex.kind
1400+
&& let hir::QPath::Resolved(_, path) = qpath
1401+
&& let hir::def::Res::Local(local_id) = path.res
1402+
&& local_id == self.local_id
1403+
{
1404+
self.spans.push(ex.span);
1405+
}
1406+
1407+
hir::intravisit::walk_expr(self, ex);
1408+
}
1409+
}
1410+
1411+
let mut finder = VariableUseFinder { local_id, spans: Vec::new() };
1412+
finder.visit_expr(hir.body(closure.body).value);
1413+
1414+
spans = finder.spans;
1415+
} else {
1416+
spans = vec![capture_kind_span];
1417+
}
1418+
1419+
err.multipart_suggestion(
1420+
"try using the closure argument",
1421+
iter::zip(spans, iter::repeat(this_name.to_string())).collect(),
1422+
Applicability::MaybeIncorrect,
1423+
);
1424+
}
1425+
1426+
fn suggest_binding_for_closure_capture_self(
1427+
&self,
1428+
err: &mut Diagnostic,
1429+
issued_spans: &UseSpans<'tcx>,
1430+
) {
1431+
let UseSpans::ClosureUse { capture_kind_span, .. } = issued_spans else { return };
1432+
let hir = self.infcx.tcx.hir();
12871433

12881434
struct ExpressionFinder<'hir> {
12891435
capture_span: Span,

compiler/rustc_hir/src/hir.rs

+31-8
Original file line numberDiff line numberDiff line change
@@ -3529,12 +3529,20 @@ impl<'hir> OwnerNode<'hir> {
35293529

35303530
pub fn body_id(&self) -> Option<BodyId> {
35313531
match self {
3532-
OwnerNode::TraitItem(TraitItem {
3533-
kind: TraitItemKind::Fn(_, TraitFn::Provided(body_id)),
3532+
OwnerNode::Item(Item {
3533+
kind:
3534+
ItemKind::Static(_, _, body) | ItemKind::Const(_, body) | ItemKind::Fn(_, _, body),
35343535
..
35353536
})
3536-
| OwnerNode::ImplItem(ImplItem { kind: ImplItemKind::Fn(_, body_id), .. })
3537-
| OwnerNode::Item(Item { kind: ItemKind::Fn(.., body_id), .. }) => Some(*body_id),
3537+
| OwnerNode::TraitItem(TraitItem {
3538+
kind:
3539+
TraitItemKind::Fn(_, TraitFn::Provided(body)) | TraitItemKind::Const(_, Some(body)),
3540+
..
3541+
})
3542+
| OwnerNode::ImplItem(ImplItem {
3543+
kind: ImplItemKind::Fn(_, body) | ImplItemKind::Const(_, body),
3544+
..
3545+
}) => Some(*body),
35383546
_ => None,
35393547
}
35403548
}
@@ -3729,12 +3737,27 @@ impl<'hir> Node<'hir> {
37293737

37303738
pub fn body_id(&self) -> Option<BodyId> {
37313739
match self {
3732-
Node::TraitItem(TraitItem {
3733-
kind: TraitItemKind::Fn(_, TraitFn::Provided(body_id)),
3740+
Node::Item(Item {
3741+
kind:
3742+
ItemKind::Static(_, _, body) | ItemKind::Const(_, body) | ItemKind::Fn(_, _, body),
3743+
..
3744+
})
3745+
| Node::TraitItem(TraitItem {
3746+
kind:
3747+
TraitItemKind::Fn(_, TraitFn::Provided(body)) | TraitItemKind::Const(_, Some(body)),
3748+
..
3749+
})
3750+
| Node::ImplItem(ImplItem {
3751+
kind: ImplItemKind::Fn(_, body) | ImplItemKind::Const(_, body),
37343752
..
37353753
})
3736-
| Node::ImplItem(ImplItem { kind: ImplItemKind::Fn(_, body_id), .. })
3737-
| Node::Item(Item { kind: ItemKind::Fn(.., body_id), .. }) => Some(*body_id),
3754+
| Node::Expr(Expr {
3755+
kind:
3756+
ExprKind::ConstBlock(AnonConst { body, .. })
3757+
| ExprKind::Closure(Closure { body, .. })
3758+
| ExprKind::Repeat(_, ArrayLen::Body(AnonConst { body, .. })),
3759+
..
3760+
}) => Some(*body),
37383761
_ => None,
37393762
}
37403763
}

tests/ui/async-await/feature-self-return-type.stderr

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ error[E0597]: `bar` does not live long enough
44
LL | let x = {
55
| - borrow later stored here
66
LL | let bar = 22;
7+
| --- binding `bar` declared here
78
LL | Foo::new(&bar).await
89
| ^^^^ borrowed value does not live long enough
910
LL |

tests/ui/async-await/issue-61949-self-return-type.stderr

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ error[E0597]: `bar` does not live long enough
1313
LL | let x = {
1414
| - borrow later stored here
1515
LL | let bar = 22;
16+
| --- binding `bar` declared here
1617
LL | Foo::new(&bar).await
1718
| ^^^^ borrowed value does not live long enough
1819
LL |
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+
}
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| v.get());
20+
//~^ error: cannot borrow `v` as mutable because it is also borrowed as immutable
21+
v.call(|(), this: &mut S| v.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+
_ = v;
30+
v.set();
31+
v.get();
32+
S::get(&v);
33+
34+
use std::ops::Add;
35+
let v = 0u32;
36+
_ = v + v;
37+
_ = v.add(3);
38+
});
39+
}

0 commit comments

Comments
 (0)