Skip to content

Commit b4b2b35

Browse files
simplify the suggestion notes
1 parent 89682a5 commit b4b2b35

7 files changed

+194
-374
lines changed

Diff for: compiler/rustc_lint/messages.ftl

+1-3
Original file line numberDiff line numberDiff line change
@@ -337,9 +337,7 @@ lint_identifier_uncommon_codepoints = identifier contains {$codepoints_len ->
337337
lint_if_let_rescope = `if let` assigns a shorter lifetime since Edition 2024
338338
.label = this value has a significant drop implementation which may observe a major change in drop order and requires your discretion
339339
.help = the value is now dropped here in Edition 2024
340-
341-
lint_if_let_rescope_suggestion = a `match` with a single arm can preserve the drop order up to Edition 2021
342-
.suggestion = rewrite this `if let` into `match`
340+
.suggestion = a `match` with a single arm can preserve the drop order up to Edition 2021
343341
344342
lint_ignored_unless_crate_specified = {$level}({$name}) is ignored unless specified at crate level
345343

Diff for: compiler/rustc_lint/src/if_let_rescope.rs

+123-108
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use rustc_errors::{
77
Applicability, Diag, EmissionGuarantee, SubdiagMessageOp, Subdiagnostic, SuggestionStyle,
88
};
99
use rustc_hir::{self as hir, HirIdSet};
10-
use rustc_macros::{LintDiagnostic, Subdiagnostic};
10+
use rustc_macros::LintDiagnostic;
1111
use rustc_middle::ty::TyCtxt;
1212
use rustc_session::lint::{FutureIncompatibilityReason, Level};
1313
use 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)]
255261
struct 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)]
264271
struct 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+
}
317334
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),
322337
}
323338

324339
struct FindSignificantDropper<'tcx, 'a> {

Diff for: tests/ui/drop/lint-if-let-rescope-gated.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ impl Droppy {
2626
fn main() {
2727
if let Some(_value) = Droppy.get() {
2828
//[with_feature_gate]~^ ERROR: `if let` assigns a shorter lifetime since Edition 2024
29-
//[with_feature_gate]~| ERROR: a `match` with a single arm can preserve the drop order up to Edition 2021
30-
//[with_feature_gate]~| WARN: this changes meaning in Rust 2024
29+
//[with_feature_gate]~| HELP: a `match` with a single arm can preserve the drop order up to Edition 2021
3130
//[with_feature_gate]~| WARN: this changes meaning in Rust 2024
3231
} else {
32+
//[with_feature_gate]~^ HELP: the value is now dropped here in Edition 2024
3333
}
3434
}

Diff for: tests/ui/drop/lint-if-let-rescope-gated.with_feature_gate.stderr

+10-31
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ LL | if let Some(_value) = Droppy.get() {
99
= warning: this changes meaning in Rust 2024
1010
= note: for more information, see issue #124085 <https://github.com/rust-lang/rust/issues/124085>
1111
help: the value is now dropped here in Edition 2024
12-
--> $DIR/lint-if-let-rescope-gated.rs:32:5
12+
--> $DIR/lint-if-let-rescope-gated.rs:31:5
1313
|
1414
LL | } else {
1515
| ^
@@ -18,37 +18,16 @@ note: the lint level is defined here
1818
|
1919
LL | #![deny(if_let_rescope)]
2020
| ^^^^^^^^^^^^^^
21-
22-
error: a `match` with a single arm can preserve the drop order up to Edition 2021
23-
--> $DIR/lint-if-let-rescope-gated.rs:27:5
24-
|
25-
LL | / if let Some(_value) = Droppy.get() {
26-
LL | |
27-
LL | |
28-
LL | |
29-
LL | |
30-
LL | | } else {
31-
LL | | }
32-
| |_____^
33-
|
34-
= warning: this changes meaning in Rust 2024
35-
= note: for more information, see issue #124085 <https://github.com/rust-lang/rust/issues/124085>
36-
help: rewrite this `if let` into `match`
37-
|
38-
LL | match Droppy.get() {
39-
| ~~~~~
40-
help: rewrite this `if let` into `match`
41-
|
42-
LL | if let Some(_value) = Droppy.get() { Some(_value) => {
43-
| +++++++++++++++++
44-
help: rewrite this `if let` into `match`
21+
help: a `match` with a single arm can preserve the drop order up to Edition 2021
4522
|
46-
LL | }}
47-
| +
48-
help: rewrite this `if let` into `match`
23+
LL ~ match Droppy.get() { Some(_value) => {
24+
LL |
25+
LL |
26+
LL |
27+
LL ~ } _ => {
28+
LL |
29+
LL ~ }}
4930
|
50-
LL | } _ => {
51-
| ~~~~
5231

53-
error: aborting due to 2 previous errors
32+
error: aborting due to 1 previous error
5433

0 commit comments

Comments
 (0)