If a label is placed on the block of a loop instead of the header, suggest moving it to the header.

This commit is contained in:
Zachary S 2025-03-17 00:25:15 -05:00
parent 227690a258
commit f478853f42
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,11 +2898,17 @@ 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| {
err.span_label(lo, "while parsing the body of this `while` expression");
err.span_label(cond.span, "this `while` condition successfully parsed");
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
})?;
self.recover_loop_else("while", lo)?;
@ -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`.