Skip to content

Commit 025fb7d

Browse files
committed
Auto merge of #39139 - estebank:issue-38147, r=nikomatsakis
Point to immutable arg/fields when trying to use as &mut 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 ``` Fixes #38147.
2 parents 23a9469 + e1280d8 commit 025fb7d

21 files changed

+310
-67
lines changed

src/librustc/hir/map/blocks.rs

+1-1
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

+57
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,63 @@ pub struct cmt_<'tcx> {
194194

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

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

src/librustc/ty/mod.rs

+2
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

+1-1
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

+4-2
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

+92-48
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) => {
@@ -1114,41 +1186,13 @@ before rustc 1.16, this temporary lived longer - see issue #39283 \
11141186
}
11151187
}
11161188
_ => {
1117-
if let Categorization::Deref(ref inner_cmt, ..) = err.cmt.cat {
1118-
if let Categorization::Local(local_id) = inner_cmt.cat {
1119-
let parent = self.tcx.hir.get_parent_node(local_id);
1120-
1121-
if let Some(fn_like) = FnLikeNode::from_node(self.tcx.hir.get(parent)) {
1122-
if let Some(i) = self.tcx.hir.body(fn_like.body()).arguments.iter()
1123-
.position(|arg| arg.pat.id == local_id) {
1124-
let arg_ty = &fn_like.decl().inputs[i];
1125-
if let hir::TyRptr(
1126-
opt_lifetime,
1127-
hir::MutTy{mutbl: hir::Mutability::MutImmutable, ref ty}) =
1128-
arg_ty.node {
1129-
if let Some(lifetime) = opt_lifetime {
1130-
if let Ok(snippet) = self.tcx.sess.codemap()
1131-
.span_to_snippet(ty.span) {
1132-
if let Ok(lifetime_snippet) = self.tcx.sess.codemap()
1133-
.span_to_snippet(lifetime.span) {
1134-
db.span_label(arg_ty.span,
1135-
&format!("use `&{} mut {}` \
1136-
here to make mutable",
1137-
lifetime_snippet,
1138-
snippet));
1139-
}
1140-
}
1141-
}
1142-
else if let Ok(snippet) = self.tcx.sess.codemap()
1143-
.span_to_snippet(arg_ty.span) {
1144-
if snippet.starts_with("&") {
1145-
db.span_label(arg_ty.span,
1146-
&format!("use `{}` here to make mutable",
1147-
snippet.replace("&", "&mut ")));
1148-
}
1149-
}
1150-
}
1151-
}
1189+
if let Categorization::Deref(..) = err.cmt.cat {
1190+
db.span_label(*error_span, &"cannot borrow as mutable");
1191+
if let Some(local_id) = err.cmt.get_arg_if_immutable(&self.tcx.hir) {
1192+
self.immutable_argument_should_be_mut(local_id, db);
1193+
} else if let Categorization::Deref(ref inner_cmt, ..) = err.cmt.cat {
1194+
if let Categorization::Local(local_id) = inner_cmt.cat {
1195+
self.immutable_argument_should_be_mut(local_id, db);
11521196
}
11531197
}
11541198
} else if let Categorization::Local(local_id) = err.cmt.cat {
+31
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() {}
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)