From cb37077612667e80022bba496d9dc48447550a7e Mon Sep 17 00:00:00 2001 From: GTimothy <22472919+GTimothy@users.noreply.github.com> Date: Tue, 16 Dec 2025 09:48:50 +0100 Subject: [PATCH 1/4] Fix 'assign to data in an index of' collection suggestions splits the large triple suggestion into three sets them to MaybeIncorrect automatically determines the required/nicest borrowing to use. --- .../src/diagnostics/mutability_errors.rs | 167 ++++++++++++++---- 1 file changed, 135 insertions(+), 32 deletions(-) diff --git a/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs b/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs index 33bb4889ae22d..1b6e828950bab 100644 --- a/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs @@ -6,7 +6,7 @@ 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, Node}; +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; @@ -669,7 +669,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); @@ -680,60 +682,160 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { return; } }; + + // Because of TypeChecking and indexing, we know: index is &Q + // with K: Eq + Hash + Borrow, + // 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; - // ---------- 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); + + let count_refs_diff: isize = + count_ty_refs(index_ty) as isize - count_ty_refs(key_ty) as isize; + + let (borrowed_prefix, borrowed_index); + + // only suggest `insert` and `entry` if index is of type K or &{n}K. + // 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() { + assert!( + count_refs_diff >= 0, + "compiler bug I think, please report this" + ); + // if base type is same, borrowed depth must be exact. + let (deref_prefix, deref_index) = + strip_n_refs(index, count_refs_diff as usize) + .map(|val| ("".to_string(), val)) + .unwrap_or_else(|(depth, val)| { + ( + "*".repeat( + usize::try_from(count_refs_diff) + .expect("passed assert") + - depth, + ), + val, + ) + }); + + self.err.multipart_suggestion( + format!("use `.insert()` to insert a value into a `{}`", self.ty), vec![ - // val.insert(index, rv); + // val.insert({deref_prefix}{deref_index}, rv); ( - val.span.shrink_to_hi().with_hi(index.span.lo()), - ".insert(".to_string(), + val.span.shrink_to_hi().with_hi(deref_index.span.lo()), + format!(".insert({deref_prefix}"), ), ( - index.span.shrink_to_hi().with_hi(rv.span.lo()), + 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![ - // 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({deref_prefix}{deref_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(deref_index.span.lo()), + format!(".entry({deref_prefix}"), ), ( - index.span.shrink_to_hi().with_hi(rv.span.lo()), - ").or_insert(".to_string(), + 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) = + match strip_n_refs(index, 0.max(count_refs_diff - 1) as usize) { + Ok(val) => (String::new(), val), + // even if we tried to strip more, we can stop there thanks to + // autoderef + Err((_depth, val)) => (String::new(), val), + }; + } else { + (borrowed_prefix, borrowed_index) = (String::new(), index) + } + // 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({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::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 @@ -769,6 +871,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { err, ty, suggested: false, + infcx: self.infcx, }; v.visit_body(&body); if !v.suggested { From f2bf6a891154d7fe857c2c627bdbd07f8c051781 Mon Sep 17 00:00:00 2001 From: GTimothy <22472919+GTimothy@users.noreply.github.com> Date: Sat, 14 Feb 2026 10:21:49 +0100 Subject: [PATCH 2/4] rebless the ui tests --- tests/ui/borrowck/index-mut-help.stderr | 10 +++++++--- .../collections/btreemap/btreemap-index-mut-2.stderr | 12 ++++++++---- .../collections/btreemap/btreemap-index-mut.stderr | 12 ++++++++---- .../ui/collections/hashmap/hashmap-index-mut.stderr | 12 ++++++++---- 4 files changed, 31 insertions(+), 15 deletions(-) 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 From 9de80d417987072795f2463bbaacf7c89d461b0b Mon Sep 17 00:00:00 2001 From: GTimothy <22472919+GTimothy@users.noreply.github.com> Date: Thu, 30 Apr 2026 12:12:04 +0200 Subject: [PATCH 3/4] add index-mut test case where mut index access to a map produces E0277 instead of E0594 a set of slightly different tests showing that E0594 can be reached in some cases via autodereferencing, but also that slight variations produce E0277 instead. --- tests/ui/borrowck/index-mut-help2.rs | 153 ++++++++++++++++++ tests/ui/borrowck/index-mut-help2.stderr | 189 +++++++++++++++++++++++ 2 files changed, 342 insertions(+) create mode 100644 tests/ui/borrowck/index-mut-help2.rs create mode 100644 tests/ui/borrowck/index-mut-help2.stderr diff --git a/tests/ui/borrowck/index-mut-help2.rs b/tests/ui/borrowck/index-mut-help2.rs new file mode 100644 index 0000000000000..0b700ef1e0f0a --- /dev/null +++ b/tests/ui/borrowck/index-mut-help2.rs @@ -0,0 +1,153 @@ +// When mutably indexing a type that implements `Index` but not `IndexMut`, a +// special 'help' message is added to the output. +// ... Except when it is not! +use std::borrow::Borrow; +use std::collections::HashMap; + +#[derive(Hash, Eq, PartialEq)] +struct A {} +#[derive(Hash, Eq, PartialEq, Copy, Clone)] +struct ACopy {} +#[derive(Hash, Eq, PartialEq)] +struct B {} +#[derive(Hash, Eq, PartialEq)] +struct C {} +#[derive(Hash, Eq, PartialEq)] +struct D {} + +impl Borrow for D { + fn borrow(&self) -> &C { + &C {} + } +} + +/// In this test the key is a A type. +fn test_a_index() { + let mut map = HashMap::::new(); + + let index = &&&&&A {}; + // index gets autodereferenced + map[index] = 23; //~ ERROR E0594 + map[&&&&&&&&index] = 23; //~ ERROR E0594 +} +/// In this test the key is a A type. Auto-dereferencing to &A works but dereferencing to A need +/// the exact amount of `*` +fn test_a_2() { + let mut map = HashMap::::new(); + + let index = &&&&&A {}; + // complete dereferencing needs to be exact + map.insert(*index, 23); //~ ERROR E0308 + // index gets autodereferenced + if let Some(val) = map.get_mut(index) { + *val = 23; + } // passes +} +/// In this test the key is a A type. Could not be merged with 2 because compiler only shows error +/// 0308 +fn test_a_3() { + let mut map = HashMap::::new(); + + let index = &&&&&A {}; + // A does not implement Copy so a Clone might be required + map.insert(*****index, 23); //~ ERROR E0507 +} + +/// In this test the key is a ACopy type. +fn test_acopy_index() { + let mut map = HashMap::::new(); + + let index = &&&&&ACopy {}; + // index gets autodereferenced + map[index] = 23; //~ ERROR E0594 +} +/// In this test the key is a ACopy type. Auto-dereferencing to &A works but dereferencing to A +/// need the exact amount of `*`. +fn test_acopy_2() { + let mut map = HashMap::::new(); + + let index = &&&&&ACopy {}; + // complete dereferencing needs to be exact + map.insert(*index, 23); //~ ERROR E0308 + + // index gets autodereferenced + if let Some(val) = map.get_mut(index) { + *val = 23; + } // passes +} +/// In this test the key is a ACopy type. Could not be merged with 2 because compiler only shows +/// error 0308 +fn test_acopy_3() { + let mut map = HashMap::::new(); + + let index = &&&&&ACopy {}; + map.insert(*****index, 23); // no E057 error in this case because ACopy is Copy +} + +/// In this test the key type is B-reference. The autodereferencing does not work in this case for +/// both for the map[index] part and `get_mut` call. +/// This leads to E0277 errors. +fn test_b() { + let mut map = HashMap::<&B, u32>::new(); + + let index = &&&&&B {}; + // index does NOT get autorederefenced + map[index] = 23; //~ ERROR E0277 + + // index does NOT get autorederefenced + if let Some(val) = map.get_mut(index) { //~ ERROR E0277 + *val = 23; + } + if let Some(val) = map.get_mut(***index) { + *val = 23; + } // passes +} + + +/// In this test the key type is C. +/// The `Borrow for D` implementation changes nothing here, same error as for test_a. +fn test_c() { + let mut map = HashMap::::new(); + + let index = &&&&&C {}; + // index gets autodereferenced + map[index] = 23; //~ ERROR E0594 + + // index gets autodereferenced + if let Some(val) = map.get_mut(index) { + *val = 23; + } // passes +} + +/// In this test the key type is D. The `Borrow for D` implementation seems to prevent +/// autodereferencing. +/// The autodereferencing does not work in this case for both for the map[index] part and `get_mut` +/// call. +/// This leads to E0277 errors. +fn test_d() { + let mut map = HashMap::::new(); + + let index = &&&&&D {}; + // index does NOT get autorederefenced + map[index] = 23; //~ ERROR E0277 + + // index does NOT get autorederefenced + if let Some(val) = map.get_mut(index) {//~ ERROR E0277 + *val = 23; + } + if let Some(val) = map.get_mut(****index) { + *val = 23; + } // passes +} + +fn main() { + test_a_index(); + test_a_2(); + test_a_3(); + test_acopy_index(); + test_acopy_2(); + test_acopy_3(); + test_b(); + test_c(); + test_d(); +} diff --git a/tests/ui/borrowck/index-mut-help2.stderr b/tests/ui/borrowck/index-mut-help2.stderr new file mode 100644 index 0000000000000..244246f496fc2 --- /dev/null +++ b/tests/ui/borrowck/index-mut-help2.stderr @@ -0,0 +1,189 @@ +error[E0308]: mismatched types + --> $DIR/index-mut-help2.rs:40:16 + | +LL | map.insert(*index, 23); + | ------ ^^^^^^ expected `A`, found `&&&&A` + | | + | arguments to this method are incorrect + | +note: method defined here + --> $SRC_DIR/std/src/collections/hash/map.rs:LL:COL + +error[E0308]: mismatched types + --> $DIR/index-mut-help2.rs:71:16 + | +LL | map.insert(*index, 23); + | ------ ^^^^^^ expected `ACopy`, found `&&&&ACopy` + | | + | arguments to this method are incorrect + | +note: method defined here + --> $SRC_DIR/std/src/collections/hash/map.rs:LL:COL +help: consider dereferencing the borrow + | +LL | map.insert(*****index, 23); + | ++++ + +error[E0277]: the trait bound `&B: Borrow<&&&&B>` is not satisfied + --> $DIR/index-mut-help2.rs:95:9 + | +LL | map[index] = 23; + | ^^^^^ the trait `Borrow<&&&&B>` is not implemented for `&B` + | + = note: required for `HashMap<&B, u32>` to implement `Index<&&&&&B>` + +error[E0277]: the trait bound `&B: Borrow<&&&&B>` is not satisfied + --> $DIR/index-mut-help2.rs:98:36 + | +LL | if let Some(val) = map.get_mut(index) { + | ------- ^^^^^ the trait `Borrow<&&&&B>` is not implemented for `&B` + | | + | required by a bound introduced by this call + | +note: required by a bound in `HashMap::::get_mut` + --> $SRC_DIR/std/src/collections/hash/map.rs:LL:COL + +error[E0277]: the trait bound `D: Borrow<&&&&D>` is not satisfied + --> $DIR/index-mut-help2.rs:132:9 + | +LL | map[index] = 23; + | ^^^^^ unsatisfied trait bound + | +help: the trait `Borrow<&&&&D>` is not implemented for `D` + but trait `Borrow` is implemented for it + --> $DIR/index-mut-help2.rs:18:1 + | +LL | impl Borrow for D { + | ^^^^^^^^^^^^^^^^^^^^ + = help: for that trait implementation, expected `C`, found `&&&&D` + = note: required for `HashMap` to implement `Index<&&&&&D>` + +error[E0277]: the trait bound `D: Borrow<&&&&D>` is not satisfied + --> $DIR/index-mut-help2.rs:135:36 + | +LL | if let Some(val) = map.get_mut(index) { + | ------- ^^^^^ unsatisfied trait bound + | | + | required by a bound introduced by this call + | +help: the trait `Borrow<&&&&D>` is not implemented for `D` + but trait `Borrow` is implemented for it + --> $DIR/index-mut-help2.rs:18:1 + | +LL | impl Borrow for D { + | ^^^^^^^^^^^^^^^^^^^^ + = help: for that trait implementation, expected `C`, found `&&&&D` +note: required by a bound in `HashMap::::get_mut` + --> $SRC_DIR/std/src/collections/hash/map.rs:LL:COL + +error[E0594]: cannot assign to data in an index of `HashMap` + --> $DIR/index-mut-help2.rs:30:5 + | +LL | map[index] = 23; + | ^^^^^^^^^^^^^^^ 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` + | +LL - map[index] = 23; +LL + map.insert(*****index, 23); + | +help: use the entry API to modify a `HashMap` for more flexibility + | +LL - map[index] = 23; +LL + let val = map.entry(*****index).insert_entry(23); + | +help: use `.get_mut()` to modify an existing key in a `HashMap` + | +LL - map[index] = 23; +LL + if let Some(val) = map.get_mut(index) { *val = 23; }; + | + +error[E0594]: cannot assign to data in an index of `HashMap` + --> $DIR/index-mut-help2.rs:31:5 + | +LL | map[&&&&&&&&index] = 23; + | ^^^^^^^^^^^^^^^^^^^^^^^ 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` + | +LL - map[&&&&&&&&index] = 23; +LL + map.insert(*****index, 23); + | +help: use the entry API to modify a `HashMap` for more flexibility + | +LL - map[&&&&&&&&index] = 23; +LL + let val = map.entry(*****index).insert_entry(23); + | +help: use `.get_mut()` to modify an existing key in a `HashMap` + | +LL - map[&&&&&&&&index] = 23; +LL + if let Some(val) = map.get_mut(index) { *val = 23; }; + | + +error[E0507]: cannot move out of `*****index` which is behind a shared reference + --> $DIR/index-mut-help2.rs:53:16 + | +LL | map.insert(*****index, 23); + | ^^^^^^^^^^ move occurs because `*****index` has type `A`, which does not implement the `Copy` trait + | +note: if `A` implemented `Clone`, you could clone the value + --> $DIR/index-mut-help2.rs:8:1 + | +LL | struct A {} + | ^^^^^^^^ consider implementing `Clone` for this type +... +LL | map.insert(*****index, 23); + | ---------- you could clone this value + +error[E0594]: cannot assign to data in an index of `HashMap` + --> $DIR/index-mut-help2.rs:62:5 + | +LL | map[index] = 23; + | ^^^^^^^^^^^^^^^ 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` + | +LL - map[index] = 23; +LL + map.insert(*****index, 23); + | +help: use the entry API to modify a `HashMap` for more flexibility + | +LL - map[index] = 23; +LL + let val = map.entry(*****index).insert_entry(23); + | +help: use `.get_mut()` to modify an existing key in a `HashMap` + | +LL - map[index] = 23; +LL + if let Some(val) = map.get_mut(index) { *val = 23; }; + | + +error[E0594]: cannot assign to data in an index of `HashMap` + --> $DIR/index-mut-help2.rs:114:5 + | +LL | map[index] = 23; + | ^^^^^^^^^^^^^^^ 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` + | +LL - map[index] = 23; +LL + map.insert(*****index, 23); + | +help: use the entry API to modify a `HashMap` for more flexibility + | +LL - map[index] = 23; +LL + let val = map.entry(*****index).insert_entry(23); + | +help: use `.get_mut()` to modify an existing key in a `HashMap` + | +LL - map[index] = 23; +LL + if let Some(val) = map.get_mut(index) { *val = 23; }; + | + +error: aborting due to 11 previous errors + +Some errors have detailed explanations: E0277, E0308, E0507, E0594. +For more information about an error, try `rustc --explain E0277`. From 5f1e96282470a3bdacb7a38fea1e2694e4887732 Mon Sep 17 00:00:00 2001 From: GTimothy <22472919+GTimothy@users.noreply.github.com> Date: Sat, 2 May 2026 13:09:58 +0200 Subject: [PATCH 4/4] fix: wrong assumption could cause panic added a valid example that violated the assumption in the proposed change: it is possible for a borrowee type to be a ref of it's borrowed type. removed the assumption, treated all cases appropriately. --- .../src/diagnostics/mutability_errors.rs | 54 ++++++++------- tests/ui/borrowck/index-mut-help2.rs | 25 ++++++- tests/ui/borrowck/index-mut-help2.stderr | 65 +++++++++++++++++-- 3 files changed, 114 insertions(+), 30 deletions(-) diff --git a/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs b/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs index 1b6e828950bab..d8fca56de39d3 100644 --- a/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs @@ -736,33 +736,35 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { let index_ty = self.infcx.tcx.typeck(val.hir_id.owner.def_id).expr_ty(index); - let count_refs_diff: isize = - count_ty_refs(index_ty) as isize - count_ty_refs(key_ty) as isize; - let (borrowed_prefix, borrowed_index); - // only suggest `insert` and `entry` if index is of type K or &{n}K. + // 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() { - assert!( - count_refs_diff >= 0, - "compiler bug I think, please report this" - ); - // if base type is same, borrowed depth must be exact. - let (deref_prefix, deref_index) = - strip_n_refs(index, count_refs_diff as usize) + 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)| { ( - "*".repeat( - usize::try_from(count_refs_diff) - .expect("passed assert") - - depth, - ), + 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), @@ -803,13 +805,17 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { // we can make the next suggestions nicer by stripping as many leading `&` as // we can, autoderef will do the rest - (borrowed_prefix, borrowed_index) = - match strip_n_refs(index, 0.max(count_refs_diff - 1) as usize) { - Ok(val) => (String::new(), val), - // even if we tried to strip more, we can stop there thanks to - // autoderef - Err((_depth, val)) => (String::new(), val), - }; + (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) } diff --git a/tests/ui/borrowck/index-mut-help2.rs b/tests/ui/borrowck/index-mut-help2.rs index 0b700ef1e0f0a..0447cb1c8abbb 100644 --- a/tests/ui/borrowck/index-mut-help2.rs +++ b/tests/ui/borrowck/index-mut-help2.rs @@ -103,7 +103,6 @@ fn test_b() { } // passes } - /// In this test the key type is C. /// The `Borrow for D` implementation changes nothing here, same error as for test_a. fn test_c() { @@ -140,6 +139,30 @@ fn test_d() { } // passes } +#[derive(Hash, PartialEq, Eq)] +struct S {} +// it is possible to have a Borrowed version of a type be of the same type but less borrowed. +// here, a borrowed version of `&&&S` is `&S`. +impl Borrow for &&&S { + fn borrow(&self) -> &S { + self + } +} + +/// In this test, the index type is the same base type as the key, but because of a peculiar Borrow +/// impl, it is of a lesser ref depth than the key. +/// There are some E0716 errors that result from the suggestion, but they already have diagnostics +fn test_s() { + let mut map = HashMap::<&&&S, usize>::new(); + let index = &S{}; + map[index] = 12; //~ ERROR E0594 + map.insert(&&index, 12); //~ ERROR E0716 + let val = map.entry(&&index).insert_entry(12); //~ ERROR E0716 + if let Some(val) = map.get_mut(index) { + *val = 12; + }; +} + fn main() { test_a_index(); test_a_2(); diff --git a/tests/ui/borrowck/index-mut-help2.stderr b/tests/ui/borrowck/index-mut-help2.stderr index 244246f496fc2..44dd966cfd6a5 100644 --- a/tests/ui/borrowck/index-mut-help2.stderr +++ b/tests/ui/borrowck/index-mut-help2.stderr @@ -44,7 +44,7 @@ note: required by a bound in `HashMap::::get_mut` --> $SRC_DIR/std/src/collections/hash/map.rs:LL:COL error[E0277]: the trait bound `D: Borrow<&&&&D>` is not satisfied - --> $DIR/index-mut-help2.rs:132:9 + --> $DIR/index-mut-help2.rs:131:9 | LL | map[index] = 23; | ^^^^^ unsatisfied trait bound @@ -59,7 +59,7 @@ LL | impl Borrow for D { = note: required for `HashMap` to implement `Index<&&&&&D>` error[E0277]: the trait bound `D: Borrow<&&&&D>` is not satisfied - --> $DIR/index-mut-help2.rs:135:36 + --> $DIR/index-mut-help2.rs:134:36 | LL | if let Some(val) = map.get_mut(index) { | ------- ^^^^^ unsatisfied trait bound @@ -161,7 +161,7 @@ LL + if let Some(val) = map.get_mut(index) { *val = 23; }; | error[E0594]: cannot assign to data in an index of `HashMap` - --> $DIR/index-mut-help2.rs:114:5 + --> $DIR/index-mut-help2.rs:113:5 | LL | map[index] = 23; | ^^^^^^^^^^^^^^^ cannot assign @@ -183,7 +183,62 @@ LL - map[index] = 23; LL + if let Some(val) = map.get_mut(index) { *val = 23; }; | -error: aborting due to 11 previous errors +error[E0594]: cannot assign to data in an index of `HashMap<&&&S, usize>` + --> $DIR/index-mut-help2.rs:158:5 + | +LL | map[index] = 12; + | ^^^^^^^^^^^^^^^ cannot assign + | + = help: trait `IndexMut` is required to modify indexed content, but it is not implemented for `HashMap<&&&S, usize>` +help: use `.insert()` to insert a value into a `HashMap<&&&S, usize>` + | +LL - map[index] = 12; +LL + map.insert(&&index, 12); + | +help: use the entry API to modify a `HashMap<&&&S, usize>` for more flexibility + | +LL - map[index] = 12; +LL + let val = map.entry(&&index).insert_entry(12); + | +help: use `.get_mut()` to modify an existing key in a `HashMap<&&&S, usize>` + | +LL - map[index] = 12; +LL + if let Some(val) = map.get_mut(index) { *val = 12; }; + | + +error[E0716]: temporary value dropped while borrowed + --> $DIR/index-mut-help2.rs:159:17 + | +LL | map.insert(&&index, 12); + | ^^^^^^ - temporary value is freed at the end of this statement + | | + | creates a temporary value which is freed while still in use +LL | let val = map.entry(&&index).insert_entry(12); + | --- borrow later used here + | +help: consider using a `let` binding to create a longer lived value + | +LL ~ let binding = &index; +LL ~ map.insert(&binding, 12); + | + +error[E0716]: temporary value dropped while borrowed + --> $DIR/index-mut-help2.rs:160:26 + | +LL | let val = map.entry(&&index).insert_entry(12); + | ^^^^^^ - temporary value is freed at the end of this statement + | | + | creates a temporary value which is freed while still in use +LL | if let Some(val) = map.get_mut(index) { + | --- borrow later used here + | +help: consider using a `let` binding to create a longer lived value + | +LL ~ let binding = &index; +LL ~ let val = map.entry(&binding).insert_entry(12); + | + +error: aborting due to 14 previous errors -Some errors have detailed explanations: E0277, E0308, E0507, E0594. +Some errors have detailed explanations: E0277, E0308, E0507, E0594, E0716. For more information about an error, try `rustc --explain E0277`.