rustc_mir_build/thir/pattern/
migration.rs

1//! Automatic migration of Rust 2021 patterns to a form valid in both Editions 2021 and 2024.
2
3use rustc_data_structures::fx::FxIndexMap;
4use rustc_errors::MultiSpan;
5use rustc_hir::{BindingMode, ByRef, HirId, Mutability};
6use rustc_lint as lint;
7use rustc_middle::ty::{self, Rust2024IncompatiblePatInfo, TyCtxt};
8use rustc_span::{Ident, Span};
9
10use crate::errors::{Rust2024IncompatiblePat, Rust2024IncompatiblePatSugg};
11use crate::fluent_generated as fluent;
12
13/// For patterns flagged for migration during HIR typeck, this handles constructing and emitting
14/// a diagnostic suggestion.
15pub(super) struct PatMigration<'a> {
16    suggestion: Vec<(Span, String)>,
17    ref_pattern_count: usize,
18    binding_mode_count: usize,
19    /// Internal state: the ref-mutability of the default binding mode at the subpattern being
20    /// lowered, with the span where it was introduced. `None` for a by-value default mode.
21    default_mode_span: Option<(Span, ty::Mutability)>,
22    /// Labels for where incompatibility-causing by-ref default binding modes were introduced.
23    // FIXME(ref_pat_eat_one_layer_2024_structural): To track the default binding mode, we duplicate
24    // logic from HIR typeck (in order to avoid needing to store all changes to the dbm in
25    // TypeckResults). Since the default binding mode acts differently under this feature gate, the
26    // labels will be wrong.
27    default_mode_labels: FxIndexMap<Span, Mutability>,
28    /// Information collected from typeck, including spans for subpatterns invalid in Rust 2024.
29    info: &'a Rust2024IncompatiblePatInfo,
30}
31
32impl<'a> PatMigration<'a> {
33    pub(super) fn new(info: &'a Rust2024IncompatiblePatInfo) -> Self {
34        PatMigration {
35            suggestion: Vec::new(),
36            ref_pattern_count: 0,
37            binding_mode_count: 0,
38            default_mode_span: None,
39            default_mode_labels: Default::default(),
40            info,
41        }
42    }
43
44    /// On Rust 2024, this emits a hard error. On earlier Editions, this emits the
45    /// future-incompatibility lint `rust_2024_incompatible_pat`.
46    pub(super) fn emit<'tcx>(self, tcx: TyCtxt<'tcx>, pat_id: HirId) {
47        let mut spans =
48            MultiSpan::from_spans(self.info.primary_labels.iter().map(|(span, _)| *span).collect());
49        for (span, label) in self.info.primary_labels.iter() {
50            spans.push_span_label(*span, label.clone());
51        }
52        let sugg = Rust2024IncompatiblePatSugg {
53            suggest_eliding_modes: self.info.suggest_eliding_modes,
54            suggestion: self.suggestion,
55            ref_pattern_count: self.ref_pattern_count,
56            binding_mode_count: self.binding_mode_count,
57            default_mode_labels: self.default_mode_labels,
58        };
59        // If a relevant span is from at least edition 2024, this is a hard error.
60        let is_hard_error = spans.primary_spans().iter().any(|span| span.at_least_rust_2024());
61        if is_hard_error {
62            let mut err =
63                tcx.dcx().struct_span_err(spans, fluent::mir_build_rust_2024_incompatible_pat);
64            if let Some(info) = lint::builtin::RUST_2024_INCOMPATIBLE_PAT.future_incompatible {
65                // provide the same reference link as the lint
66                err.note(format!("for more information, see {}", info.reference));
67            }
68            err.arg("bad_modifiers", self.info.bad_modifiers);
69            err.arg("bad_ref_pats", self.info.bad_ref_pats);
70            err.arg("is_hard_error", true);
71            err.subdiagnostic(sugg);
72            err.emit();
73        } else {
74            tcx.emit_node_span_lint(
75                lint::builtin::RUST_2024_INCOMPATIBLE_PAT,
76                pat_id,
77                spans,
78                Rust2024IncompatiblePat {
79                    sugg,
80                    bad_modifiers: self.info.bad_modifiers,
81                    bad_ref_pats: self.info.bad_ref_pats,
82                    is_hard_error,
83                },
84            );
85        }
86    }
87
88    /// Tracks when we're lowering a pattern that implicitly dereferences the scrutinee.
89    /// This should only be called when the pattern type adjustments list `adjustments` contains an
90    /// implicit deref of a reference type. Returns the prior default binding mode; this should be
91    /// followed by a call to [`PatMigration::leave_ref`] to restore it when we leave the pattern.
92    pub(super) fn visit_implicit_derefs<'tcx>(
93        &mut self,
94        pat_span: Span,
95        adjustments: &[ty::adjustment::PatAdjustment<'tcx>],
96    ) -> Option<(Span, Mutability)> {
97        // Implicitly dereferencing references changes the default binding mode, but implicit derefs
98        // of smart pointers do not. Thus, we only consider implicit derefs of reference types.
99        let implicit_deref_mutbls = adjustments.iter().filter_map(|adjust| {
100            if let &ty::Ref(_, _, mutbl) = adjust.source.kind() { Some(mutbl) } else { None }
101        });
102
103        if !self.info.suggest_eliding_modes {
104            // If we can't fix the pattern by eliding modifiers, we'll need to make the pattern
105            // fully explicit. i.e. we'll need to suggest reference patterns for this.
106            let suggestion_str: String =
107                implicit_deref_mutbls.clone().map(|mutbl| mutbl.ref_prefix_str()).collect();
108            self.suggestion.push((pat_span.shrink_to_lo(), suggestion_str));
109            self.ref_pattern_count += adjustments.len();
110        }
111
112        // Remember if this changed the default binding mode, in case we want to label it.
113        let min_mutbl = implicit_deref_mutbls.min().unwrap();
114        if self.default_mode_span.is_none_or(|(_, old_mutbl)| min_mutbl < old_mutbl) {
115            // This changes the default binding mode to `ref` or `ref mut`. Return the old mode so
116            // it can be reinstated when we leave the pattern.
117            self.default_mode_span.replace((pat_span, min_mutbl))
118        } else {
119            // This does not change the default binding mode; it was already `ref` or `ref mut`.
120            self.default_mode_span
121        }
122    }
123
124    /// Tracks the default binding mode when we're lowering a `&` or `&mut` pattern.
125    /// Returns the prior default binding mode; this should be followed by a call to
126    /// [`PatMigration::leave_ref`] to restore it when we leave the pattern.
127    pub(super) fn visit_explicit_deref(&mut self) -> Option<(Span, Mutability)> {
128        if let Some((default_mode_span, default_ref_mutbl)) = self.default_mode_span {
129            // If this eats a by-ref default binding mode, label the binding mode.
130            self.default_mode_labels.insert(default_mode_span, default_ref_mutbl);
131        }
132        // Set the default binding mode to by-value and return the old default binding mode so it
133        // can be reinstated when we leave the pattern.
134        self.default_mode_span.take()
135    }
136
137    /// Restores the default binding mode after lowering a pattern that could change it.
138    /// This should follow a call to either [`PatMigration::visit_explicit_deref`] or
139    /// [`PatMigration::visit_implicit_derefs`].
140    pub(super) fn leave_ref(&mut self, old_mode_span: Option<(Span, Mutability)>) {
141        self.default_mode_span = old_mode_span
142    }
143
144    /// Determines if a binding is relevant to the diagnostic and adjusts the notes/suggestion if
145    /// so. Bindings are relevant if they have a modifier under a by-ref default mode (invalid in
146    /// Rust 2024) or if we need to suggest a binding modifier for them.
147    pub(super) fn visit_binding(
148        &mut self,
149        pat_span: Span,
150        mode: BindingMode,
151        explicit_ba: BindingMode,
152        ident: Ident,
153    ) {
154        if explicit_ba != BindingMode::NONE
155            && let Some((default_mode_span, default_ref_mutbl)) = self.default_mode_span
156        {
157            // If this overrides a by-ref default binding mode, label the binding mode.
158            self.default_mode_labels.insert(default_mode_span, default_ref_mutbl);
159            // If our suggestion is to elide redundnt modes, this will be one of them.
160            if self.info.suggest_eliding_modes {
161                self.suggestion.push((pat_span.with_hi(ident.span.lo()), String::new()));
162                self.binding_mode_count += 1;
163            }
164        }
165        if !self.info.suggest_eliding_modes
166            && explicit_ba.0 == ByRef::No
167            && let ByRef::Yes(mutbl) = mode.0
168        {
169            // If we can't fix the pattern by eliding modifiers, we'll need to make the pattern
170            // fully explicit. i.e. we'll need to suggest reference patterns for this.
171            let sugg_str = match mutbl {
172                Mutability::Not => "ref ",
173                Mutability::Mut => "ref mut ",
174            };
175            self.suggestion
176                .push((pat_span.with_lo(ident.span.lo()).shrink_to_lo(), sugg_str.to_owned()));
177            self.binding_mode_count += 1;
178        }
179    }
180}