2
2
3
3
use rustc_ast:: BindingMode ;
4
4
use rustc_errors:: MultiSpan ;
5
- use rustc_hir:: { ByRef , HirId , Mutability } ;
5
+ use rustc_hir:: { self as hir , ByRef , HirId , Mutability } ;
6
6
use rustc_lint as lint;
7
7
use rustc_middle:: span_bug;
8
- use rustc_middle:: ty:: { self , Ty , TyCtxt } ;
8
+ use rustc_middle:: ty:: { self , Ty , TyCtxt , TypeckResults } ;
9
9
use rustc_span:: { Ident , Span } ;
10
10
11
- use crate :: errors:: { Rust2024IncompatiblePat , Rust2024IncompatiblePatSugg } ;
11
+ use crate :: errors:: * ;
12
12
use crate :: fluent_generated as fluent;
13
13
14
14
/// For patterns flagged for migration during HIR typeck, this handles constructing and emitting
15
15
/// a diagnostic suggestion.
16
16
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 ,
20
32
/// Labeled spans for subpatterns invalid in Rust 2024.
21
33
labels : & ' a [ ( Span , String ) ] ,
22
34
}
23
35
24
36
impl < ' a > PatMigration < ' a > {
25
37
pub ( super ) fn new ( labels : & ' a Vec < ( Span , String ) > ) -> Self {
26
38
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 ,
30
46
labels : labels. as_slice ( ) ,
31
47
}
32
48
}
33
49
34
50
/// On Rust 2024, this emits a hard error. On earlier Editions, this emits the
35
51
/// 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
+ ) {
37
58
let mut spans = MultiSpan :: from_spans ( self . labels . iter ( ) . map ( |( span, _) | * span) . collect ( ) ) ;
38
59
for ( span, label) in self . labels {
39
60
spans. push_span_label ( * span, label. clone ( ) ) ;
40
61
}
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) ;
46
63
// If a relevant span is from at least edition 2024, this is a hard error.
47
64
let is_hard_error = spans. primary_spans ( ) . iter ( ) . any ( |span| span. at_least_rust_2024 ( ) ) ;
48
65
if is_hard_error {
@@ -64,38 +81,159 @@ impl<'a> PatMigration<'a> {
64
81
}
65
82
}
66
83
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 " ,
73
108
} ;
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
+ }
74
170
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 ( )
76
194
} )
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 ;
80
209
}
81
210
211
+ /// Keeps track of bindings and adjusts the suggestion if necessary.
82
212
pub ( super ) fn visit_binding (
83
213
& mut self ,
84
214
pat_span : Span ,
85
215
mode : BindingMode ,
86
216
explicit_ba : BindingMode ,
87
217
ident : Ident ,
88
218
) {
219
+ // Note any binding modes we may need to make explicit.
89
220
if explicit_ba. 0 == ByRef :: No
90
221
&& let ByRef :: Yes ( mutbl) = mode. 0
91
222
{
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
+ }
99
237
}
100
238
}
101
239
}
0 commit comments