Improve NLL mutability errors
* Better explain why the place is immutable * Distinguish &T and *const T * Use better spans when a mutable borrow is for a closure capture
This commit is contained in:
parent
db568e426a
commit
13b5f69848
|
@ -202,7 +202,7 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
|
|||
/// the local assigned at `location`.
|
||||
/// This is done by searching in statements succeeding `location`
|
||||
/// and originating from `maybe_closure_span`.
|
||||
fn find_closure_span(
|
||||
pub(super) fn find_closure_span(
|
||||
&self,
|
||||
maybe_closure_span: Span,
|
||||
location: Location,
|
||||
|
|
|
@ -36,42 +36,166 @@ impl<'a, 'gcx, 'tcx> MirBorrowckCtxt<'a, 'gcx, 'tcx> {
|
|||
location: Location,
|
||||
) {
|
||||
let mut err;
|
||||
let item_msg = match self.describe_place(place) {
|
||||
Some(name) => format!("immutable item `{}`", name),
|
||||
None => "immutable item".to_owned(),
|
||||
let item_msg;
|
||||
let reason;
|
||||
let access_place_desc = self.describe_place(access_place);
|
||||
|
||||
match the_place_err {
|
||||
Place::Local(local) => {
|
||||
item_msg = format!("`{}`", access_place_desc.unwrap());
|
||||
if let Place::Local(_) = access_place {
|
||||
reason = ", as it is not declared as mutable".to_string();
|
||||
} else {
|
||||
let name = self.mir.local_decls[*local]
|
||||
.name
|
||||
.expect("immutable unnamed local");
|
||||
reason = format!(", as `{}` is not declared as mutable", name);
|
||||
}
|
||||
}
|
||||
|
||||
Place::Projection(box Projection {
|
||||
base,
|
||||
elem: ProjectionElem::Field(upvar_index, _),
|
||||
}) => {
|
||||
debug_assert!(is_closure_or_generator(
|
||||
base.ty(self.mir, self.tcx).to_ty(self.tcx)
|
||||
));
|
||||
|
||||
item_msg = format!("`{}`", access_place_desc.unwrap());
|
||||
if self.is_upvar(access_place) {
|
||||
reason = ", as it is not declared as mutable".to_string();
|
||||
} else {
|
||||
let name = self.mir.upvar_decls[upvar_index.index()].debug_name;
|
||||
reason = format!(", as `{}` is not declared as mutable", name);
|
||||
}
|
||||
}
|
||||
|
||||
Place::Projection(box Projection {
|
||||
base,
|
||||
elem: ProjectionElem::Deref,
|
||||
}) => {
|
||||
if *base == Place::Local(Local::new(1)) && !self.mir.upvar_decls.is_empty() {
|
||||
item_msg = format!("`{}`", access_place_desc.unwrap());
|
||||
debug_assert!(self.mir.local_decls[Local::new(1)].ty.is_region_ptr());
|
||||
debug_assert!(is_closure_or_generator(
|
||||
the_place_err.ty(self.mir, self.tcx).to_ty(self.tcx)
|
||||
));
|
||||
|
||||
reason = if self.is_upvar(access_place) {
|
||||
", as it is a captured variable in a `Fn` closure".to_string()
|
||||
} else {
|
||||
format!(", as `Fn` closures cannot mutate their captured variables")
|
||||
}
|
||||
} else if {
|
||||
if let Place::Local(local) = *base {
|
||||
if let Some(ClearCrossCrate::Set(BindingForm::RefForGuard))
|
||||
= self.mir.local_decls[local].is_user_variable {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} {
|
||||
item_msg = format!("`{}`", access_place_desc.unwrap());
|
||||
reason = format!(", as it is immutable for the pattern guard");
|
||||
} else {
|
||||
let pointer_type =
|
||||
if base.ty(self.mir, self.tcx).to_ty(self.tcx).is_region_ptr() {
|
||||
"`&` reference"
|
||||
} else {
|
||||
"`*const` pointer"
|
||||
};
|
||||
if let Some(desc) = access_place_desc {
|
||||
item_msg = format!("`{}`", desc);
|
||||
reason = match error_access {
|
||||
AccessKind::Mutate => format!(" which is behind a {}", pointer_type),
|
||||
AccessKind::MutableBorrow => {
|
||||
format!(", as it is behind a {}", pointer_type)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
item_msg = format!("data in a {}", pointer_type);
|
||||
reason = "".to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Place::Static(box Static { def_id, ty: _ }) => {
|
||||
if let Place::Static(_) = access_place {
|
||||
item_msg = format!("immutable static item `{}`", access_place_desc.unwrap());
|
||||
reason = "".to_string();
|
||||
} else {
|
||||
item_msg = format!("`{}`", access_place_desc.unwrap());
|
||||
let static_name = &self.tcx.item_name(*def_id);
|
||||
reason = format!(", as `{}` is an immutable static item", static_name);
|
||||
}
|
||||
}
|
||||
|
||||
Place::Projection(box Projection {
|
||||
base: _,
|
||||
elem: ProjectionElem::Index(_),
|
||||
})
|
||||
| Place::Projection(box Projection {
|
||||
base: _,
|
||||
elem: ProjectionElem::ConstantIndex { .. },
|
||||
})
|
||||
| Place::Projection(box Projection {
|
||||
base: _,
|
||||
elem: ProjectionElem::Subslice { .. },
|
||||
})
|
||||
| Place::Projection(box Projection {
|
||||
base: _,
|
||||
elem: ProjectionElem::Downcast(..),
|
||||
}) => bug!("Unexpected immutable place."),
|
||||
}
|
||||
|
||||
// `act` and `acted_on` are strings that let us abstract over
|
||||
// the verbs used in some diagnostic messages.
|
||||
let act;
|
||||
let acted_on;
|
||||
|
||||
match error_access {
|
||||
|
||||
let span = match error_access {
|
||||
AccessKind::Mutate => {
|
||||
let item_msg = match the_place_err {
|
||||
Place::Projection(box Projection {
|
||||
base: _,
|
||||
elem: ProjectionElem::Deref,
|
||||
}) => match self.describe_place(place) {
|
||||
Some(description) => {
|
||||
format!("`{}` which is behind a `&` reference", description)
|
||||
}
|
||||
None => format!("data in a `&` reference"),
|
||||
},
|
||||
_ => item_msg,
|
||||
};
|
||||
err = self.tcx.cannot_assign(span, &item_msg, Origin::Mir);
|
||||
err = self.tcx
|
||||
.cannot_assign(span, &(item_msg + &reason), Origin::Mir);
|
||||
act = "assign";
|
||||
acted_on = "written";
|
||||
span
|
||||
}
|
||||
AccessKind::MutableBorrow => {
|
||||
err = self
|
||||
.tcx
|
||||
.cannot_borrow_path_as_mutable(span, &item_msg, Origin::Mir);
|
||||
act = "borrow as mutable";
|
||||
acted_on = "borrowed as mutable";
|
||||
|
||||
let closure_span = self.find_closure_span(span, location);
|
||||
if let Some((args, var)) = closure_span {
|
||||
err = self.tcx.cannot_borrow_path_as_mutable_because(
|
||||
args,
|
||||
&item_msg,
|
||||
&reason,
|
||||
Origin::Mir,
|
||||
);
|
||||
err.span_label(
|
||||
var,
|
||||
format!(
|
||||
"mutable borrow occurs due to use of `{}` in closure",
|
||||
self.describe_place(access_place).unwrap(),
|
||||
),
|
||||
);
|
||||
args
|
||||
} else {
|
||||
err = self.tcx.cannot_borrow_path_as_mutable_because(
|
||||
span,
|
||||
&item_msg,
|
||||
&reason,
|
||||
Origin::Mir,
|
||||
);
|
||||
span
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
match the_place_err {
|
||||
// We want to suggest users use `let mut` for local (user
|
||||
|
@ -80,7 +204,7 @@ impl<'a, 'gcx, 'tcx> MirBorrowckCtxt<'a, 'gcx, 'tcx> {
|
|||
// ... but it doesn't make sense to suggest it on
|
||||
// variables that are `ref x`, `ref mut x`, `&self`,
|
||||
// or `&mut self` (such variables are simply not
|
||||
// mutable)..
|
||||
// mutable).
|
||||
let local_decl = &self.mir.local_decls[*local];
|
||||
assert_eq!(local_decl.mutability, Mutability::Not);
|
||||
|
||||
|
@ -92,6 +216,38 @@ impl<'a, 'gcx, 'tcx> MirBorrowckCtxt<'a, 'gcx, 'tcx> {
|
|||
);
|
||||
}
|
||||
|
||||
// Also suggest adding mut for upvars
|
||||
Place::Projection(box Projection {
|
||||
base,
|
||||
elem: ProjectionElem::Field(upvar_index, _),
|
||||
}) => {
|
||||
debug_assert!(is_closure_or_generator(
|
||||
base.ty(self.mir, self.tcx).to_ty(self.tcx)
|
||||
));
|
||||
|
||||
err.span_label(span, format!("cannot {ACT}", ACT = act));
|
||||
|
||||
let upvar_hir_id = self.mir.upvar_decls[upvar_index.index()]
|
||||
.var_hir_id
|
||||
.assert_crate_local();
|
||||
let upvar_node_id = self.tcx.hir.hir_to_node_id(upvar_hir_id);
|
||||
if let Some(hir::map::NodeBinding(pat)) = self.tcx.hir.find(upvar_node_id) {
|
||||
if let hir::PatKind::Binding(
|
||||
hir::BindingAnnotation::Unannotated,
|
||||
_,
|
||||
upvar_ident,
|
||||
_,
|
||||
) = pat.node
|
||||
{
|
||||
err.span_suggestion(
|
||||
upvar_ident.span,
|
||||
"consider changing this to be mutable",
|
||||
format!("mut {}", upvar_ident.name),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// complete hack to approximate old AST-borrowck
|
||||
// diagnostic: if the span starts with a mutable borrow of
|
||||
// a local variable, then just suggest the user remove it.
|
||||
|
@ -108,6 +264,25 @@ impl<'a, 'gcx, 'tcx> MirBorrowckCtxt<'a, 'gcx, 'tcx> {
|
|||
err.span_label(span, "try removing `&mut` here");
|
||||
}
|
||||
|
||||
Place::Projection(box Projection {
|
||||
base: Place::Local(local),
|
||||
elem: ProjectionElem::Deref,
|
||||
}) if {
|
||||
if let Some(ClearCrossCrate::Set(BindingForm::RefForGuard)) =
|
||||
self.mir.local_decls[*local].is_user_variable
|
||||
{
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} =>
|
||||
{
|
||||
err.span_label(span, format!("cannot {ACT}", ACT = act));
|
||||
err.note(
|
||||
"variables bound in patterns are immutable until the end of the pattern guard",
|
||||
);
|
||||
}
|
||||
|
||||
// We want to point out when a `&` can be readily replaced
|
||||
// with an `&mut`.
|
||||
//
|
||||
|
@ -116,12 +291,13 @@ impl<'a, 'gcx, 'tcx> MirBorrowckCtxt<'a, 'gcx, 'tcx> {
|
|||
Place::Projection(box Projection {
|
||||
base: Place::Local(local),
|
||||
elem: ProjectionElem::Deref,
|
||||
}) if self.mir.local_decls[*local].is_user_variable.is_some() => {
|
||||
}) if self.mir.local_decls[*local].is_user_variable.is_some() =>
|
||||
{
|
||||
let local_decl = &self.mir.local_decls[*local];
|
||||
let suggestion = match local_decl.is_user_variable.as_ref().unwrap() {
|
||||
ClearCrossCrate::Set(mir::BindingForm::ImplicitSelf) => {
|
||||
Some(suggest_ampmut_self(local_decl))
|
||||
},
|
||||
}
|
||||
|
||||
ClearCrossCrate::Set(mir::BindingForm::Var(mir::VarBindingForm {
|
||||
binding_mode: ty::BindingMode::BindByValue(_),
|
||||
|
@ -140,13 +316,22 @@ impl<'a, 'gcx, 'tcx> MirBorrowckCtxt<'a, 'gcx, 'tcx> {
|
|||
..
|
||||
})) => suggest_ref_mut(self.tcx, local_decl.source_info.span),
|
||||
|
||||
//
|
||||
ClearCrossCrate::Set(mir::BindingForm::RefForGuard) => unreachable!(),
|
||||
|
||||
ClearCrossCrate::Clear => bug!("saw cleared local state"),
|
||||
};
|
||||
|
||||
let (pointer_sigil, pointer_desc) = if local_decl.ty.is_region_ptr() {
|
||||
("&", "reference")
|
||||
} else {
|
||||
("*const", "pointer")
|
||||
};
|
||||
|
||||
if let Some((err_help_span, suggested_code)) = suggestion {
|
||||
err.span_suggestion(
|
||||
err_help_span,
|
||||
"consider changing this to be a mutable reference",
|
||||
&format!("consider changing this to be a mutable {}", pointer_desc),
|
||||
suggested_code,
|
||||
);
|
||||
}
|
||||
|
@ -155,20 +340,39 @@ impl<'a, 'gcx, 'tcx> MirBorrowckCtxt<'a, 'gcx, 'tcx> {
|
|||
err.span_label(
|
||||
span,
|
||||
format!(
|
||||
"`{NAME}` is a `&` reference, \
|
||||
"`{NAME}` is a `{SIGIL}` {DESC}, \
|
||||
so the data it refers to cannot be {ACTED_ON}",
|
||||
NAME = name,
|
||||
SIGIL = pointer_sigil,
|
||||
DESC = pointer_desc,
|
||||
ACTED_ON = acted_on
|
||||
),
|
||||
);
|
||||
} else {
|
||||
err.span_label(
|
||||
span,
|
||||
format!("cannot {ACT} through `&`-reference", ACT = act),
|
||||
format!(
|
||||
"cannot {ACT} through `{SIGIL}` {DESC}",
|
||||
ACT = act,
|
||||
SIGIL = pointer_sigil,
|
||||
DESC = pointer_desc
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Place::Projection(box Projection {
|
||||
base,
|
||||
elem: ProjectionElem::Deref,
|
||||
}) if *base == Place::Local(Local::new(1)) && !self.mir.upvar_decls.is_empty() =>
|
||||
{
|
||||
err.span_label(span, format!("cannot {ACT}", ACT = act));
|
||||
err.span_help(
|
||||
self.mir.span,
|
||||
"consider changing this to accept closures that implement `FnMut`"
|
||||
);
|
||||
}
|
||||
|
||||
_ => {
|
||||
err.span_label(span, format!("cannot {ACT}", ACT = act));
|
||||
}
|
||||
|
@ -236,10 +440,7 @@ fn suggest_ampmut<'cx, 'gcx, 'tcx>(
|
|||
if let Ok(src) = snippet {
|
||||
if src.starts_with('&') {
|
||||
let borrowed_expr = src[1..].to_string();
|
||||
return (
|
||||
assignment_rhs_span,
|
||||
format!("&mut {}", borrowed_expr),
|
||||
);
|
||||
return (assignment_rhs_span, format!("&mut {}", borrowed_expr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -256,5 +457,13 @@ fn suggest_ampmut<'cx, 'gcx, 'tcx>(
|
|||
|
||||
let ty_mut = local_decl.ty.builtin_deref(true).unwrap();
|
||||
assert_eq!(ty_mut.mutbl, hir::MutImmutable);
|
||||
if local_decl.ty.is_region_ptr() {
|
||||
(highlight_span, format!("&mut {}", ty_mut.ty))
|
||||
} else {
|
||||
(highlight_span, format!("*mut {}", ty_mut.ty))
|
||||
}
|
||||
}
|
||||
|
||||
fn is_closure_or_generator(ty: ty::Ty) -> bool {
|
||||
ty.is_closure() || ty.is_generator()
|
||||
}
|
||||
|
|
|
@ -519,24 +519,35 @@ pub trait BorrowckErrors<'cx>: Sized + Copy {
|
|||
self.cancel_if_wrong_origin(err, o)
|
||||
}
|
||||
|
||||
fn cannot_borrow_path_as_mutable(
|
||||
fn cannot_borrow_path_as_mutable_because(
|
||||
self,
|
||||
span: Span,
|
||||
path: &str,
|
||||
reason: &str,
|
||||
o: Origin,
|
||||
) -> DiagnosticBuilder<'cx> {
|
||||
let err = struct_span_err!(
|
||||
self,
|
||||
span,
|
||||
E0596,
|
||||
"cannot borrow {} as mutable{OGN}",
|
||||
"cannot borrow {} as mutable{}{OGN}",
|
||||
path,
|
||||
OGN = o
|
||||
reason,
|
||||
OGN = o,
|
||||
);
|
||||
|
||||
self.cancel_if_wrong_origin(err, o)
|
||||
}
|
||||
|
||||
fn cannot_borrow_path_as_mutable(
|
||||
self,
|
||||
span: Span,
|
||||
path: &str,
|
||||
o: Origin,
|
||||
) -> DiagnosticBuilder<'cx> {
|
||||
self.cannot_borrow_path_as_mutable_because(span, path, "", o)
|
||||
}
|
||||
|
||||
fn cannot_borrow_across_generator_yield(
|
||||
self,
|
||||
span: Span,
|
||||
|
|
Loading…
Reference in New Issue