use rustc::hir; use rustc::hir::Node; use rustc::mir::{self, ClearCrossCrate, Local, LocalInfo, Location, ReadOnlyBodyCache}; use rustc::mir::{Mutability, Place, PlaceRef, PlaceBase, ProjectionElem}; use rustc::ty::{self, Ty, TyCtxt}; use rustc_index::vec::Idx; use syntax_pos::Span; use syntax_pos::symbol::kw; use crate::borrow_check::MirBorrowckCtxt; use crate::borrow_check::diagnostics::BorrowedContentSource; use crate::util::collect_writes::FindAssignments; use rustc_errors::Applicability; #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub(crate) enum AccessKind { MutableBorrow, Mutate, } impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> { pub(crate) fn report_mutability_error( &mut self, access_place: &Place<'tcx>, span: Span, the_place_err: PlaceRef<'cx, 'tcx>, error_access: AccessKind, location: Location, ) { debug!( "report_mutability_error(\ access_place={:?}, span={:?}, the_place_err={:?}, error_access={:?}, location={:?},\ )", access_place, span, the_place_err, error_access, location, ); let mut err; let item_msg; let reason; let mut opt_source = None; let access_place_desc = self.describe_place(access_place.as_ref()); debug!("report_mutability_error: access_place_desc={:?}", access_place_desc); match the_place_err { PlaceRef { base: PlaceBase::Local(local), projection: [], } => { item_msg = format!("`{}`", access_place_desc.unwrap()); if access_place.as_local().is_some() { reason = ", as it is not declared as mutable".to_string(); } else { let name = self.local_names[*local] .expect("immutable unnamed local"); reason = format!(", as `{}` is not declared as mutable", name); } } PlaceRef { base: _, projection: [proj_base @ .., ProjectionElem::Field(upvar_index, _)], } => { debug_assert!(is_closure_or_generator( Place::ty_from( &the_place_err.base, proj_base, *self.body, self.infcx.tcx ).ty)); item_msg = format!("`{}`", access_place_desc.unwrap()); if self.is_upvar_field_projection(access_place.as_ref()).is_some() { reason = ", as it is not declared as mutable".to_string(); } else { let name = self.upvars[upvar_index.index()].name; reason = format!(", as `{}` is not declared as mutable", name); } } PlaceRef { base: &PlaceBase::Local(local), projection: [ProjectionElem::Deref], } if self.body.local_decls[local].is_ref_for_guard() => { item_msg = format!("`{}`", access_place_desc.unwrap()); reason = ", as it is immutable for the pattern guard".to_string(); } PlaceRef { base: &PlaceBase::Local(local), projection: [ProjectionElem::Deref], } if self.body.local_decls[local].is_ref_to_static() => { if access_place.projection.len() == 1 { item_msg = format!("immutable static item `{}`", access_place_desc.unwrap()); reason = String::new(); } else { item_msg = format!("`{}`", access_place_desc.unwrap()); let local_info = &self.body.local_decls[local].local_info; if let LocalInfo::StaticRef { def_id, .. } = *local_info { let static_name = &self.infcx.tcx.item_name(def_id); reason = format!(", as `{}` is an immutable static item", static_name); } else { bug!("is_ref_to_static return true, but not ref to static?"); } } } PlaceRef { base: _, projection: [proj_base @ .., ProjectionElem::Deref], } => { if the_place_err.base == &PlaceBase::Local(Local::new(1)) && proj_base.is_empty() && !self.upvars.is_empty() { item_msg = format!("`{}`", access_place_desc.unwrap()); debug_assert!(self.body.local_decls[Local::new(1)].ty.is_region_ptr()); debug_assert!(is_closure_or_generator( Place::ty_from( the_place_err.base, the_place_err.projection, *self.body, self.infcx.tcx ) .ty )); reason = if self.is_upvar_field_projection(access_place.as_ref()).is_some() { ", as it is a captured variable in a `Fn` closure".to_string() } else { ", as `Fn` closures cannot mutate their captured variables".to_string() } } else { let source = self.borrowed_content_source(PlaceRef { base: the_place_err.base, projection: proj_base, }); let pointer_type = source.describe_for_immutable_place(); opt_source = Some(source); if let Some(desc) = access_place_desc { item_msg = format!("`{}`", desc); reason = match error_access { AccessKind::Mutate => format!(" which is behind {}", pointer_type), AccessKind::MutableBorrow => { format!(", as it is behind {}", pointer_type) } } } else { item_msg = format!("data in {}", pointer_type); reason = String::new(); } } } PlaceRef { base: PlaceBase::Static(_), .. } | PlaceRef { base: _, projection: [.., ProjectionElem::Index(_)], } | PlaceRef { base: _, projection: [.., ProjectionElem::ConstantIndex { .. }], } | PlaceRef { base: _, projection: [.., ProjectionElem::Subslice { .. }], } | PlaceRef { base: _, projection: [.., ProjectionElem::Downcast(..)], } => bug!("Unexpected immutable place."), } debug!("report_mutability_error: item_msg={:?}, reason={:?}", item_msg, reason); // `act` and `acted_on` are strings that let us abstract over // the verbs used in some diagnostic messages. let act; let acted_on; let span = match error_access { AccessKind::Mutate => { err = self.cannot_assign(span, &(item_msg + &reason)); act = "assign"; acted_on = "written"; span } AccessKind::MutableBorrow => { act = "borrow as mutable"; acted_on = "borrowed as mutable"; let borrow_spans = self.borrow_spans(span, location); let borrow_span = borrow_spans.args_or_use(); err = self.cannot_borrow_path_as_mutable_because( borrow_span, &item_msg, &reason, ); borrow_spans.var_span_label( &mut err, format!( "mutable borrow occurs due to use of `{}` in closure", // always Some() if the message is printed. self.describe_place(access_place.as_ref()).unwrap_or_default(), ) ); borrow_span } }; debug!("report_mutability_error: act={:?}, acted_on={:?}", act, acted_on); match the_place_err { // Suggest making an existing shared borrow in a struct definition a mutable borrow. // // This is applicable when we have a deref of a field access to a deref of a local - // something like `*((*_1).0`. The local that we get will be a reference to the // struct we've got a field access of (it must be a reference since there's a deref // after the field access). PlaceRef { base, projection: [proj_base @ .., ProjectionElem::Deref, ProjectionElem::Field(field, _), ProjectionElem::Deref, ], } => { err.span_label(span, format!("cannot {ACT}", ACT = act)); if let Some((span, message)) = annotate_struct_field( self.infcx.tcx, Place::ty_from(base, proj_base, *self.body, self.infcx.tcx).ty, field, ) { err.span_suggestion( span, "consider changing this to be mutable", message, Applicability::MaybeIncorrect, ); } }, // Suggest removing a `&mut` from the use of a mutable reference. PlaceRef { base: PlaceBase::Local(local), projection: [], } if { self.body.local_decls.get(*local).map(|local_decl| { if let LocalInfo::User(ClearCrossCrate::Set( mir::BindingForm::ImplicitSelf(kind) )) = local_decl.local_info { // Check if the user variable is a `&mut self` and we can therefore // suggest removing the `&mut`. // // Deliberately fall into this case for all implicit self types, // so that we don't fall in to the next case with them. kind == mir::ImplicitSelfKind::MutRef } else if Some(kw::SelfLower) == self.local_names[*local] { // Otherwise, check if the name is the self kewyord - in which case // we have an explicit self. Do the same thing in this case and check // for a `self: &mut Self` to suggest removing the `&mut`. if let ty::Ref( _, _, hir::Mutability::Mutable ) = local_decl.ty.kind { true } else { false } } else { false } }).unwrap_or(false) } => { err.span_label(span, format!("cannot {ACT}", ACT = act)); err.span_label(span, "try removing `&mut` here"); }, // We want to suggest users use `let mut` for local (user // variable) mutations... PlaceRef { base: PlaceBase::Local(local), projection: [], } if self.body.local_decls[*local].can_be_made_mutable() => { // ... but it doesn't make sense to suggest it on // variables that are `ref x`, `ref mut x`, `&self`, // or `&mut self` (such variables are simply not // mutable). let local_decl = &self.body.local_decls[*local]; assert_eq!(local_decl.mutability, Mutability::Not); err.span_label(span, format!("cannot {ACT}", ACT = act)); err.span_suggestion( local_decl.source_info.span, "consider changing this to be mutable", format!("mut {}", self.local_names[*local].unwrap()), Applicability::MachineApplicable, ); } // Also suggest adding mut for upvars PlaceRef { base, projection: [proj_base @ .., ProjectionElem::Field(upvar_index, _)], } => { debug_assert!(is_closure_or_generator( Place::ty_from(base, proj_base, *self.body, self.infcx.tcx).ty )); err.span_label(span, format!("cannot {ACT}", ACT = act)); let upvar_hir_id = self.upvars[upvar_index.index()].var_hir_id; if let Some(Node::Binding(pat)) = self.infcx.tcx.hir().find(upvar_hir_id) { if let hir::PatKind::Binding( hir::BindingAnnotation::Unannotated, _, upvar_ident, _, ) = pat.kind { err.span_suggestion( upvar_ident.span, "consider changing this to be mutable", format!("mut {}", upvar_ident.name), Applicability::MachineApplicable, ); } } } // complete hack to approximate old AST-borrowck // diagnostic: if the span starts with a mutable borrow of // a local variable, then just suggest the user remove it. PlaceRef { base: PlaceBase::Local(_), projection: [], } if { if let Ok(snippet) = self.infcx.tcx.sess.source_map().span_to_snippet(span) { snippet.starts_with("&mut ") } else { false } } => { err.span_label(span, format!("cannot {ACT}", ACT = act)); err.span_label(span, "try removing `&mut` here"); } PlaceRef { base: PlaceBase::Local(local), projection: [ProjectionElem::Deref], } if self.body.local_decls[*local].is_ref_for_guard() => { err.span_label(span, format!("cannot {ACT}", ACT = act)); err.note( "variables bound in patterns are immutable until the end of the pattern guard", ); } // We want to point out when a `&` can be readily replaced // with an `&mut`. // // FIXME: can this case be generalized to work for an // arbitrary base for the projection? PlaceRef { base: PlaceBase::Local(local), projection: [ProjectionElem::Deref], } if self.body.local_decls[*local].is_user_variable() => { let local_decl = &self.body.local_decls[*local]; let suggestion = match local_decl.local_info { LocalInfo::User(ClearCrossCrate::Set(mir::BindingForm::ImplicitSelf(_))) => { Some(suggest_ampmut_self(self.infcx.tcx, local_decl)) } LocalInfo::User(ClearCrossCrate::Set(mir::BindingForm::Var( mir::VarBindingForm { binding_mode: ty::BindingMode::BindByValue(_), opt_ty_info, .. }, ))) => Some(suggest_ampmut( self.infcx.tcx, self.body, *local, local_decl, opt_ty_info, )), LocalInfo::User(ClearCrossCrate::Set(mir::BindingForm::Var( mir::VarBindingForm { binding_mode: ty::BindingMode::BindByReference(_), .. }, ))) => { let pattern_span = local_decl.source_info.span; suggest_ref_mut(self.infcx.tcx, pattern_span) .map(|replacement| (pattern_span, replacement)) } LocalInfo::User(ClearCrossCrate::Clear) => bug!("saw cleared local state"), _ => unreachable!(), }; let (pointer_sigil, pointer_desc) = if local_decl.ty.is_region_ptr() { ("&", "reference") } else { ("*const", "pointer") }; if let Some((err_help_span, suggested_code)) = suggestion { err.span_suggestion( err_help_span, &format!("consider changing this to be a mutable {}", pointer_desc), suggested_code, Applicability::MachineApplicable, ); } match self.local_names[*local] { Some(name) if !local_decl.from_compiler_desugaring() => { err.span_label( span, format!( "`{NAME}` is a `{SIGIL}` {DESC}, \ so the data it refers to cannot be {ACTED_ON}", NAME = name, SIGIL = pointer_sigil, DESC = pointer_desc, ACTED_ON = acted_on ), ); } _ => { err.span_label( span, format!( "cannot {ACT} through `{SIGIL}` {DESC}", ACT = act, SIGIL = pointer_sigil, DESC = pointer_desc ), ); } } } PlaceRef { base, projection: [ProjectionElem::Deref], // FIXME document what is this 1 magic number about } if *base == PlaceBase::Local(Local::new(1)) && !self.upvars.is_empty() => { err.span_label(span, format!("cannot {ACT}", ACT = act)); err.span_help( self.body.span, "consider changing this to accept closures that implement `FnMut`" ); } PlaceRef { base: _, projection: [.., ProjectionElem::Deref], } => { err.span_label(span, format!("cannot {ACT}", ACT = act)); match opt_source { Some(BorrowedContentSource::OverloadedDeref(ty)) => { err.help( &format!( "trait `DerefMut` is required to modify through a dereference, \ but it is not implemented for `{}`", ty, ), ); }, Some(BorrowedContentSource::OverloadedIndex(ty)) => { err.help( &format!( "trait `IndexMut` is required to modify indexed content, \ but it is not implemented for `{}`", ty, ), ); } _ => (), } } _ => { err.span_label(span, format!("cannot {ACT}", ACT = act)); } } err.buffer(&mut self.errors_buffer); } } fn suggest_ampmut_self<'tcx>( tcx: TyCtxt<'tcx>, local_decl: &mir::LocalDecl<'tcx>, ) -> (Span, String) { let sp = local_decl.source_info.span; (sp, match tcx.sess.source_map().span_to_snippet(sp) { Ok(snippet) => { let lt_pos = snippet.find('\''); if let Some(lt_pos) = lt_pos { format!("&{}mut self", &snippet[lt_pos..snippet.len() - 4]) } else { "&mut self".to_string() } } _ => "&mut self".to_string() }) } // When we want to suggest a user change a local variable to be a `&mut`, there // are three potential "obvious" things to highlight: // // let ident [: Type] [= RightHandSideExpression]; // ^^^^^ ^^^^ ^^^^^^^^^^^^^^^^^^^^^^^ // (1.) (2.) (3.) // // We can always fallback on highlighting the first. But chances are good that // the user experience will be better if we highlight one of the others if possible; // for example, if the RHS is present and the Type is not, then the type is going to // be inferred *from* the RHS, which means we should highlight that (and suggest // that they borrow the RHS mutably). // // This implementation attempts to emulate AST-borrowck prioritization // by trying (3.), then (2.) and finally falling back on (1.). fn suggest_ampmut<'tcx>( tcx: TyCtxt<'tcx>, body: ReadOnlyBodyCache<'_, 'tcx>, local: Local, local_decl: &mir::LocalDecl<'tcx>, opt_ty_info: Option, ) -> (Span, String) { let locations = body.find_assignments(local); if !locations.is_empty() { let assignment_rhs_span = body.source_info(locations[0]).span; if let Ok(src) = tcx.sess.source_map().span_to_snippet(assignment_rhs_span) { if let (true, Some(ws_pos)) = ( src.starts_with("&'"), src.find(|c: char| -> bool { c.is_whitespace() }), ) { let lt_name = &src[1..ws_pos]; let ty = &src[ws_pos..]; return (assignment_rhs_span, format!("&{} mut {}", lt_name, ty)); } else if src.starts_with('&') { let borrowed_expr = &src[1..]; return (assignment_rhs_span, format!("&mut {}", borrowed_expr)); } } } let highlight_span = match opt_ty_info { // if this is a variable binding with an explicit type, // try to highlight that for the suggestion. Some(ty_span) => ty_span, // otherwise, just highlight the span associated with // the (MIR) LocalDecl. None => local_decl.source_info.span, }; if let Ok(src) = tcx.sess.source_map().span_to_snippet(highlight_span) { if let (true, Some(ws_pos)) = ( src.starts_with("&'"), src.find(|c: char| -> bool { c.is_whitespace() }), ) { let lt_name = &src[1..ws_pos]; let ty = &src[ws_pos..]; return (highlight_span, format!("&{} mut{}", lt_name, ty)); } } let ty_mut = local_decl.ty.builtin_deref(true).unwrap(); assert_eq!(ty_mut.mutbl, hir::Mutability::Immutable); (highlight_span, if local_decl.ty.is_region_ptr() { format!("&mut {}", ty_mut.ty) } else { format!("*mut {}", ty_mut.ty) }) } fn is_closure_or_generator(ty: Ty<'_>) -> bool { ty.is_closure() || ty.is_generator() } /// Adds a suggestion to a struct definition given a field access to a local. /// This function expects the local to be a reference to a struct in order to produce a suggestion. /// /// ```text /// LL | s: &'a String /// | ---------- use `&'a mut String` here to make mutable /// ``` fn annotate_struct_field( tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, field: &mir::Field, ) -> Option<(Span, String)> { // Expect our local to be a reference to a struct of some kind. if let ty::Ref(_, ty, _) = ty.kind { if let ty::Adt(def, _) = ty.kind { let field = def.all_fields().nth(field.index())?; // Use the HIR types to construct the diagnostic message. let hir_id = tcx.hir().as_local_hir_id(field.did)?; let node = tcx.hir().find(hir_id)?; // Now we're dealing with the actual struct that we're going to suggest a change to, // we can expect a field that is an immutable reference to a type. if let hir::Node::Field(field) = node { if let hir::TyKind::Rptr(lifetime, hir::MutTy { mutbl: hir::Mutability::Immutable, ref ty }) = field.ty.kind { // Get the snippets in two parts - the named lifetime (if there is one) and // type being referenced, that way we can reconstruct the snippet without loss // of detail. let type_snippet = tcx.sess.source_map().span_to_snippet(ty.span).ok()?; let lifetime_snippet = if !lifetime.is_elided() { format!("{} ", tcx.sess.source_map().span_to_snippet(lifetime.span).ok()?) } else { String::new() }; return Some(( field.ty.span, format!( "&{}mut {}", lifetime_snippet, &*type_snippet, ), )); } } } } None } /// If possible, suggest replacing `ref` with `ref mut`. fn suggest_ref_mut(tcx: TyCtxt<'_>, binding_span: Span) -> Option { let hi_src = tcx.sess.source_map().span_to_snippet(binding_span).ok()?; if hi_src.starts_with("ref") && hi_src["ref".len()..].starts_with(rustc_lexer::is_whitespace) { let replacement = format!("ref mut{}", &hi_src["ref".len()..]); Some(replacement) } else { None } }