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}