Skip to content

Commit 95a697e

Browse files
committed
track patterns' dereferences in a forest
This will let us propagate changes to the default binding mode when suggesting adding `&` patterns.
1 parent 9964b95 commit 95a697e

File tree

2 files changed

+101
-44
lines changed

2 files changed

+101
-44
lines changed

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

Lines changed: 95 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use rustc_data_structures::fx::FxIndexMap;
44
use rustc_errors::MultiSpan;
55
use rustc_hir::{BindingMode, ByRef, HirId, Mutability};
6+
use rustc_index::IndexVec;
67
use rustc_lint as lint;
78
use rustc_middle::span_bug;
89
use rustc_middle::ty::{self, Rust2024IncompatiblePatInfo, Ty, TyCtxt};
@@ -17,9 +18,11 @@ pub(super) struct PatMigration<'a> {
1718
suggestion: Vec<(Span, String)>,
1819
ref_pattern_count: usize,
1920
binding_mode_count: usize,
20-
/// Internal state: the ref-mutability of the default binding mode at the subpattern being
21-
/// lowered, with the span where it was introduced. `None` for a by-value default mode.
22-
default_mode_span: Option<(Span, ty::Mutability)>,
21+
/// All the dereferences encountered in lowering the pattern, along with how their corresponding
22+
/// patterns affect the default binding mode.
23+
derefs: IndexVec<PatDerefIdx, PatDeref>,
24+
/// Internal state: the innermost deref above the pattern currently being lowered.
25+
innermost_deref: Option<PatDerefIdx>,
2326
/// Labels for where incompatibility-causing by-ref default binding modes were introduced.
2427
// FIXME(ref_pat_eat_one_layer_2024_structural): To track the default binding mode, we duplicate
2528
// logic from HIR typeck (in order to avoid needing to store all changes to the dbm in
@@ -30,13 +33,31 @@ pub(super) struct PatMigration<'a> {
3033
info: &'a Rust2024IncompatiblePatInfo,
3134
}
3235

36+
rustc_index::newtype_index! {
37+
struct PatDerefIdx {}
38+
}
39+
40+
struct PatDeref {
41+
/// The default binding mode for variables under this deref.
42+
real_default_mode: ByRef,
43+
/// The span that introduced the current default binding mode, or `None` for the top-level pat.
44+
default_mode_origin: Option<Span>,
45+
/// The next deref above this. Since we can't suggest using `&` or `&mut` on a by-ref default
46+
/// binding mode, a suggested deref's ancestors must also all be suggested.
47+
// FIXME(ref_pat_eat_one_layer_2024): By suggesting `&` and `&mut` patterns that can eat the
48+
// default binding mode, we'll be able to make more local suggestions. That may make this forest
49+
// structure unnecessary.
50+
parent: Option<PatDerefIdx>,
51+
}
52+
3353
impl<'a> PatMigration<'a> {
3454
pub(super) fn new(info: &'a Rust2024IncompatiblePatInfo) -> Self {
3555
PatMigration {
3656
suggestion: Vec::new(),
3757
ref_pattern_count: 0,
3858
binding_mode_count: 0,
39-
default_mode_span: None,
59+
derefs: IndexVec::new(),
60+
innermost_deref: None,
4061
default_mode_labels: Default::default(),
4162
info,
4263
}
@@ -86,15 +107,39 @@ impl<'a> PatMigration<'a> {
86107
}
87108
}
88109

110+
/// When lowering a reference pattern or a binding with a modifier, this checks if the default
111+
/// binding mode is by-ref, and if so, adds a labeled note to the diagnostic with the origin of
112+
/// the current default binding mode.
113+
fn add_default_mode_label_if_needed(&mut self) {
114+
if let ByRef::Yes(ref_mutbl) = self.real_default_mode() {
115+
// The by-ref default binding mode must have come from an implicit deref. If there was a
116+
// problem in tracking that for the diagnostic, try to avoid ICE on release builds.
117+
debug_assert!(
118+
self.innermost_deref
119+
.is_some_and(|ix| self.derefs[ix].default_mode_origin.is_some())
120+
);
121+
if let Some(ix) = self.innermost_deref
122+
&& let Some(span) = self.derefs[ix].default_mode_origin
123+
{
124+
self.default_mode_labels.insert(span, ref_mutbl);
125+
}
126+
}
127+
}
128+
129+
/// The default binding mode at the current pattern.
130+
fn real_default_mode(&self) -> ByRef {
131+
if let Some(current_ix) = self.innermost_deref {
132+
self.derefs[current_ix].real_default_mode
133+
} else {
134+
ByRef::No
135+
}
136+
}
137+
89138
/// Tracks when we're lowering a pattern that implicitly dereferences the scrutinee.
90139
/// This should only be called when the pattern type adjustments list `adjustments` is
91-
/// non-empty. Returns the prior default binding mode; this should be followed by a call to
92-
/// [`PatMigration::leave_ref`] to restore it when we leave the pattern.
93-
pub(super) fn visit_implicit_derefs<'tcx>(
94-
&mut self,
95-
pat_span: Span,
96-
adjustments: &[Ty<'tcx>],
97-
) -> Option<(Span, Mutability)> {
140+
/// non-empty.
141+
/// This should be followed by a call to [`PatMigration::leave_ref`] when we leave the pattern.
142+
pub(super) fn visit_implicit_derefs<'tcx>(&mut self, pat_span: Span, adjustments: &[Ty<'tcx>]) {
98143
let implicit_deref_mutbls = adjustments.iter().map(|ref_ty| {
99144
let &ty::Ref(_, _, mutbl) = ref_ty.kind() else {
100145
span_bug!(pat_span, "pattern implicitly dereferences a non-ref type");
@@ -112,35 +157,49 @@ impl<'a> PatMigration<'a> {
112157
}
113158

114159
// Remember if this changed the default binding mode, in case we want to label it.
115-
let min_mutbl = implicit_deref_mutbls.min().unwrap();
116-
if self.default_mode_span.is_none_or(|(_, old_mutbl)| min_mutbl < old_mutbl) {
117-
// This changes the default binding mode to `ref` or `ref mut`. Return the old mode so
118-
// it can be reinstated when we leave the pattern.
119-
self.default_mode_span.replace((pat_span, min_mutbl))
120-
} else {
121-
// This does not change the default binding mode; it was already `ref` or `ref mut`.
122-
self.default_mode_span
123-
}
160+
let new_real_ref_mutbl = match self.real_default_mode() {
161+
ByRef::Yes(Mutability::Not) => Mutability::Not,
162+
_ => implicit_deref_mutbls.min().unwrap(),
163+
};
164+
self.push_deref(pat_span, ByRef::Yes(new_real_ref_mutbl));
124165
}
125166

126167
/// Tracks the default binding mode when we're lowering a `&` or `&mut` pattern.
127-
/// Returns the prior default binding mode; this should be followed by a call to
128-
/// [`PatMigration::leave_ref`] to restore it when we leave the pattern.
129-
pub(super) fn visit_explicit_deref(&mut self) -> Option<(Span, Mutability)> {
130-
if let Some((default_mode_span, default_ref_mutbl)) = self.default_mode_span {
131-
// If this eats a by-ref default binding mode, label the binding mode.
132-
self.default_mode_labels.insert(default_mode_span, default_ref_mutbl);
133-
}
134-
// Set the default binding mode to by-value and return the old default binding mode so it
135-
// can be reinstated when we leave the pattern.
136-
self.default_mode_span.take()
168+
/// This should be followed by a call to [`PatMigration::leave_ref`] when we leave the pattern.
169+
// FIXME(ref_pat_eat_one_layer_2024): This assumes reference patterns correspond to real
170+
// dereferences. If reference patterns can match the default binding mode alone, we may need to
171+
// check `TypeckResults::skipped_ref_pats` to tell if this pattern corresponds to an implicit
172+
// dereference we've already visited.
173+
pub(super) fn visit_explicit_deref(&mut self, pat_span: Span) {
174+
// If this eats a by-ref default binding mode, label the binding mode.
175+
self.add_default_mode_label_if_needed();
176+
// Set the default binding mode to by-value.
177+
self.push_deref(pat_span, ByRef::No);
178+
}
179+
180+
/// Adds a deref to our deref-forest, so that we can track the default binding mode.
181+
// TODO: this is also for propagating binding mode changes when we suggest adding patterns
182+
fn push_deref(&mut self, span: Span, real_default_mode: ByRef) {
183+
let parent = self.innermost_deref;
184+
// If this keeps the default binding mode the same, it shares a mode origin with its
185+
// parent. If it changes the default binding mode, its mode origin is itself.
186+
let default_mode_origin = if real_default_mode == self.real_default_mode() {
187+
parent.and_then(|p| self.derefs[p].default_mode_origin)
188+
} else {
189+
Some(span)
190+
};
191+
let my_ix = self.derefs.push(PatDeref { real_default_mode, default_mode_origin, parent });
192+
self.innermost_deref = Some(my_ix);
137193
}
138194

139195
/// Restores the default binding mode after lowering a pattern that could change it.
140196
/// This should follow a call to either [`PatMigration::visit_explicit_deref`] or
141197
/// [`PatMigration::visit_implicit_derefs`].
142-
pub(super) fn leave_ref(&mut self, old_mode_span: Option<(Span, Mutability)>) {
143-
self.default_mode_span = old_mode_span
198+
pub(super) fn leave_ref(&mut self) {
199+
debug_assert!(self.innermost_deref.is_some(), "entering/leaving refs should be paired");
200+
if let Some(child_ix) = self.innermost_deref {
201+
self.innermost_deref = self.derefs[child_ix].parent;
202+
}
144203
}
145204

146205
/// Determines if a binding is relevant to the diagnostic and adjusts the notes/suggestion if
@@ -153,13 +212,11 @@ impl<'a> PatMigration<'a> {
153212
explicit_ba: BindingMode,
154213
ident: Ident,
155214
) {
156-
if explicit_ba != BindingMode::NONE
157-
&& let Some((default_mode_span, default_ref_mutbl)) = self.default_mode_span
158-
{
215+
if explicit_ba != BindingMode::NONE {
159216
// If this overrides a by-ref default binding mode, label the binding mode.
160-
self.default_mode_labels.insert(default_mode_span, default_ref_mutbl);
161-
// If our suggestion is to elide redundnt modes, this will be one of them.
162-
if self.info.suggest_eliding_modes {
217+
self.add_default_mode_label_if_needed();
218+
if self.info.suggest_eliding_modes && matches!(mode.0, ByRef::Yes(_)) {
219+
// If our suggestion is to elide redundant modes, this will be one of them.
163220
self.suggestion.push((pat_span.with_hi(ident.span.lo()), String::new()));
164221
self.binding_mode_count += 1;
165222
}

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,10 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
6666
self.typeck_results.pat_adjustments().get(pat.hir_id).map_or(&[], |v| &**v);
6767

6868
// Track the default binding mode for the Rust 2024 migration suggestion.
69-
let mut opt_old_mode_span = None;
7069
if let Some(s) = &mut self.rust_2024_migration
7170
&& !adjustments.is_empty()
7271
{
73-
opt_old_mode_span = s.visit_implicit_derefs(pat.span, adjustments);
72+
s.visit_implicit_derefs(pat.span, adjustments);
7473
}
7574

7675
// When implicit dereferences have been inserted in this pattern, the unadjusted lowered
@@ -113,7 +112,7 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
113112
if let Some(s) = &mut self.rust_2024_migration
114113
&& !adjustments.is_empty()
115114
{
116-
s.leave_ref(opt_old_mode_span);
115+
s.leave_ref();
117116
}
118117

119118
adjusted_pat
@@ -309,11 +308,12 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
309308
}
310309
hir::PatKind::Ref(subpattern, _) => {
311310
// Track the default binding mode for the Rust 2024 migration suggestion.
312-
let opt_old_mode_span =
313-
self.rust_2024_migration.as_mut().and_then(|s| s.visit_explicit_deref());
311+
if let Some(s) = &mut self.rust_2024_migration {
312+
s.visit_explicit_deref(pat.span);
313+
}
314314
let subpattern = self.lower_pattern(subpattern);
315315
if let Some(s) = &mut self.rust_2024_migration {
316-
s.leave_ref(opt_old_mode_span);
316+
s.leave_ref();
317317
}
318318
PatKind::Deref { subpattern }
319319
}

0 commit comments

Comments
 (0)