3 use rustc::mir::{self, ClearCrossCrate, Local, LocalInfo, Location, ReadOnlyBodyCache};
4 use rustc::mir::{Mutability, Place, PlaceRef, PlaceBase, ProjectionElem};
5 use rustc::ty::{self, Ty, TyCtxt};
6 use rustc_index::vec::Idx;
8 use syntax_pos::symbol::kw;
10 use crate::borrow_check::MirBorrowckCtxt;
11 use crate::borrow_check::diagnostics::BorrowedContentSource;
12 use crate::util::collect_writes::FindAssignments;
13 use rustc_errors::Applicability;
15 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
16 pub(crate) enum AccessKind {
21 impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
22 pub(crate) fn report_mutability_error(
24 access_place: &Place<'tcx>,
26 the_place_err: PlaceRef<'cx, 'tcx>,
27 error_access: AccessKind,
31 "report_mutability_error(\
32 access_place={:?}, span={:?}, the_place_err={:?}, error_access={:?}, location={:?},\
34 access_place, span, the_place_err, error_access, location,
40 let mut opt_source = None;
41 let access_place_desc = self.describe_place(access_place.as_ref());
42 debug!("report_mutability_error: access_place_desc={:?}", access_place_desc);
46 base: PlaceBase::Local(local),
49 item_msg = format!("`{}`", access_place_desc.unwrap());
50 if access_place.as_local().is_some() {
51 reason = ", as it is not declared as mutable".to_string();
53 let name = self.local_names[*local]
54 .expect("immutable unnamed local");
55 reason = format!(", as `{}` is not declared as mutable", name);
61 projection: [proj_base @ .., ProjectionElem::Field(upvar_index, _)],
63 debug_assert!(is_closure_or_generator(
71 item_msg = format!("`{}`", access_place_desc.unwrap());
72 if self.is_upvar_field_projection(access_place.as_ref()).is_some() {
73 reason = ", as it is not declared as mutable".to_string();
75 let name = self.upvars[upvar_index.index()].name;
76 reason = format!(", as `{}` is not declared as mutable", name);
81 base: &PlaceBase::Local(local),
82 projection: [ProjectionElem::Deref],
83 } if self.body.local_decls[local].is_ref_for_guard() => {
84 item_msg = format!("`{}`", access_place_desc.unwrap());
85 reason = ", as it is immutable for the pattern guard".to_string();
88 base: &PlaceBase::Local(local),
89 projection: [ProjectionElem::Deref],
90 } if self.body.local_decls[local].is_ref_to_static() => {
91 if access_place.projection.len() == 1 {
92 item_msg = format!("immutable static item `{}`", access_place_desc.unwrap());
93 reason = String::new();
95 item_msg = format!("`{}`", access_place_desc.unwrap());
96 let local_info = &self.body.local_decls[local].local_info;
97 if let LocalInfo::StaticRef { def_id, .. } = *local_info {
98 let static_name = &self.infcx.tcx.item_name(def_id);
99 reason = format!(", as `{}` is an immutable static item", static_name);
101 bug!("is_ref_to_static return true, but not ref to static?");
107 projection: [proj_base @ .., ProjectionElem::Deref],
109 if the_place_err.base == &PlaceBase::Local(Local::new(1)) &&
110 proj_base.is_empty() &&
111 !self.upvars.is_empty() {
112 item_msg = format!("`{}`", access_place_desc.unwrap());
113 debug_assert!(self.body.local_decls[Local::new(1)].ty.is_region_ptr());
114 debug_assert!(is_closure_or_generator(
117 the_place_err.projection,
125 if self.is_upvar_field_projection(access_place.as_ref()).is_some() {
126 ", as it is a captured variable in a `Fn` closure".to_string()
128 ", as `Fn` closures cannot mutate their captured variables".to_string()
131 let source = self.borrowed_content_source(PlaceRef {
132 base: the_place_err.base,
133 projection: proj_base,
135 let pointer_type = source.describe_for_immutable_place();
136 opt_source = Some(source);
137 if let Some(desc) = access_place_desc {
138 item_msg = format!("`{}`", desc);
139 reason = match error_access {
140 AccessKind::Mutate => format!(" which is behind {}", pointer_type),
141 AccessKind::MutableBorrow => {
142 format!(", as it is behind {}", pointer_type)
146 item_msg = format!("data in {}", pointer_type);
147 reason = String::new();
153 base: PlaceBase::Static(_),
158 projection: [.., ProjectionElem::Index(_)],
162 projection: [.., ProjectionElem::ConstantIndex { .. }],
166 projection: [.., ProjectionElem::Subslice { .. }],
170 projection: [.., ProjectionElem::Downcast(..)],
171 } => bug!("Unexpected immutable place."),
174 debug!("report_mutability_error: item_msg={:?}, reason={:?}", item_msg, reason);
176 // `act` and `acted_on` are strings that let us abstract over
177 // the verbs used in some diagnostic messages.
181 let span = match error_access {
182 AccessKind::Mutate => {
183 err = self.cannot_assign(span, &(item_msg + &reason));
185 acted_on = "written";
188 AccessKind::MutableBorrow => {
189 act = "borrow as mutable";
190 acted_on = "borrowed as mutable";
192 let borrow_spans = self.borrow_spans(span, location);
193 let borrow_span = borrow_spans.args_or_use();
194 err = self.cannot_borrow_path_as_mutable_because(
199 borrow_spans.var_span_label(
202 "mutable borrow occurs due to use of `{}` in closure",
203 // always Some() if the message is printed.
204 self.describe_place(access_place.as_ref()).unwrap_or_default(),
211 debug!("report_mutability_error: act={:?}, acted_on={:?}", act, acted_on);
213 match the_place_err {
214 // Suggest making an existing shared borrow in a struct definition a mutable borrow.
216 // This is applicable when we have a deref of a field access to a deref of a local -
217 // something like `*((*_1).0`. The local that we get will be a reference to the
218 // struct we've got a field access of (it must be a reference since there's a deref
219 // after the field access).
222 projection: [proj_base @ ..,
223 ProjectionElem::Deref,
224 ProjectionElem::Field(field, _),
225 ProjectionElem::Deref,
228 err.span_label(span, format!("cannot {ACT}", ACT = act));
230 if let Some((span, message)) = annotate_struct_field(
232 Place::ty_from(base, proj_base, *self.body, self.infcx.tcx).ty,
237 "consider changing this to be mutable",
239 Applicability::MaybeIncorrect,
244 // Suggest removing a `&mut` from the use of a mutable reference.
246 base: PlaceBase::Local(local),
249 self.body.local_decls.get(*local).map(|local_decl| {
250 if let LocalInfo::User(ClearCrossCrate::Set(
251 mir::BindingForm::ImplicitSelf(kind)
252 )) = local_decl.local_info {
253 // Check if the user variable is a `&mut self` and we can therefore
254 // suggest removing the `&mut`.
256 // Deliberately fall into this case for all implicit self types,
257 // so that we don't fall in to the next case with them.
258 kind == mir::ImplicitSelfKind::MutRef
259 } else if Some(kw::SelfLower) == self.local_names[*local] {
260 // Otherwise, check if the name is the self kewyord - in which case
261 // we have an explicit self. Do the same thing in this case and check
262 // for a `self: &mut Self` to suggest removing the `&mut`.
264 _, _, hir::Mutability::Mutable
265 ) = local_decl.ty.kind {
275 err.span_label(span, format!("cannot {ACT}", ACT = act));
276 err.span_label(span, "try removing `&mut` here");
279 // We want to suggest users use `let mut` for local (user
280 // variable) mutations...
282 base: PlaceBase::Local(local),
284 } if self.body.local_decls[*local].can_be_made_mutable() => {
285 // ... but it doesn't make sense to suggest it on
286 // variables that are `ref x`, `ref mut x`, `&self`,
287 // or `&mut self` (such variables are simply not
289 let local_decl = &self.body.local_decls[*local];
290 assert_eq!(local_decl.mutability, Mutability::Not);
292 err.span_label(span, format!("cannot {ACT}", ACT = act));
294 local_decl.source_info.span,
295 "consider changing this to be mutable",
296 format!("mut {}", self.local_names[*local].unwrap()),
297 Applicability::MachineApplicable,
301 // Also suggest adding mut for upvars
304 projection: [proj_base @ .., ProjectionElem::Field(upvar_index, _)],
306 debug_assert!(is_closure_or_generator(
307 Place::ty_from(base, proj_base, *self.body, self.infcx.tcx).ty
310 err.span_label(span, format!("cannot {ACT}", ACT = act));
312 let upvar_hir_id = self.upvars[upvar_index.index()].var_hir_id;
313 if let Some(Node::Binding(pat)) = self.infcx.tcx.hir().find(upvar_hir_id)
315 if let hir::PatKind::Binding(
316 hir::BindingAnnotation::Unannotated,
324 "consider changing this to be mutable",
325 format!("mut {}", upvar_ident.name),
326 Applicability::MachineApplicable,
332 // complete hack to approximate old AST-borrowck
333 // diagnostic: if the span starts with a mutable borrow of
334 // a local variable, then just suggest the user remove it.
336 base: PlaceBase::Local(_),
339 if let Ok(snippet) = self.infcx.tcx.sess.source_map().span_to_snippet(span) {
340 snippet.starts_with("&mut ")
346 err.span_label(span, format!("cannot {ACT}", ACT = act));
347 err.span_label(span, "try removing `&mut` here");
351 base: PlaceBase::Local(local),
352 projection: [ProjectionElem::Deref],
353 } if self.body.local_decls[*local].is_ref_for_guard() => {
354 err.span_label(span, format!("cannot {ACT}", ACT = act));
356 "variables bound in patterns are immutable until the end of the pattern guard",
360 // We want to point out when a `&` can be readily replaced
363 // FIXME: can this case be generalized to work for an
364 // arbitrary base for the projection?
366 base: PlaceBase::Local(local),
367 projection: [ProjectionElem::Deref],
368 } if self.body.local_decls[*local].is_user_variable() =>
370 let local_decl = &self.body.local_decls[*local];
371 let suggestion = match local_decl.local_info {
372 LocalInfo::User(ClearCrossCrate::Set(mir::BindingForm::ImplicitSelf(_))) => {
373 Some(suggest_ampmut_self(self.infcx.tcx, local_decl))
376 LocalInfo::User(ClearCrossCrate::Set(mir::BindingForm::Var(
377 mir::VarBindingForm {
378 binding_mode: ty::BindingMode::BindByValue(_),
382 ))) => Some(suggest_ampmut(
390 LocalInfo::User(ClearCrossCrate::Set(mir::BindingForm::Var(
391 mir::VarBindingForm {
392 binding_mode: ty::BindingMode::BindByReference(_),
396 let pattern_span = local_decl.source_info.span;
397 suggest_ref_mut(self.infcx.tcx, pattern_span)
398 .map(|replacement| (pattern_span, replacement))
401 LocalInfo::User(ClearCrossCrate::Clear) => bug!("saw cleared local state"),
406 let (pointer_sigil, pointer_desc) = if local_decl.ty.is_region_ptr() {
409 ("*const", "pointer")
412 if let Some((err_help_span, suggested_code)) = suggestion {
415 &format!("consider changing this to be a mutable {}", pointer_desc),
417 Applicability::MachineApplicable,
421 match self.local_names[*local] {
422 Some(name) if !local_decl.from_compiler_desugaring() => {
426 "`{NAME}` is a `{SIGIL}` {DESC}, \
427 so the data it refers to cannot be {ACTED_ON}",
429 SIGIL = pointer_sigil,
439 "cannot {ACT} through `{SIGIL}` {DESC}",
441 SIGIL = pointer_sigil,
451 projection: [ProjectionElem::Deref],
452 // FIXME document what is this 1 magic number about
453 } if *base == PlaceBase::Local(Local::new(1)) &&
454 !self.upvars.is_empty() =>
456 err.span_label(span, format!("cannot {ACT}", ACT = act));
459 "consider changing this to accept closures that implement `FnMut`"
465 projection: [.., ProjectionElem::Deref],
467 err.span_label(span, format!("cannot {ACT}", ACT = act));
470 Some(BorrowedContentSource::OverloadedDeref(ty)) => {
473 "trait `DerefMut` is required to modify through a dereference, \
474 but it is not implemented for `{}`",
479 Some(BorrowedContentSource::OverloadedIndex(ty)) => {
482 "trait `IndexMut` is required to modify indexed content, \
483 but it is not implemented for `{}`",
493 err.span_label(span, format!("cannot {ACT}", ACT = act));
497 err.buffer(&mut self.errors_buffer);
501 fn suggest_ampmut_self<'tcx>(
503 local_decl: &mir::LocalDecl<'tcx>,
504 ) -> (Span, String) {
505 let sp = local_decl.source_info.span;
506 (sp, match tcx.sess.source_map().span_to_snippet(sp) {
508 let lt_pos = snippet.find('\'');
509 if let Some(lt_pos) = lt_pos {
510 format!("&{}mut self", &snippet[lt_pos..snippet.len() - 4])
512 "&mut self".to_string()
515 _ => "&mut self".to_string()
519 // When we want to suggest a user change a local variable to be a `&mut`, there
520 // are three potential "obvious" things to highlight:
522 // let ident [: Type] [= RightHandSideExpression];
523 // ^^^^^ ^^^^ ^^^^^^^^^^^^^^^^^^^^^^^
526 // We can always fallback on highlighting the first. But chances are good that
527 // the user experience will be better if we highlight one of the others if possible;
528 // for example, if the RHS is present and the Type is not, then the type is going to
529 // be inferred *from* the RHS, which means we should highlight that (and suggest
530 // that they borrow the RHS mutably).
532 // This implementation attempts to emulate AST-borrowck prioritization
533 // by trying (3.), then (2.) and finally falling back on (1.).
534 fn suggest_ampmut<'tcx>(
536 body: ReadOnlyBodyCache<'_, 'tcx>,
538 local_decl: &mir::LocalDecl<'tcx>,
539 opt_ty_info: Option<Span>,
540 ) -> (Span, String) {
541 let locations = body.find_assignments(local);
542 if !locations.is_empty() {
543 let assignment_rhs_span = body.source_info(locations[0]).span;
544 if let Ok(src) = tcx.sess.source_map().span_to_snippet(assignment_rhs_span) {
545 if let (true, Some(ws_pos)) = (
546 src.starts_with("&'"),
547 src.find(|c: char| -> bool { c.is_whitespace() }),
549 let lt_name = &src[1..ws_pos];
550 let ty = &src[ws_pos..];
551 return (assignment_rhs_span, format!("&{} mut {}", lt_name, ty));
552 } else if src.starts_with('&') {
553 let borrowed_expr = &src[1..];
554 return (assignment_rhs_span, format!("&mut {}", borrowed_expr));
559 let highlight_span = match opt_ty_info {
560 // if this is a variable binding with an explicit type,
561 // try to highlight that for the suggestion.
562 Some(ty_span) => ty_span,
564 // otherwise, just highlight the span associated with
565 // the (MIR) LocalDecl.
566 None => local_decl.source_info.span,
569 if let Ok(src) = tcx.sess.source_map().span_to_snippet(highlight_span) {
570 if let (true, Some(ws_pos)) = (
571 src.starts_with("&'"),
572 src.find(|c: char| -> bool { c.is_whitespace() }),
574 let lt_name = &src[1..ws_pos];
575 let ty = &src[ws_pos..];
576 return (highlight_span, format!("&{} mut{}", lt_name, ty));
580 let ty_mut = local_decl.ty.builtin_deref(true).unwrap();
581 assert_eq!(ty_mut.mutbl, hir::Mutability::Immutable);
583 if local_decl.ty.is_region_ptr() {
584 format!("&mut {}", ty_mut.ty)
586 format!("*mut {}", ty_mut.ty)
590 fn is_closure_or_generator(ty: Ty<'_>) -> bool {
591 ty.is_closure() || ty.is_generator()
594 /// Adds a suggestion to a struct definition given a field access to a local.
595 /// This function expects the local to be a reference to a struct in order to produce a suggestion.
598 /// LL | s: &'a String
599 /// | ---------- use `&'a mut String` here to make mutable
601 fn annotate_struct_field(
605 ) -> Option<(Span, String)> {
606 // Expect our local to be a reference to a struct of some kind.
607 if let ty::Ref(_, ty, _) = ty.kind {
608 if let ty::Adt(def, _) = ty.kind {
609 let field = def.all_fields().nth(field.index())?;
610 // Use the HIR types to construct the diagnostic message.
611 let hir_id = tcx.hir().as_local_hir_id(field.did)?;
612 let node = tcx.hir().find(hir_id)?;
613 // Now we're dealing with the actual struct that we're going to suggest a change to,
614 // we can expect a field that is an immutable reference to a type.
615 if let hir::Node::Field(field) = node {
616 if let hir::TyKind::Rptr(lifetime, hir::MutTy {
617 mutbl: hir::Mutability::Immutable,
620 // Get the snippets in two parts - the named lifetime (if there is one) and
621 // type being referenced, that way we can reconstruct the snippet without loss
623 let type_snippet = tcx.sess.source_map().span_to_snippet(ty.span).ok()?;
624 let lifetime_snippet = if !lifetime.is_elided() {
625 format!("{} ", tcx.sess.source_map().span_to_snippet(lifetime.span).ok()?)
634 lifetime_snippet, &*type_snippet,
645 /// If possible, suggest replacing `ref` with `ref mut`.
646 fn suggest_ref_mut(tcx: TyCtxt<'_>, binding_span: Span) -> Option<String> {
647 let hi_src = tcx.sess.source_map().span_to_snippet(binding_span).ok()?;
648 if hi_src.starts_with("ref")
649 && hi_src["ref".len()..].starts_with(rustc_lexer::is_whitespace)
651 let replacement = format!("ref mut{}", &hi_src["ref".len()..]);