@@ -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