rustc_mir_build/thir/pattern/
migration.rs1use 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
10pub(super) struct PatMigration<'a> {
13 suggestion: Vec<(Span, String)>,
14 ref_pattern_count: usize,
15 binding_mode_count: usize,
16 default_mode_span: Option<(Span, ty::Mutability)>,
19 default_mode_labels: FxIndexMap<Span, Mutability>,
25 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 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 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 (false, false, _) | (_, _, false) => "",
79 (true, _, true) => " or explicitly dereference",
81 (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 for (span, def_br_mutbl) in self.default_mode_labels.into_iter().rev() {
92 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 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 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 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 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 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 let min_mutbl = implicit_deref_mutbls.min().unwrap();
162 if self.default_mode_span.is_none_or(|(_, old_mutbl)| min_mutbl < old_mutbl) {
163 self.default_mode_span.replace((pat_span, min_mutbl))
166 } else {
167 self.default_mode_span
169 }
170 }
171
172 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 self.default_mode_labels.insert(default_mode_span, default_ref_mutbl);
179 }
180 self.default_mode_span.take()
183 }
184
185 pub(super) fn leave_ref(&mut self, old_mode_span: Option<(Span, Mutability)>) {
189 self.default_mode_span = old_mode_span
190 }
191
192 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 self.default_mode_labels.insert(default_mode_span, default_ref_mutbl);
207 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 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}