From ef6df3b7135a0f70e83e840b6d6256709504b022 Mon Sep 17 00:00:00 2001 From: dianne 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 +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 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 @@ -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 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 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 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 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 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 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 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 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 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 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 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 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 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, + /// Internal state: the innermost deref above the pattern currently being lowered. + innermost_deref: Option, /// 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, + /// 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, +} + 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 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, + pub(crate) default_mode_labels: &'m FxIndexMap, } -impl Subdiagnostic for Rust2024IncompatiblePatSugg { +impl<'m> Subdiagnostic for Rust2024IncompatiblePatSugg<'m> { fn add_to_diag_with>( 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, /// All the dereferences encountered in lowering the pattern, along with how their corresponding - /// patterns affect the default binding mode. - derefs: IndexVec, + /// patterns affect the default binding mode, and whether to suggest adding/removing them. + derefs: IndexVec>, /// Internal state: the innermost deref above the pattern currently being lowered. innermost_deref: Option, /// 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, } -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 + 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>, + rust_2024_migration: Option>, } 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 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 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, 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, } +pub(crate) enum Rust2024IncompatiblePatSuggKind { + Subtractive, + Additive, +} + impl<'m> Subdiagnostic for Rust2024IncompatiblePatSugg<'m> { fn add_to_diag_with>( 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, } 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, /// 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, + /// 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, + /// The next child in their parents' linked list of children. + next_sibling: Option, + /// 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, } 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) { + 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 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 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 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 +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 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 +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 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, + /// 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, /// 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