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