|
1 | 1 | //! Automatic migration of Rust 2021 patterns to a form valid in both Editions 2021 and 2024.
|
2 | 2 |
|
3 | 3 | use rustc_data_structures::fx::FxIndexMap;
|
4 |
| -use rustc_errors::MultiSpan; |
| 4 | +use rustc_errors::{Applicability, Diag, EmissionGuarantee, MultiSpan, pluralize}; |
5 | 5 | use rustc_hir::{BindingMode, ByRef, HirId, Mutability};
|
6 | 6 | use rustc_lint as lint;
|
7 | 7 | use rustc_middle::ty::{self, Rust2024IncompatiblePatInfo, TyCtxt};
|
8 | 8 | use rustc_span::{Ident, Span};
|
9 | 9 |
|
10 |
| -use crate::errors::{Rust2024IncompatiblePat, Rust2024IncompatiblePatSugg}; |
11 |
| -use crate::fluent_generated as fluent; |
12 |
| - |
13 | 10 | /// For patterns flagged for migration during HIR typeck, this handles constructing and emitting
|
14 | 11 | /// a diagnostic suggestion.
|
15 | 12 | pub(super) struct PatMigration<'a> {
|
@@ -49,41 +46,92 @@ impl<'a> PatMigration<'a> {
|
49 | 46 | for (span, label) in self.info.primary_labels.iter() {
|
50 | 47 | spans.push_span_label(*span, label.clone());
|
51 | 48 | }
|
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 | 49 | // If a relevant span is from at least edition 2024, this is a hard error.
|
60 | 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); |
61 | 52 | if is_hard_error {
|
62 |
| - let mut err = |
63 |
| - tcx.dcx().struct_span_err(spans, fluent::mir_build_rust_2024_incompatible_pat); |
| 53 | + let mut err = tcx.dcx().struct_span_err(spans, primary_message); |
64 | 54 | if let Some(info) = lint::builtin::RUST_2024_INCOMPATIBLE_PAT.future_incompatible {
|
65 | 55 | // provide the same reference link as the lint
|
66 | 56 | err.note(format!("for more information, see {}", info.reference));
|
67 | 57 | }
|
68 |
| - err.arg("bad_mut_modifiers", self.info.bad_mut_modifiers); |
69 |
| - err.arg("bad_ref_modifiers", self.info.bad_ref_modifiers); |
70 |
| - err.arg("bad_ref_pats", self.info.bad_ref_pats); |
71 |
| - err.arg("is_hard_error", true); |
72 |
| - err.subdiagnostic(sugg); |
| 58 | + self.format_subdiagnostics(&mut err); |
73 | 59 | err.emit();
|
74 | 60 | } else {
|
75 |
| - tcx.emit_node_span_lint( |
76 |
| - lint::builtin::RUST_2024_INCOMPATIBLE_PAT, |
77 |
| - pat_id, |
78 |
| - spans, |
79 |
| - Rust2024IncompatiblePat { |
80 |
| - sugg, |
81 |
| - bad_mut_modifiers: self.info.bad_mut_modifiers, |
82 |
| - bad_ref_modifiers: self.info.bad_ref_modifiers, |
83 |
| - bad_ref_pats: self.info.bad_ref_pats, |
84 |
| - is_hard_error, |
85 |
| - }, |
86 |
| - ); |
| 61 | + tcx.node_span_lint(lint::builtin::RUST_2024_INCOMPATIBLE_PAT, pat_id, spans, |diag| { |
| 62 | + diag.primary_message(primary_message); |
| 63 | + self.format_subdiagnostics(diag); |
| 64 | + }); |
| 65 | + } |
| 66 | + } |
| 67 | + |
| 68 | + fn primary_message(&self, is_hard_error: bool) -> String { |
| 69 | + let verb1 = match (self.info.bad_mut_modifiers, self.info.bad_ref_modifiers) { |
| 70 | + (true, true) => "write explicit binding modifiers", |
| 71 | + (true, false) => "mutably bind by value", |
| 72 | + (false, true) => "explicitly borrow", |
| 73 | + (false, false) => "explicitly dereference", |
| 74 | + }; |
| 75 | + let or_verb2 = match ( |
| 76 | + self.info.bad_mut_modifiers, |
| 77 | + self.info.bad_ref_modifiers, |
| 78 | + self.info.bad_ref_pats, |
| 79 | + ) { |
| 80 | + // We only need two verb phrases if mentioning both modifiers and reference patterns. |
| 81 | + (false, false, _) | (_, _, false) => "", |
| 82 | + // If mentioning `mut`, we don't have an "explicitly" yet. |
| 83 | + (true, _, true) => " or explicitly dereference", |
| 84 | + // If mentioning `ref`/`ref mut` but not `mut`, we already have an "explicitly". |
| 85 | + (false, true, true) => " or dereference", |
| 86 | + }; |
| 87 | + let in_rust_2024 = if is_hard_error { "" } else { " in Rust 2024" }; |
| 88 | + format!("cannot {verb1}{or_verb2} within an implicitly-borrowing pattern{in_rust_2024}") |
| 89 | + } |
| 90 | + |
| 91 | + fn format_subdiagnostics(self, diag: &mut Diag<'_, impl EmissionGuarantee>) { |
| 92 | + // Format and emit explanatory notes about default binding modes. Reversing the spans' order |
| 93 | + // means if we have nested spans, the innermost ones will be visited first. |
| 94 | + for (span, def_br_mutbl) in self.default_mode_labels.into_iter().rev() { |
| 95 | + // Don't point to a macro call site. |
| 96 | + if !span.from_expansion() { |
| 97 | + let note_msg = "matching on a reference type with a non-reference pattern implicitly borrows the contents"; |
| 98 | + let label_msg = format!( |
| 99 | + "this non-reference pattern matches on a reference type `{}_`", |
| 100 | + def_br_mutbl.ref_prefix_str() |
| 101 | + ); |
| 102 | + let mut label = MultiSpan::from(span); |
| 103 | + label.push_span_label(span, label_msg); |
| 104 | + diag.span_note(label, note_msg); |
| 105 | + } |
| 106 | + } |
| 107 | + |
| 108 | + // Format and emit the suggestion. |
| 109 | + let applicability = |
| 110 | + if self.suggestion.iter().all(|(span, _)| span.can_be_used_for_suggestions()) { |
| 111 | + Applicability::MachineApplicable |
| 112 | + } else { |
| 113 | + Applicability::MaybeIncorrect |
| 114 | + }; |
| 115 | + let plural_modes = pluralize!(self.binding_mode_count); |
| 116 | + let msg = if self.info.suggest_eliding_modes { |
| 117 | + format!("remove the unnecessary binding modifier{plural_modes}") |
| 118 | + } else { |
| 119 | + let and_add_modes = if self.binding_mode_count > 0 { |
| 120 | + let a = if self.binding_mode_count == 1 { "a " } else { "" }; |
| 121 | + format!(" and borrow explicitly using {a}variable binding mode{plural_modes}",) |
| 122 | + } else { |
| 123 | + String::new() |
| 124 | + }; |
| 125 | + if self.ref_pattern_count == 1 { |
| 126 | + format!("match on the reference with a reference pattern{and_add_modes}") |
| 127 | + } else { |
| 128 | + format!("match on these references with reference patterns{and_add_modes}") |
| 129 | + } |
| 130 | + }; |
| 131 | + // FIXME(dianne): for peace of mind, don't risk emitting a 0-part suggestion (that panics!) |
| 132 | + debug_assert!(!self.suggestion.is_empty()); |
| 133 | + if !self.suggestion.is_empty() { |
| 134 | + diag.multipart_suggestion_verbose(msg, self.suggestion, applicability); |
87 | 135 | }
|
88 | 136 | }
|
89 | 137 |
|
|
0 commit comments