Skip to content

Commit 13b5f69

Browse files
committed
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
1 parent db568e4 commit 13b5f69

File tree

3 files changed

+257
-37
lines changed

3 files changed

+257
-37
lines changed

src/librustc_mir/borrow_check/error_reporting.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
202202
/// the local assigned at `location`.
203203
/// This is done by searching in statements succeeding `location`
204204
/// and originating from `maybe_closure_span`.
205-
fn find_closure_span(
205+
pub(super) fn find_closure_span(
206206
&self,
207207
maybe_closure_span: Span,
208208
location: Location,

src/librustc_mir/borrow_check/mutability_errors.rs

+242-33
Original file line numberDiff line numberDiff line change
@@ -36,42 +36,166 @@ impl<'a, 'gcx, 'tcx> MirBorrowckCtxt<'a, 'gcx, 'tcx> {
3636
location: Location,
3737
) {
3838
let mut err;
39-
let item_msg = match self.describe_place(place) {
40-
Some(name) => format!("immutable item `{}`", name),
41-
None => "immutable item".to_owned(),
42-
};
39+
let item_msg;
40+
let reason;
41+
let access_place_desc = self.describe_place(access_place);
42+
43+
match the_place_err {
44+
Place::Local(local) => {
45+
item_msg = format!("`{}`", access_place_desc.unwrap());
46+
if let Place::Local(_) = access_place {
47+
reason = ", as it is not declared as mutable".to_string();
48+
} else {
49+
let name = self.mir.local_decls[*local]
50+
.name
51+
.expect("immutable unnamed local");
52+
reason = format!(", as `{}` is not declared as mutable", name);
53+
}
54+
}
55+
56+
Place::Projection(box Projection {
57+
base,
58+
elem: ProjectionElem::Field(upvar_index, _),
59+
}) => {
60+
debug_assert!(is_closure_or_generator(
61+
base.ty(self.mir, self.tcx).to_ty(self.tcx)
62+
));
63+
64+
item_msg = format!("`{}`", access_place_desc.unwrap());
65+
if self.is_upvar(access_place) {
66+
reason = ", as it is not declared as mutable".to_string();
67+
} else {
68+
let name = self.mir.upvar_decls[upvar_index.index()].debug_name;
69+
reason = format!(", as `{}` is not declared as mutable", name);
70+
}
71+
}
72+
73+
Place::Projection(box Projection {
74+
base,
75+
elem: ProjectionElem::Deref,
76+
}) => {
77+
if *base == Place::Local(Local::new(1)) && !self.mir.upvar_decls.is_empty() {
78+
item_msg = format!("`{}`", access_place_desc.unwrap());
79+
debug_assert!(self.mir.local_decls[Local::new(1)].ty.is_region_ptr());
80+
debug_assert!(is_closure_or_generator(
81+
the_place_err.ty(self.mir, self.tcx).to_ty(self.tcx)
82+
));
83+
84+
reason = if self.is_upvar(access_place) {
85+
", as it is a captured variable in a `Fn` closure".to_string()
86+
} else {
87+
format!(", as `Fn` closures cannot mutate their captured variables")
88+
}
89+
} else if {
90+
if let Place::Local(local) = *base {
91+
if let Some(ClearCrossCrate::Set(BindingForm::RefForGuard))
92+
= self.mir.local_decls[local].is_user_variable {
93+
true
94+
} else {
95+
false
96+
}
97+
} else {
98+
false
99+
}
100+
} {
101+
item_msg = format!("`{}`", access_place_desc.unwrap());
102+
reason = format!(", as it is immutable for the pattern guard");
103+
} else {
104+
let pointer_type =
105+
if base.ty(self.mir, self.tcx).to_ty(self.tcx).is_region_ptr() {
106+
"`&` reference"
107+
} else {
108+
"`*const` pointer"
109+
};
110+
if let Some(desc) = access_place_desc {
111+
item_msg = format!("`{}`", desc);
112+
reason = match error_access {
113+
AccessKind::Mutate => format!(" which is behind a {}", pointer_type),
114+
AccessKind::MutableBorrow => {
115+
format!(", as it is behind a {}", pointer_type)
116+
}
117+
}
118+
} else {
119+
item_msg = format!("data in a {}", pointer_type);
120+
reason = "".to_string();
121+
}
122+
}
123+
}
124+
125+
Place::Static(box Static { def_id, ty: _ }) => {
126+
if let Place::Static(_) = access_place {
127+
item_msg = format!("immutable static item `{}`", access_place_desc.unwrap());
128+
reason = "".to_string();
129+
} else {
130+
item_msg = format!("`{}`", access_place_desc.unwrap());
131+
let static_name = &self.tcx.item_name(*def_id);
132+
reason = format!(", as `{}` is an immutable static item", static_name);
133+
}
134+
}
135+
136+
Place::Projection(box Projection {
137+
base: _,
138+
elem: ProjectionElem::Index(_),
139+
})
140+
| Place::Projection(box Projection {
141+
base: _,
142+
elem: ProjectionElem::ConstantIndex { .. },
143+
})
144+
| Place::Projection(box Projection {
145+
base: _,
146+
elem: ProjectionElem::Subslice { .. },
147+
})
148+
| Place::Projection(box Projection {
149+
base: _,
150+
elem: ProjectionElem::Downcast(..),
151+
}) => bug!("Unexpected immutable place."),
152+
}
43153

44154
// `act` and `acted_on` are strings that let us abstract over
45155
// the verbs used in some diagnostic messages.
46156
let act;
47157
let acted_on;
48158

49-
match error_access {
159+
160+
let span = match error_access {
50161
AccessKind::Mutate => {
51-
let item_msg = match the_place_err {
52-
Place::Projection(box Projection {
53-
base: _,
54-
elem: ProjectionElem::Deref,
55-
}) => match self.describe_place(place) {
56-
Some(description) => {
57-
format!("`{}` which is behind a `&` reference", description)
58-
}
59-
None => format!("data in a `&` reference"),
60-
},
61-
_ => item_msg,
62-
};
63-
err = self.tcx.cannot_assign(span, &item_msg, Origin::Mir);
162+
err = self.tcx
163+
.cannot_assign(span, &(item_msg + &reason), Origin::Mir);
64164
act = "assign";
65165
acted_on = "written";
166+
span
66167
}
67168
AccessKind::MutableBorrow => {
68-
err = self
69-
.tcx
70-
.cannot_borrow_path_as_mutable(span, &item_msg, Origin::Mir);
71169
act = "borrow as mutable";
72170
acted_on = "borrowed as mutable";
171+
172+
let closure_span = self.find_closure_span(span, location);
173+
if let Some((args, var)) = closure_span {
174+
err = self.tcx.cannot_borrow_path_as_mutable_because(
175+
args,
176+
&item_msg,
177+
&reason,
178+
Origin::Mir,
179+
);
180+
err.span_label(
181+
var,
182+
format!(
183+
"mutable borrow occurs due to use of `{}` in closure",
184+
self.describe_place(access_place).unwrap(),
185+
),
186+
);
187+
args
188+
} else {
189+
err = self.tcx.cannot_borrow_path_as_mutable_because(
190+
span,
191+
&item_msg,
192+
&reason,
193+
Origin::Mir,
194+
);
195+
span
196+
}
73197
}
74-
}
198+
};
75199

76200
match the_place_err {
77201
// We want to suggest users use `let mut` for local (user
@@ -80,7 +204,7 @@ impl<'a, 'gcx, 'tcx> MirBorrowckCtxt<'a, 'gcx, 'tcx> {
80204
// ... but it doesn't make sense to suggest it on
81205
// variables that are `ref x`, `ref mut x`, `&self`,
82206
// or `&mut self` (such variables are simply not
83-
// mutable)..
207+
// mutable).
84208
let local_decl = &self.mir.local_decls[*local];
85209
assert_eq!(local_decl.mutability, Mutability::Not);
86210

@@ -92,6 +216,38 @@ impl<'a, 'gcx, 'tcx> MirBorrowckCtxt<'a, 'gcx, 'tcx> {
92216
);
93217
}
94218

219+
// Also suggest adding mut for upvars
220+
Place::Projection(box Projection {
221+
base,
222+
elem: ProjectionElem::Field(upvar_index, _),
223+
}) => {
224+
debug_assert!(is_closure_or_generator(
225+
base.ty(self.mir, self.tcx).to_ty(self.tcx)
226+
));
227+
228+
err.span_label(span, format!("cannot {ACT}", ACT = act));
229+
230+
let upvar_hir_id = self.mir.upvar_decls[upvar_index.index()]
231+
.var_hir_id
232+
.assert_crate_local();
233+
let upvar_node_id = self.tcx.hir.hir_to_node_id(upvar_hir_id);
234+
if let Some(hir::map::NodeBinding(pat)) = self.tcx.hir.find(upvar_node_id) {
235+
if let hir::PatKind::Binding(
236+
hir::BindingAnnotation::Unannotated,
237+
_,
238+
upvar_ident,
239+
_,
240+
) = pat.node
241+
{
242+
err.span_suggestion(
243+
upvar_ident.span,
244+
"consider changing this to be mutable",
245+
format!("mut {}", upvar_ident.name),
246+
);
247+
}
248+
}
249+
}
250+
95251
// complete hack to approximate old AST-borrowck
96252
// diagnostic: if the span starts with a mutable borrow of
97253
// a local variable, then just suggest the user remove it.
@@ -108,6 +264,25 @@ impl<'a, 'gcx, 'tcx> MirBorrowckCtxt<'a, 'gcx, 'tcx> {
108264
err.span_label(span, "try removing `&mut` here");
109265
}
110266

267+
Place::Projection(box Projection {
268+
base: Place::Local(local),
269+
elem: ProjectionElem::Deref,
270+
}) if {
271+
if let Some(ClearCrossCrate::Set(BindingForm::RefForGuard)) =
272+
self.mir.local_decls[*local].is_user_variable
273+
{
274+
true
275+
} else {
276+
false
277+
}
278+
} =>
279+
{
280+
err.span_label(span, format!("cannot {ACT}", ACT = act));
281+
err.note(
282+
"variables bound in patterns are immutable until the end of the pattern guard",
283+
);
284+
}
285+
111286
// We want to point out when a `&` can be readily replaced
112287
// with an `&mut`.
113288
//
@@ -116,12 +291,13 @@ impl<'a, 'gcx, 'tcx> MirBorrowckCtxt<'a, 'gcx, 'tcx> {
116291
Place::Projection(box Projection {
117292
base: Place::Local(local),
118293
elem: ProjectionElem::Deref,
119-
}) if self.mir.local_decls[*local].is_user_variable.is_some() => {
294+
}) if self.mir.local_decls[*local].is_user_variable.is_some() =>
295+
{
120296
let local_decl = &self.mir.local_decls[*local];
121297
let suggestion = match local_decl.is_user_variable.as_ref().unwrap() {
122298
ClearCrossCrate::Set(mir::BindingForm::ImplicitSelf) => {
123299
Some(suggest_ampmut_self(local_decl))
124-
},
300+
}
125301

126302
ClearCrossCrate::Set(mir::BindingForm::Var(mir::VarBindingForm {
127303
binding_mode: ty::BindingMode::BindByValue(_),
@@ -140,13 +316,22 @@ impl<'a, 'gcx, 'tcx> MirBorrowckCtxt<'a, 'gcx, 'tcx> {
140316
..
141317
})) => suggest_ref_mut(self.tcx, local_decl.source_info.span),
142318

319+
//
320+
ClearCrossCrate::Set(mir::BindingForm::RefForGuard) => unreachable!(),
321+
143322
ClearCrossCrate::Clear => bug!("saw cleared local state"),
144323
};
145324

325+
let (pointer_sigil, pointer_desc) = if local_decl.ty.is_region_ptr() {
326+
("&", "reference")
327+
} else {
328+
("*const", "pointer")
329+
};
330+
146331
if let Some((err_help_span, suggested_code)) = suggestion {
147332
err.span_suggestion(
148333
err_help_span,
149-
"consider changing this to be a mutable reference",
334+
&format!("consider changing this to be a mutable {}", pointer_desc),
150335
suggested_code,
151336
);
152337
}
@@ -155,20 +340,39 @@ impl<'a, 'gcx, 'tcx> MirBorrowckCtxt<'a, 'gcx, 'tcx> {
155340
err.span_label(
156341
span,
157342
format!(
158-
"`{NAME}` is a `&` reference, \
343+
"`{NAME}` is a `{SIGIL}` {DESC}, \
159344
so the data it refers to cannot be {ACTED_ON}",
160345
NAME = name,
346+
SIGIL = pointer_sigil,
347+
DESC = pointer_desc,
161348
ACTED_ON = acted_on
162349
),
163350
);
164351
} else {
165352
err.span_label(
166353
span,
167-
format!("cannot {ACT} through `&`-reference", ACT = act),
354+
format!(
355+
"cannot {ACT} through `{SIGIL}` {DESC}",
356+
ACT = act,
357+
SIGIL = pointer_sigil,
358+
DESC = pointer_desc
359+
),
168360
);
169361
}
170362
}
171363

364+
Place::Projection(box Projection {
365+
base,
366+
elem: ProjectionElem::Deref,
367+
}) if *base == Place::Local(Local::new(1)) && !self.mir.upvar_decls.is_empty() =>
368+
{
369+
err.span_label(span, format!("cannot {ACT}", ACT = act));
370+
err.span_help(
371+
self.mir.span,
372+
"consider changing this to accept closures that implement `FnMut`"
373+
);
374+
}
375+
172376
_ => {
173377
err.span_label(span, format!("cannot {ACT}", ACT = act));
174378
}
@@ -236,10 +440,7 @@ fn suggest_ampmut<'cx, 'gcx, 'tcx>(
236440
if let Ok(src) = snippet {
237441
if src.starts_with('&') {
238442
let borrowed_expr = src[1..].to_string();
239-
return (
240-
assignment_rhs_span,
241-
format!("&mut {}", borrowed_expr),
242-
);
443+
return (assignment_rhs_span, format!("&mut {}", borrowed_expr));
243444
}
244445
}
245446
}
@@ -256,5 +457,13 @@ fn suggest_ampmut<'cx, 'gcx, 'tcx>(
256457

257458
let ty_mut = local_decl.ty.builtin_deref(true).unwrap();
258459
assert_eq!(ty_mut.mutbl, hir::MutImmutable);
259-
(highlight_span, format!("&mut {}", ty_mut.ty))
460+
if local_decl.ty.is_region_ptr() {
461+
(highlight_span, format!("&mut {}", ty_mut.ty))
462+
} else {
463+
(highlight_span, format!("*mut {}", ty_mut.ty))
464+
}
465+
}
466+
467+
fn is_closure_or_generator(ty: ty::Ty) -> bool {
468+
ty.is_closure() || ty.is_generator()
260469
}

0 commit comments

Comments
 (0)