1//! Automatic migration of Rust 2021 patterns to a form valid in both Editions 2021 and 2024.
23use 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};
910/// 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.
18default_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.
24default_mode_labels: FxIndexMap<Span, Mutability>,
25/// Information collected from typeck, including spans for subpatterns invalid in Rust 2024.
26info: &'a Rust2024IncompatiblePatInfo,
27}
2829impl<'a> PatMigration<'a> {
30pub(super) fn new(info: &'a Rust2024IncompatiblePatInfo) -> Self {
31PatMigration {
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(),
37info,
38 }
39 }
4041/// On Rust 2024, this emits a hard error. On earlier Editions, this emits the
42 /// future-incompatibility lint `rust_2024_incompatible_pat`.
43pub(super) fn emit<'tcx>(self, tcx: TyCtxt<'tcx>, pat_id: HirId) {
44let mut spans =
45MultiSpan::from_spans(self.info.primary_labels.iter().map(|(span, _)| *span).collect());
46for (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.
50let is_hard_error = spans.primary_spans().iter().any(|span| span.at_least_rust_2024());
51let primary_message = self.primary_message(is_hard_error);
52if is_hard_error {
53let mut err = tcx.dcx().struct_span_err(spans, primary_message);
54err.note("for more information, see <https://doc.rust-lang.org/reference/patterns.html#binding-modes>");
55self.format_subdiagnostics(&mut err);
56err.emit();
57 } else {
58tcx.emit_node_span_lint(
59 lint::builtin::RUST_2024_INCOMPATIBLE_PAT,
60pat_id,
61spans,
62 rustc_errors::DiagDecorator(|diag| {
63diag.primary_message(primary_message);
64self.format_subdiagnostics(diag);
65 }),
66 );
67 }
68 }
6970fn primary_message(&self, is_hard_error: bool) -> String {
71let 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 };
77let or_verb2 = match (
78self.info.bad_mut_modifiers,
79self.info.bad_ref_modifiers,
80self.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 };
89let 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 }
9293fn 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.
96for (span, def_br_mutbl) in self.default_mode_labels.into_iter().rev() {
97// Don't point to a macro call site.
98if !span.from_expansion() {
99let note_msg = "matching on a reference type with a non-reference pattern implicitly borrows the contents";
100let 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 );
104let mut label = MultiSpan::from(span);
105 label.push_span_label(span, label_msg);
106 diag.span_note(label, note_msg);
107 }
108 }
109110// Format and emit the suggestion.
111let applicability =
112if self.suggestion.iter().all(|(span, _)| span.can_be_used_for_suggestions()) {
113 Applicability::MachineApplicable114 } else {
115 Applicability::MaybeIncorrect116 };
117let plural_modes = if self.binding_mode_count == 1 { "" } else { "s" }pluralize!(self.binding_mode_count);
118let 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 {
121let 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};
126let and_explain_modes = if self.binding_mode_count > 0 {
127let 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!)
135if true {
if !!self.suggestion.is_empty() {
::core::panicking::panic("assertion failed: !self.suggestion.is_empty()")
};
};debug_assert!(!self.suggestion.is_empty());
136if !self.suggestion.is_empty() {
137diag.multipart_suggestion(msg, self.suggestion, applicability);
138 }
139 }
140141/// 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.
145pub(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.
152let implicit_deref_mutbls = adjustments.iter().filter_map(|adjust| {
153if let &ty::Ref(_, _, mutbl) = adjust.source.kind() { Some(mutbl) } else { None }
154 });
155156if !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.
159let suggestion_str: String =
160implicit_deref_mutbls.clone().map(|mutbl| mutbl.ref_prefix_str()).collect();
161self.suggestion.push((pat_span.shrink_to_lo(), suggestion_str));
162self.ref_pattern_count += adjustments.len();
163 }
164165// Remember if this changed the default binding mode, in case we want to label it.
166let min_mutbl = implicit_deref_mutbls.min().unwrap();
167if 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.
170self.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`.
173self.default_mode_span
174 }
175 }
176177/// 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.
180pub(super) fn visit_explicit_deref(&mut self) -> Option<(Span, Mutability)> {
181if 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.
183self.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.
187self.default_mode_span.take()
188 }
189190/// 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`].
193pub(super) fn leave_ref(&mut self, old_mode_span: Option<(Span, Mutability)>) {
194self.default_mode_span = old_mode_span195 }
196197/// 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.
200pub(super) fn visit_binding(
201&mut self,
202 pat_span: Span,
203 mode: BindingMode,
204 explicit_ba: BindingMode,
205 ident: Ident,
206 ) {
207if explicit_ba != BindingMode::NONE208 && 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.
211self.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.
213if self.info.suggest_eliding_modes {
214self.suggestion.push((pat_span.with_hi(ident.span.lo()), String::new()));
215self.binding_mode_count += 1;
216 }
217 }
218if !self.info.suggest_eliding_modes
219 && explicit_ba.0 == ByRef::No220 && 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.
224let sugg_str = match mutbl {
225 Mutability::Not => "ref ",
226 Mutability::Mut => "ref mut ",
227 };
228self.suggestion
229 .push((pat_span.with_lo(ident.span.lo()).shrink_to_lo(), sugg_str.to_owned()));
230self.binding_mode_count += 1;
231 }
232 }
233}