Skip to content
Open
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
142 changes: 112 additions & 30 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,60 +681,139 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
return;
}
};

// Lets see:
// because of TypeChecking and indexing, we know: index is &Q
// with K: Eq + Hash + Borrow<Q>,
// with Q: Eq + Hash + ?Sized,index is a &Q with K:Borrow<Q>,
//
//
//
// there is no other constraint on the types, therefore we need to look at the
// constraints of the suggestions:
// pub fn get_mut<Q>(&mut self, k: &Q) -> Option<&mut V>
// where
// K: Borrow<Q>,
// Q: Hash + Eq + ?Sized,
//
// pub fn insert(&mut self, k: K, v: V) -> Option<V>
// pub fn entry(&mut self, key: K) -> Entry<'_, K, V>
//
// But lets note that there could be also, if imported from hashbrown:
// pub fn entry_ref<'a, 'b, Q>(
// &'a mut self,
// key: &'b Q,
// ) -> EntryRef<'a, 'b, K, Q, V, S, A>
// where
// Q: Hash + Equivalent<K> + ?Sized,

/// testing if index AND key are &Q. We cannot simply test equality because the
/// lifetimes differ.
fn index_and_key_are_same_borrowed_type<'tcx>(
index: Ty<'tcx>,
key: Ty<'tcx>,
) -> bool {
if let (ty::Ref(_, index_inner_ty, _), ty::Ref(_, key_inner_ty, _)) =
(index.kind(), key.kind())
{
*index_inner_ty == *key_inner_ty
} else {
false
}
}
/// testing if index is &K:
fn index_is_borrowed_key<'tcx>(index: Ty<'tcx>, key: Ty<'tcx>) -> bool {
if let ty::Ref(_, inner_ty, _) = index.kind() {
*inner_ty == key
} else {
false
}
}
/// checking if the key is copy clone
fn key_is_copyclone<'tcx>(key: Ty<'tcx>) -> bool {
key.is_trivially_pure_clone_copy()
}

// 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();

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(
format!(
"use `.insert()` to insert a value into a `{}`, `.get_mut()` \
to modify it, or the entry API for more flexibility",
self.ty,
),
vec![
let index_ty =
self.infcx.tcx.typeck(val.hir_id.owner.def_id).expr_ty(index);
// only suggest `insert` and `entry` if K is copy/clone because of the signature.
let index_is_borrowed_key = index_is_borrowed_key(index_ty, key_type);
if (index_is_borrowed_key
|| index_and_key_are_same_borrowed_type(index_ty, key_type))
&& key_is_copyclone(key_type)
{
let offset = BytePos(index_is_borrowed_key as u32);
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()),
".insert(".to_string(),
val.span.shrink_to_hi().with_hi(index.span.lo() + offset), //remove the
//leading &
format!(".insert("),
),
(
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![
// 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);
// 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()),
".entry(".to_string(),
val.span.shrink_to_hi().with_hi(index.span.lo() + offset), //remove the
//leading &
format!(".entry("),
),
(
index.span.shrink_to_hi().with_hi(rv.span.lo()),
").or_insert(".to_string(),
").insert_entry(".to_string(),
),
(rv.span.shrink_to_hi(), ")".to_string()),
],
Applicability::MaybeIncorrect,
);
}
// 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(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()),
],
Applicability::MachineApplicable,
Applicability::MaybeIncorrect,
);
//FIXME: in the future, include the ref_entry suggestion here if it is added
//to std.

self.suggested = true;
} else if let hir::ExprKind::MethodCall(_path, receiver, _, sp) = expr.kind
&& let hir::ExprKind::Index(val, index, _) = receiver.kind
Expand All @@ -745,7 +826,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
(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("),
),
(
index.span.shrink_to_hi().with_hi(receiver.span.hi()),
Expand All @@ -768,6 +849,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
err,
ty,
suggested: false,
infcx: self.infcx,
};
v.visit_body(&body);
if !v.suggested {
Expand Down
10 changes: 7 additions & 3 deletions tests/ui/borrowck/index-mut-help.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -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 the entry API to modify a `HashMap<&str, String>` for more flexibility
|
LL - map["peter"] = "0".to_string();
LL + if let Some(val) = map.get_mut("peter") { *val = "0".to_string(); };
LL + let val = map.entry("peter").insert_entry("0".to_string());
|
help: use `.get_mut()` to modify an existing key in a `HashMap<&str, String>`
|
LL - map["peter"] = "0".to_string();
LL + let val = map.entry("peter").or_insert("0".to_string());
LL + if let Some(val) = map.get_mut("peter") { *val = "0".to_string(); };
|

error[E0596]: cannot borrow data in an index of `HashMap<&str, String>` as mutable
Expand Down
12 changes: 8 additions & 4 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 the entry API to modify a `BTreeMap<u32, u32>` for more flexibility
|
LL - map[&0] = 1;
LL + if let Some(val) = map.get_mut(&0) { *val = 1; };
LL + let val = map.entry(0).insert_entry(1);
|
help: use `.get_mut()` to modify an existing key in a `BTreeMap<u32, u32>`
|
LL - map[&0] = 1;
LL + let val = map.entry(&0).or_insert(1);
LL + if let Some(val) = map.get_mut(&0) { *val = 1; };
|

error: aborting due to 1 previous error
Expand Down
12 changes: 8 additions & 4 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 the entry API to modify a `BTreeMap<u32, u32>` for more flexibility
|
LL - map[&0] = 1;
LL + if let Some(val) = map.get_mut(&0) { *val = 1; };
LL + let val = map.entry(0).insert_entry(1);
|
help: use `.get_mut()` to modify an existing key in a `BTreeMap<u32, u32>`
|
LL - map[&0] = 1;
LL + let val = map.entry(&0).or_insert(1);
LL + if let Some(val) = map.get_mut(&0) { *val = 1; };
|

error: aborting due to 1 previous error
Expand Down
12 changes: 8 additions & 4 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 the entry API to modify a `HashMap<u32, u32>` for more flexibility
|
LL - map[&0] = 1;
LL + if let Some(val) = map.get_mut(&0) { *val = 1; };
LL + let val = map.entry(0).insert_entry(1);
|
help: use `.get_mut()` to modify an existing key in a `HashMap<u32, u32>`
|
LL - map[&0] = 1;
LL + let val = map.entry(&0).or_insert(1);
LL + if let Some(val) = map.get_mut(&0) { *val = 1; };
|

error: aborting due to 1 previous error
Expand Down
Loading