33use rustc_data_structures:: fx:: FxIndexMap ;
44use rustc_errors:: MultiSpan ;
55use rustc_hir:: { BindingMode , ByRef , HirId , Mutability } ;
6+ use rustc_index:: IndexVec ;
67use rustc_lint as lint;
78use rustc_middle:: span_bug;
89use 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+
3353impl < ' 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 }
0 commit comments