1 use core::unicode::property::Pattern_White_Space;
4 use rustc::mir::{self, BindingForm, ClearCrossCrate, Local, Location, Body};
6 Mutability, Place, PlaceRef, PlaceBase, Projection, ProjectionElem, Static, StaticKind
8 use rustc::ty::{self, Ty, TyCtxt};
9 use rustc_data_structures::indexed_vec::Idx;
11 use syntax_pos::symbol::kw;
13 use crate::borrow_check::MirBorrowckCtxt;
14 use crate::borrow_check::error_reporting::BorrowedContentSource;
15 use crate::util::collect_writes::FindAssignments;
16 use rustc_errors::Applicability;
18 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
19 pub(super) enum AccessKind {
25 impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
26 pub(super) fn report_mutability_error(
28 access_place: &Place<'tcx>,
30 the_place_err: PlaceRef<'cx, 'tcx>,
31 error_access: AccessKind,
35 "report_mutability_error(\
36 access_place={:?}, span={:?}, the_place_err={:?}, error_access={:?}, location={:?},\
38 access_place, span, the_place_err, error_access, location,
44 let mut opt_source = None;
45 let access_place_desc = self.describe_place(access_place.as_ref());
46 debug!("report_mutability_error: access_place_desc={:?}", access_place_desc);
50 base: PlaceBase::Local(local),
53 item_msg = format!("`{}`", access_place_desc.unwrap());
55 base: PlaceBase::Local(_),
58 reason = ", as it is not declared as mutable".to_string();
60 let name = self.body.local_decls[*local]
62 .expect("immutable unnamed local");
63 reason = format!(", as `{}` is not declared as mutable", name);
72 elem: ProjectionElem::Field(upvar_index, _),
75 debug_assert!(is_closure_or_generator(
76 Place::ty_from(&the_place_err.base, &base, self.body, self.infcx.tcx).ty
79 item_msg = format!("`{}`", access_place_desc.unwrap());
80 if self.is_upvar_field_projection(access_place.as_ref()).is_some() {
81 reason = ", as it is not declared as mutable".to_string();
83 let name = self.upvars[upvar_index.index()].name;
84 reason = format!(", as `{}` is not declared as mutable", name);
93 elem: ProjectionElem::Deref,
96 if the_place_err.base == &PlaceBase::Local(Local::new(1)) &&
98 !self.upvars.is_empty() {
99 item_msg = format!("`{}`", access_place_desc.unwrap());
100 debug_assert!(self.body.local_decls[Local::new(1)].ty.is_region_ptr());
101 debug_assert!(is_closure_or_generator(
104 the_place_err.projection,
112 if self.is_upvar_field_projection(access_place.as_ref()).is_some() {
113 ", as it is a captured variable in a `Fn` closure".to_string()
115 ", as `Fn` closures cannot mutate their captured variables".to_string()
118 if let (PlaceBase::Local(local), None) = (&the_place_err.base, base) {
119 self.body.local_decls[*local].is_ref_for_guard()
124 item_msg = format!("`{}`", access_place_desc.unwrap());
125 reason = ", as it is immutable for the pattern guard".to_string();
127 let source = self.borrowed_content_source(PlaceRef {
128 base: the_place_err.base,
131 let pointer_type = source.describe_for_immutable_place();
132 opt_source = Some(source);
133 if let Some(desc) = access_place_desc {
134 item_msg = format!("`{}`", desc);
135 reason = match error_access {
137 AccessKind::Mutate => format!(" which is behind {}", pointer_type),
138 AccessKind::MutableBorrow => {
139 format!(", as it is behind {}", pointer_type)
143 item_msg = format!("data in {}", pointer_type);
144 reason = String::new();
151 PlaceBase::Static(box Static {
152 kind: StaticKind::Promoted(_),
160 PlaceBase::Static(box Static {
161 kind: StaticKind::Static(def_id),
167 base: PlaceBase::Static(_),
170 item_msg = format!("immutable static item `{}`", access_place_desc.unwrap());
171 reason = String::new();
173 item_msg = format!("`{}`", access_place_desc.unwrap());
174 let static_name = &self.infcx.tcx.item_name(*def_id);
175 reason = format!(", as `{}` is an immutable static item", static_name);
182 Some(box Projection {
184 elem: ProjectionElem::Index(_),
190 Some(box Projection {
192 elem: ProjectionElem::ConstantIndex { .. },
197 projection: Some(box Projection {
199 elem: ProjectionElem::Subslice { .. },
204 projection: Some(box Projection {
206 elem: ProjectionElem::Downcast(..),
208 } => bug!("Unexpected immutable place."),
211 debug!("report_mutability_error: item_msg={:?}, reason={:?}", item_msg, reason);
213 // `act` and `acted_on` are strings that let us abstract over
214 // the verbs used in some diagnostic messages.
218 let span = match error_access {
219 AccessKind::Move => {
220 err = self.cannot_move_out_of(span, &(item_msg + &reason));
221 err.span_label(span, "cannot move");
222 err.buffer(&mut self.errors_buffer);
225 AccessKind::Mutate => {
226 err = self.cannot_assign(span, &(item_msg + &reason));
228 acted_on = "written";
231 AccessKind::MutableBorrow => {
232 act = "borrow as mutable";
233 acted_on = "borrowed as mutable";
235 let borrow_spans = self.borrow_spans(span, location);
236 let borrow_span = borrow_spans.args_or_use();
237 err = self.cannot_borrow_path_as_mutable_because(
242 borrow_spans.var_span_label(
245 "mutable borrow occurs due to use of `{}` in closure",
246 // always Some() if the message is printed.
247 self.describe_place(access_place.as_ref()).unwrap_or_default(),
254 debug!("report_mutability_error: act={:?}, acted_on={:?}", act, acted_on);
256 match the_place_err {
257 // Suggest making an existing shared borrow in a struct definition a mutable borrow.
259 // This is applicable when we have a deref of a field access to a deref of a local -
260 // something like `*((*_1).0`. The local that we get will be a reference to the
261 // struct we've got a field access of (it must be a reference since there's a deref
262 // after the field access).
265 projection: Some(box Projection {
266 base: Some(box Projection {
267 base: Some(box Projection {
269 elem: ProjectionElem::Deref,
271 elem: ProjectionElem::Field(field, _),
273 elem: ProjectionElem::Deref,
276 err.span_label(span, format!("cannot {ACT}", ACT = act));
278 if let Some((span, message)) = annotate_struct_field(
280 Place::ty_from(&base, &base_proj, self.body, self.infcx.tcx).ty,
285 "consider changing this to be mutable",
287 Applicability::MaybeIncorrect,
292 // Suggest removing a `&mut` from the use of a mutable reference.
294 base: PlaceBase::Local(local),
297 self.body.local_decls.get(*local).map(|local_decl| {
298 if let ClearCrossCrate::Set(
299 mir::BindingForm::ImplicitSelf(kind)
300 ) = local_decl.is_user_variable.as_ref().unwrap() {
301 // Check if the user variable is a `&mut self` and we can therefore
302 // suggest removing the `&mut`.
304 // Deliberately fall into this case for all implicit self types,
305 // so that we don't fall in to the next case with them.
306 *kind == mir::ImplicitSelfKind::MutRef
307 } else if Some(kw::SelfLower) == local_decl.name {
308 // Otherwise, check if the name is the self kewyord - in which case
309 // we have an explicit self. Do the same thing in this case and check
310 // for a `self: &mut Self` to suggest removing the `&mut`.
312 _, _, hir::Mutability::MutMutable
313 ) = local_decl.ty.sty {
323 err.span_label(span, format!("cannot {ACT}", ACT = act));
324 err.span_label(span, "try removing `&mut` here");
327 // We want to suggest users use `let mut` for local (user
328 // variable) mutations...
330 base: PlaceBase::Local(local),
332 } if self.body.local_decls[*local].can_be_made_mutable() => {
333 // ... but it doesn't make sense to suggest it on
334 // variables that are `ref x`, `ref mut x`, `&self`,
335 // or `&mut self` (such variables are simply not
337 let local_decl = &self.body.local_decls[*local];
338 assert_eq!(local_decl.mutability, Mutability::Not);
340 err.span_label(span, format!("cannot {ACT}", ACT = act));
342 local_decl.source_info.span,
343 "consider changing this to be mutable",
344 format!("mut {}", local_decl.name.unwrap()),
345 Applicability::MachineApplicable,
349 // Also suggest adding mut for upvars
352 projection: Some(box Projection {
354 elem: ProjectionElem::Field(upvar_index, _),
357 debug_assert!(is_closure_or_generator(
358 Place::ty_from(&base, &proj_base, self.body, self.infcx.tcx).ty
361 err.span_label(span, format!("cannot {ACT}", ACT = act));
363 let upvar_hir_id = self.upvars[upvar_index.index()].var_hir_id;
364 if let Some(Node::Binding(pat)) = self.infcx.tcx.hir().find(upvar_hir_id)
366 if let hir::PatKind::Binding(
367 hir::BindingAnnotation::Unannotated,
375 "consider changing this to be mutable",
376 format!("mut {}", upvar_ident.name),
377 Applicability::MachineApplicable,
383 // complete hack to approximate old AST-borrowck
384 // diagnostic: if the span starts with a mutable borrow of
385 // a local variable, then just suggest the user remove it.
387 base: PlaceBase::Local(_),
390 if let Ok(snippet) = self.infcx.tcx.sess.source_map().span_to_snippet(span) {
391 snippet.starts_with("&mut ")
397 err.span_label(span, format!("cannot {ACT}", ACT = act));
398 err.span_label(span, "try removing `&mut` here");
402 base: PlaceBase::Local(local),
403 projection: Some(box Projection {
405 elem: ProjectionElem::Deref,
408 if let Some(ClearCrossCrate::Set(BindingForm::RefForGuard)) =
409 self.body.local_decls[*local].is_user_variable
417 err.span_label(span, format!("cannot {ACT}", ACT = act));
419 "variables bound in patterns are immutable until the end of the pattern guard",
423 // We want to point out when a `&` can be readily replaced
426 // FIXME: can this case be generalized to work for an
427 // arbitrary base for the projection?
429 base: PlaceBase::Local(local),
430 projection: Some(box Projection {
432 elem: ProjectionElem::Deref,
434 } if self.body.local_decls[*local].is_user_variable.is_some() =>
436 let local_decl = &self.body.local_decls[*local];
437 let suggestion = match local_decl.is_user_variable.as_ref().unwrap() {
438 ClearCrossCrate::Set(mir::BindingForm::ImplicitSelf(_)) => {
439 Some(suggest_ampmut_self(self.infcx.tcx, local_decl))
442 ClearCrossCrate::Set(mir::BindingForm::Var(mir::VarBindingForm {
443 binding_mode: ty::BindingMode::BindByValue(_),
446 })) => Some(suggest_ampmut(
454 ClearCrossCrate::Set(mir::BindingForm::Var(mir::VarBindingForm {
455 binding_mode: ty::BindingMode::BindByReference(_),
458 let pattern_span = local_decl.source_info.span;
459 suggest_ref_mut(self.infcx.tcx, pattern_span)
460 .map(|replacement| (pattern_span, replacement))
463 ClearCrossCrate::Set(mir::BindingForm::RefForGuard) => unreachable!(),
465 ClearCrossCrate::Clear => bug!("saw cleared local state"),
468 let (pointer_sigil, pointer_desc) = if local_decl.ty.is_region_ptr() {
471 ("*const", "pointer")
474 if let Some((err_help_span, suggested_code)) = suggestion {
477 &format!("consider changing this to be a mutable {}", pointer_desc),
479 Applicability::MachineApplicable,
483 match local_decl.name {
484 Some(name) if !local_decl.from_compiler_desugaring() => {
488 "`{NAME}` is a `{SIGIL}` {DESC}, \
489 so the data it refers to cannot be {ACTED_ON}",
491 SIGIL = pointer_sigil,
501 "cannot {ACT} through `{SIGIL}` {DESC}",
503 SIGIL = pointer_sigil,
513 projection: Some(box Projection {
515 elem: ProjectionElem::Deref,
517 // FIXME document what is this 1 magic number about
518 } if *base == PlaceBase::Local(Local::new(1)) &&
519 !self.upvars.is_empty() =>
521 err.span_label(span, format!("cannot {ACT}", ACT = act));
524 "consider changing this to accept closures that implement `FnMut`"
530 projection: Some(box Projection {
532 elem: ProjectionElem::Deref,
535 err.span_label(span, format!("cannot {ACT}", ACT = act));
538 Some(BorrowedContentSource::OverloadedDeref(ty)) => {
541 "trait `DerefMut` is required to modify through a dereference, \
542 but it is not implemented for `{}`",
547 Some(BorrowedContentSource::OverloadedIndex(ty)) => {
550 "trait `IndexMut` is required to modify indexed content, \
551 but it is not implemented for `{}`",
561 err.span_label(span, format!("cannot {ACT}", ACT = act));
565 err.buffer(&mut self.errors_buffer);
569 fn suggest_ampmut_self<'tcx>(
571 local_decl: &mir::LocalDecl<'tcx>,
572 ) -> (Span, String) {
573 let sp = local_decl.source_info.span;
574 (sp, match tcx.sess.source_map().span_to_snippet(sp) {
576 let lt_pos = snippet.find('\'');
577 if let Some(lt_pos) = lt_pos {
578 format!("&{}mut self", &snippet[lt_pos..snippet.len() - 4])
580 "&mut self".to_string()
583 _ => "&mut self".to_string()
587 // When we want to suggest a user change a local variable to be a `&mut`, there
588 // are three potential "obvious" things to highlight:
590 // let ident [: Type] [= RightHandSideExpression];
591 // ^^^^^ ^^^^ ^^^^^^^^^^^^^^^^^^^^^^^
594 // We can always fallback on highlighting the first. But chances are good that
595 // the user experience will be better if we highlight one of the others if possible;
596 // for example, if the RHS is present and the Type is not, then the type is going to
597 // be inferred *from* the RHS, which means we should highlight that (and suggest
598 // that they borrow the RHS mutably).
600 // This implementation attempts to emulate AST-borrowck prioritization
601 // by trying (3.), then (2.) and finally falling back on (1.).
602 fn suggest_ampmut<'tcx>(
606 local_decl: &mir::LocalDecl<'tcx>,
607 opt_ty_info: Option<Span>,
608 ) -> (Span, String) {
609 let locations = body.find_assignments(local);
610 if !locations.is_empty() {
611 let assignment_rhs_span = body.source_info(locations[0]).span;
612 if let Ok(src) = tcx.sess.source_map().span_to_snippet(assignment_rhs_span) {
613 if let (true, Some(ws_pos)) = (
614 src.starts_with("&'"),
615 src.find(|c: char| -> bool { c.is_whitespace() }),
617 let lt_name = &src[1..ws_pos];
618 let ty = &src[ws_pos..];
619 return (assignment_rhs_span, format!("&{} mut {}", lt_name, ty));
620 } else if src.starts_with('&') {
621 let borrowed_expr = &src[1..];
622 return (assignment_rhs_span, format!("&mut {}", borrowed_expr));
627 let highlight_span = match opt_ty_info {
628 // if this is a variable binding with an explicit type,
629 // try to highlight that for the suggestion.
630 Some(ty_span) => ty_span,
632 // otherwise, just highlight the span associated with
633 // the (MIR) LocalDecl.
634 None => local_decl.source_info.span,
637 if let Ok(src) = tcx.sess.source_map().span_to_snippet(highlight_span) {
638 if let (true, Some(ws_pos)) = (
639 src.starts_with("&'"),
640 src.find(|c: char| -> bool { c.is_whitespace() }),
642 let lt_name = &src[1..ws_pos];
643 let ty = &src[ws_pos..];
644 return (highlight_span, format!("&{} mut{}", lt_name, ty));
648 let ty_mut = local_decl.ty.builtin_deref(true).unwrap();
649 assert_eq!(ty_mut.mutbl, hir::MutImmutable);
651 if local_decl.ty.is_region_ptr() {
652 format!("&mut {}", ty_mut.ty)
654 format!("*mut {}", ty_mut.ty)
658 fn is_closure_or_generator(ty: Ty<'_>) -> bool {
659 ty.is_closure() || ty.is_generator()
662 /// Adds a suggestion to a struct definition given a field access to a local.
663 /// This function expects the local to be a reference to a struct in order to produce a suggestion.
666 /// LL | s: &'a String
667 /// | ---------- use `&'a mut String` here to make mutable
669 fn annotate_struct_field(
673 ) -> Option<(Span, String)> {
674 // Expect our local to be a reference to a struct of some kind.
675 if let ty::Ref(_, ty, _) = ty.sty {
676 if let ty::Adt(def, _) = ty.sty {
677 let field = def.all_fields().nth(field.index())?;
678 // Use the HIR types to construct the diagnostic message.
679 let hir_id = tcx.hir().as_local_hir_id(field.did)?;
680 let node = tcx.hir().find(hir_id)?;
681 // Now we're dealing with the actual struct that we're going to suggest a change to,
682 // we can expect a field that is an immutable reference to a type.
683 if let hir::Node::Field(field) = node {
684 if let hir::TyKind::Rptr(lifetime, hir::MutTy {
685 mutbl: hir::Mutability::MutImmutable,
688 // Get the snippets in two parts - the named lifetime (if there is one) and
689 // type being referenced, that way we can reconstruct the snippet without loss
691 let type_snippet = tcx.sess.source_map().span_to_snippet(ty.span).ok()?;
692 let lifetime_snippet = if !lifetime.is_elided() {
693 format!("{} ", tcx.sess.source_map().span_to_snippet(lifetime.span).ok()?)
702 lifetime_snippet, &*type_snippet,
713 /// If possible, suggest replacing `ref` with `ref mut`.
714 fn suggest_ref_mut(tcx: TyCtxt<'_>, binding_span: Span) -> Option<(String)> {
715 let hi_src = tcx.sess.source_map().span_to_snippet(binding_span).unwrap();
716 if hi_src.starts_with("ref")
717 && hi_src["ref".len()..].starts_with(Pattern_White_Space)
719 let replacement = format!("ref mut{}", &hi_src["ref".len()..]);