Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 114 additions & 45 deletions compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -668,7 +668,9 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
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);
Expand All @@ -679,73 +681,139 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
return;
}
};

/// 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>(ty: Ty<'tcx>) -> usize {
let mut count = 0;
let mut ty = ty;
while let ty::Ref(_, inner_ty, _) = ty.kind() {
ty = *inner_ty;
count += 1;
}
count
}

// we know ty is a map, with a key type at walk distance 2.
let key_type = self.ty.walk().nth(1).unwrap().expect_ty();
let key_ref_depth = count_ty_refs(key_type);

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;
// ---------- place
self.err.multipart_suggestions(
let (prefix, gm_prefix) = {
let ref_depth_difference: usize;

if let Some(index_ty) =
self.infcx.tcx.typeck(val.hir_id.owner.def_id).expr_ty_opt(index)
{
let index_ref_depth = count_ty_refs(index_ty);
ref_depth_difference = index_ref_depth - key_ref_depth; //index should
//be deeper than key
} else {
// no type ?
// FIXME: unsure how to handle this case
return;
};

// remove the excessive referencing if necessary, but get_mut requires a ref
match ref_depth_difference {
0 => (String::new(), String::from("&")),
n => ("*".repeat(n), "*".repeat(n - 1)),
}
};

self.err.multipart_suggestion(
format!("use `.insert()` to insert a value into a `{}`", self.ty),
vec![
// val.insert(index, rv);
(
val.span.shrink_to_hi().with_hi(index.span.lo()),
format!(".insert({prefix}"),
),
(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 `.insert()` to insert a value into a `{}`, `.get_mut()` \
to modify it, or the entry API for more flexibility",
"use `.get_mut()` to modify an existing key in a `{}`",
self.ty,
),
vec![
vec![
// val.insert(index, rv);
(
val.span.shrink_to_hi().with_hi(index.span.lo()),
".insert(".to_string(),
),
(
index.span.shrink_to_hi().with_hi(rv.span.lo()),
", ".to_string(),
),
(rv.span.shrink_to_hi(), ")".to_string()),
],
vec![
// if let Some(v) = val.get_mut(index) { *v = rv; }
(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(place.span.hi()),
") { *val".to_string(),
),
(rv.span.shrink_to_hi(), "; }".to_string()),
],
vec![
// let x = val.entry(index).or_insert(rv);
(val.span.shrink_to_lo(), "let val = ".to_string()),
(
val.span.shrink_to_hi().with_hi(index.span.lo()),
".entry(".to_string(),
),
(
index.span.shrink_to_hi().with_hi(rv.span.lo()),
").or_insert(".to_string(),
),
(rv.span.shrink_to_hi(), ")".to_string()),
],
// if let Some(v) = val.get_mut(index) { *v = rv; }
(val.span.shrink_to_lo(), "if let Some(val) = ".to_string()),
(
val.span.shrink_to_hi().with_hi(index.span.lo()),
format!(".get_mut({gm_prefix}"),
),
(
index.span.shrink_to_hi().with_hi(place.span.hi()),
") { *val".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(index).insert_entry(rv);
(val.span.shrink_to_lo(), "let val = ".to_string()),
(
val.span.shrink_to_hi().with_hi(index.span.lo()),
format!(".entry({prefix}"),
),
(
index.span.shrink_to_hi().with_hi(rv.span.lo()),
").insert_entry(".to_string(),
),
(rv.span.shrink_to_hi(), ")".to_string()),
],
Applicability::MachineApplicable,
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
{
let gm_prefix = {
let ref_depth_difference: usize;

if let Some(index_ty) =
self.infcx.tcx.typeck(val.hir_id.owner.def_id).expr_ty_opt(index)
{
let index_ref_depth = count_ty_refs(index_ty);
ref_depth_difference = index_ref_depth - key_ref_depth; //index should
//be deeper than key
} else {
// no type ?
// FIXME: unsure how to handle this case
return;
};

// remove the excessive referencing if necessary, but get_mut requires a ref
match ref_depth_difference {
0 => String::from("&"),
n => "*".repeat(n - 1),
}
};
// 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(),
format!(".get_mut({gm_prefix}"),
),
(
index.span.shrink_to_hi().with_hi(receiver.span.hi()),
Expand All @@ -768,6 +836,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
err,
ty,
suggested: false,
infcx: self.infcx,
};
v.visit_body(&body);
if !v.suggested {
Expand Down
12 changes: 8 additions & 4 deletions tests/ui/borrowck/index-mut-help.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ LL | map["peter"].clear();
help: to modify a `HashMap<&str, String>` use `.get_mut()`
|
LL - map["peter"].clear();
LL + if let Some(val) = map.get_mut("peter") { val.clear(); };
LL + if let Some(val) = map.get_mut(&"peter") { val.clear(); };
|

error[E0594]: cannot assign to data in an index of `HashMap<&str, String>`
Expand All @@ -18,16 +18,20 @@ LL | map["peter"] = "0".to_string();
| ^^^^^^^^^^^^ cannot assign
|
= help: trait `IndexMut` is required to modify indexed content, but it is not implemented for `HashMap<&str, String>`
help: use `.insert()` to insert a value into a `HashMap<&str, String>`, `.get_mut()` to modify it, or the entry API for more flexibility
help: use `.insert()` to insert a value into a `HashMap<&str, String>`
|
LL - map["peter"] = "0".to_string();
LL + map.insert("peter", "0".to_string());
|
help: use `.get_mut()` to modify an existing key in a `HashMap<&str, String>`
|
LL - map["peter"] = "0".to_string();
LL + if let Some(val) = map.get_mut("peter") { *val = "0".to_string(); };
LL + if let Some(val) = map.get_mut(&"peter") { *val = "0".to_string(); };
|
help: use the entry API to modify a `HashMap<&str, String>` for more flexibility
|
LL - map["peter"] = "0".to_string();
LL + let val = map.entry("peter").or_insert("0".to_string());
LL + let val = map.entry("peter").insert_entry("0".to_string());
|

error[E0596]: cannot borrow data in an index of `HashMap<&str, String>` as mutable
Expand Down
10 changes: 7 additions & 3 deletions tests/ui/collections/btreemap/btreemap-index-mut-2.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,20 @@ LL | map[&0] = 1;
| ^^^^^^^^^^^ cannot assign
|
= help: trait `IndexMut` is required to modify indexed content, but it is not implemented for `BTreeMap<u32, u32>`
help: use `.insert()` to insert a value into a `BTreeMap<u32, u32>`, `.get_mut()` to modify it, or the entry API for more flexibility
help: use `.insert()` to insert a value into a `BTreeMap<u32, u32>`
|
LL - map[&0] = 1;
LL + map.insert(&0, 1);
LL + map.insert(*&0, 1);
|
help: use `.get_mut()` to modify an existing key in a `BTreeMap<u32, u32>`
|
LL - map[&0] = 1;
LL + if let Some(val) = map.get_mut(&0) { *val = 1; };
|
help: use the entry API to modify a `BTreeMap<u32, u32>` for more flexibility
|
LL - map[&0] = 1;
LL + let val = map.entry(&0).or_insert(1);
LL + let val = map.entry(*&0).insert_entry(1);
|

error: aborting due to 1 previous error
Expand Down
10 changes: 7 additions & 3 deletions tests/ui/collections/btreemap/btreemap-index-mut.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,20 @@ LL | map[&0] = 1;
| ^^^^^^^^^^^ cannot assign
|
= help: trait `IndexMut` is required to modify indexed content, but it is not implemented for `BTreeMap<u32, u32>`
help: use `.insert()` to insert a value into a `BTreeMap<u32, u32>`, `.get_mut()` to modify it, or the entry API for more flexibility
help: use `.insert()` to insert a value into a `BTreeMap<u32, u32>`
|
LL - map[&0] = 1;
LL + map.insert(&0, 1);
LL + map.insert(*&0, 1);
|
help: use `.get_mut()` to modify an existing key in a `BTreeMap<u32, u32>`
|
LL - map[&0] = 1;
LL + if let Some(val) = map.get_mut(&0) { *val = 1; };
|
help: use the entry API to modify a `BTreeMap<u32, u32>` for more flexibility
|
LL - map[&0] = 1;
LL + let val = map.entry(&0).or_insert(1);
LL + let val = map.entry(*&0).insert_entry(1);
|

error: aborting due to 1 previous error
Expand Down
10 changes: 7 additions & 3 deletions tests/ui/collections/hashmap/hashmap-index-mut.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,20 @@ LL | map[&0] = 1;
| ^^^^^^^^^^^ cannot assign
|
= help: trait `IndexMut` is required to modify indexed content, but it is not implemented for `HashMap<u32, u32>`
help: use `.insert()` to insert a value into a `HashMap<u32, u32>`, `.get_mut()` to modify it, or the entry API for more flexibility
help: use `.insert()` to insert a value into a `HashMap<u32, u32>`
|
LL - map[&0] = 1;
LL + map.insert(&0, 1);
LL + map.insert(*&0, 1);
|
help: use `.get_mut()` to modify an existing key in a `HashMap<u32, u32>`
|
LL - map[&0] = 1;
LL + if let Some(val) = map.get_mut(&0) { *val = 1; };
|
help: use the entry API to modify a `HashMap<u32, u32>` for more flexibility
|
LL - map[&0] = 1;
LL + let val = map.entry(&0).or_insert(1);
LL + let val = map.entry(*&0).insert_entry(1);
|

error: aborting due to 1 previous error
Expand Down
Loading