diff options
| author | Zachary S <zasample18+github@gmail.com> | 2025-03-17 00:25:15 -0500 | 
|---|---|---|
| committer | Zachary S <zasample18+github@gmail.com> | 2025-03-17 01:59:37 -0500 | 
| commit | f478853f425fc0207add653b48c49c937acaa94e (patch) | |
| tree | 3f7fe7d662114e8b9f812c37cfe967072858265d | |
| parent | 227690a258492c84ae9927d18289208d0180e62f (diff) | |
| download | rust-f478853f425fc0207add653b48c49c937acaa94e.tar.gz rust-f478853f425fc0207add653b48c49c937acaa94e.zip | |
If a label is placed on the block of a loop instead of the header, suggest moving it to the header.
| -rw-r--r-- | compiler/rustc_parse/src/parser/diagnostics.rs | 29 | ||||
| -rw-r--r-- | compiler/rustc_parse/src/parser/expr.rs | 36 | ||||
| -rw-r--r-- | compiler/rustc_parse/src/parser/item.rs | 2 | ||||
| -rw-r--r-- | compiler/rustc_parse/src/parser/mod.rs | 2 | ||||
| -rw-r--r-- | compiler/rustc_parse/src/parser/stmt.rs | 22 | ||||
| -rw-r--r-- | tests/ui/loops/label-on-block-suggest-move.rs | 90 | ||||
| -rw-r--r-- | tests/ui/loops/label-on-block-suggest-move.stderr | 140 | 
7 files changed, 296 insertions, 25 deletions
| diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs index 716ababb008..c1cca1186af 100644 --- a/compiler/rustc_parse/src/parser/diagnostics.rs +++ b/compiler/rustc_parse/src/parser/diagnostics.rs @@ -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 } diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 0774324eae7..cd931888fba 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -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)) } diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index 2388463ecfc..f4df4044dd2 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -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;`. diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs index 323c0c1d6d0..a79b4048288 100644 --- a/compiler/rustc_parse/src/parser/mod.rs +++ b/compiler/rustc_parse/src/parser/mod.rs @@ -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)), diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs index 0896bd88b4c..0fe247078d5 100644 --- a/compiler/rustc_parse/src/parser/stmt.rs +++ b/compiler/rustc_parse/src/parser/stmt.rs @@ -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(); } diff --git a/tests/ui/loops/label-on-block-suggest-move.rs b/tests/ui/loops/label-on-block-suggest-move.rs new file mode 100644 index 00000000000..656034cd0e9 --- /dev/null +++ b/tests/ui/loops/label-on-block-suggest-move.rs @@ -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] +} diff --git a/tests/ui/loops/label-on-block-suggest-move.stderr b/tests/ui/loops/label-on-block-suggest-move.stderr new file mode 100644 index 00000000000..66866703ca6 --- /dev/null +++ b/tests/ui/loops/label-on-block-suggest-move.stderr @@ -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`. | 
