1
Fork 0

Rollup merge of #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 #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`?
This commit is contained in:
Matthias Krüger 2025-03-19 16:52:56 +01:00 committed by GitHub
commit c3f74bcb39
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 296 additions and 25 deletions

View file

@ -2874,7 +2874,12 @@ impl<'a> Parser<'a> {
first_pat
}
pub(crate) fn maybe_recover_unexpected_block_label(&mut self) -> bool {
/// If `loop_header` is `Some` and an unexpected block label is encountered,
/// it is suggested to be moved just before `loop_header`, else it is suggested to be removed.
pub(crate) fn maybe_recover_unexpected_block_label(
&mut self,
loop_header: Option<Span>,
) -> bool {
// Check for `'a : {`
if !(self.check_lifetime()
&& self.look_ahead(1, |t| *t == token::Colon)
@ -2885,16 +2890,28 @@ impl<'a> Parser<'a> {
let label = self.eat_label().expect("just checked if a label exists");
self.bump(); // eat `:`
let span = label.ident.span.to(self.prev_token.span);
self.dcx()
let mut diag = self
.dcx()
.struct_span_err(span, "block label not supported here")
.with_span_label(span, "not supported here")
.with_tool_only_span_suggestion(
.with_span_label(span, "not supported here");
if let Some(loop_header) = loop_header {
diag.multipart_suggestion(
"if you meant to label the loop, move this label before the loop",
vec![
(label.ident.span.until(self.token.span), String::from("")),
(loop_header.shrink_to_lo(), format!("{}: ", label.ident)),
],
Applicability::MachineApplicable,
);
} else {
diag.tool_only_span_suggestion(
label.ident.span.until(self.token.span),
"remove this block label",
"",
Applicability::MachineApplicable,
)
.emit();
);
}
diag.emit();
true
}

View file

@ -2286,7 +2286,7 @@ impl<'a> Parser<'a> {
});
}
let (attrs, blk) = self.parse_block_common(lo, blk_mode, true)?;
let (attrs, blk) = self.parse_block_common(lo, blk_mode, true, None)?;
Ok(self.mk_expr_with_attrs(blk.span, ExprKind::Block(blk, opt_label), attrs))
}
@ -2851,7 +2851,11 @@ impl<'a> Parser<'a> {
));
}
let (attrs, loop_block) = self.parse_inner_attrs_and_block()?;
let (attrs, loop_block) = self.parse_inner_attrs_and_block(
// Only suggest moving erroneous block label to the loop header
// if there is not already a label there
opt_label.is_none().then_some(lo),
)?;
let kind = ExprKind::ForLoop { pat, iter: expr, body: loop_block, label: opt_label, kind };
@ -2894,7 +2898,13 @@ impl<'a> Parser<'a> {
err.span_label(lo, "while parsing the condition of this `while` expression");
err
})?;
let (attrs, body) = self.parse_inner_attrs_and_block().map_err(|mut err| {
let (attrs, body) = self
.parse_inner_attrs_and_block(
// Only suggest moving erroneous block label to the loop header
// if there is not already a label there
opt_label.is_none().then_some(lo),
)
.map_err(|mut err| {
err.span_label(lo, "while parsing the body of this `while` expression");
err.span_label(cond.span, "this `while` condition successfully parsed");
err
@ -2912,7 +2922,11 @@ impl<'a> Parser<'a> {
/// Parses `loop { ... }` (`loop` token already eaten).
fn parse_expr_loop(&mut self, opt_label: Option<Label>, lo: Span) -> PResult<'a, P<Expr>> {
let loop_span = self.prev_token.span;
let (attrs, body) = self.parse_inner_attrs_and_block()?;
let (attrs, body) = self.parse_inner_attrs_and_block(
// Only suggest moving erroneous block label to the loop header
// if there is not already a label there
opt_label.is_none().then_some(lo),
)?;
self.recover_loop_else("loop", lo)?;
Ok(self.mk_expr_with_attrs(
lo.to(self.prev_token.span),
@ -2962,7 +2976,7 @@ impl<'a> Parser<'a> {
Applicability::MaybeIncorrect, // speculative
);
}
if self.maybe_recover_unexpected_block_label() {
if self.maybe_recover_unexpected_block_label(None) {
e.cancel();
self.bump();
} else {
@ -3376,7 +3390,7 @@ impl<'a> Parser<'a> {
/// Parses a `try {...}` expression (`try` token already eaten).
fn parse_try_block(&mut self, span_lo: Span) -> PResult<'a, P<Expr>> {
let (attrs, body) = self.parse_inner_attrs_and_block()?;
let (attrs, body) = self.parse_inner_attrs_and_block(None)?;
if self.eat_keyword(exp!(Catch)) {
Err(self.dcx().create_err(errors::CatchAfterTry { span: self.prev_token.span }))
} else {
@ -3424,7 +3438,7 @@ impl<'a> Parser<'a> {
}
let capture_clause = self.parse_capture_clause()?;
let decl_span = lo.to(self.prev_token.span);
let (attrs, body) = self.parse_inner_attrs_and_block()?;
let (attrs, body) = self.parse_inner_attrs_and_block(None)?;
let kind = ExprKind::Gen(capture_clause, body, kind, decl_span);
Ok(self.mk_expr_with_attrs(lo.to(self.prev_token.span), kind, attrs))
}

View file

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

View file

@ -1374,7 +1374,7 @@ impl<'a> Parser<'a> {
self.psess.gated_spans.gate(sym::inline_const_pat, span);
}
self.expect_keyword(exp!(Const))?;
let (attrs, blk) = self.parse_inner_attrs_and_block()?;
let (attrs, blk) = self.parse_inner_attrs_and_block(None)?;
let anon_const = AnonConst {
id: DUMMY_NODE_ID,
value: self.mk_expr(blk.span, ExprKind::Block(blk, None)),

View file

@ -482,7 +482,7 @@ impl<'a> Parser<'a> {
/// Parses a block. No inner attributes are allowed.
pub fn parse_block(&mut self) -> PResult<'a, P<Block>> {
let (attrs, block) = self.parse_inner_attrs_and_block()?;
let (attrs, block) = self.parse_inner_attrs_and_block(None)?;
if let [.., last] = &*attrs {
let suggest_to_outer = match &last.kind {
ast::AttrKind::Normal(attr) => attr.item.is_valid_for_outer_style(),
@ -660,22 +660,32 @@ impl<'a> Parser<'a> {
Err(self.error_block_no_opening_brace_msg(Cow::from(msg)))
}
/// Parses a block. Inner attributes are allowed.
pub(super) fn parse_inner_attrs_and_block(&mut self) -> PResult<'a, (AttrVec, P<Block>)> {
self.parse_block_common(self.token.span, BlockCheckMode::Default, true)
/// Parses a block. Inner attributes are allowed, block labels are not.
///
/// If `loop_header` is `Some` and an unexpected block label is encountered,
/// it is suggested to be moved just before `loop_header`, else it is suggested to be removed.
pub(super) fn parse_inner_attrs_and_block(
&mut self,
loop_header: Option<Span>,
) -> PResult<'a, (AttrVec, P<Block>)> {
self.parse_block_common(self.token.span, BlockCheckMode::Default, true, loop_header)
}
/// Parses a block. Inner attributes are allowed.
/// Parses a block. Inner attributes are allowed, block labels are not.
///
/// If `loop_header` is `Some` and an unexpected block label is encountered,
/// it is suggested to be moved just before `loop_header`, else it is suggested to be removed.
pub(super) fn parse_block_common(
&mut self,
lo: Span,
blk_mode: BlockCheckMode,
can_be_struct_literal: bool,
loop_header: Option<Span>,
) -> PResult<'a, (AttrVec, P<Block>)> {
maybe_whole!(self, NtBlock, |block| (AttrVec::new(), block));
let maybe_ident = self.prev_token.clone();
self.maybe_recover_unexpected_block_label();
self.maybe_recover_unexpected_block_label(loop_header);
if !self.eat(exp!(OpenBrace)) {
return self.error_block_no_opening_brace();
}

View file

@ -0,0 +1,90 @@
// see https://github.com/rust-lang/rust/issues/138585
#![allow(break_with_label_and_loop)] // doesn't work locally
fn main() {
loop 'a: {}
//~^ ERROR: block label not supported here
//~| HELP: if you meant to label the loop, move this label before the loop
while false 'a: {}
//~^ ERROR: block label not supported here
//~| HELP: if you meant to label the loop, move this label before the loop
for i in [0] 'a: {}
//~^ ERROR: block label not supported here
//~| HELP: if you meant to label the loop, move this label before the loop
'a: loop {
// first block is parsed as the break expr's value with or without parens
while break 'a 'b: {} 'c: {}
//~^ ERROR: block label not supported here
//~| HELP: if you meant to label the loop, move this label before the loop
while break 'a ('b: {}) 'c: {}
//~^ ERROR: block label not supported here
//~| HELP: if you meant to label the loop, move this label before the loop
// without the parens, the first block is parsed as the while-loop's body
// (see the 'no errors' section)
// #[allow(break_with_label_and_loop)] (doesn't work locally)
while (break 'a {}) 'c: {}
//~^ ERROR: block label not supported here
//~| HELP: if you meant to label the loop, move this label before the loop
}
// do not suggest moving the label if there is already a label on the loop
'a: loop 'b: {}
//~^ ERROR: block label not supported here
//~| HELP: remove this block label
'a: while false 'b: {}
//~^ ERROR: block label not supported here
//~| HELP: remove this block label
'a: for i in [0] 'b: {}
//~^ ERROR: block label not supported here
//~| HELP: remove this block label
'a: loop {
// first block is parsed as the break expr's value with or without parens
'd: while break 'a 'b: {} 'c: {}
//~^ ERROR: block label not supported here
//~| HELP: remove this block label
'd: while break 'a ('b: {}) 'c: {}
//~^ ERROR: block label not supported here
//~| HELP: remove this block label
// without the parens, the first block is parsed as the while-loop's body
// (see the 'no errors' section)
// #[allow(break_with_label_and_loop)] (doesn't work locally)
'd: while (break 'a {}) 'c: {}
//~^ ERROR: block label not supported here
//~| HELP: remove this block label
}
// no errors
loop { 'a: {} }
'a: loop { 'b: {} }
while false { 'a: {} }
'a: while false { 'b: {} }
for i in [0] { 'a: {} }
'a: for i in [0] { 'b: {} }
'a: {}
'a: { 'b: {} }
'a: loop {
// first block is parsed as the break expr's value if it is a labeled block
while break 'a 'b: {} {}
'd: while break 'a 'b: {} {}
while break 'a ('b: {}) {}
'd: while break 'a ('b: {}) {}
// first block is parsed as the while-loop's body if it has no label
// (the break expr is parsed as having no value),
// so the second block is a normal stmt-block, and the label is allowed
while break 'a {} 'c: {}
while break 'a {} {}
'd: while break 'a {} 'c: {}
'd: while break 'a {} {}
}
// unrelated errors that should not be affected
'a: 'b: {}
//~^ ERROR: expected `while`, `for`, `loop` or `{` after a label
//~| HELP: consider removing the label
loop { while break 'b: {} {} }
//~^ ERROR: parentheses are required around this expression to avoid confusion with a labeled break expression
//~| HELP: wrap the expression in parentheses
//~| ERROR: `break` or `continue` with no label in the condition of a `while` loop [E0590]
}

View file

@ -0,0 +1,140 @@
error: block label not supported here
--> $DIR/label-on-block-suggest-move.rs:5:10
|
LL | loop 'a: {}
| ^^^ not supported here
|
help: if you meant to label the loop, move this label before the loop
|
LL - loop 'a: {}
LL + 'a: loop {}
|
error: block label not supported here
--> $DIR/label-on-block-suggest-move.rs:8:17
|
LL | while false 'a: {}
| ^^^ not supported here
|
help: if you meant to label the loop, move this label before the loop
|
LL - while false 'a: {}
LL + 'a: while false {}
|
error: block label not supported here
--> $DIR/label-on-block-suggest-move.rs:11:18
|
LL | for i in [0] 'a: {}
| ^^^ not supported here
|
help: if you meant to label the loop, move this label before the loop
|
LL - for i in [0] 'a: {}
LL + 'a: for i in [0] {}
|
error: block label not supported here
--> $DIR/label-on-block-suggest-move.rs:16:31
|
LL | while break 'a 'b: {} 'c: {}
| ^^^ not supported here
|
help: if you meant to label the loop, move this label before the loop
|
LL - while break 'a 'b: {} 'c: {}
LL + 'c: while break 'a 'b: {} {}
|
error: block label not supported here
--> $DIR/label-on-block-suggest-move.rs:19:33
|
LL | while break 'a ('b: {}) 'c: {}
| ^^^ not supported here
|
help: if you meant to label the loop, move this label before the loop
|
LL - while break 'a ('b: {}) 'c: {}
LL + 'c: while break 'a ('b: {}) {}
|
error: block label not supported here
--> $DIR/label-on-block-suggest-move.rs:26:29
|
LL | while (break 'a {}) 'c: {}
| ^^^ not supported here
|
help: if you meant to label the loop, move this label before the loop
|
LL - while (break 'a {}) 'c: {}
LL + 'c: while (break 'a {}) {}
|
error: block label not supported here
--> $DIR/label-on-block-suggest-move.rs:32:14
|
LL | 'a: loop 'b: {}
| ^^^ not supported here
error: block label not supported here
--> $DIR/label-on-block-suggest-move.rs:35:21
|
LL | 'a: while false 'b: {}
| ^^^ not supported here
error: block label not supported here
--> $DIR/label-on-block-suggest-move.rs:38:22
|
LL | 'a: for i in [0] 'b: {}
| ^^^ not supported here
error: block label not supported here
--> $DIR/label-on-block-suggest-move.rs:43:35
|
LL | 'd: while break 'a 'b: {} 'c: {}
| ^^^ not supported here
error: block label not supported here
--> $DIR/label-on-block-suggest-move.rs:46:37
|
LL | 'd: while break 'a ('b: {}) 'c: {}
| ^^^ not supported here
error: block label not supported here
--> $DIR/label-on-block-suggest-move.rs:53:33
|
LL | 'd: while (break 'a {}) 'c: {}
| ^^^ not supported here
error: expected `while`, `for`, `loop` or `{` after a label
--> $DIR/label-on-block-suggest-move.rs:83:9
|
LL | 'a: 'b: {}
| ^^ expected `while`, `for`, `loop` or `{` after a label
|
help: consider removing the label
|
LL - 'a: 'b: {}
LL + 'b: {}
|
error: parentheses are required around this expression to avoid confusion with a labeled break expression
--> $DIR/label-on-block-suggest-move.rs:86:24
|
LL | loop { while break 'b: {} {} }
| ^^^^^^
|
help: wrap the expression in parentheses
|
LL | loop { while break ('b: {}) {} }
| + +
error[E0590]: `break` or `continue` with no label in the condition of a `while` loop
--> $DIR/label-on-block-suggest-move.rs:86:18
|
LL | loop { while break 'b: {} {} }
| ^^^^^^^^^^^^ unlabeled `break` in the condition of a `while` loop
error: aborting due to 15 previous errors
For more information about this error, try `rustc --explain E0590`.