blob: bf5f2c0eec23edc8382fc0819410dd4811600948 [file] [log] [blame]
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, ClearCrossCrate, Local, LocalDecl, LocalInfo, LocalKind, Location},
};
use rustc_span::source_map::DesugaringKind;
use rustc_span::symbol::{kw, Symbol};
use rustc_span::Span;
use crate::borrow_check::diagnostics::BorrowedContentSource;
use crate::borrow_check::MirBorrowckCtxt;
use crate::util::collect_writes::FindAssignments;
use rustc_errors::{Applicability, DiagnosticBuilder};
#[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_place(access_place.as_ref());
debug!("report_mutability_error: access_place_desc={:?}", access_place_desc);
match the_place_err {
PlaceRef { local, projection: [] } => {
item_msg = format!("`{}`", access_place_desc.unwrap());
if access_place.as_local().is_some() {
reason = ", as it is not declared as mutable".to_string();
} else {
let name = self.local_names[local].expect("immutable unnamed local");
reason = format!(", as `{}` is not declared as mutable", name);
}
}
PlaceRef {
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 a 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 = format!("`{}`", access_place_desc.unwrap());
if self.is_upvar_field_projection(access_place.as_ref()).is_some() {
reason = ", as it is not declared as mutable".to_string();
} else {
let name = self.upvars[upvar_index.index()].place.to_string(self.infcx.tcx);
reason = format!(", as `{}` is not declared as mutable", name);
}
}
}
PlaceRef { local, projection: [ProjectionElem::Deref] }
if self.body.local_decls[local].is_ref_for_guard() =>
{
item_msg = format!("`{}`", access_place_desc.unwrap());
reason = ", as it is immutable for the pattern guard".to_string();
}
PlaceRef { local, projection: [ProjectionElem::Deref] }
if self.body.local_decls[local].is_ref_to_static() =>
{
if access_place.projection.len() == 1 {
item_msg = format!("immutable static item `{}`", access_place_desc.unwrap());
reason = String::new();
} else {
item_msg = format!("`{}`", access_place_desc.unwrap());
let local_info = &self.body.local_decls[local].local_info;
if let Some(box LocalInfo::StaticRef { def_id, .. }) = *local_info {
let static_name = &self.infcx.tcx.item_name(def_id);
reason = format!(", as `{}` is an immutable static item", static_name);
} else {
bug!("is_ref_to_static return true, but not ref to static?");
}
}
}
PlaceRef { 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 = format!("`{}`", access_place_desc.unwrap());
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) = access_place_desc {
item_msg = format!("`{}`", desc);
reason = match error_access {
AccessKind::Mutate => format!(" which is behind {}", pointer_type),
AccessKind::MutableBorrow => {
format!(", as it is behind {}", pointer_type)
}
}
} else {
item_msg = format!("data in {}", pointer_type);
reason = String::new();
}
}
}
PlaceRef {
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:
[proj_base @ .., ProjectionElem::Deref, ProjectionElem::Field(field, _), ProjectionElem::Deref],
} => {
err.span_label(span, format!("cannot {ACT}", ACT = act));
if let Some((span, message)) = annotate_struct_field(
self.infcx.tcx,
Place::ty_from(local, proj_base, self.body, self.infcx.tcx).ty,
field,
) {
err.span_suggestion(
span,
"consider changing this to be mutable",
message,
Applicability::MaybeIncorrect,
);
}
}
// Suggest removing a `&mut` from the use of a mutable reference.
PlaceRef { local, projection: [] }
if self
.body
.local_decls
.get(local)
.map(|l| mut_borrow_of_mutable_ref(l, self.local_names[local]))
.unwrap_or(false) =>
{
err.span_label(span, format!("cannot {ACT}", ACT = act));
err.span_label(span, "try removing `&mut` here");
}
// We want to suggest users use `let mut` for local (user
// variable) mutations...
PlaceRef { 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, 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::Binding(pat)) = self.infcx.tcx.hir().find(upvar_hir_id) {
if let hir::PatKind::Binding(
hir::BindingAnnotation::Unannotated,
_,
upvar_ident,
_,
) = pat.kind
{
err.span_suggestion(
upvar_ident.span,
"consider changing this to be mutable",
format!("mut {}", upvar_ident.name),
Applicability::MachineApplicable,
);
}
}
let tcx = self.infcx.tcx;
if let ty::Ref(_, ty, Mutability::Mut) = the_place_err.ty(self.body, tcx).ty.kind()
{
if let ty::Closure(id, _) = ty.kind() {
self.show_mutating_upvar(tcx, id, 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_label(span, "try removing `&mut` here");
}
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_ref().unwrap() {
box LocalInfo::User(ClearCrossCrate::Set(
mir::BindingForm::ImplicitSelf(_),
)) => {
let (span, suggestion) =
suggest_ampmut_self(self.infcx.tcx, local_decl);
Some((true, span, suggestion))
}
box 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 locations = self.body.find_assignments(local);
let opt_assignment_rhs_span = locations
.first()
.map(|&location| self.body.source_info(location).span);
let opt_desugaring_kind =
opt_assignment_rhs_span.and_then(|span| span.desugaring_kind());
match opt_desugaring_kind {
// on for loops, RHS points to the iterator part
Some(DesugaringKind::ForLoop(_)) => {
self.suggest_similar_mut_method_for_for_loop(&mut err);
Some((
false,
opt_assignment_rhs_span.unwrap(),
format!(
"this iterator yields `{SIGIL}` {DESC}s",
SIGIL = pointer_sigil,
DESC = pointer_desc
),
))
}
// don't create labels for compiler-generated spans
Some(_) => None,
None => {
let (span, suggestion) = suggest_ampmut(
self.infcx.tcx,
local_decl,
opt_assignment_rhs_span,
*opt_ty_info,
);
Some((true, span, suggestion))
}
}
}
box 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))
}
box 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, &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));
}
}
err.buffer(&mut self.errors_buffer);
}
/// 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 td = if let Some(a) =
self.infcx.tcx.impl_of_method(my_def).and_then(|x| self.infcx.tcx.trait_id_of_impl(x))
{
a
} else {
return (false, None);
};
(
true,
td.as_local().and_then(|tld| {
let h = hir_map.local_def_id_to_hir_id(tld);
match hir_map.find(h) {
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<'_>,
id: &hir::def_id::DefId,
the_place_err: PlaceRef<'tcx>,
err: &mut DiagnosticBuilder<'_>,
) {
let closure_local_def_id = id.expect_local();
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 a origin for this closure kind starting at this root variable so it's safe to unwrap here
let captured_places = tables.closure_min_captures[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::UpvarBorrow {
kind: 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 `{}` borrowed, but not mutably", upvar),
}
break;
}
}
if capture_reason.is_empty() {
bug!("upvar `{}` borrowed, but cannot find reason", upvar);
}
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 DiagnosticBuilder<'_>) {
use hir::{
BodyId, Expr,
ExprKind::{Block, Call, DropTemps, Match, MethodCall},
HirId, ImplItem, ImplItemKind, Item, ItemKind,
};
fn maybe_body_id_of_fn(hir_map: &Map<'tcx>, 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, ..),
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)
.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 {
if suggestions.peek().is_some() {
err.span_suggestions(
path_segment.ident.span,
&format!("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 DiagnosticBuilder<'_>, 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 item_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.span == self.body.span)
.map(|(pos, _)| pos)
.next();
let def_id = hir.local_def_id(item_id);
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`");
if self.infcx.tcx.sess.source_map().is_multiline(self.body.span) {
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(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>,
) -> (Span, String) {
if let Some(assignment_rhs_span) = opt_assignment_rhs_span {
if let Ok(src) = tcx.sess.source_map().span_to_snippet(assignment_rhs_span) {
if let (true, Some(ws_pos)) =
(src.starts_with("&'"), src.find(|c: char| -> bool { c.is_whitespace() }))
{
let lt_name = &src[1..ws_pos];
let ty = &src[ws_pos..];
if !ty.trim_start().starts_with("mut") {
return (assignment_rhs_span, format!("&{} mut {}", lt_name, ty));
}
} else if let Some(stripped) = src.strip_prefix('&') {
if !stripped.trim_start().starts_with("mut") {
return (assignment_rhs_span, format!("&mut {}", stripped));
}
}
}
}
let highlight_span = match opt_ty_info {
// if this is a variable binding with an explicit type,
// try to highlight that for the suggestion.
Some(ty_span) => ty_span,
// otherwise, just highlight the span associated with
// the (MIR) LocalDecl.
None => local_decl.source_info.span,
};
if let Ok(src) = tcx.sess.source_map().span_to_snippet(highlight_span) {
if let (true, Some(ws_pos)) =
(src.starts_with("&'"), src.find(|c: char| -> bool { c.is_whitespace() }))
{
let lt_name = &src[1..ws_pos];
let ty = &src[ws_pos..];
return (highlight_span, format!("&{} mut{}", lt_name, ty));
}
}
let ty_mut = local_decl.ty.builtin_deref(true).unwrap();
assert_eq!(ty_mut.mutbl, hir::Mutability::Not);
(
highlight_span,
if local_decl.ty.is_region_ptr() {
format!("&mut {}", ty_mut.ty)
} else {
format!("*mut {}", ty_mut.ty)
},
)
}
fn is_closure_or_generator(ty: Ty<'_>) -> bool {
ty.is_closure() || ty.is_generator()
}
/// Adds a suggestion to a struct definition given a field access to a local.
/// This function expects the local to be a reference to a struct in order to produce a suggestion.
///
/// ```text
/// LL | s: &'a String
/// | ---------- use `&'a mut String` here to make mutable
/// ```
fn annotate_struct_field(
tcx: TyCtxt<'tcx>,
ty: Ty<'tcx>,
field: &mir::Field,
) -> Option<(Span, String)> {
// Expect our local to be a reference to a struct of some kind.
if let ty::Ref(_, ty, _) = ty.kind() {
if let ty::Adt(def, _) = ty.kind() {
let field = def.all_fields().nth(field.index())?;
// Use the HIR types to construct the diagnostic message.
let hir_id = tcx.hir().local_def_id_to_hir_id(field.did.as_local()?);
let node = tcx.hir().find(hir_id)?;
// Now we're dealing with the actual struct that we're going to suggest a change to,
// we can expect a field that is an immutable reference to a type.
if let hir::Node::Field(field) = node {
if let hir::TyKind::Rptr(
lifetime,
hir::MutTy { mutbl: hir::Mutability::Not, ref ty },
) = field.ty.kind
{
// Get the snippets in two parts - the named lifetime (if there is one) and
// type being referenced, that way we can reconstruct the snippet without loss
// of detail.
let type_snippet = tcx.sess.source_map().span_to_snippet(ty.span).ok()?;
let lifetime_snippet = if !lifetime.is_elided() {
format!("{} ", tcx.sess.source_map().span_to_snippet(lifetime.span).ok()?)
} else {
String::new()
};
return Some((
field.ty.span,
format!("&{}mut {}", lifetime_snippet, &*type_snippet,),
));
}
}
}
}
None
}
/// If possible, suggest replacing `ref` with `ref mut`.
fn suggest_ref_mut(tcx: TyCtxt<'_>, binding_span: Span) -> Option<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
}
}