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:
parent
227690a258
commit
f478853f42
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