@@ -7,7 +7,7 @@ use rustc_errors::{
7
7
Applicability , Diag , EmissionGuarantee , SubdiagMessageOp , Subdiagnostic , SuggestionStyle ,
8
8
} ;
9
9
use rustc_hir:: { self as hir, HirIdSet } ;
10
- use rustc_macros:: { LintDiagnostic , Subdiagnostic } ;
10
+ use rustc_macros:: LintDiagnostic ;
11
11
use rustc_middle:: ty:: TyCtxt ;
12
12
use rustc_session:: lint:: { FutureIncompatibilityReason , Level } ;
13
13
use rustc_session:: { declare_lint, impl_lint_pass} ;
@@ -124,103 +124,109 @@ impl IfLetRescope {
124
124
let source_map = tcx. sess . source_map ( ) ;
125
125
let expr_end = expr. span . shrink_to_hi ( ) ;
126
126
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 ! [ ] ;
127
129
let mut closing_brackets = 0 ;
128
130
let mut alt_heads = vec ! [ ] ;
129
131
let mut match_heads = vec ! [ ] ;
130
132
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 ;
132
135
let mut empty_alt = false ;
133
136
while let hir:: ExprKind :: If ( cond, conseq, alt) = expr. kind {
134
137
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 {
136
141
span,
137
142
pat,
138
143
init,
139
144
ty : ty_ascription,
140
145
recovered : Recovered :: No ,
141
146
} ) = 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)
159
147
{
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)
169
155
{
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
+ }
182
193
} 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 ;
186
199
}
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 ;
200
200
}
201
201
}
202
202
}
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 ;
203
206
if let Some ( alt) = alt {
204
- add_bracket_to_match_head = matches ! ( alt. kind, hir:: ExprKind :: If ( ..) ) ;
205
207
expr = alt;
206
208
} else {
207
209
break ;
208
210
}
209
211
}
210
- if let Some ( ( span, hir_id) ) = first_if_to_rewrite {
212
+ if let Some ( ( span, hir_id) ) = first_if_to_lint {
211
213
tcx. emit_node_span_lint (
212
214
IF_LET_RESCOPE ,
213
215
hir_id,
214
216
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
+ } ) ,
224
230
} ,
225
231
) ;
226
232
}
@@ -254,71 +260,80 @@ impl<'tcx> LateLintPass<'tcx> for IfLetRescope {
254
260
#[ diag( lint_if_let_rescope) ]
255
261
struct IfLetRescopeLint {
256
262
#[ label]
257
- significant_dropper : Span ,
263
+ significant_droppers : Vec < Span > ,
258
264
#[ help]
259
- lifetime_end : Span ,
265
+ lifetime_ends : Vec < Span > ,
266
+ #[ subdiagnostic]
267
+ rewrite : Option < IfLetRescopeRewrite > ,
260
268
}
261
269
262
- #[ derive( LintDiagnostic ) ]
263
- #[ diag( lint_if_let_rescope_suggestion) ]
270
+ // #[derive(Subdiagnostic)]
264
271
struct IfLetRescopeRewrite {
265
- #[ subdiagnostic]
266
272
match_heads : Vec < SingleArmMatchBegin > ,
267
- #[ subdiagnostic]
268
273
consequent_heads : Vec < ConsequentRewrite > ,
269
- #[ subdiagnostic]
270
274
closing_brackets : ClosingBrackets ,
271
- #[ subdiagnostic]
272
275
alt_heads : Vec < AltHead > ,
273
276
}
274
277
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 {
294
279
fn add_to_diag_with < G : EmissionGuarantee , F : SubdiagMessageOp < G > > (
295
280
self ,
296
281
diag : & mut Diag < ' _ , G > ,
297
282
f : & F ,
298
283
) {
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
+ ) ) ;
306
312
let msg = f ( diag, crate :: fluent_generated:: lint_suggestion. into ( ) ) ;
307
313
diag. multipart_suggestion_with_style (
308
314
msg,
309
- vec ! [ ( self . span , code ) ] ,
315
+ suggestions ,
310
316
Applicability :: MachineApplicable ,
311
317
SuggestionStyle :: ShowCode ,
312
318
) ;
313
319
}
314
320
}
315
321
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
+ }
317
334
enum 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 ) ,
322
337
}
323
338
324
339
struct FindSignificantDropper < ' tcx , ' a > {
0 commit comments