1 use rustc::mir::{self, ClearCrossCrate, Local, LocalInfo, Location, ReadOnlyBodyAndCache};
2 use rustc::mir::{Mutability, Place, PlaceRef, ProjectionElem};
3 use rustc::ty::{self, Ty, TyCtxt};
6 use rustc_index::vec::Idx;
7 use rustc_span::symbol::kw;
10 use crate::borrow_check::diagnostics::BorrowedContentSource;
11 use crate::borrow_check::MirBorrowckCtxt;
12 use crate::util::collect_writes::FindAssignments;
13 use rustc_errors::{Applicability, DiagnosticBuilder};
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<'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);
45 PlaceRef { local, projection: [] } => {
46 item_msg = format!("`{}`", access_place_desc.unwrap());
47 if access_place.as_local().is_some() {
48 reason = ", as it is not declared as mutable".to_string();
50 let name = self.local_names[local].expect("immutable unnamed local");
51 reason = format!(", as `{}` is not declared as mutable", name);
57 projection: [proj_base @ .., ProjectionElem::Field(upvar_index, _)],
59 debug_assert!(is_closure_or_generator(
60 Place::ty_from(local, proj_base, *self.body, self.infcx.tcx).ty
63 item_msg = format!("`{}`", access_place_desc.unwrap());
64 if self.is_upvar_field_projection(access_place.as_ref()).is_some() {
65 reason = ", as it is not declared as mutable".to_string();
67 let name = self.upvars[upvar_index.index()].name;
68 reason = format!(", as `{}` is not declared as mutable", name);
72 PlaceRef { local, projection: [ProjectionElem::Deref] }
73 if self.body.local_decls[local].is_ref_for_guard() =>
75 item_msg = format!("`{}`", access_place_desc.unwrap());
76 reason = ", as it is immutable for the pattern guard".to_string();
78 PlaceRef { local, projection: [ProjectionElem::Deref] }
79 if self.body.local_decls[local].is_ref_to_static() =>
81 if access_place.projection.len() == 1 {
82 item_msg = format!("immutable static item `{}`", access_place_desc.unwrap());
83 reason = String::new();
85 item_msg = format!("`{}`", access_place_desc.unwrap());
86 let local_info = &self.body.local_decls[local].local_info;
87 if let LocalInfo::StaticRef { def_id, .. } = *local_info {
88 let static_name = &self.infcx.tcx.item_name(def_id);
89 reason = format!(", as `{}` is an immutable static item", static_name);
91 bug!("is_ref_to_static return true, but not ref to static?");
95 PlaceRef { local: _, projection: [proj_base @ .., ProjectionElem::Deref] } => {
96 if the_place_err.local == Local::new(1)
97 && proj_base.is_empty()
98 && !self.upvars.is_empty()
100 item_msg = format!("`{}`", access_place_desc.unwrap());
101 debug_assert!(self.body.local_decls[Local::new(1)].ty.is_region_ptr());
102 debug_assert!(is_closure_or_generator(
105 the_place_err.projection,
112 reason = 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 let source = self.borrowed_content_source(PlaceRef {
119 local: the_place_err.local,
120 projection: proj_base,
122 let pointer_type = source.describe_for_immutable_place();
123 opt_source = Some(source);
124 if let Some(desc) = access_place_desc {
125 item_msg = format!("`{}`", desc);
126 reason = match error_access {
127 AccessKind::Mutate => format!(" which is behind {}", pointer_type),
128 AccessKind::MutableBorrow => {
129 format!(", as it is behind {}", pointer_type)
133 item_msg = format!("data in {}", pointer_type);
134 reason = String::new();
139 PlaceRef { local: _, projection: [.., ProjectionElem::Index(_)] }
140 | PlaceRef { local: _, projection: [.., ProjectionElem::ConstantIndex { .. }] }
141 | PlaceRef { local: _, projection: [.., ProjectionElem::Subslice { .. }] }
142 | PlaceRef { local: _, projection: [.., ProjectionElem::Downcast(..)] } => {
143 bug!("Unexpected immutable place.")
147 debug!("report_mutability_error: item_msg={:?}, reason={:?}", item_msg, reason);
149 // `act` and `acted_on` are strings that let us abstract over
150 // the verbs used in some diagnostic messages.
154 let span = match error_access {
155 AccessKind::Mutate => {
156 err = self.cannot_assign(span, &(item_msg + &reason));
158 acted_on = "written";
161 AccessKind::MutableBorrow => {
162 act = "borrow as mutable";
163 acted_on = "borrowed as mutable";
165 let borrow_spans = self.borrow_spans(span, location);
166 let borrow_span = borrow_spans.args_or_use();
167 err = self.cannot_borrow_path_as_mutable_because(borrow_span, &item_msg, &reason);
168 borrow_spans.var_span_label(
171 "mutable borrow occurs due to use of `{}` in closure",
172 // always Some() if the message is printed.
173 self.describe_place(access_place.as_ref()).unwrap_or_default(),
180 debug!("report_mutability_error: act={:?}, acted_on={:?}", act, acted_on);
182 match the_place_err {
183 // Suggest making an existing shared borrow in a struct definition a mutable borrow.
185 // This is applicable when we have a deref of a field access to a deref of a local -
186 // something like `*((*_1).0`. The local that we get will be a reference to the
187 // struct we've got a field access of (it must be a reference since there's a deref
188 // after the field access).
192 [proj_base @ .., ProjectionElem::Deref, ProjectionElem::Field(field, _), ProjectionElem::Deref],
194 err.span_label(span, format!("cannot {ACT}", ACT = act));
196 if let Some((span, message)) = annotate_struct_field(
198 Place::ty_from(local, proj_base, *self.body, self.infcx.tcx).ty,
203 "consider changing this to be mutable",
205 Applicability::MaybeIncorrect,
210 // Suggest removing a `&mut` from the use of a mutable reference.
211 PlaceRef { local, projection: [] }
217 if let LocalInfo::User(ClearCrossCrate::Set(
218 mir::BindingForm::ImplicitSelf(kind),
219 )) = local_decl.local_info
221 // Check if the user variable is a `&mut self` and we can therefore
222 // suggest removing the `&mut`.
224 // Deliberately fall into this case for all implicit self types,
225 // so that we don't fall in to the next case with them.
226 kind == mir::ImplicitSelfKind::MutRef
227 } else if Some(kw::SelfLower) == self.local_names[local] {
228 // Otherwise, check if the name is the self kewyord - in which case
229 // we have an explicit self. Do the same thing in this case and check
230 // for a `self: &mut Self` to suggest removing the `&mut`.
231 if let ty::Ref(_, _, hir::Mutability::Mut) = local_decl.ty.kind {
243 err.span_label(span, format!("cannot {ACT}", ACT = act));
244 err.span_label(span, "try removing `&mut` here");
247 // We want to suggest users use `let mut` for local (user
248 // variable) mutations...
249 PlaceRef { local, projection: [] }
250 if self.body.local_decls[local].can_be_made_mutable() =>
252 // ... but it doesn't make sense to suggest it on
253 // variables that are `ref x`, `ref mut x`, `&self`,
254 // or `&mut self` (such variables are simply not
256 let local_decl = &self.body.local_decls[local];
257 assert_eq!(local_decl.mutability, Mutability::Not);
259 err.span_label(span, format!("cannot {ACT}", ACT = act));
261 local_decl.source_info.span,
262 "consider changing this to be mutable",
263 format!("mut {}", self.local_names[local].unwrap()),
264 Applicability::MachineApplicable,
268 // Also suggest adding mut for upvars
271 projection: [proj_base @ .., ProjectionElem::Field(upvar_index, _)],
273 debug_assert!(is_closure_or_generator(
274 Place::ty_from(local, proj_base, *self.body, self.infcx.tcx).ty
277 err.span_label(span, format!("cannot {ACT}", ACT = act));
279 let upvar_hir_id = self.upvars[upvar_index.index()].var_hir_id;
280 if let Some(Node::Binding(pat)) = self.infcx.tcx.hir().find(upvar_hir_id) {
281 if let hir::PatKind::Binding(
282 hir::BindingAnnotation::Unannotated,
290 "consider changing this to be mutable",
291 format!("mut {}", upvar_ident.name),
292 Applicability::MachineApplicable,
298 // complete hack to approximate old AST-borrowck
299 // diagnostic: if the span starts with a mutable borrow of
300 // a local variable, then just suggest the user remove it.
301 PlaceRef { local: _, projection: [] }
303 if let Ok(snippet) = self.infcx.tcx.sess.source_map().span_to_snippet(span) {
304 snippet.starts_with("&mut ")
310 err.span_label(span, format!("cannot {ACT}", ACT = act));
311 err.span_label(span, "try removing `&mut` here");
314 PlaceRef { local, projection: [ProjectionElem::Deref] }
315 if self.body.local_decls[local].is_ref_for_guard() =>
317 err.span_label(span, format!("cannot {ACT}", ACT = act));
319 "variables bound in patterns are immutable until the end of the pattern guard",
323 // We want to point out when a `&` can be readily replaced
326 // FIXME: can this case be generalized to work for an
327 // arbitrary base for the projection?
328 PlaceRef { local, projection: [ProjectionElem::Deref] }
329 if self.body.local_decls[local].is_user_variable() =>
331 let local_decl = &self.body.local_decls[local];
333 let (pointer_sigil, pointer_desc) = if local_decl.ty.is_region_ptr() {
336 ("*const", "pointer")
339 match self.local_names[local] {
340 Some(name) if !local_decl.from_compiler_desugaring() => {
341 let suggestion = match local_decl.local_info {
342 LocalInfo::User(ClearCrossCrate::Set(
343 mir::BindingForm::ImplicitSelf(_),
344 )) => Some(suggest_ampmut_self(self.infcx.tcx, local_decl)),
346 LocalInfo::User(ClearCrossCrate::Set(mir::BindingForm::Var(
347 mir::VarBindingForm {
348 binding_mode: ty::BindingMode::BindByValue(_),
352 ))) => Some(suggest_ampmut(
360 LocalInfo::User(ClearCrossCrate::Set(mir::BindingForm::Var(
361 mir::VarBindingForm {
362 binding_mode: ty::BindingMode::BindByReference(_),
366 let pattern_span = local_decl.source_info.span;
367 suggest_ref_mut(self.infcx.tcx, pattern_span)
368 .map(|replacement| (pattern_span, replacement))
371 LocalInfo::User(ClearCrossCrate::Clear) => {
372 bug!("saw cleared local state")
378 if let Some((err_help_span, suggested_code)) = suggestion {
381 &format!("consider changing this to be a mutable {}", pointer_desc),
383 Applicability::MachineApplicable,
389 "`{NAME}` is a `{SIGIL}` {DESC}, \
390 so the data it refers to cannot be {ACTED_ON}",
392 SIGIL = pointer_sigil,
402 "cannot {ACT} through `{SIGIL}` {DESC}",
404 SIGIL = pointer_sigil,
414 projection: [ProjectionElem::Deref],
415 // FIXME document what is this 1 magic number about
416 } if local == Local::new(1) && !self.upvars.is_empty() => {
417 self.expected_fn_found_fn_mut_call(&mut err, span, act);
420 PlaceRef { local: _, projection: [.., ProjectionElem::Deref] } => {
421 err.span_label(span, format!("cannot {ACT}", ACT = act));
424 Some(BorrowedContentSource::OverloadedDeref(ty)) => {
426 "trait `DerefMut` is required to modify through a dereference, \
427 but it is not implemented for `{}`",
431 Some(BorrowedContentSource::OverloadedIndex(ty)) => {
433 "trait `IndexMut` is required to modify indexed content, \
434 but it is not implemented for `{}`",
443 err.span_label(span, format!("cannot {ACT}", ACT = act));
447 err.buffer(&mut self.errors_buffer);
450 /// Targeted error when encountering an `FnMut` closure where an `Fn` closure was expected.
451 fn expected_fn_found_fn_mut_call(&self, err: &mut DiagnosticBuilder<'_>, sp: Span, act: &str) {
452 err.span_label(sp, format!("cannot {}", act));
454 let hir = self.infcx.tcx.hir();
455 let closure_id = hir.as_local_hir_id(self.mir_def_id).unwrap();
456 let fn_call_id = hir.get_parent_node(closure_id);
457 let node = hir.get(fn_call_id);
458 let item_id = hir.get_parent_item(fn_call_id);
459 let mut look_at_return = true;
460 // If we can detect the expression to be an `fn` call where the closure was an argument,
461 // we point at the `fn` definition argument...
463 hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Call(func, args), .. }) => {
467 .filter(|(_, arg)| arg.span == self.body.span)
470 let def_id = hir.local_def_id(item_id);
471 let tables = self.infcx.tcx.typeck_tables_of(def_id);
472 if let Some(ty::FnDef(def_id, _)) =
473 tables.node_type_opt(func.hir_id).as_ref().map(|ty| &ty.kind)
475 let arg = match hir.get_if_local(*def_id) {
476 Some(hir::Node::Item(hir::Item {
478 kind: hir::ItemKind::Fn(sig, ..),
481 | Some(hir::Node::TraitItem(hir::TraitItem {
483 kind: hir::TraitItemKind::Fn(sig, _),
486 | Some(hir::Node::ImplItem(hir::ImplItem {
488 kind: hir::ImplItemKind::Fn(sig, _),
494 pos + if sig.decl.implicit_self.has_implicit_self() {
502 .unwrap_or(ident.span),
506 if let Some(span) = arg {
507 err.span_label(span, "change this to accept `FnMut` instead of `Fn`");
508 err.span_label(func.span, "expects `Fn` instead of `FnMut`");
509 if self.infcx.tcx.sess.source_map().is_multiline(self.body.span) {
510 err.span_label(self.body.span, "in this closure");
512 look_at_return = false;
518 if look_at_return && hir.get_return_block(closure_id).is_some() {
519 // ...otherwise we are probably in the tail expression of the function, point at the
521 match hir.get(hir.get_parent_item(fn_call_id)) {
522 hir::Node::Item(hir::Item { ident, kind: hir::ItemKind::Fn(sig, ..), .. })
523 | hir::Node::TraitItem(hir::TraitItem {
525 kind: hir::TraitItemKind::Fn(sig, _),
528 | hir::Node::ImplItem(hir::ImplItem {
530 kind: hir::ImplItemKind::Fn(sig, _),
533 err.span_label(ident.span, "");
535 sig.decl.output.span(),
536 "change this to return `FnMut` instead of `Fn`",
538 err.span_label(self.body.span, "in this closure");
546 fn suggest_ampmut_self<'tcx>(
548 local_decl: &mir::LocalDecl<'tcx>,
549 ) -> (Span, String) {
550 let sp = local_decl.source_info.span;
553 match tcx.sess.source_map().span_to_snippet(sp) {
555 let lt_pos = snippet.find('\'');
556 if let Some(lt_pos) = lt_pos {
557 format!("&{}mut self", &snippet[lt_pos..snippet.len() - 4])
559 "&mut self".to_string()
562 _ => "&mut self".to_string(),
567 // When we want to suggest a user change a local variable to be a `&mut`, there
568 // are three potential "obvious" things to highlight:
570 // let ident [: Type] [= RightHandSideExpression];
571 // ^^^^^ ^^^^ ^^^^^^^^^^^^^^^^^^^^^^^
574 // We can always fallback on highlighting the first. But chances are good that
575 // the user experience will be better if we highlight one of the others if possible;
576 // for example, if the RHS is present and the Type is not, then the type is going to
577 // be inferred *from* the RHS, which means we should highlight that (and suggest
578 // that they borrow the RHS mutably).
580 // This implementation attempts to emulate AST-borrowck prioritization
581 // by trying (3.), then (2.) and finally falling back on (1.).
582 fn suggest_ampmut<'tcx>(
584 body: ReadOnlyBodyAndCache<'_, 'tcx>,
586 local_decl: &mir::LocalDecl<'tcx>,
587 opt_ty_info: Option<Span>,
588 ) -> (Span, String) {
589 let locations = body.find_assignments(local);
590 if !locations.is_empty() {
591 let assignment_rhs_span = body.source_info(locations[0]).span;
592 if let Ok(src) = tcx.sess.source_map().span_to_snippet(assignment_rhs_span) {
593 if let (true, Some(ws_pos)) =
594 (src.starts_with("&'"), src.find(|c: char| -> bool { c.is_whitespace() }))
596 let lt_name = &src[1..ws_pos];
597 let ty = &src[ws_pos..];
598 return (assignment_rhs_span, format!("&{} mut {}", lt_name, ty));
599 } else if src.starts_with('&') {
600 let borrowed_expr = &src[1..];
601 return (assignment_rhs_span, format!("&mut {}", borrowed_expr));
606 let highlight_span = match opt_ty_info {
607 // if this is a variable binding with an explicit type,
608 // try to highlight that for the suggestion.
609 Some(ty_span) => ty_span,
611 // otherwise, just highlight the span associated with
612 // the (MIR) LocalDecl.
613 None => local_decl.source_info.span,
616 if let Ok(src) = tcx.sess.source_map().span_to_snippet(highlight_span) {
617 if let (true, Some(ws_pos)) =
618 (src.starts_with("&'"), src.find(|c: char| -> bool { c.is_whitespace() }))
620 let lt_name = &src[1..ws_pos];
621 let ty = &src[ws_pos..];
622 return (highlight_span, format!("&{} mut{}", lt_name, ty));
626 let ty_mut = local_decl.ty.builtin_deref(true).unwrap();
627 assert_eq!(ty_mut.mutbl, hir::Mutability::Not);
630 if local_decl.ty.is_region_ptr() {
631 format!("&mut {}", ty_mut.ty)
633 format!("*mut {}", ty_mut.ty)
638 fn is_closure_or_generator(ty: Ty<'_>) -> bool {
639 ty.is_closure() || ty.is_generator()
642 /// Adds a suggestion to a struct definition given a field access to a local.
643 /// This function expects the local to be a reference to a struct in order to produce a suggestion.
646 /// LL | s: &'a String
647 /// | ---------- use `&'a mut String` here to make mutable
649 fn annotate_struct_field(
653 ) -> Option<(Span, String)> {
654 // Expect our local to be a reference to a struct of some kind.
655 if let ty::Ref(_, ty, _) = ty.kind {
656 if let ty::Adt(def, _) = ty.kind {
657 let field = def.all_fields().nth(field.index())?;
658 // Use the HIR types to construct the diagnostic message.
659 let hir_id = tcx.hir().as_local_hir_id(field.did)?;
660 let node = tcx.hir().find(hir_id)?;
661 // Now we're dealing with the actual struct that we're going to suggest a change to,
662 // we can expect a field that is an immutable reference to a type.
663 if let hir::Node::Field(field) = node {
664 if let hir::TyKind::Rptr(
666 hir::MutTy { mutbl: hir::Mutability::Not, ref ty },
669 // Get the snippets in two parts - the named lifetime (if there is one) and
670 // type being referenced, that way we can reconstruct the snippet without loss
672 let type_snippet = tcx.sess.source_map().span_to_snippet(ty.span).ok()?;
673 let lifetime_snippet = if !lifetime.is_elided() {
674 format!("{} ", tcx.sess.source_map().span_to_snippet(lifetime.span).ok()?)
681 format!("&{}mut {}", lifetime_snippet, &*type_snippet,),
691 /// If possible, suggest replacing `ref` with `ref mut`.
692 fn suggest_ref_mut(tcx: TyCtxt<'_>, binding_span: Span) -> Option<String> {
693 let hi_src = tcx.sess.source_map().span_to_snippet(binding_span).ok()?;
694 if hi_src.starts_with("ref") && hi_src["ref".len()..].starts_with(rustc_lexer::is_whitespace) {
695 let replacement = format!("ref mut{}", &hi_src["ref".len()..]);