Skip to content

Commit 381b275

Browse files
committed
Suggest using a temporary variable to fix borrowck errors
In Rust, nesting method calls with both require `&mut` access to `self` produces a borrow-check error: error[E0499]: cannot borrow `*self` as mutable more than once at a time --> src/lib.rs:7:14 | 7 | self.foo(self.bar()); | ---------^^^^^^^^^^- | | | | | | | second mutable borrow occurs here | | first borrow later used by call | first mutable borrow occurs here That's because Rust has a left-to-right evaluation order, and the method receiver is passed first. Thus, the argument to the method cannot then mutate `self`. There's an easy solution to this error: just extract a local variable for the inner argument: let tmp = self.bar(); self.foo(tmp); However, the error doesn't give any suggestion of how to solve the problem. As a result, new users may assume that it's impossible to express their code correctly and get stuck. This commit adds a (non-structured) suggestion to extract a local variable for the inner argument to solve the error. The suggestion uses heuristics that eliminate most false positives, though there are a few false negatives (cases where the suggestion should be emitted but is not). Those other cases can be implemented in a future change.
1 parent 0b42dea commit 381b275

10 files changed

+276
-2
lines changed

Diff for: compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs

+87-2
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,18 @@ use rustc_span::symbol::sym;
1515
use rustc_span::{BytePos, MultiSpan, Span, DUMMY_SP};
1616
use rustc_trait_selection::infer::InferCtxtExt;
1717

18+
use crate::borrow_set::TwoPhaseActivation;
1819
use crate::borrowck_errors;
1920

21+
use crate::diagnostics::find_all_local_uses;
2022
use crate::{
2123
borrow_set::BorrowData, diagnostics::Instance, prefixes::IsPrefixOf,
2224
InitializationRequiringAction, MirBorrowckCtxt, PrefixSet, WriteKind,
2325
};
2426

2527
use super::{
26-
explain_borrow::BorrowExplanation, FnSelfUseKind, IncludingDowncast, RegionName,
27-
RegionNameSource, UseSpans,
28+
explain_borrow::{BorrowExplanation, LaterUseKind},
29+
FnSelfUseKind, IncludingDowncast, RegionName, RegionNameSource, UseSpans,
2830
};
2931

3032
#[derive(Debug)]
@@ -768,9 +770,92 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
768770
Some((issued_span, span)),
769771
);
770772

773+
self.suggest_using_local_if_applicable(
774+
&mut err,
775+
location,
776+
(place, span),
777+
gen_borrow_kind,
778+
issued_borrow,
779+
explanation,
780+
);
781+
771782
err
772783
}
773784

785+
#[instrument(level = "debug", skip(self, err))]
786+
fn suggest_using_local_if_applicable(
787+
&self,
788+
err: &mut DiagnosticBuilder<'_>,
789+
location: Location,
790+
(place, span): (Place<'tcx>, Span),
791+
gen_borrow_kind: BorrowKind,
792+
issued_borrow: &BorrowData<'tcx>,
793+
explanation: BorrowExplanation,
794+
) {
795+
let used_in_call =
796+
matches!(explanation, BorrowExplanation::UsedLater(LaterUseKind::Call, _call_span, _));
797+
if !used_in_call {
798+
debug!("not later used in call");
799+
return;
800+
}
801+
802+
let outer_call_loc =
803+
if let TwoPhaseActivation::ActivatedAt(loc) = issued_borrow.activation_location {
804+
loc
805+
} else {
806+
issued_borrow.reserve_location
807+
};
808+
let outer_call_stmt = self.body.stmt_at(outer_call_loc);
809+
810+
let inner_param_location = location;
811+
let Some(inner_param_stmt) = self.body.stmt_at(inner_param_location).left() else {
812+
debug!("`inner_param_location` {:?} is not for a statement", inner_param_location);
813+
return;
814+
};
815+
let Some(&inner_param) = inner_param_stmt.kind.as_assign().map(|(p, _)| p) else {
816+
debug!(
817+
"`inner_param_location` {:?} is not for an assignment: {:?}",
818+
inner_param_location, inner_param_stmt
819+
);
820+
return;
821+
};
822+
let inner_param_uses = find_all_local_uses::find(self.body, inner_param.local);
823+
let Some((inner_call_loc,inner_call_term)) = inner_param_uses.into_iter().find_map(|loc| {
824+
let Either::Right(term) = self.body.stmt_at(loc) else {
825+
debug!("{:?} is a statement, so it can't be a call", loc);
826+
return None;
827+
};
828+
let TerminatorKind::Call { args, .. } = &term.kind else {
829+
debug!("not a call: {:?}", term);
830+
return None;
831+
};
832+
debug!("checking call args for uses of inner_param: {:?}", args);
833+
if args.contains(&Operand::Move(inner_param)) {
834+
Some((loc,term))
835+
} else {
836+
None
837+
}
838+
}) else {
839+
debug!("no uses of inner_param found as a by-move call arg");
840+
return;
841+
};
842+
debug!("===> outer_call_loc = {:?}, inner_call_loc = {:?}", outer_call_loc, inner_call_loc);
843+
844+
let inner_call_span = inner_call_term.source_info.span;
845+
let outer_call_span = outer_call_stmt.either(|s| s.source_info, |t| t.source_info).span;
846+
if outer_call_span == inner_call_span || !outer_call_span.contains(inner_call_span) {
847+
// FIXME: This stops the suggestion in some cases where it should be emitted.
848+
// Fix the spans for those cases so it's emitted correctly.
849+
debug!(
850+
"outer span {:?} does not strictly contain inner span {:?}",
851+
outer_call_span, inner_call_span
852+
);
853+
return;
854+
}
855+
err.span_help(inner_call_span, "try adding a local storing this argument...");
856+
err.span_help(outer_call_span, "...and then using that local as the argument to this call");
857+
}
858+
774859
fn suggest_split_at_mut_if_applicable(
775860
&self,
776861
err: &mut DiagnosticBuilder<'_>,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
use std::collections::BTreeSet;
2+
3+
use rustc_middle::mir::visit::{PlaceContext, Visitor};
4+
use rustc_middle::mir::{Body, Local, Location};
5+
6+
/// Find all uses of (including assignments to) a [`Local`].
7+
///
8+
/// Uses `BTreeSet` so output is deterministic.
9+
pub(super) fn find<'tcx>(body: &Body<'tcx>, local: Local) -> BTreeSet<Location> {
10+
let mut visitor = AllLocalUsesVisitor { for_local: local, uses: BTreeSet::default() };
11+
visitor.visit_body(body);
12+
visitor.uses
13+
}
14+
15+
struct AllLocalUsesVisitor {
16+
for_local: Local,
17+
uses: BTreeSet<Location>,
18+
}
19+
20+
impl<'tcx> Visitor<'tcx> for AllLocalUsesVisitor {
21+
fn visit_local(&mut self, local: &Local, _context: PlaceContext, location: Location) {
22+
if *local == self.for_local {
23+
self.uses.insert(location);
24+
}
25+
}
26+
}

Diff for: compiler/rustc_borrowck/src/diagnostics/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use rustc_target::abi::VariantIdx;
1919
use super::borrow_set::BorrowData;
2020
use super::MirBorrowckCtxt;
2121

22+
mod find_all_local_uses;
2223
mod find_use;
2324
mod outlives_suggestion;
2425
mod region_name;

Diff for: compiler/rustc_middle/src/mir/mod.rs

+14
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use crate::ty::print::{FmtPrinter, Printer};
1212
use crate::ty::subst::{Subst, SubstsRef};
1313
use crate::ty::{self, List, Ty, TyCtxt};
1414
use crate::ty::{AdtDef, InstanceDef, Region, ScalarInt, UserTypeAnnotationIndex};
15+
1516
use rustc_hir::def::{CtorKind, Namespace};
1617
use rustc_hir::def_id::{DefId, CRATE_DEF_INDEX};
1718
use rustc_hir::{self, GeneratorKind};
@@ -30,6 +31,9 @@ use rustc_serialize::{Decodable, Encodable};
3031
use rustc_span::symbol::Symbol;
3132
use rustc_span::{Span, DUMMY_SP};
3233
use rustc_target::asm::InlineAsmRegOrRegClass;
34+
35+
use either::Either;
36+
3337
use std::borrow::Cow;
3438
use std::convert::TryInto;
3539
use std::fmt::{self, Debug, Display, Formatter, Write};
@@ -503,6 +507,16 @@ impl<'tcx> Body<'tcx> {
503507
Location { block: bb, statement_index: self[bb].statements.len() }
504508
}
505509

510+
pub fn stmt_at(&self, location: Location) -> Either<&Statement<'tcx>, &Terminator<'tcx>> {
511+
let Location { block, statement_index } = location;
512+
let block_data = &self.basic_blocks[block];
513+
block_data
514+
.statements
515+
.get(statement_index)
516+
.map(Either::Left)
517+
.unwrap_or_else(|| Either::Right(block_data.terminator()))
518+
}
519+
506520
#[inline]
507521
pub fn predecessors(&self) -> &Predecessors {
508522
self.predecessor_cache.compute(&self.basic_blocks)

Diff for: src/test/ui/borrowck/suggest-local-var-double-mut.rs

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// See issue #77834.
2+
3+
#![crate_type = "lib"]
4+
5+
mod method_syntax {
6+
struct Foo;
7+
8+
impl Foo {
9+
fn foo(&mut self, _: f32) -> i32 { todo!() }
10+
fn bar(&mut self) -> f32 { todo!() }
11+
fn baz(&mut self) {
12+
self.foo(self.bar()); //~ ERROR
13+
}
14+
}
15+
}
16+
17+
mod fully_qualified_syntax {
18+
struct Foo;
19+
20+
impl Foo {
21+
fn foo(&mut self, _: f32) -> i32 { todo!() }
22+
fn bar(&mut self) -> f32 { todo!() }
23+
fn baz(&mut self) {
24+
Self::foo(self, Self::bar(self)); //~ ERROR
25+
}
26+
}
27+
}
+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
error[E0499]: cannot borrow `*self` as mutable more than once at a time
2+
--> $DIR/suggest-local-var-double-mut.rs:12:22
3+
|
4+
LL | self.foo(self.bar());
5+
| ---------^^^^^^^^^^-
6+
| | | |
7+
| | | second mutable borrow occurs here
8+
| | first borrow later used by call
9+
| first mutable borrow occurs here
10+
|
11+
help: try adding a local storing this argument...
12+
--> $DIR/suggest-local-var-double-mut.rs:12:22
13+
|
14+
LL | self.foo(self.bar());
15+
| ^^^^^^^^^^
16+
help: ...and then using that local as the argument to this call
17+
--> $DIR/suggest-local-var-double-mut.rs:12:13
18+
|
19+
LL | self.foo(self.bar());
20+
| ^^^^^^^^^^^^^^^^^^^^
21+
22+
error[E0499]: cannot borrow `*self` as mutable more than once at a time
23+
--> $DIR/suggest-local-var-double-mut.rs:24:39
24+
|
25+
LL | Self::foo(self, Self::bar(self));
26+
| --------- ---- ^^^^ second mutable borrow occurs here
27+
| | |
28+
| | first mutable borrow occurs here
29+
| first borrow later used by call
30+
|
31+
help: try adding a local storing this argument...
32+
--> $DIR/suggest-local-var-double-mut.rs:24:29
33+
|
34+
LL | Self::foo(self, Self::bar(self));
35+
| ^^^^^^^^^^^^^^^
36+
help: ...and then using that local as the argument to this call
37+
--> $DIR/suggest-local-var-double-mut.rs:24:13
38+
|
39+
LL | Self::foo(self, Self::bar(self));
40+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
41+
42+
error: aborting due to 2 previous errors
43+
44+
For more information about this error, try `rustc --explain E0499`.
+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// See issue #77834.
2+
3+
#![crate_type = "lib"]
4+
5+
mod method_syntax {
6+
struct Foo;
7+
8+
impl Foo {
9+
fn foo(&self, _: f32) -> i32 { todo!() }
10+
fn bar(&mut self) -> f32 { todo!() }
11+
fn baz(&mut self) {
12+
self.foo(self.bar()); //~ ERROR
13+
}
14+
}
15+
}
16+
17+
mod fully_qualified_syntax {
18+
struct Foo;
19+
20+
impl Foo {
21+
fn foo(&self, _: f32) -> i32 { todo!() }
22+
fn bar(&mut self) -> f32 { todo!() }
23+
fn baz(&mut self) {
24+
Self::foo(self, Self::bar(self)); //~ ERROR
25+
}
26+
}
27+
}
+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
error[E0502]: cannot borrow `*self` as mutable because it is also borrowed as immutable
2+
--> $DIR/suggest-local-var-imm-and-mut.rs:12:22
3+
|
4+
LL | self.foo(self.bar());
5+
| ---------^^^^^^^^^^-
6+
| | | |
7+
| | | mutable borrow occurs here
8+
| | immutable borrow later used by call
9+
| immutable borrow occurs here
10+
11+
error[E0502]: cannot borrow `*self` as mutable because it is also borrowed as immutable
12+
--> $DIR/suggest-local-var-imm-and-mut.rs:24:29
13+
|
14+
LL | Self::foo(self, Self::bar(self));
15+
| --------- ---- ^^^^^^^^^^^^^^^ mutable borrow occurs here
16+
| | |
17+
| | immutable borrow occurs here
18+
| immutable borrow later used by call
19+
20+
error: aborting due to 2 previous errors
21+
22+
For more information about this error, try `rustc --explain E0502`.

Diff for: src/test/ui/borrowck/two-phase-cannot-nest-mut-self-calls.stderr

+17
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,23 @@ LL | |
1313
LL | | 0
1414
LL | | });
1515
| |______- immutable borrow occurs here
16+
|
17+
help: try adding a local storing this argument...
18+
--> $DIR/two-phase-cannot-nest-mut-self-calls.rs:16:9
19+
|
20+
LL | vec.push(2);
21+
| ^^^^^^^^^^^
22+
help: ...and then using that local as the argument to this call
23+
--> $DIR/two-phase-cannot-nest-mut-self-calls.rs:14:5
24+
|
25+
LL | / vec.get({
26+
LL | |
27+
LL | | vec.push(2);
28+
LL | |
29+
LL | |
30+
LL | | 0
31+
LL | | });
32+
| |______^
1633

1734
error: aborting due to previous error
1835

Diff for: src/test/ui/codemap_tests/one_line.stderr

+11
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,17 @@ LL | v.push(v.pop().unwrap());
77
| | | second mutable borrow occurs here
88
| | first borrow later used by call
99
| first mutable borrow occurs here
10+
|
11+
help: try adding a local storing this argument...
12+
--> $DIR/one_line.rs:3:12
13+
|
14+
LL | v.push(v.pop().unwrap());
15+
| ^^^^^^^
16+
help: ...and then using that local as the argument to this call
17+
--> $DIR/one_line.rs:3:5
18+
|
19+
LL | v.push(v.pop().unwrap());
20+
| ^^^^^^^^^^^^^^^^^^^^^^^^
1021

1122
error: aborting due to previous error
1223

0 commit comments

Comments
 (0)