1 // Copyright 2018 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.
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.
13 use rustc::mir::{self, BindingForm, Constant, ClearCrossCrate, Local, Location, Mir};
14 use rustc::mir::{Mutability, Operand, Place, Projection, ProjectionElem, Static, Terminator};
15 use rustc::mir::TerminatorKind;
16 use rustc::ty::{self, Const, DefIdTree, TyS, TyKind, TyCtxt};
17 use rustc_data_structures::indexed_vec::Idx;
19 use syntax_pos::symbol::keywords;
21 use dataflow::move_paths::InitLocation;
22 use borrow_check::MirBorrowckCtxt;
23 use util::borrowck_errors::{BorrowckErrors, Origin};
24 use util::collect_writes::FindAssignments;
25 use util::suggest_ref_mut;
26 use rustc_errors::Applicability;
28 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
29 pub(super) enum AccessKind {
35 impl<'a, 'gcx, 'tcx> MirBorrowckCtxt<'a, 'gcx, 'tcx> {
36 pub(super) fn report_mutability_error(
38 access_place: &Place<'tcx>,
40 the_place_err: &Place<'tcx>,
41 error_access: AccessKind,
45 "report_mutability_error(\
46 access_place={:?}, span={:?}, the_place_err={:?}, error_access={:?}, location={:?},\
48 access_place, span, the_place_err, error_access, location,
54 let access_place_desc = self.describe_place(access_place);
55 debug!("report_mutability_error: access_place_desc={:?}", access_place_desc);
58 Place::Local(local) => {
59 item_msg = format!("`{}`", access_place_desc.unwrap());
60 if let Place::Local(_) = access_place {
61 reason = ", as it is not declared as mutable".to_string();
63 let name = self.mir.local_decls[*local]
65 .expect("immutable unnamed local");
66 reason = format!(", as `{}` is not declared as mutable", name);
70 Place::Projection(box Projection {
72 elem: ProjectionElem::Field(upvar_index, _),
74 debug_assert!(is_closure_or_generator(
75 base.ty(self.mir, self.infcx.tcx).to_ty(self.infcx.tcx)
78 item_msg = format!("`{}`", access_place_desc.unwrap());
79 if access_place.is_upvar_field_projection(self.mir, &self.infcx.tcx).is_some() {
80 reason = ", as it is not declared as mutable".to_string();
82 let name = self.mir.upvar_decls[upvar_index.index()].debug_name;
83 reason = format!(", as `{}` is not declared as mutable", name);
87 Place::Projection(box Projection {
89 elem: ProjectionElem::Deref,
91 if *base == Place::Local(Local::new(1)) && !self.mir.upvar_decls.is_empty() {
92 item_msg = format!("`{}`", access_place_desc.unwrap());
93 debug_assert!(self.mir.local_decls[Local::new(1)].ty.is_region_ptr());
94 debug_assert!(is_closure_or_generator(
95 the_place_err.ty(self.mir, self.infcx.tcx).to_ty(self.infcx.tcx)
98 reason = if access_place.is_upvar_field_projection(self.mir,
99 &self.infcx.tcx).is_some() {
100 ", as it is a captured variable in a `Fn` closure".to_string()
102 ", as `Fn` closures cannot mutate their captured variables".to_string()
105 if let Place::Local(local) = *base {
106 if let Some(ClearCrossCrate::Set(BindingForm::RefForGuard))
107 = self.mir.local_decls[local].is_user_variable {
116 item_msg = format!("`{}`", access_place_desc.unwrap());
117 reason = ", as it is immutable for the pattern guard".to_string();
120 if base.ty(self.mir, self.infcx.tcx).to_ty(self.infcx.tcx).is_region_ptr() {
125 if let Some(desc) = access_place_desc {
126 item_msg = format!("`{}`", desc);
127 reason = match error_access {
129 AccessKind::Mutate => format!(" which is behind a {}", pointer_type),
130 AccessKind::MutableBorrow => {
131 format!(", as it is behind a {}", pointer_type)
135 item_msg = format!("data in a {}", pointer_type);
136 reason = String::new();
141 Place::Promoted(_) => unreachable!(),
143 Place::Static(box Static { def_id, ty: _ }) => {
144 if let Place::Static(_) = access_place {
145 item_msg = format!("immutable static item `{}`", access_place_desc.unwrap());
146 reason = String::new();
148 item_msg = format!("`{}`", access_place_desc.unwrap());
149 let static_name = &self.infcx.tcx.item_name(*def_id);
150 reason = format!(", as `{}` is an immutable static item", static_name);
154 Place::Projection(box Projection {
156 elem: ProjectionElem::Index(_),
158 | Place::Projection(box Projection {
160 elem: ProjectionElem::ConstantIndex { .. },
162 | Place::Projection(box Projection {
164 elem: ProjectionElem::Subslice { .. },
166 | Place::Projection(box Projection {
168 elem: ProjectionElem::Downcast(..),
169 }) => bug!("Unexpected immutable place."),
172 debug!("report_mutability_error: item_msg={:?}, reason={:?}", item_msg, reason);
174 // `act` and `acted_on` are strings that let us abstract over
175 // the verbs used in some diagnostic messages.
179 let span = match error_access {
180 AccessKind::Move => {
182 .cannot_move_out_of(span, &(item_msg + &reason), Origin::Mir);
183 err.span_label(span, "cannot move");
184 err.buffer(&mut self.errors_buffer);
187 AccessKind::Mutate => {
189 .cannot_assign(span, &(item_msg + &reason), Origin::Mir);
191 acted_on = "written";
194 AccessKind::MutableBorrow => {
195 act = "borrow as mutable";
196 acted_on = "borrowed as mutable";
198 let borrow_spans = self.borrow_spans(span, location);
199 let borrow_span = borrow_spans.args_or_use();
200 err = self.infcx.tcx.cannot_borrow_path_as_mutable_because(
206 borrow_spans.var_span_label(
209 "mutable borrow occurs due to use of `{}` in closure",
210 // always Some() if the message is printed.
211 self.describe_place(access_place).unwrap_or_default(),
218 debug!("report_mutability_error: act={:?}, acted_on={:?}", act, acted_on);
220 match the_place_err {
221 // Suggest making an existing shared borrow in a struct definition a mutable borrow.
223 // This is applicable when we have a deref of a field access to a deref of a local -
224 // something like `*((*_1).0`. The local that we get will be a reference to the
225 // struct we've got a field access of (it must be a reference since there's a deref
226 // after the field access).
227 Place::Projection(box Projection {
228 base: Place::Projection(box Projection {
229 base: Place::Projection(box Projection {
231 elem: ProjectionElem::Deref,
233 elem: ProjectionElem::Field(field, _),
235 elem: ProjectionElem::Deref,
237 err.span_label(span, format!("cannot {ACT}", ACT = act));
239 if let Some((span, message)) = annotate_struct_field(
241 base.ty(self.mir, self.infcx.tcx).to_ty(self.infcx.tcx),
244 err.span_suggestion_with_applicability(
246 "consider changing this to be mutable",
248 Applicability::MaybeIncorrect,
253 // Suggest removing a `&mut` from the use of a mutable reference.
256 self.mir.local_decls.get(*local).map(|local_decl| {
257 if let ClearCrossCrate::Set(
258 mir::BindingForm::ImplicitSelf(kind)
259 ) = local_decl.is_user_variable.as_ref().unwrap() {
260 // Check if the user variable is a `&mut self` and we can therefore
261 // suggest removing the `&mut`.
263 // Deliberately fall into this case for all implicit self types,
264 // so that we don't fall in to the next case with them.
265 *kind == mir::ImplicitSelfKind::MutRef
266 } else if Some(keywords::SelfValue.name()) == local_decl.name {
267 // Otherwise, check if the name is the self kewyord - in which case
268 // we have an explicit self. Do the same thing in this case and check
269 // for a `self: &mut Self` to suggest removing the `&mut`.
270 if let ty::TyKind::Ref(
271 _, _, hir::Mutability::MutMutable
272 ) = local_decl.ty.sty {
283 err.span_label(span, format!("cannot {ACT}", ACT = act));
284 err.span_label(span, "try removing `&mut` here");
287 // We want to suggest users use `let mut` for local (user
288 // variable) mutations...
289 Place::Local(local) if self.mir.local_decls[*local].can_be_made_mutable() => {
290 // ... but it doesn't make sense to suggest it on
291 // variables that are `ref x`, `ref mut x`, `&self`,
292 // or `&mut self` (such variables are simply not
294 let local_decl = &self.mir.local_decls[*local];
295 assert_eq!(local_decl.mutability, Mutability::Not);
297 err.span_label(span, format!("cannot {ACT}", ACT = act));
298 err.span_suggestion_with_applicability(
299 local_decl.source_info.span,
300 "consider changing this to be mutable",
301 format!("mut {}", local_decl.name.unwrap()),
302 Applicability::MachineApplicable,
306 // Also suggest adding mut for upvars
307 Place::Projection(box Projection {
309 elem: ProjectionElem::Field(upvar_index, _),
311 debug_assert!(is_closure_or_generator(
312 base.ty(self.mir, self.infcx.tcx).to_ty(self.infcx.tcx)
315 err.span_label(span, format!("cannot {ACT}", ACT = act));
317 let upvar_hir_id = self.mir.upvar_decls[upvar_index.index()]
319 .assert_crate_local();
320 let upvar_node_id = self.infcx.tcx.hir.hir_to_node_id(upvar_hir_id);
321 if let Some(Node::Binding(pat)) = self.infcx.tcx.hir.find(upvar_node_id) {
322 if let hir::PatKind::Binding(
323 hir::BindingAnnotation::Unannotated,
329 err.span_suggestion_with_applicability(
331 "consider changing this to be mutable",
332 format!("mut {}", upvar_ident.name),
333 Applicability::MachineApplicable,
339 // complete hack to approximate old AST-borrowck
340 // diagnostic: if the span starts with a mutable borrow of
341 // a local variable, then just suggest the user remove it.
344 if let Ok(snippet) = self.infcx.tcx.sess.source_map().span_to_snippet(span) {
345 snippet.starts_with("&mut ")
351 err.span_label(span, format!("cannot {ACT}", ACT = act));
352 err.span_label(span, "try removing `&mut` here");
355 Place::Projection(box Projection {
356 base: Place::Local(local),
357 elem: ProjectionElem::Deref,
359 if let Some(ClearCrossCrate::Set(BindingForm::RefForGuard)) =
360 self.mir.local_decls[*local].is_user_variable
368 err.span_label(span, format!("cannot {ACT}", ACT = act));
370 "variables bound in patterns are immutable until the end of the pattern guard",
374 // We want to point out when a `&` can be readily replaced
377 // FIXME: can this case be generalized to work for an
378 // arbitrary base for the projection?
379 Place::Projection(box Projection {
380 base: Place::Local(local),
381 elem: ProjectionElem::Deref,
382 }) if self.mir.local_decls[*local].is_user_variable.is_some() =>
384 let local_decl = &self.mir.local_decls[*local];
385 let suggestion = match local_decl.is_user_variable.as_ref().unwrap() {
386 ClearCrossCrate::Set(mir::BindingForm::ImplicitSelf(_)) => {
387 Some(suggest_ampmut_self(self.infcx.tcx, local_decl))
390 ClearCrossCrate::Set(mir::BindingForm::Var(mir::VarBindingForm {
391 binding_mode: ty::BindingMode::BindByValue(_),
394 })) => Some(suggest_ampmut(
402 ClearCrossCrate::Set(mir::BindingForm::Var(mir::VarBindingForm {
403 binding_mode: ty::BindingMode::BindByReference(_),
406 let pattern_span = local_decl.source_info.span;
407 suggest_ref_mut(self.infcx.tcx, pattern_span)
408 .map(|replacement| (pattern_span, replacement))
411 ClearCrossCrate::Set(mir::BindingForm::RefForGuard) => unreachable!(),
413 ClearCrossCrate::Clear => bug!("saw cleared local state"),
416 let (pointer_sigil, pointer_desc) = if local_decl.ty.is_region_ptr() {
419 ("*const", "pointer")
422 if let Some((err_help_span, suggested_code)) = suggestion {
423 err.span_suggestion_with_applicability(
425 &format!("consider changing this to be a mutable {}", pointer_desc),
427 Applicability::MachineApplicable,
431 if let Some(name) = local_decl.name {
435 "`{NAME}` is a `{SIGIL}` {DESC}, \
436 so the data it refers to cannot be {ACTED_ON}",
438 SIGIL = pointer_sigil,
447 "cannot {ACT} through `{SIGIL}` {DESC}",
449 SIGIL = pointer_sigil,
456 Place::Projection(box Projection {
458 elem: ProjectionElem::Deref,
459 }) if *base == Place::Local(Local::new(1)) && !self.mir.upvar_decls.is_empty() =>
461 err.span_label(span, format!("cannot {ACT}", ACT = act));
464 "consider changing this to accept closures that implement `FnMut`"
468 Place::Projection(box Projection {
469 base: Place::Local(local),
470 elem: ProjectionElem::Deref,
471 }) if error_access == AccessKind::MutableBorrow => {
472 err.span_label(span, format!("cannot {ACT}", ACT = act));
474 let mpi = self.move_data.rev_lookup.find_local(*local);
475 for i in self.move_data.init_path_map[mpi].iter() {
476 if let InitLocation::Statement(location) = self.move_data.inits[*i].location {
479 kind: TerminatorKind::Call {
480 func: Operand::Constant(box Constant {
483 sty: TyKind::FnDef(id, substs),
494 ) = &self.mir.basic_blocks()[location.block].terminator {
495 let index_trait = self.infcx.tcx.lang_items().index_trait();
496 if self.infcx.tcx.parent(id) == index_trait {
497 let mut found = false;
498 self.infcx.tcx.for_each_relevant_impl(
499 self.infcx.tcx.lang_items().index_mut_trait().unwrap(),
506 let extra = if found {
509 format!(", but it is not implemented for `{}`",
515 "trait `IndexMut` is required to modify indexed content{}",
526 err.span_label(span, format!("cannot {ACT}", ACT = act));
530 err.buffer(&mut self.errors_buffer);
534 fn suggest_ampmut_self<'cx, 'gcx, 'tcx>(
535 tcx: TyCtxt<'cx, 'gcx, 'tcx>,
536 local_decl: &mir::LocalDecl<'tcx>,
537 ) -> (Span, String) {
538 let sp = local_decl.source_info.span;
539 (sp, match tcx.sess.source_map().span_to_snippet(sp) {
541 let lt_pos = snippet.find('\'');
542 if let Some(lt_pos) = lt_pos {
543 format!("&{}mut self", &snippet[lt_pos..snippet.len() - 4])
545 "&mut self".to_string()
548 _ => "&mut self".to_string()
552 // When we want to suggest a user change a local variable to be a `&mut`, there
553 // are three potential "obvious" things to highlight:
555 // let ident [: Type] [= RightHandSideExpression];
556 // ^^^^^ ^^^^ ^^^^^^^^^^^^^^^^^^^^^^^
559 // We can always fallback on highlighting the first. But chances are good that
560 // the user experience will be better if we highlight one of the others if possible;
561 // for example, if the RHS is present and the Type is not, then the type is going to
562 // be inferred *from* the RHS, which means we should highlight that (and suggest
563 // that they borrow the RHS mutably).
565 // This implementation attempts to emulate AST-borrowck prioritization
566 // by trying (3.), then (2.) and finally falling back on (1.).
567 fn suggest_ampmut<'cx, 'gcx, 'tcx>(
568 tcx: TyCtxt<'cx, 'gcx, 'tcx>,
571 local_decl: &mir::LocalDecl<'tcx>,
572 opt_ty_info: Option<Span>,
573 ) -> (Span, String) {
574 let locations = mir.find_assignments(local);
575 if !locations.is_empty() {
576 let assignment_rhs_span = mir.source_info(locations[0]).span;
577 if let Ok(src) = tcx.sess.source_map().span_to_snippet(assignment_rhs_span) {
578 if let (true, Some(ws_pos)) = (
579 src.starts_with("&'"),
580 src.find(|c: char| -> bool { c.is_whitespace() }),
582 let lt_name = &src[1..ws_pos];
583 let ty = &src[ws_pos..];
584 return (assignment_rhs_span, format!("&{} mut {}", lt_name, ty));
585 } else if src.starts_with('&') {
586 let borrowed_expr = &src[1..];
587 return (assignment_rhs_span, format!("&mut {}", borrowed_expr));
592 let highlight_span = match opt_ty_info {
593 // if this is a variable binding with an explicit type,
594 // try to highlight that for the suggestion.
595 Some(ty_span) => ty_span,
597 // otherwise, just highlight the span associated with
598 // the (MIR) LocalDecl.
599 None => local_decl.source_info.span,
602 if let Ok(src) = tcx.sess.source_map().span_to_snippet(highlight_span) {
603 if let (true, Some(ws_pos)) = (
604 src.starts_with("&'"),
605 src.find(|c: char| -> bool { c.is_whitespace() }),
607 let lt_name = &src[1..ws_pos];
608 let ty = &src[ws_pos..];
609 return (highlight_span, format!("&{} mut{}", lt_name, ty));
613 let ty_mut = local_decl.ty.builtin_deref(true).unwrap();
614 assert_eq!(ty_mut.mutbl, hir::MutImmutable);
616 if local_decl.ty.is_region_ptr() {
617 format!("&mut {}", ty_mut.ty)
619 format!("*mut {}", ty_mut.ty)
623 fn is_closure_or_generator(ty: ty::Ty) -> bool {
624 ty.is_closure() || ty.is_generator()
627 /// Add a suggestion to a struct definition given a field access to a local.
628 /// This function expects the local to be a reference to a struct in order to produce a suggestion.
631 /// LL | s: &'a String
632 /// | ---------- use `&'a mut String` here to make mutable
634 fn annotate_struct_field(
635 tcx: TyCtxt<'cx, 'gcx, 'tcx>,
638 ) -> Option<(Span, String)> {
639 // Expect our local to be a reference to a struct of some kind.
640 if let ty::TyKind::Ref(_, ty, _) = ty.sty {
641 if let ty::TyKind::Adt(def, _) = ty.sty {
642 let field = def.all_fields().nth(field.index())?;
643 // Use the HIR types to construct the diagnostic message.
644 let node_id = tcx.hir.as_local_node_id(field.did)?;
645 let node = tcx.hir.find(node_id)?;
646 // Now we're dealing with the actual struct that we're going to suggest a change to,
647 // we can expect a field that is an immutable reference to a type.
648 if let hir::Node::Field(field) = node {
649 if let hir::TyKind::Rptr(lifetime, hir::MutTy {
650 mutbl: hir::Mutability::MutImmutable,
653 // Get the snippets in two parts - the named lifetime (if there is one) and
654 // type being referenced, that way we can reconstruct the snippet without loss
656 let type_snippet = tcx.sess.source_map().span_to_snippet(ty.span).ok()?;
657 let lifetime_snippet = if !lifetime.is_elided() {
658 format!("{} ", tcx.sess.source_map().span_to_snippet(lifetime.span).ok()?)
667 lifetime_snippet, &*type_snippet,