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::{Applicability, Diag, EmissionGuarantee, MultiSpan, pluralize};
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
10/// For patterns flagged for migration during HIR typeck, this handles constructing and emitting
11/// a diagnostic suggestion.
12pub(super) struct PatMigration<'a> {
13    suggestion: Vec<(Span, String)>,
14    ref_pattern_count: usize,
15    binding_mode_count: usize,
16    /// Internal state: the ref-mutability of the default binding mode at the subpattern being
17    /// lowered, with the span where it was introduced. `None` for a by-value default mode.
18    default_mode_span: Option<(Span, ty::Mutability)>,
19    /// Labels for where incompatibility-causing by-ref default binding modes were introduced.
20    // FIXME(ref_pat_eat_one_layer_2024_structural): To track the default binding mode, we duplicate
21    // logic from HIR typeck (in order to avoid needing to store all changes to the dbm in
22    // TypeckResults). Since the default binding mode acts differently under this feature gate, the
23    // labels will be wrong.
24    default_mode_labels: FxIndexMap<Span, Mutability>,
25    /// Information collected from typeck, including spans for subpatterns invalid in Rust 2024.
26    info: &'a Rust2024IncompatiblePatInfo,
27}
28
29impl<'a> PatMigration<'a> {
30    pub(super) fn new(info: &'a Rust2024IncompatiblePatInfo) -> Self {
31        PatMigration {
32            suggestion: Vec::new(),
33            ref_pattern_count: 0,
34            binding_mode_count: 0,
35            default_mode_span: None,
36            default_mode_labels: Default::default(),
37            info,
38        }
39    }
40
41    /// On Rust 2024, this emits a hard error. On earlier Editions, this emits the
42    /// future-incompatibility lint `rust_2024_incompatible_pat`.
43    pub(super) fn emit<'tcx>(self, tcx: TyCtxt<'tcx>, pat_id: HirId) {
44        let mut spans =
45            MultiSpan::from_spans(self.info.primary_labels.iter().map(|(span, _)| *span).collect());
46        for (span, label) in self.info.primary_labels.iter() {
47            spans.push_span_label(*span, label.clone());
48        }
49        // If a relevant span is from at least edition 2024, this is a hard error.
50        let is_hard_error = spans.primary_spans().iter().any(|span| span.at_least_rust_2024());
51        let primary_message = self.primary_message(is_hard_error);
52        if is_hard_error {
53            let mut err = tcx.dcx().struct_span_err(spans, primary_message);
54            err.note("for more information, see <https://doc.rust-lang.org/reference/patterns.html#binding-modes>");
55            self.format_subdiagnostics(&mut err);
56            err.emit();
57        } else {
58            tcx.node_span_lint(lint::builtin::RUST_2024_INCOMPATIBLE_PAT, pat_id, spans, |diag| {
59                diag.primary_message(primary_message);
60                self.format_subdiagnostics(diag);
61            });
62        }
63    }
64
65    fn primary_message(&self, is_hard_error: bool) -> String {
66        let verb1 = match (self.info.bad_mut_modifiers, self.info.bad_ref_modifiers) {
67            (true, true) => "write explicit binding modifiers",
68            (true, false) => "mutably bind by value",
69            (false, true) => "explicitly borrow",
70            (false, false) => "explicitly dereference",
71        };
72        let or_verb2 = match (
73            self.info.bad_mut_modifiers,
74            self.info.bad_ref_modifiers,
75            self.info.bad_ref_pats,
76        ) {
77            // We only need two verb phrases if mentioning both modifiers and reference patterns.
78            (false, false, _) | (_, _, false) => "",
79            // If mentioning `mut`, we don't have an "explicitly" yet.
80            (true, _, true) => " or explicitly dereference",
81            // If mentioning `ref`/`ref mut` but not `mut`, we already have an "explicitly".
82            (false, true, true) => " or dereference",
83        };
84        let in_rust_2024 = if is_hard_error { "" } else { " in Rust 2024" };
85        format!("cannot {verb1}{or_verb2} within an implicitly-borrowing pattern{in_rust_2024}")
86    }
87
88    fn format_subdiagnostics(self, diag: &mut Diag<'_, impl EmissionGuarantee>) {
89        // Format and emit explanatory notes about default binding modes. Reversing the spans' order
90        // means if we have nested spans, the innermost ones will be visited first.
91        for (span, def_br_mutbl) in self.default_mode_labels.into_iter().rev() {
92            // Don't point to a macro call site.
93            if !span.from_expansion() {
94                let note_msg = "matching on a reference type with a non-reference pattern implicitly borrows the contents";
95                let label_msg = format!(
96                    "this non-reference pattern matches on a reference type `{}_`",
97                    def_br_mutbl.ref_prefix_str()
98                );
99                let mut label = MultiSpan::from(span);
100                label.push_span_label(span, label_msg);
101                diag.span_note(label, note_msg);
102            }
103        }
104
105        // Format and emit the suggestion.
106        let applicability =
107            if self.suggestion.iter().all(|(span, _)| span.can_be_used_for_suggestions()) {
108                Applicability::MachineApplicable
109            } else {
110                Applicability::MaybeIncorrect
111            };
112        let plural_modes = pluralize!(self.binding_mode_count);
113        let msg = if self.info.suggest_eliding_modes {
114            format!("remove the unnecessary binding modifier{plural_modes}")
115        } else {
116            let match_on_these_references = if self.ref_pattern_count == 1 {
117                "match on the reference with a reference pattern"
118            } else {
119                "match on these references with reference patterns"
120            };
121            let and_explain_modes = if self.binding_mode_count > 0 {
122                let a = if self.binding_mode_count == 1 { "a " } else { "" };
123                format!(" and borrow explicitly using {a}variable binding mode{plural_modes}")
124            } else {
125                " to avoid implicitly borrowing".to_owned()
126            };
127            format!("{match_on_these_references}{and_explain_modes}")
128        };
129        // FIXME(dianne): for peace of mind, don't risk emitting a 0-part suggestion (that panics!)
130        debug_assert!(!self.suggestion.is_empty());
131        if !self.suggestion.is_empty() {
132            diag.multipart_suggestion_verbose(msg, self.suggestion, applicability);
133        }
134    }
135
136    /// Tracks when we're lowering a pattern that implicitly dereferences the scrutinee.
137    /// This should only be called when the pattern type adjustments list `adjustments` contains an
138    /// implicit deref of a reference type. Returns the prior default binding mode; this should be
139    /// followed by a call to [`PatMigration::leave_ref`] to restore it when we leave the pattern.
140    pub(super) fn visit_implicit_derefs<'tcx>(
141        &mut self,
142        pat_span: Span,
143        adjustments: &[ty::adjustment::PatAdjustment<'tcx>],
144    ) -> Option<(Span, Mutability)> {
145        // Implicitly dereferencing references changes the default binding mode, but implicit derefs
146        // of smart pointers do not. Thus, we only consider implicit derefs of reference types.
147        let implicit_deref_mutbls = adjustments.iter().filter_map(|adjust| {
148            if let &ty::Ref(_, _, mutbl) = adjust.source.kind() { Some(mutbl) } else { None }
149        });
150
151        if !self.info.suggest_eliding_modes {
152            // If we can't fix the pattern by eliding modifiers, we'll need to make the pattern
153            // fully explicit. i.e. we'll need to suggest reference patterns for this.
154            let suggestion_str: String =
155                implicit_deref_mutbls.clone().map(|mutbl| mutbl.ref_prefix_str()).collect();
156            self.suggestion.push((pat_span.shrink_to_lo(), suggestion_str));
157            self.ref_pattern_count += adjustments.len();
158        }
159
160        // Remember if this changed the default binding mode, in case we want to label it.
161        let min_mutbl = implicit_deref_mutbls.min().unwrap();
162        if self.default_mode_span.is_none_or(|(_, old_mutbl)| min_mutbl < old_mutbl) {
163            // This changes the default binding mode to `ref` or `ref mut`. Return the old mode so
164            // it can be reinstated when we leave the pattern.
165            self.default_mode_span.replace((pat_span, min_mutbl))
166        } else {
167            // This does not change the default binding mode; it was already `ref` or `ref mut`.
168            self.default_mode_span
169        }
170    }
171
172    /// Tracks the default binding mode when we're lowering a `&` or `&mut` pattern.
173    /// Returns the prior default binding mode; this should be followed by a call to
174    /// [`PatMigration::leave_ref`] to restore it when we leave the pattern.
175    pub(super) fn visit_explicit_deref(&mut self) -> Option<(Span, Mutability)> {
176        if let Some((default_mode_span, default_ref_mutbl)) = self.default_mode_span {
177            // If this eats a by-ref default binding mode, label the binding mode.
178            self.default_mode_labels.insert(default_mode_span, default_ref_mutbl);
179        }
180        // Set the default binding mode to by-value and return the old default binding mode so it
181        // can be reinstated when we leave the pattern.
182        self.default_mode_span.take()
183    }
184
185    /// Restores the default binding mode after lowering a pattern that could change it.
186    /// This should follow a call to either [`PatMigration::visit_explicit_deref`] or
187    /// [`PatMigration::visit_implicit_derefs`].
188    pub(super) fn leave_ref(&mut self, old_mode_span: Option<(Span, Mutability)>) {
189        self.default_mode_span = old_mode_span
190    }
191
192    /// Determines if a binding is relevant to the diagnostic and adjusts the notes/suggestion if
193    /// so. Bindings are relevant if they have a modifier under a by-ref default mode (invalid in
194    /// Rust 2024) or if we need to suggest a binding modifier for them.
195    pub(super) fn visit_binding(
196        &mut self,
197        pat_span: Span,
198        mode: BindingMode,
199        explicit_ba: BindingMode,
200        ident: Ident,
201    ) {
202        if explicit_ba != BindingMode::NONE
203            && let Some((default_mode_span, default_ref_mutbl)) = self.default_mode_span
204        {
205            // If this overrides a by-ref default binding mode, label the binding mode.
206            self.default_mode_labels.insert(default_mode_span, default_ref_mutbl);
207            // If our suggestion is to elide redundnt modes, this will be one of them.
208            if self.info.suggest_eliding_modes {
209                self.suggestion.push((pat_span.with_hi(ident.span.lo()), String::new()));
210                self.binding_mode_count += 1;
211            }
212        }
213        if !self.info.suggest_eliding_modes
214            && explicit_ba.0 == ByRef::No
215            && let ByRef::Yes(mutbl) = mode.0
216        {
217            // If we can't fix the pattern by eliding modifiers, we'll need to make the pattern
218            // fully explicit. i.e. we'll need to suggest reference patterns for this.
219            let sugg_str = match mutbl {
220                Mutability::Not => "ref ",
221                Mutability::Mut => "ref mut ",
222            };
223            self.suggestion
224                .push((pat_span.with_lo(ident.span.lo()).shrink_to_lo(), sugg_str.to_owned()));
225            self.binding_mode_count += 1;
226        }
227    }
228}