| use core::ops::ControlFlow; | |
| use either::Either; | |
| use hir::{ExprKind, Param}; | |
| use rustc_abi::FieldIdx; | |
| use rustc_errors::{Applicability, Diag}; | |
| use rustc_hir::def_id::DefId; | |
| use rustc_hir::intravisit::Visitor; | |
| use rustc_hir::{self as hir, BindingMode, ByRef, Expr, Node}; | |
| use rustc_middle::bug; | |
| use rustc_middle::hir::place::PlaceBase; | |
| use rustc_middle::mir::visit::PlaceContext; | |
| use rustc_middle::mir::{ | |
| self, BindingForm, Body, BorrowKind, Local, LocalDecl, LocalInfo, LocalKind, Location, | |
| Mutability, Operand, Place, PlaceRef, ProjectionElem, RawPtrKind, Rvalue, Statement, | |
| StatementKind, TerminatorKind, | |
| }; | |
| use rustc_middle::ty::{self, InstanceKind, Ty, TyCtxt, Upcast}; | |
| use rustc_span::{BytePos, DesugaringKind, Span, Symbol, kw, sym}; | |
| use rustc_trait_selection::error_reporting::InferCtxtErrorExt; | |
| use rustc_trait_selection::infer::InferCtxtExt; | |
| use rustc_trait_selection::traits; | |
| use tracing::{debug, trace}; | |
| use crate::diagnostics::BorrowedContentSource; | |
| use crate::{MirBorrowckCtxt, session_diagnostics}; | |
| #[derive(Copy, Clone, Debug, Eq, PartialEq)] | |
| pub(crate) enum AccessKind { | |
| MutableBorrow, | |
| Mutate, | |
| } | |
| /// Finds all statements that assign directly to local (i.e., X = ...) and returns their | |
| /// locations. | |
| fn find_assignments(body: &Body<'_>, local: Local) -> Vec<Location> { | |
| use rustc_middle::mir::visit::Visitor; | |
| struct FindLocalAssignmentVisitor { | |
| needle: Local, | |
| locations: Vec<Location>, | |
| } | |
| impl<'tcx> Visitor<'tcx> for FindLocalAssignmentVisitor { | |
| fn visit_local(&mut self, local: Local, place_context: PlaceContext, location: Location) { | |
| if self.needle != local { | |
| return; | |
| } | |
| if place_context.is_place_assignment() { | |
| self.locations.push(location); | |
| } | |
| } | |
| } | |
| let mut visitor = FindLocalAssignmentVisitor { needle: local, locations: vec![] }; | |
| visitor.visit_body(body); | |
| visitor.locations | |
| } | |
| impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, '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_name(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_like( | |
| Place::ty_from(local, proj_base, self.body, self.infcx.tcx).ty | |
| )); | |
| let imm_borrow_derefed = self.upvars[upvar_index.index()] | |
| .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()].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(); | |
| let LocalInfo::StaticRef { def_id, .. } = *local_info else { | |
| bug!("is_ref_to_static return true, but not ref to static?"); | |
| }; | |
| let static_name = &self.infcx.tcx.item_name(def_id); | |
| reason = format!(", as `{static_name}` is an immutable static item"); | |
| } | |
| } | |
| PlaceRef { local, projection: [proj_base @ .., ProjectionElem::Deref] } => { | |
| if 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_ref()); | |
| debug_assert!(is_closure_like(the_place_err.ty(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, 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::OpaqueCast { .. } | |
| | ProjectionElem::Subslice { .. } | |
| | ProjectionElem::Downcast(..) | |
| | ProjectionElem::UnwrapUnsafeBinder(_), | |
| ], | |
| } => 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 mut suggest = true; | |
| let mut mut_error = None; | |
| let mut count = 1; | |
| let span = match error_access { | |
| AccessKind::Mutate => { | |
| err = self.cannot_assign(span, &(item_msg + &reason)); | |
| act = "assign"; | |
| acted_on = "written to"; | |
| 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(); | |
| match the_place_err { | |
| PlaceRef { local, projection: [] } | |
| if self.body.local_decls[local].can_be_made_mutable() => | |
| { | |
| let span = self.body.local_decls[local].source_info.span; | |
| mut_error = Some(span); | |
| if let Some((buffered_err, c)) = self.get_buffered_mut_error(span) { | |
| // We've encountered a second (or more) attempt to mutably borrow an | |
| // immutable binding, so the likely problem is with the binding | |
| // declaration, not the use. We collect these in a single diagnostic | |
| // and make the binding the primary span of the error. | |
| err = buffered_err; | |
| count = c + 1; | |
| if count == 2 { | |
| err.replace_span_with(span, false); | |
| err.span_label(span, "not mutable"); | |
| } | |
| suggest = false; | |
| } else { | |
| err = self.cannot_borrow_path_as_mutable_because( | |
| borrow_span, | |
| &item_msg, | |
| &reason, | |
| ); | |
| } | |
| } | |
| _ => { | |
| err = self.cannot_borrow_path_as_mutable_because( | |
| borrow_span, | |
| &item_msg, | |
| &reason, | |
| ); | |
| } | |
| } | |
| if suggest { | |
| borrow_spans.var_subdiag( | |
| &mut err, | |
| Some(mir::BorrowKind::Mut { kind: mir::MutBorrowKind::Default }), | |
| |_kind, var_span| { | |
| let place = self.describe_any_place(access_place.as_ref()); | |
| session_diagnostics::CaptureVarCause::MutableBorrowUsePlaceClosure { | |
| place, | |
| var_span, | |
| } | |
| }, | |
| ); | |
| } | |
| 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: | |
| [ | |
| proj_base @ .., | |
| ProjectionElem::Deref, | |
| ProjectionElem::Field(field, _), | |
| ProjectionElem::Deref, | |
| ], | |
| } => { | |
| err.span_label(span, format!("cannot {act}")); | |
| let place = Place::ty_from(local, proj_base, self.body, self.infcx.tcx); | |
| if let Some(span) = get_mut_span_in_struct_field(self.infcx.tcx, place.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) | |
| .is_some_and(|l| mut_borrow_of_mutable_ref(l, self.local_name(local))) => | |
| { | |
| let decl = &self.body.local_decls[local]; | |
| err.span_label(span, format!("cannot {act}")); | |
| if let Some(mir::Statement { | |
| source_info, | |
| kind: | |
| mir::StatementKind::Assign(box ( | |
| _, | |
| mir::Rvalue::Ref( | |
| _, | |
| mir::BorrowKind::Mut { kind: mir::MutBorrowKind::Default }, | |
| _, | |
| ), | |
| )), | |
| .. | |
| }) = &self.body[location.block].statements.get(location.statement_index) | |
| { | |
| match *decl.local_info() { | |
| LocalInfo::User(BindingForm::Var(mir::VarBindingForm { | |
| binding_mode: BindingMode(ByRef::No, Mutability::Not), | |
| opt_ty_info: Some(sp), | |
| pat_span, | |
| .. | |
| })) => { | |
| if suggest { | |
| err.span_note(sp, "the binding is already a mutable borrow"); | |
| err.span_suggestion_verbose( | |
| pat_span.shrink_to_lo(), | |
| "consider making the binding mutable if you need to reborrow \ | |
| multiple times", | |
| "mut ".to_string(), | |
| Applicability::MaybeIncorrect, | |
| ); | |
| } | |
| } | |
| _ => { | |
| 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 ") { | |
| // In calls, `&mut &mut T` may be deref-coerced to `&mut T`, and | |
| // removing the extra `&mut` is the most direct suggestion. But for | |
| // pattern-matching expressions (`match`, `if let`, `while let`), that | |
| // can easily turn into a move, so prefer suggesting an explicit | |
| // reborrow via `&mut *x` instead. | |
| let mut in_pat_scrutinee = false; | |
| let mut is_deref_coerced = false; | |
| if let Some(expr) = self.find_expr(source_info.span) { | |
| let tcx = self.infcx.tcx; | |
| let span = expr.span.source_callsite(); | |
| for (_, node) in tcx.hir_parent_iter(expr.hir_id) { | |
| if let Node::Expr(parent_expr) = node { | |
| match parent_expr.kind { | |
| ExprKind::Match(scrutinee, ..) | |
| if scrutinee | |
| .span | |
| .source_callsite() | |
| .contains(span) => | |
| { | |
| in_pat_scrutinee = true; | |
| break; | |
| } | |
| ExprKind::Let(let_expr) | |
| if let_expr | |
| .init | |
| .span | |
| .source_callsite() | |
| .contains(span) => | |
| { | |
| in_pat_scrutinee = true; | |
| break; | |
| } | |
| _ => {} | |
| } | |
| } | |
| } | |
| let typeck = tcx.typeck(expr.hir_id.owner.def_id); | |
| is_deref_coerced = | |
| typeck.expr_adjustments(expr).iter().any(|adj| { | |
| matches!(adj.kind, ty::adjustment::Adjust::Deref(_)) | |
| }); | |
| } | |
| if in_pat_scrutinee { | |
| // Best-effort structured suggestion: insert `*` after `&mut `. | |
| err.span_suggestion_verbose( | |
| source_info | |
| .span | |
| .with_lo(source_info.span.lo() + BytePos(5)) | |
| .shrink_to_lo(), | |
| "to reborrow the mutable reference, add `*`", | |
| "*", | |
| Applicability::MaybeIncorrect, | |
| ); | |
| } else if is_deref_coerced { | |
| // 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)), | |
| "if there is only one mutable reborrow, remove the `&mut`", | |
| "", | |
| Applicability::MaybeIncorrect, | |
| ); | |
| } | |
| } 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.is_not() { | |
| if matches!( | |
| decl.local_info(), | |
| LocalInfo::User(BindingForm::ImplicitSelf(hir::ImplicitSelfKind::RefMut)) | |
| ) { | |
| err.note( | |
| "as `Self` may be unsized, this call attempts to take `&mut &mut self`", | |
| ); | |
| err.note("however, `&mut self` expands to `self: &mut Self`, therefore `self` cannot be borrowed mutably"); | |
| } else { | |
| 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); | |
| if count < 10 { | |
| err.span_label(span, format!("cannot {act}")); | |
| } | |
| if suggest { | |
| self.construct_mut_suggestion_for_local_binding_patterns(&mut err, local); | |
| 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_like( | |
| Place::ty_from(local, proj_base, self.body, self.infcx.tcx).ty | |
| )); | |
| let captured_place = self.upvars[upvar_index.index()]; | |
| err.span_label(span, format!("cannot {act}")); | |
| let upvar_hir_id = captured_place.get_root_variable(); | |
| if let Node::Pat(pat) = self.infcx.tcx.hir_node(upvar_hir_id) | |
| && let hir::PatKind::Binding(hir::BindingMode::NONE, _, upvar_ident, _) = | |
| pat.kind | |
| { | |
| if upvar_ident.name == kw::SelfLower { | |
| for (_, node) in self.infcx.tcx.hir_parent_iter(upvar_hir_id) { | |
| if let Some(fn_decl) = node.fn_decl() { | |
| if !matches!( | |
| fn_decl.implicit_self(), | |
| hir::ImplicitSelfKind::RefImm | hir::ImplicitSelfKind::RefMut | |
| ) { | |
| err.span_suggestion_verbose( | |
| upvar_ident.span.shrink_to_lo(), | |
| "consider changing this to be mutable", | |
| "mut ", | |
| Applicability::MachineApplicable, | |
| ); | |
| break; | |
| } | |
| } | |
| } | |
| } else { | |
| err.span_suggestion_verbose( | |
| upvar_ident.span.shrink_to_lo(), | |
| "consider changing this to be mutable", | |
| "mut ", | |
| 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 self | |
| .infcx | |
| .tcx | |
| .sess | |
| .source_map() | |
| .span_to_snippet(span) | |
| .is_ok_and(|snippet| snippet.starts_with("&mut ")) => | |
| { | |
| err.span_label(span, format!("cannot {act}")); | |
| err.span_suggestion_verbose( | |
| span.with_hi(span.lo() + BytePos(5)), | |
| "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}")); | |
| 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_ref() { ("&", "reference") } else { ("*const", "pointer") }; | |
| match self.local_name(local) { | |
| Some(name) if !local_decl.from_compiler_desugaring() => { | |
| err.span_label( | |
| span, | |
| format!( | |
| "`{name}` is a `{pointer_sigil}` {pointer_desc}, so it cannot be \ | |
| {acted_on}", | |
| ), | |
| ); | |
| self.suggest_using_iter_mut(&mut err); | |
| self.suggest_make_local_mut(&mut err, local, name); | |
| } | |
| _ => { | |
| err.span_label( | |
| span, | |
| format!("cannot {act} through `{pointer_sigil}` {pointer_desc}"), | |
| ); | |
| } | |
| } | |
| } | |
| PlaceRef { local, projection: [ProjectionElem::Deref] } | |
| if local == ty::CAPTURE_STRUCT_LOCAL && !self.upvars.is_empty() => | |
| { | |
| self.point_at_binding_outside_closure(&mut err, local, access_place); | |
| self.expected_fn_found_fn_mut_call(&mut err, span, act); | |
| } | |
| PlaceRef { local, projection: [.., ProjectionElem::Deref] } => { | |
| err.span_label(span, format!("cannot {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}`", | |
| )); | |
| self.suggest_map_index_mut_alternatives(ty, &mut err, span); | |
| } | |
| _ => { | |
| let local = &self.body.local_decls[local]; | |
| match *local.local_info() { | |
| LocalInfo::StaticRef { def_id, .. } => { | |
| let span = self.infcx.tcx.def_span(def_id); | |
| err.span_label(span, format!("this `static` cannot be {acted_on}")); | |
| } | |
| LocalInfo::ConstRef { def_id } => { | |
| let span = self.infcx.tcx.def_span(def_id); | |
| err.span_label(span, format!("this `const` cannot be {acted_on}")); | |
| } | |
| LocalInfo::BlockTailTemp(_) | LocalInfo::Boring | |
| if !local.source_info.span.overlaps(span) => | |
| { | |
| err.span_label( | |
| local.source_info.span, | |
| format!("this cannot be {acted_on}"), | |
| ); | |
| } | |
| _ => {} | |
| } | |
| } | |
| } | |
| } | |
| PlaceRef { local, .. } => { | |
| let local = &self.body.local_decls[local]; | |
| if !local.source_info.span.overlaps(span) { | |
| err.span_label(local.source_info.span, format!("this cannot be {acted_on}")); | |
| } | |
| err.span_label(span, format!("cannot {act}")); | |
| } | |
| } | |
| if let Some(span) = mut_error { | |
| self.buffer_mut_error(span, err, count); | |
| } else { | |
| self.buffer_error(err); | |
| } | |
| } | |
| /// Suggest `map[k] = v` => `map.insert(k, v)` and the like. | |
| fn suggest_map_index_mut_alternatives(&self, ty: Ty<'tcx>, err: &mut Diag<'infcx>, span: Span) { | |
| let Some(adt) = ty.ty_adt_def() else { return }; | |
| let did = adt.did(); | |
| if self.infcx.tcx.is_diagnostic_item(sym::HashMap, did) | |
| || self.infcx.tcx.is_diagnostic_item(sym::BTreeMap, did) | |
| { | |
| /// Walks through the HIR, looking for the corresponding span for this error. | |
| /// When it finds it, see if it corresponds to assignment operator whose LHS | |
| /// is an index expr. | |
| struct SuggestIndexOperatorAlternativeVisitor<'a, 'infcx, 'tcx> { | |
| assign_span: Span, | |
| err: &'a mut Diag<'infcx>, | |
| ty: Ty<'tcx>, | |
| suggested: bool, | |
| infcx: &'a rustc_infer::infer::InferCtxt<'tcx>, | |
| } | |
| impl<'a, 'infcx, 'tcx> Visitor<'tcx> for SuggestIndexOperatorAlternativeVisitor<'a, 'infcx, 'tcx> { | |
| fn visit_stmt(&mut self, stmt: &'tcx hir::Stmt<'tcx>) { | |
| hir::intravisit::walk_stmt(self, stmt); | |
| let expr = match stmt.kind { | |
| hir::StmtKind::Semi(expr) | hir::StmtKind::Expr(expr) => expr, | |
| hir::StmtKind::Let(hir::LetStmt { init: Some(expr), .. }) => expr, | |
| _ => { | |
| return; | |
| } | |
| }; | |
| // Because of TypeChecking and indexing, we know: index is &Q | |
| // with K: Eq + Hash + Borrow<Q>, | |
| // with Q: Eq + Hash + ?Sized, | |
| // | |
| // which fulfill the requirements of `get_mut`. If Q=K or Q=&{n}K, the requirements | |
| // of `entry` and `insert` are fulfilled too after dereferencing. If K is not | |
| // copy, a subsequent `clone` call may be needed. | |
| /// Taken straight from https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/fn.peel_hir_ty_refs.html | |
| /// Adapted to mid using https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.Ty.html#method.peel_refs | |
| /// Simplified to counting only | |
| /// Peels off all references on the type. Returns the number of references | |
| /// removed. | |
| fn count_ty_refs<'tcx>(mut ty: Ty<'tcx>) -> usize { | |
| let mut count = 0; | |
| while let ty::Ref(_, inner_ty, _) = ty.kind() { | |
| ty = *inner_ty; | |
| count += 1; | |
| } | |
| count | |
| } | |
| /// Try to strip `n` `&` reference from an expression. | |
| /// If the expression does not have enough leading `&`, return an Error | |
| /// containing a count of the successfully stripped ones and the stripped | |
| /// expression. | |
| fn strip_n_refs<'a, 'b>( | |
| mut expr: &'a Expr<'b>, | |
| n: usize, | |
| ) -> Result<&'a Expr<'b>, (usize, &'a Expr<'b>)> { | |
| for count in 0..n { | |
| match expr { | |
| Expr { | |
| kind: ExprKind::AddrOf(hir::BorrowKind::Ref, _, inner), | |
| .. | |
| } => expr = inner, | |
| _ => return Err((count, expr)), | |
| } | |
| } | |
| Ok(expr) | |
| } | |
| // we know ty is a map, with a key type at walk distance 2. | |
| let key_ty = self.ty.walk().nth(1).unwrap().expect_ty(); | |
| if let hir::ExprKind::Assign(place, rv, _sp) = expr.kind | |
| && let hir::ExprKind::Index(val, index, _) = place.kind | |
| && (expr.span == self.assign_span || place.span == self.assign_span) | |
| { | |
| // val[index] = rv; | |
| let index_ty = | |
| self.infcx.tcx.typeck(val.hir_id.owner.def_id).expr_ty(index); | |
| let (borrowed_prefix, borrowed_index); | |
| // only suggest `insert` and `entry` if index is of type K or &{n}K or *{n}K (when there is a Borrow impl for this case). | |
| // We use `peel_refs` because borrow lifetimes may differ in both index and | |
| // key. I.e, if they are of the same base type: | |
| if index_ty.peel_refs() == key_ty.peel_refs() { | |
| let (index_refs, key_refs) = | |
| (count_ty_refs(index_ty), count_ty_refs(key_ty)); | |
| let (deref_prefix, deref_index) = if index_refs >= key_refs { | |
| // index is &{n}K | |
| strip_n_refs(index, index_refs - key_refs) | |
| .map(|val| ("".to_string(), val)) | |
| .unwrap_or_else(|(depth, val)| { | |
| ( | |
| if key_refs == 0 { | |
| "*".repeat( | |
| (index_refs-key_refs).checked_sub(depth).expect("return depth from strip_n_refs should be smaller than the input") | |
| ) | |
| } else { | |
| String::new() //if key K is a ref, autoderef finish this for us. | |
| }, | |
| val, | |
| ) | |
| }) | |
| } else { | |
| // in this case the minimal ref addition works for all subcases | |
| ("&".repeat(key_refs - index_refs), index) | |
| }; | |
| self.err.multipart_suggestion( | |
| format!("use `.insert()` to insert a value into a `{}`", self.ty), | |
| vec![ | |
| // val.insert({deref_prefix}{deref_index}, rv); | |
| ( | |
| val.span.shrink_to_hi().with_hi(deref_index.span.lo()), | |
| format!(".insert({deref_prefix}"), | |
| ), | |
| ( | |
| deref_index.span.shrink_to_hi().with_hi(rv.span.lo()), | |
| ", ".to_string(), | |
| ), | |
| (rv.span.shrink_to_hi(), ")".to_string()), | |
| ], | |
| Applicability::MaybeIncorrect, | |
| ); | |
| self.err.multipart_suggestion( | |
| format!( | |
| "use the entry API to modify a `{}` for more flexibility", | |
| self.ty | |
| ), | |
| vec![ | |
| // let x = val.entry({deref_prefix}{deref_index}).insert_entry(rv); | |
| (val.span.shrink_to_lo(), "let val = ".to_string()), | |
| ( | |
| val.span.shrink_to_hi().with_hi(deref_index.span.lo()), | |
| format!(".entry({deref_prefix}"), | |
| ), | |
| ( | |
| deref_index.span.shrink_to_hi().with_hi(rv.span.lo()), | |
| ").insert_entry(".to_string(), | |
| ), | |
| (rv.span.shrink_to_hi(), ")".to_string()), | |
| ], | |
| Applicability::MaybeIncorrect, | |
| ); | |
| // we can make the next suggestions nicer by stripping as many leading `&` as | |
| // we can, autoderef will do the rest | |
| (borrowed_prefix, borrowed_index) = ( | |
| String::new(), | |
| if index_refs > key_refs { | |
| strip_n_refs(index, index_refs - key_refs - 1) | |
| .unwrap_or_else(|(_depth, val)| val) | |
| // even if we tried to strip more, we can stop there thanks to autoderef | |
| } else { | |
| // when the diff is negative or zero, we already are in the index=&Q case. | |
| index | |
| }, | |
| ); | |
| } else { | |
| (borrowed_prefix, borrowed_index) = (String::new(), index) | |
| } | |
| // in all cases, suggest get_mut because K:Borrow<K> or Q:Borrow<K> as a | |
| // requirement of indexing. | |
| self.err.multipart_suggestion( | |
| format!( | |
| "use `.get_mut()` to modify an existing key in a `{}`", | |
| self.ty, | |
| ), | |
| vec![ | |
| // if let Some(v) = val.get_mut({borrowed_prefix}{borrowed_index}) { *v = rv; } | |
| (val.span.shrink_to_lo(), "if let Some(val) = ".to_string()), | |
| ( | |
| val.span.shrink_to_hi().with_hi(borrowed_index.span.lo()), | |
| format!(".get_mut({borrowed_prefix}"), | |
| ), | |
| ( | |
| borrowed_index.span.shrink_to_hi().with_hi(place.span.hi()), | |
| ") { *val".to_string(), | |
| ), | |
| (rv.span.shrink_to_hi(), "; }".to_string()), | |
| ], | |
| Applicability::MaybeIncorrect, | |
| ); | |
| self.suggested = true; | |
| } else if let hir::ExprKind::MethodCall(_path, receiver, _, sp) = expr.kind | |
| && let hir::ExprKind::Index(val, index, _) = receiver.kind | |
| && receiver.span == self.assign_span | |
| { | |
| // val[index].path(args..); | |
| self.err.multipart_suggestion( | |
| format!("to modify a `{}` use `.get_mut()`", self.ty), | |
| vec![ | |
| (val.span.shrink_to_lo(), "if let Some(val) = ".to_string()), | |
| ( | |
| val.span.shrink_to_hi().with_hi(index.span.lo()), | |
| ".get_mut(".to_string(), | |
| ), | |
| ( | |
| index.span.shrink_to_hi().with_hi(receiver.span.hi()), | |
| ") { val".to_string(), | |
| ), | |
| (sp.shrink_to_hi(), "; }".to_string()), | |
| ], | |
| Applicability::MachineApplicable, | |
| ); | |
| self.suggested = true; | |
| } | |
| } | |
| } | |
| let def_id = self.body.source.def_id(); | |
| let Some(local_def_id) = def_id.as_local() else { return }; | |
| let Some(body) = self.infcx.tcx.hir_maybe_body_owned_by(local_def_id) else { return }; | |
| let mut v = SuggestIndexOperatorAlternativeVisitor { | |
| assign_span: span, | |
| err, | |
| ty, | |
| suggested: false, | |
| infcx: self.infcx, | |
| }; | |
| v.visit_body(&body); | |
| if !v.suggested { | |
| err.help(format!( | |
| "to modify a `{ty}`, use `.get_mut()`, `.insert()` or the entry API", | |
| )); | |
| } | |
| } | |
| } | |
| /// 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 | |
| /// | |
| /// The returned values are: | |
| /// - is the current item an assoc `fn` of an impl that corresponds to a trait def? if so, we | |
| /// have to suggest changing both the impl `fn` arg and the trait `fn` arg | |
| /// - is the trait from the local crate? If not, we can't suggest changing signatures | |
| /// - `Span` of the argument in the trait definition | |
| fn is_error_in_trait(&self, local: Local) -> (bool, bool, Option<Span>) { | |
| let tcx = self.infcx.tcx; | |
| if self.body.local_kind(local) != LocalKind::Arg { | |
| return (false, false, None); | |
| } | |
| let my_def = self.body.source.def_id(); | |
| let Some(td) = tcx.trait_impl_of_assoc(my_def).map(|id| self.infcx.tcx.impl_trait_id(id)) | |
| else { | |
| return (false, false, None); | |
| }; | |
| let implemented_trait_item = self.infcx.tcx.trait_item_of(my_def); | |
| ( | |
| true, | |
| td.is_local(), | |
| implemented_trait_item.and_then(|f_in_trait| { | |
| let f_in_trait = f_in_trait.as_local()?; | |
| if let Node::TraitItem(ti) = self.infcx.tcx.hir_node_by_def_id(f_in_trait) | |
| && let hir::TraitItemKind::Fn(sig, _) = ti.kind | |
| && let Some(ty) = sig.decl.inputs.get(local.index() - 1) | |
| && let hir::TyKind::Ref(_, mut_ty) = ty.kind | |
| && let hir::Mutability::Not = mut_ty.mutbl | |
| && sig.decl.implicit_self().has_implicit_self() | |
| { | |
| Some(ty.span) | |
| } else { | |
| None | |
| } | |
| }), | |
| ) | |
| } | |
| fn construct_mut_suggestion_for_local_binding_patterns( | |
| &self, | |
| err: &mut Diag<'_>, | |
| local: Local, | |
| ) { | |
| let local_decl = &self.body.local_decls[local]; | |
| debug!("local_decl: {:?}", local_decl); | |
| let pat_span = match *local_decl.local_info() { | |
| LocalInfo::User(BindingForm::Var(mir::VarBindingForm { | |
| binding_mode: BindingMode(ByRef::No, Mutability::Not), | |
| opt_ty_info: _, | |
| opt_match_place: _, | |
| pat_span, | |
| introductions: _, | |
| })) => pat_span, | |
| _ => local_decl.source_info.span, | |
| }; | |
| // With ref-binding patterns, the mutability suggestion has to apply to | |
| // the binding, not the reference (which would be a type error): | |
| // | |
| // `let &b = a;` -> `let &(mut b) = a;` | |
| // or | |
| // `fn foo(&x: &i32)` -> `fn foo(&(mut x): &i32)` | |
| let def_id = self.body.source.def_id(); | |
| if let Some(local_def_id) = def_id.as_local() | |
| && let Some(body) = self.infcx.tcx.hir_maybe_body_owned_by(local_def_id) | |
| && let Some(hir_id) = (BindingFinder { span: pat_span }).visit_body(&body).break_value() | |
| && let node = self.infcx.tcx.hir_node(hir_id) | |
| && let hir::Node::LetStmt(hir::LetStmt { | |
| pat: hir::Pat { kind: hir::PatKind::Ref(_, _, _), .. }, | |
| .. | |
| }) | |
| | hir::Node::Param(Param { | |
| pat: hir::Pat { kind: hir::PatKind::Ref(_, _, _), .. }, | |
| .. | |
| }) = node | |
| { | |
| err.multipart_suggestion( | |
| "consider changing this to be mutable", | |
| vec![ | |
| (pat_span.until(local_decl.source_info.span), "&(mut ".to_string()), | |
| ( | |
| local_decl.source_info.span.shrink_to_hi().with_hi(pat_span.hi()), | |
| ")".to_string(), | |
| ), | |
| ], | |
| Applicability::MachineApplicable, | |
| ); | |
| return; | |
| } | |
| err.span_suggestion_verbose( | |
| local_decl.source_info.span.shrink_to_lo(), | |
| "consider changing this to be mutable", | |
| "mut ", | |
| Applicability::MachineApplicable, | |
| ); | |
| } | |
| // Point to span of upvar making closure call that requires a mutable borrow | |
| fn show_mutating_upvar( | |
| &self, | |
| tcx: TyCtxt<'_>, | |
| closure_local_def_id: hir::def_id::LocalDefId, | |
| the_place_err: PlaceRef<'tcx>, | |
| err: &mut Diag<'_>, | |
| ) { | |
| let tables = tcx.typeck(closure_local_def_id); | |
| if let Some((span, closure_kind_origin)) = tcx.closure_kind_origin(closure_local_def_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::Mutable | ty::BorrowKind::UniqueImmutable, | |
| ) => { | |
| capture_reason = format!("mutable borrow of `{upvar}`"); | |
| } | |
| ty::UpvarCapture::ByValue | ty::UpvarCapture::ByUse => { | |
| 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") | |
| }; | |
| // Sometimes we deliberately don't store the name of a place when coming from a macro in | |
| // another crate. We generally want to limit those diagnostics a little, to hide | |
| // implementation details (such as those from pin!() or format!()). In that case show a | |
| // slightly different error message, or none at all if something else happened. In other | |
| // cases the message is likely not useful. | |
| if let Some(place_name) = self.describe_place(the_place_err) { | |
| err.span_label( | |
| *span, | |
| format!("calling `{place_name}` requires mutable binding due to {reason}"), | |
| ); | |
| } else if span.from_expansion() { | |
| err.span_label( | |
| *span, | |
| format!("a call in this macro requires a mutable binding due to {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 Diag<'_>, span: Span) { | |
| use hir::ExprKind::{AddrOf, Block, Call, MethodCall}; | |
| use hir::{BorrowKind, Expr}; | |
| let tcx = self.infcx.tcx; | |
| struct Finder { | |
| span: Span, | |
| } | |
| impl<'tcx> Visitor<'tcx> for Finder { | |
| type Result = ControlFlow<&'tcx Expr<'tcx>>; | |
| fn visit_expr(&mut self, e: &'tcx hir::Expr<'tcx>) -> Self::Result { | |
| if e.span == self.span { | |
| ControlFlow::Break(e) | |
| } else { | |
| hir::intravisit::walk_expr(self, e) | |
| } | |
| } | |
| } | |
| if let Some(body) = tcx.hir_maybe_body_owned_by(self.mir_def_id()) | |
| && let Block(block, _) = body.value.kind | |
| { | |
| // `span` corresponds to the expression being iterated, find the `for`-loop desugared | |
| // expression with that span in order to identify potential fixes when encountering a | |
| // read-only iterator that should be mutable. | |
| if let ControlFlow::Break(expr) = (Finder { span }).visit_block(block) | |
| && let Call(_, [expr]) = expr.kind | |
| { | |
| match expr.kind { | |
| MethodCall(path_segment, _, _, span) => { | |
| // We have `for _ in iter.read_only_iter()`, try to | |
| // suggest `for _ in iter.mutable_iter()` instead. | |
| let opt_suggestions = tcx | |
| .typeck(path_segment.hir_id.owner.def_id) | |
| .type_dependent_def_id(expr.hir_id) | |
| .and_then(|def_id| tcx.impl_of_assoc(def_id)) | |
| .map(|def_id| tcx.associated_items(def_id)) | |
| .map(|assoc_items| { | |
| assoc_items | |
| .in_definition_order() | |
| .map(|assoc_item_def| assoc_item_def.ident(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, | |
| ); | |
| } | |
| } | |
| AddrOf(BorrowKind::Ref, Mutability::Not, expr) => { | |
| // We have `for _ in &i`, suggest `for _ in &mut i`. | |
| err.span_suggestion_verbose( | |
| expr.span.shrink_to_lo(), | |
| "use a mutable iterator instead", | |
| "mut ", | |
| Applicability::MachineApplicable, | |
| ); | |
| } | |
| _ => {} | |
| } | |
| } | |
| } | |
| } | |
| /// When modifying a binding from inside of an `Fn` closure, point at the binding definition. | |
| fn point_at_binding_outside_closure( | |
| &self, | |
| err: &mut Diag<'_>, | |
| local: Local, | |
| access_place: Place<'tcx>, | |
| ) { | |
| let place = access_place.as_ref(); | |
| for (index, elem) in place.projection.into_iter().enumerate() { | |
| if let ProjectionElem::Deref = elem { | |
| if index == 0 { | |
| if self.body.local_decls[local].is_ref_for_guard() { | |
| continue; | |
| } | |
| if let LocalInfo::StaticRef { .. } = *self.body.local_decls[local].local_info() | |
| { | |
| continue; | |
| } | |
| } | |
| if let Some(field) = self.is_upvar_field_projection(PlaceRef { | |
| local, | |
| projection: place.projection.split_at(index + 1).0, | |
| }) { | |
| let var_index = field.index(); | |
| let upvar = self.upvars[var_index]; | |
| if let Some(hir_id) = upvar.info.capture_kind_expr_id { | |
| let node = self.infcx.tcx.hir_node(hir_id); | |
| if let hir::Node::Expr(expr) = node | |
| && let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = expr.kind | |
| && let hir::def::Res::Local(hir_id) = path.res | |
| && let hir::Node::Pat(pat) = self.infcx.tcx.hir_node(hir_id) | |
| { | |
| let name = upvar.to_string(self.infcx.tcx); | |
| err.span_label( | |
| pat.span, | |
| format!("`{name}` declared here, outside the closure"), | |
| ); | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| /// Targeted error when encountering an `FnMut` closure where an `Fn` closure was expected. | |
| fn expected_fn_found_fn_mut_call(&self, err: &mut Diag<'_>, sp: Span, act: &str) { | |
| err.span_label(sp, format!("cannot {act}")); | |
| let tcx = self.infcx.tcx; | |
| let closure_id = self.mir_hir_id(); | |
| let closure_span = tcx.def_span(self.mir_def_id()); | |
| let fn_call_id = tcx.parent_hir_id(closure_id); | |
| let node = tcx.hir_node(fn_call_id); | |
| let def_id = tcx.hir_enclosing_body_owner(fn_call_id); | |
| let mut look_at_return = true; | |
| err.span_label(closure_span, "in this closure"); | |
| let closure_arg_has_fn_trait_bound = | |
| |callee_def_id, input_index, generic_args: ty::GenericArgsRef<'tcx>| { | |
| let sig = tcx.fn_sig(callee_def_id).instantiate(tcx, generic_args).skip_binder(); | |
| let Some(input_ty): Option<Ty<'tcx>> = sig.inputs().get(input_index).copied() | |
| else { | |
| return false; | |
| }; | |
| tcx.predicates_of(callee_def_id) | |
| .instantiate(tcx, generic_args) | |
| .predicates | |
| .iter() | |
| .any(|predicate| { | |
| predicate.as_trait_clause().is_some_and(|trait_pred| { | |
| trait_pred.polarity() == ty::PredicatePolarity::Positive | |
| && tcx.fn_trait_kind_from_def_id(trait_pred.def_id()) | |
| == Some(ty::ClosureKind::Fn) | |
| && trait_pred.self_ty().skip_binder().peel_refs() | |
| == input_ty.peel_refs() | |
| }) | |
| }) | |
| }; | |
| // If the HIR node is a function or method call, get the DefId | |
| // of the callee function or method, the span, and argument info for the call expr. | |
| let get_call_details = | |
| || -> Option<(DefId, Span, usize, usize, ty::GenericArgsRef<'tcx>)> { | |
| let hir::Node::Expr(hir::Expr { hir_id, kind, .. }) = node else { | |
| return None; | |
| }; | |
| let typeck_results = tcx.typeck(def_id); | |
| match kind { | |
| hir::ExprKind::Call(expr, args) => { | |
| if let Some(ty::FnDef(def_id, generic_args)) = | |
| typeck_results.node_type_opt(expr.hir_id).as_ref().map(|ty| ty.kind()) | |
| { | |
| let arg_pos = args.iter().position(|arg| arg.hir_id == closure_id)?; | |
| Some((*def_id, expr.span, arg_pos, arg_pos, generic_args)) | |
| } else { | |
| None | |
| } | |
| } | |
| hir::ExprKind::MethodCall(_, _, args, span) => { | |
| let arg_pos = args.iter().position(|arg| arg.hir_id == closure_id)?; | |
| let def_id = typeck_results.type_dependent_def_id(*hir_id)?; | |
| let generic_args = typeck_results.node_args_opt(*hir_id)?; | |
| Some((def_id, *span, arg_pos, arg_pos + 1, generic_args)) | |
| } | |
| _ => None, | |
| } | |
| }; | |
| // If we can detect the expression to be a function or method call where the closure was | |
| // an argument, we point at the function or method definition argument... | |
| if let Some((callee_def_id, call_span, arg_pos, input_index, generic_args)) = | |
| get_call_details() | |
| { | |
| let arg = match tcx.hir_get_if_local(callee_def_id) { | |
| Some( | |
| hir::Node::Item(hir::Item { | |
| kind: hir::ItemKind::Fn { ident, sig, .. }, .. | |
| }) | |
| | hir::Node::TraitItem(hir::TraitItem { | |
| ident, | |
| kind: hir::TraitItemKind::Fn(sig, _), | |
| .. | |
| }) | |
| | hir::Node::ImplItem(hir::ImplItem { | |
| ident, | |
| kind: hir::ImplItemKind::Fn(sig, _), | |
| .. | |
| }), | |
| ) => Some( | |
| sig.decl | |
| .inputs | |
| .get( | |
| arg_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(call_span, "expects `Fn` instead of `FnMut`"); | |
| look_at_return = false; | |
| } else if closure_arg_has_fn_trait_bound(callee_def_id, input_index, generic_args) { | |
| // The callee is not local, so we cannot point at its argument declaration, but we | |
| // can still explain that this call site expects an `Fn` closure. Avoid falling | |
| // through to the enclosing function's return type, which is misleading in cases | |
| // like `flat_map(|_| external::map(|_| ...))`. | |
| err.span_label(call_span, "expects `Fn` instead of `FnMut`"); | |
| look_at_return = false; | |
| } | |
| } | |
| if look_at_return && tcx.hir_get_fn_id_for_return_block(closure_id).is_some() { | |
| // ...otherwise we are probably in the tail expression of the function, point at the | |
| // return type. | |
| match tcx.hir_node_by_def_id(tcx.hir_get_parent_item(fn_call_id).def_id) { | |
| hir::Node::Item(hir::Item { | |
| kind: hir::ItemKind::Fn { ident, 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`", | |
| ); | |
| } | |
| _ => {} | |
| } | |
| } | |
| } | |
| fn suggest_using_iter_mut(&self, err: &mut Diag<'_>) { | |
| let source = self.body.source; | |
| if let InstanceKind::Item(def_id) = source.instance | |
| && let Some(Node::Expr(hir::Expr { hir_id, kind, .. })) = | |
| self.infcx.tcx.hir_get_if_local(def_id) | |
| && let ExprKind::Closure(hir::Closure { kind: hir::ClosureKind::Closure, .. }) = kind | |
| && let Node::Expr(expr) = self.infcx.tcx.parent_hir_node(*hir_id) | |
| { | |
| let mut cur_expr = expr; | |
| while let ExprKind::MethodCall(path_segment, recv, _, _) = cur_expr.kind { | |
| if path_segment.ident.name == sym::iter { | |
| // Check that the type has an `iter_mut` method. | |
| let res = self | |
| .infcx | |
| .tcx | |
| .typeck(path_segment.hir_id.owner.def_id) | |
| .type_dependent_def_id(cur_expr.hir_id) | |
| .and_then(|def_id| self.infcx.tcx.impl_of_assoc(def_id)) | |
| .map(|def_id| self.infcx.tcx.associated_items(def_id)) | |
| .map(|assoc_items| { | |
| assoc_items.filter_by_name_unhygienic(sym::iter_mut).peekable() | |
| }); | |
| if let Some(mut res) = res | |
| && res.peek().is_some() | |
| { | |
| err.span_suggestion_verbose( | |
| path_segment.ident.span, | |
| "you may want to use `iter_mut` here", | |
| "iter_mut", | |
| Applicability::MaybeIncorrect, | |
| ); | |
| } | |
| break; | |
| } else { | |
| cur_expr = recv; | |
| } | |
| } | |
| } | |
| } | |
| fn suggest_make_local_mut(&self, err: &mut Diag<'_>, local: Local, name: Symbol) { | |
| let local_decl = &self.body.local_decls[local]; | |
| let (pointer_sigil, pointer_desc) = | |
| if local_decl.ty.is_ref() { ("&", "reference") } else { ("*const", "pointer") }; | |
| let (is_trait_sig, is_local, local_trait) = self.is_error_in_trait(local); | |
| if is_trait_sig && !is_local { | |
| // Do not suggest changing the signature when the trait comes from another crate. | |
| err.span_label( | |
| local_decl.source_info.span, | |
| format!("this is an immutable {pointer_desc}"), | |
| ); | |
| return; | |
| } | |
| // Do not suggest changing type if that is not under user control. | |
| if self.is_closure_arg_with_non_locally_decided_type(local) { | |
| return; | |
| } | |
| let decl_span = local_decl.source_info.span; | |
| let (amp_mut_sugg, local_var_ty_info) = match *local_decl.local_info() { | |
| LocalInfo::User(mir::BindingForm::ImplicitSelf(_)) => { | |
| let (span, suggestion) = suggest_ampmut_self(self.infcx.tcx, decl_span); | |
| let additional = local_trait.map(|span| suggest_ampmut_self(self.infcx.tcx, span)); | |
| (AmpMutSugg::Type { span, suggestion, additional }, None) | |
| } | |
| LocalInfo::User(mir::BindingForm::Var(mir::VarBindingForm { | |
| binding_mode: BindingMode(ByRef::No, _), | |
| opt_ty_info, | |
| .. | |
| })) => { | |
| // Check if the RHS is from desugaring. | |
| let first_assignment = find_assignments(&self.body, local).first().copied(); | |
| let first_assignment_stmt = first_assignment | |
| .and_then(|loc| self.body[loc.block].statements.get(loc.statement_index)); | |
| trace!(?first_assignment_stmt); | |
| let opt_assignment_rhs_span = | |
| first_assignment.map(|loc| self.body.source_info(loc).span); | |
| let mut source_span = opt_assignment_rhs_span; | |
| if let Some(mir::Statement { | |
| source_info: _, | |
| kind: | |
| mir::StatementKind::Assign(box ( | |
| _, | |
| mir::Rvalue::Use(mir::Operand::Copy(place), _), | |
| )), | |
| .. | |
| }) = first_assignment_stmt | |
| { | |
| let local_span = self.body.local_decls[place.local].source_info.span; | |
| // `&self` in async functions have a `desugaring_kind`, but the local we assign | |
| // it with does not, so use the local_span for our checks later. | |
| source_span = Some(local_span); | |
| if let Some(DesugaringKind::ForLoop) = local_span.desugaring_kind() { | |
| // On for loops, RHS points to the iterator part. | |
| self.suggest_similar_mut_method_for_for_loop(err, local_span); | |
| err.span_label( | |
| local_span, | |
| format!("this iterator yields `{pointer_sigil}` {pointer_desc}s",), | |
| ); | |
| return; | |
| } | |
| } | |
| // Don't create labels for compiler-generated spans or spans not from users' code. | |
| if source_span.is_some_and(|s| { | |
| s.desugaring_kind().is_some() || self.infcx.tcx.sess.source_map().is_imported(s) | |
| }) { | |
| return; | |
| } | |
| // This could be because we're in an `async fn`. | |
| if name == kw::SelfLower && opt_ty_info.is_none() { | |
| let (span, suggestion) = suggest_ampmut_self(self.infcx.tcx, decl_span); | |
| (AmpMutSugg::Type { span, suggestion, additional: None }, None) | |
| } else if let Some(sugg) = | |
| suggest_ampmut(self.infcx, self.body(), first_assignment_stmt) | |
| { | |
| (sugg, opt_ty_info) | |
| } else { | |
| return; | |
| } | |
| } | |
| LocalInfo::User(mir::BindingForm::Var(mir::VarBindingForm { | |
| binding_mode: BindingMode(ByRef::Yes(..), _), | |
| .. | |
| })) => { | |
| let pattern_span: Span = local_decl.source_info.span; | |
| let Some(span) = suggest_ref_mut(self.infcx.tcx, pattern_span) else { | |
| return; | |
| }; | |
| (AmpMutSugg::Type { span, suggestion: "mut ".to_owned(), additional: None }, None) | |
| } | |
| _ => unreachable!(), | |
| }; | |
| let mut suggest = |suggs: Vec<_>, applicability, extra| { | |
| if suggs.iter().any(|(span, _)| self.infcx.tcx.sess.source_map().is_imported(*span)) { | |
| return; | |
| } | |
| err.multipart_suggestion( | |
| format!( | |
| "consider changing this to be a mutable {pointer_desc}{}{extra}", | |
| if is_trait_sig { | |
| " in the `impl` method and the `trait` definition" | |
| } else { | |
| "" | |
| } | |
| ), | |
| suggs, | |
| applicability, | |
| ); | |
| }; | |
| let (mut sugg, add_type_annotation_if_not_exists) = match amp_mut_sugg { | |
| AmpMutSugg::Type { span, suggestion, additional } => { | |
| let mut sugg = vec![(span, suggestion)]; | |
| sugg.extend(additional); | |
| suggest(sugg, Applicability::MachineApplicable, ""); | |
| return; | |
| } | |
| AmpMutSugg::MapGetMut { span, suggestion } => { | |
| if self.infcx.tcx.sess.source_map().is_imported(span) { | |
| return; | |
| } | |
| err.multipart_suggestion( | |
| "consider using `get_mut`", | |
| vec![(span, suggestion)], | |
| Applicability::MaybeIncorrect, | |
| ); | |
| return; | |
| } | |
| AmpMutSugg::Expr { span, suggestion } => { | |
| // `Expr` suggestions should change type annotations if they already exist (probably immut), | |
| // but do not add new type annotations. | |
| (vec![(span, suggestion)], false) | |
| } | |
| AmpMutSugg::ChangeBinding => (vec![], true), | |
| }; | |
| // Find a binding's type to make mutable. | |
| let (binding_exists, span) = match local_var_ty_info { | |
| // If this is a variable binding with an explicit type, | |
| // then we will suggest changing it to be mutable. | |
| // This is `Applicability::MachineApplicable`. | |
| Some(ty_span) => (true, ty_span), | |
| // Otherwise, we'll suggest *adding* an annotated type, we'll suggest | |
| // the RHS's type for that. | |
| // This is `Applicability::HasPlaceholders`. | |
| None => (false, decl_span), | |
| }; | |
| if !binding_exists && !add_type_annotation_if_not_exists { | |
| suggest(sugg, Applicability::MachineApplicable, ""); | |
| return; | |
| } | |
| // If the binding already exists and is a reference with an explicit | |
| // lifetime, then we can suggest adding ` mut`. This is special-cased from | |
| // the path without an explicit lifetime. | |
| let (sugg_span, sugg_str, suggest_now) = if let Ok(src) = self.infcx.tcx.sess.source_map().span_to_snippet(span) | |
| && src.starts_with("&'") | |
| // Note that `&' a T` is invalid so this is correct. | |
| && let Some(ws_pos) = src.find(char::is_whitespace) | |
| { | |
| let span = span.with_lo(span.lo() + BytePos(ws_pos as u32)).shrink_to_lo(); | |
| (span, " mut".to_owned(), true) | |
| // If there is already a binding, we modify it to be `mut`. | |
| } else if binding_exists { | |
| // Replace the sigil with the mutable version. We may be dealing | |
| // with parser recovery here and cannot assume the user actually | |
| // typed `&` or `*const`, so we compute the prefix from the snippet. | |
| let Ok(src) = self.infcx.tcx.sess.source_map().span_to_snippet(span) else { | |
| return; | |
| }; | |
| let (prefix_len, replacement) = if local_decl.ty.is_ref() { | |
| (src.chars().next().map_or(0, char::len_utf8), "&mut ") | |
| } else { | |
| (src.find("const").map_or(1, |i| i + "const".len()), "*mut ") | |
| }; | |
| let ws_len = src[prefix_len..].len() - src[prefix_len..].trim_start().len(); | |
| let span = span.with_hi(span.lo() + BytePos((prefix_len + ws_len) as u32)); | |
| (span, replacement.to_owned(), true) | |
| } else { | |
| // Otherwise, suggest that the user annotates the binding; We provide the | |
| // type of the local. | |
| let ty = local_decl.ty.builtin_deref(true).unwrap(); | |
| (span, format!("{}mut {}", if local_decl.ty.is_ref() { "&" } else { "*" }, ty), false) | |
| }; | |
| if suggest_now { | |
| // Suggest changing `&x` to `&mut x` and changing `&T` to `&mut T` at the same time. | |
| let has_change = !sugg.is_empty(); | |
| sugg.push((sugg_span, sugg_str)); | |
| suggest( | |
| sugg, | |
| Applicability::MachineApplicable, | |
| // FIXME(fee1-dead) this somehow doesn't fire | |
| if has_change { " and changing the binding's type" } else { "" }, | |
| ); | |
| return; | |
| } else if !sugg.is_empty() { | |
| suggest(sugg, Applicability::MachineApplicable, ""); | |
| return; | |
| } | |
| let def_id = self.body.source.def_id(); | |
| let hir_id = if let Some(local_def_id) = def_id.as_local() | |
| && let Some(body) = self.infcx.tcx.hir_maybe_body_owned_by(local_def_id) | |
| { | |
| BindingFinder { span: sugg_span }.visit_body(&body).break_value() | |
| } else { | |
| None | |
| }; | |
| let node = hir_id.map(|hir_id| self.infcx.tcx.hir_node(hir_id)); | |
| let Some(hir::Node::LetStmt(local)) = node else { | |
| err.span_label( | |
| sugg_span, | |
| format!("consider changing this binding's type to be: `{sugg_str}`"), | |
| ); | |
| return; | |
| }; | |
| let tables = self.infcx.tcx.typeck(def_id.as_local().unwrap()); | |
| if let Some(clone_trait) = self.infcx.tcx.lang_items().clone_trait() | |
| && let Some(expr) = local.init | |
| && let ty = tables.node_type_opt(expr.hir_id) | |
| && let Some(ty) = ty | |
| && let ty::Ref(..) = ty.kind() | |
| { | |
| match self | |
| .infcx | |
| .type_implements_trait_shallow(clone_trait, ty.peel_refs(), self.infcx.param_env) | |
| .as_deref() | |
| { | |
| Some([]) => { | |
| // FIXME: This error message isn't useful, since we're just | |
| // vaguely suggesting to clone a value that already | |
| // implements `Clone`. | |
| // | |
| // A correct suggestion here would take into account the fact | |
| // that inference may be affected by missing types on bindings, | |
| // etc., to improve "tests/ui/borrowck/issue-91206.stderr", for | |
| // example. | |
| } | |
| None => { | |
| if let hir::ExprKind::MethodCall(segment, _rcvr, [], span) = expr.kind | |
| && segment.ident.name == sym::clone | |
| { | |
| err.span_help( | |
| span, | |
| format!( | |
| "`{}` doesn't implement `Clone`, so this call clones \ | |
| the reference `{ty}`", | |
| ty.peel_refs(), | |
| ), | |
| ); | |
| } | |
| // The type doesn't implement Clone. | |
| let trait_ref = ty::Binder::dummy(ty::TraitRef::new( | |
| self.infcx.tcx, | |
| clone_trait, | |
| [ty.peel_refs()], | |
| )); | |
| let obligation = traits::Obligation::new( | |
| self.infcx.tcx, | |
| traits::ObligationCause::dummy(), | |
| self.infcx.param_env, | |
| trait_ref, | |
| ); | |
| self.infcx.err_ctxt().suggest_derive( | |
| &obligation, | |
| err, | |
| trait_ref.upcast(self.infcx.tcx), | |
| ); | |
| } | |
| Some(errors) => { | |
| if let hir::ExprKind::MethodCall(segment, _rcvr, [], span) = expr.kind | |
| && segment.ident.name == sym::clone | |
| { | |
| err.span_help( | |
| span, | |
| format!( | |
| "`{}` doesn't implement `Clone` because its \ | |
| implementations trait bounds could not be met, so \ | |
| this call clones the reference `{ty}`", | |
| ty.peel_refs(), | |
| ), | |
| ); | |
| err.note(format!( | |
| "the following trait bounds weren't met: {}", | |
| errors | |
| .iter() | |
| .map(|e| e.obligation.predicate.to_string()) | |
| .collect::<Vec<_>>() | |
| .join("\n"), | |
| )); | |
| } | |
| // The type doesn't implement Clone because of unmet obligations. | |
| for error in errors { | |
| if let traits::FulfillmentErrorCode::Select( | |
| traits::SelectionError::Unimplemented, | |
| ) = error.code | |
| && let ty::PredicateKind::Clause(ty::ClauseKind::Trait(pred)) = | |
| error.obligation.predicate.kind().skip_binder() | |
| { | |
| self.infcx.err_ctxt().suggest_derive( | |
| &error.obligation, | |
| err, | |
| error.obligation.predicate.kind().rebind(pred), | |
| ); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| let (changing, span, sugg) = match local.ty { | |
| Some(ty) => ("changing", ty.span, sugg_str), | |
| None => ("specifying", local.pat.span.shrink_to_hi(), format!(": {sugg_str}")), | |
| }; | |
| err.span_suggestion_verbose( | |
| span, | |
| format!("consider {changing} this binding's type"), | |
| sugg, | |
| Applicability::HasPlaceholders, | |
| ); | |
| } | |
| /// Returns `true` if `local` is an argument in a closure passed to a | |
| /// function defined in another crate. | |
| /// | |
| /// For example, in the following code this function returns `true` for `x` | |
| /// since `Option::inspect()` is not defined in the current crate: | |
| /// | |
| /// ```text | |
| /// some_option.as_mut().inspect(|x| { | |
| /// ``` | |
| fn is_closure_arg_with_non_locally_decided_type(&self, local: Local) -> bool { | |
| // We don't care about regular local variables, only args. | |
| if self.body.local_kind(local) != LocalKind::Arg { | |
| return false; | |
| } | |
| // Make sure we are inside a closure. | |
| let InstanceKind::Item(body_def_id) = self.body.source.instance else { | |
| return false; | |
| }; | |
| let Some(Node::Expr(hir::Expr { hir_id: body_hir_id, kind, .. })) = | |
| self.infcx.tcx.hir_get_if_local(body_def_id) | |
| else { | |
| return false; | |
| }; | |
| let ExprKind::Closure(hir::Closure { kind: hir::ClosureKind::Closure, .. }) = kind else { | |
| return false; | |
| }; | |
| // Check if the method/function that our closure is passed to is defined | |
| // in another crate. | |
| let Node::Expr(closure_parent) = self.infcx.tcx.parent_hir_node(*body_hir_id) else { | |
| return false; | |
| }; | |
| match closure_parent.kind { | |
| ExprKind::MethodCall(method, _, _, _) => self | |
| .infcx | |
| .tcx | |
| .typeck(method.hir_id.owner.def_id) | |
| .type_dependent_def_id(closure_parent.hir_id) | |
| .is_some_and(|def_id| !def_id.is_local()), | |
| ExprKind::Call(func, _) => self | |
| .infcx | |
| .tcx | |
| .typeck(func.hir_id.owner.def_id) | |
| .node_type_opt(func.hir_id) | |
| .and_then(|ty| match ty.kind() { | |
| ty::FnDef(def_id, _) => Some(def_id), | |
| _ => None, | |
| }) | |
| .is_some_and(|def_id| !def_id.is_local()), | |
| _ => false, | |
| } | |
| } | |
| } | |
| struct BindingFinder { | |
| span: Span, | |
| } | |
| impl<'tcx> Visitor<'tcx> for BindingFinder { | |
| type Result = ControlFlow<hir::HirId>; | |
| fn visit_stmt(&mut self, s: &'tcx hir::Stmt<'tcx>) -> Self::Result { | |
| if let hir::StmtKind::Let(local) = s.kind | |
| && local.pat.span == self.span | |
| { | |
| ControlFlow::Break(local.hir_id) | |
| } else { | |
| hir::intravisit::walk_stmt(self, s) | |
| } | |
| } | |
| fn visit_param(&mut self, param: &'tcx hir::Param<'tcx>) -> Self::Result { | |
| if let hir::Pat { kind: hir::PatKind::Ref(_, _, _), span, .. } = param.pat | |
| && *span == self.span | |
| { | |
| ControlFlow::Break(param.hir_id) | |
| } else { | |
| ControlFlow::Continue(()) | |
| } | |
| } | |
| } | |
| 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() { | |
| // Check if mutably borrowing a mutable reference. | |
| LocalInfo::User(mir::BindingForm::Var(mir::VarBindingForm { | |
| binding_mode: BindingMode(ByRef::No, Mutability::Not), | |
| .. | |
| })) => matches!(local_decl.ty.kind(), ty::Ref(_, _, hir::Mutability::Mut)), | |
| LocalInfo::User(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 into the next case with them. | |
| kind == hir::ImplicitSelfKind::RefMut | |
| } | |
| _ 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: TyCtxt<'_>, span: Span) -> (Span, String) { | |
| match tcx.sess.source_map().span_to_snippet(span) { | |
| Ok(snippet) if snippet.ends_with("self") => { | |
| (span.with_hi(span.hi() - BytePos(4)).shrink_to_hi(), "mut ".to_string()) | |
| } | |
| _ => (span, "&mut self".to_string()), | |
| } | |
| } | |
| enum AmpMutSugg { | |
| /// Type suggestion. Changes `&self` to `&mut self`, `x: &T` to `x: &mut T`, | |
| /// `ref x` to `ref mut x`, etc. | |
| Type { | |
| span: Span, | |
| suggestion: String, | |
| additional: Option<(Span, String)>, | |
| }, | |
| /// Suggestion for expressions, `&x` to `&mut x`, `&x[i]` to `&mut x[i]`, etc. | |
| Expr { | |
| span: Span, | |
| suggestion: String, | |
| }, | |
| /// Suggests `.get_mut` in the case of `&map[&key]` for Hash/BTreeMap. | |
| MapGetMut { | |
| span: Span, | |
| suggestion: String, | |
| }, | |
| ChangeBinding, | |
| } | |
| // 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>( | |
| infcx: &crate::BorrowckInferCtxt<'tcx>, | |
| body: &Body<'tcx>, | |
| opt_assignment_rhs_stmt: Option<&Statement<'tcx>>, | |
| ) -> Option<AmpMutSugg> { | |
| let tcx = infcx.tcx; | |
| // If there is a RHS and it starts with a `&` from it, then check if it is | |
| // mutable, and if not, put suggest putting `mut ` to make it mutable. | |
| // We don't have to worry about lifetime annotations here because they are | |
| // not valid when taking a reference. For example, the following is not valid Rust: | |
| // | |
| // let x: &i32 = &'a 5; | |
| // ^^ lifetime annotation not allowed | |
| // | |
| if let Some(rhs_stmt) = opt_assignment_rhs_stmt | |
| && let StatementKind::Assign(box (lhs, rvalue)) = &rhs_stmt.kind | |
| && let mut rhs_span = rhs_stmt.source_info.span | |
| && let Ok(mut rhs_str) = tcx.sess.source_map().span_to_snippet(rhs_span) | |
| { | |
| let mut rvalue = rvalue; | |
| // Take some special care when handling `let _x = &*_y`: | |
| // We want to know if this is part of an overloaded index, so `let x = &a[0]`, | |
| // or whether this is a usertype ascription (`let _x: &T = y`). | |
| if let Rvalue::Ref(_, BorrowKind::Shared, place) = rvalue | |
| && place.projection.len() == 1 | |
| && place.projection[0] == ProjectionElem::Deref | |
| && let Some(assign) = find_assignments(&body, place.local).first() | |
| { | |
| // If this is a usertype ascription (`let _x: &T = _y`) then pierce through it as either we want | |
| // to suggest `&mut` on the expression (handled here) or we return `None` and let the caller | |
| // suggest `&mut` on the type if the expression seems fine (e.g. `let _x: &T = &mut _y`). | |
| if let Some(user_ty_projs) = body.local_decls[lhs.local].user_ty.as_ref() | |
| && let [user_ty_proj] = user_ty_projs.contents.as_slice() | |
| && user_ty_proj.projs.is_empty() | |
| && let Either::Left(rhs_stmt_new) = body.stmt_at(*assign) | |
| && let StatementKind::Assign(box (_, rvalue_new)) = &rhs_stmt_new.kind | |
| && let rhs_span_new = rhs_stmt_new.source_info.span | |
| && let Ok(rhs_str_new) = tcx.sess.source_map().span_to_snippet(rhs_span_new) | |
| { | |
| (rvalue, rhs_span, rhs_str) = (rvalue_new, rhs_span_new, rhs_str_new); | |
| } | |
| if let Either::Right(call) = body.stmt_at(*assign) | |
| && let TerminatorKind::Call { | |
| func: Operand::Constant(box const_operand), args, .. | |
| } = &call.kind | |
| && let ty::FnDef(method_def_id, method_args) = *const_operand.ty().kind() | |
| && let Some(trait_) = tcx.trait_of_assoc(method_def_id) | |
| && tcx.is_lang_item(trait_, hir::LangItem::Index) | |
| { | |
| let trait_ref = ty::TraitRef::from_assoc( | |
| tcx, | |
| tcx.require_lang_item(hir::LangItem::IndexMut, rhs_span), | |
| method_args, | |
| ); | |
| // The type only implements `Index` but not `IndexMut`, we must not suggest `&mut`. | |
| if !infcx | |
| .type_implements_trait(trait_ref.def_id, trait_ref.args, infcx.param_env) | |
| .must_apply_considering_regions() | |
| { | |
| // Suggest `get_mut` if type is a `BTreeMap` or `HashMap`. | |
| if let ty::Adt(def, _) = trait_ref.self_ty().kind() | |
| && [sym::BTreeMap, sym::HashMap] | |
| .into_iter() | |
| .any(|s| tcx.is_diagnostic_item(s, def.did())) | |
| && let [map, key] = &**args | |
| && let Ok(map) = tcx.sess.source_map().span_to_snippet(map.span) | |
| && let Ok(key) = tcx.sess.source_map().span_to_snippet(key.span) | |
| { | |
| let span = rhs_span; | |
| let suggestion = format!("{map}.get_mut({key}).unwrap()"); | |
| return Some(AmpMutSugg::MapGetMut { span, suggestion }); | |
| } | |
| return None; | |
| } | |
| } | |
| } | |
| let sugg = match rvalue { | |
| Rvalue::Ref(_, BorrowKind::Shared, _) if let Some(ref_idx) = rhs_str.find('&') => { | |
| // Shrink the span to just after the `&` in `&variable`. | |
| Some(( | |
| rhs_span.with_lo(rhs_span.lo() + BytePos(ref_idx as u32 + 1)).shrink_to_lo(), | |
| "mut ".to_owned(), | |
| )) | |
| } | |
| Rvalue::RawPtr(RawPtrKind::Const, _) if let Some(const_idx) = rhs_str.find("const") => { | |
| // Suggest changing `&raw const` to `&raw mut` if applicable. | |
| let const_idx = const_idx as u32; | |
| Some(( | |
| rhs_span | |
| .with_lo(rhs_span.lo() + BytePos(const_idx)) | |
| .with_hi(rhs_span.lo() + BytePos(const_idx + "const".len() as u32)), | |
| "mut".to_owned(), | |
| )) | |
| } | |
| _ => None, | |
| }; | |
| if let Some((span, suggestion)) = sugg { | |
| return Some(AmpMutSugg::Expr { span, suggestion }); | |
| } | |
| } | |
| Some(AmpMutSugg::ChangeBinding) | |
| } | |
| /// If the type is a `Coroutine`, `Closure`, or `CoroutineClosure` | |
| fn is_closure_like(ty: Ty<'_>) -> bool { | |
| ty.is_closure() || ty.is_coroutine() || ty.is_coroutine_closure() | |
| } | |
| /// 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: FieldIdx, | |
| ) -> 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())? | |
| // 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) = tcx.hir_node_by_def_id(field.did.as_local()?) | |
| && let hir::TyKind::Ref(lt, hir::MutTy { mutbl: hir::Mutability::Not, ty }) = field.ty.kind | |
| { | |
| return Some(lt.ident.span.between(ty.span)); | |
| } | |
| None | |
| } | |
| /// If possible, suggest replacing `ref` with `ref mut`. | |
| fn suggest_ref_mut(tcx: TyCtxt<'_>, span: Span) -> Option<Span> { | |
| let pattern_str = tcx.sess.source_map().span_to_snippet(span).ok()?; | |
| if let Some(rest) = pattern_str.strip_prefix("ref") | |
| && rest.starts_with(rustc_lexer::is_whitespace) | |
| { | |
| let span = span.with_lo(span.lo() + BytePos(4)).shrink_to_lo(); | |
| Some(span) | |
| } else { | |
| None | |
| } | |
| } |