22
33use rustc_data_structures:: fx:: FxIndexMap ;
44use rustc_errors:: MultiSpan ;
5- use rustc_hir:: { BindingMode , ByRef , HirId , Mutability } ;
5+ use rustc_hir:: { self as hir , BindingMode , ByRef , HirId , Mutability } ;
66use rustc_index:: IndexVec ;
77use rustc_lint as lint;
88use 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+
3640rustc_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}
0 commit comments