@@ -7,7 +7,7 @@ use rustc_errors::{
77 Applicability , Diag , EmissionGuarantee , SubdiagMessageOp , Subdiagnostic , SuggestionStyle ,
88} ;
99use rustc_hir:: { self as hir, HirIdSet } ;
10- use rustc_macros:: { LintDiagnostic , Subdiagnostic } ;
10+ use rustc_macros:: LintDiagnostic ;
1111use rustc_middle:: ty:: TyCtxt ;
1212use rustc_session:: lint:: { FutureIncompatibilityReason , Level } ;
1313use rustc_session:: { declare_lint, impl_lint_pass} ;
@@ -124,103 +124,109 @@ impl IfLetRescope {
124124 let source_map = tcx. sess . source_map ( ) ;
125125 let expr_end = expr. span . shrink_to_hi ( ) ;
126126 let mut add_bracket_to_match_head = match_head_needs_bracket ( tcx, expr) ;
127+ let mut significant_droppers = vec ! [ ] ;
128+ let mut lifetime_ends = vec ! [ ] ;
127129 let mut closing_brackets = 0 ;
128130 let mut alt_heads = vec ! [ ] ;
129131 let mut match_heads = vec ! [ ] ;
130132 let mut consequent_heads = vec ! [ ] ;
131- let mut first_if_to_rewrite = None ;
133+ let mut first_if_to_lint = None ;
134+ let mut first_if_to_rewrite = false ;
132135 let mut empty_alt = false ;
133136 while let hir:: ExprKind :: If ( cond, conseq, alt) = expr. kind {
134137 self . skip . insert ( expr. hir_id ) ;
135- let hir:: ExprKind :: Let ( & hir:: LetExpr {
138+ // We are interested in `let` fragment of the condition.
139+ // Otherwise, we probe into the `else` fragment.
140+ if let hir:: ExprKind :: Let ( & hir:: LetExpr {
136141 span,
137142 pat,
138143 init,
139144 ty : ty_ascription,
140145 recovered : Recovered :: No ,
141146 } ) = cond. kind
142- else {
143- if let Some ( alt) = alt {
144- add_bracket_to_match_head = matches ! ( alt. kind, hir:: ExprKind :: If ( ..) ) ;
145- expr = alt;
146- continue ;
147- } else {
148- // finalize and emit span
149- break ;
150- }
151- } ;
152- let if_let_pat = expr. span . shrink_to_lo ( ) . between ( init. span ) ;
153- // the consequent fragment is always a block
154- let before_conseq = conseq. span . shrink_to_lo ( ) ;
155- let lifetime_end = source_map. end_point ( conseq. span ) ;
156-
157- if let ControlFlow :: Break ( significant_dropper) =
158- ( FindSignificantDropper { cx } ) . visit_expr ( init)
159147 {
160- tcx. emit_node_span_lint (
161- IF_LET_RESCOPE ,
162- expr. hir_id ,
163- span,
164- IfLetRescopeLint { significant_dropper, lifetime_end } ,
165- ) ;
166- if ty_ascription. is_some ( )
167- || !expr. span . can_be_used_for_suggestions ( )
168- || !pat. span . can_be_used_for_suggestions ( )
148+ let if_let_pat = expr. span . shrink_to_lo ( ) . between ( init. span ) ;
149+ // The consequent fragment is always a block.
150+ let before_conseq = conseq. span . shrink_to_lo ( ) ;
151+ let lifetime_end = source_map. end_point ( conseq. span ) ;
152+
153+ if let ControlFlow :: Break ( significant_dropper) =
154+ ( FindSignificantDropper { cx } ) . visit_expr ( init)
169155 {
170- // Our `match` rewrites does not support type ascription,
171- // so we just bail.
172- // Alternatively when the span comes from proc macro expansion,
173- // we will also bail.
174- // FIXME(#101728): change this when type ascription syntax is stabilized again
175- } else if let Ok ( pat) = source_map. span_to_snippet ( pat. span ) {
176- let emit_suggestion = || {
177- first_if_to_rewrite =
178- first_if_to_rewrite. or_else ( || Some ( ( expr. span , expr. hir_id ) ) ) ;
179- if add_bracket_to_match_head {
180- closing_brackets += 2 ;
181- match_heads. push ( SingleArmMatchBegin :: WithOpenBracket ( if_let_pat) ) ;
156+ first_if_to_lint = first_if_to_lint. or_else ( || Some ( ( span, expr. hir_id ) ) ) ;
157+ significant_droppers. push ( significant_dropper) ;
158+ lifetime_ends. push ( lifetime_end) ;
159+ if ty_ascription. is_some ( )
160+ || !expr. span . can_be_used_for_suggestions ( )
161+ || !pat. span . can_be_used_for_suggestions ( )
162+ {
163+ // Our `match` rewrites does not support type ascription,
164+ // so we just bail.
165+ // Alternatively when the span comes from proc macro expansion,
166+ // we will also bail.
167+ // FIXME(#101728): change this when type ascription syntax is stabilized again
168+ } else if let Ok ( pat) = source_map. span_to_snippet ( pat. span ) {
169+ let emit_suggestion = |alt_span| {
170+ first_if_to_rewrite = true ;
171+ if add_bracket_to_match_head {
172+ closing_brackets += 2 ;
173+ match_heads. push ( SingleArmMatchBegin :: WithOpenBracket ( if_let_pat) ) ;
174+ } else {
175+ // Sometimes, wrapping `match` into a block is undesirable,
176+ // because the scrutinee temporary lifetime is shortened and
177+ // the proposed fix will not work.
178+ closing_brackets += 1 ;
179+ match_heads
180+ . push ( SingleArmMatchBegin :: WithoutOpenBracket ( if_let_pat) ) ;
181+ }
182+ consequent_heads. push ( ConsequentRewrite { span : before_conseq, pat } ) ;
183+ if let Some ( alt_span) = alt_span {
184+ alt_heads. push ( AltHead ( alt_span) ) ;
185+ }
186+ } ;
187+ if let Some ( alt) = alt {
188+ let alt_head = conseq. span . between ( alt. span ) ;
189+ if alt_head. can_be_used_for_suggestions ( ) {
190+ // We lint only when the `else` span is user code, too.
191+ emit_suggestion ( Some ( alt_head) ) ;
192+ }
182193 } else {
183- // It has to be a block
184- closing_brackets += 1 ;
185- match_heads. push ( SingleArmMatchBegin :: WithoutOpenBracket ( if_let_pat) ) ;
194+ // This is the end of the `if .. else ..` cascade.
195+ // We can stop here.
196+ emit_suggestion ( None ) ;
197+ empty_alt = true ;
198+ break ;
186199 }
187- consequent_heads. push ( ConsequentRewrite { span : before_conseq, pat } ) ;
188- } ;
189- if let Some ( alt) = alt {
190- let alt_head = conseq. span . between ( alt. span ) ;
191- if alt_head. can_be_used_for_suggestions ( ) {
192- // lint
193- emit_suggestion ( ) ;
194- alt_heads. push ( AltHead ( alt_head) ) ;
195- }
196- } else {
197- emit_suggestion ( ) ;
198- empty_alt = true ;
199- break ;
200200 }
201201 }
202202 }
203+ // At this point, any `if let` fragment in the cascade is definitely preceeded by `else`,
204+ // so a opening bracket is mandatory before each `match`.
205+ add_bracket_to_match_head = true ;
203206 if let Some ( alt) = alt {
204- add_bracket_to_match_head = matches ! ( alt. kind, hir:: ExprKind :: If ( ..) ) ;
205207 expr = alt;
206208 } else {
207209 break ;
208210 }
209211 }
210- if let Some ( ( span, hir_id) ) = first_if_to_rewrite {
212+ if let Some ( ( span, hir_id) ) = first_if_to_lint {
211213 tcx. emit_node_span_lint (
212214 IF_LET_RESCOPE ,
213215 hir_id,
214216 span,
215- IfLetRescopeRewrite {
216- match_heads,
217- consequent_heads,
218- closing_brackets : ClosingBrackets {
219- span : expr_end,
220- count : closing_brackets,
221- empty_alt,
222- } ,
223- alt_heads,
217+ IfLetRescopeLint {
218+ significant_droppers,
219+ lifetime_ends,
220+ rewrite : first_if_to_rewrite. then_some ( IfLetRescopeRewrite {
221+ match_heads,
222+ consequent_heads,
223+ closing_brackets : ClosingBrackets {
224+ span : expr_end,
225+ count : closing_brackets,
226+ empty_alt,
227+ } ,
228+ alt_heads,
229+ } ) ,
224230 } ,
225231 ) ;
226232 }
@@ -254,71 +260,80 @@ impl<'tcx> LateLintPass<'tcx> for IfLetRescope {
254260#[ diag( lint_if_let_rescope) ]
255261struct IfLetRescopeLint {
256262 #[ label]
257- significant_dropper : Span ,
263+ significant_droppers : Vec < Span > ,
258264 #[ help]
259- lifetime_end : Span ,
265+ lifetime_ends : Vec < Span > ,
266+ #[ subdiagnostic]
267+ rewrite : Option < IfLetRescopeRewrite > ,
260268}
261269
262- #[ derive( LintDiagnostic ) ]
263- #[ diag( lint_if_let_rescope_suggestion) ]
270+ // #[derive(Subdiagnostic)]
264271struct IfLetRescopeRewrite {
265- #[ subdiagnostic]
266272 match_heads : Vec < SingleArmMatchBegin > ,
267- #[ subdiagnostic]
268273 consequent_heads : Vec < ConsequentRewrite > ,
269- #[ subdiagnostic]
270274 closing_brackets : ClosingBrackets ,
271- #[ subdiagnostic]
272275 alt_heads : Vec < AltHead > ,
273276}
274277
275- #[ derive( Subdiagnostic ) ]
276- #[ multipart_suggestion( lint_suggestion, applicability = "machine-applicable" ) ]
277- struct AltHead ( #[ suggestion_part( code = " _ => " ) ] Span ) ;
278-
279- #[ derive( Subdiagnostic ) ]
280- #[ multipart_suggestion( lint_suggestion, applicability = "machine-applicable" ) ]
281- struct ConsequentRewrite {
282- #[ suggestion_part( code = "{{ {pat} => " ) ]
283- span : Span ,
284- pat : String ,
285- }
286-
287- struct ClosingBrackets {
288- span : Span ,
289- count : usize ,
290- empty_alt : bool ,
291- }
292-
293- impl Subdiagnostic for ClosingBrackets {
278+ impl Subdiagnostic for IfLetRescopeRewrite {
294279 fn add_to_diag_with < G : EmissionGuarantee , F : SubdiagMessageOp < G > > (
295280 self ,
296281 diag : & mut Diag < ' _ , G > ,
297282 f : & F ,
298283 ) {
299- let code: String = self
300- . empty_alt
301- . then_some ( " _ => {}" . chars ( ) )
302- . into_iter ( )
303- . flatten ( )
304- . chain ( repeat ( '}' ) . take ( self . count ) )
305- . collect ( ) ;
284+ let mut suggestions = vec ! [ ] ;
285+ for match_head in self . match_heads {
286+ match match_head {
287+ SingleArmMatchBegin :: WithOpenBracket ( span) => {
288+ suggestions. push ( ( span, "{ match " . into ( ) ) )
289+ }
290+ SingleArmMatchBegin :: WithoutOpenBracket ( span) => {
291+ suggestions. push ( ( span, "match " . into ( ) ) )
292+ }
293+ }
294+ }
295+ for ConsequentRewrite { span, pat } in self . consequent_heads {
296+ suggestions. push ( ( span, format ! ( "{{ {pat} => " ) ) ) ;
297+ }
298+ for AltHead ( span) in self . alt_heads {
299+ suggestions. push ( ( span, " _ => " . into ( ) ) ) ;
300+ }
301+ let closing_brackets = self . closing_brackets ;
302+ suggestions. push ( (
303+ closing_brackets. span ,
304+ closing_brackets
305+ . empty_alt
306+ . then_some ( " _ => {}" . chars ( ) )
307+ . into_iter ( )
308+ . flatten ( )
309+ . chain ( repeat ( '}' ) . take ( closing_brackets. count ) )
310+ . collect ( ) ,
311+ ) ) ;
306312 let msg = f ( diag, crate :: fluent_generated:: lint_suggestion. into ( ) ) ;
307313 diag. multipart_suggestion_with_style (
308314 msg,
309- vec ! [ ( self . span , code ) ] ,
315+ suggestions ,
310316 Applicability :: MachineApplicable ,
311317 SuggestionStyle :: ShowCode ,
312318 ) ;
313319 }
314320}
315321
316- #[ derive( Subdiagnostic ) ]
322+ struct AltHead ( Span ) ;
323+
324+ struct ConsequentRewrite {
325+ span : Span ,
326+ pat : String ,
327+ }
328+
329+ struct ClosingBrackets {
330+ span : Span ,
331+ count : usize ,
332+ empty_alt : bool ,
333+ }
317334enum SingleArmMatchBegin {
318- #[ multipart_suggestion( lint_suggestion, applicability = "machine-applicable" ) ]
319- WithOpenBracket ( #[ suggestion_part( code = "{{ match " ) ] Span ) ,
320- #[ multipart_suggestion( lint_suggestion, applicability = "machine-applicable" ) ]
321- WithoutOpenBracket ( #[ suggestion_part( code = "match " ) ] Span ) ,
335+ WithOpenBracket ( Span ) ,
336+ WithoutOpenBracket ( Span ) ,
322337}
323338
324339struct FindSignificantDropper < ' tcx , ' a > {
0 commit comments