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.
This commit is contained in:
parent
0b42deaccc
commit
e27315268b
11 changed files with 309 additions and 2 deletions
|
@ -15,16 +15,18 @@ use rustc_span::symbol::sym;
|
|||
use rustc_span::{BytePos, MultiSpan, Span, DUMMY_SP};
|
||||
use rustc_trait_selection::infer::InferCtxtExt;
|
||||
|
||||
use crate::borrow_set::TwoPhaseActivation;
|
||||
use crate::borrowck_errors;
|
||||
|
||||
use crate::diagnostics::find_all_local_uses;
|
||||
use crate::{
|
||||
borrow_set::BorrowData, diagnostics::Instance, prefixes::IsPrefixOf,
|
||||
InitializationRequiringAction, MirBorrowckCtxt, PrefixSet, WriteKind,
|
||||
};
|
||||
|
||||
use super::{
|
||||
explain_borrow::BorrowExplanation, FnSelfUseKind, IncludingDowncast, RegionName,
|
||||
RegionNameSource, UseSpans,
|
||||
explain_borrow::{BorrowExplanation, LaterUseKind},
|
||||
FnSelfUseKind, IncludingDowncast, RegionName, RegionNameSource, UseSpans,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -768,9 +770,92 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
|
|||
Some((issued_span, span)),
|
||||
);
|
||||
|
||||
self.suggest_using_local_if_applicable(
|
||||
&mut err,
|
||||
location,
|
||||
(place, span),
|
||||
gen_borrow_kind,
|
||||
issued_borrow,
|
||||
explanation,
|
||||
);
|
||||
|
||||
err
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self, err))]
|
||||
fn suggest_using_local_if_applicable(
|
||||
&self,
|
||||
err: &mut DiagnosticBuilder<'_>,
|
||||
location: Location,
|
||||
(place, span): (Place<'tcx>, Span),
|
||||
gen_borrow_kind: BorrowKind,
|
||||
issued_borrow: &BorrowData<'tcx>,
|
||||
explanation: BorrowExplanation,
|
||||
) {
|
||||
let used_in_call =
|
||||
matches!(explanation, BorrowExplanation::UsedLater(LaterUseKind::Call, _call_span, _));
|
||||
if !used_in_call {
|
||||
debug!("not later used in call");
|
||||
return;
|
||||
}
|
||||
|
||||
let outer_call_loc =
|
||||
if let TwoPhaseActivation::ActivatedAt(loc) = issued_borrow.activation_location {
|
||||
loc
|
||||
} else {
|
||||
issued_borrow.reserve_location
|
||||
};
|
||||
let outer_call_stmt = self.body.stmt_at(outer_call_loc);
|
||||
|
||||
let inner_param_location = location;
|
||||
let Some(inner_param_stmt) = self.body.stmt_at(inner_param_location).left() else {
|
||||
debug!("`inner_param_location` {:?} is not for a statement", inner_param_location);
|
||||
return;
|
||||
};
|
||||
let Some(&inner_param) = inner_param_stmt.kind.as_assign().map(|(p, _)| p) else {
|
||||
debug!(
|
||||
"`inner_param_location` {:?} is not for an assignment: {:?}",
|
||||
inner_param_location, inner_param_stmt
|
||||
);
|
||||
return;
|
||||
};
|
||||
let inner_param_uses = find_all_local_uses::find(self.body, inner_param.local);
|
||||
let Some((inner_call_loc,inner_call_term)) = inner_param_uses.into_iter().find_map(|loc| {
|
||||
let Either::Right(term) = self.body.stmt_at(loc) else {
|
||||
debug!("{:?} is a statement, so it can't be a call", loc);
|
||||
return None;
|
||||
};
|
||||
let TerminatorKind::Call { args, .. } = &term.kind else {
|
||||
debug!("not a call: {:?}", term);
|
||||
return None;
|
||||
};
|
||||
debug!("checking call args for uses of inner_param: {:?}", args);
|
||||
if args.contains(&Operand::Move(inner_param)) {
|
||||
Some((loc,term))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) else {
|
||||
debug!("no uses of inner_param found as a by-move call arg");
|
||||
return;
|
||||
};
|
||||
debug!("===> outer_call_loc = {:?}, inner_call_loc = {:?}", outer_call_loc, inner_call_loc);
|
||||
|
||||
let inner_call_span = inner_call_term.source_info.span;
|
||||
let outer_call_span = outer_call_stmt.either(|s| s.source_info, |t| t.source_info).span;
|
||||
if outer_call_span == inner_call_span || !outer_call_span.contains(inner_call_span) {
|
||||
// FIXME: This stops the suggestion in some cases where it should be emitted.
|
||||
// Fix the spans for those cases so it's emitted correctly.
|
||||
debug!(
|
||||
"outer span {:?} does not strictly contain inner span {:?}",
|
||||
outer_call_span, inner_call_span
|
||||
);
|
||||
return;
|
||||
}
|
||||
err.span_help(inner_call_span, "try adding a local storing this argument...");
|
||||
err.span_help(outer_call_span, "...and then using that local as the argument to this call");
|
||||
}
|
||||
|
||||
fn suggest_split_at_mut_if_applicable(
|
||||
&self,
|
||||
err: &mut DiagnosticBuilder<'_>,
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
use std::collections::BTreeSet;
|
||||
|
||||
use rustc_middle::mir::visit::{PlaceContext, Visitor};
|
||||
use rustc_middle::mir::{Body, Local, Location};
|
||||
|
||||
/// Find all uses of (including assignments to) a [`Local`].
|
||||
///
|
||||
/// Uses `BTreeSet` so output is deterministic.
|
||||
pub(super) fn find<'tcx>(body: &Body<'tcx>, local: Local) -> BTreeSet<Location> {
|
||||
let mut visitor = AllLocalUsesVisitor { for_local: local, uses: BTreeSet::default() };
|
||||
visitor.visit_body(body);
|
||||
visitor.uses
|
||||
}
|
||||
|
||||
struct AllLocalUsesVisitor {
|
||||
for_local: Local,
|
||||
uses: BTreeSet<Location>,
|
||||
}
|
||||
|
||||
impl<'tcx> Visitor<'tcx> for AllLocalUsesVisitor {
|
||||
fn visit_local(&mut self, local: &Local, _context: PlaceContext, location: Location) {
|
||||
if *local == self.for_local {
|
||||
self.uses.insert(location);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,6 +19,7 @@ use rustc_target::abi::VariantIdx;
|
|||
use super::borrow_set::BorrowData;
|
||||
use super::MirBorrowckCtxt;
|
||||
|
||||
mod find_all_local_uses;
|
||||
mod find_use;
|
||||
mod outlives_suggestion;
|
||||
mod region_name;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue