From e1c6eade1619e2b84b39d61811407afc1f82fd12 Mon Sep 17 00:00:00 2001 From: dianne Date: Sun, 9 Feb 2025 04:11:23 -0800 Subject: [PATCH 1/8] move pattern migration setup/emitting to a separate module --- compiler/rustc_mir_build/src/errors.rs | 3 - .../src/thir/pattern/migration.rs | 87 +++++++++++++++++++ .../rustc_mir_build/src/thir/pattern/mod.rs | 75 ++++------------ 3 files changed, 104 insertions(+), 61 deletions(-) create mode 100644 compiler/rustc_mir_build/src/thir/pattern/migration.rs diff --git a/compiler/rustc_mir_build/src/errors.rs b/compiler/rustc_mir_build/src/errors.rs index 07bdc59756aa7..9f8b52165ab90 100644 --- a/compiler/rustc_mir_build/src/errors.rs +++ b/compiler/rustc_mir_build/src/errors.rs @@ -1113,9 +1113,6 @@ pub(crate) struct Rust2024IncompatiblePatSugg { pub(crate) suggestion: Vec<(Span, String)>, pub(crate) ref_pattern_count: usize, pub(crate) 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. - pub(crate) default_mode_span: Option<(Span, ty::Mutability)>, /// Labels for where incompatibility-causing by-ref default binding modes were introduced. pub(crate) default_mode_labels: FxIndexMap, } diff --git a/compiler/rustc_mir_build/src/thir/pattern/migration.rs b/compiler/rustc_mir_build/src/thir/pattern/migration.rs new file mode 100644 index 0000000000000..be1ebd57d1182 --- /dev/null +++ b/compiler/rustc_mir_build/src/thir/pattern/migration.rs @@ -0,0 +1,87 @@ +//! Automatic migration of Rust 2021 patterns to a form valid in both Editions 2021 and 2024. + +use rustc_data_structures::fx::FxIndexMap; +use rustc_errors::MultiSpan; +use rustc_hir::HirId; +use rustc_lint as lint; +use rustc_middle::ty::{self, Rust2024IncompatiblePatInfo, TyCtxt}; +use rustc_span::Span; + +use crate::errors::{Rust2024IncompatiblePat, Rust2024IncompatiblePatSugg}; +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> { + pub(super) suggestion: Vec<(Span, String)>, + pub(super) ref_pattern_count: usize, + pub(super) 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. + pub(super) default_mode_span: Option<(Span, ty::Mutability)>, + /// 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 + // TypeckResults). Since the default binding mode acts differently under this feature gate, the + // labels will be wrong. + pub(super) default_mode_labels: FxIndexMap, + /// Information collected from typeck, including spans for subpatterns invalid in Rust 2024. + pub(super) info: &'a Rust2024IncompatiblePatInfo, +} + +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, + default_mode_labels: Default::default(), + info, + } + } + + /// 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) { + 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, + }; + // 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 { + let mut err = + tcx.dcx().struct_span_err(spans, fluent::mir_build_rust_2024_incompatible_pat); + if let Some(info) = lint::builtin::RUST_2024_INCOMPATIBLE_PAT.future_incompatible { + // provide the same reference link as the lint + err.note(format!("for more information, see {}", info.reference)); + } + err.arg("bad_modifiers", self.info.bad_modifiers); + err.arg("bad_ref_pats", self.info.bad_ref_pats); + err.arg("is_hard_error", true); + err.subdiagnostic(sugg); + err.emit(); + } else { + tcx.emit_node_span_lint( + lint::builtin::RUST_2024_INCOMPATIBLE_PAT, + pat_id, + spans, + Rust2024IncompatiblePat { + sugg, + bad_modifiers: self.info.bad_modifiers, + bad_ref_pats: self.info.bad_ref_pats, + is_hard_error, + }, + ); + } + } +} diff --git a/compiler/rustc_mir_build/src/thir/pattern/mod.rs b/compiler/rustc_mir_build/src/thir/pattern/mod.rs index 83fef7b0de6f5..5535ac9bd636f 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/mod.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/mod.rs @@ -2,18 +2,17 @@ mod check_match; mod const_to_pat; +mod migration; use std::cmp::Ordering; use std::sync::Arc; use rustc_abi::{FieldIdx, Integer}; -use rustc_errors::MultiSpan; use rustc_errors::codes::*; use rustc_hir::def::{CtorOf, DefKind, Res}; use rustc_hir::pat_util::EnumerateAndAdjustIterator; use rustc_hir::{self as hir, ByRef, Mutability, RangeEnd}; use rustc_index::Idx; -use rustc_lint as lint; use rustc_middle::mir::interpret::LitToConstInput; use rustc_middle::thir::{ Ascription, FieldPat, LocalVarId, Pat, PatKind, PatRange, PatRangeBoundary, @@ -26,8 +25,8 @@ use rustc_span::{ErrorGuaranteed, Span}; use tracing::{debug, instrument}; pub(crate) use self::check_match::check_match; +use self::migration::PatMigration; use crate::errors::*; -use crate::fluent_generated as fluent; struct PatCtxt<'a, 'tcx> { tcx: TyCtxt<'tcx>, @@ -35,7 +34,7 @@ struct PatCtxt<'a, 'tcx> { typeck_results: &'a ty::TypeckResults<'tcx>, /// Used by the Rust 2024 migration lint. - rust_2024_migration_suggestion: Option, + rust_2024_migration: Option>, } pub(super) fn pat_from_hir<'a, 'tcx>( @@ -44,59 +43,19 @@ pub(super) fn pat_from_hir<'a, 'tcx>( typeck_results: &'a ty::TypeckResults<'tcx>, pat: &'tcx hir::Pat<'tcx>, ) -> Box> { - let migration_info = typeck_results.rust_2024_migration_desugared_pats().get(pat.hir_id); let mut pcx = PatCtxt { tcx, typing_env, typeck_results, - rust_2024_migration_suggestion: migration_info.and_then(|info| { - Some(Rust2024IncompatiblePatSugg { - suggest_eliding_modes: info.suggest_eliding_modes, - suggestion: Vec::new(), - ref_pattern_count: 0, - binding_mode_count: 0, - default_mode_span: None, - default_mode_labels: Default::default(), - }) - }), + rust_2024_migration: typeck_results + .rust_2024_migration_desugared_pats() + .get(pat.hir_id) + .map(PatMigration::new), }; let result = pcx.lower_pattern(pat); debug!("pat_from_hir({:?}) = {:?}", pat, result); - if let Some(info) = migration_info - && let Some(sugg) = pcx.rust_2024_migration_suggestion - { - let mut spans = - MultiSpan::from_spans(info.primary_labels.iter().map(|(span, _)| *span).collect()); - for (span, label) in &info.primary_labels { - spans.push_span_label(*span, label.clone()); - } - // 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 { - let mut err = - tcx.dcx().struct_span_err(spans, fluent::mir_build_rust_2024_incompatible_pat); - if let Some(lint_info) = lint::builtin::RUST_2024_INCOMPATIBLE_PAT.future_incompatible { - // provide the same reference link as the lint - err.note(format!("for more information, see {}", lint_info.reference)); - } - err.arg("bad_modifiers", info.bad_modifiers); - err.arg("bad_ref_pats", info.bad_ref_pats); - err.arg("is_hard_error", true); - err.subdiagnostic(sugg); - err.emit(); - } else { - tcx.emit_node_span_lint( - lint::builtin::RUST_2024_INCOMPATIBLE_PAT, - pat.hir_id, - spans, - Rust2024IncompatiblePat { - sugg, - bad_modifiers: info.bad_modifiers, - bad_ref_pats: info.bad_ref_pats, - is_hard_error, - }, - ); - } + if let Some(m) = pcx.rust_2024_migration { + m.emit(tcx, pat.hir_id); } result } @@ -107,7 +66,7 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { self.typeck_results.pat_adjustments().get(pat.hir_id).map_or(&[], |v| &**v); let mut opt_old_mode_span = None; - if let Some(s) = &mut self.rust_2024_migration_suggestion + if let Some(s) = &mut self.rust_2024_migration && !adjustments.is_empty() { let implicit_deref_mutbls = adjustments.iter().map(|ref_ty| { @@ -117,7 +76,7 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { mutbl }); - if !s.suggest_eliding_modes { + if !s.info.suggest_eliding_modes { let suggestion_str: String = implicit_deref_mutbls.clone().map(|mutbl| mutbl.ref_prefix_str()).collect(); s.suggestion.push((pat.span.shrink_to_lo(), suggestion_str)); @@ -169,7 +128,7 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { }) }); - if let Some(s) = &mut self.rust_2024_migration_suggestion + if let Some(s) = &mut self.rust_2024_migration && let Some(old_mode_span) = opt_old_mode_span { s.default_mode_span = old_mode_span; @@ -368,7 +327,7 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { } hir::PatKind::Ref(subpattern, _) => { // Track the default binding mode for the Rust 2024 migration suggestion. - let old_mode_span = self.rust_2024_migration_suggestion.as_mut().and_then(|s| { + let old_mode_span = self.rust_2024_migration.as_mut().and_then(|s| { if let Some((default_mode_span, default_ref_mutbl)) = s.default_mode_span { // If this eats a by-ref default binding mode, label the binding mode. s.default_mode_labels.insert(default_mode_span, default_ref_mutbl); @@ -376,7 +335,7 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { s.default_mode_span.take() }); let subpattern = self.lower_pattern(subpattern); - if let Some(s) = &mut self.rust_2024_migration_suggestion { + if let Some(s) = &mut self.rust_2024_migration { s.default_mode_span = old_mode_span; } PatKind::Deref { subpattern } @@ -408,19 +367,19 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { .get(pat.hir_id) .expect("missing binding mode"); - if let Some(s) = &mut self.rust_2024_migration_suggestion { + if let Some(s) = &mut self.rust_2024_migration { if explicit_ba != hir::BindingMode::NONE && let Some((default_mode_span, default_ref_mutbl)) = s.default_mode_span { // If this overrides a by-ref default binding mode, label the binding mode. s.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 s.suggest_eliding_modes { + if s.info.suggest_eliding_modes { s.suggestion.push((pat.span.with_hi(ident.span.lo()), String::new())); s.binding_mode_count += 1; } } - if !s.suggest_eliding_modes + if !s.info.suggest_eliding_modes && explicit_ba.0 == ByRef::No && let ByRef::Yes(mutbl) = mode.0 { From f1c287f45bace992857b0a9c850da6f1a849a6f7 Mon Sep 17 00:00:00 2001 From: dianne Date: Sun, 9 Feb 2025 07:11:08 -0800 Subject: [PATCH 2/8] move pattern migration internals to the `migration` module --- .../src/thir/pattern/migration.rs | 113 ++++++++++++++++-- .../rustc_mir_build/src/thir/pattern/mod.rs | 67 ++--------- 2 files changed, 114 insertions(+), 66 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/migration.rs b/compiler/rustc_mir_build/src/thir/pattern/migration.rs index be1ebd57d1182..bd7787b643d57 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/migration.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/migration.rs @@ -2,10 +2,11 @@ use rustc_data_structures::fx::FxIndexMap; use rustc_errors::MultiSpan; -use rustc_hir::HirId; +use rustc_hir::{BindingMode, ByRef, HirId, Mutability}; use rustc_lint as lint; -use rustc_middle::ty::{self, Rust2024IncompatiblePatInfo, TyCtxt}; -use rustc_span::Span; +use rustc_middle::span_bug; +use rustc_middle::ty::{self, Rust2024IncompatiblePatInfo, Ty, TyCtxt}; +use rustc_span::{Ident, Span}; use crate::errors::{Rust2024IncompatiblePat, Rust2024IncompatiblePatSugg}; use crate::fluent_generated as fluent; @@ -13,20 +14,20 @@ 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> { - pub(super) suggestion: Vec<(Span, String)>, - pub(super) ref_pattern_count: usize, - pub(super) binding_mode_count: usize, + 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. - pub(super) default_mode_span: Option<(Span, ty::Mutability)>, + default_mode_span: Option<(Span, ty::Mutability)>, /// 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 // TypeckResults). Since the default binding mode acts differently under this feature gate, the // labels will be wrong. - pub(super) default_mode_labels: FxIndexMap, + default_mode_labels: FxIndexMap, /// Information collected from typeck, including spans for subpatterns invalid in Rust 2024. - pub(super) info: &'a Rust2024IncompatiblePatInfo, + info: &'a Rust2024IncompatiblePatInfo, } impl<'a> PatMigration<'a> { @@ -84,4 +85,98 @@ 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. 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)> { + 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(); + } + + // 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 + } + } + + /// 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() + } + + /// 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 + } + + /// Determines if a binding is relevant to the diagnostic and adjusts the notes/suggestion if + /// so. Bindings are relevant if they have a modifier under a by-ref default mode (invalid in + /// Rust 2024) or if we need to suggest a binding modifier for them. + pub(super) fn visit_binding( + &mut self, + pat_span: Span, + mode: BindingMode, + explicit_ba: BindingMode, + ident: Ident, + ) { + if explicit_ba != BindingMode::NONE + && let Some((default_mode_span, default_ref_mutbl)) = self.default_mode_span + { + // 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.suggestion.push((pat_span.with_hi(ident.span.lo()), String::new())); + self.binding_mode_count += 1; + } + } + if !self.info.suggest_eliding_modes + && explicit_ba.0 == ByRef::No + && let ByRef::Yes(mutbl) = mode.0 + { + // 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; + } + } } diff --git a/compiler/rustc_mir_build/src/thir/pattern/mod.rs b/compiler/rustc_mir_build/src/thir/pattern/mod.rs index 5535ac9bd636f..5734eb1a0d818 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/mod.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/mod.rs @@ -11,7 +11,7 @@ use rustc_abi::{FieldIdx, Integer}; use rustc_errors::codes::*; use rustc_hir::def::{CtorOf, DefKind, Res}; use rustc_hir::pat_util::EnumerateAndAdjustIterator; -use rustc_hir::{self as hir, ByRef, Mutability, RangeEnd}; +use rustc_hir::{self as hir, RangeEnd}; use rustc_index::Idx; use rustc_middle::mir::interpret::LitToConstInput; use rustc_middle::thir::{ @@ -65,31 +65,13 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { let adjustments: &[Ty<'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() { - 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 !s.info.suggest_eliding_modes { - let suggestion_str: String = - implicit_deref_mutbls.clone().map(|mutbl| mutbl.ref_prefix_str()).collect(); - s.suggestion.push((pat.span.shrink_to_lo(), suggestion_str)); - s.ref_pattern_count += adjustments.len(); - } - - // Remember if this changed the default binding mode, in case we want to label it. - let min_mutbl = implicit_deref_mutbls.min().unwrap(); - if s.default_mode_span.is_none_or(|(_, old_mutbl)| min_mutbl < old_mutbl) { - opt_old_mode_span = Some(s.default_mode_span); - s.default_mode_span = Some((pat.span, min_mutbl)); - } - }; + opt_old_mode_span = s.visit_implicit_derefs(pat.span, adjustments); + } // When implicit dereferences have been inserted in this pattern, the unadjusted lowered // pattern has the type that results *after* dereferencing. For example, in this code: @@ -129,9 +111,9 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { }); if let Some(s) = &mut self.rust_2024_migration - && let Some(old_mode_span) = opt_old_mode_span + && !adjustments.is_empty() { - s.default_mode_span = old_mode_span; + s.leave_ref(opt_old_mode_span); } adjusted_pat @@ -327,16 +309,11 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { } hir::PatKind::Ref(subpattern, _) => { // Track the default binding mode for the Rust 2024 migration suggestion. - let old_mode_span = self.rust_2024_migration.as_mut().and_then(|s| { - if let Some((default_mode_span, default_ref_mutbl)) = s.default_mode_span { - // If this eats a by-ref default binding mode, label the binding mode. - s.default_mode_labels.insert(default_mode_span, default_ref_mutbl); - } - s.default_mode_span.take() - }); + let opt_old_mode_span = + self.rust_2024_migration.as_mut().and_then(|s| s.visit_explicit_deref()); let subpattern = self.lower_pattern(subpattern); if let Some(s) = &mut self.rust_2024_migration { - s.default_mode_span = old_mode_span; + s.leave_ref(opt_old_mode_span); } PatKind::Deref { subpattern } } @@ -368,31 +345,7 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { .expect("missing binding mode"); if let Some(s) = &mut self.rust_2024_migration { - if explicit_ba != hir::BindingMode::NONE - && let Some((default_mode_span, default_ref_mutbl)) = s.default_mode_span - { - // If this overrides a by-ref default binding mode, label the binding mode. - s.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 s.info.suggest_eliding_modes { - s.suggestion.push((pat.span.with_hi(ident.span.lo()), String::new())); - s.binding_mode_count += 1; - } - } - if !s.info.suggest_eliding_modes - && explicit_ba.0 == ByRef::No - && let ByRef::Yes(mutbl) = mode.0 - { - let sugg_str = match mutbl { - Mutability::Not => "ref ", - Mutability::Mut => "ref mut ", - }; - s.suggestion.push(( - pat.span.with_lo(ident.span.lo()).shrink_to_lo(), - sugg_str.to_owned(), - )); - s.binding_mode_count += 1; - } + s.visit_binding(pat.span, mode, explicit_ba, ident); } // A ref x pattern is the same node used for x, and as such it has From 2d032138b2b5d4249a2831bbdcfe3c74e7be7203 Mon Sep 17 00:00:00 2001 From: dianne Date: Thu, 30 Jan 2025 21:42:31 -0800 Subject: [PATCH 3/8] 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 0a22e939496e5..a8efcc003336e 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 7a6f2269d44a2..9f8c24cf3404b 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 191800df07a2e..f7848266d49ef 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 26ff4176a42500f445c3ed0b546f704b3744cbb4 Mon Sep 17 00:00:00 2001 From: dianne Date: Sun, 9 Feb 2025 19:22:29 -0800 Subject: [PATCH 4/8] 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 5734eb1a0d818..db1d01c4f7bf3 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 6a7f026686e1e68a2929caba21fe64d83a235b0d Mon Sep 17 00:00:00 2001 From: dianne Date: Mon, 10 Feb 2025 01:01:41 -0800 Subject: [PATCH 5/8] 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 9f8b52165ab90..cb0f2615e9faa 100644 --- a/compiler/rustc_mir_build/src/errors.rs +++ b/compiler/rustc_mir_build/src/errors.rs @@ -1098,15 +1098,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, @@ -1114,10 +1114,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>, @@ -1125,7 +1125,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 db1d01c4f7bf3..7e60cfec8861b 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 7458b295724fd3c67ffda0e58ca5373d5d2cef08 Mon Sep 17 00:00:00 2001 From: dianne Date: Mon, 10 Feb 2025 02:52:51 -0800 Subject: [PATCH 6/8] 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 7e60cfec8861b..f259f7ce1c234 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 8e7edcac93b1f4283ebd0ca669656b8d3a796e4d Mon Sep 17 00:00:00 2001 From: dianne Date: Mon, 10 Feb 2025 03:40:00 -0800 Subject: [PATCH 7/8] produce suggestions which remove reference patterns --- compiler/rustc_hir_typeck/src/pat.rs | 15 +- .../rustc_middle/src/ty/typeck_results.rs | 2 - compiler/rustc_mir_build/src/errors.rs | 45 +++- .../src/thir/pattern/migration.rs | 217 +++++++++++++++--- .../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 | 14 +- .../migration_lint.stderr | 45 ++-- 9 files changed, 264 insertions(+), 104 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/pat.rs b/compiler/rustc_hir_typeck/src/pat.rs index 7c2a2b3fdf7da..9e08de3999e80 100644 --- a/compiler/rustc_hir_typeck/src/pat.rs +++ b/compiler/rustc_hir_typeck/src/pat.rs @@ -2797,13 +2797,11 @@ 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. + // binding mode. 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(), }); // Only provide a detailed label if the problematic subpattern isn't from an expansion. @@ -2815,20 +2813,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // so, we may want to inspect the span's source callee or macro backtrace. "occurs within macro expansion".to_owned() } else { - 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" }; let dbm_str = match def_br_mutbl { diff --git a/compiler/rustc_middle/src/ty/typeck_results.rs b/compiler/rustc_middle/src/ty/typeck_results.rs index 49bdb5e9dc318..fb9970b65f8d7 100644 --- a/compiler/rustc_middle/src/ty/typeck_results.rs +++ b/compiler/rustc_middle/src/ty/typeck_results.rs @@ -822,6 +822,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 cb0f2615e9faa..6d82dd0cfcdb6 100644 --- a/compiler/rustc_mir_build/src/errors.rs +++ b/compiler/rustc_mir_build/src/errors.rs @@ -1107,22 +1107,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() { @@ -1144,17 +1151,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..47ec8fb6a48ef 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,31 @@ 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. + 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 +316,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 +325,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 +373,91 @@ 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. + 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 f259f7ce1c234..162ad21391980 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 b7fb70dfd2469..a37caa044be87 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 @@ -45,10 +45,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 @@ -79,10 +80,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 a8efcc003336e..7c5fc25a0d393 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); 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 f7848266d49ef..8f303cee5df3b 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 From ec5c713ac59312d337635a7b03e5caf41b30a252 Mon Sep 17 00:00:00 2001 From: dianne Date: Mon, 10 Feb 2025 03:49:56 -0800 Subject: [PATCH 8/8] simplify `&ref x` to `x` --- .../src/thir/pattern/migration.rs | 58 ++++++++++++++----- .../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, 80 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 47ec8fb6a48ef..31263c92f769c 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,22 +296,39 @@ 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 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 { @@ -339,6 +359,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, @@ -348,6 +369,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. @@ -365,7 +387,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, @@ -375,15 +397,22 @@ 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. - let suggest = mode != BindingMode(self.sugg_default_mode(), Mutability::Not); + // 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. + let suggest = + !simplify_deref_ref && mode != BindingMode(self.sugg_default_mode(), Mutability::Not); // 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. @@ -396,8 +425,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); } @@ -415,6 +442,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 162ad21391980..45ce80d072576 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 7c5fc25a0d393..d40ec2a17bfa7 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 8f303cee5df3b..23b30f051506c 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: aborting due to 29 previous errors