From ef6df3b7135a0f70e83e840b6d6256709504b022 Mon Sep 17 00:00:00 2001
From: dianne <diannes.gm@gmail.com>
Date: Wed, 19 Feb 2025 00:04:26 -0800
Subject: [PATCH 01/10] add a failing test

---
 .../migration_lint.fixed                      |  6 +++++
 .../migration_lint.rs                         |  6 +++++
 .../migration_lint.stderr                     | 23 ++++++++++++++++++-
 3 files changed, 34 insertions(+), 1 deletion(-)

diff --git a/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.fixed b/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.fixed
index e35896f32ad7d..bb4ecc09063b7 100644
--- a/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.fixed
+++ b/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.fixed
@@ -244,4 +244,10 @@ fn main() {
     let &[migration_lint_macros::bind_ref!(a)] = &[0];
     //~^ ERROR: binding modifiers may only be written when the default binding mode is `move`
     assert_type_eq(a, &0u32);
+
+    // Test that we use the correct span when labeling a `&` whose subpattern is from an expansion.
+    let &[&migration_lint_macros::bind_ref!(a)] = &[&0];
+    //~^ ERROR: reference patterns may only be written when the default binding mode is `move` in Rust 2024
+    //~| WARN: this changes meaning in Rust 2024
+    assert_type_eq(a, &0u32);
 }
diff --git a/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.rs b/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.rs
index 10a23e6f2fa18..2837c8d81dbdd 100644
--- a/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.rs
+++ b/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.rs
@@ -244,4 +244,10 @@ fn main() {
     let [migration_lint_macros::bind_ref!(a)] = &[0];
     //~^ ERROR: binding modifiers may only be written when the default binding mode is `move`
     assert_type_eq(a, &0u32);
+
+    // Test that we use the correct span when labeling a `&` whose subpattern is from an expansion.
+    let [&migration_lint_macros::bind_ref!(a)] = &[&0];
+    //~^ ERROR: reference patterns may only be written when the default binding mode is `move` in Rust 2024
+    //~| WARN: this changes meaning in Rust 2024
+    assert_type_eq(a, &0u32);
 }
diff --git a/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.stderr b/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.stderr
index 3dd91c86a3b8f..eb76615aac129 100644
--- a/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.stderr
+++ b/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.stderr
@@ -580,5 +580,26 @@ help: make the implied reference pattern explicit
 LL |     let &[migration_lint_macros::bind_ref!(a)] = &[0];
    |         +
 
-error: aborting due to 30 previous errors
+error: reference patterns may only be written when the default binding mode is `move` in Rust 2024
+  --> $DIR/auxiliary/migration_lint_macros.rs:15:22
+   |
+LL |       ($foo:ident) => {
+   |  ______________________^
+LL | |         ref $foo
+   | |________________^ reference pattern not allowed under `ref` default binding mode
+   |
+   = warning: this changes meaning in Rust 2024
+   = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/match-ergonomics.html>
+note: matching on a reference type with a non-reference pattern changes the default binding mode
+  --> $DIR/migration_lint.rs:249:9
+   |
+LL |     let [&migration_lint_macros::bind_ref!(a)] = &[&0];
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this matches on type `&_`
+help: make the implied reference pattern explicit
+  --> $DIR/migration_lint.rs:249:9
+   |
+LL |     let &[&migration_lint_macros::bind_ref!(a)] = &[&0];
+   |         +
+
+error: aborting due to 31 previous errors
 

From 51a2ee3252b33d31574ad0d03766e1b7a4034812 Mon Sep 17 00:00:00 2001
From: dianne <diannes.gm@gmail.com>
Date: Wed, 19 Feb 2025 00:50:31 -0800
Subject: [PATCH 02/10] don't get trapped inside of expansions when trimming
 labels

---
 compiler/rustc_hir_typeck/src/pat.rs          | 26 +++++++++----------
 .../migration_lint.stderr                     |  9 +++----
 2 files changed, 16 insertions(+), 19 deletions(-)

diff --git a/compiler/rustc_hir_typeck/src/pat.rs b/compiler/rustc_hir_typeck/src/pat.rs
index ae00bb4e218ab..242536e001821 100644
--- a/compiler/rustc_hir_typeck/src/pat.rs
+++ b/compiler/rustc_hir_typeck/src/pat.rs
@@ -835,20 +835,20 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                     self.add_rust_2024_migration_desugared_pat(
                         pat_info.top_info.hir_id,
                         pat,
-                        ident.span,
+                        't',
                         def_br_mutbl,
                     );
                     BindingMode(ByRef::No, Mutability::Mut)
                 }
             }
             BindingMode(ByRef::No, mutbl) => BindingMode(def_br, mutbl),
-            BindingMode(ByRef::Yes(_), _) => {
+            BindingMode(ByRef::Yes(user_br_mutbl), _) => {
                 if let ByRef::Yes(def_br_mutbl) = def_br {
                     // `ref`/`ref mut` overrides the binding mode on edition <= 2021
                     self.add_rust_2024_migration_desugared_pat(
                         pat_info.top_info.hir_id,
                         pat,
-                        ident.span,
+                        if user_br_mutbl.is_mut() { 't' } else { 'f' },
                         def_br_mutbl,
                     );
                 }
@@ -2387,7 +2387,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                     self.add_rust_2024_migration_desugared_pat(
                         pat_info.top_info.hir_id,
                         pat,
-                        inner.span,
+                        if pat_mutbl.is_mut() { 't' } else { '&' },
                         inh_mut,
                     )
                 }
@@ -2779,18 +2779,19 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         &self,
         pat_id: HirId,
         subpat: &'tcx Pat<'tcx>,
-        cutoff_span: Span,
+        final_char: char,
         def_br_mutbl: Mutability,
     ) {
         // Try to trim the span we're labeling to just the `&` or binding mode that's an issue.
         // If the subpattern's span is is from an expansion, the emitted label will not be trimmed.
-        let source_map = self.tcx.sess.source_map();
-        let cutoff_span = source_map
-            .span_extend_prev_while(cutoff_span, |c| c.is_whitespace() || c == '(')
-            .unwrap_or(cutoff_span);
-        // Ensure we use the syntax context and thus edition of `subpat.span`; this will be a hard
-        // error if the subpattern is of edition >= 2024.
-        let trimmed_span = subpat.span.until(cutoff_span).with_ctxt(subpat.span.ctxt());
+        // Importantly, the edition of the trimmed span should be the same as `subpat.span`; this
+        // will be a hard error if the subpattern is of edition >= 2024.
+        let from_expansion = subpat.span.from_expansion();
+        let trimmed_span = if from_expansion {
+            subpat.span
+        } else {
+            self.tcx.sess.source_map().span_through_char(subpat.span, final_char)
+        };
 
         let mut typeck_results = self.typeck_results.borrow_mut();
         let mut table = typeck_results.rust_2024_migration_desugared_pats_mut();
@@ -2824,7 +2825,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         };
         // Only provide a detailed label if the problematic subpattern isn't from an expansion.
         // In the case that it's from a macro, we'll add a more detailed note in the emitter.
-        let from_expansion = subpat.span.from_expansion();
         let primary_label = if from_expansion {
             // We can't suggest eliding modifiers within expansions.
             info.suggest_eliding_modes = false;
diff --git a/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.stderr b/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.stderr
index eb76615aac129..6efda4f757ff6 100644
--- a/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.stderr
+++ b/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.stderr
@@ -581,12 +581,10 @@ LL |     let &[migration_lint_macros::bind_ref!(a)] = &[0];
    |         +
 
 error: reference patterns may only be written when the default binding mode is `move` in Rust 2024
-  --> $DIR/auxiliary/migration_lint_macros.rs:15:22
+  --> $DIR/migration_lint.rs:249:10
    |
-LL |       ($foo:ident) => {
-   |  ______________________^
-LL | |         ref $foo
-   | |________________^ reference pattern not allowed under `ref` default binding mode
+LL |     let [&migration_lint_macros::bind_ref!(a)] = &[&0];
+   |          ^ reference pattern not allowed under `ref` default binding mode
    |
    = warning: this changes meaning in Rust 2024
    = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/match-ergonomics.html>
@@ -596,7 +594,6 @@ note: matching on a reference type with a non-reference pattern changes the defa
 LL |     let [&migration_lint_macros::bind_ref!(a)] = &[&0];
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this matches on type `&_`
 help: make the implied reference pattern explicit
-  --> $DIR/migration_lint.rs:249:9
    |
 LL |     let &[&migration_lint_macros::bind_ref!(a)] = &[&0];
    |         +

From 9964b95c974034f3a249089331dbdf4c3af3cb19 Mon Sep 17 00:00:00 2001
From: dianne <diannes.gm@gmail.com>
Date: Thu, 30 Jan 2025 21:42:31 -0800
Subject: [PATCH 03/10] remove comment on pattern migration tests about
 possible future improvements

The following commits are the possible future improvements.
---
 .../migration_lint.fixed                      |  2 -
 .../migration_lint.rs                         |  2 -
 .../migration_lint.stderr                     | 48 +++++++++----------
 3 files changed, 24 insertions(+), 28 deletions(-)

diff --git a/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.fixed b/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.fixed
index bb4ecc09063b7..0d25ac32a4f90 100644
--- a/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.fixed
+++ b/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.fixed
@@ -151,8 +151,6 @@ fn main() {
     //~^ ERROR: reference patterns may only be written when the default binding mode is `move` in Rust 2024
     //~| WARN: this changes meaning in Rust 2024
 
-    // NB: Most of the following tests are for possible future improvements to migration suggestions
-
     // Test removing multiple binding modifiers.
     let Struct { a, b, c } = &Struct { a: 0, b: 0, c: 0 };
     //~^ ERROR: binding modifiers may only be written when the default binding mode is `move` in Rust 2024
diff --git a/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.rs b/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.rs
index 2837c8d81dbdd..de9daf82860f0 100644
--- a/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.rs
+++ b/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.rs
@@ -151,8 +151,6 @@ fn main() {
     //~^ ERROR: reference patterns may only be written when the default binding mode is `move` in Rust 2024
     //~| WARN: this changes meaning in Rust 2024
 
-    // NB: Most of the following tests are for possible future improvements to migration suggestions
-
     // Test removing multiple binding modifiers.
     let Struct { ref a, ref b, c } = &Struct { a: 0, b: 0, c: 0 };
     //~^ ERROR: binding modifiers may only be written when the default binding mode is `move` in Rust 2024
diff --git a/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.stderr b/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.stderr
index 6efda4f757ff6..b6bf4237f2b12 100644
--- a/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.stderr
+++ b/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.stderr
@@ -342,7 +342,7 @@ LL |     let &[&(_)] = &[&0];
    |         +
 
 error: binding modifiers may only be written when the default binding mode is `move` in Rust 2024
-  --> $DIR/migration_lint.rs:157:18
+  --> $DIR/migration_lint.rs:155:18
    |
 LL |     let Struct { ref a, ref b, c } = &Struct { a: 0, b: 0, c: 0 };
    |                  ^^^    ^^^ binding modifier not allowed under `ref` default binding mode
@@ -352,7 +352,7 @@ LL |     let Struct { ref a, ref b, c } = &Struct { a: 0, b: 0, c: 0 };
    = warning: this changes meaning in Rust 2024
    = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/match-ergonomics.html>
 note: matching on a reference type with a non-reference pattern changes the default binding mode
-  --> $DIR/migration_lint.rs:157:9
+  --> $DIR/migration_lint.rs:155:9
    |
 LL |     let Struct { ref a, ref b, c } = &Struct { a: 0, b: 0, c: 0 };
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^ this matches on type `&_`
@@ -363,7 +363,7 @@ LL +     let Struct { a, b, c } = &Struct { a: 0, b: 0, c: 0 };
    |
 
 error: binding modifiers may only be written when the default binding mode is `move` in Rust 2024
-  --> $DIR/migration_lint.rs:164:18
+  --> $DIR/migration_lint.rs:162:18
    |
 LL |     let Struct { ref a, ref mut b, c } = &mut Struct { a: 0, b: 0, c: 0 };
    |                  ^^^    ^^^^^^^ binding modifier not allowed under `ref mut` default binding mode
@@ -373,7 +373,7 @@ LL |     let Struct { ref a, ref mut b, c } = &mut Struct { a: 0, b: 0, c: 0 };
    = warning: this changes meaning in Rust 2024
    = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/match-ergonomics.html>
 note: matching on a reference type with a non-reference pattern changes the default binding mode
-  --> $DIR/migration_lint.rs:164:9
+  --> $DIR/migration_lint.rs:162:9
    |
 LL |     let Struct { ref a, ref mut b, c } = &mut Struct { a: 0, b: 0, c: 0 };
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this matches on type `&mut _`
@@ -383,7 +383,7 @@ LL |     let &mut Struct { ref a, ref mut b, ref mut c } = &mut Struct { a: 0, b
    |         ++++                            +++++++
 
 error: reference patterns may only be written when the default binding mode is `move` in Rust 2024
-  --> $DIR/migration_lint.rs:172:21
+  --> $DIR/migration_lint.rs:170:21
    |
 LL |     let Struct { a: &[ref a], b: &mut [[b]], c } = &mut &Struct { a: &[0], b: &mut [&[0]], c: 0 };
    |                     ^            ^^^^ reference pattern not allowed under `ref` default binding mode
@@ -393,7 +393,7 @@ LL |     let Struct { a: &[ref a], b: &mut [[b]], c } = &mut &Struct { a: &[0],
    = warning: this changes meaning in Rust 2024
    = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/match-ergonomics.html>
 note: matching on a reference type with a non-reference pattern changes the default binding mode
-  --> $DIR/migration_lint.rs:172:9
+  --> $DIR/migration_lint.rs:170:9
    |
 LL |     let Struct { a: &[ref a], b: &mut [[b]], c } = &mut &Struct { a: &[0], b: &mut [&[0]], c: 0 };
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this matches on type `&_`
@@ -403,7 +403,7 @@ LL |     let &mut &Struct { a: &[ref a], b: &mut [&[ref b]], ref c } = &mut &Str
    |         ++++++                               + +++      +++
 
 error: reference patterns may only be written when the default binding mode is `move` in Rust 2024
-  --> $DIR/migration_lint.rs:180:13
+  --> $DIR/migration_lint.rs:178:13
    |
 LL |     let Foo(&ref a) = &Foo(&0);
    |             ^ reference pattern not allowed under `ref` default binding mode
@@ -411,7 +411,7 @@ LL |     let Foo(&ref a) = &Foo(&0);
    = warning: this changes meaning in Rust 2024
    = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/match-ergonomics.html>
 note: matching on a reference type with a non-reference pattern changes the default binding mode
-  --> $DIR/migration_lint.rs:180:9
+  --> $DIR/migration_lint.rs:178:9
    |
 LL |     let Foo(&ref a) = &Foo(&0);
    |         ^^^^^^^^^^^ this matches on type `&_`
@@ -421,7 +421,7 @@ LL |     let &Foo(&ref a) = &Foo(&0);
    |         +
 
 error: reference patterns may only be written when the default binding mode is `move` in Rust 2024
-  --> $DIR/migration_lint.rs:186:10
+  --> $DIR/migration_lint.rs:184:10
    |
 LL |     let (&a, b, [c], [(d, [e])]) = &(&0, 0, &[0], &mut [&mut (0, &[0])]);
    |          ^ reference pattern not allowed under `ref` default binding mode
@@ -429,7 +429,7 @@ LL |     let (&a, b, [c], [(d, [e])]) = &(&0, 0, &[0], &mut [&mut (0, &[0])]);
    = warning: this changes meaning in Rust 2024
    = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/match-ergonomics.html>
 note: matching on a reference type with a non-reference pattern changes the default binding mode
-  --> $DIR/migration_lint.rs:186:9
+  --> $DIR/migration_lint.rs:184:9
    |
 LL |     let (&a, b, [c], [(d, [e])]) = &(&0, 0, &[0], &mut [&mut (0, &[0])]);
    |         ^^^^^^^^^^^^^^^^^^^^^^^^ this matches on type `&_`
@@ -439,7 +439,7 @@ LL |     let &(&a, ref b, &[ref c], &mut [&mut (ref d, &[ref e])]) = &(&0, 0, &[
    |         +     +++    + +++     ++++  ++++  +++    + +++
 
 error: binding modifiers may only be written when the default binding mode is `move` in Rust 2024
-  --> $DIR/migration_lint.rs:196:19
+  --> $DIR/migration_lint.rs:194:19
    |
 LL |     let (a, [b], [mut c]) = &(0, &mut [0], &[0]);
    |                   ^^^ binding modifier not allowed under `ref` default binding mode
@@ -447,7 +447,7 @@ LL |     let (a, [b], [mut c]) = &(0, &mut [0], &[0]);
    = warning: this changes meaning in Rust 2024
    = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/match-ergonomics.html>
 note: matching on a reference type with a non-reference pattern changes the default binding mode
-  --> $DIR/migration_lint.rs:196:9
+  --> $DIR/migration_lint.rs:194:9
    |
 LL |     let (a, [b], [mut c]) = &(0, &mut [0], &[0]);
    |         ^^^^^^^^^^^^^^^^^ this matches on type `&_`
@@ -457,7 +457,7 @@ LL |     let &(ref a, &mut [ref b], &[mut c]) = &(0, &mut [0], &[0]);
    |         + +++    ++++  +++     +
 
 error: reference patterns may only be written when the default binding mode is `move` in Rust 2024
-  --> $DIR/migration_lint.rs:204:10
+  --> $DIR/migration_lint.rs:202:10
    |
 LL |     let (&a, (b, &[ref c])) = &(&0, &mut (0, &[0]));
    |          ^       ^ reference pattern not allowed under `ref` default binding mode
@@ -467,7 +467,7 @@ LL |     let (&a, (b, &[ref c])) = &(&0, &mut (0, &[0]));
    = warning: this changes meaning in Rust 2024
    = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/match-ergonomics.html>
 note: matching on a reference type with a non-reference pattern changes the default binding mode
-  --> $DIR/migration_lint.rs:204:9
+  --> $DIR/migration_lint.rs:202:9
    |
 LL |     let (&a, (b, &[ref c])) = &(&0, &mut (0, &[0]));
    |         ^^^^^^^^^^^^^^^^^^^ this matches on type `&_`
@@ -477,7 +477,7 @@ LL |     let &(&a, &mut (ref b, &[ref c])) = &(&0, &mut (0, &[0]));
    |         +     ++++  +++
 
 error: binding modifiers may only be written when the default binding mode is `move` in Rust 2024
-  --> $DIR/migration_lint.rs:212:10
+  --> $DIR/migration_lint.rs:210:10
    |
 LL |     let [mut a @ b] = &[0];
    |          ^^^ binding modifier not allowed under `ref` default binding mode
@@ -485,7 +485,7 @@ LL |     let [mut a @ b] = &[0];
    = warning: this changes meaning in Rust 2024
    = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/match-ergonomics.html>
 note: matching on a reference type with a non-reference pattern changes the default binding mode
-  --> $DIR/migration_lint.rs:212:9
+  --> $DIR/migration_lint.rs:210:9
    |
 LL |     let [mut a @ b] = &[0];
    |         ^^^^^^^^^^^ this matches on type `&_`
@@ -495,7 +495,7 @@ LL |     let &[mut a @ ref b] = &[0];
    |         +         +++
 
 error: binding modifiers may only be written when the default binding mode is `move` in Rust 2024
-  --> $DIR/migration_lint.rs:219:14
+  --> $DIR/migration_lint.rs:217:14
    |
 LL |     let [a @ mut b] = &[0];
    |              ^^^ binding modifier not allowed under `ref` default binding mode
@@ -503,7 +503,7 @@ LL |     let [a @ mut b] = &[0];
    = warning: this changes meaning in Rust 2024
    = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/match-ergonomics.html>
 note: matching on a reference type with a non-reference pattern changes the default binding mode
-  --> $DIR/migration_lint.rs:219:9
+  --> $DIR/migration_lint.rs:217:9
    |
 LL |     let [a @ mut b] = &[0];
    |         ^^^^^^^^^^^ this matches on type `&_`
@@ -513,7 +513,7 @@ LL |     let &[ref a @ mut b] = &[0];
    |         + +++
 
 error: reference patterns may only be written when the default binding mode is `move` in Rust 2024
-  --> $DIR/migration_lint.rs:226:14
+  --> $DIR/migration_lint.rs:224:14
    |
 LL |     let [Foo(&ref a @ ref b), Foo(&ref c @ d)] = [&Foo(&0); 2];
    |              ^                    ^ reference pattern not allowed under `ref` default binding mode
@@ -523,12 +523,12 @@ LL |     let [Foo(&ref a @ ref b), Foo(&ref c @ d)] = [&Foo(&0); 2];
    = warning: this changes meaning in Rust 2024
    = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/match-ergonomics.html>
 note: matching on a reference type with a non-reference pattern changes the default binding mode
-  --> $DIR/migration_lint.rs:226:31
+  --> $DIR/migration_lint.rs:224:31
    |
 LL |     let [Foo(&ref a @ ref b), Foo(&ref c @ d)] = [&Foo(&0); 2];
    |                               ^^^^^^^^^^^^^^^ this matches on type `&_`
 note: matching on a reference type with a non-reference pattern changes the default binding mode
-  --> $DIR/migration_lint.rs:226:10
+  --> $DIR/migration_lint.rs:224:10
    |
 LL |     let [Foo(&ref a @ ref b), Foo(&ref c @ d)] = [&Foo(&0); 2];
    |          ^^^^^^^^^^^^^^^^^^^ this matches on type `&_`
@@ -538,7 +538,7 @@ LL |     let [&Foo(&ref a @ ref b), &Foo(&ref c @ d)] = [&Foo(&0); 2];
    |          +                     +
 
 error: reference patterns may only be written when the default binding mode is `move` in Rust 2024
-  --> $DIR/migration_lint.rs:235:14
+  --> $DIR/migration_lint.rs:233:14
    |
 LL |     let [Foo(&ref a @ [ref b]), Foo(&ref c @ [d])] = [&Foo(&[0]); 2];
    |              ^                      ^ reference pattern not allowed under `ref` default binding mode
@@ -548,12 +548,12 @@ LL |     let [Foo(&ref a @ [ref b]), Foo(&ref c @ [d])] = [&Foo(&[0]); 2];
    = warning: this changes meaning in Rust 2024
    = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/match-ergonomics.html>
 note: matching on a reference type with a non-reference pattern changes the default binding mode
-  --> $DIR/migration_lint.rs:235:33
+  --> $DIR/migration_lint.rs:233:33
    |
 LL |     let [Foo(&ref a @ [ref b]), Foo(&ref c @ [d])] = [&Foo(&[0]); 2];
    |                                 ^^^^^^^^^^^^^^^^^ this matches on type `&_`
 note: matching on a reference type with a non-reference pattern changes the default binding mode
-  --> $DIR/migration_lint.rs:235:10
+  --> $DIR/migration_lint.rs:233:10
    |
 LL |     let [Foo(&ref a @ [ref b]), Foo(&ref c @ [d])] = [&Foo(&[0]); 2];
    |          ^^^^^^^^^^^^^^^^^^^^^ this matches on type `&_`

From 95a697e996a68ee82c2effb6ee71a932e5a1f8f4 Mon Sep 17 00:00:00 2001
From: dianne <diannes.gm@gmail.com>
Date: Sun, 9 Feb 2025 19:22:29 -0800
Subject: [PATCH 04/10] track patterns' dereferences in a forest

This will let us propagate changes to the default binding mode when
suggesting adding `&` patterns.
---
 .../src/thir/pattern/migration.rs             | 133 +++++++++++++-----
 .../rustc_mir_build/src/thir/pattern/mod.rs   |  12 +-
 2 files changed, 101 insertions(+), 44 deletions(-)

diff --git a/compiler/rustc_mir_build/src/thir/pattern/migration.rs b/compiler/rustc_mir_build/src/thir/pattern/migration.rs
index bd7787b643d57..722b269090c50 100644
--- a/compiler/rustc_mir_build/src/thir/pattern/migration.rs
+++ b/compiler/rustc_mir_build/src/thir/pattern/migration.rs
@@ -3,6 +3,7 @@
 use rustc_data_structures::fx::FxIndexMap;
 use rustc_errors::MultiSpan;
 use rustc_hir::{BindingMode, ByRef, HirId, Mutability};
+use rustc_index::IndexVec;
 use rustc_lint as lint;
 use rustc_middle::span_bug;
 use rustc_middle::ty::{self, Rust2024IncompatiblePatInfo, Ty, TyCtxt};
@@ -17,9 +18,11 @@ pub(super) struct PatMigration<'a> {
     suggestion: Vec<(Span, String)>,
     ref_pattern_count: usize,
     binding_mode_count: usize,
-    /// Internal state: the ref-mutability of the default binding mode at the subpattern being
-    /// lowered, with the span where it was introduced. `None` for a by-value default mode.
-    default_mode_span: Option<(Span, ty::Mutability)>,
+    /// All the dereferences encountered in lowering the pattern, along with how their corresponding
+    /// patterns affect the default binding mode.
+    derefs: IndexVec<PatDerefIdx, PatDeref>,
+    /// Internal state: the innermost deref above the pattern currently being lowered.
+    innermost_deref: Option<PatDerefIdx>,
     /// Labels for where incompatibility-causing by-ref default binding modes were introduced.
     // FIXME(ref_pat_eat_one_layer_2024_structural): To track the default binding mode, we duplicate
     // logic from HIR typeck (in order to avoid needing to store all changes to the dbm in
@@ -30,13 +33,31 @@ pub(super) struct PatMigration<'a> {
     info: &'a Rust2024IncompatiblePatInfo,
 }
 
+rustc_index::newtype_index! {
+    struct PatDerefIdx {}
+}
+
+struct PatDeref {
+    /// The default binding mode for variables under this deref.
+    real_default_mode: ByRef,
+    /// The span that introduced the current default binding mode, or `None` for the top-level pat.
+    default_mode_origin: Option<Span>,
+    /// The next deref above this. Since we can't suggest using `&` or `&mut` on a by-ref default
+    /// binding mode, a suggested deref's ancestors must also all be suggested.
+    // FIXME(ref_pat_eat_one_layer_2024): By suggesting `&` and `&mut` patterns that can eat the
+    // default binding mode, we'll be able to make more local suggestions. That may make this forest
+    // structure unnecessary.
+    parent: Option<PatDerefIdx>,
+}
+
 impl<'a> PatMigration<'a> {
     pub(super) fn new(info: &'a Rust2024IncompatiblePatInfo) -> Self {
         PatMigration {
             suggestion: Vec::new(),
             ref_pattern_count: 0,
             binding_mode_count: 0,
-            default_mode_span: None,
+            derefs: IndexVec::new(),
+            innermost_deref: None,
             default_mode_labels: Default::default(),
             info,
         }
@@ -86,15 +107,39 @@ impl<'a> PatMigration<'a> {
         }
     }
 
+    /// When lowering a reference pattern or a binding with a modifier, this checks if the default
+    /// binding mode is by-ref, and if so, adds a labeled note to the diagnostic with the origin of
+    /// the current default binding mode.
+    fn add_default_mode_label_if_needed(&mut self) {
+        if let ByRef::Yes(ref_mutbl) = self.real_default_mode() {
+            // The by-ref default binding mode must have come from an implicit deref. If there was a
+            // problem in tracking that for the diagnostic, try to avoid ICE on release builds.
+            debug_assert!(
+                self.innermost_deref
+                    .is_some_and(|ix| self.derefs[ix].default_mode_origin.is_some())
+            );
+            if let Some(ix) = self.innermost_deref
+                && let Some(span) = self.derefs[ix].default_mode_origin
+            {
+                self.default_mode_labels.insert(span, ref_mutbl);
+            }
+        }
+    }
+
+    /// The default binding mode at the current pattern.
+    fn real_default_mode(&self) -> ByRef {
+        if let Some(current_ix) = self.innermost_deref {
+            self.derefs[current_ix].real_default_mode
+        } else {
+            ByRef::No
+        }
+    }
+
     /// Tracks when we're lowering a pattern that implicitly dereferences the scrutinee.
     /// This should only be called when the pattern type adjustments list `adjustments` is
-    /// non-empty. Returns the prior default binding mode; this should be followed by a call to
-    /// [`PatMigration::leave_ref`] to restore it when we leave the pattern.
-    pub(super) fn visit_implicit_derefs<'tcx>(
-        &mut self,
-        pat_span: Span,
-        adjustments: &[Ty<'tcx>],
-    ) -> Option<(Span, Mutability)> {
+    /// non-empty.
+    /// This should be followed by a call to [`PatMigration::leave_ref`] when we leave the pattern.
+    pub(super) fn visit_implicit_derefs<'tcx>(&mut self, pat_span: Span, adjustments: &[Ty<'tcx>]) {
         let implicit_deref_mutbls = adjustments.iter().map(|ref_ty| {
             let &ty::Ref(_, _, mutbl) = ref_ty.kind() else {
                 span_bug!(pat_span, "pattern implicitly dereferences a non-ref type");
@@ -112,35 +157,49 @@ impl<'a> PatMigration<'a> {
         }
 
         // Remember if this changed the default binding mode, in case we want to label it.
-        let min_mutbl = implicit_deref_mutbls.min().unwrap();
-        if self.default_mode_span.is_none_or(|(_, old_mutbl)| min_mutbl < old_mutbl) {
-            // This changes the default binding mode to `ref` or `ref mut`. Return the old mode so
-            // it can be reinstated when we leave the pattern.
-            self.default_mode_span.replace((pat_span, min_mutbl))
-        } else {
-            // This does not change the default binding mode; it was already `ref` or `ref mut`.
-            self.default_mode_span
-        }
+        let new_real_ref_mutbl = match self.real_default_mode() {
+            ByRef::Yes(Mutability::Not) => Mutability::Not,
+            _ => implicit_deref_mutbls.min().unwrap(),
+        };
+        self.push_deref(pat_span, ByRef::Yes(new_real_ref_mutbl));
     }
 
     /// Tracks the default binding mode when we're lowering a `&` or `&mut` pattern.
-    /// Returns the prior default binding mode; this should be followed by a call to
-    /// [`PatMigration::leave_ref`] to restore it when we leave the pattern.
-    pub(super) fn visit_explicit_deref(&mut self) -> Option<(Span, Mutability)> {
-        if let Some((default_mode_span, default_ref_mutbl)) = self.default_mode_span {
-            // If this eats a by-ref default binding mode, label the binding mode.
-            self.default_mode_labels.insert(default_mode_span, default_ref_mutbl);
-        }
-        // Set the default binding mode to by-value and return the old default binding mode so it
-        // can be reinstated when we leave the pattern.
-        self.default_mode_span.take()
+    /// This should be followed by a call to [`PatMigration::leave_ref`] when we leave the pattern.
+    // FIXME(ref_pat_eat_one_layer_2024): This assumes reference patterns correspond to real
+    // dereferences. If reference patterns can match the default binding mode alone, we may need to
+    // check `TypeckResults::skipped_ref_pats` to tell if this pattern corresponds to an implicit
+    // dereference we've already visited.
+    pub(super) fn visit_explicit_deref(&mut self, pat_span: Span) {
+        // If this eats a by-ref default binding mode, label the binding mode.
+        self.add_default_mode_label_if_needed();
+        // Set the default binding mode to by-value.
+        self.push_deref(pat_span, ByRef::No);
+    }
+
+    /// Adds a deref to our deref-forest, so that we can track the default binding mode.
+    // TODO: this is also for propagating binding mode changes when we suggest adding patterns
+    fn push_deref(&mut self, span: Span, real_default_mode: ByRef) {
+        let parent = self.innermost_deref;
+        // If this keeps the default binding mode the same, it shares a mode origin with its
+        // parent. If it changes the default binding mode, its mode origin is itself.
+        let default_mode_origin = if real_default_mode == self.real_default_mode() {
+            parent.and_then(|p| self.derefs[p].default_mode_origin)
+        } else {
+            Some(span)
+        };
+        let my_ix = self.derefs.push(PatDeref { real_default_mode, default_mode_origin, parent });
+        self.innermost_deref = Some(my_ix);
     }
 
     /// Restores the default binding mode after lowering a pattern that could change it.
     /// This should follow a call to either [`PatMigration::visit_explicit_deref`] or
     /// [`PatMigration::visit_implicit_derefs`].
-    pub(super) fn leave_ref(&mut self, old_mode_span: Option<(Span, Mutability)>) {
-        self.default_mode_span = old_mode_span
+    pub(super) fn leave_ref(&mut self) {
+        debug_assert!(self.innermost_deref.is_some(), "entering/leaving refs should be paired");
+        if let Some(child_ix) = self.innermost_deref {
+            self.innermost_deref = self.derefs[child_ix].parent;
+        }
     }
 
     /// Determines if a binding is relevant to the diagnostic and adjusts the notes/suggestion if
@@ -153,13 +212,11 @@ impl<'a> PatMigration<'a> {
         explicit_ba: BindingMode,
         ident: Ident,
     ) {
-        if explicit_ba != BindingMode::NONE
-            && let Some((default_mode_span, default_ref_mutbl)) = self.default_mode_span
-        {
+        if explicit_ba != BindingMode::NONE {
             // If this overrides a by-ref default binding mode, label the binding mode.
-            self.default_mode_labels.insert(default_mode_span, default_ref_mutbl);
-            // If our suggestion is to elide redundnt modes, this will be one of them.
-            if self.info.suggest_eliding_modes {
+            self.add_default_mode_label_if_needed();
+            if self.info.suggest_eliding_modes && matches!(mode.0, ByRef::Yes(_)) {
+                // If our suggestion is to elide redundant modes, this will be one of them.
                 self.suggestion.push((pat_span.with_hi(ident.span.lo()), String::new()));
                 self.binding_mode_count += 1;
             }
diff --git a/compiler/rustc_mir_build/src/thir/pattern/mod.rs b/compiler/rustc_mir_build/src/thir/pattern/mod.rs
index 8dc3f998e0916..d1941564c0f78 100644
--- a/compiler/rustc_mir_build/src/thir/pattern/mod.rs
+++ b/compiler/rustc_mir_build/src/thir/pattern/mod.rs
@@ -66,11 +66,10 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
             self.typeck_results.pat_adjustments().get(pat.hir_id).map_or(&[], |v| &**v);
 
         // Track the default binding mode for the Rust 2024 migration suggestion.
-        let mut opt_old_mode_span = None;
         if let Some(s) = &mut self.rust_2024_migration
             && !adjustments.is_empty()
         {
-            opt_old_mode_span = s.visit_implicit_derefs(pat.span, adjustments);
+            s.visit_implicit_derefs(pat.span, adjustments);
         }
 
         // When implicit dereferences have been inserted in this pattern, the unadjusted lowered
@@ -113,7 +112,7 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
         if let Some(s) = &mut self.rust_2024_migration
             && !adjustments.is_empty()
         {
-            s.leave_ref(opt_old_mode_span);
+            s.leave_ref();
         }
 
         adjusted_pat
@@ -309,11 +308,12 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
             }
             hir::PatKind::Ref(subpattern, _) => {
                 // Track the default binding mode for the Rust 2024 migration suggestion.
-                let opt_old_mode_span =
-                    self.rust_2024_migration.as_mut().and_then(|s| s.visit_explicit_deref());
+                if let Some(s) = &mut self.rust_2024_migration {
+                    s.visit_explicit_deref(pat.span);
+                }
                 let subpattern = self.lower_pattern(subpattern);
                 if let Some(s) = &mut self.rust_2024_migration {
-                    s.leave_ref(opt_old_mode_span);
+                    s.leave_ref();
                 }
                 PatKind::Deref { subpattern }
             }

From fa739e68aea44a0a68df1f8fcd00d0bfbed7dfee Mon Sep 17 00:00:00 2001
From: dianne <diannes.gm@gmail.com>
Date: Mon, 10 Feb 2025 01:01:41 -0800
Subject: [PATCH 05/10] separate suggestion building from default binding mode
 tracking

This lets us revise the suggestion on-the-fly as we discover which
reference patterns and binding modifiers are necessary vs. which may be
omitted.
---
 compiler/rustc_mir_build/src/errors.rs        |  12 +-
 .../src/thir/pattern/migration.rs             | 187 +++++++++++++-----
 .../rustc_mir_build/src/thir/pattern/mod.rs   |   4 +-
 3 files changed, 141 insertions(+), 62 deletions(-)

diff --git a/compiler/rustc_mir_build/src/errors.rs b/compiler/rustc_mir_build/src/errors.rs
index f1753be845d4f..2db99f95eaecf 100644
--- a/compiler/rustc_mir_build/src/errors.rs
+++ b/compiler/rustc_mir_build/src/errors.rs
@@ -1097,15 +1097,15 @@ pub(crate) enum MiscPatternSuggestion {
 
 #[derive(LintDiagnostic)]
 #[diag(mir_build_rust_2024_incompatible_pat)]
-pub(crate) struct Rust2024IncompatiblePat {
+pub(crate) struct Rust2024IncompatiblePat<'m> {
     #[subdiagnostic]
-    pub(crate) sugg: Rust2024IncompatiblePatSugg,
+    pub(crate) sugg: Rust2024IncompatiblePatSugg<'m>,
     pub(crate) bad_modifiers: bool,
     pub(crate) bad_ref_pats: bool,
     pub(crate) is_hard_error: bool,
 }
 
-pub(crate) struct Rust2024IncompatiblePatSugg {
+pub(crate) struct Rust2024IncompatiblePatSugg<'m> {
     /// If true, our suggestion is to elide explicit binding modifiers.
     /// If false, our suggestion is to make the pattern fully explicit.
     pub(crate) suggest_eliding_modes: bool,
@@ -1113,10 +1113,10 @@ pub(crate) struct Rust2024IncompatiblePatSugg {
     pub(crate) ref_pattern_count: usize,
     pub(crate) binding_mode_count: usize,
     /// Labels for where incompatibility-causing by-ref default binding modes were introduced.
-    pub(crate) default_mode_labels: FxIndexMap<Span, ty::Mutability>,
+    pub(crate) default_mode_labels: &'m FxIndexMap<Span, ty::Mutability>,
 }
 
-impl Subdiagnostic for Rust2024IncompatiblePatSugg {
+impl<'m> Subdiagnostic for Rust2024IncompatiblePatSugg<'m> {
     fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
         self,
         diag: &mut Diag<'_, G>,
@@ -1124,7 +1124,7 @@ impl Subdiagnostic for Rust2024IncompatiblePatSugg {
     ) {
         // Format and emit explanatory notes about default binding modes. Reversing the spans' order
         // means if we have nested spans, the innermost ones will be visited first.
-        for (span, def_br_mutbl) in self.default_mode_labels.into_iter().rev() {
+        for (&span, &def_br_mutbl) in self.default_mode_labels.iter().rev() {
             // Don't point to a macro call site.
             if !span.from_expansion() {
                 let note_msg = "matching on a reference type with a non-reference pattern changes the default binding mode";
diff --git a/compiler/rustc_mir_build/src/thir/pattern/migration.rs b/compiler/rustc_mir_build/src/thir/pattern/migration.rs
index 722b269090c50..6864df726a165 100644
--- a/compiler/rustc_mir_build/src/thir/pattern/migration.rs
+++ b/compiler/rustc_mir_build/src/thir/pattern/migration.rs
@@ -2,7 +2,7 @@
 
 use rustc_data_structures::fx::FxIndexMap;
 use rustc_errors::MultiSpan;
-use rustc_hir::{BindingMode, ByRef, HirId, Mutability};
+use rustc_hir::{self as hir, BindingMode, ByRef, HirId, Mutability};
 use rustc_index::IndexVec;
 use rustc_lint as lint;
 use rustc_middle::span_bug;
@@ -14,13 +14,13 @@ use crate::fluent_generated as fluent;
 
 /// For patterns flagged for migration during HIR typeck, this handles constructing and emitting
 /// a diagnostic suggestion.
-pub(super) struct PatMigration<'a> {
-    suggestion: Vec<(Span, String)>,
-    ref_pattern_count: usize,
-    binding_mode_count: usize,
+pub(super) struct PatMigration<'a, 'tcx> {
+    /// All the variable bindings encountered in lowering the pattern, along with whether to
+    /// suggest adding/removing them.
+    bindings: IndexVec<PatBindingIdx, PatBinding>,
     /// All the dereferences encountered in lowering the pattern, along with how their corresponding
-    /// patterns affect the default binding mode.
-    derefs: IndexVec<PatDerefIdx, PatDeref>,
+    /// patterns affect the default binding mode, and whether to suggest adding/removing them.
+    derefs: IndexVec<PatDerefIdx, PatDeref<'a, 'tcx>>,
     /// Internal state: the innermost deref above the pattern currently being lowered.
     innermost_deref: Option<PatDerefIdx>,
     /// Labels for where incompatibility-causing by-ref default binding modes were introduced.
@@ -33,11 +33,31 @@ pub(super) struct PatMigration<'a> {
     info: &'a Rust2024IncompatiblePatInfo,
 }
 
+rustc_index::newtype_index! {
+    struct PatBindingIdx {}
+}
+
 rustc_index::newtype_index! {
     struct PatDerefIdx {}
 }
 
-struct PatDeref {
+struct PatBinding {
+    /// The span of the binding modifier (empty if no explicit modifier was provided).
+    span: Span,
+    /// The actual binding mode of this binding.
+    mode: BindingMode,
+    /// Whether to include a binding modifier (e.g. `ref` or `mut`) in the suggested pattern.
+    suggest: bool,
+}
+
+struct PatDeref<'a, 'tcx> {
+    /// The span of the pattern where this deref occurs (implicitly or explicitly).
+    span: Span,
+    /// Whether this span is for a potentially-removable explicitly-provided deref, or an implicit
+    /// dereference which we can potentially suggest making explicit.
+    kind: PatDerefKind<'a, 'tcx>,
+    /// Whether to include this as a `&` or `&mut` in the suggested pattern.
+    suggest: bool,
     /// The default binding mode for variables under this deref.
     real_default_mode: ByRef,
     /// The span that introduced the current default binding mode, or `None` for the top-level pat.
@@ -50,12 +70,32 @@ struct PatDeref {
     parent: Option<PatDerefIdx>,
 }
 
-impl<'a> PatMigration<'a> {
+enum PatDerefKind<'a, 'tcx> {
+    /// For dereferences from lowering `&` and `&mut` patterns
+    Explicit,
+    /// For dereferences inserted by match ergonomics
+    Implicit { ref_tys: &'a [Ty<'tcx>] },
+}
+
+/// Assuming the input is a slice of reference types implicitly dereferenced by match ergonomics
+/// (stored in [`ty::TypeckResults::pat_adjustments`]), iterate over their reference mutabilities.
+/// A span is provided for debugging purposes.
+fn iter_ref_mutbls<'a, 'tcx>(
+    span: Span,
+    ref_tys: &'a [Ty<'tcx>],
+) -> impl Iterator<Item = Mutability> + use<'a, 'tcx> {
+    ref_tys.iter().map(move |ref_ty| {
+        let &ty::Ref(_, _, mutbl) = ref_ty.kind() else {
+            span_bug!(span, "pattern implicitly dereferences a non-ref type");
+        };
+        mutbl
+    })
+}
+
+impl<'a, 'tcx> PatMigration<'a, 'tcx> {
     pub(super) fn new(info: &'a Rust2024IncompatiblePatInfo) -> Self {
         PatMigration {
-            suggestion: Vec::new(),
-            ref_pattern_count: 0,
-            binding_mode_count: 0,
+            bindings: IndexVec::new(),
             derefs: IndexVec::new(),
             innermost_deref: None,
             default_mode_labels: Default::default(),
@@ -65,19 +105,13 @@ impl<'a> PatMigration<'a> {
 
     /// On Rust 2024, this emits a hard error. On earlier Editions, this emits the
     /// future-incompatibility lint `rust_2024_incompatible_pat`.
-    pub(super) fn emit<'tcx>(self, tcx: TyCtxt<'tcx>, pat_id: HirId) {
+    pub(super) fn emit(self, tcx: TyCtxt<'tcx>, pat_id: HirId) {
         let mut spans =
             MultiSpan::from_spans(self.info.primary_labels.iter().map(|(span, _)| *span).collect());
         for (span, label) in self.info.primary_labels.iter() {
             spans.push_span_label(*span, label.clone());
         }
-        let sugg = Rust2024IncompatiblePatSugg {
-            suggest_eliding_modes: self.info.suggest_eliding_modes,
-            suggestion: self.suggestion,
-            ref_pattern_count: self.ref_pattern_count,
-            binding_mode_count: self.binding_mode_count,
-            default_mode_labels: self.default_mode_labels,
-        };
+        let sugg = self.build_suggestion();
         // If a relevant span is from at least edition 2024, this is a hard error.
         let is_hard_error = spans.primary_spans().iter().any(|span| span.at_least_rust_2024());
         if is_hard_error {
@@ -126,6 +160,61 @@ impl<'a> PatMigration<'a> {
         }
     }
 
+    fn build_suggestion<'m>(&'m self) -> Rust2024IncompatiblePatSugg<'m> {
+        let mut removed_modifiers = 0;
+        let mut added_modifiers = 0;
+        let modes = self.bindings.iter().filter_map(|binding| {
+            if binding.mode == BindingMode::NONE {
+                // This binding mode is written as the empty string; no need to suggest.
+                None
+            } else {
+                if !binding.suggest && !binding.span.is_empty() {
+                    // This binding is in the source but not the suggestion; suggest removing it.
+                    removed_modifiers += 1;
+                    Some((binding.span, String::new()))
+                } else if binding.suggest && binding.span.is_empty() {
+                    // This binding is in the suggestion but not the source; suggest adding it.
+                    added_modifiers += 1;
+                    Some((binding.span, binding.mode.prefix_str().to_owned()))
+                } else {
+                    // This binding is as it should be.
+                    None
+                }
+            }
+        });
+
+        let mut added_ref_pats = 0;
+        let derefs = self.derefs.iter().filter_map(|deref| match deref.kind {
+            PatDerefKind::Explicit if !deref.suggest => {
+                // This is a ref pattern in the source but not the suggestion; suggest removing it.
+                // TODO: we don't yet suggest removing reference patterns
+                todo!();
+            }
+            PatDerefKind::Implicit { ref_tys } if deref.suggest => {
+                // This is a ref pattern in the suggestion but not the source; suggest adding it.
+                let ref_pat_str =
+                    iter_ref_mutbls(deref.span, ref_tys).map(Mutability::ref_prefix_str).collect();
+                added_ref_pats += ref_tys.len();
+                Some((deref.span.shrink_to_lo(), ref_pat_str))
+            }
+            _ => None,
+        });
+
+        let suggestion = modes.chain(derefs).collect();
+        let binding_mode_count = if added_modifiers == 0 && added_ref_pats == 0 {
+            removed_modifiers
+        } else {
+            added_modifiers
+        };
+        Rust2024IncompatiblePatSugg {
+            suggest_eliding_modes: self.info.suggest_eliding_modes,
+            suggestion,
+            binding_mode_count,
+            ref_pattern_count: added_ref_pats,
+            default_mode_labels: &self.default_mode_labels,
+        }
+    }
+
     /// The default binding mode at the current pattern.
     fn real_default_mode(&self) -> ByRef {
         if let Some(current_ix) = self.innermost_deref {
@@ -136,32 +225,17 @@ impl<'a> PatMigration<'a> {
     }
 
     /// Tracks when we're lowering a pattern that implicitly dereferences the scrutinee.
-    /// This should only be called when the pattern type adjustments list `adjustments` is
-    /// non-empty.
+    /// This should only be called when the pattern type adjustments list `ref_tys` is non-empty.
     /// This should be followed by a call to [`PatMigration::leave_ref`] when we leave the pattern.
-    pub(super) fn visit_implicit_derefs<'tcx>(&mut self, pat_span: Span, adjustments: &[Ty<'tcx>]) {
-        let implicit_deref_mutbls = adjustments.iter().map(|ref_ty| {
-            let &ty::Ref(_, _, mutbl) = ref_ty.kind() else {
-                span_bug!(pat_span, "pattern implicitly dereferences a non-ref type");
-            };
-            mutbl
-        });
-
-        if !self.info.suggest_eliding_modes {
-            // If we can't fix the pattern by eliding modifiers, we'll need to make the pattern
-            // fully explicit. i.e. we'll need to suggest reference patterns for this.
-            let suggestion_str: String =
-                implicit_deref_mutbls.clone().map(|mutbl| mutbl.ref_prefix_str()).collect();
-            self.suggestion.push((pat_span.shrink_to_lo(), suggestion_str));
-            self.ref_pattern_count += adjustments.len();
-        }
-
+    pub(super) fn visit_implicit_derefs(&mut self, pat: &hir::Pat<'_>, ref_tys: &'a [Ty<'tcx>]) {
         // Remember if this changed the default binding mode, in case we want to label it.
         let new_real_ref_mutbl = match self.real_default_mode() {
             ByRef::Yes(Mutability::Not) => Mutability::Not,
-            _ => implicit_deref_mutbls.min().unwrap(),
+            _ => iter_ref_mutbls(pat.span, ref_tys).min().unwrap(),
         };
-        self.push_deref(pat_span, ByRef::Yes(new_real_ref_mutbl));
+        self.push_deref(pat.span, ByRef::Yes(new_real_ref_mutbl), PatDerefKind::Implicit {
+            ref_tys,
+        });
     }
 
     /// Tracks the default binding mode when we're lowering a `&` or `&mut` pattern.
@@ -174,12 +248,12 @@ impl<'a> PatMigration<'a> {
         // If this eats a by-ref default binding mode, label the binding mode.
         self.add_default_mode_label_if_needed();
         // Set the default binding mode to by-value.
-        self.push_deref(pat_span, ByRef::No);
+        self.push_deref(pat_span, ByRef::No, PatDerefKind::Explicit);
     }
 
     /// Adds a deref to our deref-forest, so that we can track the default binding mode.
     // TODO: this is also for propagating binding mode changes when we suggest adding patterns
-    fn push_deref(&mut self, span: Span, real_default_mode: ByRef) {
+    fn push_deref(&mut self, span: Span, real_default_mode: ByRef, kind: PatDerefKind<'a, 'tcx>) {
         let parent = self.innermost_deref;
         // If this keeps the default binding mode the same, it shares a mode origin with its
         // parent. If it changes the default binding mode, its mode origin is itself.
@@ -188,7 +262,15 @@ impl<'a> PatMigration<'a> {
         } else {
             Some(span)
         };
-        let my_ix = self.derefs.push(PatDeref { real_default_mode, default_mode_origin, parent });
+        let my_ix = self.derefs.push(PatDeref {
+            span,
+            suggest: !self.info.suggest_eliding_modes
+                || matches!(kind, PatDerefKind::Explicit { .. }),
+            kind,
+            real_default_mode,
+            default_mode_origin,
+            parent,
+        });
         self.innermost_deref = Some(my_ix);
     }
 
@@ -217,23 +299,20 @@ impl<'a> PatMigration<'a> {
             self.add_default_mode_label_if_needed();
             if self.info.suggest_eliding_modes && matches!(mode.0, ByRef::Yes(_)) {
                 // If our suggestion is to elide redundant modes, this will be one of them.
-                self.suggestion.push((pat_span.with_hi(ident.span.lo()), String::new()));
-                self.binding_mode_count += 1;
+                self.bindings.push(PatBinding {
+                    span: pat_span.with_hi(ident.span.lo()),
+                    mode,
+                    suggest: false,
+                });
             }
         }
         if !self.info.suggest_eliding_modes
             && explicit_ba.0 == ByRef::No
-            && let ByRef::Yes(mutbl) = mode.0
+            && matches!(mode.0, ByRef::Yes(_))
         {
             // If we can't fix the pattern by eliding modifiers, we'll need to make the pattern
             // fully explicit. i.e. we'll need to suggest reference patterns for this.
-            let sugg_str = match mutbl {
-                Mutability::Not => "ref ",
-                Mutability::Mut => "ref mut ",
-            };
-            self.suggestion
-                .push((pat_span.with_lo(ident.span.lo()).shrink_to_lo(), sugg_str.to_owned()));
-            self.binding_mode_count += 1;
+            self.bindings.push(PatBinding { span: pat_span.shrink_to_lo(), mode, suggest: true });
         }
     }
 }
diff --git a/compiler/rustc_mir_build/src/thir/pattern/mod.rs b/compiler/rustc_mir_build/src/thir/pattern/mod.rs
index d1941564c0f78..3bfec6549fbb1 100644
--- a/compiler/rustc_mir_build/src/thir/pattern/mod.rs
+++ b/compiler/rustc_mir_build/src/thir/pattern/mod.rs
@@ -34,7 +34,7 @@ struct PatCtxt<'a, 'tcx> {
     typeck_results: &'a ty::TypeckResults<'tcx>,
 
     /// Used by the Rust 2024 migration lint.
-    rust_2024_migration: Option<PatMigration<'a>>,
+    rust_2024_migration: Option<PatMigration<'a, 'tcx>>,
 }
 
 pub(super) fn pat_from_hir<'a, 'tcx>(
@@ -69,7 +69,7 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
         if let Some(s) = &mut self.rust_2024_migration
             && !adjustments.is_empty()
         {
-            s.visit_implicit_derefs(pat.span, adjustments);
+            s.visit_implicit_derefs(pat, adjustments);
         }
 
         // When implicit dereferences have been inserted in this pattern, the unadjusted lowered

From cc37b1639fc19c969032645c4ff218551151c8d2 Mon Sep 17 00:00:00 2001
From: dianne <diannes.gm@gmail.com>
Date: Mon, 10 Feb 2025 02:52:51 -0800
Subject: [PATCH 06/10] compute dbm in user's pattern from deref kind and
 mutability

A slight refactor: we'll always want to pass the mutability in to
`push_deref` to determine the default binding mode when a pattern is
removed, so this eliminates the redundancy of having both that and the
user's dbm as separate arguments.
---
 .../src/thir/pattern/migration.rs             | 26 +++++++++++--------
 .../rustc_mir_build/src/thir/pattern/mod.rs   |  4 +--
 2 files changed, 17 insertions(+), 13 deletions(-)

diff --git a/compiler/rustc_mir_build/src/thir/pattern/migration.rs b/compiler/rustc_mir_build/src/thir/pattern/migration.rs
index 6864df726a165..16bd9f7b1aa35 100644
--- a/compiler/rustc_mir_build/src/thir/pattern/migration.rs
+++ b/compiler/rustc_mir_build/src/thir/pattern/migration.rs
@@ -228,14 +228,10 @@ impl<'a, 'tcx> PatMigration<'a, 'tcx> {
     /// This should only be called when the pattern type adjustments list `ref_tys` is non-empty.
     /// This should be followed by a call to [`PatMigration::leave_ref`] when we leave the pattern.
     pub(super) fn visit_implicit_derefs(&mut self, pat: &hir::Pat<'_>, ref_tys: &'a [Ty<'tcx>]) {
-        // Remember if this changed the default binding mode, in case we want to label it.
-        let new_real_ref_mutbl = match self.real_default_mode() {
-            ByRef::Yes(Mutability::Not) => Mutability::Not,
-            _ => iter_ref_mutbls(pat.span, ref_tys).min().unwrap(),
-        };
-        self.push_deref(pat.span, ByRef::Yes(new_real_ref_mutbl), PatDerefKind::Implicit {
-            ref_tys,
-        });
+        let mutbl = iter_ref_mutbls(pat.span, ref_tys)
+            .min()
+            .expect("`ref_tys` should have at least one element");
+        self.push_deref(pat.span, mutbl, PatDerefKind::Implicit { ref_tys });
     }
 
     /// Tracks the default binding mode when we're lowering a `&` or `&mut` pattern.
@@ -244,17 +240,25 @@ impl<'a, 'tcx> PatMigration<'a, 'tcx> {
     // dereferences. If reference patterns can match the default binding mode alone, we may need to
     // check `TypeckResults::skipped_ref_pats` to tell if this pattern corresponds to an implicit
     // dereference we've already visited.
-    pub(super) fn visit_explicit_deref(&mut self, pat_span: Span) {
+    pub(super) fn visit_explicit_deref(&mut self, pat_span: Span, mutbl: Mutability) {
         // If this eats a by-ref default binding mode, label the binding mode.
         self.add_default_mode_label_if_needed();
         // Set the default binding mode to by-value.
-        self.push_deref(pat_span, ByRef::No, PatDerefKind::Explicit);
+        self.push_deref(pat_span, mutbl, PatDerefKind::Explicit);
     }
 
     /// Adds a deref to our deref-forest, so that we can track the default binding mode.
     // TODO: this is also for propagating binding mode changes when we suggest adding patterns
-    fn push_deref(&mut self, span: Span, real_default_mode: ByRef, kind: PatDerefKind<'a, 'tcx>) {
+    fn push_deref(&mut self, span: Span, mutbl: Mutability, kind: PatDerefKind<'a, 'tcx>) {
         let parent = self.innermost_deref;
+        // Get the new default binding mode in the pattern the user wrote.
+        let real_default_mode = match kind {
+            PatDerefKind::Implicit { .. } => match self.real_default_mode() {
+                ByRef::Yes(old_mutbl) => ByRef::Yes(Ord::min(mutbl, old_mutbl)),
+                ByRef::No => ByRef::Yes(mutbl),
+            },
+            PatDerefKind::Explicit => ByRef::No,
+        };
         // If this keeps the default binding mode the same, it shares a mode origin with its
         // parent. If it changes the default binding mode, its mode origin is itself.
         let default_mode_origin = if real_default_mode == self.real_default_mode() {
diff --git a/compiler/rustc_mir_build/src/thir/pattern/mod.rs b/compiler/rustc_mir_build/src/thir/pattern/mod.rs
index 3bfec6549fbb1..e91c3d5dc99db 100644
--- a/compiler/rustc_mir_build/src/thir/pattern/mod.rs
+++ b/compiler/rustc_mir_build/src/thir/pattern/mod.rs
@@ -306,10 +306,10 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
                 let mutability = if mutable { hir::Mutability::Mut } else { hir::Mutability::Not };
                 PatKind::DerefPattern { subpattern: self.lower_pattern(subpattern), mutability }
             }
-            hir::PatKind::Ref(subpattern, _) => {
+            hir::PatKind::Ref(subpattern, mutbl) => {
                 // Track the default binding mode for the Rust 2024 migration suggestion.
                 if let Some(s) = &mut self.rust_2024_migration {
-                    s.visit_explicit_deref(pat.span);
+                    s.visit_explicit_deref(pat.span, mutbl);
                 }
                 let subpattern = self.lower_pattern(subpattern);
                 if let Some(s) = &mut self.rust_2024_migration {

From 84c3aa11761ef9a38f227acc561b8f714da4e208 Mon Sep 17 00:00:00 2001
From: dianne <diannes.gm@gmail.com>
Date: Mon, 10 Feb 2025 03:40:00 -0800
Subject: [PATCH 07/10] produce suggestions which remove reference patterns

This doesn't yet respect whether patterns are from macro expansions, so
it may try to suggest changes within an expansion (leading to a test
failure).
---
 compiler/rustc_hir_typeck/src/pat.rs          |  23 +-
 .../rustc_middle/src/ty/typeck_results.rs     |   4 +-
 compiler/rustc_mir_build/src/errors.rs        |  45 +++-
 .../src/thir/pattern/migration.rs             | 219 +++++++++++++++---
 .../rustc_mir_build/src/thir/pattern/mod.rs   |   2 +-
 ...nding-on-inh-ref-errors.classic2024.stderr |  14 +-
 ...ng-on-inh-ref-errors.structural2024.stderr |  14 +-
 .../migration_lint.fixed                      |  16 +-
 .../migration_lint.stderr                     |  53 ++---
 9 files changed, 271 insertions(+), 119 deletions(-)

diff --git a/compiler/rustc_hir_typeck/src/pat.rs b/compiler/rustc_hir_typeck/src/pat.rs
index 242536e001821..1895e0abad7e8 100644
--- a/compiler/rustc_hir_typeck/src/pat.rs
+++ b/compiler/rustc_hir_typeck/src/pat.rs
@@ -2798,36 +2798,19 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         // FIXME(ref_pat_eat_one_layer_2024): The migration diagnostic doesn't know how to track the
         // default binding mode in the presence of Rule 3 or Rule 5. As a consequence, the labels it
         // gives for default binding modes are wrong, as well as suggestions based on the default
-        // binding mode. This keeps it from making those suggestions, as doing so could panic.
-        let info = table.entry(pat_id).or_insert_with(|| ty::Rust2024IncompatiblePatInfo {
-            primary_labels: Vec::new(),
-            bad_modifiers: false,
-            bad_ref_pats: false,
-            suggest_eliding_modes: !self.tcx.features().ref_pat_eat_one_layer_2024()
-                && !self.tcx.features().ref_pat_eat_one_layer_2024_structural(),
-        });
+        // binding mode.
+        let info = table.entry(pat_id).or_default();
 
-        let pat_kind = if let PatKind::Binding(user_bind_annot, _, _, _) = subpat.kind {
+        let pat_kind = if matches!(subpat.kind, PatKind::Binding(..)) {
             info.bad_modifiers = true;
-            // If the user-provided binding modifier doesn't match the default binding mode, we'll
-            // need to suggest reference patterns, which can affect other bindings.
-            // For simplicity, we opt to suggest making the pattern fully explicit.
-            info.suggest_eliding_modes &=
-                user_bind_annot == BindingMode(ByRef::Yes(def_br_mutbl), Mutability::Not);
             "binding modifier"
         } else {
             info.bad_ref_pats = true;
-            // For simplicity, we don't try to suggest eliding reference patterns. Thus, we'll
-            // suggest adding them instead, which can affect the types assigned to bindings.
-            // As such, we opt to suggest making the pattern fully explicit.
-            info.suggest_eliding_modes = false;
             "reference pattern"
         };
         // Only provide a detailed label if the problematic subpattern isn't from an expansion.
         // In the case that it's from a macro, we'll add a more detailed note in the emitter.
         let primary_label = if from_expansion {
-            // We can't suggest eliding modifiers within expansions.
-            info.suggest_eliding_modes = false;
             // NB: This wording assumes the only expansions that can produce problematic reference
             // patterns and bindings are macros. If a desugaring or AST pass is added that can do
             // so, we may want to inspect the span's source callee or macro backtrace.
diff --git a/compiler/rustc_middle/src/ty/typeck_results.rs b/compiler/rustc_middle/src/ty/typeck_results.rs
index b8c73d2584379..f923205e631aa 100644
--- a/compiler/rustc_middle/src/ty/typeck_results.rs
+++ b/compiler/rustc_middle/src/ty/typeck_results.rs
@@ -812,7 +812,7 @@ impl<'tcx> std::fmt::Display for UserTypeKind<'tcx> {
 
 /// Information on a pattern incompatible with Rust 2024, for use by the error/migration diagnostic
 /// emitted during THIR construction.
-#[derive(TyEncodable, TyDecodable, Debug, HashStable)]
+#[derive(TyEncodable, TyDecodable, Debug, Default, HashStable)]
 pub struct Rust2024IncompatiblePatInfo {
     /// Labeled spans for `&`s, `&mut`s, and binding modifiers incompatible with Rust 2024.
     pub primary_labels: Vec<(Span, String)>,
@@ -820,6 +820,4 @@ pub struct Rust2024IncompatiblePatInfo {
     pub bad_modifiers: bool,
     /// Whether any `&` or `&mut` patterns occur under a non-`move` default binding mode.
     pub bad_ref_pats: bool,
-    /// If `true`, we can give a simpler suggestion solely by eliding explicit binding modifiers.
-    pub suggest_eliding_modes: bool,
 }
diff --git a/compiler/rustc_mir_build/src/errors.rs b/compiler/rustc_mir_build/src/errors.rs
index 2db99f95eaecf..89a7a7bb0e11a 100644
--- a/compiler/rustc_mir_build/src/errors.rs
+++ b/compiler/rustc_mir_build/src/errors.rs
@@ -1106,22 +1106,29 @@ pub(crate) struct Rust2024IncompatiblePat<'m> {
 }
 
 pub(crate) struct Rust2024IncompatiblePatSugg<'m> {
-    /// If true, our suggestion is to elide explicit binding modifiers.
-    /// If false, our suggestion is to make the pattern fully explicit.
-    pub(crate) suggest_eliding_modes: bool,
     pub(crate) suggestion: Vec<(Span, String)>,
+    /// If `Some(..)`, we provide a suggestion about either adding or removing syntax.
+    /// If `None`, we suggest both additions and removals; use a generic wording for simplicity.
+    pub(crate) kind: Option<Rust2024IncompatiblePatSuggKind>,
     pub(crate) ref_pattern_count: usize,
     pub(crate) binding_mode_count: usize,
     /// Labels for where incompatibility-causing by-ref default binding modes were introduced.
     pub(crate) default_mode_labels: &'m FxIndexMap<Span, ty::Mutability>,
 }
 
+pub(crate) enum Rust2024IncompatiblePatSuggKind {
+    Subtractive,
+    Additive,
+}
+
 impl<'m> Subdiagnostic for Rust2024IncompatiblePatSugg<'m> {
     fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
         self,
         diag: &mut Diag<'_, G>,
         _f: &F,
     ) {
+        use Rust2024IncompatiblePatSuggKind::*;
+
         // Format and emit explanatory notes about default binding modes. Reversing the spans' order
         // means if we have nested spans, the innermost ones will be visited first.
         for (&span, &def_br_mutbl) in self.default_mode_labels.iter().rev() {
@@ -1143,17 +1150,33 @@ impl<'m> Subdiagnostic for Rust2024IncompatiblePatSugg<'m> {
             } else {
                 Applicability::MaybeIncorrect
             };
-        let msg = if self.suggest_eliding_modes {
-            let plural_modes = pluralize!(self.binding_mode_count);
-            format!("remove the unnecessary binding modifier{plural_modes}")
-        } else {
-            let plural_derefs = pluralize!(self.ref_pattern_count);
-            let and_modes = if self.binding_mode_count > 0 {
-                format!(" and variable binding mode{}", pluralize!(self.binding_mode_count))
+        let msg = if let Some(kind) = self.kind {
+            let derefs = if self.ref_pattern_count > 0 {
+                format!("reference pattern{}", pluralize!(self.ref_pattern_count))
             } else {
                 String::new()
             };
-            format!("make the implied reference pattern{plural_derefs}{and_modes} explicit")
+            let modes = if self.binding_mode_count > 0 {
+                match kind {
+                    Subtractive => {
+                        format!("binding modifier{}", pluralize!(self.binding_mode_count))
+                    }
+                    Additive => {
+                        format!("variable binding mode{}", pluralize!(self.binding_mode_count))
+                    }
+                }
+            } else {
+                String::new()
+            };
+            let and = if !derefs.is_empty() && !modes.is_empty() { " and " } else { "" };
+            match kind {
+                Subtractive => format!("remove the unnecessary {derefs}{and}{modes}"),
+                Additive => {
+                    format!("make the implied {derefs}{and}{modes} explicit")
+                }
+            }
+        } else {
+            "rewrite the pattern".to_owned()
         };
         // FIXME(dianne): for peace of mind, don't risk emitting a 0-part suggestion (that panics!)
         if !self.suggestion.is_empty() {
diff --git a/compiler/rustc_mir_build/src/thir/pattern/migration.rs b/compiler/rustc_mir_build/src/thir/pattern/migration.rs
index 16bd9f7b1aa35..f4e8cec173f7c 100644
--- a/compiler/rustc_mir_build/src/thir/pattern/migration.rs
+++ b/compiler/rustc_mir_build/src/thir/pattern/migration.rs
@@ -7,9 +7,12 @@ use rustc_index::IndexVec;
 use rustc_lint as lint;
 use rustc_middle::span_bug;
 use rustc_middle::ty::{self, Rust2024IncompatiblePatInfo, Ty, TyCtxt};
+use rustc_span::source_map::SourceMap;
 use rustc_span::{Ident, Span};
 
-use crate::errors::{Rust2024IncompatiblePat, Rust2024IncompatiblePatSugg};
+use crate::errors::{
+    Rust2024IncompatiblePat, Rust2024IncompatiblePatSugg, Rust2024IncompatiblePatSuggKind,
+};
 use crate::fluent_generated as fluent;
 
 /// For patterns flagged for migration during HIR typeck, this handles constructing and emitting
@@ -48,18 +51,27 @@ struct PatBinding {
     mode: BindingMode,
     /// Whether to include a binding modifier (e.g. `ref` or `mut`) in the suggested pattern.
     suggest: bool,
+    /// The next binding in the innermost enclosing deref's list of bindings.
+    next_sibling: Option<PatBindingIdx>,
 }
 
 struct PatDeref<'a, 'tcx> {
     /// The span of the pattern where this deref occurs (implicitly or explicitly).
     span: Span,
+    /// The mutability of the ref pattern (or for implicit derefs, of the reference type).
+    // FIXME(ref_pattern_eat_one_layer_2024): Under RFC 3627's Rule 5, a `&` pattern can match a
+    // `&mut` type or `ref mut` binding mode. Thus, an omitted `&` could result in a `ref mut`
+    // default binding mode. We may want to track both the pattern and ref type's mutabilities.
+    mutbl: Mutability,
     /// Whether this span is for a potentially-removable explicitly-provided deref, or an implicit
     /// dereference which we can potentially suggest making explicit.
     kind: PatDerefKind<'a, 'tcx>,
     /// Whether to include this as a `&` or `&mut` in the suggested pattern.
     suggest: bool,
-    /// The default binding mode for variables under this deref.
+    /// The default binding mode for variables under this deref in the user's pattern.
     real_default_mode: ByRef,
+    /// The default binding mode for variable under this deref in our suggestion.
+    sugg_default_mode: ByRef,
     /// The span that introduced the current default binding mode, or `None` for the top-level pat.
     default_mode_origin: Option<Span>,
     /// The next deref above this. Since we can't suggest using `&` or `&mut` on a by-ref default
@@ -68,11 +80,23 @@ struct PatDeref<'a, 'tcx> {
     // default binding mode, we'll be able to make more local suggestions. That may make this forest
     // structure unnecessary.
     parent: Option<PatDerefIdx>,
+    /// The head of the linked list of child derefs directly under this. When we suggest a `&`
+    /// pattern, any implicit `&mut` children will go from producing a `ref` default binding mode
+    /// to `ref mut`, so we check recursively in that case to see if any bindings would change.
+    // FIXME(ref_pat_eat_one_layer_2024_structural): Aside from this maybe being unnecessary if we
+    // can make more local suggestions (see the above fixme), RFC 3627's Rule 3 should also obsolete
+    // this (see the comments on `propagate_default_mode_change`).
+    first_child: Option<PatDerefIdx>,
+    /// The next child in their parents' linked list of children.
+    next_sibling: Option<PatDerefIdx>,
+    /// The head of the linked list of bindings directly under this deref. If we suggest this
+    /// deref, we'll also need to suggest binding modifiers for any by-ref bindings.
+    first_binding: Option<PatBindingIdx>,
 }
 
 enum PatDerefKind<'a, 'tcx> {
     /// For dereferences from lowering `&` and `&mut` patterns
-    Explicit,
+    Explicit { inner_span: Span },
     /// For dereferences inserted by match ergonomics
     Implicit { ref_tys: &'a [Ty<'tcx>] },
 }
@@ -111,7 +135,7 @@ impl<'a, 'tcx> PatMigration<'a, 'tcx> {
         for (span, label) in self.info.primary_labels.iter() {
             spans.push_span_label(*span, label.clone());
         }
-        let sugg = self.build_suggestion();
+        let sugg = self.build_suggestion(tcx.sess.source_map());
         // If a relevant span is from at least edition 2024, this is a hard error.
         let is_hard_error = spans.primary_spans().iter().any(|span| span.at_least_rust_2024());
         if is_hard_error {
@@ -160,7 +184,7 @@ impl<'a, 'tcx> PatMigration<'a, 'tcx> {
         }
     }
 
-    fn build_suggestion<'m>(&'m self) -> Rust2024IncompatiblePatSugg<'m> {
+    fn build_suggestion<'m>(&'m self, source_map: &SourceMap) -> Rust2024IncompatiblePatSugg<'m> {
         let mut removed_modifiers = 0;
         let mut added_modifiers = 0;
         let modes = self.bindings.iter().filter_map(|binding| {
@@ -183,12 +207,16 @@ impl<'a, 'tcx> PatMigration<'a, 'tcx> {
             }
         });
 
+        let mut removed_ref_pats = 0;
         let mut added_ref_pats = 0;
         let derefs = self.derefs.iter().filter_map(|deref| match deref.kind {
-            PatDerefKind::Explicit if !deref.suggest => {
+            PatDerefKind::Explicit { inner_span } if !deref.suggest => {
                 // This is a ref pattern in the source but not the suggestion; suggest removing it.
-                // TODO: we don't yet suggest removing reference patterns
-                todo!();
+                removed_ref_pats += 1;
+                // Avoid eating the '(' in `&(...)`
+                let span = source_map.span_until_char(deref.span.with_hi(inner_span.lo()), '(');
+                // But *do* eat the ' ' in `&mut [...]`
+                Some((source_map.span_extend_while_whitespace(span), String::new()))
             }
             PatDerefKind::Implicit { ref_tys } if deref.suggest => {
                 // This is a ref pattern in the suggestion but not the source; suggest adding it.
@@ -201,21 +229,25 @@ impl<'a, 'tcx> PatMigration<'a, 'tcx> {
         });
 
         let suggestion = modes.chain(derefs).collect();
-        let binding_mode_count = if added_modifiers == 0 && added_ref_pats == 0 {
-            removed_modifiers
-        } else {
-            added_modifiers
-        };
+        let (kind, binding_mode_count, ref_pattern_count) =
+            if added_modifiers == 0 && added_ref_pats == 0 {
+                let kind = Rust2024IncompatiblePatSuggKind::Subtractive;
+                (Some(kind), removed_modifiers, removed_ref_pats)
+            } else if removed_modifiers == 0 && removed_ref_pats == 0 {
+                (Some(Rust2024IncompatiblePatSuggKind::Additive), added_modifiers, added_ref_pats)
+            } else {
+                (None, 0, 0)
+            };
         Rust2024IncompatiblePatSugg {
-            suggest_eliding_modes: self.info.suggest_eliding_modes,
             suggestion,
+            kind,
             binding_mode_count,
-            ref_pattern_count: added_ref_pats,
+            ref_pattern_count,
             default_mode_labels: &self.default_mode_labels,
         }
     }
 
-    /// The default binding mode at the current pattern.
+    /// The default binding mode at the current point in the pattern the user wrote.
     fn real_default_mode(&self) -> ByRef {
         if let Some(current_ix) = self.innermost_deref {
             self.derefs[current_ix].real_default_mode
@@ -224,10 +256,21 @@ impl<'a, 'tcx> PatMigration<'a, 'tcx> {
         }
     }
 
+    /// The default binding mode at the current point in the pattern we're suggesting.
+    fn sugg_default_mode(&self) -> ByRef {
+        if let Some(deref_ix) = self.innermost_deref {
+            self.derefs[deref_ix].sugg_default_mode
+        } else {
+            ByRef::No
+        }
+    }
+
     /// Tracks when we're lowering a pattern that implicitly dereferences the scrutinee.
     /// This should only be called when the pattern type adjustments list `ref_tys` is non-empty.
     /// This should be followed by a call to [`PatMigration::leave_ref`] when we leave the pattern.
     pub(super) fn visit_implicit_derefs(&mut self, pat: &hir::Pat<'_>, ref_tys: &'a [Ty<'tcx>]) {
+        // The effective mutability of this (as far as the default binding mode goes) is `ref` if
+        // any of `ref_tys` are shared, and `ref mut` if they're all mutable.
         let mutbl = iter_ref_mutbls(pat.span, ref_tys)
             .min()
             .expect("`ref_tys` should have at least one element");
@@ -240,15 +283,32 @@ impl<'a, 'tcx> PatMigration<'a, 'tcx> {
     // dereferences. If reference patterns can match the default binding mode alone, we may need to
     // check `TypeckResults::skipped_ref_pats` to tell if this pattern corresponds to an implicit
     // dereference we've already visited.
-    pub(super) fn visit_explicit_deref(&mut self, pat_span: Span, mutbl: Mutability) {
+    pub(super) fn visit_explicit_deref(
+        &mut self,
+        pat_span: Span,
+        mutbl: Mutability,
+        subpat: &hir::Pat<'_>,
+    ) {
         // If this eats a by-ref default binding mode, label the binding mode.
         self.add_default_mode_label_if_needed();
-        // Set the default binding mode to by-value.
-        self.push_deref(pat_span, mutbl, PatDerefKind::Explicit);
+        // This sets the default binding mode to by-value in the user's pattern, but we'll try to
+        // suggest removing it.
+        // TODO: if this is inside a macro expansion, we won't be able to remove it.
+        self.push_deref(pat_span, mutbl, PatDerefKind::Explicit { inner_span: subpat.span });
+
+        // If the immediate subpattern is a binding, removing this reference pattern would change
+        // its type. To avoid that, we include it and all its parents in that case.
+        // FIXME(ref_pat_eat_one_layer_2024): This assumes ref pats can't eat the binding mode
+        // alone. Depending on the pattern typing rules in use, we can be more precise here.
+        // TODO: if the binding is by-`ref`, we can keep only the parent derefs and remove the `ref`
+        if matches!(subpat.kind, hir::PatKind::Binding(_, _, _, _)) {
+            self.add_derefs_to_suggestion(self.innermost_deref);
+        }
     }
 
-    /// Adds a deref to our deref-forest, so that we can track the default binding mode.
-    // TODO: this is also for propagating binding mode changes when we suggest adding patterns
+    /// Adds a deref to our deref-forest, so that we can track the default binding mode and
+    /// propagate binding mode changes when we suggest adding patterns.
+    /// See [`PatMigration::propagate_default_mode_change`].
     fn push_deref(&mut self, span: Span, mutbl: Mutability, kind: PatDerefKind<'a, 'tcx>) {
         let parent = self.innermost_deref;
         // Get the new default binding mode in the pattern the user wrote.
@@ -257,7 +317,7 @@ impl<'a, 'tcx> PatMigration<'a, 'tcx> {
                 ByRef::Yes(old_mutbl) => ByRef::Yes(Ord::min(mutbl, old_mutbl)),
                 ByRef::No => ByRef::Yes(mutbl),
             },
-            PatDerefKind::Explicit => ByRef::No,
+            PatDerefKind::Explicit { .. } => ByRef::No,
         };
         // If this keeps the default binding mode the same, it shares a mode origin with its
         // parent. If it changes the default binding mode, its mode origin is itself.
@@ -266,15 +326,28 @@ impl<'a, 'tcx> PatMigration<'a, 'tcx> {
         } else {
             Some(span)
         };
+        // Get the default binding mode in the suggestion, assuming we don't include a reference
+        // pattern for this deref. We may add one later if necessary.
+        let sugg_default_mode = ByRef::Yes(match self.sugg_default_mode() {
+            ByRef::Yes(parent_mutbl) => Ord::min(mutbl, parent_mutbl),
+            ByRef::No => mutbl,
+        });
         let my_ix = self.derefs.push(PatDeref {
             span,
-            suggest: !self.info.suggest_eliding_modes
-                || matches!(kind, PatDerefKind::Explicit { .. }),
+            mutbl,
             kind,
+            suggest: false,
+            sugg_default_mode,
             real_default_mode,
             default_mode_origin,
             parent,
+            next_sibling: parent.and_then(|p| self.derefs[p].first_child),
+            first_child: None,
+            first_binding: None,
         });
+        if let Some(p) = parent {
+            self.derefs[p].first_child = Some(my_ix);
+        }
         self.innermost_deref = Some(my_ix);
     }
 
@@ -301,22 +374,92 @@ impl<'a, 'tcx> PatMigration<'a, 'tcx> {
         if explicit_ba != BindingMode::NONE {
             // If this overrides a by-ref default binding mode, label the binding mode.
             self.add_default_mode_label_if_needed();
-            if self.info.suggest_eliding_modes && matches!(mode.0, ByRef::Yes(_)) {
-                // If our suggestion is to elide redundant modes, this will be one of them.
-                self.bindings.push(PatBinding {
-                    span: pat_span.with_hi(ident.span.lo()),
-                    mode,
-                    suggest: false,
-                });
+        }
+
+        // If `mode` doesn't match the default, we'll need to specify its binding modifiers
+        // explicitly, which in turn necessitates a by-move default binding mode.
+        // TODO: if this is inside a macro expansion, we won't be able to change it.
+        let suggest = mode != BindingMode(self.sugg_default_mode(), Mutability::Not);
+
+        // Track the binding
+        let span = if explicit_ba == BindingMode::NONE {
+            pat_span.shrink_to_lo()
+        } else {
+            pat_span.with_hi(ident.span.lo())
+        };
+        // If we're not already suggesting an explicit binding modifier for this binding, we may
+        // need to later, if adding reference patterns above it changes the default binding mode.
+        // In that case, track it as a child of the innermost dereference above it.
+        let parent_deref = if suggest { None } else { self.innermost_deref };
+        let next_sibling = parent_deref.and_then(|p| self.derefs[p].first_binding);
+        let bind_ix = self.bindings.push(PatBinding { span, mode, suggest, next_sibling });
+        if let Some(p) = parent_deref {
+            self.derefs[p].first_binding = Some(bind_ix);
+        }
+
+        // If there was a mismatch, add `&`s to make sure we're in a by-move default binding mode.
+        // TODO: to rewrite `&ref x` as `x`, we'll need to be able to accept a by-value default
+        // binding mode if we remove the `&` that was eating a reference from `x`'s type.
+        if suggest {
+            self.add_derefs_to_suggestion(self.innermost_deref);
+        }
+    }
+
+    /// Include a deref and all its ancestors in the suggestion. If this would change the mode of
+    /// a binding, we include a binding modifier for it in the suggestion, which may in turn
+    /// require including more explicit dereferences, etc.
+    fn add_derefs_to_suggestion(&mut self, mut opt_ix: Option<PatDerefIdx>) {
+        while let Some(ix) = opt_ix {
+            let deref = &mut self.derefs[ix];
+            if deref.suggest {
+                // If this is already marked as suggested, its ancestors will be too.
+                break;
+            }
+            deref.suggest = true;
+            deref.sugg_default_mode = ByRef::No;
+            opt_ix = deref.parent;
+            let propagate_downstream_ref_mut = deref.mutbl.is_not();
+            self.propagate_default_mode_change(ix, propagate_downstream_ref_mut);
+        }
+    }
+
+    /// If including a `&` or `&mut` pattern in our suggestion would change the binding mode of any
+    /// variables, add any necessary binding modifiers and reference patterns to keep them the same.
+    fn propagate_default_mode_change(&mut self, start_ix: PatDerefIdx, propagate_ref_mut: bool) {
+        // After suggesting a deref, any immediate-child bindings will by default be by-value, so
+        // we'll need to suggest modifiers if they should be by-ref. Likewise, if suggesting a `&`
+        // changes the ref-mutability of a downstream binding under an implicit `&mut`, we'll need
+        // to add a binding modifier and `&mut` patterns.
+        let mut opt_bind_ix = self.derefs[start_ix].first_binding;
+        while let Some(bind_ix) = opt_bind_ix {
+            let binding = &mut self.bindings[bind_ix];
+            opt_bind_ix = binding.next_sibling;
+            // FIXME(ref_pat_eat_one_layer_2024_structural): With RFC 3627's Rule 3, an implicit
+            // `&mut` under a `&` pattern won't set the default binding mode to `ref mut`, so we
+            // won't need to do any mutability checks or ref-mutability propagation. We'd only call
+            // this on `&`/`&mut` patterns we suggest, not their descendants, so we can assume the
+            // default binding mode is by-move and that the deref is already suggested.
+            if binding.mode.0 != self.derefs[start_ix].sugg_default_mode {
+                binding.suggest = true;
+                self.add_derefs_to_suggestion(Some(start_ix));
             }
         }
-        if !self.info.suggest_eliding_modes
-            && explicit_ba.0 == ByRef::No
-            && matches!(mode.0, ByRef::Yes(_))
-        {
-            // If we can't fix the pattern by eliding modifiers, we'll need to make the pattern
-            // fully explicit. i.e. we'll need to suggest reference patterns for this.
-            self.bindings.push(PatBinding { span: pat_span.shrink_to_lo(), mode, suggest: true });
+
+        // If we change an implicit dereference of a shared reference to a `&` pattern, any implicit
+        // derefs of `&mut` references in children (until we hit another implicit `&`) will now
+        // produce a `ref mut` default binding mode instead of `ref`. We'll need to recur in case
+        // any downstream bindings' modes are changed.
+        // FIXME(ref_pat_eat_one_layer_2024_structural): See the above fixme. This can all go.
+        if propagate_ref_mut {
+            let mut opt_child_ix = self.derefs[start_ix].first_child;
+            while let Some(child_ix) = opt_child_ix {
+                let child = &mut self.derefs[child_ix];
+                opt_child_ix = child.next_sibling;
+                if child.mutbl.is_mut() {
+                    child.sugg_default_mode = ByRef::Yes(Mutability::Mut);
+                    self.propagate_default_mode_change(child_ix, true);
+                }
+            }
         }
     }
 }
diff --git a/compiler/rustc_mir_build/src/thir/pattern/mod.rs b/compiler/rustc_mir_build/src/thir/pattern/mod.rs
index e91c3d5dc99db..b797912ea8365 100644
--- a/compiler/rustc_mir_build/src/thir/pattern/mod.rs
+++ b/compiler/rustc_mir_build/src/thir/pattern/mod.rs
@@ -309,7 +309,7 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
             hir::PatKind::Ref(subpattern, mutbl) => {
                 // Track the default binding mode for the Rust 2024 migration suggestion.
                 if let Some(s) = &mut self.rust_2024_migration {
-                    s.visit_explicit_deref(pat.span, mutbl);
+                    s.visit_explicit_deref(pat.span, mutbl, subpattern);
                 }
                 let subpattern = self.lower_pattern(subpattern);
                 if let Some(s) = &mut self.rust_2024_migration {
diff --git a/tests/ui/pattern/rfc-3627-match-ergonomics-2024/experimental/ref-binding-on-inh-ref-errors.classic2024.stderr b/tests/ui/pattern/rfc-3627-match-ergonomics-2024/experimental/ref-binding-on-inh-ref-errors.classic2024.stderr
index 56125be2d6fc8..cdc707013f607 100644
--- a/tests/ui/pattern/rfc-3627-match-ergonomics-2024/experimental/ref-binding-on-inh-ref-errors.classic2024.stderr
+++ b/tests/ui/pattern/rfc-3627-match-ergonomics-2024/experimental/ref-binding-on-inh-ref-errors.classic2024.stderr
@@ -46,10 +46,11 @@ note: matching on a reference type with a non-reference pattern changes the defa
    |
 LL |     let [ref x] = &[0];
    |         ^^^^^^^ this matches on type `&_`
-help: make the implied reference pattern explicit
+help: remove the unnecessary binding modifier
+   |
+LL -     let [ref x] = &[0];
+LL +     let [x] = &[0];
    |
-LL |     let &[ref x] = &[0];
-   |         +
 
 error: binding modifiers may only be written when the default binding mode is `move`
   --> $DIR/ref-binding-on-inh-ref-errors.rs:79:10
@@ -80,10 +81,11 @@ note: matching on a reference type with a non-reference pattern changes the defa
    |
 LL |     let [ref mut x] = &mut [0];
    |         ^^^^^^^^^^^ this matches on type `&mut _`
-help: make the implied reference pattern explicit
+help: remove the unnecessary binding modifier
+   |
+LL -     let [ref mut x] = &mut [0];
+LL +     let [x] = &mut [0];
    |
-LL |     let &mut [ref mut x] = &mut [0];
-   |         ++++
 
 error: aborting due to 6 previous errors
 
diff --git a/tests/ui/pattern/rfc-3627-match-ergonomics-2024/experimental/ref-binding-on-inh-ref-errors.structural2024.stderr b/tests/ui/pattern/rfc-3627-match-ergonomics-2024/experimental/ref-binding-on-inh-ref-errors.structural2024.stderr
index 31930e8c03371..f3963a5737fc6 100644
--- a/tests/ui/pattern/rfc-3627-match-ergonomics-2024/experimental/ref-binding-on-inh-ref-errors.structural2024.stderr
+++ b/tests/ui/pattern/rfc-3627-match-ergonomics-2024/experimental/ref-binding-on-inh-ref-errors.structural2024.stderr
@@ -152,10 +152,11 @@ note: matching on a reference type with a non-reference pattern changes the defa
    |
 LL |     let [ref x] = &[0];
    |         ^^^^^^^ this matches on type `&_`
-help: make the implied reference pattern explicit
+help: remove the unnecessary binding modifier
+   |
+LL -     let [ref x] = &[0];
+LL +     let [x] = &[0];
    |
-LL |     let &[ref x] = &[0];
-   |         +
 
 error: binding modifiers may only be written when the default binding mode is `move`
   --> $DIR/ref-binding-on-inh-ref-errors.rs:79:10
@@ -186,10 +187,11 @@ note: matching on a reference type with a non-reference pattern changes the defa
    |
 LL |     let [ref mut x] = &mut [0];
    |         ^^^^^^^^^^^ this matches on type `&mut _`
-help: make the implied reference pattern explicit
+help: remove the unnecessary binding modifier
+   |
+LL -     let [ref mut x] = &mut [0];
+LL +     let [x] = &mut [0];
    |
-LL |     let &mut [ref mut x] = &mut [0];
-   |         ++++
 
 error: aborting due to 12 previous errors
 
diff --git a/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.fixed b/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.fixed
index 0d25ac32a4f90..9384cdf5ed652 100644
--- a/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.fixed
+++ b/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.fixed
@@ -96,7 +96,7 @@ fn main() {
         assert_type_eq(x, 0u8);
     }
 
-    if let &mut Some(&mut Some(&mut Some(ref mut x))) = &mut Some(&mut Some(&mut Some(0u8))) {
+    if let Some(Some(Some(x))) = &mut Some(&mut Some(&mut Some(0u8))) {
         //~^ ERROR: reference patterns may only be written when the default binding mode is `move` in Rust 2024
         //~| WARN: this changes meaning in Rust 2024
         assert_type_eq(x, &mut 0u8);
@@ -121,7 +121,7 @@ fn main() {
     assert_type_eq(b, &&0u32);
     assert_type_eq(c, &&0u32);
 
-    if let &Struct { a: &Some(a), b: &Some(&b), c: &Some(ref c) } =
+    if let &Struct { a: &Some(a), b: &Some(&b), c: Some(c) } =
         //~^ ERROR: reference patterns may only be written when the default binding mode is `move` in Rust 2024
         //~| WARN: this changes meaning in Rust 2024
         &(Struct { a: &Some(&0), b: &Some(&0), c: &Some(&0) })
@@ -142,12 +142,12 @@ fn main() {
         _ => {}
     }
 
-    let &mut [&mut &[ref a]] = &mut [&mut &[0]];
+    let [[a]] = &mut [&mut &[0]];
     //~^ ERROR: binding modifiers and reference patterns may only be written when the default binding mode is `move` in Rust 2024
     //~| WARN: this changes meaning in Rust 2024
     assert_type_eq(a, &0u32);
 
-    let &[&(_)] = &[&0];
+    let [(_)] = &[&0];
     //~^ ERROR: reference patterns may only be written when the default binding mode is `move` in Rust 2024
     //~| WARN: this changes meaning in Rust 2024
 
@@ -167,7 +167,7 @@ fn main() {
     assert_type_eq(c, &mut 0u32);
 
     // Test removing multiple reference patterns of various mutabilities, plus a binding modifier.
-    let &mut &Struct { a: &[ref a], b: &mut [&[ref b]], ref c } = &mut &Struct { a: &[0], b: &mut [&[0]], c: 0 };
+    let Struct { a: [a], b: [[b]], c } = &mut &Struct { a: &[0], b: &mut [&[0]], c: 0 };
     //~^ ERROR: reference patterns may only be written when the default binding mode is `move` in Rust 2024
     //~| WARN: this changes meaning in Rust 2024
     assert_type_eq(a, &0u32);
@@ -181,7 +181,7 @@ fn main() {
     assert_type_eq(a, &0u32);
 
     // Test that we don't change bindings' modes when adding reference paterns (caught early).
-    let &(&a, ref b, &[ref c], &mut [&mut (ref d, &[ref e])]) = &(&0, 0, &[0], &mut [&mut (0, &[0])]);
+    let &(&a, ref b, [c], &mut [&mut (ref d, [e])]) = &(&0, 0, &[0], &mut [&mut (0, &[0])]);
     //~^ ERROR: reference patterns may only be written when the default binding mode is `move` in Rust 2024
     //~| WARN: this changes meaning in Rust 2024
     assert_type_eq(a, 0u32);
@@ -199,7 +199,7 @@ fn main() {
     assert_type_eq(c, 0u32);
 
     // Test featuring both additions and removals.
-    let &(&a, &mut (ref b, &[ref c])) = &(&0, &mut (0, &[0]));
+    let &(&a, &mut (ref b, [c])) = &(&0, &mut (0, &[0]));
     //~^ ERROR: reference patterns may only be written when the default binding mode is `move` in Rust 2024
     //~| WARN: this changes meaning in Rust 2024
     assert_type_eq(a, 0u32);
@@ -239,7 +239,7 @@ fn main() {
     assert_type_eq(d, 0u32);
 
     // Test that we use the correct message and suggestion style when pointing inside expansions.
-    let &[migration_lint_macros::bind_ref!(a)] = &[0];
+    let [migration_lint_macros::bind_ref!(a)] = &[0];
     //~^ ERROR: binding modifiers may only be written when the default binding mode is `move`
     assert_type_eq(a, &0u32);
 
diff --git a/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.stderr b/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.stderr
index b6bf4237f2b12..2c3c2908c0ca1 100644
--- a/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.stderr
+++ b/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.stderr
@@ -215,10 +215,11 @@ note: matching on a reference type with a non-reference pattern changes the defa
    |
 LL |     if let Some(&mut Some(Some(x))) = &mut Some(&mut Some(&mut Some(0u8))) {
    |            ^^^^^^^^^^^^^^^^^^^^^^^^ this matches on type `&mut _`
-help: make the implied reference patterns and variable binding mode explicit
+help: remove the unnecessary reference pattern
+   |
+LL -     if let Some(&mut Some(Some(x))) = &mut Some(&mut Some(&mut Some(0u8))) {
+LL +     if let Some(Some(Some(x))) = &mut Some(&mut Some(&mut Some(0u8))) {
    |
-LL |     if let &mut Some(&mut Some(&mut Some(ref mut x))) = &mut Some(&mut Some(&mut Some(0u8))) {
-   |            ++++                ++++      +++++++
 
 error: binding modifiers may only be written when the default binding mode is `move` in Rust 2024
   --> $DIR/migration_lint.rs:111:21
@@ -273,10 +274,10 @@ note: matching on a reference type with a non-reference pattern changes the defa
    |
 LL |     if let Struct { a: &Some(a), b: Some(&b), c: Some(c) } =
    |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this matches on type `&_`
-help: make the implied reference patterns and variable binding mode explicit
+help: make the implied reference patterns explicit
    |
-LL |     if let &Struct { a: &Some(a), b: &Some(&b), c: &Some(ref c) } =
-   |            +                         +             +     +++
+LL |     if let &Struct { a: &Some(a), b: &Some(&b), c: Some(c) } =
+   |            +                         +
 
 error: binding modifiers may only be written when the default binding mode is `move`
   --> $DIR/migration_lint.rs:137:15
@@ -318,10 +319,11 @@ note: matching on a reference type with a non-reference pattern changes the defa
    |
 LL |     let [&mut [ref a]] = &mut [&mut &[0]];
    |         ^^^^^^^^^^^^^^ this matches on type `&mut _`
-help: make the implied reference patterns explicit
+help: remove the unnecessary reference pattern and binding modifier
+   |
+LL -     let [&mut [ref a]] = &mut [&mut &[0]];
+LL +     let [[a]] = &mut [&mut &[0]];
    |
-LL |     let &mut [&mut &[ref a]] = &mut [&mut &[0]];
-   |         ++++       +
 
 error: reference patterns may only be written when the default binding mode is `move` in Rust 2024
   --> $DIR/migration_lint.rs:150:10
@@ -336,10 +338,11 @@ note: matching on a reference type with a non-reference pattern changes the defa
    |
 LL |     let [&(_)] = &[&0];
    |         ^^^^^^ this matches on type `&_`
-help: make the implied reference pattern explicit
+help: remove the unnecessary reference pattern
+   |
+LL -     let [&(_)] = &[&0];
+LL +     let [(_)] = &[&0];
    |
-LL |     let &[&(_)] = &[&0];
-   |         +
 
 error: binding modifiers may only be written when the default binding mode is `move` in Rust 2024
   --> $DIR/migration_lint.rs:155:18
@@ -397,10 +400,11 @@ note: matching on a reference type with a non-reference pattern changes the defa
    |
 LL |     let Struct { a: &[ref a], b: &mut [[b]], c } = &mut &Struct { a: &[0], b: &mut [&[0]], c: 0 };
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this matches on type `&_`
-help: make the implied reference patterns and variable binding modes explicit
+help: remove the unnecessary reference patterns and binding modifier
+   |
+LL -     let Struct { a: &[ref a], b: &mut [[b]], c } = &mut &Struct { a: &[0], b: &mut [&[0]], c: 0 };
+LL +     let Struct { a: [a], b: [[b]], c } = &mut &Struct { a: &[0], b: &mut [&[0]], c: 0 };
    |
-LL |     let &mut &Struct { a: &[ref a], b: &mut [&[ref b]], ref c } = &mut &Struct { a: &[0], b: &mut [&[0]], c: 0 };
-   |         ++++++                               + +++      +++
 
 error: reference patterns may only be written when the default binding mode is `move` in Rust 2024
   --> $DIR/migration_lint.rs:178:13
@@ -435,8 +439,8 @@ LL |     let (&a, b, [c], [(d, [e])]) = &(&0, 0, &[0], &mut [&mut (0, &[0])]);
    |         ^^^^^^^^^^^^^^^^^^^^^^^^ this matches on type `&_`
 help: make the implied reference patterns and variable binding modes explicit
    |
-LL |     let &(&a, ref b, &[ref c], &mut [&mut (ref d, &[ref e])]) = &(&0, 0, &[0], &mut [&mut (0, &[0])]);
-   |         +     +++    + +++     ++++  ++++  +++    + +++
+LL |     let &(&a, ref b, [c], &mut [&mut (ref d, [e])]) = &(&0, 0, &[0], &mut [&mut (0, &[0])]);
+   |         +     +++         ++++  ++++  +++
 
 error: binding modifiers may only be written when the default binding mode is `move` in Rust 2024
   --> $DIR/migration_lint.rs:194:19
@@ -471,10 +475,11 @@ note: matching on a reference type with a non-reference pattern changes the defa
    |
 LL |     let (&a, (b, &[ref c])) = &(&0, &mut (0, &[0]));
    |         ^^^^^^^^^^^^^^^^^^^ this matches on type `&_`
-help: make the implied reference patterns and variable binding mode explicit
+help: rewrite the pattern
+   |
+LL -     let (&a, (b, &[ref c])) = &(&0, &mut (0, &[0]));
+LL +     let &(&a, &mut (ref b, [c])) = &(&0, &mut (0, &[0]));
    |
-LL |     let &(&a, &mut (ref b, &[ref c])) = &(&0, &mut (0, &[0]));
-   |         +     ++++  +++
 
 error: binding modifiers may only be written when the default binding mode is `move` in Rust 2024
   --> $DIR/migration_lint.rs:210:10
@@ -563,22 +568,18 @@ LL |     let [&Foo(&ref a @ [ref b]), &Foo(&ref c @ [d])] = [&Foo(&[0]); 2];
    |          +                       +
 
 error: binding modifiers may only be written when the default binding mode is `move`
-  --> $DIR/migration_lint.rs:244:10
+  --> $DIR/migration_lint.rs:242:10
    |
 LL |     let [migration_lint_macros::bind_ref!(a)] = &[0];
    |          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ occurs within macro expansion
    |
    = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/match-ergonomics.html>
 note: matching on a reference type with a non-reference pattern changes the default binding mode
-  --> $DIR/migration_lint.rs:244:9
+  --> $DIR/migration_lint.rs:242:9
    |
 LL |     let [migration_lint_macros::bind_ref!(a)] = &[0];
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this matches on type `&_`
    = note: this error originates in the macro `migration_lint_macros::bind_ref` (in Nightly builds, run with -Z macro-backtrace for more info)
-help: make the implied reference pattern explicit
-   |
-LL |     let &[migration_lint_macros::bind_ref!(a)] = &[0];
-   |         +
 
 error: reference patterns may only be written when the default binding mode is `move` in Rust 2024
   --> $DIR/migration_lint.rs:249:10

From 998cc7269831dd1654b775cf53d1128c25ac699b Mon Sep 17 00:00:00 2001
From: dianne <diannes.gm@gmail.com>
Date: Tue, 18 Feb 2025 22:54:25 -0800
Subject: [PATCH 08/10] don't suggest removals inside macro expansions

This fixes the regression in the previous commit.
---
 .../rustc_mir_build/src/thir/pattern/migration.rs  | 14 +++++++++++---
 .../migration_lint.fixed                           |  2 +-
 .../migration_lint.stderr                          |  4 ++++
 3 files changed, 16 insertions(+), 4 deletions(-)

diff --git a/compiler/rustc_mir_build/src/thir/pattern/migration.rs b/compiler/rustc_mir_build/src/thir/pattern/migration.rs
index f4e8cec173f7c..0ad7dbc3b468a 100644
--- a/compiler/rustc_mir_build/src/thir/pattern/migration.rs
+++ b/compiler/rustc_mir_build/src/thir/pattern/migration.rs
@@ -293,9 +293,14 @@ impl<'a, 'tcx> PatMigration<'a, 'tcx> {
         self.add_default_mode_label_if_needed();
         // This sets the default binding mode to by-value in the user's pattern, but we'll try to
         // suggest removing it.
-        // TODO: if this is inside a macro expansion, we won't be able to remove it.
         self.push_deref(pat_span, mutbl, PatDerefKind::Explicit { inner_span: subpat.span });
 
+        // If this is inside a macro expansion, we won't be able to remove it.
+        if pat_span.from_expansion() {
+            self.add_derefs_to_suggestion(self.innermost_deref);
+            return;
+        }
+
         // If the immediate subpattern is a binding, removing this reference pattern would change
         // its type. To avoid that, we include it and all its parents in that case.
         // FIXME(ref_pat_eat_one_layer_2024): This assumes ref pats can't eat the binding mode
@@ -378,8 +383,11 @@ impl<'a, 'tcx> PatMigration<'a, 'tcx> {
 
         // If `mode` doesn't match the default, we'll need to specify its binding modifiers
         // explicitly, which in turn necessitates a by-move default binding mode.
-        // TODO: if this is inside a macro expansion, we won't be able to change it.
-        let suggest = mode != BindingMode(self.sugg_default_mode(), Mutability::Not);
+        // Additionally, if this is inside a macro expansion, we won't be able to change it. If a
+        // binding modifier is missing inside the expansion, there's not much we can do, but we can
+        // avoid suggestions to elide binding modifiers that are explicit within expansions.
+        let suggest = mode != BindingMode(self.sugg_default_mode(), Mutability::Not)
+            || pat_span.from_expansion() && explicit_ba != BindingMode::NONE;
 
         // Track the binding
         let span = if explicit_ba == BindingMode::NONE {
diff --git a/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.fixed b/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.fixed
index 9384cdf5ed652..ec1e1c6452a95 100644
--- a/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.fixed
+++ b/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.fixed
@@ -239,7 +239,7 @@ fn main() {
     assert_type_eq(d, 0u32);
 
     // Test that we use the correct message and suggestion style when pointing inside expansions.
-    let [migration_lint_macros::bind_ref!(a)] = &[0];
+    let &[migration_lint_macros::bind_ref!(a)] = &[0];
     //~^ ERROR: binding modifiers may only be written when the default binding mode is `move`
     assert_type_eq(a, &0u32);
 
diff --git a/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.stderr b/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.stderr
index 2c3c2908c0ca1..67eb2ce216add 100644
--- a/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.stderr
+++ b/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.stderr
@@ -580,6 +580,10 @@ note: matching on a reference type with a non-reference pattern changes the defa
 LL |     let [migration_lint_macros::bind_ref!(a)] = &[0];
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this matches on type `&_`
    = note: this error originates in the macro `migration_lint_macros::bind_ref` (in Nightly builds, run with -Z macro-backtrace for more info)
+help: make the implied reference pattern explicit
+   |
+LL |     let &[migration_lint_macros::bind_ref!(a)] = &[0];
+   |         +
 
 error: reference patterns may only be written when the default binding mode is `move` in Rust 2024
   --> $DIR/migration_lint.rs:249:10

From f65fde616b9cce240d353a2ccb5f9f0e8e5d96e5 Mon Sep 17 00:00:00 2001
From: dianne <diannes.gm@gmail.com>
Date: Tue, 18 Feb 2025 23:32:02 -0800
Subject: [PATCH 09/10] more tests for avoiding changes inside expansions

---
 .../auxiliary/migration_lint_macros.rs        |  7 ++++
 .../migration_lint.fixed                      | 10 +++++
 .../migration_lint.rs                         | 10 +++++
 .../migration_lint.stderr                     | 42 +++++++++++++++++--
 4 files changed, 66 insertions(+), 3 deletions(-)

diff --git a/tests/ui/pattern/rfc-3627-match-ergonomics-2024/auxiliary/migration_lint_macros.rs b/tests/ui/pattern/rfc-3627-match-ergonomics-2024/auxiliary/migration_lint_macros.rs
index b18f87fd56995..fcde98812270d 100644
--- a/tests/ui/pattern/rfc-3627-match-ergonomics-2024/auxiliary/migration_lint_macros.rs
+++ b/tests/ui/pattern/rfc-3627-match-ergonomics-2024/auxiliary/migration_lint_macros.rs
@@ -16,3 +16,10 @@ macro_rules! bind_ref {
         ref $foo
     };
 }
+
+#[macro_export]
+macro_rules! match_ref {
+    ($p:pat) => {
+        &$p
+    };
+}
diff --git a/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.fixed b/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.fixed
index ec1e1c6452a95..66717fcfe0bca 100644
--- a/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.fixed
+++ b/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.fixed
@@ -243,9 +243,19 @@ fn main() {
     //~^ ERROR: binding modifiers may only be written when the default binding mode is `move`
     assert_type_eq(a, &0u32);
 
+    let &[migration_lint_macros::match_ref!(a)] = &[&0];
+    //~^ ERROR: reference patterns may only be written when the default binding mode is `move`
+    assert_type_eq(a, 0u32);
+
     // Test that we use the correct span when labeling a `&` whose subpattern is from an expansion.
+    // Also test that we don't simplify `&ref x` to `x` when the `ref` is from an expansion.
     let &[&migration_lint_macros::bind_ref!(a)] = &[&0];
     //~^ ERROR: reference patterns may only be written when the default binding mode is `move` in Rust 2024
     //~| WARN: this changes meaning in Rust 2024
     assert_type_eq(a, &0u32);
+
+    // Test that we don't simplify `&ref x` to `x` when the `&` is from an expansion.
+    let &[migration_lint_macros::match_ref!(ref a)] = &[&0];
+    //~^ ERROR: reference patterns may only be written when the default binding mode is `move`
+    assert_type_eq(a, &0u32);
 }
diff --git a/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.rs b/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.rs
index de9daf82860f0..ee3c6f8a49955 100644
--- a/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.rs
+++ b/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.rs
@@ -243,9 +243,19 @@ fn main() {
     //~^ ERROR: binding modifiers may only be written when the default binding mode is `move`
     assert_type_eq(a, &0u32);
 
+    let [migration_lint_macros::match_ref!(a)] = &[&0];
+    //~^ ERROR: reference patterns may only be written when the default binding mode is `move`
+    assert_type_eq(a, 0u32);
+
     // Test that we use the correct span when labeling a `&` whose subpattern is from an expansion.
+    // Also test that we don't simplify `&ref x` to `x` when the `ref` is from an expansion.
     let [&migration_lint_macros::bind_ref!(a)] = &[&0];
     //~^ ERROR: reference patterns may only be written when the default binding mode is `move` in Rust 2024
     //~| WARN: this changes meaning in Rust 2024
     assert_type_eq(a, &0u32);
+
+    // Test that we don't simplify `&ref x` to `x` when the `&` is from an expansion.
+    let [migration_lint_macros::match_ref!(ref a)] = &[&0];
+    //~^ ERROR: reference patterns may only be written when the default binding mode is `move`
+    assert_type_eq(a, &0u32);
 }
diff --git a/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.stderr b/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.stderr
index 67eb2ce216add..27d7308e2e0d7 100644
--- a/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.stderr
+++ b/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.stderr
@@ -585,8 +585,26 @@ help: make the implied reference pattern explicit
 LL |     let &[migration_lint_macros::bind_ref!(a)] = &[0];
    |         +
 
+error: reference patterns may only be written when the default binding mode is `move`
+  --> $DIR/migration_lint.rs:246:10
+   |
+LL |     let [migration_lint_macros::match_ref!(a)] = &[&0];
+   |          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ occurs within macro expansion
+   |
+   = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/match-ergonomics.html>
+note: matching on a reference type with a non-reference pattern changes the default binding mode
+  --> $DIR/migration_lint.rs:246:9
+   |
+LL |     let [migration_lint_macros::match_ref!(a)] = &[&0];
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this matches on type `&_`
+   = note: this error originates in the macro `migration_lint_macros::match_ref` (in Nightly builds, run with -Z macro-backtrace for more info)
+help: make the implied reference pattern explicit
+   |
+LL |     let &[migration_lint_macros::match_ref!(a)] = &[&0];
+   |         +
+
 error: reference patterns may only be written when the default binding mode is `move` in Rust 2024
-  --> $DIR/migration_lint.rs:249:10
+  --> $DIR/migration_lint.rs:252:10
    |
 LL |     let [&migration_lint_macros::bind_ref!(a)] = &[&0];
    |          ^ reference pattern not allowed under `ref` default binding mode
@@ -594,7 +612,7 @@ LL |     let [&migration_lint_macros::bind_ref!(a)] = &[&0];
    = warning: this changes meaning in Rust 2024
    = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/match-ergonomics.html>
 note: matching on a reference type with a non-reference pattern changes the default binding mode
-  --> $DIR/migration_lint.rs:249:9
+  --> $DIR/migration_lint.rs:252:9
    |
 LL |     let [&migration_lint_macros::bind_ref!(a)] = &[&0];
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this matches on type `&_`
@@ -603,5 +621,23 @@ help: make the implied reference pattern explicit
 LL |     let &[&migration_lint_macros::bind_ref!(a)] = &[&0];
    |         +
 
-error: aborting due to 31 previous errors
+error: reference patterns may only be written when the default binding mode is `move`
+  --> $DIR/migration_lint.rs:258:10
+   |
+LL |     let [migration_lint_macros::match_ref!(ref a)] = &[&0];
+   |          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ occurs within macro expansion
+   |
+   = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/match-ergonomics.html>
+note: matching on a reference type with a non-reference pattern changes the default binding mode
+  --> $DIR/migration_lint.rs:258:9
+   |
+LL |     let [migration_lint_macros::match_ref!(ref a)] = &[&0];
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this matches on type `&_`
+   = note: this error originates in the macro `migration_lint_macros::match_ref` (in Nightly builds, run with -Z macro-backtrace for more info)
+help: make the implied reference pattern explicit
+   |
+LL |     let &[migration_lint_macros::match_ref!(ref a)] = &[&0];
+   |         +
+
+error: aborting due to 33 previous errors
 

From b7ff9e8cc602ab25963e4eed6b8c33a43a16f2ef Mon Sep 17 00:00:00 2001
From: dianne <diannes.gm@gmail.com>
Date: Mon, 10 Feb 2025 03:49:56 -0800
Subject: [PATCH 10/10] simplify `&ref x` to `x`

---
 .../src/thir/pattern/migration.rs             | 60 ++++++++++++++-----
 .../rustc_mir_build/src/thir/pattern/mod.rs   |  4 +-
 ...ng-on-inh-ref-errors.structural2024.stderr | 35 ++++++-----
 .../migration_lint.fixed                      |  6 +-
 .../migration_lint.stderr                     | 21 ++++---
 5 files changed, 81 insertions(+), 45 deletions(-)

diff --git a/compiler/rustc_mir_build/src/thir/pattern/migration.rs b/compiler/rustc_mir_build/src/thir/pattern/migration.rs
index 0ad7dbc3b468a..12b6afabadfae 100644
--- a/compiler/rustc_mir_build/src/thir/pattern/migration.rs
+++ b/compiler/rustc_mir_build/src/thir/pattern/migration.rs
@@ -74,6 +74,9 @@ struct PatDeref<'a, 'tcx> {
     sugg_default_mode: ByRef,
     /// The span that introduced the current default binding mode, or `None` for the top-level pat.
     default_mode_origin: Option<Span>,
+    /// Whether this is an instance of `&ref x` which we may be able to simplify to `x`.
+    /// Stores the HIR id of the binding pattern `ref x`, to identify it later.
+    simplify_deref_ref: Option<HirId>,
     /// The next deref above this. Since we can't suggest using `&` or `&mut` on a by-ref default
     /// binding mode, a suggested deref's ancestors must also all be suggested.
     // FIXME(ref_pat_eat_one_layer_2024): By suggesting `&` and `&mut` patterns that can eat the
@@ -293,7 +296,8 @@ impl<'a, 'tcx> PatMigration<'a, 'tcx> {
         self.add_default_mode_label_if_needed();
         // This sets the default binding mode to by-value in the user's pattern, but we'll try to
         // suggest removing it.
-        self.push_deref(pat_span, mutbl, PatDerefKind::Explicit { inner_span: subpat.span });
+        let my_ix =
+            self.push_deref(pat_span, mutbl, PatDerefKind::Explicit { inner_span: subpat.span });
 
         // If this is inside a macro expansion, we won't be able to remove it.
         if pat_span.from_expansion() {
@@ -301,20 +305,36 @@ impl<'a, 'tcx> PatMigration<'a, 'tcx> {
             return;
         }
 
-        // If the immediate subpattern is a binding, removing this reference pattern would change
-        // its type. To avoid that, we include it and all its parents in that case.
+        // If the subpattern is a binding, removing this reference pattern would change its type.
         // FIXME(ref_pat_eat_one_layer_2024): This assumes ref pats can't eat the binding mode
         // alone. Depending on the pattern typing rules in use, we can be more precise here.
-        // TODO: if the binding is by-`ref`, we can keep only the parent derefs and remove the `ref`
-        if matches!(subpat.kind, hir::PatKind::Binding(_, _, _, _)) {
-            self.add_derefs_to_suggestion(self.innermost_deref);
+        if let hir::PatKind::Binding(explicit_ba, _, _, _) = subpat.kind {
+            if explicit_ba == BindingMode(ByRef::Yes(mutbl), Mutability::Not) {
+                // If the binding has a `ref` modifier, we can elide both this `&` and the `ref`;
+                // i.e. we can simplify `&ref x` to `x`, as long as all parent derefs are explicit.
+                // NB: We don't rewrite `&ref x @ ...` to `x @ &...`, so we may end up needing to
+                // reinstate this `&` later if the binding's subpattern requires it.
+                // FIXME(ref_pat_eat_one_layer_2024): With RFC 3627's Rule 5, `&` patterns can match
+                // `&mut` types; we'll have to check the mutability of the type rather than the
+                // pattern to see whether we can elide it.
+                self.derefs[my_ix].simplify_deref_ref = Some(subpat.hir_id);
+                self.add_derefs_to_suggestion(self.derefs[my_ix].parent);
+            } else {
+                // Otherwise, we need to suggest including this `&` as well.
+                self.add_derefs_to_suggestion(self.innermost_deref);
+            }
         }
     }
 
     /// Adds a deref to our deref-forest, so that we can track the default binding mode and
     /// propagate binding mode changes when we suggest adding patterns.
     /// See [`PatMigration::propagate_default_mode_change`].
-    fn push_deref(&mut self, span: Span, mutbl: Mutability, kind: PatDerefKind<'a, 'tcx>) {
+    fn push_deref(
+        &mut self,
+        span: Span,
+        mutbl: Mutability,
+        kind: PatDerefKind<'a, 'tcx>,
+    ) -> PatDerefIdx {
         let parent = self.innermost_deref;
         // Get the new default binding mode in the pattern the user wrote.
         let real_default_mode = match kind {
@@ -345,6 +365,7 @@ impl<'a, 'tcx> PatMigration<'a, 'tcx> {
             sugg_default_mode,
             real_default_mode,
             default_mode_origin,
+            simplify_deref_ref: None,
             parent,
             next_sibling: parent.and_then(|p| self.derefs[p].first_child),
             first_child: None,
@@ -354,6 +375,7 @@ impl<'a, 'tcx> PatMigration<'a, 'tcx> {
             self.derefs[p].first_child = Some(my_ix);
         }
         self.innermost_deref = Some(my_ix);
+        my_ix
     }
 
     /// Restores the default binding mode after lowering a pattern that could change it.
@@ -371,7 +393,7 @@ impl<'a, 'tcx> PatMigration<'a, 'tcx> {
     /// Rust 2024) or if we need to suggest a binding modifier for them.
     pub(super) fn visit_binding(
         &mut self,
-        pat_span: Span,
+        pat: &hir::Pat<'_>,
         mode: BindingMode,
         explicit_ba: BindingMode,
         ident: Ident,
@@ -381,19 +403,26 @@ impl<'a, 'tcx> PatMigration<'a, 'tcx> {
             self.add_default_mode_label_if_needed();
         }
 
-        // If `mode` doesn't match the default, we'll need to specify its binding modifiers
-        // explicitly, which in turn necessitates a by-move default binding mode.
+        // As a special case, we may simplify `&ref x` to `x`; check our parent to see if we can.
+        // The default binding mode will always be by-move in this case.
+        let simplify_deref_ref = self.innermost_deref.is_some_and(|p| {
+            self.derefs[p].simplify_deref_ref.is_some_and(|binding_id| pat.hir_id == binding_id)
+        });
+
+        // Otherwise, if `mode` doesn't match the default, we'll need to specify its binding
+        // modifiers explicitly, which in turn necessitates a by-move default binding mode.
         // Additionally, if this is inside a macro expansion, we won't be able to change it. If a
         // binding modifier is missing inside the expansion, there's not much we can do, but we can
         // avoid suggestions to elide binding modifiers that are explicit within expansions.
-        let suggest = mode != BindingMode(self.sugg_default_mode(), Mutability::Not)
-            || pat_span.from_expansion() && explicit_ba != BindingMode::NONE;
+        let suggest = !simplify_deref_ref
+            && mode != BindingMode(self.sugg_default_mode(), Mutability::Not)
+            || pat.span.from_expansion() && explicit_ba != BindingMode::NONE;
 
         // Track the binding
         let span = if explicit_ba == BindingMode::NONE {
-            pat_span.shrink_to_lo()
+            pat.span.shrink_to_lo()
         } else {
-            pat_span.with_hi(ident.span.lo())
+            pat.span.with_hi(ident.span.lo())
         };
         // If we're not already suggesting an explicit binding modifier for this binding, we may
         // need to later, if adding reference patterns above it changes the default binding mode.
@@ -406,8 +435,6 @@ impl<'a, 'tcx> PatMigration<'a, 'tcx> {
         }
 
         // If there was a mismatch, add `&`s to make sure we're in a by-move default binding mode.
-        // TODO: to rewrite `&ref x` as `x`, we'll need to be able to accept a by-value default
-        // binding mode if we remove the `&` that was eating a reference from `x`'s type.
         if suggest {
             self.add_derefs_to_suggestion(self.innermost_deref);
         }
@@ -425,6 +452,7 @@ impl<'a, 'tcx> PatMigration<'a, 'tcx> {
             }
             deref.suggest = true;
             deref.sugg_default_mode = ByRef::No;
+            deref.simplify_deref_ref = None;
             opt_ix = deref.parent;
             let propagate_downstream_ref_mut = deref.mutbl.is_not();
             self.propagate_default_mode_change(ix, propagate_downstream_ref_mut);
diff --git a/compiler/rustc_mir_build/src/thir/pattern/mod.rs b/compiler/rustc_mir_build/src/thir/pattern/mod.rs
index b797912ea8365..c3e0d76dc9479 100644
--- a/compiler/rustc_mir_build/src/thir/pattern/mod.rs
+++ b/compiler/rustc_mir_build/src/thir/pattern/mod.rs
@@ -344,8 +344,8 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
                     .get(pat.hir_id)
                     .expect("missing binding mode");
 
-                if let Some(s) = &mut self.rust_2024_migration {
-                    s.visit_binding(pat.span, mode, explicit_ba, ident);
+                if let Some(m) = &mut self.rust_2024_migration {
+                    m.visit_binding(pat, mode, explicit_ba, ident);
                 }
 
                 // A ref x pattern is the same node used for x, and as such it has
diff --git a/tests/ui/pattern/rfc-3627-match-ergonomics-2024/experimental/ref-binding-on-inh-ref-errors.structural2024.stderr b/tests/ui/pattern/rfc-3627-match-ergonomics-2024/experimental/ref-binding-on-inh-ref-errors.structural2024.stderr
index f3963a5737fc6..9070f085066f8 100644
--- a/tests/ui/pattern/rfc-3627-match-ergonomics-2024/experimental/ref-binding-on-inh-ref-errors.structural2024.stderr
+++ b/tests/ui/pattern/rfc-3627-match-ergonomics-2024/experimental/ref-binding-on-inh-ref-errors.structural2024.stderr
@@ -10,10 +10,11 @@ note: matching on a reference type with a non-reference pattern changes the defa
    |
 LL |     let [&ref x] = &[&0];
    |         ^^^^^^^^ this matches on type `&_`
-help: make the implied reference pattern explicit
+help: rewrite the pattern
+   |
+LL -     let [&ref x] = &[&0];
+LL +     let &[x] = &[&0];
    |
-LL |     let &[&ref x] = &[&0];
-   |         +
 
 error: binding modifiers may only be written when the default binding mode is `move`
   --> $DIR/ref-binding-on-inh-ref-errors.rs:20:11
@@ -27,10 +28,11 @@ note: matching on a reference type with a non-reference pattern changes the defa
    |
 LL |     let [&ref x] = &mut [&0];
    |         ^^^^^^^^ this matches on type `&mut _`
-help: make the implied reference pattern explicit
+help: rewrite the pattern
+   |
+LL -     let [&ref x] = &mut [&0];
+LL +     let &mut [x] = &mut [&0];
    |
-LL |     let &mut [&ref x] = &mut [&0];
-   |         ++++
 
 error: binding modifiers may only be written when the default binding mode is `move`
   --> $DIR/ref-binding-on-inh-ref-errors.rs:25:15
@@ -61,10 +63,11 @@ note: matching on a reference type with a non-reference pattern changes the defa
    |
 LL |     let [&mut ref mut x] = &mut [&mut 0];
    |         ^^^^^^^^^^^^^^^^ this matches on type `&mut _`
-help: make the implied reference pattern explicit
+help: rewrite the pattern
+   |
+LL -     let [&mut ref mut x] = &mut [&mut 0];
+LL +     let &mut [x] = &mut [&mut 0];
    |
-LL |     let &mut [&mut ref mut x] = &mut [&mut 0];
-   |         ++++
 
 error: binding modifiers may only be written when the default binding mode is `move`
   --> $DIR/ref-binding-on-inh-ref-errors.rs:39:11
@@ -78,10 +81,11 @@ note: matching on a reference type with a non-reference pattern changes the defa
    |
 LL |     let [&ref x] = &[&mut 0];
    |         ^^^^^^^^ this matches on type `&_`
-help: make the implied reference pattern explicit
+help: rewrite the pattern
+   |
+LL -     let [&ref x] = &[&mut 0];
+LL +     let &[x] = &[&mut 0];
    |
-LL |     let &[&ref x] = &[&mut 0];
-   |         +
 
 error: binding modifiers may only be written when the default binding mode is `move`
   --> $DIR/ref-binding-on-inh-ref-errors.rs:45:11
@@ -95,10 +99,11 @@ note: matching on a reference type with a non-reference pattern changes the defa
    |
 LL |     let [&ref x] = &mut [&mut 0];
    |         ^^^^^^^^ this matches on type `&mut _`
-help: make the implied reference pattern explicit
+help: rewrite the pattern
+   |
+LL -     let [&ref x] = &mut [&mut 0];
+LL +     let &mut [x] = &mut [&mut 0];
    |
-LL |     let &mut [&ref x] = &mut [&mut 0];
-   |         ++++
 
 error: binding modifiers may only be written when the default binding mode is `move`
   --> $DIR/ref-binding-on-inh-ref-errors.rs:54:15
diff --git a/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.fixed b/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.fixed
index 66717fcfe0bca..34eb6500ec279 100644
--- a/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.fixed
+++ b/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.fixed
@@ -175,7 +175,7 @@ fn main() {
     assert_type_eq(c, &0u32);
 
     // Test that we don't change bindings' types when removing reference patterns.
-    let &Foo(&ref a) = &Foo(&0);
+    let &Foo(a) = &Foo(&0);
     //~^ ERROR: reference patterns may only be written when the default binding mode is `move` in Rust 2024
     //~| WARN: this changes meaning in Rust 2024
     assert_type_eq(a, &0u32);
@@ -221,7 +221,7 @@ fn main() {
     assert_type_eq(b, 0u32);
 
     // Test that we respect bindings' subpatterns' types when rewriting `&ref x` to `x`.
-    let [&Foo(&ref a @ ref b), &Foo(&ref c @ d)] = [&Foo(&0); 2];
+    let [&Foo(a @ b), &Foo(&ref c @ d)] = [&Foo(&0); 2];
     //~^ ERROR: reference patterns may only be written when the default binding mode is `move` in Rust 2024
     //~| WARN: this changes meaning in Rust 2024
     assert_type_eq(a, &0u32);
@@ -230,7 +230,7 @@ fn main() {
     assert_type_eq(d, 0u32);
 
     // Test that we respect bindings' subpatterns' modes when rewriting `&ref x` to `x`.
-    let [&Foo(&ref a @ [ref b]), &Foo(&ref c @ [d])] = [&Foo(&[0]); 2];
+    let [&Foo(a @ [b]), &Foo(&ref c @ [d])] = [&Foo(&[0]); 2];
     //~^ ERROR: reference patterns may only be written when the default binding mode is `move` in Rust 2024
     //~| WARN: this changes meaning in Rust 2024
     assert_type_eq(a, &[0u32]);
diff --git a/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.stderr b/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.stderr
index 27d7308e2e0d7..e157b2d22b7d3 100644
--- a/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.stderr
+++ b/tests/ui/pattern/rfc-3627-match-ergonomics-2024/migration_lint.stderr
@@ -419,10 +419,11 @@ note: matching on a reference type with a non-reference pattern changes the defa
    |
 LL |     let Foo(&ref a) = &Foo(&0);
    |         ^^^^^^^^^^^ this matches on type `&_`
-help: make the implied reference pattern explicit
+help: rewrite the pattern
+   |
+LL -     let Foo(&ref a) = &Foo(&0);
+LL +     let &Foo(a) = &Foo(&0);
    |
-LL |     let &Foo(&ref a) = &Foo(&0);
-   |         +
 
 error: reference patterns may only be written when the default binding mode is `move` in Rust 2024
   --> $DIR/migration_lint.rs:184:10
@@ -537,10 +538,11 @@ note: matching on a reference type with a non-reference pattern changes the defa
    |
 LL |     let [Foo(&ref a @ ref b), Foo(&ref c @ d)] = [&Foo(&0); 2];
    |          ^^^^^^^^^^^^^^^^^^^ this matches on type `&_`
-help: make the implied reference patterns explicit
+help: rewrite the pattern
+   |
+LL -     let [Foo(&ref a @ ref b), Foo(&ref c @ d)] = [&Foo(&0); 2];
+LL +     let [&Foo(a @ b), &Foo(&ref c @ d)] = [&Foo(&0); 2];
    |
-LL |     let [&Foo(&ref a @ ref b), &Foo(&ref c @ d)] = [&Foo(&0); 2];
-   |          +                     +
 
 error: reference patterns may only be written when the default binding mode is `move` in Rust 2024
   --> $DIR/migration_lint.rs:233:14
@@ -562,10 +564,11 @@ note: matching on a reference type with a non-reference pattern changes the defa
    |
 LL |     let [Foo(&ref a @ [ref b]), Foo(&ref c @ [d])] = [&Foo(&[0]); 2];
    |          ^^^^^^^^^^^^^^^^^^^^^ this matches on type `&_`
-help: make the implied reference patterns explicit
+help: rewrite the pattern
+   |
+LL -     let [Foo(&ref a @ [ref b]), Foo(&ref c @ [d])] = [&Foo(&[0]); 2];
+LL +     let [&Foo(a @ [b]), &Foo(&ref c @ [d])] = [&Foo(&[0]); 2];
    |
-LL |     let [&Foo(&ref a @ [ref b]), &Foo(&ref c @ [d])] = [&Foo(&[0]); 2];
-   |          +                       +
 
 error: binding modifiers may only be written when the default binding mode is `move`
   --> $DIR/migration_lint.rs:242:10