2
2
3
3
use rustc_data_structures:: fx:: FxIndexMap ;
4
4
use rustc_errors:: MultiSpan ;
5
- use rustc_hir:: { BindingMode , ByRef , HirId , Mutability } ;
5
+ use rustc_hir:: { self as hir , BindingMode , ByRef , HirId , Mutability } ;
6
6
use rustc_index:: IndexVec ;
7
7
use rustc_lint as lint;
8
8
use rustc_middle:: span_bug;
@@ -14,13 +14,13 @@ use crate::fluent_generated as fluent;
14
14
15
15
/// For patterns flagged for migration during HIR typeck, this handles constructing and emitting
16
16
/// 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 > ,
21
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 > ,
22
+ /// patterns affect the default binding mode, and whether to suggest adding/removing them .
23
+ derefs : IndexVec < PatDerefIdx , PatDeref < ' a , ' tcx > > ,
24
24
/// Internal state: the innermost deref above the pattern currently being lowered.
25
25
innermost_deref : Option < PatDerefIdx > ,
26
26
/// Labels for where incompatibility-causing by-ref default binding modes were introduced.
@@ -33,11 +33,31 @@ pub(super) struct PatMigration<'a> {
33
33
info : & ' a Rust2024IncompatiblePatInfo ,
34
34
}
35
35
36
+ rustc_index:: newtype_index! {
37
+ struct PatBindingIdx { }
38
+ }
39
+
36
40
rustc_index:: newtype_index! {
37
41
struct PatDerefIdx { }
38
42
}
39
43
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 ,
41
61
/// The default binding mode for variables under this deref.
42
62
real_default_mode : ByRef ,
43
63
/// The span that introduced the current default binding mode, or `None` for the top-level pat.
@@ -50,12 +70,32 @@ struct PatDeref {
50
70
parent : Option < PatDerefIdx > ,
51
71
}
52
72
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 > {
54
96
pub ( super ) fn new ( info : & ' a Rust2024IncompatiblePatInfo ) -> Self {
55
97
PatMigration {
56
- suggestion : Vec :: new ( ) ,
57
- ref_pattern_count : 0 ,
58
- binding_mode_count : 0 ,
98
+ bindings : IndexVec :: new ( ) ,
59
99
derefs : IndexVec :: new ( ) ,
60
100
innermost_deref : None ,
61
101
default_mode_labels : Default :: default ( ) ,
@@ -65,19 +105,13 @@ impl<'a> PatMigration<'a> {
65
105
66
106
/// On Rust 2024, this emits a hard error. On earlier Editions, this emits the
67
107
/// 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 ) {
69
109
let mut spans =
70
110
MultiSpan :: from_spans ( self . info . primary_labels . iter ( ) . map ( |( span, _) | * span) . collect ( ) ) ;
71
111
for ( span, label) in self . info . primary_labels . iter ( ) {
72
112
spans. push_span_label ( * span, label. clone ( ) ) ;
73
113
}
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 ( ) ;
81
115
// If a relevant span is from at least edition 2024, this is a hard error.
82
116
let is_hard_error = spans. primary_spans ( ) . iter ( ) . any ( |span| span. at_least_rust_2024 ( ) ) ;
83
117
if is_hard_error {
@@ -126,6 +160,61 @@ impl<'a> PatMigration<'a> {
126
160
}
127
161
}
128
162
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
+
129
218
/// The default binding mode at the current pattern.
130
219
fn real_default_mode ( & self ) -> ByRef {
131
220
if let Some ( current_ix) = self . innermost_deref {
@@ -136,32 +225,17 @@ impl<'a> PatMigration<'a> {
136
225
}
137
226
138
227
/// 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.
141
229
/// 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 > ] ) {
159
231
// Remember if this changed the default binding mode, in case we want to label it.
160
232
let new_real_ref_mutbl = match self . real_default_mode ( ) {
161
233
ByRef :: Yes ( Mutability :: Not ) => Mutability :: Not ,
162
- _ => implicit_deref_mutbls . min ( ) . unwrap ( ) ,
234
+ _ => iter_ref_mutbls ( pat . span , ref_tys ) . min ( ) . unwrap ( ) ,
163
235
} ;
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
+ } ) ;
165
239
}
166
240
167
241
/// Tracks the default binding mode when we're lowering a `&` or `&mut` pattern.
@@ -174,12 +248,12 @@ impl<'a> PatMigration<'a> {
174
248
// If this eats a by-ref default binding mode, label the binding mode.
175
249
self . add_default_mode_label_if_needed ( ) ;
176
250
// 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 ) ;
178
252
}
179
253
180
254
/// Adds a deref to our deref-forest, so that we can track the default binding mode.
181
255
// 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 > ) {
183
257
let parent = self . innermost_deref ;
184
258
// If this keeps the default binding mode the same, it shares a mode origin with its
185
259
// parent. If it changes the default binding mode, its mode origin is itself.
@@ -188,7 +262,15 @@ impl<'a> PatMigration<'a> {
188
262
} else {
189
263
Some ( span)
190
264
} ;
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
+ } ) ;
192
274
self . innermost_deref = Some ( my_ix) ;
193
275
}
194
276
@@ -217,23 +299,20 @@ impl<'a> PatMigration<'a> {
217
299
self . add_default_mode_label_if_needed ( ) ;
218
300
if self . info . suggest_eliding_modes && matches ! ( mode. 0 , ByRef :: Yes ( _) ) {
219
301
// 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
+ } ) ;
222
307
}
223
308
}
224
309
if !self . info . suggest_eliding_modes
225
310
&& explicit_ba. 0 == ByRef :: No
226
- && let ByRef :: Yes ( mutbl ) = mode . 0
311
+ && matches ! ( mode . 0 , ByRef :: Yes ( _ ) )
227
312
{
228
313
// If we can't fix the pattern by eliding modifiers, we'll need to make the pattern
229
314
// 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 } ) ;
237
316
}
238
317
}
239
318
}
0 commit comments