Skip to content

Commit 0d9998c

Browse files
committed
Added diagnostics for suggesting mut x on repeated mutations of x.
(Follow-on commits updating the test suite show the resulting changes to diagnostic output.)
1 parent 620a853 commit 0d9998c

File tree

3 files changed

+63
-46
lines changed

3 files changed

+63
-46
lines changed

src/librustc/mir/mod.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,45 @@ pub struct LocalDecl<'tcx> {
635635
}
636636

637637
impl<'tcx> LocalDecl<'tcx> {
638+
/// Returns true only if local is a binding that can itself be
639+
/// made mutable via the addition of the `mut` keyword, namely
640+
/// something like the occurrences of `x` in:
641+
/// - `fn foo(x: Type) { ... }`,
642+
/// - `let x = ...`,
643+
/// - or `match ... { C(x) => ... }`
644+
pub fn can_be_made_mutable(&self) -> bool
645+
{
646+
match self.is_user_variable {
647+
Some(ClearCrossCrate::Set(BindingForm::Var(VarBindingForm {
648+
binding_mode: ty::BindingMode::BindByValue(_),
649+
opt_ty_info: _,
650+
}))) => true,
651+
652+
// FIXME: might be able to thread the distinction between
653+
// `self`/`mut self`/`&self`/`&mut self` into the
654+
// `BindingForm::ImplicitSelf` variant, (and then return
655+
// true here for solely the first case).
656+
_ => false,
657+
}
658+
}
659+
660+
/// Returns true if local is definitely not a `ref ident` or
661+
/// `ref mut ident` binding. (Such bindings cannot be made into
662+
/// mutable bindings, but the inverse does not necessarily hold).
663+
pub fn is_nonref_binding(&self) -> bool
664+
{
665+
match self.is_user_variable {
666+
Some(ClearCrossCrate::Set(BindingForm::Var(VarBindingForm {
667+
binding_mode: ty::BindingMode::BindByValue(_),
668+
opt_ty_info: _,
669+
}))) => true,
670+
671+
Some(ClearCrossCrate::Set(BindingForm::ImplicitSelf)) => true,
672+
673+
_ => false,
674+
}
675+
}
676+
638677
/// Create a new `LocalDecl` for a temporary.
639678
#[inline]
640679
pub fn new_temp(ty: Ty<'tcx>, span: Span) -> Self {

src/librustc_mir/borrow_check/error_reporting.rs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -593,11 +593,18 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
593593
err.emit();
594594
}
595595

596+
/// Reports an illegal reassignment; for example, an assignment to
597+
/// (part of) a non-`mut` local that occurs potentially after that
598+
/// local has already been initialized. `place` is the path being
599+
/// assigned; `err_place` is a place providing a reason why
600+
/// `place` is not mutable (e.g. the non-`mut` local `x` in an
601+
/// assignment to `x.f`).
596602
pub(super) fn report_illegal_reassignment(
597603
&mut self,
598604
_context: Context,
599605
(place, span): (&Place<'tcx>, Span),
600606
assigned_span: Span,
607+
err_place: &Place<'tcx>,
601608
) {
602609
let is_arg = if let Place::Local(local) = place {
603610
if let LocalKind::Arg = self.mir.local_kind(*local) {
@@ -621,16 +628,23 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
621628
"cannot assign twice to immutable variable"
622629
};
623630
if span != assigned_span {
624-
if is_arg {
625-
err.span_label(assigned_span, "argument not declared as `mut`");
626-
} else {
631+
if !is_arg {
627632
let value_msg = match self.describe_place(place) {
628633
Some(name) => format!("`{}`", name),
629634
None => "value".to_owned(),
630635
};
631636
err.span_label(assigned_span, format!("first assignment to {}", value_msg));
632637
}
633638
}
639+
if let Place::Local(local) = err_place {
640+
let local_decl = &self.mir.local_decls[*local];
641+
if let Some(name) = local_decl.name {
642+
if local_decl.can_be_made_mutable() {
643+
err.span_label(local_decl.source_info.span,
644+
format!("consider changing this to `mut {}`", name));
645+
}
646+
}
647+
}
634648
err.span_label(span, msg);
635649
err.emit();
636650
}

src/librustc_mir/borrow_check/mod.rs

Lines changed: 7 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1398,9 +1398,10 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
13981398
) {
13991399
debug!("check_if_reassignment_to_immutable_state({:?})", place);
14001400
// determine if this path has a non-mut owner (and thus needs checking).
1401-
if let Ok(..) = self.is_mutable(place, LocalMutationIsAllowed::No) {
1402-
return;
1403-
}
1401+
let err_place = match self.is_mutable(place, LocalMutationIsAllowed::No) {
1402+
Ok(..) => return,
1403+
Err(place) => place,
1404+
};
14041405
debug!(
14051406
"check_if_reassignment_to_immutable_state({:?}) - is an imm local",
14061407
place
@@ -1410,7 +1411,7 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
14101411
let init = self.move_data.inits[i];
14111412
let init_place = &self.move_data.move_paths[init.path].place;
14121413
if places_conflict(self.tcx, self.mir, &init_place, place, Deep) {
1413-
self.report_illegal_reassignment(context, (place, span), init.span);
1414+
self.report_illegal_reassignment(context, (place, span), init.span, err_place);
14141415
break;
14151416
}
14161417
}
@@ -1784,7 +1785,7 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
17841785
match the_place_err {
17851786
// We want to suggest users use `let mut` for local (user
17861787
// variable) mutations...
1787-
Place::Local(local) if local_can_be_made_mutable(self.mir, *local) => {
1788+
Place::Local(local) if self.mir.local_decls[*local].can_be_made_mutable() => {
17881789
// ... but it doesn't make sense to suggest it on
17891790
// variables that are `ref x`, `ref mut x`, `&self`,
17901791
// or `&mut self` (such variables are simply not
@@ -1819,7 +1820,7 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
18191820
// arbitrary base for the projection?
18201821
Place::Projection(box Projection { base: Place::Local(local),
18211822
elem: ProjectionElem::Deref })
1822-
if local_is_nonref_binding(self.mir, *local) =>
1823+
if self.mir.local_decls[*local].is_nonref_binding() =>
18231824
{
18241825
let (err_help_span, suggested_code) =
18251826
find_place_to_suggest_ampmut(self.tcx, self.mir, *local);
@@ -1846,43 +1847,6 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
18461847
err.emit();
18471848
return true;
18481849

1849-
// Returns true if local is a binding that can itself be made
1850-
// mutable via the addition of the `mut` keyword, namely
1851-
// something like:
1852-
// - `fn foo(x: Type) { ... }`,
1853-
// - `let x = ...`,
1854-
// - or `match ... { C(x) => ... }`
1855-
fn local_can_be_made_mutable(mir: &Mir, local: mir::Local) -> bool
1856-
{
1857-
let local = &mir.local_decls[local];
1858-
match local.is_user_variable {
1859-
Some(ClearCrossCrate::Set(mir::BindingForm::Var(mir::VarBindingForm {
1860-
binding_mode: ty::BindingMode::BindByValue(_),
1861-
opt_ty_info: _,
1862-
}))) => true,
1863-
1864-
_ => false,
1865-
}
1866-
}
1867-
1868-
// Returns true if local is definitely not a `ref ident` or
1869-
// `ref mut ident` binding. (Such bindings cannot be made into
1870-
// mutable bindings.)
1871-
fn local_is_nonref_binding(mir: &Mir, local: mir::Local) -> bool
1872-
{
1873-
let local = &mir.local_decls[local];
1874-
match local.is_user_variable {
1875-
Some(ClearCrossCrate::Set(mir::BindingForm::Var(mir::VarBindingForm {
1876-
binding_mode: ty::BindingMode::BindByValue(_),
1877-
opt_ty_info: _,
1878-
}))) => true,
1879-
1880-
Some(ClearCrossCrate::Set(mir::BindingForm::ImplicitSelf)) => true,
1881-
1882-
_ => false,
1883-
}
1884-
}
1885-
18861850
// Returns the span to highlight and the associated text to
18871851
// present when suggesting that the user use an `&mut`.
18881852
//

0 commit comments

Comments
 (0)