diff options
Diffstat (limited to 'compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs')
-rw-r--r-- | compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs | 1115 |
1 files changed, 1115 insertions, 0 deletions
diff --git a/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs b/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs new file mode 100644 index 000000000..0ad4abbce --- /dev/null +++ b/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs @@ -0,0 +1,1115 @@ +use rustc_hir as hir; +use rustc_hir::Node; +use rustc_middle::hir::map::Map; +use rustc_middle::mir::{Mutability, Place, PlaceRef, ProjectionElem}; +use rustc_middle::ty::{self, Ty, TyCtxt}; +use rustc_middle::{ + hir::place::PlaceBase, + mir::{ + self, BindingForm, ClearCrossCrate, ImplicitSelfKind, Local, LocalDecl, LocalInfo, + LocalKind, Location, + }, +}; +use rustc_span::source_map::DesugaringKind; +use rustc_span::symbol::{kw, Symbol}; +use rustc_span::{BytePos, Span}; + +use crate::diagnostics::BorrowedContentSource; +use crate::MirBorrowckCtxt; +use rustc_const_eval::util::collect_writes::FindAssignments; +use rustc_errors::{Applicability, Diagnostic}; + +#[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<'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_any_place(access_place.as_ref()); + debug!("report_mutability_error: access_place_desc={:?}", access_place_desc); + + match the_place_err { + PlaceRef { local, projection: [] } => { + item_msg = access_place_desc; + 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 `{name}` is not declared as mutable"); + } + } + + PlaceRef { + local, + projection: [proj_base @ .., ProjectionElem::Field(upvar_index, _)], + } => { + debug_assert!(is_closure_or_generator( + Place::ty_from(local, proj_base, self.body, self.infcx.tcx).ty + )); + + let imm_borrow_derefed = self.upvars[upvar_index.index()] + .place + .place + .deref_tys() + .any(|ty| matches!(ty.kind(), ty::Ref(.., hir::Mutability::Not))); + + // If the place is immutable then: + // + // - Either we deref an immutable ref to get to our final place. + // - We don't capture derefs of raw ptrs + // - Or the final place is immut because the root variable of the capture + // isn't marked mut and we should suggest that to the user. + if imm_borrow_derefed { + // If we deref an immutable ref then the suggestion here doesn't help. + return; + } else { + item_msg = access_place_desc; + 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()].place.to_string(self.infcx.tcx); + reason = format!(", as `{name}` is not declared as mutable"); + } + } + } + + PlaceRef { local, projection: [ProjectionElem::Deref] } + if self.body.local_decls[local].is_ref_for_guard() => + { + item_msg = access_place_desc; + reason = ", as it is immutable for the pattern guard".to_string(); + } + PlaceRef { 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}"); + reason = String::new(); + } else { + item_msg = access_place_desc; + let local_info = &self.body.local_decls[local].local_info; + if let Some(box LocalInfo::StaticRef { def_id, .. }) = *local_info { + let static_name = &self.infcx.tcx.item_name(def_id); + reason = format!(", as `{static_name}` is an immutable static item"); + } else { + bug!("is_ref_to_static return true, but not ref to static?"); + } + } + } + PlaceRef { local: _, projection: [proj_base @ .., ProjectionElem::Deref] } => { + if the_place_err.local == ty::CAPTURE_STRUCT_LOCAL + && proj_base.is_empty() + && !self.upvars.is_empty() + { + item_msg = access_place_desc; + debug_assert!( + self.body.local_decls[ty::CAPTURE_STRUCT_LOCAL].ty.is_region_ptr() + ); + debug_assert!(is_closure_or_generator( + Place::ty_from( + the_place_err.local, + 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 { + local: the_place_err.local, + projection: proj_base, + }); + let pointer_type = source.describe_for_immutable_place(self.infcx.tcx); + opt_source = Some(source); + if let Some(desc) = self.describe_place(access_place.as_ref()) { + 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 { + local: _, + projection: + [ + .., + ProjectionElem::Index(_) + | ProjectionElem::ConstantIndex { .. } + | ProjectionElem::Subslice { .. } + | 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", + self.describe_any_place(access_place.as_ref()), + ), + "mutable", + ); + 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 { + local, + projection: + &[ + ref proj_base @ .., + ProjectionElem::Deref, + ProjectionElem::Field(field, _), + ProjectionElem::Deref, + ], + } => { + err.span_label(span, format!("cannot {ACT}", ACT = act)); + + if let Some(span) = get_mut_span_in_struct_field( + self.infcx.tcx, + Place::ty_from(local, proj_base, self.body, self.infcx.tcx).ty, + field, + ) { + err.span_suggestion_verbose( + span, + "consider changing this to be mutable", + " mut ", + Applicability::MaybeIncorrect, + ); + } + } + + // Suggest removing a `&mut` from the use of a mutable reference. + PlaceRef { local, projection: [] } + if self + .body + .local_decls + .get(local) + .map(|l| mut_borrow_of_mutable_ref(l, self.local_names[local])) + .unwrap_or(false) => + { + let decl = &self.body.local_decls[local]; + err.span_label(span, format!("cannot {ACT}", ACT = act)); + if let Some(mir::Statement { + source_info, + kind: + mir::StatementKind::Assign(box ( + _, + mir::Rvalue::Ref( + _, + mir::BorrowKind::Mut { allow_two_phase_borrow: false }, + _, + ), + )), + .. + }) = &self.body[location.block].statements.get(location.statement_index) + { + match decl.local_info { + Some(box LocalInfo::User(ClearCrossCrate::Set(BindingForm::Var( + mir::VarBindingForm { + binding_mode: ty::BindingMode::BindByValue(Mutability::Not), + opt_ty_info: Some(sp), + opt_match_place: _, + pat_span: _, + }, + )))) => { + err.span_note(sp, "the binding is already a mutable borrow"); + } + _ => { + err.span_note( + decl.source_info.span, + "the binding is already a mutable borrow", + ); + } + } + if let Ok(snippet) = + self.infcx.tcx.sess.source_map().span_to_snippet(source_info.span) + { + if snippet.starts_with("&mut ") { + // We don't have access to the HIR to get accurate spans, but we can + // give a best effort structured suggestion. + err.span_suggestion_verbose( + source_info.span.with_hi(source_info.span.lo() + BytePos(5)), + "try removing `&mut` here", + "", + Applicability::MachineApplicable, + ); + } else { + // This can occur with things like `(&mut self).foo()`. + err.span_help(source_info.span, "try removing `&mut` here"); + } + } else { + err.span_help(source_info.span, "try removing `&mut` here"); + } + } else if decl.mutability == Mutability::Not + && !matches!( + decl.local_info, + Some(box LocalInfo::User(ClearCrossCrate::Set(BindingForm::ImplicitSelf( + ImplicitSelfKind::MutRef + )))) + ) + { + err.span_suggestion_verbose( + decl.source_info.span.shrink_to_lo(), + "consider making the binding mutable", + "mut ", + Applicability::MachineApplicable, + ); + } + } + + // We want to suggest users use `let mut` for local (user + // variable) mutations... + PlaceRef { 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, + ); + let tcx = self.infcx.tcx; + if let ty::Closure(id, _) = *the_place_err.ty(self.body, tcx).ty.kind() { + self.show_mutating_upvar(tcx, id.expect_local(), the_place_err, &mut err); + } + } + + // Also suggest adding mut for upvars + PlaceRef { + local, + projection: [proj_base @ .., ProjectionElem::Field(upvar_index, _)], + } => { + debug_assert!(is_closure_or_generator( + Place::ty_from(local, proj_base, self.body, self.infcx.tcx).ty + )); + + let captured_place = &self.upvars[upvar_index.index()].place; + + err.span_label(span, format!("cannot {ACT}", ACT = act)); + + let upvar_hir_id = captured_place.get_root_variable(); + + if let Some(Node::Pat(pat)) = self.infcx.tcx.hir().find(upvar_hir_id) + && 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, + ); + } + + let tcx = self.infcx.tcx; + if let ty::Ref(_, ty, Mutability::Mut) = the_place_err.ty(self.body, tcx).ty.kind() + && let ty::Closure(id, _) = *ty.kind() + { + self.show_mutating_upvar(tcx, id.expect_local(), the_place_err, &mut err); + } + } + + // 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 { 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_suggestion( + span, + "try removing `&mut` here", + "", + Applicability::MaybeIncorrect, + ); + } + + PlaceRef { 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 { local, projection: [ProjectionElem::Deref] } + if self.body.local_decls[local].is_user_variable() => + { + let local_decl = &self.body.local_decls[local]; + + let (pointer_sigil, pointer_desc) = if local_decl.ty.is_region_ptr() { + ("&", "reference") + } else { + ("*const", "pointer") + }; + + match self.local_names[local] { + Some(name) if !local_decl.from_compiler_desugaring() => { + let label = match local_decl.local_info.as_deref().unwrap() { + LocalInfo::User(ClearCrossCrate::Set( + mir::BindingForm::ImplicitSelf(_), + )) => { + let (span, suggestion) = + suggest_ampmut_self(self.infcx.tcx, local_decl); + Some((true, span, suggestion)) + } + + LocalInfo::User(ClearCrossCrate::Set(mir::BindingForm::Var( + mir::VarBindingForm { + binding_mode: ty::BindingMode::BindByValue(_), + opt_ty_info, + .. + }, + ))) => { + // check if the RHS is from desugaring + let opt_assignment_rhs_span = + self.body.find_assignments(local).first().map(|&location| { + if let Some(mir::Statement { + source_info: _, + kind: + mir::StatementKind::Assign(box ( + _, + mir::Rvalue::Use(mir::Operand::Copy(place)), + )), + }) = self.body[location.block] + .statements + .get(location.statement_index) + { + self.body.local_decls[place.local].source_info.span + } else { + self.body.source_info(location).span + } + }); + match opt_assignment_rhs_span.and_then(|s| s.desugaring_kind()) { + // on for loops, RHS points to the iterator part + Some(DesugaringKind::ForLoop) => { + self.suggest_similar_mut_method_for_for_loop(&mut err); + err.span_label(opt_assignment_rhs_span.unwrap(), format!( + "this iterator yields `{pointer_sigil}` {pointer_desc}s", + )); + None + } + // don't create labels for compiler-generated spans + Some(_) => None, + None => { + let label = if name != kw::SelfLower { + suggest_ampmut( + self.infcx.tcx, + local_decl, + opt_assignment_rhs_span, + *opt_ty_info, + ) + } else { + match local_decl.local_info.as_deref() { + Some(LocalInfo::User(ClearCrossCrate::Set( + mir::BindingForm::Var(mir::VarBindingForm { + opt_ty_info: None, + .. + }), + ))) => { + let (span, sugg) = suggest_ampmut_self( + self.infcx.tcx, + local_decl, + ); + (true, span, sugg) + } + // explicit self (eg `self: &'a Self`) + _ => suggest_ampmut( + self.infcx.tcx, + local_decl, + opt_assignment_rhs_span, + *opt_ty_info, + ), + } + }; + Some(label) + } + } + } + + 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| (true, pattern_span, replacement)) + } + + LocalInfo::User(ClearCrossCrate::Clear) => { + bug!("saw cleared local state") + } + + _ => unreachable!(), + }; + + match label { + Some((true, err_help_span, suggested_code)) => { + let (is_trait_sig, local_trait) = self.is_error_in_trait(local); + if !is_trait_sig { + err.span_suggestion( + err_help_span, + &format!( + "consider changing this to be a mutable {pointer_desc}" + ), + suggested_code, + Applicability::MachineApplicable, + ); + } else if let Some(x) = local_trait { + err.span_suggestion( + x, + &format!( + "consider changing that to be a mutable {pointer_desc}" + ), + suggested_code, + Applicability::MachineApplicable, + ); + } + } + Some((false, err_label_span, message)) => { + err.span_label( + err_label_span, + &format!( + "consider changing this binding's type to be: `{message}`" + ), + ); + } + None => {} + } + 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 { local, projection: [ProjectionElem::Deref] } + if local == ty::CAPTURE_STRUCT_LOCAL && !self.upvars.is_empty() => + { + self.expected_fn_found_fn_mut_call(&mut err, span, act); + } + + PlaceRef { local: _, 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)); + } + } + + self.buffer_error(err); + } + + /// User cannot make signature of a trait mutable without changing the + /// trait. So we find if this error belongs to a trait and if so we move + /// suggestion to the trait or disable it if it is out of scope of this crate + fn is_error_in_trait(&self, local: Local) -> (bool, Option<Span>) { + if self.body.local_kind(local) != LocalKind::Arg { + return (false, None); + } + let hir_map = self.infcx.tcx.hir(); + let my_def = self.body.source.def_id(); + let my_hir = hir_map.local_def_id_to_hir_id(my_def.as_local().unwrap()); + let Some(td) = + self.infcx.tcx.impl_of_method(my_def).and_then(|x| self.infcx.tcx.trait_id_of_impl(x)) + else { + return (false, None); + }; + ( + true, + td.as_local().and_then(|tld| match hir_map.find_by_def_id(tld) { + Some(Node::Item(hir::Item { + kind: hir::ItemKind::Trait(_, _, _, _, items), + .. + })) => { + let mut f_in_trait_opt = None; + for hir::TraitItemRef { id: fi, kind: k, .. } in *items { + let hi = fi.hir_id(); + if !matches!(k, hir::AssocItemKind::Fn { .. }) { + continue; + } + if hir_map.name(hi) != hir_map.name(my_hir) { + continue; + } + f_in_trait_opt = Some(hi); + break; + } + f_in_trait_opt.and_then(|f_in_trait| match hir_map.find(f_in_trait) { + Some(Node::TraitItem(hir::TraitItem { + kind: + hir::TraitItemKind::Fn( + hir::FnSig { decl: hir::FnDecl { inputs, .. }, .. }, + _, + ), + .. + })) => { + let hir::Ty { span, .. } = inputs[local.index() - 1]; + Some(span) + } + _ => None, + }) + } + _ => None, + }), + ) + } + + // point to span of upvar making closure call require mutable borrow + fn show_mutating_upvar( + &self, + tcx: TyCtxt<'_>, + closure_local_def_id: hir::def_id::LocalDefId, + the_place_err: PlaceRef<'tcx>, + err: &mut Diagnostic, + ) { + let tables = tcx.typeck(closure_local_def_id); + let closure_hir_id = tcx.hir().local_def_id_to_hir_id(closure_local_def_id); + if let Some((span, closure_kind_origin)) = + &tables.closure_kind_origins().get(closure_hir_id) + { + let reason = if let PlaceBase::Upvar(upvar_id) = closure_kind_origin.base { + let upvar = ty::place_to_string_for_capture(tcx, closure_kind_origin); + let root_hir_id = upvar_id.var_path.hir_id; + // we have an origin for this closure kind starting at this root variable so it's safe to unwrap here + let captured_places = + tables.closure_min_captures[&closure_local_def_id].get(&root_hir_id).unwrap(); + + let origin_projection = closure_kind_origin + .projections + .iter() + .map(|proj| proj.kind) + .collect::<Vec<_>>(); + let mut capture_reason = String::new(); + for captured_place in captured_places { + let captured_place_kinds = captured_place + .place + .projections + .iter() + .map(|proj| proj.kind) + .collect::<Vec<_>>(); + if rustc_middle::ty::is_ancestor_or_same_capture( + &captured_place_kinds, + &origin_projection, + ) { + match captured_place.info.capture_kind { + ty::UpvarCapture::ByRef( + ty::BorrowKind::MutBorrow | ty::BorrowKind::UniqueImmBorrow, + ) => { + capture_reason = format!("mutable borrow of `{upvar}`"); + } + ty::UpvarCapture::ByValue => { + capture_reason = format!("possible mutation of `{upvar}`"); + } + _ => bug!("upvar `{upvar}` borrowed, but not mutably"), + } + break; + } + } + if capture_reason.is_empty() { + bug!("upvar `{upvar}` borrowed, but cannot find reason"); + } + capture_reason + } else { + bug!("not an upvar") + }; + err.span_label( + *span, + format!( + "calling `{}` requires mutable binding due to {}", + self.describe_place(the_place_err).unwrap(), + reason + ), + ); + } + } + + // Attempt to search similar mutable associated items for suggestion. + // In the future, attempt in all path but initially for RHS of for_loop + fn suggest_similar_mut_method_for_for_loop(&self, err: &mut Diagnostic) { + use hir::{ + BodyId, Expr, + ExprKind::{Block, Call, DropTemps, Match, MethodCall}, + HirId, ImplItem, ImplItemKind, Item, ItemKind, + }; + + fn maybe_body_id_of_fn(hir_map: Map<'_>, id: HirId) -> Option<BodyId> { + match hir_map.find(id) { + Some(Node::Item(Item { kind: ItemKind::Fn(_, _, body_id), .. })) + | Some(Node::ImplItem(ImplItem { kind: ImplItemKind::Fn(_, body_id), .. })) => { + Some(*body_id) + } + _ => None, + } + } + let hir_map = self.infcx.tcx.hir(); + let mir_body_hir_id = self.mir_hir_id(); + if let Some(fn_body_id) = maybe_body_id_of_fn(hir_map, mir_body_hir_id) { + if let Block( + hir::Block { + expr: + Some(Expr { + kind: + DropTemps(Expr { + kind: + Match( + Expr { + kind: + Call( + _, + [ + Expr { + kind: + MethodCall( + path_segment, + _args, + span, + ), + hir_id, + .. + }, + .., + ], + ), + .. + }, + .., + ), + .. + }), + .. + }), + .. + }, + _, + ) = hir_map.body(fn_body_id).value.kind + { + let opt_suggestions = path_segment + .hir_id + .map(|path_hir_id| self.infcx.tcx.typeck(path_hir_id.owner)) + .and_then(|typeck| typeck.type_dependent_def_id(*hir_id)) + .and_then(|def_id| self.infcx.tcx.impl_of_method(def_id)) + .map(|def_id| self.infcx.tcx.associated_items(def_id)) + .map(|assoc_items| { + assoc_items + .in_definition_order() + .map(|assoc_item_def| assoc_item_def.ident(self.infcx.tcx)) + .filter(|&ident| { + let original_method_ident = path_segment.ident; + original_method_ident != ident + && ident + .as_str() + .starts_with(&original_method_ident.name.to_string()) + }) + .map(|ident| format!("{ident}()")) + .peekable() + }); + + if let Some(mut suggestions) = opt_suggestions + && suggestions.peek().is_some() + { + err.span_suggestions( + *span, + "use mutable method", + suggestions, + Applicability::MaybeIncorrect, + ); + } + } + }; + } + + /// Targeted error when encountering an `FnMut` closure where an `Fn` closure was expected. + fn expected_fn_found_fn_mut_call(&self, err: &mut Diagnostic, sp: Span, act: &str) { + err.span_label(sp, format!("cannot {act}")); + + let hir = self.infcx.tcx.hir(); + let closure_id = self.mir_hir_id(); + let fn_call_id = hir.get_parent_node(closure_id); + let node = hir.get(fn_call_id); + let def_id = hir.enclosing_body_owner(fn_call_id); + let mut look_at_return = true; + // If we can detect the expression to be an `fn` call where the closure was an argument, + // we point at the `fn` definition argument... + if let hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Call(func, args), .. }) = node { + let arg_pos = args + .iter() + .enumerate() + .filter(|(_, arg)| arg.hir_id == closure_id) + .map(|(pos, _)| pos) + .next(); + let tables = self.infcx.tcx.typeck(def_id); + if let Some(ty::FnDef(def_id, _)) = + tables.node_type_opt(func.hir_id).as_ref().map(|ty| ty.kind()) + { + let arg = match hir.get_if_local(*def_id) { + Some( + hir::Node::Item(hir::Item { + ident, kind: hir::ItemKind::Fn(sig, ..), .. + }) + | hir::Node::TraitItem(hir::TraitItem { + ident, + kind: hir::TraitItemKind::Fn(sig, _), + .. + }) + | hir::Node::ImplItem(hir::ImplItem { + ident, + kind: hir::ImplItemKind::Fn(sig, _), + .. + }), + ) => Some( + arg_pos + .and_then(|pos| { + sig.decl.inputs.get( + pos + if sig.decl.implicit_self.has_implicit_self() { + 1 + } else { + 0 + }, + ) + }) + .map(|arg| arg.span) + .unwrap_or(ident.span), + ), + _ => None, + }; + if let Some(span) = arg { + err.span_label(span, "change this to accept `FnMut` instead of `Fn`"); + err.span_label(func.span, "expects `Fn` instead of `FnMut`"); + err.span_label(self.body.span, "in this closure"); + look_at_return = false; + } + } + } + + if look_at_return && hir.get_return_block(closure_id).is_some() { + // ...otherwise we are probably in the tail expression of the function, point at the + // return type. + match hir.get_by_def_id(hir.get_parent_item(fn_call_id)) { + hir::Node::Item(hir::Item { ident, kind: hir::ItemKind::Fn(sig, ..), .. }) + | hir::Node::TraitItem(hir::TraitItem { + ident, + kind: hir::TraitItemKind::Fn(sig, _), + .. + }) + | hir::Node::ImplItem(hir::ImplItem { + ident, + kind: hir::ImplItemKind::Fn(sig, _), + .. + }) => { + err.span_label(ident.span, ""); + err.span_label( + sig.decl.output.span(), + "change this to return `FnMut` instead of `Fn`", + ); + err.span_label(self.body.span, "in this closure"); + } + _ => {} + } + } + } +} + +fn mut_borrow_of_mutable_ref(local_decl: &LocalDecl<'_>, local_name: Option<Symbol>) -> bool { + debug!("local_info: {:?}, ty.kind(): {:?}", local_decl.local_info, local_decl.ty.kind()); + + match local_decl.local_info.as_deref() { + // Check if mutably borrowing a mutable reference. + Some(LocalInfo::User(ClearCrossCrate::Set(mir::BindingForm::Var( + mir::VarBindingForm { + binding_mode: ty::BindingMode::BindByValue(Mutability::Not), .. + }, + )))) => matches!(local_decl.ty.kind(), ty::Ref(_, _, hir::Mutability::Mut)), + Some(LocalInfo::User(ClearCrossCrate::Set(mir::BindingForm::ImplicitSelf(kind)))) => { + // 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 + } + _ if Some(kw::SelfLower) == local_name => { + // Otherwise, check if the name is the `self` keyword - 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`. + matches!(local_decl.ty.kind(), ty::Ref(_, _, hir::Mutability::Mut)) + } + _ => false, + } +} + +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>, + local_decl: &mir::LocalDecl<'tcx>, + opt_assignment_rhs_span: Option<Span>, + opt_ty_info: Option<Span>, +) -> (bool, Span, String) { + if let Some(assignment_rhs_span) = opt_assignment_rhs_span + && let Ok(src) = tcx.sess.source_map().span_to_snippet(assignment_rhs_span) + { + let is_mutbl = |ty: &str| -> bool { + if let Some(rest) = ty.strip_prefix("mut") { + match rest.chars().next() { + // e.g. `&mut x` + Some(c) if c.is_whitespace() => true, + // e.g. `&mut(x)` + Some('(') => true, + // e.g. `&mut{x}` + Some('{') => true, + // e.g. `&mutablevar` + _ => false, + } + } else { + false + } + }; + if let (true, Some(ws_pos)) = (src.starts_with("&'"), src.find(char::is_whitespace)) { + let lt_name = &src[1..ws_pos]; + let ty = src[ws_pos..].trim_start(); + if !is_mutbl(ty) { + return (true, assignment_rhs_span, format!("&{lt_name} mut {ty}")); + } + } else if let Some(stripped) = src.strip_prefix('&') { + let stripped = stripped.trim_start(); + if !is_mutbl(stripped) { + return (true, assignment_rhs_span, format!("&mut {stripped}")); + } + } + } + + let (suggestability, 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) => (true, ty_span), + + // otherwise, just highlight the span associated with + // the (MIR) LocalDecl. + None => (false, local_decl.source_info.span), + }; + + if let Ok(src) = tcx.sess.source_map().span_to_snippet(highlight_span) + && let (true, Some(ws_pos)) = (src.starts_with("&'"), src.find(char::is_whitespace)) + { + let lt_name = &src[1..ws_pos]; + let ty = &src[ws_pos..]; + return (true, highlight_span, format!("&{} mut{}", lt_name, ty)); + } + + let ty_mut = local_decl.ty.builtin_deref(true).unwrap(); + assert_eq!(ty_mut.mutbl, hir::Mutability::Not); + ( + suggestability, + 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() +} + +/// Given a field that needs to be mutable, returns a span where the " mut " could go. +/// This function expects the local to be a reference to a struct in order to produce a span. +/// +/// ```text +/// LL | s: &'a String +/// | ^^^ returns a span taking up the space here +/// ``` +fn get_mut_span_in_struct_field<'tcx>( + tcx: TyCtxt<'tcx>, + ty: Ty<'tcx>, + field: mir::Field, +) -> Option<Span> { + // Expect our local to be a reference to a struct of some kind. + if let ty::Ref(_, ty, _) = ty.kind() + && let ty::Adt(def, _) = ty.kind() + && let field = def.all_fields().nth(field.index())? + // Use the HIR types to construct the diagnostic message. + && let node = tcx.hir().find_by_def_id(field.did.as_local()?)? + // 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. + && let hir::Node::Field(field) = node + && let hir::TyKind::Rptr(lt, hir::MutTy { mutbl: hir::Mutability::Not, ty }) = field.ty.kind + { + return Some(lt.span.between(ty.span)); + } + + None +} + +/// If possible, suggest replacing `ref` with `ref mut`. +fn suggest_ref_mut(tcx: TyCtxt<'_>, binding_span: Span) -> Option<String> { + 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 + } +} |