Skip to content

Commit ec99e3e

Browse files
committed
clarify wording of match ergonomics diagnostics
1 parent d773bd0 commit ec99e3e

19 files changed

+629
-548
lines changed

compiler/rustc_hir_typeck/src/pat.rs

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3115,20 +3115,29 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
31153115
// binding mode. This keeps it from making those suggestions, as doing so could panic.
31163116
let info = table.entry(pat_id).or_insert_with(|| ty::Rust2024IncompatiblePatInfo {
31173117
primary_labels: Vec::new(),
3118-
bad_modifiers: false,
3118+
bad_ref_modifiers: false,
3119+
bad_mut_modifiers: false,
31193120
bad_ref_pats: false,
31203121
suggest_eliding_modes: !self.tcx.features().ref_pat_eat_one_layer_2024()
31213122
&& !self.tcx.features().ref_pat_eat_one_layer_2024_structural(),
31223123
});
31233124

31243125
let pat_kind = if let PatKind::Binding(user_bind_annot, _, _, _) = subpat.kind {
3125-
info.bad_modifiers = true;
31263126
// If the user-provided binding modifier doesn't match the default binding mode, we'll
31273127
// need to suggest reference patterns, which can affect other bindings.
31283128
// For simplicity, we opt to suggest making the pattern fully explicit.
31293129
info.suggest_eliding_modes &=
31303130
user_bind_annot == BindingMode(ByRef::Yes(def_br_mutbl), Mutability::Not);
3131-
"binding modifier"
3131+
if user_bind_annot == BindingMode(ByRef::No, Mutability::Mut) {
3132+
info.bad_mut_modifiers = true;
3133+
"`mut` binding modifier"
3134+
} else {
3135+
info.bad_ref_modifiers = true;
3136+
match user_bind_annot.1 {
3137+
Mutability::Not => "explicit `ref` binding modifier",
3138+
Mutability::Mut => "explicit `ref mut` binding modifier",
3139+
}
3140+
}
31323141
} else {
31333142
info.bad_ref_pats = true;
31343143
// For simplicity, we don't try to suggest eliding reference patterns. Thus, we'll
@@ -3147,11 +3156,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
31473156
// so, we may want to inspect the span's source callee or macro backtrace.
31483157
"occurs within macro expansion".to_owned()
31493158
} else {
3150-
let dbm_str = match def_br_mutbl {
3151-
Mutability::Not => "ref",
3152-
Mutability::Mut => "ref mut",
3153-
};
3154-
format!("{pat_kind} not allowed under `{dbm_str}` default binding mode")
3159+
format!("{pat_kind} not allowed when implicitly borrowing")
31553160
};
31563161
info.primary_labels.push((trimmed_span, primary_label));
31573162
}

compiler/rustc_lint_defs/src/builtin.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1601,7 +1601,7 @@ declare_lint! {
16011601
"detects patterns whose meaning will change in Rust 2024",
16021602
@future_incompatible = FutureIncompatibleInfo {
16031603
reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2024),
1604-
reference: "<https://doc.rust-lang.org/nightly/edition-guide/rust-2024/match-ergonomics.html>",
1604+
reference: "<https://doc.rust-lang.org/edition-guide/rust-2024/match-ergonomics.html>",
16051605
};
16061606
}
16071607

compiler/rustc_middle/src/ty/typeck_results.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -858,8 +858,10 @@ impl<'tcx> std::fmt::Display for UserTypeKind<'tcx> {
858858
pub struct Rust2024IncompatiblePatInfo {
859859
/// Labeled spans for `&`s, `&mut`s, and binding modifiers incompatible with Rust 2024.
860860
pub primary_labels: Vec<(Span, String)>,
861-
/// Whether any binding modifiers occur under a non-`move` default binding mode.
862-
pub bad_modifiers: bool,
861+
/// Whether any `mut` binding modifiers occur under a non-`move` default binding mode.
862+
pub bad_mut_modifiers: bool,
863+
/// Whether any `ref`/`ref mut` binding modifiers occur under a non-`move` default binding mode.
864+
pub bad_ref_modifiers: bool,
863865
/// Whether any `&` or `&mut` patterns occur under a non-`move` default binding mode.
864866
pub bad_ref_pats: bool,
865867
/// If `true`, we can give a simpler suggestion solely by eliding explicit binding modifiers.

compiler/rustc_mir_build/messages.ftl

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -322,17 +322,6 @@ mir_build_pointer_pattern = function pointers and raw pointers not derived from
322322
323323
mir_build_privately_uninhabited = pattern `{$witness_1}` is currently uninhabited, but this variant contains private fields which may become inhabited in the future
324324
325-
mir_build_rust_2024_incompatible_pat = {$bad_modifiers ->
326-
*[true] binding modifiers{$bad_ref_pats ->
327-
*[true] {" "}and reference patterns
328-
[false] {""}
329-
}
330-
[false] reference patterns
331-
} may only be written when the default binding mode is `move`{$is_hard_error ->
332-
*[true] {""}
333-
[false] {" "}in Rust 2024
334-
}
335-
336325
mir_build_static_in_pattern = statics cannot be referenced in patterns
337326
.label = can't be used in patterns
338327
mir_build_static_in_pattern_def = `static` defined here

compiler/rustc_mir_build/src/errors.rs

Lines changed: 1 addition & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
use rustc_data_structures::fx::FxIndexMap;
21
use rustc_errors::codes::*;
32
use rustc_errors::{
43
Applicability, Diag, DiagArgValue, DiagCtxtHandle, Diagnostic, EmissionGuarantee, Level,
5-
MultiSpan, Subdiagnostic, pluralize,
4+
MultiSpan, Subdiagnostic,
65
};
76
use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic};
87
use rustc_middle::ty::{self, Ty};
@@ -1087,69 +1086,6 @@ pub(crate) enum MiscPatternSuggestion {
10871086
},
10881087
}
10891088

1090-
#[derive(LintDiagnostic)]
1091-
#[diag(mir_build_rust_2024_incompatible_pat)]
1092-
pub(crate) struct Rust2024IncompatiblePat {
1093-
#[subdiagnostic]
1094-
pub(crate) sugg: Rust2024IncompatiblePatSugg,
1095-
pub(crate) bad_modifiers: bool,
1096-
pub(crate) bad_ref_pats: bool,
1097-
pub(crate) is_hard_error: bool,
1098-
}
1099-
1100-
pub(crate) struct Rust2024IncompatiblePatSugg {
1101-
/// If true, our suggestion is to elide explicit binding modifiers.
1102-
/// If false, our suggestion is to make the pattern fully explicit.
1103-
pub(crate) suggest_eliding_modes: bool,
1104-
pub(crate) suggestion: Vec<(Span, String)>,
1105-
pub(crate) ref_pattern_count: usize,
1106-
pub(crate) binding_mode_count: usize,
1107-
/// Labels for where incompatibility-causing by-ref default binding modes were introduced.
1108-
pub(crate) default_mode_labels: FxIndexMap<Span, ty::Mutability>,
1109-
}
1110-
1111-
impl Subdiagnostic for Rust2024IncompatiblePatSugg {
1112-
fn add_to_diag<G: EmissionGuarantee>(self, diag: &mut Diag<'_, G>) {
1113-
// Format and emit explanatory notes about default binding modes. Reversing the spans' order
1114-
// means if we have nested spans, the innermost ones will be visited first.
1115-
for (span, def_br_mutbl) in self.default_mode_labels.into_iter().rev() {
1116-
// Don't point to a macro call site.
1117-
if !span.from_expansion() {
1118-
let note_msg = "matching on a reference type with a non-reference pattern changes the default binding mode";
1119-
let label_msg =
1120-
format!("this matches on type `{}_`", def_br_mutbl.ref_prefix_str());
1121-
let mut label = MultiSpan::from(span);
1122-
label.push_span_label(span, label_msg);
1123-
diag.span_note(label, note_msg);
1124-
}
1125-
}
1126-
1127-
// Format and emit the suggestion.
1128-
let applicability =
1129-
if self.suggestion.iter().all(|(span, _)| span.can_be_used_for_suggestions()) {
1130-
Applicability::MachineApplicable
1131-
} else {
1132-
Applicability::MaybeIncorrect
1133-
};
1134-
let msg = if self.suggest_eliding_modes {
1135-
let plural_modes = pluralize!(self.binding_mode_count);
1136-
format!("remove the unnecessary binding modifier{plural_modes}")
1137-
} else {
1138-
let plural_derefs = pluralize!(self.ref_pattern_count);
1139-
let and_modes = if self.binding_mode_count > 0 {
1140-
format!(" and variable binding mode{}", pluralize!(self.binding_mode_count))
1141-
} else {
1142-
String::new()
1143-
};
1144-
format!("make the implied reference pattern{plural_derefs}{and_modes} explicit")
1145-
};
1146-
// FIXME(dianne): for peace of mind, don't risk emitting a 0-part suggestion (that panics!)
1147-
if !self.suggestion.is_empty() {
1148-
diag.multipart_suggestion_verbose(msg, self.suggestion, applicability);
1149-
}
1150-
}
1151-
}
1152-
11531089
#[derive(Diagnostic)]
11541090
#[diag(mir_build_loop_match_invalid_update)]
11551091
pub(crate) struct LoopMatchInvalidUpdate {

compiler/rustc_mir_build/src/thir/pattern/migration.rs

Lines changed: 80 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
//! Automatic migration of Rust 2021 patterns to a form valid in both Editions 2021 and 2024.
22
33
use rustc_data_structures::fx::FxIndexMap;
4-
use rustc_errors::MultiSpan;
4+
use rustc_errors::{Applicability, Diag, EmissionGuarantee, MultiSpan, pluralize};
55
use rustc_hir::{BindingMode, ByRef, HirId, Mutability};
66
use rustc_lint as lint;
77
use rustc_middle::ty::{self, Rust2024IncompatiblePatInfo, TyCtxt};
88
use rustc_span::{Ident, Span};
99

10-
use crate::errors::{Rust2024IncompatiblePat, Rust2024IncompatiblePatSugg};
11-
use crate::fluent_generated as fluent;
12-
1310
/// For patterns flagged for migration during HIR typeck, this handles constructing and emitting
1411
/// a diagnostic suggestion.
1512
pub(super) struct PatMigration<'a> {
@@ -49,39 +46,90 @@ impl<'a> PatMigration<'a> {
4946
for (span, label) in self.info.primary_labels.iter() {
5047
spans.push_span_label(*span, label.clone());
5148
}
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-
};
5949
// If a relevant span is from at least edition 2024, this is a hard error.
6050
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);
6152
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);
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);
7256
err.emit();
7357
} 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-
);
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+
// We only need two verb phrases if mentioning both modifiers and reference patterns.
78+
(false, false, _) | (_, _, false) => "",
79+
// If mentioning `mut`, we don't have an "explicitly" yet.
80+
(true, _, true) => " or explicitly dereference",
81+
// If mentioning `ref`/`ref mut` but not `mut`, we already have an "explicitly".
82+
(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+
// Format and emit explanatory notes about default binding modes. Reversing the spans' order
90+
// means if we have nested spans, the innermost ones will be visited first.
91+
for (span, def_br_mutbl) in self.default_mode_labels.into_iter().rev() {
92+
// Don't point to a macro call site.
93+
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+
// Format and emit the suggestion.
106+
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+
// FIXME(dianne): for peace of mind, don't risk emitting a 0-part suggestion (that panics!)
130+
debug_assert!(!self.suggestion.is_empty());
131+
if !self.suggestion.is_empty() {
132+
diag.multipart_suggestion_verbose(msg, self.suggestion, applicability);
85133
}
86134
}
87135

tests/ui/pattern/rfc-3627-match-ergonomics-2024/experimental/mixed-editions.classic2021.stderr

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,20 +59,20 @@ LL | let [&bind_ref_mut!(x)] = &mut [0];
5959
| |
6060
| help: replace this `&` with `&mut`: `&mut`
6161

62-
error: binding modifiers may only be written when the default binding mode is `move`
62+
error: cannot explicitly borrow within an implicitly-borrowing pattern
6363
--> $DIR/mixed-editions.rs:30:10
6464
|
6565
LL | let [bind_ref!(y)] = &[0];
6666
| ^^^^^^^^^^^^ occurs within macro expansion
6767
|
68-
= note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/match-ergonomics.html>
69-
note: matching on a reference type with a non-reference pattern changes the default binding mode
68+
= note: for more information, see <https://doc.rust-lang.org/reference/patterns.html#binding-modes>
69+
note: matching on a reference type with a non-reference pattern implicitly borrows the contents
7070
--> $DIR/mixed-editions.rs:30:9
7171
|
7272
LL | let [bind_ref!(y)] = &[0];
73-
| ^^^^^^^^^^^^^^ this matches on type `&_`
73+
| ^^^^^^^^^^^^^^ this non-reference pattern matches on a reference type `&_`
7474
= note: this error originates in the macro `bind_ref` (in Nightly builds, run with -Z macro-backtrace for more info)
75-
help: make the implied reference pattern explicit
75+
help: match on the reference with a reference pattern to avoid implicitly borrowing
7676
|
7777
LL | let &[bind_ref!(y)] = &[0];
7878
| +

tests/ui/pattern/rfc-3627-match-ergonomics-2024/experimental/mixed-editions.classic2024.stderr

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,14 @@ LL | let [&bind_ref_mut!(x)] = &mut [0];
5858
| |
5959
| help: replace this `&` with `&mut`: `&mut`
6060

61-
error: binding modifiers may only be written when the default binding mode is `move`
61+
error: cannot explicitly borrow within an implicitly-borrowing pattern
6262
--> $DIR/mixed-editions.rs:26:21
6363
|
6464
LL | let match_ctor!(ref x) = &[0];
65-
| ^^^ binding modifier not allowed under `ref` default binding mode
65+
| ^^^ explicit `ref` binding modifier not allowed when implicitly borrowing
6666
|
67-
= note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/match-ergonomics.html>
68-
help: make the implied reference pattern explicit
67+
= note: for more information, see <https://doc.rust-lang.org/reference/patterns.html#binding-modes>
68+
help: match on the reference with a reference pattern to avoid implicitly borrowing
6969
--> $DIR/auxiliary/mixed-editions-macros.rs:11:9
7070
|
7171
LL | &[$p]

tests/ui/pattern/rfc-3627-match-ergonomics-2024/experimental/mixed-editions.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,11 @@ fn assert_type_eq<T, U: Eq<T>>(_: T, _: U) {}
2424
/// only when the binding is from edition 2024.
2525
fn ref_binding_tests() {
2626
let match_ctor!(ref x) = &[0];
27-
//[classic2024,structural2024]~^ ERROR: binding modifiers may only be written when the default binding mode is `move`
27+
//[classic2024,structural2024]~^ ERROR: cannot explicitly borrow within an implicitly-borrowing pattern
2828
#[cfg(any(classic2021, structural2021))] assert_type_eq(x, &0u32);
2929

3030
let [bind_ref!(y)] = &[0];
31-
//[classic2021,structural2021]~^ ERROR: binding modifiers may only be written when the default binding mode is `move`
31+
//[classic2021,structural2021]~^ ERROR: cannot explicitly borrow within an implicitly-borrowing pattern
3232
#[cfg(any(classic2024, structural2024))] assert_type_eq(y, &0u32);
3333
}
3434

tests/ui/pattern/rfc-3627-match-ergonomics-2024/experimental/mixed-editions.structural2021.stderr

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,20 +37,20 @@ LL | let [&bind_ref_mut!(x)] = &mut [0];
3737
| |
3838
| help: replace this `&` with `&mut`: `&mut`
3939

40-
error: binding modifiers may only be written when the default binding mode is `move`
40+
error: cannot explicitly borrow within an implicitly-borrowing pattern
4141
--> $DIR/mixed-editions.rs:30:10
4242
|
4343
LL | let [bind_ref!(y)] = &[0];
4444
| ^^^^^^^^^^^^ occurs within macro expansion
4545
|
46-
= note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/match-ergonomics.html>
47-
note: matching on a reference type with a non-reference pattern changes the default binding mode
46+
= note: for more information, see <https://doc.rust-lang.org/reference/patterns.html#binding-modes>
47+
note: matching on a reference type with a non-reference pattern implicitly borrows the contents
4848
--> $DIR/mixed-editions.rs:30:9
4949
|
5050
LL | let [bind_ref!(y)] = &[0];
51-
| ^^^^^^^^^^^^^^ this matches on type `&_`
51+
| ^^^^^^^^^^^^^^ this non-reference pattern matches on a reference type `&_`
5252
= note: this error originates in the macro `bind_ref` (in Nightly builds, run with -Z macro-backtrace for more info)
53-
help: make the implied reference pattern explicit
53+
help: match on the reference with a reference pattern to avoid implicitly borrowing
5454
|
5555
LL | let &[bind_ref!(y)] = &[0];
5656
| +

0 commit comments

Comments
 (0)