Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 55c9732

Browse files
authoredMar 20, 2025
Unrolled build for rust-lang#138589
Rollup merge of rust-lang#138589 - zachs18:block-label-not-supported-here-loop-body-help, r=petrochenkov If a label is placed on the block of a loop instead of the header, suggest moving it to the header. Fixes rust-lang#138585 If a label is placed on the block of a loop instead of the header, suggest to the user moving it to the loop header instead of ~~suggesting to remove it~~ emitting a tool-only suggestion to remove it. ```rs fn main() { loop 'a: { return; } } ``` ```diff error: block label not supported here --> src/main.rs:2:10 | 2 | loop 'a: { return; } | ^^^ not supported here + | +help: if you meant to label the loop, move this label before the loop + | +2 - loop 'a: { return; } +2 + 'a: loop { return; } + | ``` Questions for reviewer: * The "desired output" in the linked issue had the main diagnostic be "misplaced loop label". Should the main diagnostic message the changed instead of leaving it as "block label not supported here"? * Should this be `Applicability::MachineApplicable`?
2 parents 1aeb99d + f478853 commit 55c9732

File tree

7 files changed

+296
-25
lines changed

7 files changed

+296
-25
lines changed
 

‎compiler/rustc_parse/src/parser/diagnostics.rs

+23-6
Original file line numberDiff line numberDiff line change
@@ -2874,7 +2874,12 @@ impl<'a> Parser<'a> {
28742874
first_pat
28752875
}
28762876

2877-
pub(crate) fn maybe_recover_unexpected_block_label(&mut self) -> bool {
2877+
/// If `loop_header` is `Some` and an unexpected block label is encountered,
2878+
/// it is suggested to be moved just before `loop_header`, else it is suggested to be removed.
2879+
pub(crate) fn maybe_recover_unexpected_block_label(
2880+
&mut self,
2881+
loop_header: Option<Span>,
2882+
) -> bool {
28782883
// Check for `'a : {`
28792884
if !(self.check_lifetime()
28802885
&& self.look_ahead(1, |t| *t == token::Colon)
@@ -2885,16 +2890,28 @@ impl<'a> Parser<'a> {
28852890
let label = self.eat_label().expect("just checked if a label exists");
28862891
self.bump(); // eat `:`
28872892
let span = label.ident.span.to(self.prev_token.span);
2888-
self.dcx()
2893+
let mut diag = self
2894+
.dcx()
28892895
.struct_span_err(span, "block label not supported here")
2890-
.with_span_label(span, "not supported here")
2891-
.with_tool_only_span_suggestion(
2896+
.with_span_label(span, "not supported here");
2897+
if let Some(loop_header) = loop_header {
2898+
diag.multipart_suggestion(
2899+
"if you meant to label the loop, move this label before the loop",
2900+
vec![
2901+
(label.ident.span.until(self.token.span), String::from("")),
2902+
(loop_header.shrink_to_lo(), format!("{}: ", label.ident)),
2903+
],
2904+
Applicability::MachineApplicable,
2905+
);
2906+
} else {
2907+
diag.tool_only_span_suggestion(
28922908
label.ident.span.until(self.token.span),
28932909
"remove this block label",
28942910
"",
28952911
Applicability::MachineApplicable,
2896-
)
2897-
.emit();
2912+
);
2913+
}
2914+
diag.emit();
28982915
true
28992916
}
29002917

‎compiler/rustc_parse/src/parser/expr.rs

+25-11
Original file line numberDiff line numberDiff line change
@@ -2286,7 +2286,7 @@ impl<'a> Parser<'a> {
22862286
});
22872287
}
22882288

2289-
let (attrs, blk) = self.parse_block_common(lo, blk_mode, true)?;
2289+
let (attrs, blk) = self.parse_block_common(lo, blk_mode, true, None)?;
22902290
Ok(self.mk_expr_with_attrs(blk.span, ExprKind::Block(blk, opt_label), attrs))
22912291
}
22922292

@@ -2851,7 +2851,11 @@ impl<'a> Parser<'a> {
28512851
));
28522852
}
28532853

2854-
let (attrs, loop_block) = self.parse_inner_attrs_and_block()?;
2854+
let (attrs, loop_block) = self.parse_inner_attrs_and_block(
2855+
// Only suggest moving erroneous block label to the loop header
2856+
// if there is not already a label there
2857+
opt_label.is_none().then_some(lo),
2858+
)?;
28552859

28562860
let kind = ExprKind::ForLoop { pat, iter: expr, body: loop_block, label: opt_label, kind };
28572861

@@ -2894,11 +2898,17 @@ impl<'a> Parser<'a> {
28942898
err.span_label(lo, "while parsing the condition of this `while` expression");
28952899
err
28962900
})?;
2897-
let (attrs, body) = self.parse_inner_attrs_and_block().map_err(|mut err| {
2898-
err.span_label(lo, "while parsing the body of this `while` expression");
2899-
err.span_label(cond.span, "this `while` condition successfully parsed");
2900-
err
2901-
})?;
2901+
let (attrs, body) = self
2902+
.parse_inner_attrs_and_block(
2903+
// Only suggest moving erroneous block label to the loop header
2904+
// if there is not already a label there
2905+
opt_label.is_none().then_some(lo),
2906+
)
2907+
.map_err(|mut err| {
2908+
err.span_label(lo, "while parsing the body of this `while` expression");
2909+
err.span_label(cond.span, "this `while` condition successfully parsed");
2910+
err
2911+
})?;
29022912

29032913
self.recover_loop_else("while", lo)?;
29042914

@@ -2912,7 +2922,11 @@ impl<'a> Parser<'a> {
29122922
/// Parses `loop { ... }` (`loop` token already eaten).
29132923
fn parse_expr_loop(&mut self, opt_label: Option<Label>, lo: Span) -> PResult<'a, P<Expr>> {
29142924
let loop_span = self.prev_token.span;
2915-
let (attrs, body) = self.parse_inner_attrs_and_block()?;
2925+
let (attrs, body) = self.parse_inner_attrs_and_block(
2926+
// Only suggest moving erroneous block label to the loop header
2927+
// if there is not already a label there
2928+
opt_label.is_none().then_some(lo),
2929+
)?;
29162930
self.recover_loop_else("loop", lo)?;
29172931
Ok(self.mk_expr_with_attrs(
29182932
lo.to(self.prev_token.span),
@@ -2962,7 +2976,7 @@ impl<'a> Parser<'a> {
29622976
Applicability::MaybeIncorrect, // speculative
29632977
);
29642978
}
2965-
if self.maybe_recover_unexpected_block_label() {
2979+
if self.maybe_recover_unexpected_block_label(None) {
29662980
e.cancel();
29672981
self.bump();
29682982
} else {
@@ -3376,7 +3390,7 @@ impl<'a> Parser<'a> {
33763390

33773391
/// Parses a `try {...}` expression (`try` token already eaten).
33783392
fn parse_try_block(&mut self, span_lo: Span) -> PResult<'a, P<Expr>> {
3379-
let (attrs, body) = self.parse_inner_attrs_and_block()?;
3393+
let (attrs, body) = self.parse_inner_attrs_and_block(None)?;
33803394
if self.eat_keyword(exp!(Catch)) {
33813395
Err(self.dcx().create_err(errors::CatchAfterTry { span: self.prev_token.span }))
33823396
} else {
@@ -3424,7 +3438,7 @@ impl<'a> Parser<'a> {
34243438
}
34253439
let capture_clause = self.parse_capture_clause()?;
34263440
let decl_span = lo.to(self.prev_token.span);
3427-
let (attrs, body) = self.parse_inner_attrs_and_block()?;
3441+
let (attrs, body) = self.parse_inner_attrs_and_block(None)?;
34283442
let kind = ExprKind::Gen(capture_clause, body, kind, decl_span);
34293443
Ok(self.mk_expr_with_attrs(lo.to(self.prev_token.span), kind, attrs))
34303444
}

‎compiler/rustc_parse/src/parser/item.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -2529,7 +2529,7 @@ impl<'a> Parser<'a> {
25292529
*sig_hi = self.prev_token.span;
25302530
(AttrVec::new(), None)
25312531
} else if self.check(exp!(OpenBrace)) || self.token.is_whole_block() {
2532-
self.parse_block_common(self.token.span, BlockCheckMode::Default, false)
2532+
self.parse_block_common(self.token.span, BlockCheckMode::Default, false, None)
25332533
.map(|(attrs, body)| (attrs, Some(body)))?
25342534
} else if self.token == token::Eq {
25352535
// Recover `fn foo() = $expr;`.

‎compiler/rustc_parse/src/parser/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1374,7 +1374,7 @@ impl<'a> Parser<'a> {
13741374
self.psess.gated_spans.gate(sym::inline_const_pat, span);
13751375
}
13761376
self.expect_keyword(exp!(Const))?;
1377-
let (attrs, blk) = self.parse_inner_attrs_and_block()?;
1377+
let (attrs, blk) = self.parse_inner_attrs_and_block(None)?;
13781378
let anon_const = AnonConst {
13791379
id: DUMMY_NODE_ID,
13801380
value: self.mk_expr(blk.span, ExprKind::Block(blk, None)),

‎compiler/rustc_parse/src/parser/stmt.rs

+16-6
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,7 @@ impl<'a> Parser<'a> {
482482

483483
/// Parses a block. No inner attributes are allowed.
484484
pub fn parse_block(&mut self) -> PResult<'a, P<Block>> {
485-
let (attrs, block) = self.parse_inner_attrs_and_block()?;
485+
let (attrs, block) = self.parse_inner_attrs_and_block(None)?;
486486
if let [.., last] = &*attrs {
487487
let suggest_to_outer = match &last.kind {
488488
ast::AttrKind::Normal(attr) => attr.item.is_valid_for_outer_style(),
@@ -660,22 +660,32 @@ impl<'a> Parser<'a> {
660660
Err(self.error_block_no_opening_brace_msg(Cow::from(msg)))
661661
}
662662

663-
/// Parses a block. Inner attributes are allowed.
664-
pub(super) fn parse_inner_attrs_and_block(&mut self) -> PResult<'a, (AttrVec, P<Block>)> {
665-
self.parse_block_common(self.token.span, BlockCheckMode::Default, true)
663+
/// Parses a block. Inner attributes are allowed, block labels are not.
664+
///
665+
/// If `loop_header` is `Some` and an unexpected block label is encountered,
666+
/// it is suggested to be moved just before `loop_header`, else it is suggested to be removed.
667+
pub(super) fn parse_inner_attrs_and_block(
668+
&mut self,
669+
loop_header: Option<Span>,
670+
) -> PResult<'a, (AttrVec, P<Block>)> {
671+
self.parse_block_common(self.token.span, BlockCheckMode::Default, true, loop_header)
666672
}
667673

668-
/// Parses a block. Inner attributes are allowed.
674+
/// Parses a block. Inner attributes are allowed, block labels are not.
675+
///
676+
/// If `loop_header` is `Some` and an unexpected block label is encountered,
677+
/// it is suggested to be moved just before `loop_header`, else it is suggested to be removed.
669678
pub(super) fn parse_block_common(
670679
&mut self,
671680
lo: Span,
672681
blk_mode: BlockCheckMode,
673682
can_be_struct_literal: bool,
683+
loop_header: Option<Span>,
674684
) -> PResult<'a, (AttrVec, P<Block>)> {
675685
maybe_whole!(self, NtBlock, |block| (AttrVec::new(), block));
676686

677687
let maybe_ident = self.prev_token.clone();
678-
self.maybe_recover_unexpected_block_label();
688+
self.maybe_recover_unexpected_block_label(loop_header);
679689
if !self.eat(exp!(OpenBrace)) {
680690
return self.error_block_no_opening_brace();
681691
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// see https://github.com/rust-lang/rust/issues/138585
2+
#![allow(break_with_label_and_loop)] // doesn't work locally
3+
4+
fn main() {
5+
loop 'a: {}
6+
//~^ ERROR: block label not supported here
7+
//~| HELP: if you meant to label the loop, move this label before the loop
8+
while false 'a: {}
9+
//~^ ERROR: block label not supported here
10+
//~| HELP: if you meant to label the loop, move this label before the loop
11+
for i in [0] 'a: {}
12+
//~^ ERROR: block label not supported here
13+
//~| HELP: if you meant to label the loop, move this label before the loop
14+
'a: loop {
15+
// first block is parsed as the break expr's value with or without parens
16+
while break 'a 'b: {} 'c: {}
17+
//~^ ERROR: block label not supported here
18+
//~| HELP: if you meant to label the loop, move this label before the loop
19+
while break 'a ('b: {}) 'c: {}
20+
//~^ ERROR: block label not supported here
21+
//~| HELP: if you meant to label the loop, move this label before the loop
22+
23+
// without the parens, the first block is parsed as the while-loop's body
24+
// (see the 'no errors' section)
25+
// #[allow(break_with_label_and_loop)] (doesn't work locally)
26+
while (break 'a {}) 'c: {}
27+
//~^ ERROR: block label not supported here
28+
//~| HELP: if you meant to label the loop, move this label before the loop
29+
}
30+
31+
// do not suggest moving the label if there is already a label on the loop
32+
'a: loop 'b: {}
33+
//~^ ERROR: block label not supported here
34+
//~| HELP: remove this block label
35+
'a: while false 'b: {}
36+
//~^ ERROR: block label not supported here
37+
//~| HELP: remove this block label
38+
'a: for i in [0] 'b: {}
39+
//~^ ERROR: block label not supported here
40+
//~| HELP: remove this block label
41+
'a: loop {
42+
// first block is parsed as the break expr's value with or without parens
43+
'd: while break 'a 'b: {} 'c: {}
44+
//~^ ERROR: block label not supported here
45+
//~| HELP: remove this block label
46+
'd: while break 'a ('b: {}) 'c: {}
47+
//~^ ERROR: block label not supported here
48+
//~| HELP: remove this block label
49+
50+
// without the parens, the first block is parsed as the while-loop's body
51+
// (see the 'no errors' section)
52+
// #[allow(break_with_label_and_loop)] (doesn't work locally)
53+
'd: while (break 'a {}) 'c: {}
54+
//~^ ERROR: block label not supported here
55+
//~| HELP: remove this block label
56+
}
57+
58+
// no errors
59+
loop { 'a: {} }
60+
'a: loop { 'b: {} }
61+
while false { 'a: {} }
62+
'a: while false { 'b: {} }
63+
for i in [0] { 'a: {} }
64+
'a: for i in [0] { 'b: {} }
65+
'a: {}
66+
'a: { 'b: {} }
67+
'a: loop {
68+
// first block is parsed as the break expr's value if it is a labeled block
69+
while break 'a 'b: {} {}
70+
'd: while break 'a 'b: {} {}
71+
while break 'a ('b: {}) {}
72+
'd: while break 'a ('b: {}) {}
73+
// first block is parsed as the while-loop's body if it has no label
74+
// (the break expr is parsed as having no value),
75+
// so the second block is a normal stmt-block, and the label is allowed
76+
while break 'a {} 'c: {}
77+
while break 'a {} {}
78+
'd: while break 'a {} 'c: {}
79+
'd: while break 'a {} {}
80+
}
81+
82+
// unrelated errors that should not be affected
83+
'a: 'b: {}
84+
//~^ ERROR: expected `while`, `for`, `loop` or `{` after a label
85+
//~| HELP: consider removing the label
86+
loop { while break 'b: {} {} }
87+
//~^ ERROR: parentheses are required around this expression to avoid confusion with a labeled break expression
88+
//~| HELP: wrap the expression in parentheses
89+
//~| ERROR: `break` or `continue` with no label in the condition of a `while` loop [E0590]
90+
}
There was a problem loading the remainder of the diff.

0 commit comments

Comments
 (0)
Failed to load comments.