Skip to content

Commit 6a7f026

Browse files
committed
separate suggestion building from default binding mode tracking
This lets us revise the suggestion on-the-fly as we discover which reference patterns and binding modifiers are necessary vs. which may be omitted.
1 parent 26ff417 commit 6a7f026

File tree

3 files changed

+141
-62
lines changed

3 files changed

+141
-62
lines changed

compiler/rustc_mir_build/src/errors.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -1098,34 +1098,34 @@ pub(crate) enum MiscPatternSuggestion {
10981098

10991099
#[derive(LintDiagnostic)]
11001100
#[diag(mir_build_rust_2024_incompatible_pat)]
1101-
pub(crate) struct Rust2024IncompatiblePat {
1101+
pub(crate) struct Rust2024IncompatiblePat<'m> {
11021102
#[subdiagnostic]
1103-
pub(crate) sugg: Rust2024IncompatiblePatSugg,
1103+
pub(crate) sugg: Rust2024IncompatiblePatSugg<'m>,
11041104
pub(crate) bad_modifiers: bool,
11051105
pub(crate) bad_ref_pats: bool,
11061106
pub(crate) is_hard_error: bool,
11071107
}
11081108

1109-
pub(crate) struct Rust2024IncompatiblePatSugg {
1109+
pub(crate) struct Rust2024IncompatiblePatSugg<'m> {
11101110
/// If true, our suggestion is to elide explicit binding modifiers.
11111111
/// If false, our suggestion is to make the pattern fully explicit.
11121112
pub(crate) suggest_eliding_modes: bool,
11131113
pub(crate) suggestion: Vec<(Span, String)>,
11141114
pub(crate) ref_pattern_count: usize,
11151115
pub(crate) binding_mode_count: usize,
11161116
/// Labels for where incompatibility-causing by-ref default binding modes were introduced.
1117-
pub(crate) default_mode_labels: FxIndexMap<Span, ty::Mutability>,
1117+
pub(crate) default_mode_labels: &'m FxIndexMap<Span, ty::Mutability>,
11181118
}
11191119

1120-
impl Subdiagnostic for Rust2024IncompatiblePatSugg {
1120+
impl<'m> Subdiagnostic for Rust2024IncompatiblePatSugg<'m> {
11211121
fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
11221122
self,
11231123
diag: &mut Diag<'_, G>,
11241124
_f: &F,
11251125
) {
11261126
// Format and emit explanatory notes about default binding modes. Reversing the spans' order
11271127
// means if we have nested spans, the innermost ones will be visited first.
1128-
for (span, def_br_mutbl) in self.default_mode_labels.into_iter().rev() {
1128+
for (&span, &def_br_mutbl) in self.default_mode_labels.iter().rev() {
11291129
// Don't point to a macro call site.
11301130
if !span.from_expansion() {
11311131
let note_msg = "matching on a reference type with a non-reference pattern changes the default binding mode";

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

+133-54
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
use rustc_data_structures::fx::FxIndexMap;
44
use rustc_errors::MultiSpan;
5-
use rustc_hir::{BindingMode, ByRef, HirId, Mutability};
5+
use rustc_hir::{self as hir, BindingMode, ByRef, HirId, Mutability};
66
use rustc_index::IndexVec;
77
use rustc_lint as lint;
88
use rustc_middle::span_bug;
@@ -14,13 +14,13 @@ use crate::fluent_generated as fluent;
1414

1515
/// For patterns flagged for migration during HIR typeck, this handles constructing and emitting
1616
/// a diagnostic suggestion.
17-
pub(super) struct PatMigration<'a> {
18-
suggestion: Vec<(Span, String)>,
19-
ref_pattern_count: usize,
20-
binding_mode_count: usize,
17+
pub(super) struct PatMigration<'a, 'tcx> {
18+
/// All the variable bindings encountered in lowering the pattern, along with whether to
19+
/// suggest adding/removing them.
20+
bindings: IndexVec<PatBindingIdx, PatBinding>,
2121
/// 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>,
22+
/// patterns affect the default binding mode, and whether to suggest adding/removing them.
23+
derefs: IndexVec<PatDerefIdx, PatDeref<'a, 'tcx>>,
2424
/// Internal state: the innermost deref above the pattern currently being lowered.
2525
innermost_deref: Option<PatDerefIdx>,
2626
/// Labels for where incompatibility-causing by-ref default binding modes were introduced.
@@ -33,11 +33,31 @@ pub(super) struct PatMigration<'a> {
3333
info: &'a Rust2024IncompatiblePatInfo,
3434
}
3535

36+
rustc_index::newtype_index! {
37+
struct PatBindingIdx {}
38+
}
39+
3640
rustc_index::newtype_index! {
3741
struct PatDerefIdx {}
3842
}
3943

40-
struct PatDeref {
44+
struct PatBinding {
45+
/// The span of the binding modifier (empty if no explicit modifier was provided).
46+
span: Span,
47+
/// The actual binding mode of this binding.
48+
mode: BindingMode,
49+
/// Whether to include a binding modifier (e.g. `ref` or `mut`) in the suggested pattern.
50+
suggest: bool,
51+
}
52+
53+
struct PatDeref<'a, 'tcx> {
54+
/// The span of the pattern where this deref occurs (implicitly or explicitly).
55+
span: Span,
56+
/// Whether this span is for a potentially-removable explicitly-provided deref, or an implicit
57+
/// dereference which we can potentially suggest making explicit.
58+
kind: PatDerefKind<'a, 'tcx>,
59+
/// Whether to include this as a `&` or `&mut` in the suggested pattern.
60+
suggest: bool,
4161
/// The default binding mode for variables under this deref.
4262
real_default_mode: ByRef,
4363
/// The span that introduced the current default binding mode, or `None` for the top-level pat.
@@ -50,12 +70,32 @@ struct PatDeref {
5070
parent: Option<PatDerefIdx>,
5171
}
5272

53-
impl<'a> PatMigration<'a> {
73+
enum PatDerefKind<'a, 'tcx> {
74+
/// For dereferences from lowering `&` and `&mut` patterns
75+
Explicit,
76+
/// For dereferences inserted by match ergonomics
77+
Implicit { ref_tys: &'a [Ty<'tcx>] },
78+
}
79+
80+
/// Assuming the input is a slice of reference types implicitly dereferenced by match ergonomics
81+
/// (stored in [`ty::TypeckResults::pat_adjustments`]), iterate over their reference mutabilities.
82+
/// A span is provided for debugging purposes.
83+
fn iter_ref_mutbls<'a, 'tcx>(
84+
span: Span,
85+
ref_tys: &'a [Ty<'tcx>],
86+
) -> impl Iterator<Item = Mutability> + use<'a, 'tcx> {
87+
ref_tys.iter().map(move |ref_ty| {
88+
let &ty::Ref(_, _, mutbl) = ref_ty.kind() else {
89+
span_bug!(span, "pattern implicitly dereferences a non-ref type");
90+
};
91+
mutbl
92+
})
93+
}
94+
95+
impl<'a, 'tcx> PatMigration<'a, 'tcx> {
5496
pub(super) fn new(info: &'a Rust2024IncompatiblePatInfo) -> Self {
5597
PatMigration {
56-
suggestion: Vec::new(),
57-
ref_pattern_count: 0,
58-
binding_mode_count: 0,
98+
bindings: IndexVec::new(),
5999
derefs: IndexVec::new(),
60100
innermost_deref: None,
61101
default_mode_labels: Default::default(),
@@ -65,19 +105,13 @@ impl<'a> PatMigration<'a> {
65105

66106
/// On Rust 2024, this emits a hard error. On earlier Editions, this emits the
67107
/// future-incompatibility lint `rust_2024_incompatible_pat`.
68-
pub(super) fn emit<'tcx>(self, tcx: TyCtxt<'tcx>, pat_id: HirId) {
108+
pub(super) fn emit(self, tcx: TyCtxt<'tcx>, pat_id: HirId) {
69109
let mut spans =
70110
MultiSpan::from_spans(self.info.primary_labels.iter().map(|(span, _)| *span).collect());
71111
for (span, label) in self.info.primary_labels.iter() {
72112
spans.push_span_label(*span, label.clone());
73113
}
74-
let sugg = Rust2024IncompatiblePatSugg {
75-
suggest_eliding_modes: self.info.suggest_eliding_modes,
76-
suggestion: self.suggestion,
77-
ref_pattern_count: self.ref_pattern_count,
78-
binding_mode_count: self.binding_mode_count,
79-
default_mode_labels: self.default_mode_labels,
80-
};
114+
let sugg = self.build_suggestion();
81115
// If a relevant span is from at least edition 2024, this is a hard error.
82116
let is_hard_error = spans.primary_spans().iter().any(|span| span.at_least_rust_2024());
83117
if is_hard_error {
@@ -126,6 +160,61 @@ impl<'a> PatMigration<'a> {
126160
}
127161
}
128162

163+
fn build_suggestion<'m>(&'m self) -> Rust2024IncompatiblePatSugg<'m> {
164+
let mut removed_modifiers = 0;
165+
let mut added_modifiers = 0;
166+
let modes = self.bindings.iter().filter_map(|binding| {
167+
if binding.mode == BindingMode::NONE {
168+
// This binding mode is written as the empty string; no need to suggest.
169+
None
170+
} else {
171+
if !binding.suggest && !binding.span.is_empty() {
172+
// This binding is in the source but not the suggestion; suggest removing it.
173+
removed_modifiers += 1;
174+
Some((binding.span, String::new()))
175+
} else if binding.suggest && binding.span.is_empty() {
176+
// This binding is in the suggestion but not the source; suggest adding it.
177+
added_modifiers += 1;
178+
Some((binding.span, binding.mode.prefix_str().to_owned()))
179+
} else {
180+
// This binding is as it should be.
181+
None
182+
}
183+
}
184+
});
185+
186+
let mut added_ref_pats = 0;
187+
let derefs = self.derefs.iter().filter_map(|deref| match deref.kind {
188+
PatDerefKind::Explicit if !deref.suggest => {
189+
// This is a ref pattern in the source but not the suggestion; suggest removing it.
190+
// TODO: we don't yet suggest removing reference patterns
191+
todo!();
192+
}
193+
PatDerefKind::Implicit { ref_tys } if deref.suggest => {
194+
// This is a ref pattern in the suggestion but not the source; suggest adding it.
195+
let ref_pat_str =
196+
iter_ref_mutbls(deref.span, ref_tys).map(Mutability::ref_prefix_str).collect();
197+
added_ref_pats += ref_tys.len();
198+
Some((deref.span.shrink_to_lo(), ref_pat_str))
199+
}
200+
_ => None,
201+
});
202+
203+
let suggestion = modes.chain(derefs).collect();
204+
let binding_mode_count = if added_modifiers == 0 && added_ref_pats == 0 {
205+
removed_modifiers
206+
} else {
207+
added_modifiers
208+
};
209+
Rust2024IncompatiblePatSugg {
210+
suggest_eliding_modes: self.info.suggest_eliding_modes,
211+
suggestion,
212+
binding_mode_count,
213+
ref_pattern_count: added_ref_pats,
214+
default_mode_labels: &self.default_mode_labels,
215+
}
216+
}
217+
129218
/// The default binding mode at the current pattern.
130219
fn real_default_mode(&self) -> ByRef {
131220
if let Some(current_ix) = self.innermost_deref {
@@ -136,32 +225,17 @@ impl<'a> PatMigration<'a> {
136225
}
137226

138227
/// Tracks when we're lowering a pattern that implicitly dereferences the scrutinee.
139-
/// This should only be called when the pattern type adjustments list `adjustments` is
140-
/// non-empty.
228+
/// This should only be called when the pattern type adjustments list `ref_tys` is non-empty.
141229
/// 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>]) {
143-
let implicit_deref_mutbls = adjustments.iter().map(|ref_ty| {
144-
let &ty::Ref(_, _, mutbl) = ref_ty.kind() else {
145-
span_bug!(pat_span, "pattern implicitly dereferences a non-ref type");
146-
};
147-
mutbl
148-
});
149-
150-
if !self.info.suggest_eliding_modes {
151-
// If we can't fix the pattern by eliding modifiers, we'll need to make the pattern
152-
// fully explicit. i.e. we'll need to suggest reference patterns for this.
153-
let suggestion_str: String =
154-
implicit_deref_mutbls.clone().map(|mutbl| mutbl.ref_prefix_str()).collect();
155-
self.suggestion.push((pat_span.shrink_to_lo(), suggestion_str));
156-
self.ref_pattern_count += adjustments.len();
157-
}
158-
230+
pub(super) fn visit_implicit_derefs(&mut self, pat: &hir::Pat<'_>, ref_tys: &'a [Ty<'tcx>]) {
159231
// Remember if this changed the default binding mode, in case we want to label it.
160232
let new_real_ref_mutbl = match self.real_default_mode() {
161233
ByRef::Yes(Mutability::Not) => Mutability::Not,
162-
_ => implicit_deref_mutbls.min().unwrap(),
234+
_ => iter_ref_mutbls(pat.span, ref_tys).min().unwrap(),
163235
};
164-
self.push_deref(pat_span, ByRef::Yes(new_real_ref_mutbl));
236+
self.push_deref(pat.span, ByRef::Yes(new_real_ref_mutbl), PatDerefKind::Implicit {
237+
ref_tys,
238+
});
165239
}
166240

167241
/// Tracks the default binding mode when we're lowering a `&` or `&mut` pattern.
@@ -174,12 +248,12 @@ impl<'a> PatMigration<'a> {
174248
// If this eats a by-ref default binding mode, label the binding mode.
175249
self.add_default_mode_label_if_needed();
176250
// Set the default binding mode to by-value.
177-
self.push_deref(pat_span, ByRef::No);
251+
self.push_deref(pat_span, ByRef::No, PatDerefKind::Explicit);
178252
}
179253

180254
/// Adds a deref to our deref-forest, so that we can track the default binding mode.
181255
// 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) {
256+
fn push_deref(&mut self, span: Span, real_default_mode: ByRef, kind: PatDerefKind<'a, 'tcx>) {
183257
let parent = self.innermost_deref;
184258
// If this keeps the default binding mode the same, it shares a mode origin with its
185259
// parent. If it changes the default binding mode, its mode origin is itself.
@@ -188,7 +262,15 @@ impl<'a> PatMigration<'a> {
188262
} else {
189263
Some(span)
190264
};
191-
let my_ix = self.derefs.push(PatDeref { real_default_mode, default_mode_origin, parent });
265+
let my_ix = self.derefs.push(PatDeref {
266+
span,
267+
suggest: !self.info.suggest_eliding_modes
268+
|| matches!(kind, PatDerefKind::Explicit { .. }),
269+
kind,
270+
real_default_mode,
271+
default_mode_origin,
272+
parent,
273+
});
192274
self.innermost_deref = Some(my_ix);
193275
}
194276

@@ -217,23 +299,20 @@ impl<'a> PatMigration<'a> {
217299
self.add_default_mode_label_if_needed();
218300
if self.info.suggest_eliding_modes && matches!(mode.0, ByRef::Yes(_)) {
219301
// If our suggestion is to elide redundant modes, this will be one of them.
220-
self.suggestion.push((pat_span.with_hi(ident.span.lo()), String::new()));
221-
self.binding_mode_count += 1;
302+
self.bindings.push(PatBinding {
303+
span: pat_span.with_hi(ident.span.lo()),
304+
mode,
305+
suggest: false,
306+
});
222307
}
223308
}
224309
if !self.info.suggest_eliding_modes
225310
&& explicit_ba.0 == ByRef::No
226-
&& let ByRef::Yes(mutbl) = mode.0
311+
&& matches!(mode.0, ByRef::Yes(_))
227312
{
228313
// If we can't fix the pattern by eliding modifiers, we'll need to make the pattern
229314
// fully explicit. i.e. we'll need to suggest reference patterns for this.
230-
let sugg_str = match mutbl {
231-
Mutability::Not => "ref ",
232-
Mutability::Mut => "ref mut ",
233-
};
234-
self.suggestion
235-
.push((pat_span.with_lo(ident.span.lo()).shrink_to_lo(), sugg_str.to_owned()));
236-
self.binding_mode_count += 1;
315+
self.bindings.push(PatBinding { span: pat_span.shrink_to_lo(), mode, suggest: true });
237316
}
238317
}
239318
}

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ struct PatCtxt<'a, 'tcx> {
3434
typeck_results: &'a ty::TypeckResults<'tcx>,
3535

3636
/// Used by the Rust 2024 migration lint.
37-
rust_2024_migration: Option<PatMigration<'a>>,
37+
rust_2024_migration: Option<PatMigration<'a, 'tcx>>,
3838
}
3939

4040
pub(super) fn pat_from_hir<'a, 'tcx>(
@@ -69,7 +69,7 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
6969
if let Some(s) = &mut self.rust_2024_migration
7070
&& !adjustments.is_empty()
7171
{
72-
s.visit_implicit_derefs(pat.span, adjustments);
72+
s.visit_implicit_derefs(pat, adjustments);
7373
}
7474

7575
// When implicit dereferences have been inserted in this pattern, the unadjusted lowered

0 commit comments

Comments
 (0)