Skip to main content

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.emit_node_span_lint(
59                lint::builtin::RUST_2024_INCOMPATIBLE_PAT,
60                pat_id,
61                spans,
62                rustc_errors::DiagDecorator(|diag| {
63                    diag.primary_message(primary_message);
64                    self.format_subdiagnostics(diag);
65                }),
66            );
67        }
68    }
69
70    fn primary_message(&self, is_hard_error: bool) -> String {
71        let verb1 = match (self.info.bad_mut_modifiers, self.info.bad_ref_modifiers) {
72            (true, true) => "write explicit binding modifiers",
73            (true, false) => "mutably bind by value",
74            (false, true) => "explicitly borrow",
75            (false, false) => "explicitly dereference",
76        };
77        let or_verb2 = match (
78            self.info.bad_mut_modifiers,
79            self.info.bad_ref_modifiers,
80            self.info.bad_ref_pats,
81        ) {
82            // We only need two verb phrases if mentioning both modifiers and reference patterns.
83            (false, false, _) | (_, _, false) => "",
84            // If mentioning `mut`, we don't have an "explicitly" yet.
85            (true, _, true) => " or explicitly dereference",
86            // If mentioning `ref`/`ref mut` but not `mut`, we already have an "explicitly".
87            (false, true, true) => " or dereference",
88        };
89        let in_rust_2024 = if is_hard_error { "" } else { " in Rust 2024" };
90        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("cannot {0}{1} within an implicitly-borrowing pattern{2}",
                verb1, or_verb2, in_rust_2024))
    })format!("cannot {verb1}{or_verb2} within an implicitly-borrowing pattern{in_rust_2024}")
91    }
92
93    fn format_subdiagnostics(self, diag: &mut Diag<'_, impl EmissionGuarantee>) {
94        // Format and emit explanatory notes about default binding modes. Reversing the spans' order
95        // means if we have nested spans, the innermost ones will be visited first.
96        for (span, def_br_mutbl) in self.default_mode_labels.into_iter().rev() {
97            // Don't point to a macro call site.
98            if !span.from_expansion() {
99                let note_msg = "matching on a reference type with a non-reference pattern implicitly borrows the contents";
100                let label_msg = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("this non-reference pattern matches on a reference type `{0}_`",
                def_br_mutbl.ref_prefix_str()))
    })format!(
101                    "this non-reference pattern matches on a reference type `{}_`",
102                    def_br_mutbl.ref_prefix_str()
103                );
104                let mut label = MultiSpan::from(span);
105                label.push_span_label(span, label_msg);
106                diag.span_note(label, note_msg);
107            }
108        }
109
110        // Format and emit the suggestion.
111        let applicability =
112            if self.suggestion.iter().all(|(span, _)| span.can_be_used_for_suggestions()) {
113                Applicability::MachineApplicable
114            } else {
115                Applicability::MaybeIncorrect
116            };
117        let plural_modes = if self.binding_mode_count == 1 { "" } else { "s" }pluralize!(self.binding_mode_count);
118        let msg = if self.info.suggest_eliding_modes {
119            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("remove the unnecessary binding modifier{0}",
                plural_modes))
    })format!("remove the unnecessary binding modifier{plural_modes}")
120        } else {
121            let match_on_these_references = if self.ref_pattern_count == 1 {
122                "match on the reference with a reference pattern"
123            } else {
124                "match on these references with reference patterns"
125            };
126            let and_explain_modes = if self.binding_mode_count > 0 {
127                let a = if self.binding_mode_count == 1 { "a " } else { "" };
128                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!(" and borrow explicitly using {0}variable binding mode{1}",
                a, plural_modes))
    })format!(" and borrow explicitly using {a}variable binding mode{plural_modes}")
129            } else {
130                " to avoid implicitly borrowing".to_owned()
131            };
132            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}{1}", match_on_these_references,
                and_explain_modes))
    })format!("{match_on_these_references}{and_explain_modes}")
133        };
134        // FIXME(dianne): for peace of mind, don't risk emitting a 0-part suggestion (that panics!)
135        if true {
    if !!self.suggestion.is_empty() {
        ::core::panicking::panic("assertion failed: !self.suggestion.is_empty()")
    };
};debug_assert!(!self.suggestion.is_empty());
136        if !self.suggestion.is_empty() {
137            diag.multipart_suggestion(msg, self.suggestion, applicability);
138        }
139    }
140
141    /// Tracks when we're lowering a pattern that implicitly dereferences the scrutinee.
142    /// This should only be called when the pattern type adjustments list `adjustments` contains an
143    /// implicit deref of a reference type. Returns the prior default binding mode; this should be
144    /// followed by a call to [`PatMigration::leave_ref`] to restore it when we leave the pattern.
145    pub(super) fn visit_implicit_derefs<'tcx>(
146        &mut self,
147        pat_span: Span,
148        adjustments: &[ty::adjustment::PatAdjustment<'tcx>],
149    ) -> Option<(Span, Mutability)> {
150        // Implicitly dereferencing references changes the default binding mode, but implicit derefs
151        // of smart pointers do not. Thus, we only consider implicit derefs of reference types.
152        let implicit_deref_mutbls = adjustments.iter().filter_map(|adjust| {
153            if let &ty::Ref(_, _, mutbl) = adjust.source.kind() { Some(mutbl) } else { None }
154        });
155
156        if !self.info.suggest_eliding_modes {
157            // If we can't fix the pattern by eliding modifiers, we'll need to make the pattern
158            // fully explicit. i.e. we'll need to suggest reference patterns for this.
159            let suggestion_str: String =
160                implicit_deref_mutbls.clone().map(|mutbl| mutbl.ref_prefix_str()).collect();
161            self.suggestion.push((pat_span.shrink_to_lo(), suggestion_str));
162            self.ref_pattern_count += adjustments.len();
163        }
164
165        // Remember if this changed the default binding mode, in case we want to label it.
166        let min_mutbl = implicit_deref_mutbls.min().unwrap();
167        if self.default_mode_span.is_none_or(|(_, old_mutbl)| min_mutbl < old_mutbl) {
168            // This changes the default binding mode to `ref` or `ref mut`. Return the old mode so
169            // it can be reinstated when we leave the pattern.
170            self.default_mode_span.replace((pat_span, min_mutbl))
171        } else {
172            // This does not change the default binding mode; it was already `ref` or `ref mut`.
173            self.default_mode_span
174        }
175    }
176
177    /// Tracks the default binding mode when we're lowering a `&` or `&mut` pattern.
178    /// Returns the prior default binding mode; this should be followed by a call to
179    /// [`PatMigration::leave_ref`] to restore it when we leave the pattern.
180    pub(super) fn visit_explicit_deref(&mut self) -> Option<(Span, Mutability)> {
181        if let Some((default_mode_span, default_ref_mutbl)) = self.default_mode_span {
182            // If this eats a by-ref default binding mode, label the binding mode.
183            self.default_mode_labels.insert(default_mode_span, default_ref_mutbl);
184        }
185        // Set the default binding mode to by-value and return the old default binding mode so it
186        // can be reinstated when we leave the pattern.
187        self.default_mode_span.take()
188    }
189
190    /// Restores the default binding mode after lowering a pattern that could change it.
191    /// This should follow a call to either [`PatMigration::visit_explicit_deref`] or
192    /// [`PatMigration::visit_implicit_derefs`].
193    pub(super) fn leave_ref(&mut self, old_mode_span: Option<(Span, Mutability)>) {
194        self.default_mode_span = old_mode_span
195    }
196
197    /// Determines if a binding is relevant to the diagnostic and adjusts the notes/suggestion if
198    /// so. Bindings are relevant if they have a modifier under a by-ref default mode (invalid in
199    /// Rust 2024) or if we need to suggest a binding modifier for them.
200    pub(super) fn visit_binding(
201        &mut self,
202        pat_span: Span,
203        mode: BindingMode,
204        explicit_ba: BindingMode,
205        ident: Ident,
206    ) {
207        if explicit_ba != BindingMode::NONE
208            && let Some((default_mode_span, default_ref_mutbl)) = self.default_mode_span
209        {
210            // If this overrides a by-ref default binding mode, label the binding mode.
211            self.default_mode_labels.insert(default_mode_span, default_ref_mutbl);
212            // If our suggestion is to elide redundnt modes, this will be one of them.
213            if self.info.suggest_eliding_modes {
214                self.suggestion.push((pat_span.with_hi(ident.span.lo()), String::new()));
215                self.binding_mode_count += 1;
216            }
217        }
218        if !self.info.suggest_eliding_modes
219            && explicit_ba.0 == ByRef::No
220            && let ByRef::Yes(_, mutbl) = mode.0
221        {
222            // If we can't fix the pattern by eliding modifiers, we'll need to make the pattern
223            // fully explicit. i.e. we'll need to suggest reference patterns for this.
224            let sugg_str = match mutbl {
225                Mutability::Not => "ref ",
226                Mutability::Mut => "ref mut ",
227            };
228            self.suggestion
229                .push((pat_span.with_lo(ident.span.lo()).shrink_to_lo(), sugg_str.to_owned()));
230            self.binding_mode_count += 1;
231        }
232    }
233}