3 use rustc_index::vec::Idx;
4 use rustc_middle::mir::{self, ClearCrossCrate, Local, LocalInfo, Location};
5 use rustc_middle::mir::{Mutability, Place, PlaceRef, ProjectionElem};
6 use rustc_middle::ty::{self, Ty, TyCtxt};
7 use rustc_span::source_map::DesugaringKind;
8 use rustc_span::symbol::kw;
11 use crate::borrow_check::diagnostics::BorrowedContentSource;
12 use crate::borrow_check::MirBorrowckCtxt;
13 use crate::util::collect_writes::FindAssignments;
14 use rustc_errors::{Applicability, DiagnosticBuilder};
16 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
17 pub(crate) enum AccessKind {
22 impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
23 pub(crate) fn report_mutability_error(
25 access_place: Place<'tcx>,
27 the_place_err: PlaceRef<'tcx>,
28 error_access: AccessKind,
32 "report_mutability_error(\
33 access_place={:?}, span={:?}, the_place_err={:?}, error_access={:?}, location={:?},\
35 access_place, span, the_place_err, error_access, location,
41 let mut opt_source = None;
42 let access_place_desc = self.describe_place(access_place.as_ref());
43 debug!("report_mutability_error: access_place_desc={:?}", access_place_desc);
46 PlaceRef { local, projection: [] } => {
47 item_msg = format!("`{}`", access_place_desc.unwrap());
48 if access_place.as_local().is_some() {
49 reason = ", as it is not declared as mutable".to_string();
51 let name = self.local_names[local].expect("immutable unnamed local");
52 reason = format!(", as `{}` is not declared as mutable", name);
58 projection: [proj_base @ .., ProjectionElem::Field(upvar_index, _)],
60 debug_assert!(is_closure_or_generator(
61 Place::ty_from(local, proj_base, self.body, self.infcx.tcx).ty
64 item_msg = format!("`{}`", access_place_desc.unwrap());
65 if self.is_upvar_field_projection(access_place.as_ref()).is_some() {
66 reason = ", as it is not declared as mutable".to_string();
68 let name = self.upvars[upvar_index.index()].name;
69 reason = format!(", as `{}` is not declared as mutable", name);
73 PlaceRef { local, projection: [ProjectionElem::Deref] }
74 if self.body.local_decls[local].is_ref_for_guard() =>
76 item_msg = format!("`{}`", access_place_desc.unwrap());
77 reason = ", as it is immutable for the pattern guard".to_string();
79 PlaceRef { local, projection: [ProjectionElem::Deref] }
80 if self.body.local_decls[local].is_ref_to_static() =>
82 if access_place.projection.len() == 1 {
83 item_msg = format!("immutable static item `{}`", access_place_desc.unwrap());
84 reason = String::new();
86 item_msg = format!("`{}`", access_place_desc.unwrap());
87 let local_info = &self.body.local_decls[local].local_info;
88 if let LocalInfo::StaticRef { def_id, .. } = *local_info {
89 let static_name = &self.infcx.tcx.item_name(def_id);
90 reason = format!(", as `{}` is an immutable static item", static_name);
92 bug!("is_ref_to_static return true, but not ref to static?");
96 PlaceRef { local: _, projection: [proj_base @ .., ProjectionElem::Deref] } => {
97 if the_place_err.local == Local::new(1)
98 && proj_base.is_empty()
99 && !self.upvars.is_empty()
101 item_msg = format!("`{}`", access_place_desc.unwrap());
102 debug_assert!(self.body.local_decls[Local::new(1)].ty.is_region_ptr());
103 debug_assert!(is_closure_or_generator(
106 the_place_err.projection,
113 reason = if self.is_upvar_field_projection(access_place.as_ref()).is_some() {
114 ", as it is a captured variable in a `Fn` closure".to_string()
116 ", as `Fn` closures cannot mutate their captured variables".to_string()
119 let source = self.borrowed_content_source(PlaceRef {
120 local: the_place_err.local,
121 projection: proj_base,
123 let pointer_type = source.describe_for_immutable_place(self.infcx.tcx);
124 opt_source = Some(source);
125 if let Some(desc) = access_place_desc {
126 item_msg = format!("`{}`", desc);
127 reason = match error_access {
128 AccessKind::Mutate => format!(" which is behind {}", pointer_type),
129 AccessKind::MutableBorrow => {
130 format!(", as it is behind {}", pointer_type)
134 item_msg = format!("data in {}", pointer_type);
135 reason = String::new();
143 [.., ProjectionElem::Index(_)
144 | ProjectionElem::ConstantIndex { .. }
145 | ProjectionElem::Subslice { .. }
146 | ProjectionElem::Downcast(..)],
147 } => bug!("Unexpected immutable place."),
150 debug!("report_mutability_error: item_msg={:?}, reason={:?}", item_msg, reason);
152 // `act` and `acted_on` are strings that let us abstract over
153 // the verbs used in some diagnostic messages.
157 let span = match error_access {
158 AccessKind::Mutate => {
159 err = self.cannot_assign(span, &(item_msg + &reason));
161 acted_on = "written";
164 AccessKind::MutableBorrow => {
165 act = "borrow as mutable";
166 acted_on = "borrowed as mutable";
168 let borrow_spans = self.borrow_spans(span, location);
169 let borrow_span = borrow_spans.args_or_use();
170 err = self.cannot_borrow_path_as_mutable_because(borrow_span, &item_msg, &reason);
171 borrow_spans.var_span_label(
174 "mutable borrow occurs due to use of {} in closure",
175 self.describe_any_place(access_place.as_ref()),
182 debug!("report_mutability_error: act={:?}, acted_on={:?}", act, acted_on);
184 match the_place_err {
185 // Suggest making an existing shared borrow in a struct definition a mutable borrow.
187 // This is applicable when we have a deref of a field access to a deref of a local -
188 // something like `*((*_1).0`. The local that we get will be a reference to the
189 // struct we've got a field access of (it must be a reference since there's a deref
190 // after the field access).
194 [proj_base @ .., ProjectionElem::Deref, ProjectionElem::Field(field, _), ProjectionElem::Deref],
196 err.span_label(span, format!("cannot {ACT}", ACT = act));
198 if let Some((span, message)) = annotate_struct_field(
200 Place::ty_from(local, proj_base, self.body, self.infcx.tcx).ty,
205 "consider changing this to be mutable",
207 Applicability::MaybeIncorrect,
212 // Suggest removing a `&mut` from the use of a mutable reference.
213 PlaceRef { local, projection: [] }
219 if let LocalInfo::User(ClearCrossCrate::Set(
220 mir::BindingForm::ImplicitSelf(kind),
221 )) = local_decl.local_info
223 // Check if the user variable is a `&mut self` and we can therefore
224 // suggest removing the `&mut`.
226 // Deliberately fall into this case for all implicit self types,
227 // so that we don't fall in to the next case with them.
228 kind == mir::ImplicitSelfKind::MutRef
229 } else if Some(kw::SelfLower) == self.local_names[local] {
230 // Otherwise, check if the name is the self kewyord - in which case
231 // we have an explicit self. Do the same thing in this case and check
232 // for a `self: &mut Self` to suggest removing the `&mut`.
233 if let ty::Ref(_, _, hir::Mutability::Mut) = local_decl.ty.kind {
245 err.span_label(span, format!("cannot {ACT}", ACT = act));
246 err.span_label(span, "try removing `&mut` here");
249 // We want to suggest users use `let mut` for local (user
250 // variable) mutations...
251 PlaceRef { local, projection: [] }
252 if self.body.local_decls[local].can_be_made_mutable() =>
254 // ... but it doesn't make sense to suggest it on
255 // variables that are `ref x`, `ref mut x`, `&self`,
256 // or `&mut self` (such variables are simply not
258 let local_decl = &self.body.local_decls[local];
259 assert_eq!(local_decl.mutability, Mutability::Not);
261 err.span_label(span, format!("cannot {ACT}", ACT = act));
263 local_decl.source_info.span,
264 "consider changing this to be mutable",
265 format!("mut {}", self.local_names[local].unwrap()),
266 Applicability::MachineApplicable,
270 // Also suggest adding mut for upvars
273 projection: [proj_base @ .., ProjectionElem::Field(upvar_index, _)],
275 debug_assert!(is_closure_or_generator(
276 Place::ty_from(local, proj_base, self.body, self.infcx.tcx).ty
279 err.span_label(span, format!("cannot {ACT}", ACT = act));
281 let upvar_hir_id = self.upvars[upvar_index.index()].var_hir_id;
282 if let Some(Node::Binding(pat)) = self.infcx.tcx.hir().find(upvar_hir_id) {
283 if let hir::PatKind::Binding(
284 hir::BindingAnnotation::Unannotated,
292 "consider changing this to be mutable",
293 format!("mut {}", upvar_ident.name),
294 Applicability::MachineApplicable,
300 // complete hack to approximate old AST-borrowck
301 // diagnostic: if the span starts with a mutable borrow of
302 // a local variable, then just suggest the user remove it.
303 PlaceRef { local: _, projection: [] }
305 if let Ok(snippet) = self.infcx.tcx.sess.source_map().span_to_snippet(span) {
306 snippet.starts_with("&mut ")
312 err.span_label(span, format!("cannot {ACT}", ACT = act));
313 err.span_label(span, "try removing `&mut` here");
316 PlaceRef { local, projection: [ProjectionElem::Deref] }
317 if self.body.local_decls[local].is_ref_for_guard() =>
319 err.span_label(span, format!("cannot {ACT}", ACT = act));
321 "variables bound in patterns are immutable until the end of the pattern guard",
325 // We want to point out when a `&` can be readily replaced
328 // FIXME: can this case be generalized to work for an
329 // arbitrary base for the projection?
330 PlaceRef { local, projection: [ProjectionElem::Deref] }
331 if self.body.local_decls[local].is_user_variable() =>
333 let local_decl = &self.body.local_decls[local];
335 let (pointer_sigil, pointer_desc) = if local_decl.ty.is_region_ptr() {
338 ("*const", "pointer")
341 match self.local_names[local] {
342 Some(name) if !local_decl.from_compiler_desugaring() => {
343 let label = match local_decl.local_info {
344 LocalInfo::User(ClearCrossCrate::Set(
345 mir::BindingForm::ImplicitSelf(_),
347 let (span, suggestion) =
348 suggest_ampmut_self(self.infcx.tcx, local_decl);
349 Some((true, span, suggestion))
352 LocalInfo::User(ClearCrossCrate::Set(mir::BindingForm::Var(
353 mir::VarBindingForm {
354 binding_mode: ty::BindingMode::BindByValue(_),
359 // check if the RHS is from desugaring
360 let locations = self.body.find_assignments(local);
361 let opt_assignment_rhs_span = locations
363 .map(|&location| self.body.source_info(location).span);
364 let opt_desugaring_kind =
365 opt_assignment_rhs_span.and_then(|span| span.desugaring_kind());
366 match opt_desugaring_kind {
367 // on for loops, RHS points to the iterator part
368 Some(DesugaringKind::ForLoop) => Some((
370 opt_assignment_rhs_span.unwrap(),
372 "this iterator yields `{SIGIL}` {DESC}s",
373 SIGIL = pointer_sigil,
377 // don't create labels for compiler-generated spans
380 let (span, suggestion) = suggest_ampmut(
383 opt_assignment_rhs_span,
386 Some((true, span, suggestion))
391 LocalInfo::User(ClearCrossCrate::Set(mir::BindingForm::Var(
392 mir::VarBindingForm {
393 binding_mode: ty::BindingMode::BindByReference(_),
397 let pattern_span = local_decl.source_info.span;
398 suggest_ref_mut(self.infcx.tcx, pattern_span)
399 .map(|replacement| (true, pattern_span, replacement))
402 LocalInfo::User(ClearCrossCrate::Clear) => {
403 bug!("saw cleared local state")
410 Some((true, err_help_span, suggested_code)) => {
414 "consider changing this to be a mutable {}",
418 Applicability::MachineApplicable,
421 Some((false, err_label_span, message)) => {
422 err.span_label(err_label_span, &message);
429 "`{NAME}` is a `{SIGIL}` {DESC}, \
430 so the data it refers to cannot be {ACTED_ON}",
432 SIGIL = pointer_sigil,
442 "cannot {ACT} through `{SIGIL}` {DESC}",
444 SIGIL = pointer_sigil,
454 projection: [ProjectionElem::Deref],
455 // FIXME document what is this 1 magic number about
456 } if local == Local::new(1) && !self.upvars.is_empty() => {
457 self.expected_fn_found_fn_mut_call(&mut err, span, act);
460 PlaceRef { local: _, projection: [.., ProjectionElem::Deref] } => {
461 err.span_label(span, format!("cannot {ACT}", ACT = act));
464 Some(BorrowedContentSource::OverloadedDeref(ty)) => {
466 "trait `DerefMut` is required to modify through a dereference, \
467 but it is not implemented for `{}`",
471 Some(BorrowedContentSource::OverloadedIndex(ty)) => {
473 "trait `IndexMut` is required to modify indexed content, \
474 but it is not implemented for `{}`",
483 err.span_label(span, format!("cannot {ACT}", ACT = act));
487 err.buffer(&mut self.errors_buffer);
490 /// Targeted error when encountering an `FnMut` closure where an `Fn` closure was expected.
491 fn expected_fn_found_fn_mut_call(&self, err: &mut DiagnosticBuilder<'_>, sp: Span, act: &str) {
492 err.span_label(sp, format!("cannot {}", act));
494 let hir = self.infcx.tcx.hir();
495 let closure_id = hir.as_local_hir_id(self.mir_def_id).unwrap();
496 let fn_call_id = hir.get_parent_node(closure_id);
497 let node = hir.get(fn_call_id);
498 let item_id = hir.get_parent_item(fn_call_id);
499 let mut look_at_return = true;
500 // If we can detect the expression to be an `fn` call where the closure was an argument,
501 // we point at the `fn` definition argument...
502 if let hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Call(func, args), .. }) = node {
506 .filter(|(_, arg)| arg.span == self.body.span)
509 let def_id = hir.local_def_id(item_id);
510 let tables = self.infcx.tcx.typeck_tables_of(def_id);
511 if let Some(ty::FnDef(def_id, _)) =
512 tables.node_type_opt(func.hir_id).as_ref().map(|ty| &ty.kind)
514 let arg = match hir.get_if_local(*def_id) {
516 hir::Node::Item(hir::Item {
517 ident, kind: hir::ItemKind::Fn(sig, ..), ..
519 | hir::Node::TraitItem(hir::TraitItem {
521 kind: hir::TraitItemKind::Fn(sig, _),
524 | hir::Node::ImplItem(hir::ImplItem {
526 kind: hir::ImplItemKind::Fn(sig, _),
533 pos + if sig.decl.implicit_self.has_implicit_self() {
541 .unwrap_or(ident.span),
545 if let Some(span) = arg {
546 err.span_label(span, "change this to accept `FnMut` instead of `Fn`");
547 err.span_label(func.span, "expects `Fn` instead of `FnMut`");
548 if self.infcx.tcx.sess.source_map().is_multiline(self.body.span) {
549 err.span_label(self.body.span, "in this closure");
551 look_at_return = false;
556 if look_at_return && hir.get_return_block(closure_id).is_some() {
557 // ...otherwise we are probably in the tail expression of the function, point at the
559 match hir.get(hir.get_parent_item(fn_call_id)) {
560 hir::Node::Item(hir::Item { ident, kind: hir::ItemKind::Fn(sig, ..), .. })
561 | hir::Node::TraitItem(hir::TraitItem {
563 kind: hir::TraitItemKind::Fn(sig, _),
566 | hir::Node::ImplItem(hir::ImplItem {
568 kind: hir::ImplItemKind::Fn(sig, _),
571 err.span_label(ident.span, "");
573 sig.decl.output.span(),
574 "change this to return `FnMut` instead of `Fn`",
576 err.span_label(self.body.span, "in this closure");
584 fn suggest_ampmut_self<'tcx>(
586 local_decl: &mir::LocalDecl<'tcx>,
587 ) -> (Span, String) {
588 let sp = local_decl.source_info.span;
591 match tcx.sess.source_map().span_to_snippet(sp) {
593 let lt_pos = snippet.find('\'');
594 if let Some(lt_pos) = lt_pos {
595 format!("&{}mut self", &snippet[lt_pos..snippet.len() - 4])
597 "&mut self".to_string()
600 _ => "&mut self".to_string(),
605 // When we want to suggest a user change a local variable to be a `&mut`, there
606 // are three potential "obvious" things to highlight:
608 // let ident [: Type] [= RightHandSideExpression];
609 // ^^^^^ ^^^^ ^^^^^^^^^^^^^^^^^^^^^^^
612 // We can always fallback on highlighting the first. But chances are good that
613 // the user experience will be better if we highlight one of the others if possible;
614 // for example, if the RHS is present and the Type is not, then the type is going to
615 // be inferred *from* the RHS, which means we should highlight that (and suggest
616 // that they borrow the RHS mutably).
618 // This implementation attempts to emulate AST-borrowck prioritization
619 // by trying (3.), then (2.) and finally falling back on (1.).
620 fn suggest_ampmut<'tcx>(
622 local_decl: &mir::LocalDecl<'tcx>,
623 opt_assignment_rhs_span: Option<Span>,
624 opt_ty_info: Option<Span>,
625 ) -> (Span, String) {
626 if let Some(assignment_rhs_span) = opt_assignment_rhs_span {
627 if let Ok(src) = tcx.sess.source_map().span_to_snippet(assignment_rhs_span) {
628 if let (true, Some(ws_pos)) =
629 (src.starts_with("&'"), src.find(|c: char| -> bool { c.is_whitespace() }))
631 let lt_name = &src[1..ws_pos];
632 let ty = &src[ws_pos..];
633 return (assignment_rhs_span, format!("&{} mut {}", lt_name, ty));
634 } else if src.starts_with('&') {
635 let borrowed_expr = &src[1..];
636 return (assignment_rhs_span, format!("&mut {}", borrowed_expr));
641 let highlight_span = match opt_ty_info {
642 // if this is a variable binding with an explicit type,
643 // try to highlight that for the suggestion.
644 Some(ty_span) => ty_span,
646 // otherwise, just highlight the span associated with
647 // the (MIR) LocalDecl.
648 None => local_decl.source_info.span,
651 if let Ok(src) = tcx.sess.source_map().span_to_snippet(highlight_span) {
652 if let (true, Some(ws_pos)) =
653 (src.starts_with("&'"), src.find(|c: char| -> bool { c.is_whitespace() }))
655 let lt_name = &src[1..ws_pos];
656 let ty = &src[ws_pos..];
657 return (highlight_span, format!("&{} mut{}", lt_name, ty));
661 let ty_mut = local_decl.ty.builtin_deref(true).unwrap();
662 assert_eq!(ty_mut.mutbl, hir::Mutability::Not);
665 if local_decl.ty.is_region_ptr() {
666 format!("&mut {}", ty_mut.ty)
668 format!("*mut {}", ty_mut.ty)
673 fn is_closure_or_generator(ty: Ty<'_>) -> bool {
674 ty.is_closure() || ty.is_generator()
677 /// Adds a suggestion to a struct definition given a field access to a local.
678 /// This function expects the local to be a reference to a struct in order to produce a suggestion.
681 /// LL | s: &'a String
682 /// | ---------- use `&'a mut String` here to make mutable
684 fn annotate_struct_field(
688 ) -> Option<(Span, String)> {
689 // Expect our local to be a reference to a struct of some kind.
690 if let ty::Ref(_, ty, _) = ty.kind {
691 if let ty::Adt(def, _) = ty.kind {
692 let field = def.all_fields().nth(field.index())?;
693 // Use the HIR types to construct the diagnostic message.
694 let hir_id = tcx.hir().as_local_hir_id(field.did)?;
695 let node = tcx.hir().find(hir_id)?;
696 // Now we're dealing with the actual struct that we're going to suggest a change to,
697 // we can expect a field that is an immutable reference to a type.
698 if let hir::Node::Field(field) = node {
699 if let hir::TyKind::Rptr(
701 hir::MutTy { mutbl: hir::Mutability::Not, ref ty },
704 // Get the snippets in two parts - the named lifetime (if there is one) and
705 // type being referenced, that way we can reconstruct the snippet without loss
707 let type_snippet = tcx.sess.source_map().span_to_snippet(ty.span).ok()?;
708 let lifetime_snippet = if !lifetime.is_elided() {
709 format!("{} ", tcx.sess.source_map().span_to_snippet(lifetime.span).ok()?)
716 format!("&{}mut {}", lifetime_snippet, &*type_snippet,),
726 /// If possible, suggest replacing `ref` with `ref mut`.
727 fn suggest_ref_mut(tcx: TyCtxt<'_>, binding_span: Span) -> Option<String> {
728 let hi_src = tcx.sess.source_map().span_to_snippet(binding_span).ok()?;
729 if hi_src.starts_with("ref") && hi_src["ref".len()..].starts_with(rustc_lexer::is_whitespace) {
730 let replacement = format!("ref mut{}", &hi_src["ref".len()..]);