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:
commit
c3f74bcb39
7 changed files with 296 additions and 25 deletions
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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;`.
|
||||
|
|
|
@ -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)),
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
90
tests/ui/loops/label-on-block-suggest-move.rs
Normal file
90
tests/ui/loops/label-on-block-suggest-move.rs
Normal 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]
|
||||
}
|
140
tests/ui/loops/label-on-block-suggest-move.stderr
Normal file
140
tests/ui/loops/label-on-block-suggest-move.stderr
Normal 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`.
|
Loading…
Add table
Add a link
Reference in a new issue