Skip to content

Commit 11610db

Browse files
Rollup merge of #152216 - GTimothy:map-diagnostics-fix, r=JonathanBrouwer
Fix 'assign to data in an index of' collection suggestions fixes #150001 fixes rust-lang/rust-analyzer#16076 fixes #134917 The issues are threefold and linked: 1. Assigning data to a ~~collection~~BTreeMap/HashMap suggests 3 solutions all marked as `MachineApplicable` 2. The suggestions are slightly wrong with regards to their borrowing needs. 3. The suggestions are not guaranteed to produce code that is valid, and suggestion number two is equivalent to an update, not an insertion. This PR: - splits the large triple suggestion into three - sets them to `MaybeIncorrect` - automatically determines the required borrowing to use. I think this solution may not be very elegant, expecially the key typechecking / borrowing part, but it works. I am however very open to any improvement/change :) edit: edited to replace 'collection' with BTreeMap/HashMap'
2 parents bc6d9b1 + 5f1e962 commit 11610db

7 files changed

Lines changed: 592 additions & 47 deletions

File tree

compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs

Lines changed: 141 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use rustc_abi::FieldIdx;
66
use rustc_errors::{Applicability, Diag};
77
use rustc_hir::def_id::DefId;
88
use rustc_hir::intravisit::Visitor;
9-
use rustc_hir::{self as hir, BindingMode, ByRef, Node};
9+
use rustc_hir::{self as hir, BindingMode, ByRef, Expr, Node};
1010
use rustc_middle::bug;
1111
use rustc_middle::hir::place::PlaceBase;
1212
use rustc_middle::mir::visit::PlaceContext;
@@ -669,7 +669,9 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
669669
err: &'a mut Diag<'infcx>,
670670
ty: Ty<'tcx>,
671671
suggested: bool,
672+
infcx: &'a rustc_infer::infer::InferCtxt<'tcx>,
672673
}
674+
673675
impl<'a, 'infcx, 'tcx> Visitor<'tcx> for SuggestIndexOperatorAlternativeVisitor<'a, 'infcx, 'tcx> {
674676
fn visit_stmt(&mut self, stmt: &'tcx hir::Stmt<'tcx>) {
675677
hir::intravisit::walk_stmt(self, stmt);
@@ -680,60 +682,166 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
680682
return;
681683
}
682684
};
685+
686+
// Because of TypeChecking and indexing, we know: index is &Q
687+
// with K: Eq + Hash + Borrow<Q>,
688+
// with Q: Eq + Hash + ?Sized,
689+
//
690+
// which fulfill the requirements of `get_mut`. If Q=K or Q=&{n}K, the requirements
691+
// of `entry` and `insert` are fulfilled too after dereferencing. If K is not
692+
// copy, a subsequent `clone` call may be needed.
693+
694+
/// Taken straight from https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/fn.peel_hir_ty_refs.html
695+
/// Adapted to mid using https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.Ty.html#method.peel_refs
696+
/// Simplified to counting only
697+
/// Peels off all references on the type. Returns the number of references
698+
/// removed.
699+
fn count_ty_refs<'tcx>(mut ty: Ty<'tcx>) -> usize {
700+
let mut count = 0;
701+
while let ty::Ref(_, inner_ty, _) = ty.kind() {
702+
ty = *inner_ty;
703+
count += 1;
704+
}
705+
count
706+
}
707+
708+
/// Try to strip `n` `&` reference from an expression.
709+
/// If the expression does not have enough leading `&`, return an Error
710+
/// containing a count of the successfully stripped ones and the stripped
711+
/// expression.
712+
fn strip_n_refs<'a, 'b>(
713+
mut expr: &'a Expr<'b>,
714+
n: usize,
715+
) -> Result<&'a Expr<'b>, (usize, &'a Expr<'b>)> {
716+
for count in 0..n {
717+
match expr {
718+
Expr {
719+
kind: ExprKind::AddrOf(hir::BorrowKind::Ref, _, inner),
720+
..
721+
} => expr = inner,
722+
_ => return Err((count, expr)),
723+
}
724+
}
725+
Ok(expr)
726+
}
727+
728+
// we know ty is a map, with a key type at walk distance 2.
729+
let key_ty = self.ty.walk().nth(1).unwrap().expect_ty();
730+
683731
if let hir::ExprKind::Assign(place, rv, _sp) = expr.kind
684732
&& let hir::ExprKind::Index(val, index, _) = place.kind
685733
&& (expr.span == self.assign_span || place.span == self.assign_span)
686734
{
687735
// val[index] = rv;
688-
// ---------- place
689-
self.err.multipart_suggestions(
690-
format!(
691-
"use `.insert()` to insert a value into a `{}`, `.get_mut()` \
692-
to modify it, or the entry API for more flexibility",
693-
self.ty,
694-
),
695-
vec![
736+
let index_ty =
737+
self.infcx.tcx.typeck(val.hir_id.owner.def_id).expr_ty(index);
738+
739+
let (borrowed_prefix, borrowed_index);
740+
741+
// 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).
742+
// We use `peel_refs` because borrow lifetimes may differ in both index and
743+
// key. I.e, if they are of the same base type:
744+
if index_ty.peel_refs() == key_ty.peel_refs() {
745+
let (index_refs, key_refs) =
746+
(count_ty_refs(index_ty), count_ty_refs(key_ty));
747+
748+
let (deref_prefix, deref_index) = if index_refs >= key_refs {
749+
// index is &{n}K
750+
strip_n_refs(index, index_refs - key_refs)
751+
.map(|val| ("".to_string(), val))
752+
.unwrap_or_else(|(depth, val)| {
753+
(
754+
if key_refs == 0 {
755+
"*".repeat(
756+
(index_refs-key_refs).checked_sub(depth).expect("return depth from strip_n_refs should be smaller than the input")
757+
)
758+
} else {
759+
String::new() //if key K is a ref, autoderef finish this for us.
760+
},
761+
val,
762+
)
763+
})
764+
} else {
765+
// in this case the minimal ref addition works for all subcases
766+
("&".repeat(key_refs - index_refs), index)
767+
};
768+
769+
self.err.multipart_suggestion(
770+
format!("use `.insert()` to insert a value into a `{}`", self.ty),
696771
vec![
697-
// val.insert(index, rv);
772+
// val.insert({deref_prefix}{deref_index}, rv);
698773
(
699-
val.span.shrink_to_hi().with_hi(index.span.lo()),
700-
".insert(".to_string(),
774+
val.span.shrink_to_hi().with_hi(deref_index.span.lo()),
775+
format!(".insert({deref_prefix}"),
701776
),
702777
(
703-
index.span.shrink_to_hi().with_hi(rv.span.lo()),
778+
deref_index.span.shrink_to_hi().with_hi(rv.span.lo()),
704779
", ".to_string(),
705780
),
706781
(rv.span.shrink_to_hi(), ")".to_string()),
707782
],
783+
Applicability::MaybeIncorrect,
784+
);
785+
self.err.multipart_suggestion(
786+
format!(
787+
"use the entry API to modify a `{}` for more flexibility",
788+
self.ty
789+
),
708790
vec![
709-
// if let Some(v) = val.get_mut(index) { *v = rv; }
710-
(val.span.shrink_to_lo(), "if let Some(val) = ".to_string()),
711-
(
712-
val.span.shrink_to_hi().with_hi(index.span.lo()),
713-
".get_mut(".to_string(),
714-
),
715-
(
716-
index.span.shrink_to_hi().with_hi(place.span.hi()),
717-
") { *val".to_string(),
718-
),
719-
(rv.span.shrink_to_hi(), "; }".to_string()),
720-
],
721-
vec![
722-
// let x = val.entry(index).or_insert(rv);
791+
// let x = val.entry({deref_prefix}{deref_index}).insert_entry(rv);
723792
(val.span.shrink_to_lo(), "let val = ".to_string()),
724793
(
725-
val.span.shrink_to_hi().with_hi(index.span.lo()),
726-
".entry(".to_string(),
794+
val.span.shrink_to_hi().with_hi(deref_index.span.lo()),
795+
format!(".entry({deref_prefix}"),
727796
),
728797
(
729-
index.span.shrink_to_hi().with_hi(rv.span.lo()),
730-
").or_insert(".to_string(),
798+
deref_index.span.shrink_to_hi().with_hi(rv.span.lo()),
799+
").insert_entry(".to_string(),
731800
),
732801
(rv.span.shrink_to_hi(), ")".to_string()),
733802
],
803+
Applicability::MaybeIncorrect,
804+
);
805+
806+
// we can make the next suggestions nicer by stripping as many leading `&` as
807+
// we can, autoderef will do the rest
808+
(borrowed_prefix, borrowed_index) = (
809+
String::new(),
810+
if index_refs > key_refs {
811+
strip_n_refs(index, index_refs - key_refs - 1)
812+
.unwrap_or_else(|(_depth, val)| val)
813+
// even if we tried to strip more, we can stop there thanks to autoderef
814+
} else {
815+
// when the diff is negative or zero, we already are in the index=&Q case.
816+
index
817+
},
818+
);
819+
} else {
820+
(borrowed_prefix, borrowed_index) = (String::new(), index)
821+
}
822+
// in all cases, suggest get_mut because K:Borrow<K> or Q:Borrow<K> as a
823+
// requirement of indexing.
824+
self.err.multipart_suggestion(
825+
format!(
826+
"use `.get_mut()` to modify an existing key in a `{}`",
827+
self.ty,
828+
),
829+
vec![
830+
// if let Some(v) = val.get_mut({borrowed_prefix}{borrowed_index}) { *v = rv; }
831+
(val.span.shrink_to_lo(), "if let Some(val) = ".to_string()),
832+
(
833+
val.span.shrink_to_hi().with_hi(borrowed_index.span.lo()),
834+
format!(".get_mut({borrowed_prefix}"),
835+
),
836+
(
837+
borrowed_index.span.shrink_to_hi().with_hi(place.span.hi()),
838+
") { *val".to_string(),
839+
),
840+
(rv.span.shrink_to_hi(), "; }".to_string()),
734841
],
735-
Applicability::MachineApplicable,
842+
Applicability::MaybeIncorrect,
736843
);
844+
737845
self.suggested = true;
738846
} else if let hir::ExprKind::MethodCall(_path, receiver, _, sp) = expr.kind
739847
&& let hir::ExprKind::Index(val, index, _) = receiver.kind
@@ -769,6 +877,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
769877
err,
770878
ty,
771879
suggested: false,
880+
infcx: self.infcx,
772881
};
773882
v.visit_body(&body);
774883
if !v.suggested {

tests/ui/borrowck/index-mut-help.stderr

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,20 @@ LL | map["peter"] = "0".to_string();
1818
| ^^^^^^^^^^^^ cannot assign
1919
|
2020
= help: trait `IndexMut` is required to modify indexed content, but it is not implemented for `HashMap<&str, String>`
21-
help: use `.insert()` to insert a value into a `HashMap<&str, String>`, `.get_mut()` to modify it, or the entry API for more flexibility
21+
help: use `.insert()` to insert a value into a `HashMap<&str, String>`
2222
|
2323
LL - map["peter"] = "0".to_string();
2424
LL + map.insert("peter", "0".to_string());
2525
|
26+
help: use the entry API to modify a `HashMap<&str, String>` for more flexibility
27+
|
2628
LL - map["peter"] = "0".to_string();
27-
LL + if let Some(val) = map.get_mut("peter") { *val = "0".to_string(); };
29+
LL + let val = map.entry("peter").insert_entry("0".to_string());
30+
|
31+
help: use `.get_mut()` to modify an existing key in a `HashMap<&str, String>`
2832
|
2933
LL - map["peter"] = "0".to_string();
30-
LL + let val = map.entry("peter").or_insert("0".to_string());
34+
LL + if let Some(val) = map.get_mut("peter") { *val = "0".to_string(); };
3135
|
3236

3337
error[E0596]: cannot borrow data in an index of `HashMap<&str, String>` as mutable

0 commit comments

Comments
 (0)