Skip to content

Commit 6cdc4e9

Browse files
committed
Suggest use .get_mut instead of &mut when source type is HashMap or BTreeMap
Signed-off-by: xizheyin <[email protected]>
1 parent e41e112 commit 6cdc4e9

File tree

7 files changed

+157
-62
lines changed

7 files changed

+157
-62
lines changed

compiler/rustc_borrowck/src/diagnostics/mod.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -889,7 +889,7 @@ pub(super) enum BorrowedContentSource<'tcx> {
889889
DerefMutableRef,
890890
DerefSharedRef,
891891
OverloadedDeref(Ty<'tcx>),
892-
OverloadedIndex(Ty<'tcx>),
892+
OverloadedIndex(Ty<'tcx>, Ty<'tcx>),
893893
}
894894

895895
impl<'tcx> BorrowedContentSource<'tcx> {
@@ -905,7 +905,7 @@ impl<'tcx> BorrowedContentSource<'tcx> {
905905
_ => None,
906906
})
907907
.unwrap_or_else(|| format!("dereference of `{ty}`")),
908-
BorrowedContentSource::OverloadedIndex(ty) => format!("index of `{ty}`"),
908+
BorrowedContentSource::OverloadedIndex(ty, _) => format!("index of `{ty}`"),
909909
}
910910
}
911911

@@ -917,7 +917,7 @@ impl<'tcx> BorrowedContentSource<'tcx> {
917917
// Overloaded deref and index operators should be evaluated into a
918918
// temporary. So we don't need a description here.
919919
BorrowedContentSource::OverloadedDeref(_)
920-
| BorrowedContentSource::OverloadedIndex(_) => None,
920+
| BorrowedContentSource::OverloadedIndex(_, _) => None,
921921
}
922922
}
923923

@@ -935,7 +935,7 @@ impl<'tcx> BorrowedContentSource<'tcx> {
935935
_ => None,
936936
})
937937
.unwrap_or_else(|| format!("dereference of `{ty}`")),
938-
BorrowedContentSource::OverloadedIndex(ty) => format!("an index of `{ty}`"),
938+
BorrowedContentSource::OverloadedIndex(ty, _) => format!("an index of `{ty}`"),
939939
}
940940
}
941941

@@ -951,7 +951,7 @@ impl<'tcx> BorrowedContentSource<'tcx> {
951951
} else if tcx.is_lang_item(trait_id, LangItem::Index)
952952
|| tcx.is_lang_item(trait_id, LangItem::IndexMut)
953953
{
954-
Some(BorrowedContentSource::OverloadedIndex(args.type_at(0)))
954+
Some(BorrowedContentSource::OverloadedIndex(args.type_at(0), args.type_at(1)))
955955
} else {
956956
None
957957
}

compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs

Lines changed: 82 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -519,7 +519,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
519519
but it is not implemented for `{ty}`",
520520
));
521521
}
522-
Some(BorrowedContentSource::OverloadedIndex(ty)) => {
522+
Some(BorrowedContentSource::OverloadedIndex(ty, _)) => {
523523
err.help(format!(
524524
"trait `IndexMut` is required to modify indexed content, \
525525
but it is not implemented for `{ty}`",
@@ -1158,24 +1158,28 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
11581158
opt_ty_info,
11591159
..
11601160
})) => {
1161-
// check if the RHS is from desugaring
1162-
let opt_assignment_rhs_span =
1163-
self.find_assignments(local).first().map(|&___location| {
1161+
// check if the RHS is from desugaring, and return rhs of assignment
1162+
let (opt_assignment_rhs, opt_assignment_rhs_span) = self
1163+
.find_assignments(local)
1164+
.first()
1165+
.map(|&___location| {
1166+
let mut rhs = None;
1167+
let mut span = self.body.source_info(___location).span;
11641168
if let Some(mir::Statement {
11651169
source_info: _,
1166-
kind:
1167-
mir::StatementKind::Assign(box (
1168-
_,
1169-
mir::Rvalue::Use(mir::Operand::Copy(place)),
1170-
)),
1170+
kind: mir::StatementKind::Assign(box (_, rvalue)),
11711171
..
11721172
}) = self.body[___location.block].statements.get(___location.statement_index)
11731173
{
1174-
self.body.local_decls[place.local].source_info.span
1175-
} else {
1176-
self.body.source_info(___location).span
1174+
// If there is a assignment, we should return the rhs
1175+
rhs = Some(rvalue);
1176+
if let mir::Rvalue::Use(mir::Operand::Copy(place)) = rvalue {
1177+
span = self.body.local_decls[place.local].source_info.span;
1178+
}
11771179
}
1178-
});
1180+
(rhs, Some(span))
1181+
})
1182+
.unwrap_or((None, None));
11791183
match opt_assignment_rhs_span.and_then(|s| s.desugaring_kind()) {
11801184
// on for loops, RHS points to the iterator part
11811185
Some(DesugaringKind::ForLoop) => {
@@ -1196,7 +1200,13 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
11961200
None
11971201
}
11981202
None => {
1199-
if name != kw::SelfLower {
1203+
let result = self.suggest_get_mut_or_ref_mut(
1204+
opt_assignment_rhs,
1205+
opt_assignment_rhs_span,
1206+
);
1207+
if let ControlFlow::Break(suggest_get_mut) = result {
1208+
suggest_get_mut
1209+
} else if name != kw::SelfLower {
12001210
suggest_ampmut(
12011211
self.infcx.tcx,
12021212
local_decl.ty,
@@ -1414,6 +1424,64 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
14141424
None => {}
14151425
}
14161426
}
1427+
1428+
/// check if the RHS is an overloaded index expression whose source type
1429+
/// doesn't implement `IndexMut`. This category contains two sub-categories:
1430+
/// 1. HashMap or BTreeMap
1431+
/// 2. Other types
1432+
/// For the first sub-category, we suggest using `.get_mut()` instead of `&mut`,
1433+
/// For the second sub-category, we still suggest use `&mut`.
1434+
/// See issue #143732.
1435+
///
1436+
/// Break indicates we should not suggest use `&mut`
1437+
/// Continue indicates we continue to try suggesting use `&mut`
1438+
fn suggest_get_mut_or_ref_mut(
1439+
&self,
1440+
opt_assignment_rhs: Option<&mir::Rvalue<'tcx>>,
1441+
opt_assignment_rhs_span: Option<Span>,
1442+
) -> ControlFlow<Option<AmpMutSugg>, ()> {
1443+
if let Some(mir::Rvalue::Ref(_, _, place)) = opt_assignment_rhs
1444+
&& let BorrowedContentSource::OverloadedIndex(ty, index_ty) =
1445+
self.borrowed_content_source(place.as_ref())
1446+
&& let Some(index_mut_trait) = self.infcx.tcx.lang_items().index_mut_trait()
1447+
&& !self
1448+
.infcx
1449+
.type_implements_trait(index_mut_trait, [ty, index_ty], self.infcx.param_env)
1450+
.must_apply_modulo_regions()
1451+
{
1452+
if let Some(adt) = ty.ty_adt_def()
1453+
&& (self.infcx.tcx.is_diagnostic_item(sym::HashMap, adt.did())
1454+
|| self.infcx.tcx.is_diagnostic_item(sym::BTreeMap, adt.did()))
1455+
&& let Some(rhs_span) = opt_assignment_rhs_span
1456+
&& let Ok(rhs_str) = self.infcx.tcx.sess.source_map().span_to_snippet(rhs_span)
1457+
&& let Some(content) = rhs_str.strip_prefix('&')
1458+
&& content.contains('[')
1459+
&& content.contains(']')
1460+
&& let Some(bracket_start) = content.find('[')
1461+
&& let Some(bracket_end) = content.rfind(']')
1462+
&& bracket_start < bracket_end
1463+
{
1464+
let map_part = &content[..bracket_start];
1465+
let key_part = &content[bracket_start + 1..bracket_end];
1466+
1467+
// We only suggest use `.get_mut()` for HashMap or BTreeMap
1468+
// We don't try suggesting use `&mut`, so break
1469+
ControlFlow::Break(Some(AmpMutSugg {
1470+
has_sugg: true,
1471+
span: rhs_span,
1472+
suggestion: format!("{}.get_mut({}).unwrap()", map_part, key_part),
1473+
additional: None,
1474+
}))
1475+
} else {
1476+
// Type not implemented `IndexMut` but not HashMap or BTreeMap or error
1477+
// and we did not suggest either `.get_mut()` or `&mut`, so break
1478+
ControlFlow::Break(None)
1479+
}
1480+
} else {
1481+
// Type Impl IndexMut, we should continue to try suggesting use `&mut`
1482+
ControlFlow::Continue(())
1483+
}
1484+
}
14171485
}
14181486

14191487
struct BindingFinder {
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// We don't suggest change `&` to `&mut` with HashMap and BTreeMap
2+
// instead we suggest using .get_mut() instead of &mut, see issue #143732
3+
4+
//@ run-rustfix
5+
6+
fn main() {
7+
let mut map = std::collections::BTreeMap::new();
8+
map.insert(0, "string".to_owned());
9+
10+
let string = map.get_mut(&0).unwrap();
11+
string.push_str("test"); //~ ERROR cannot borrow `*string` as mutable, as it is behind a `&` reference [E0596]
12+
13+
let mut map = std::collections::HashMap::new();
14+
map.insert(0, "string".to_owned());
15+
16+
let string = map.get_mut(&0).unwrap();
17+
string.push_str("test"); //~ ERROR cannot borrow `*string` as mutable, as it is behind a `&` reference [E0596]
18+
19+
let mut vec = vec![String::new(), String::new()];
20+
let string = &mut vec[0];
21+
string.push_str("test"); //~ ERROR cannot borrow `*string` as mutable, as it is behind a `&` reference [E0596]
22+
}
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,7 @@
1-
// We don't suggest change `&` to `&mut`
1+
// We don't suggest change `&` to `&mut` with HashMap and BTreeMap
22
// instead we suggest using .get_mut() instead of &mut, see issue #143732
33

4-
// Custom type that implements Index<usize> but not IndexMut
5-
struct ReadOnlyVec<T> {
6-
data: Vec<T>,
7-
}
8-
9-
impl<T> ReadOnlyVec<T> {
10-
fn new(data: Vec<T>) -> Self {
11-
ReadOnlyVec { data }
12-
}
13-
}
14-
15-
impl<T> std::ops::Index<usize> for ReadOnlyVec<T> {
16-
type Output = T;
17-
18-
fn index(&self, index: usize) -> &Self::Output {
19-
&self.data[index]
20-
}
21-
}
4+
//@ run-rustfix
225

236
fn main() {
247
let mut map = std::collections::BTreeMap::new();
@@ -36,9 +19,4 @@ fn main() {
3619
let mut vec = vec![String::new(), String::new()];
3720
let string = &vec[0];
3821
string.push_str("test"); //~ ERROR cannot borrow `*string` as mutable, as it is behind a `&` reference [E0596]
39-
40-
// Example with our custom ReadOnlyVec type
41-
let read_only_vec = ReadOnlyVec::new(vec![String::new(), String::new()]);
42-
let string_ref = &read_only_vec[0];
43-
string_ref.push_str("test"); //~ ERROR cannot borrow `*string_ref` as mutable, as it is behind a `&` reference [E0596]
4422
}
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,29 @@
11
error[E0596]: cannot borrow `*string` as mutable, as it is behind a `&` reference
2-
--> $DIR/suggest-use-as-mut-for-map.rs:28:5
2+
--> $DIR/suggest-use-as-mut-for-map-1.rs:11:5
33
|
44
LL | string.push_str("test");
55
| ^^^^^^ `string` is a `&` reference, so the data it refers to cannot be borrowed as mutable
66
|
77
help: consider changing this to be a mutable reference
88
|
9-
LL | let string = &mut map[&0];
10-
| +++
9+
LL - let string = &map[&0];
10+
LL + let string = map.get_mut(&0).unwrap();
11+
|
1112

1213
error[E0596]: cannot borrow `*string` as mutable, as it is behind a `&` reference
13-
--> $DIR/suggest-use-as-mut-for-map.rs:34:5
14+
--> $DIR/suggest-use-as-mut-for-map-1.rs:17:5
1415
|
1516
LL | string.push_str("test");
1617
| ^^^^^^ `string` is a `&` reference, so the data it refers to cannot be borrowed as mutable
1718
|
1819
help: consider changing this to be a mutable reference
1920
|
20-
LL | let string = &mut map[&0];
21-
| +++
21+
LL - let string = &map[&0];
22+
LL + let string = map.get_mut(&0).unwrap();
23+
|
2224

2325
error[E0596]: cannot borrow `*string` as mutable, as it is behind a `&` reference
24-
--> $DIR/suggest-use-as-mut-for-map.rs:38:5
26+
--> $DIR/suggest-use-as-mut-for-map-1.rs:21:5
2527
|
2628
LL | string.push_str("test");
2729
| ^^^^^^ `string` is a `&` reference, so the data it refers to cannot be borrowed as mutable
@@ -31,17 +33,6 @@ help: consider changing this to be a mutable reference
3133
LL | let string = &mut vec[0];
3234
| +++
3335

34-
error[E0596]: cannot borrow `*string_ref` as mutable, as it is behind a `&` reference
35-
--> $DIR/suggest-use-as-mut-for-map.rs:43:5
36-
|
37-
LL | string_ref.push_str("test");
38-
| ^^^^^^^^^^ `string_ref` is a `&` reference, so the data it refers to cannot be borrowed as mutable
39-
|
40-
help: consider changing this to be a mutable reference
41-
|
42-
LL | let string_ref = &mut read_only_vec[0];
43-
| +++
44-
45-
error: aborting due to 4 previous errors
36+
error: aborting due to 3 previous errors
4637

4738
For more information about this error, try `rustc --explain E0596`.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// We don't suggest anything, see issue #143732
2+
3+
// Custom type that implements Index<usize> but not IndexMut
4+
struct ReadOnlyVec<T> {
5+
data: Vec<T>,
6+
}
7+
8+
impl<T> ReadOnlyVec<T> {
9+
fn new(data: Vec<T>) -> Self {
10+
ReadOnlyVec { data }
11+
}
12+
}
13+
14+
impl<T> std::ops::Index<usize> for ReadOnlyVec<T> {
15+
type Output = T;
16+
17+
fn index(&self, index: usize) -> &Self::Output {
18+
&self.data[index]
19+
}
20+
}
21+
22+
fn main() {
23+
// Example with our custom ReadOnlyVec type
24+
let read_only_vec = ReadOnlyVec::new(vec![String::new(), String::new()]);
25+
let string_ref = &read_only_vec[0];
26+
string_ref.push_str("test"); //~ ERROR cannot borrow `*string_ref` as mutable, as it is behind a `&` reference [E0596]
27+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
error[E0596]: cannot borrow `*string_ref` as mutable, as it is behind a `&` reference
2+
--> $DIR/suggest-use-as-mut-for-map-2.rs:26:5
3+
|
4+
LL | string_ref.push_str("test");
5+
| ^^^^^^^^^^ `string_ref` is a `&` reference, so the data it refers to cannot be borrowed as mutable
6+
7+
error: aborting due to 1 previous error
8+
9+
For more information about this error, try `rustc --explain E0596`.

0 commit comments

Comments
 (0)