Skip to content

Commit e1280d8

Browse files
committed
Point to immutable arg/fields when trying to use as &mut
Point to immutable borrow arguments and fields when trying to use them as mutable borrows. Add label to primary span on "cannot borrow as mutable" errors. Present the following output when trying to access an immutable borrow's field as mutable: ``` error[E0389]: cannot borrow data mutably in a `&` reference --> $DIR/issue-38147-1.rs:27:9 | 26 | fn f(&self) { | ----- use `&mut self` here to make mutable 27 | f.s.push('x'); | ^^^ assignment into an immutable reference ``` And the following when trying to access an immutable struct field as mutable: ``` error: cannot borrow immutable borrowed content `*self.s` as mutable --> $DIR/issue-38147-3.rs:17:9 | 12 | s: &'a String | ------------- use `&'a mut String` here to make mutable ...| 16 | fn f(&self) { | ----- use `&mut self` here to make mutable 17 | self.s.push('x'); | ^^^^^^ cannot borrow as mutable ```
1 parent 491b978 commit e1280d8

21 files changed

+310
-67
lines changed

src/librustc/hir/map/blocks.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ use syntax_pos::Span;
3838
/// - The default implementation for a trait method.
3939
///
4040
/// To construct one, use the `Code::from_node` function.
41-
#[derive(Copy, Clone)]
41+
#[derive(Copy, Clone, Debug)]
4242
pub struct FnLikeNode<'a> { node: map::Node<'a> }
4343

4444
/// MaybeFnLike wraps a method that indicates if an object

src/librustc/middle/mem_categorization.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,63 @@ pub struct cmt_<'tcx> {
193193

194194
pub type cmt<'tcx> = Rc<cmt_<'tcx>>;
195195

196+
impl<'tcx> cmt_<'tcx> {
197+
pub fn get_field(&self, name: ast::Name) -> Option<DefId> {
198+
match self.cat {
199+
Categorization::Deref(ref cmt, ..) |
200+
Categorization::Interior(ref cmt, _) |
201+
Categorization::Downcast(ref cmt, _) => {
202+
if let Categorization::Local(_) = cmt.cat {
203+
if let ty::TyAdt(def, _) = self.ty.sty {
204+
return def.struct_variant().find_field_named(name).map(|x| x.did);
205+
}
206+
None
207+
} else {
208+
cmt.get_field(name)
209+
}
210+
}
211+
_ => None
212+
}
213+
}
214+
215+
pub fn get_field_name(&self) -> Option<ast::Name> {
216+
match self.cat {
217+
Categorization::Interior(_, ref ik) => {
218+
if let InteriorKind::InteriorField(FieldName::NamedField(name)) = *ik {
219+
Some(name)
220+
} else {
221+
None
222+
}
223+
}
224+
Categorization::Deref(ref cmt, ..) |
225+
Categorization::Downcast(ref cmt, _) => {
226+
cmt.get_field_name()
227+
}
228+
_ => None,
229+
}
230+
}
231+
232+
pub fn get_arg_if_immutable(&self, map: &hir_map::Map) -> Option<ast::NodeId> {
233+
match self.cat {
234+
Categorization::Deref(ref cmt, ..) |
235+
Categorization::Interior(ref cmt, _) |
236+
Categorization::Downcast(ref cmt, _) => {
237+
if let Categorization::Local(nid) = cmt.cat {
238+
if let ty::TyAdt(_, _) = self.ty.sty {
239+
if let ty::TyRef(_, ty::TypeAndMut{mutbl: MutImmutable, ..}) = cmt.ty.sty {
240+
return Some(nid);
241+
}
242+
}
243+
None
244+
} else {
245+
cmt.get_arg_if_immutable(map)
246+
}
247+
}
248+
_ => None
249+
}
250+
}
251+
}
252+
196253
pub trait ast_node {
197254
fn id(&self) -> ast::NodeId;
198255
fn span(&self) -> Span;

src/librustc/ty/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1325,6 +1325,7 @@ bitflags! {
13251325
}
13261326
}
13271327

1328+
#[derive(Debug)]
13281329
pub struct VariantDef {
13291330
/// The variant's DefId. If this is a tuple-like struct,
13301331
/// this is the DefId of the struct's ctor.
@@ -1335,6 +1336,7 @@ pub struct VariantDef {
13351336
pub ctor_kind: CtorKind,
13361337
}
13371338

1339+
#[derive(Debug)]
13381340
pub struct FieldDef {
13391341
pub did: DefId,
13401342
pub name: Name,

src/librustc/ty/sty.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ pub enum TypeVariants<'tcx> {
134134
TyRawPtr(TypeAndMut<'tcx>),
135135

136136
/// A reference; a pointer with an associated lifetime. Written as
137-
/// `&a mut T` or `&'a T`.
137+
/// `&'a mut T` or `&'a T`.
138138
TyRef(&'tcx Region, TypeAndMut<'tcx>),
139139

140140
/// The anonymous type of a function declaration/definition. Each

src/librustc_borrowck/borrowck/gather_loans/mod.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,15 +195,17 @@ fn check_aliasability<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
195195
bccx.report_aliasability_violation(
196196
borrow_span,
197197
loan_cause,
198-
mc::AliasableReason::UnaliasableImmutable);
198+
mc::AliasableReason::UnaliasableImmutable,
199+
cmt);
199200
Err(())
200201
}
201202
(mc::Aliasability::FreelyAliasable(alias_cause), ty::UniqueImmBorrow) |
202203
(mc::Aliasability::FreelyAliasable(alias_cause), ty::MutBorrow) => {
203204
bccx.report_aliasability_violation(
204205
borrow_span,
205206
loan_cause,
206-
alias_cause);
207+
alias_cause,
208+
cmt);
207209
Err(())
208210
}
209211
(..) => {

src/librustc_borrowck/borrowck/mod.rs

Lines changed: 92 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -540,7 +540,7 @@ pub fn opt_loan_path<'tcx>(cmt: &mc::cmt<'tcx>) -> Option<Rc<LoanPath<'tcx>>> {
540540
// Errors
541541

542542
// Errors that can occur
543-
#[derive(PartialEq)]
543+
#[derive(Debug, PartialEq)]
544544
pub enum bckerr_code<'tcx> {
545545
err_mutbl,
546546
/// superscope, subscope, loan cause
@@ -550,7 +550,7 @@ pub enum bckerr_code<'tcx> {
550550

551551
// Combination of an error code and the categorization of the expression
552552
// that caused it
553-
#[derive(PartialEq)]
553+
#[derive(Debug, PartialEq)]
554554
pub struct BckError<'tcx> {
555555
span: Span,
556556
cause: AliasableViolationKind,
@@ -601,12 +601,8 @@ impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> {
601601
_ => { }
602602
}
603603

604-
// General fallback.
605-
let span = err.span.clone();
606-
let mut db = self.struct_span_err(
607-
err.span,
608-
&self.bckerr_to_string(&err));
609-
self.note_and_explain_bckerr(&mut db, err, span);
604+
let mut db = self.bckerr_to_diag(&err);
605+
self.note_and_explain_bckerr(&mut db, err);
610606
db.emit();
611607
}
612608

@@ -771,8 +767,11 @@ impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> {
771767
self.tcx.sess.span_err_with_code(s, msg, code);
772768
}
773769

774-
pub fn bckerr_to_string(&self, err: &BckError<'tcx>) -> String {
775-
match err.code {
770+
pub fn bckerr_to_diag(&self, err: &BckError<'tcx>) -> DiagnosticBuilder<'a> {
771+
let span = err.span.clone();
772+
let mut immutable_field = None;
773+
774+
let msg = &match err.code {
776775
err_mutbl => {
777776
let descr = match err.cmt.note {
778777
mc::NoteClosureEnv(_) | mc::NoteUpvarRef(_) => {
@@ -783,6 +782,7 @@ impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> {
783782
format!("{} {}",
784783
err.cmt.mutbl.to_user_str(),
785784
self.cmt_to_string(&err.cmt))
785+
786786
}
787787
Some(lp) => {
788788
format!("{} {} `{}`",
@@ -807,6 +807,19 @@ impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> {
807807
BorrowViolation(euv::AutoUnsafe) |
808808
BorrowViolation(euv::ForLoop) |
809809
BorrowViolation(euv::MatchDiscriminant) => {
810+
// Check for this field's definition to see if it is an immutable reference
811+
// and suggest making it mutable if that is the case.
812+
immutable_field = err.cmt.get_field_name()
813+
.and_then(|name| err.cmt.get_field(name))
814+
.and_then(|did| self.tcx.hir.as_local_node_id(did))
815+
.and_then(|nid| {
816+
if let hir_map::Node::NodeField(ref field) = self.tcx.hir.get(nid) {
817+
return self.suggest_mut_for_immutable(&field.ty)
818+
.map(|msg| (self.tcx.hir.span(nid), msg));
819+
}
820+
None
821+
});
822+
810823
format!("cannot borrow {} as mutable", descr)
811824
}
812825
BorrowViolation(euv::ClosureInvocation) => {
@@ -830,13 +843,20 @@ impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> {
830843
its contents can be safely reborrowed",
831844
descr)
832845
}
846+
};
847+
848+
let mut db = self.struct_span_err(span, msg);
849+
if let Some((span, msg)) = immutable_field {
850+
db.span_label(span, &msg);
833851
}
852+
db
834853
}
835854

836855
pub fn report_aliasability_violation(&self,
837856
span: Span,
838857
kind: AliasableViolationKind,
839-
cause: mc::AliasableReason) {
858+
cause: mc::AliasableReason,
859+
cmt: mc::cmt<'tcx>) {
840860
let mut is_closure = false;
841861
let prefix = match kind {
842862
MutabilityViolation => {
@@ -903,6 +923,9 @@ impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> {
903923
self.tcx.sess, span, E0389,
904924
"{} in a `&` reference", prefix);
905925
e.span_label(span, &"assignment into an immutable reference");
926+
if let Some(nid) = cmt.get_arg_if_immutable(&self.tcx.hir) {
927+
self.immutable_argument_should_be_mut(nid, &mut e);
928+
}
906929
e
907930
}
908931
};
@@ -913,6 +936,55 @@ impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> {
913936
err.emit();
914937
}
915938

939+
/// Given a type, if it is an immutable reference, return a suggestion to make it mutable
940+
fn suggest_mut_for_immutable(&self, pty: &hir::Ty) -> Option<String> {
941+
// Check wether the argument is an immutable reference
942+
if let hir::TyRptr(opt_lifetime, hir::MutTy {
943+
mutbl: hir::Mutability::MutImmutable,
944+
ref ty
945+
}) = pty.node {
946+
// Account for existing lifetimes when generating the message
947+
if let Some(lifetime) = opt_lifetime {
948+
if let Ok(snippet) = self.tcx.sess.codemap().span_to_snippet(ty.span) {
949+
if let Ok(lifetime_snippet) = self.tcx.sess.codemap()
950+
.span_to_snippet(lifetime.span) {
951+
return Some(format!("use `&{} mut {}` here to make mutable",
952+
lifetime_snippet,
953+
snippet));
954+
}
955+
}
956+
} else if let Ok(snippet) = self.tcx.sess.codemap().span_to_snippet(pty.span) {
957+
if snippet.starts_with("&") {
958+
return Some(format!("use `{}` here to make mutable",
959+
snippet.replace("&", "&mut ")));
960+
}
961+
} else {
962+
bug!("couldn't find a snippet for span: {:?}", pty.span);
963+
}
964+
}
965+
None
966+
}
967+
968+
fn immutable_argument_should_be_mut(&self, nid: ast::NodeId, db: &mut DiagnosticBuilder) {
969+
let parent = self.tcx.hir.get_parent_node(nid);
970+
let parent_node = self.tcx.hir.get(parent);
971+
972+
// The parent node is like a fn
973+
if let Some(fn_like) = FnLikeNode::from_node(parent_node) {
974+
// `nid`'s parent's `Body`
975+
let fn_body = self.tcx.hir.body(fn_like.body());
976+
// Get the position of `nid` in the arguments list
977+
let arg_pos = fn_body.arguments.iter().position(|arg| arg.pat.id == nid);
978+
if let Some(i) = arg_pos {
979+
// The argument's `Ty`
980+
let arg_ty = &fn_like.decl().inputs[i];
981+
if let Some(msg) = self.suggest_mut_for_immutable(&arg_ty) {
982+
db.span_label(arg_ty.span, &msg);
983+
}
984+
}
985+
}
986+
}
987+
916988
fn report_out_of_scope_escaping_closure_capture(&self,
917989
err: &BckError<'tcx>,
918990
capture_span: Span)
@@ -961,8 +1033,8 @@ impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> {
9611033
}
9621034
}
9631035

964-
pub fn note_and_explain_bckerr(&self, db: &mut DiagnosticBuilder, err: BckError<'tcx>,
965-
error_span: Span) {
1036+
pub fn note_and_explain_bckerr(&self, db: &mut DiagnosticBuilder, err: BckError<'tcx>) {
1037+
let error_span = err.span.clone();
9661038
match err.code {
9671039
err_mutbl => self.note_and_explain_mutbl_error(db, &err, &error_span),
9681040
err_out_of_scope(super_scope, sub_scope, cause) => {
@@ -1103,41 +1175,13 @@ impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> {
11031175
}
11041176
}
11051177
_ => {
1106-
if let Categorization::Deref(ref inner_cmt, ..) = err.cmt.cat {
1107-
if let Categorization::Local(local_id) = inner_cmt.cat {
1108-
let parent = self.tcx.hir.get_parent_node(local_id);
1109-
1110-
if let Some(fn_like) = FnLikeNode::from_node(self.tcx.hir.get(parent)) {
1111-
if let Some(i) = self.tcx.hir.body(fn_like.body()).arguments.iter()
1112-
.position(|arg| arg.pat.id == local_id) {
1113-
let arg_ty = &fn_like.decl().inputs[i];
1114-
if let hir::TyRptr(
1115-
opt_lifetime,
1116-
hir::MutTy{mutbl: hir::Mutability::MutImmutable, ref ty}) =
1117-
arg_ty.node {
1118-
if let Some(lifetime) = opt_lifetime {
1119-
if let Ok(snippet) = self.tcx.sess.codemap()
1120-
.span_to_snippet(ty.span) {
1121-
if let Ok(lifetime_snippet) = self.tcx.sess.codemap()
1122-
.span_to_snippet(lifetime.span) {
1123-
db.span_label(arg_ty.span,
1124-
&format!("use `&{} mut {}` \
1125-
here to make mutable",
1126-
lifetime_snippet,
1127-
snippet));
1128-
}
1129-
}
1130-
}
1131-
else if let Ok(snippet) = self.tcx.sess.codemap()
1132-
.span_to_snippet(arg_ty.span) {
1133-
if snippet.starts_with("&") {
1134-
db.span_label(arg_ty.span,
1135-
&format!("use `{}` here to make mutable",
1136-
snippet.replace("&", "&mut ")));
1137-
}
1138-
}
1139-
}
1140-
}
1178+
if let Categorization::Deref(..) = err.cmt.cat {
1179+
db.span_label(*error_span, &"cannot borrow as mutable");
1180+
if let Some(local_id) = err.cmt.get_arg_if_immutable(&self.tcx.hir) {
1181+
self.immutable_argument_should_be_mut(local_id, db);
1182+
} else if let Categorization::Deref(ref inner_cmt, ..) = err.cmt.cat {
1183+
if let Categorization::Local(local_id) = inner_cmt.cat {
1184+
self.immutable_argument_should_be_mut(local_id, db);
11411185
}
11421186
}
11431187
} else if let Categorization::Local(local_id) = err.cmt.cat {
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
struct Pass<'a> {
12+
s: &'a mut String
13+
}
14+
15+
impl<'a> Pass<'a> {
16+
fn f(&mut self) {
17+
self.s.push('x');
18+
}
19+
}
20+
21+
struct Foo<'a> {
22+
s: &'a mut String
23+
}
24+
25+
impl<'a> Foo<'a> {
26+
fn f(&self) {
27+
self.s.push('x');
28+
}
29+
}
30+
31+
fn main() {}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
error[E0389]: cannot borrow data mutably in a `&` reference
2+
--> $DIR/issue-38147-1.rs:27:9
3+
|
4+
26 | fn f(&self) {
5+
| ----- use `&mut self` here to make mutable
6+
27 | self.s.push('x');
7+
| ^^^^^^ assignment into an immutable reference
8+
9+
error: aborting due to previous error
10+

0 commit comments

Comments
 (0)