diff --git a/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs b/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs index 96090e85e5622..421b5cb8f3ed3 100644 --- a/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs @@ -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); @@ -679,24 +681,83 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { return; } }; + + // Lets see: + // because of TypeChecking and indexing, we know: index is &Q + // with K: Eq + Hash + Borrow, + // with Q: Eq + Hash + ?Sized,index is a &Q with K:Borrow, + // + // + // + // there is no other constraint on the types, therefore we need to look at the + // constraints of the suggestions: + // pub fn get_mut(&mut self, k: &Q) -> Option<&mut V> + // where + // K: Borrow, + // Q: Hash + Eq + ?Sized, + // + // pub fn insert(&mut self, k: K, v: V) -> Option + // 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 + ?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()), @@ -704,35 +765,55 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { ), (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 or Q:Borrow 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 @@ -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()), @@ -768,6 +849,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { err, ty, suggested: false, + infcx: self.infcx, }; v.visit_body(&body); if !v.suggested { diff --git a/tests/ui/borrowck/index-mut-help.stderr b/tests/ui/borrowck/index-mut-help.stderr index 6c3bd0df20b20..fc8d02b0f0b43 100644 --- a/tests/ui/borrowck/index-mut-help.stderr +++ b/tests/ui/borrowck/index-mut-help.stderr @@ -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 diff --git a/tests/ui/collections/btreemap/btreemap-index-mut-2.stderr b/tests/ui/collections/btreemap/btreemap-index-mut-2.stderr index 74a8aaf8aee9a..bb82ef43ff112 100644 --- a/tests/ui/collections/btreemap/btreemap-index-mut-2.stderr +++ b/tests/ui/collections/btreemap/btreemap-index-mut-2.stderr @@ -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` -help: use `.insert()` to insert a value into a `BTreeMap`, `.get_mut()` to modify it, or the entry API for more flexibility +help: use `.insert()` to insert a value into a `BTreeMap` | LL - map[&0] = 1; -LL + map.insert(&0, 1); +LL + map.insert(0, 1); + | +help: use the entry API to modify a `BTreeMap` 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` | 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 diff --git a/tests/ui/collections/btreemap/btreemap-index-mut.stderr b/tests/ui/collections/btreemap/btreemap-index-mut.stderr index e8850ed2a1742..e09ede34db563 100644 --- a/tests/ui/collections/btreemap/btreemap-index-mut.stderr +++ b/tests/ui/collections/btreemap/btreemap-index-mut.stderr @@ -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` -help: use `.insert()` to insert a value into a `BTreeMap`, `.get_mut()` to modify it, or the entry API for more flexibility +help: use `.insert()` to insert a value into a `BTreeMap` | LL - map[&0] = 1; -LL + map.insert(&0, 1); +LL + map.insert(0, 1); + | +help: use the entry API to modify a `BTreeMap` 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` | 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 diff --git a/tests/ui/collections/hashmap/hashmap-index-mut.stderr b/tests/ui/collections/hashmap/hashmap-index-mut.stderr index e8b22350c59da..bb291367dd438 100644 --- a/tests/ui/collections/hashmap/hashmap-index-mut.stderr +++ b/tests/ui/collections/hashmap/hashmap-index-mut.stderr @@ -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` -help: use `.insert()` to insert a value into a `HashMap`, `.get_mut()` to modify it, or the entry API for more flexibility +help: use `.insert()` to insert a value into a `HashMap` | LL - map[&0] = 1; -LL + map.insert(&0, 1); +LL + map.insert(0, 1); + | +help: use the entry API to modify a `HashMap` 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` | 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