Skip to content

Commit 3023330

Browse files
committed
pattern migration beautification, part 1: beautify in simple cases
This makes it so that if we can suggest removing all of the explicit reference patterns and binding modes from a pattern to migrate it, we do that. Otherwise, we use the old behavior of making everything explicit. In a later commit, this will be extended (and partially rewritten) to handle cases where we can omit some reference patterns to avoid specifying some binding modes, but not all.
1 parent cb725bd commit 3023330

File tree

8 files changed

+253
-75
lines changed

8 files changed

+253
-75
lines changed

compiler/rustc_mir_build/src/errors.rs

+19-4
Original file line numberDiff line numberDiff line change
@@ -1104,10 +1104,16 @@ pub(crate) struct Rust2024IncompatiblePat {
11041104

11051105
pub(crate) struct Rust2024IncompatiblePatSugg {
11061106
pub(crate) suggestion: Vec<(Span, String)>,
1107+
pub(crate) kind: Rust2024IncompatiblePatSuggKind,
11071108
pub(crate) ref_pattern_count: usize,
11081109
pub(crate) binding_mode_count: usize,
11091110
}
11101111

1112+
pub(crate) enum Rust2024IncompatiblePatSuggKind {
1113+
Subtractive,
1114+
Additive,
1115+
}
1116+
11111117
impl Subdiagnostic for Rust2024IncompatiblePatSugg {
11121118
fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
11131119
self,
@@ -1120,14 +1126,23 @@ impl Subdiagnostic for Rust2024IncompatiblePatSugg {
11201126
} else {
11211127
Applicability::MaybeIncorrect
11221128
};
1123-
let plural_derefs = pluralize!(self.ref_pattern_count);
1124-
let and_modes = if self.binding_mode_count > 0 {
1125-
format!(" and variable binding mode{}", pluralize!(self.binding_mode_count))
1129+
let derefs = if self.ref_pattern_count > 0 {
1130+
format!("reference pattern{}", pluralize!(self.ref_pattern_count))
1131+
} else {
1132+
String::new()
1133+
};
1134+
let modes = if self.binding_mode_count > 0 {
1135+
format!("variable binding mode{}", pluralize!(self.binding_mode_count))
11261136
} else {
11271137
String::new()
11281138
};
1139+
let and = if !derefs.is_empty() && !modes.is_empty() { " and " } else { "" };
1140+
let (old_visibility, new_visibility) = match self.kind {
1141+
Rust2024IncompatiblePatSuggKind::Subtractive => ("", "implicit"),
1142+
Rust2024IncompatiblePatSuggKind::Additive => ("implied ", "explicit"),
1143+
};
11291144
diag.multipart_suggestion_verbose(
1130-
format!("make the implied reference pattern{plural_derefs}{and_modes} explicit"),
1145+
format!("make the {old_visibility}{derefs}{and}{modes} {new_visibility}"),
11311146
self.suggestion,
11321147
applicability,
11331148
);

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

+170-32
Original file line numberDiff line numberDiff line change
@@ -2,47 +2,64 @@
22
33
use rustc_ast::BindingMode;
44
use rustc_errors::MultiSpan;
5-
use rustc_hir::{ByRef, HirId, Mutability};
5+
use rustc_hir::{self as hir, ByRef, HirId, Mutability};
66
use rustc_lint as lint;
77
use rustc_middle::span_bug;
8-
use rustc_middle::ty::{self, Ty, TyCtxt};
8+
use rustc_middle::ty::{self, Ty, TyCtxt, TypeckResults};
99
use rustc_span::{Ident, Span};
1010

11-
use crate::errors::{Rust2024IncompatiblePat, Rust2024IncompatiblePatSugg};
11+
use crate::errors::*;
1212
use crate::fluent_generated as fluent;
1313

1414
/// For patterns flagged for migration during HIR typeck, this handles constructing and emitting
1515
/// a diagnostic suggestion.
1616
pub(super) struct PatMigration<'a> {
17-
suggestion: Vec<(Span, String)>,
18-
ref_pattern_count: usize,
19-
binding_mode_count: usize,
17+
/// `&` and `&mut` patterns we may need to suggest removing.
18+
explicit_derefs: Vec<Span>,
19+
/// Variable binding modes we may need to suggest making implicit.
20+
explicit_modes: Vec<Span>,
21+
/// Implicit dereferences we may need to suggest adding `&` or `&mut` patterns for, together
22+
/// with the HIR id of the pattern where they occur, for formatting.
23+
implicit_derefs: Vec<(Span, HirId)>,
24+
/// Implicit by-reference binding modes we may need to suggest making explicit.
25+
implicit_modes: Vec<(Span, Mutability)>,
26+
/// How many references deep is the current pattern? For determining default binding mode.
27+
current_ref_depth: usize,
28+
/// How many references deep is the binding mode able to be `ref mut`?
29+
current_max_ref_mut_depth: usize,
30+
/// Whether we can suggest making derefs and binding modes implicit rather than explicit.
31+
can_suggest_removing: bool,
2032
/// Labeled spans for subpatterns invalid in Rust 2024.
2133
labels: &'a [(Span, String)],
2234
}
2335

2436
impl<'a> PatMigration<'a> {
2537
pub(super) fn new(labels: &'a Vec<(Span, String)>) -> Self {
2638
PatMigration {
27-
suggestion: Vec::new(),
28-
ref_pattern_count: 0,
29-
binding_mode_count: 0,
39+
explicit_derefs: Vec::new(),
40+
explicit_modes: Vec::new(),
41+
implicit_derefs: Vec::new(),
42+
implicit_modes: Vec::new(),
43+
current_ref_depth: 0,
44+
current_max_ref_mut_depth: 0,
45+
can_suggest_removing: true,
3046
labels: labels.as_slice(),
3147
}
3248
}
3349

3450
/// On Rust 2024, this emits a hard error. On earlier Editions, this emits the
3551
/// future-incompatibility lint `rust_2024_incompatible_pat`.
36-
pub(super) fn emit<'tcx>(self, tcx: TyCtxt<'tcx>, pat_id: HirId) {
52+
pub(super) fn emit<'tcx>(
53+
self,
54+
tcx: TyCtxt<'tcx>,
55+
typeck_results: &'a TypeckResults<'tcx>,
56+
pat_id: HirId,
57+
) {
3758
let mut spans = MultiSpan::from_spans(self.labels.iter().map(|(span, _)| *span).collect());
3859
for (span, label) in self.labels {
3960
spans.push_span_label(*span, label.clone());
4061
}
41-
let sugg = Rust2024IncompatiblePatSugg {
42-
suggestion: self.suggestion,
43-
ref_pattern_count: self.ref_pattern_count,
44-
binding_mode_count: self.binding_mode_count,
45-
};
62+
let sugg = self.build_suggestion(typeck_results);
4663
// If a relevant span is from at least edition 2024, this is a hard error.
4764
let is_hard_error = spans.primary_spans().iter().any(|span| span.at_least_rust_2024());
4865
if is_hard_error {
@@ -64,38 +81,159 @@ impl<'a> PatMigration<'a> {
6481
}
6582
}
6683

67-
pub(super) fn visit_implicit_derefs<'tcx>(&mut self, pat_span: Span, adjustments: &[Ty<'tcx>]) {
68-
let suggestion_str: String = adjustments
69-
.iter()
70-
.map(|ref_ty| {
71-
let &ty::Ref(_, _, mutbl) = ref_ty.kind() else {
72-
span_bug!(pat_span, "pattern implicitly dereferences a non-ref type");
84+
fn build_suggestion(
85+
&self,
86+
typeck_results: &'a TypeckResults<'_>,
87+
) -> Rust2024IncompatiblePatSugg {
88+
if self.can_suggest_removing {
89+
// We can suggest a simple pattern by removing all explicit derefs and binding modes.
90+
let suggestion = self
91+
.explicit_modes
92+
.iter()
93+
.chain(&self.explicit_derefs)
94+
.map(|&removed_sp| (removed_sp, String::new()))
95+
.collect();
96+
Rust2024IncompatiblePatSugg {
97+
suggestion,
98+
kind: Rust2024IncompatiblePatSuggKind::Subtractive,
99+
ref_pattern_count: self.explicit_derefs.len(),
100+
binding_mode_count: self.explicit_modes.len(),
101+
}
102+
} else {
103+
// We can't suggest a simple pattern, so fully elaborate the pattern's match ergonomics.
104+
let modes = self.implicit_modes.iter().map(|&(sp, mutbl)| {
105+
let sugg_str = match mutbl {
106+
Mutability::Not => "ref ",
107+
Mutability::Mut => "ref mut ",
73108
};
109+
(sp, sugg_str.to_owned())
110+
});
111+
let mut ref_pattern_count = 0;
112+
let derefs = self.implicit_derefs.iter().map(|&(sp, hir_id)| {
113+
let adjustments = typeck_results.pat_adjustments().get(hir_id).unwrap();
114+
let ref_pat_str = adjustments
115+
.iter()
116+
.map(|ref_ty| {
117+
let &ty::Ref(_, _, mutbl) = ref_ty.kind() else {
118+
span_bug!(sp, "pattern implicitly dereferences a non-ref type");
119+
};
120+
121+
mutbl.ref_prefix_str()
122+
})
123+
.collect();
124+
ref_pattern_count += adjustments.len();
125+
(sp.shrink_to_lo(), ref_pat_str)
126+
});
127+
Rust2024IncompatiblePatSugg {
128+
suggestion: modes.chain(derefs).collect(),
129+
kind: Rust2024IncompatiblePatSuggKind::Additive,
130+
ref_pattern_count,
131+
binding_mode_count: self.implicit_modes.len(),
132+
}
133+
}
134+
}
135+
136+
/// The default binding mode at the current pattern, if all reference patterns were removed.
137+
fn default_mode(&self) -> ByRef {
138+
if self.current_ref_depth == 0 {
139+
ByRef::No
140+
} else {
141+
// If all `&` and `&mut` patterns are removed, the default binding mode's reference
142+
// mutability is mutable if and only if there are only `&mut` reference types.
143+
// See `FnCtxt::peel_off_references` in `rustc_hir_typeck::pat` for more information.
144+
let mutbl = if self.current_max_ref_mut_depth == self.current_ref_depth {
145+
Mutability::Mut
146+
} else {
147+
Mutability::Not
148+
};
149+
ByRef::Yes(mutbl)
150+
}
151+
}
152+
153+
/// Tracks when we're lowering a `&` or `&mut` pattern and adjusts the suggestion if necessary.
154+
/// This should be followed by a call to [`PatMigration::leave_ref`] when we leave the pattern.
155+
pub(super) fn visit_explicit_deref<'tcx>(
156+
&mut self,
157+
pat_span: Span,
158+
mutbl: Mutability,
159+
subpat: &'tcx hir::Pat<'tcx>,
160+
) {
161+
self.explicit_derefs.push(pat_span.with_hi(subpat.span.lo()));
162+
163+
// If the immediate subpattern is a binding, removing this reference pattern would change
164+
// its type, so we opt not to remove any, for simplicity.
165+
// FIXME(ref_pat_eat_one_layer_2024): This assumes ref pats can't eat the binding mode
166+
// alone. Depending on the pattern typing rules in use, we can be more precise here.
167+
if matches!(subpat.kind, hir::PatKind::Binding(_, _, _, _)) {
168+
self.can_suggest_removing = false;
169+
}
74170

75-
mutbl.ref_prefix_str()
171+
// Keep track of the reference depth for determining the default binding mode.
172+
if self.current_max_ref_mut_depth == self.current_ref_depth && mutbl.is_mut() {
173+
self.current_max_ref_mut_depth += 1;
174+
}
175+
self.current_ref_depth += 1;
176+
}
177+
178+
/// Tracks when we're lowering a pattern that implicitly dereferences the scrutinee.
179+
/// This should be followed by a call to [`PatMigration::leave_ref`] when we leave the pattern.
180+
pub(super) fn visit_implicit_derefs<'tcx>(
181+
&mut self,
182+
pat: &'tcx hir::Pat<'tcx>,
183+
adjustments: &[Ty<'tcx>],
184+
) {
185+
self.implicit_derefs.push((pat.span, pat.hir_id));
186+
187+
// Keep track of the reference depth for determining the default binding mode.
188+
if self.current_max_ref_mut_depth == self.current_ref_depth
189+
&& adjustments.iter().all(|ref_ty| {
190+
let &ty::Ref(_, _, mutbl) = ref_ty.kind() else {
191+
span_bug!(pat.span, "pattern implicitly dereferences a non-ref type");
192+
};
193+
mutbl.is_mut()
76194
})
77-
.collect();
78-
self.suggestion.push((pat_span.shrink_to_lo(), suggestion_str));
79-
self.ref_pattern_count += adjustments.len();
195+
{
196+
self.current_max_ref_mut_depth += 1;
197+
}
198+
self.current_ref_depth += 1;
199+
}
200+
201+
/// Tracks when we leave a reference (either implicitly or explicitly derefed) while lowering.
202+
/// This should follow a call to [`PatMigration::visit_explicit_deref`] or
203+
/// [`PatMigration::visit_implicit_derefs`].
204+
pub(super) fn leave_ref(&mut self) {
205+
if self.current_max_ref_mut_depth == self.current_ref_depth {
206+
self.current_max_ref_mut_depth -= 1;
207+
}
208+
self.current_ref_depth -= 1;
80209
}
81210

211+
/// Keeps track of bindings and adjusts the suggestion if necessary.
82212
pub(super) fn visit_binding(
83213
&mut self,
84214
pat_span: Span,
85215
mode: BindingMode,
86216
explicit_ba: BindingMode,
87217
ident: Ident,
88218
) {
219+
// Note any binding modes we may need to make explicit.
89220
if explicit_ba.0 == ByRef::No
90221
&& let ByRef::Yes(mutbl) = mode.0
91222
{
92-
let sugg_str = match mutbl {
93-
Mutability::Not => "ref ",
94-
Mutability::Mut => "ref mut ",
95-
};
96-
self.suggestion
97-
.push((pat_span.with_lo(ident.span.lo()).shrink_to_lo(), sugg_str.to_owned()));
98-
self.binding_mode_count += 1;
223+
self.implicit_modes.push((ident.span.shrink_to_lo(), mutbl));
224+
}
225+
226+
if self.can_suggest_removing {
227+
if mode == BindingMode(self.default_mode(), Mutability::Not) {
228+
// Note any binding modes we may need to make implicit.
229+
if matches!(explicit_ba.0, ByRef::Yes(_)) {
230+
self.explicit_modes.push(pat_span.with_hi(ident.span.lo()))
231+
}
232+
} else {
233+
// If removing reference patterns would change the mode of this binding, we opt not
234+
// to remove any, for simplicity.
235+
self.can_suggest_removing = false;
236+
}
99237
}
100238
}
101239
}

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

+20-5
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,22 @@ pub(super) fn pat_from_hir<'a, 'tcx>(
5555
let result = pcx.lower_pattern(pat);
5656
debug!("pat_from_hir({:?}) = {:?}", pat, result);
5757
if let Some(m) = pcx.rust_2024_migration {
58-
m.emit(tcx, pat.hir_id);
58+
m.emit(tcx, typeck_results, pat.hir_id);
5959
}
6060
result
6161
}
6262

6363
impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
6464
fn lower_pattern(&mut self, pat: &'tcx hir::Pat<'tcx>) -> Box<Pat<'tcx>> {
65+
let adjustments: &[Ty<'tcx>] =
66+
self.typeck_results.pat_adjustments().get(pat.hir_id).map_or(&[], |v| &**v);
67+
68+
if let Some(m) = &mut self.rust_2024_migration
69+
&& !adjustments.is_empty()
70+
{
71+
m.visit_implicit_derefs(pat, adjustments);
72+
}
73+
6574
// When implicit dereferences have been inserted in this pattern, the unadjusted lowered
6675
// pattern has the type that results *after* dereferencing. For example, in this code:
6776
//
@@ -90,8 +99,6 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
9099
_ => self.lower_pattern_unadjusted(pat),
91100
};
92101

93-
let adjustments: &[Ty<'tcx>] =
94-
self.typeck_results.pat_adjustments().get(pat.hir_id).map_or(&[], |v| &**v);
95102
let adjusted_pat = adjustments.iter().rev().fold(unadjusted_pat, |thir_pat, ref_ty| {
96103
debug!("{:?}: wrapping pattern with type {:?}", thir_pat, ref_ty);
97104
Box::new(Pat {
@@ -104,7 +111,7 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
104111
if let Some(m) = &mut self.rust_2024_migration
105112
&& !adjustments.is_empty()
106113
{
107-
m.visit_implicit_derefs(pat.span, adjustments);
114+
m.leave_ref();
108115
}
109116

110117
adjusted_pat
@@ -295,7 +302,15 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
295302
let mutability = if mutable { hir::Mutability::Mut } else { hir::Mutability::Not };
296303
PatKind::DerefPattern { subpattern: self.lower_pattern(subpattern), mutability }
297304
}
298-
hir::PatKind::Ref(subpattern, _) | hir::PatKind::Box(subpattern) => {
305+
hir::PatKind::Ref(subpattern, mutbl) => {
306+
if let Some(m) = &mut self.rust_2024_migration {
307+
m.visit_explicit_deref(pat.span, mutbl, subpattern)
308+
}
309+
let subpattern = self.lower_pattern(subpattern);
310+
self.rust_2024_migration.as_mut().map(|m| m.leave_ref());
311+
PatKind::Deref { subpattern }
312+
}
313+
hir::PatKind::Box(subpattern) => {
299314
PatKind::Deref { subpattern: self.lower_pattern(subpattern) }
300315
}
301316

tests/ui/pattern/rfc-3627-match-ergonomics-2024/experimental/ref-binding-on-inh-ref-errors.classic2024.stderr

+8-6
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,11 @@ LL | let [ref x] = &[0];
3535
| ^^^ cannot override to bind by-reference when that is the implicit default
3636
|
3737
= note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/match-ergonomics.html>
38-
help: make the implied reference pattern explicit
38+
help: make the variable binding mode implicit
39+
|
40+
LL - let [ref x] = &[0];
41+
LL + let [x] = &[0];
3942
|
40-
LL | let &[ref x] = &[0];
41-
| +
4243

4344
error: this pattern relies on behavior which may change in edition 2024
4445
--> $DIR/ref-binding-on-inh-ref-errors.rs:88:10
@@ -59,10 +60,11 @@ LL | let [ref mut x] = &mut [0];
5960
| ^^^^^^^ cannot override to bind by-reference when that is the implicit default
6061
|
6162
= note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/match-ergonomics.html>
62-
help: make the implied reference pattern explicit
63+
help: make the variable binding mode implicit
64+
|
65+
LL - let [ref mut x] = &mut [0];
66+
LL + let [x] = &mut [0];
6367
|
64-
LL | let &mut [ref mut x] = &mut [0];
65-
| ++++
6668

6769
error: aborting due to 6 previous errors
6870

0 commit comments

Comments
 (0)