Skip to content

Commit c825459

Browse files
committed
Provide help on closures capturing self causing borrow checker errors
1 parent 2b8590e commit c825459

File tree

5 files changed

+252
-5
lines changed

5 files changed

+252
-5
lines changed

compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs

+146-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
use crate::diagnostics::mutability_errors::mut_borrow_of_mutable_ref;
12
use either::Either;
3+
use hir::Closure;
24
use rustc_const_eval::util::CallKind;
35
use rustc_data_structures::captures::Captures;
46
use rustc_data_structures::fx::FxHashSet;
@@ -20,7 +22,7 @@ use rustc_middle::ty::{self, suggest_constraining_type_params, PredicateKind, Ty
2022
use rustc_mir_dataflow::move_paths::{InitKind, MoveOutIndex, MovePathIndex};
2123
use rustc_span::def_id::LocalDefId;
2224
use rustc_span::hygiene::DesugaringKind;
23-
use rustc_span::symbol::sym;
25+
use rustc_span::symbol::{kw, sym};
2426
use rustc_span::{BytePos, Span, Symbol};
2527
use rustc_trait_selection::infer::InferCtxtExt;
2628

@@ -39,6 +41,8 @@ use super::{
3941
DescribePlaceOpt, RegionName, RegionNameSource, UseSpans,
4042
};
4143

44+
use rustc_hir::def::Res;
45+
4246
#[derive(Debug)]
4347
struct MoveSite {
4448
/// Index of the "move out" that we found. The `MoveData` can
@@ -356,7 +360,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
356360
if let Some(hir::Node::Item(hir::Item {
357361
kind: hir::ItemKind::Fn(_, _, body_id),
358362
..
359-
})) = hir.find(hir.local_def_id_to_hir_id(self.mir_def_id()))
363+
})) = hir.find(self.mir_hir_id())
360364
&& let Some(hir::Node::Expr(expr)) = hir.find(body_id.hir_id)
361365
{
362366
let place = &self.move_data.move_paths[mpi].place;
@@ -948,7 +952,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
948952
}
949953
(BorrowKind::Mut { .. }, BorrowKind::Shared) => {
950954
first_borrow_desc = "immutable ";
951-
self.cannot_reborrow_already_borrowed(
955+
let mut err = self.cannot_reborrow_already_borrowed(
952956
span,
953957
&desc_place,
954958
&msg_place,
@@ -958,7 +962,13 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
958962
"immutable",
959963
&msg_borrow,
960964
None,
961-
)
965+
);
966+
self.suggest_binding_for_closure_capture_self(
967+
&mut err,
968+
issued_borrow.borrowed_place,
969+
&issued_spans,
970+
);
971+
err
962972
}
963973

964974
(BorrowKind::Mut { .. }, BorrowKind::Mut { .. }) => {
@@ -1240,6 +1250,138 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
12401250
}
12411251
}
12421252

1253+
fn suggest_binding_for_closure_capture_self(
1254+
&self,
1255+
err: &mut Diagnostic,
1256+
borrowed_place: Place<'tcx>,
1257+
issued_spans: &UseSpans<'tcx>,
1258+
) {
1259+
let UseSpans::ClosureUse { capture_kind_span, .. } = issued_spans else { return };
1260+
let hir = self.infcx.tcx.hir();
1261+
1262+
// check whether the borrowed place is capturing `self` by mut reference
1263+
let local = borrowed_place.local;
1264+
let Some(_) = self
1265+
.body
1266+
.local_decls
1267+
.get(local)
1268+
.map(|l| mut_borrow_of_mutable_ref(l, self.local_names[local])) else { return };
1269+
1270+
struct ExpressionFinder<'hir> {
1271+
capture_span: Span,
1272+
closure_change_spans: Vec<Span>,
1273+
closure_arg_span: Option<Span>,
1274+
in_closure: bool,
1275+
suggest_arg: String,
1276+
hir: rustc_middle::hir::map::Map<'hir>,
1277+
closure_local_id: Option<hir::HirId>,
1278+
closure_call_changes: Vec<(Span, String)>,
1279+
}
1280+
impl<'hir> Visitor<'hir> for ExpressionFinder<'hir> {
1281+
fn visit_expr(&mut self, e: &'hir hir::Expr<'hir>) {
1282+
if e.span.contains(self.capture_span) {
1283+
if let hir::ExprKind::Closure(&Closure {
1284+
movability: None,
1285+
body,
1286+
fn_arg_span,
1287+
fn_decl: hir::FnDecl{ inputs, .. },
1288+
..
1289+
}) = e.kind &&
1290+
let Some(hir::Node::Expr(body )) = self.hir.find(body.hir_id) {
1291+
self.suggest_arg = "this: &Self".to_string();
1292+
if inputs.len() > 0 {
1293+
self.suggest_arg.push_str(", ");
1294+
}
1295+
self.in_closure = true;
1296+
self.closure_arg_span = fn_arg_span;
1297+
self.visit_expr(body);
1298+
self.in_closure = false;
1299+
}
1300+
}
1301+
if let hir::Expr { kind: hir::ExprKind::Path(path), .. } = e {
1302+
if let hir::QPath::Resolved(_, hir::Path { segments: [seg], ..}) = path &&
1303+
seg.ident.name == kw::SelfLower && self.in_closure {
1304+
self.closure_change_spans.push(e.span);
1305+
}
1306+
}
1307+
hir::intravisit::walk_expr(self, e);
1308+
}
1309+
1310+
fn visit_local(&mut self, local: &'hir hir::Local<'hir>) {
1311+
if let hir::Pat { kind: hir::PatKind::Binding(_, hir_id, _ident, _), .. } = local.pat &&
1312+
let Some(init) = local.init
1313+
{
1314+
if let hir::Expr { kind: hir::ExprKind::Closure(&Closure {
1315+
movability: None,
1316+
..
1317+
}), .. } = init &&
1318+
init.span.contains(self.capture_span) {
1319+
self.closure_local_id = Some(*hir_id);
1320+
}
1321+
}
1322+
hir::intravisit::walk_local(self, local);
1323+
}
1324+
1325+
fn visit_stmt(&mut self, s: &'hir hir::Stmt<'hir>) {
1326+
if let hir::StmtKind::Semi(e) = s.kind &&
1327+
let hir::ExprKind::Call(hir::Expr { kind: hir::ExprKind::Path(path), ..}, args) = e.kind &&
1328+
let hir::QPath::Resolved(_, hir::Path { segments: [seg], ..}) = path &&
1329+
let Res::Local(hir_id) = seg.res &&
1330+
Some(hir_id) == self.closure_local_id {
1331+
let mut arg_str = "self".to_string();
1332+
if args.len() > 0 {
1333+
arg_str.push_str(", ");
1334+
}
1335+
self.closure_call_changes.push((seg.ident.span, arg_str));
1336+
}
1337+
hir::intravisit::walk_stmt(self, s);
1338+
}
1339+
}
1340+
1341+
if let Some(hir::Node::ImplItem(
1342+
hir::ImplItem { kind: hir::ImplItemKind::Fn(_fn_sig, body_id), .. }
1343+
)) = hir.find(self.mir_hir_id()) &&
1344+
let Some(hir::Node::Expr(expr)) = hir.find(body_id.hir_id) {
1345+
let mut finder = ExpressionFinder {
1346+
capture_span: *capture_kind_span,
1347+
closure_change_spans: vec![],
1348+
closure_arg_span: None,
1349+
in_closure: false,
1350+
suggest_arg: String::new(),
1351+
closure_local_id: None,
1352+
closure_call_changes: vec![],
1353+
hir,
1354+
};
1355+
finder.visit_expr(expr);
1356+
1357+
if finder.closure_change_spans.is_empty() || finder.closure_call_changes.is_empty() {
1358+
return;
1359+
}
1360+
1361+
let mut sugg = vec![];
1362+
let sm = self.infcx.tcx.sess.source_map();
1363+
1364+
if let Some(span) = finder.closure_arg_span {
1365+
sugg.push((sm.next_point(span.shrink_to_lo()).shrink_to_hi(), finder.suggest_arg));
1366+
}
1367+
for span in finder.closure_change_spans {
1368+
sugg.push((span, "this".to_string()));
1369+
}
1370+
1371+
for (span, suggest) in finder.closure_call_changes {
1372+
if let Ok(span) = sm.span_extend_while(span, |c| c != '(') {
1373+
sugg.push((sm.next_point(span).shrink_to_hi(), suggest));
1374+
}
1375+
}
1376+
1377+
err.multipart_suggestion_verbose(
1378+
"try explicitly pass `&Self` into the Closure as an argument",
1379+
sugg,
1380+
Applicability::MachineApplicable,
1381+
);
1382+
}
1383+
}
1384+
12431385
/// Returns the description of the root place for a conflicting borrow and the full
12441386
/// descriptions of the places that caused the conflict.
12451387
///

compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1094,7 +1094,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
10941094
}
10951095
}
10961096

1097-
fn mut_borrow_of_mutable_ref(local_decl: &LocalDecl<'_>, local_name: Option<Symbol>) -> bool {
1097+
pub fn mut_borrow_of_mutable_ref(local_decl: &LocalDecl<'_>, local_name: Option<Symbol>) -> bool {
10981098
debug!("local_info: {:?}, ty.kind(): {:?}", local_decl.local_info, local_decl.ty.kind());
10991099

11001100
match local_decl.local_info.as_deref() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//run-rustfix
2+
#![allow(unused)]
3+
4+
struct S;
5+
impl S {
6+
fn foo(&mut self) {
7+
let x = |this: &Self, v: i32| {
8+
this.bar();
9+
this.hel();
10+
};
11+
self.qux(); //~ ERROR cannot borrow `*self` as mutable because it is also borrowed as immutable
12+
x(self, 1);
13+
x(self, 3);
14+
}
15+
fn bar(&self) {}
16+
fn hel(&self) {}
17+
fn qux(&mut self) {}
18+
19+
fn hello(&mut self) {
20+
let y = |this: &Self| {
21+
this.bar();
22+
};
23+
self.qux(); //~ ERROR cannot borrow `*self` as mutable because it is also borrowed as immutable
24+
y(self);
25+
}
26+
}
27+
28+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//run-rustfix
2+
#![allow(unused)]
3+
4+
struct S;
5+
impl S {
6+
fn foo(&mut self) {
7+
let x = |v: i32| {
8+
self.bar();
9+
self.hel();
10+
};
11+
self.qux(); //~ ERROR cannot borrow `*self` as mutable because it is also borrowed as immutable
12+
x(1);
13+
x(3);
14+
}
15+
fn bar(&self) {}
16+
fn hel(&self) {}
17+
fn qux(&mut self) {}
18+
19+
fn hello(&mut self) {
20+
let y = || {
21+
self.bar();
22+
};
23+
self.qux(); //~ ERROR cannot borrow `*self` as mutable because it is also borrowed as immutable
24+
y();
25+
}
26+
}
27+
28+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
error[E0502]: cannot borrow `*self` as mutable because it is also borrowed as immutable
2+
--> $DIR/issue-105761-suggest-self-for-closure.rs:11:9
3+
|
4+
LL | let x = |v: i32| {
5+
| -------- immutable borrow occurs here
6+
LL | self.bar();
7+
| ---- first borrow occurs due to use of `self` in closure
8+
...
9+
LL | self.qux();
10+
| ^^^^^^^^^^ mutable borrow occurs here
11+
LL | x(1);
12+
| - immutable borrow later used here
13+
|
14+
help: try explicitly pass `&Self` into the Closure as an argument
15+
|
16+
LL ~ let x = |this: &Self, v: i32| {
17+
LL ~ this.bar();
18+
LL ~ this.hel();
19+
LL | };
20+
LL | self.qux();
21+
LL ~ x(self, 1);
22+
LL ~ x(self, 3);
23+
|
24+
25+
error[E0502]: cannot borrow `*self` as mutable because it is also borrowed as immutable
26+
--> $DIR/issue-105761-suggest-self-for-closure.rs:23:9
27+
|
28+
LL | let y = || {
29+
| -- immutable borrow occurs here
30+
LL | self.bar();
31+
| ---- first borrow occurs due to use of `self` in closure
32+
LL | };
33+
LL | self.qux();
34+
| ^^^^^^^^^^ mutable borrow occurs here
35+
LL | y();
36+
| - immutable borrow later used here
37+
|
38+
help: try explicitly pass `&Self` into the Closure as an argument
39+
|
40+
LL ~ let y = |this: &Self| {
41+
LL ~ this.bar();
42+
LL | };
43+
LL | self.qux();
44+
LL ~ y(self);
45+
|
46+
47+
error: aborting due to 2 previous errors
48+
49+
For more information about this error, try `rustc --explain E0502`.

0 commit comments

Comments
 (0)