22
33use rustc_ast:: BindingMode ;
44use rustc_errors:: MultiSpan ;
5- use rustc_hir:: { ByRef , HirId , Mutability } ;
5+ use rustc_hir:: { self as hir , ByRef , HirId , Mutability } ;
66use rustc_lint as lint;
77use rustc_middle:: span_bug;
8- use rustc_middle:: ty:: { self , Ty , TyCtxt } ;
8+ use rustc_middle:: ty:: { self , Ty , TyCtxt , TypeckResults } ;
99use rustc_span:: { Ident , Span } ;
1010
11- use crate :: errors:: { Rust2024IncompatiblePat , Rust2024IncompatiblePatSugg } ;
11+ use crate :: errors:: * ;
1212use crate :: fluent_generated as fluent;
1313
1414/// For patterns flagged for migration during HIR typeck, this handles constructing and emitting
1515/// a diagnostic suggestion.
1616pub ( 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 ,
2032 /// Labeled spans for subpatterns invalid in Rust 2024.
2133 labels : & ' a [ ( Span , String ) ] ,
2234}
2335
2436impl < ' a > PatMigration < ' a > {
2537 pub ( super ) fn new ( labels : & ' a Vec < ( Span , String ) > ) -> Self {
2638 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 ,
3046 labels : labels. as_slice ( ) ,
3147 }
3248 }
3349
3450 /// On Rust 2024, this emits a hard error. On earlier Editions, this emits the
3551 /// 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+ ) {
3758 let mut spans = MultiSpan :: from_spans ( self . labels . iter ( ) . map ( |( span, _) | * span) . collect ( ) ) ;
3859 for ( span, label) in self . labels {
3960 spans. push_span_label ( * span, label. clone ( ) ) ;
4061 }
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) ;
4663 // If a relevant span is from at least edition 2024, this is a hard error.
4764 let is_hard_error = spans. primary_spans ( ) . iter ( ) . any ( |span| span. at_least_rust_2024 ( ) ) ;
4865 if is_hard_error {
@@ -64,38 +81,159 @@ impl<'a> PatMigration<'a> {
6481 }
6582 }
6683
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 " ,
73108 } ;
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+ }
74170
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 ( )
76194 } )
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 ;
80209 }
81210
211+ /// Keeps track of bindings and adjusts the suggestion if necessary.
82212 pub ( super ) fn visit_binding (
83213 & mut self ,
84214 pat_span : Span ,
85215 mode : BindingMode ,
86216 explicit_ba : BindingMode ,
87217 ident : Ident ,
88218 ) {
219+ // Note any binding modes we may need to make explicit.
89220 if explicit_ba. 0 == ByRef :: No
90221 && let ByRef :: Yes ( mutbl) = mode. 0
91222 {
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+ }
99237 }
100238 }
101239}
0 commit comments