borrowck: suggest &mut *x for pattern reborrows

This commit is contained in:
Yuki Okushi 2026-02-07 19:44:34 +09:00
parent c58d9f9f82
commit 0de7415875
7 changed files with 175 additions and 23 deletions

View file

@ -354,14 +354,71 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
self.infcx.tcx.sess.source_map().span_to_snippet(source_info.span)
{
if snippet.starts_with("&mut ") {
// We don't have access to the HIR to get accurate spans, but we can
// give a best effort structured suggestion.
err.span_suggestion_verbose(
source_info.span.with_hi(source_info.span.lo() + BytePos(5)),
"if there is only one mutable reborrow, remove the `&mut`",
"",
Applicability::MaybeIncorrect,
);
// In calls, `&mut &mut T` may be deref-coerced to `&mut T`, and
// removing the extra `&mut` is the most direct suggestion. But for
// pattern-matching expressions (`match`, `if let`, `while let`), that
// can easily turn into a move, so prefer suggesting an explicit
// reborrow via `&mut *x` instead.
let mut in_pat_scrutinee = false;
let mut is_deref_coerced = false;
if let Some(expr) = self.find_expr(source_info.span) {
let tcx = self.infcx.tcx;
let span = expr.span.source_callsite();
for (_, node) in tcx.hir_parent_iter(expr.hir_id) {
if let Node::Expr(parent_expr) = node {
match parent_expr.kind {
ExprKind::Match(scrutinee, ..)
if scrutinee
.span
.source_callsite()
.contains(span) =>
{
in_pat_scrutinee = true;
break;
}
ExprKind::Let(let_expr)
if let_expr
.init
.span
.source_callsite()
.contains(span) =>
{
in_pat_scrutinee = true;
break;
}
_ => {}
}
}
}
let typeck = tcx.typeck(expr.hir_id.owner.def_id);
is_deref_coerced =
typeck.expr_adjustments(expr).iter().any(|adj| {
matches!(adj.kind, ty::adjustment::Adjust::Deref(_))
});
}
if in_pat_scrutinee {
// Best-effort structured suggestion: insert `*` after `&mut `.
err.span_suggestion_verbose(
source_info
.span
.with_lo(source_info.span.lo() + BytePos(5))
.shrink_to_lo(),
"to reborrow the mutable reference, add `*`",
"*",
Applicability::MaybeIncorrect,
);
} else if is_deref_coerced {
// We don't have access to the HIR to get accurate spans, but we
// can give a best effort structured suggestion.
err.span_suggestion_verbose(
source_info.span.with_hi(source_info.span.lo() + BytePos(5)),
"if there is only one mutable reborrow, remove the `&mut`",
"",
Applicability::MaybeIncorrect,
);
}
} else {
// This can occur with things like `(&mut self).foo()`.
err.span_help(source_info.span, "try removing `&mut` here");

View file

@ -0,0 +1,37 @@
// Suggest not mutably borrowing a mutable reference
//@ run-rustfix
#![crate_type = "rlib"]
pub fn f(mut b: &mut i32) {
//~^ ERROR: cannot borrow
//~| NOTE: not mutable
//~| NOTE: the binding is already a mutable borrow
//~| HELP: consider making the binding mutable if you need to reborrow multiple times
h(b);
//~^ NOTE: cannot borrow as mutable
//~| HELP: if there is only one mutable reborrow, remove the `&mut`
g(&mut &mut b);
//~^ NOTE: cannot borrow as mutable
}
pub fn g(mut b: &mut i32) { //~ NOTE: the binding is already a mutable borrow
//~^ HELP: consider making the binding mutable if you need to reborrow multiple times
h(&mut &mut b);
//~^ ERROR: cannot borrow
//~| NOTE: cannot borrow as mutable
}
pub fn h(_: &mut i32) {}
trait Foo {
fn bar(&mut self);
}
impl Foo for &mut String {
fn bar(&mut self) {}
}
pub fn baz(mut f: &mut String) { //~ HELP consider making the binding mutable
f.bar(); //~ ERROR cannot borrow `f` as mutable, as it is not declared as mutable
//~^ NOTE cannot borrow as mutable
}

View file

@ -1,4 +1,5 @@
// Suggest not mutably borrowing a mutable reference
//@ run-rustfix
#![crate_type = "rlib"]
pub fn f(b: &mut i32) {
@ -11,7 +12,6 @@ pub fn f(b: &mut i32) {
//~| HELP: if there is only one mutable reborrow, remove the `&mut`
g(&mut &mut b);
//~^ NOTE: cannot borrow as mutable
//~| HELP: if there is only one mutable reborrow, remove the `&mut`
}
pub fn g(b: &mut i32) { //~ NOTE: the binding is already a mutable borrow
@ -19,7 +19,6 @@ pub fn g(b: &mut i32) { //~ NOTE: the binding is already a mutable borrow
h(&mut &mut b);
//~^ ERROR: cannot borrow
//~| NOTE: cannot borrow as mutable
//~| HELP: if there is only one mutable reborrow, remove the `&mut`
}
pub fn h(_: &mut i32) {}

View file

@ -1,5 +1,5 @@
error[E0596]: cannot borrow `b` as mutable, as it is not declared as mutable
--> $DIR/mut-borrow-of-mut-ref.rs:4:10
--> $DIR/mut-borrow-of-mut-ref.rs:5:10
|
LL | pub fn f(b: &mut i32) {
| ^ not mutable
@ -11,7 +11,7 @@ LL | g(&mut &mut b);
| ------ cannot borrow as mutable
|
note: the binding is already a mutable borrow
--> $DIR/mut-borrow-of-mut-ref.rs:4:13
--> $DIR/mut-borrow-of-mut-ref.rs:5:13
|
LL | pub fn f(b: &mut i32) {
| ^^^^^^^^
@ -24,11 +24,6 @@ help: if there is only one mutable reborrow, remove the `&mut`
LL - h(&mut b);
LL + h(b);
|
help: if there is only one mutable reborrow, remove the `&mut`
|
LL - g(&mut &mut b);
LL + g(&mut b);
|
error[E0596]: cannot borrow `b` as mutable, as it is not declared as mutable
--> $DIR/mut-borrow-of-mut-ref.rs:19:12
@ -45,14 +40,9 @@ help: consider making the binding mutable if you need to reborrow multiple times
|
LL | pub fn g(mut b: &mut i32) {
| +++
help: if there is only one mutable reborrow, remove the `&mut`
|
LL - h(&mut &mut b);
LL + h(&mut b);
|
error[E0596]: cannot borrow `f` as mutable, as it is not declared as mutable
--> $DIR/mut-borrow-of-mut-ref.rs:36:5
--> $DIR/mut-borrow-of-mut-ref.rs:35:5
|
LL | f.bar();
| ^ cannot borrow as mutable

View file

@ -0,0 +1,23 @@
// Regression test for #81059.
// edition:2024
//@ run-rustfix
#![allow(unused)]
fn test(mut outer: &mut Option<i32>) {
//~^ NOTE: the binding is already a mutable borrow
//~| HELP: consider making the binding mutable if you need to reborrow multiple times
match (&mut *outer, 23) {
//~^ ERROR: cannot borrow `outer` as mutable, as it is not declared as mutable
//~| NOTE: cannot borrow as mutable
//~| HELP: to reborrow the mutable reference, add `*`
(Some(inner), _) => {
*inner = 17;
}
_ => {
*outer = Some(2);
}
}
}
fn main() {}

View file

@ -0,0 +1,23 @@
// Regression test for #81059.
// edition:2024
//@ run-rustfix
#![allow(unused)]
fn test(outer: &mut Option<i32>) {
//~^ NOTE: the binding is already a mutable borrow
//~| HELP: consider making the binding mutable if you need to reborrow multiple times
match (&mut outer, 23) {
//~^ ERROR: cannot borrow `outer` as mutable, as it is not declared as mutable
//~| NOTE: cannot borrow as mutable
//~| HELP: to reborrow the mutable reference, add `*`
(Some(inner), _) => {
*inner = 17;
}
_ => {
*outer = Some(2);
}
}
}
fn main() {}

View file

@ -0,0 +1,23 @@
error[E0596]: cannot borrow `outer` as mutable, as it is not declared as mutable
--> $DIR/reborrow-in-match-suggest-deref.rs:10:12
|
LL | match (&mut outer, 23) {
| ^^^^^^^^^^ cannot borrow as mutable
|
note: the binding is already a mutable borrow
--> $DIR/reborrow-in-match-suggest-deref.rs:7:16
|
LL | fn test(outer: &mut Option<i32>) {
| ^^^^^^^^^^^^^^^^
help: consider making the binding mutable if you need to reborrow multiple times
|
LL | fn test(mut outer: &mut Option<i32>) {
| +++
help: to reborrow the mutable reference, add `*`
|
LL | match (&mut *outer, 23) {
| +
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0596`.