Skip to content

loop match: error on #[const_continue] outside #[loop_match] #143360

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compiler/rustc_hir/src/hir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2992,7 +2992,7 @@ impl fmt::Display for LoopIdError {
}
}

#[derive(Copy, Clone, Debug, HashStable_Generic)]
#[derive(Copy, Clone, Debug, PartialEq, HashStable_Generic)]
pub struct Destination {
/// This is `Some(_)` iff there is an explicit user-specified 'label
pub label: Option<Label>,
Expand Down
34 changes: 20 additions & 14 deletions compiler/rustc_hir_typeck/src/loops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ use std::collections::BTreeMap;
use std::fmt;

use Context::*;
use rustc_ast::Label;
use rustc_attr_data_structures::{AttributeKind, find_attr};
use rustc_hir as hir;
use rustc_hir::def::DefKind;
Expand Down Expand Up @@ -43,7 +42,7 @@ enum Context {
/// E.g. `#[loop_match] loop { state = 'label: { /* ... */ } }`.
LoopMatch {
/// The label of the labeled block (not of the loop itself).
labeled_block: Label,
labeled_block: Destination,
},
}

Expand Down Expand Up @@ -186,18 +185,18 @@ impl<'hir> Visitor<'hir> for CheckLoopVisitor<'hir> {
{
self.with_context(UnlabeledBlock(b.span.shrink_to_lo()), |v| v.visit_block(b));
}
hir::ExprKind::Break(break_label, ref opt_expr) => {
hir::ExprKind::Break(break_destination, ref opt_expr) => {
if let Some(e) = opt_expr {
self.visit_expr(e);
}

if self.require_label_in_labeled_block(e.span, &break_label, "break") {
if self.require_label_in_labeled_block(e.span, &break_destination, "break") {
// If we emitted an error about an unlabeled break in a labeled
// block, we don't need any further checking for this break any more
return;
}

let loop_id = match break_label.target_id {
let loop_id = match break_destination.target_id {
Ok(loop_id) => Some(loop_id),
Err(hir::LoopIdError::OutsideLoopScope) => None,
Err(hir::LoopIdError::UnlabeledCfInWhileCondition) => {
Expand All @@ -212,18 +211,25 @@ impl<'hir> Visitor<'hir> for CheckLoopVisitor<'hir> {

// A `#[const_continue]` must break to a block in a `#[loop_match]`.
if find_attr!(self.tcx.hir_attrs(e.hir_id), AttributeKind::ConstContinue(_)) {
if let Some(break_label) = break_label.label {
if let Some(label) = break_destination.label {
let is_target_label = |cx: &Context| match cx {
Context::LoopMatch { labeled_block } => {
break_label.ident.name == labeled_block.ident.name
// NOTE: with macro expansion, the label might look different here
// even though it does still refer to the same HIR node. A block
// can't have two labels, so the hir_id is a unique identifier.
assert!(labeled_block.target_id.is_ok()); // see `is_loop_match`.
break_destination.target_id == labeled_block.target_id
}
_ => false,
};

if !self.cx_stack.iter().rev().any(is_target_label) {
let span = break_label.ident.span;
let span = label.ident.span;
self.tcx.dcx().emit_fatal(ConstContinueBadLabel { span });
}
} else {
let span = e.span;
self.tcx.dcx().emit_fatal(ConstContinueBadLabel { span });
}
}

Expand All @@ -249,7 +255,7 @@ impl<'hir> Visitor<'hir> for CheckLoopVisitor<'hir> {
Some(kind) => {
let suggestion = format!(
"break{}",
break_label
break_destination
.label
.map_or_else(String::new, |l| format!(" {}", l.ident))
);
Expand All @@ -259,7 +265,7 @@ impl<'hir> Visitor<'hir> for CheckLoopVisitor<'hir> {
kind: kind.name(),
suggestion,
loop_label,
break_label: break_label.label,
break_label: break_destination.label,
break_expr_kind: &break_expr.kind,
break_expr_span: break_expr.span,
});
Expand All @@ -268,7 +274,7 @@ impl<'hir> Visitor<'hir> for CheckLoopVisitor<'hir> {
}

let sp_lo = e.span.with_lo(e.span.lo() + BytePos("break".len() as u32));
let label_sp = match break_label.label {
let label_sp = match break_destination.label {
Some(label) => sp_lo.with_hi(label.ident.span.hi()),
None => sp_lo.shrink_to_lo(),
};
Expand Down Expand Up @@ -416,7 +422,7 @@ impl<'hir> CheckLoopVisitor<'hir> {
&self,
e: &'hir hir::Expr<'hir>,
body: &'hir hir::Block<'hir>,
) -> Option<Label> {
) -> Option<Destination> {
if !find_attr!(self.tcx.hir_attrs(e.hir_id), AttributeKind::LoopMatch(_)) {
return None;
}
Expand All @@ -438,8 +444,8 @@ impl<'hir> CheckLoopVisitor<'hir> {

let hir::ExprKind::Assign(_, rhs_expr, _) = loop_body_expr.kind else { return None };

let hir::ExprKind::Block(_, label) = rhs_expr.kind else { return None };
let hir::ExprKind::Block(block, label) = rhs_expr.kind else { return None };

label
Some(Destination { label, target_id: Ok(block.hir_id) })
}
}
13 changes: 13 additions & 0 deletions tests/ui/loop-match/const-continue-outside-loop-match.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Test that the correct error is emitted when `#[const_continue]` occurs outside of
// a loop match. See also https://github.com/rust-lang/rust/issues/143165.
#![allow(incomplete_features)]
#![feature(loop_match)]
#![crate_type = "lib"]

fn main() {
loop {
#[const_continue]
break ();
//~^ ERROR `#[const_continue]` must break to a labeled block that participates in a `#[loop_match]`
}
}
8 changes: 8 additions & 0 deletions tests/ui/loop-match/const-continue-outside-loop-match.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
error: `#[const_continue]` must break to a labeled block that participates in a `#[loop_match]`
--> $DIR/const-continue-outside-loop-match.rs:10:9
|
LL | break ();
| ^^^^^^^^

error: aborting due to 1 previous error

21 changes: 21 additions & 0 deletions tests/ui/loop-match/const-continue-to-block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,24 @@ fn const_continue_to_block() -> u8 {
}
}
}

fn const_continue_to_shadowed_block() -> u8 {
let state = 0;
#[loop_match]
loop {
state = 'blk: {
match state {
0 => {
#[const_continue]
break 'blk 1;
}
_ => 'blk: {
//~^ WARN label name `'blk` shadows a label name that is already in scope
#[const_continue]
break 'blk 2;
//~^ ERROR `#[const_continue]` must break to a labeled block that participates in a `#[loop_match]`
}
}
}
}
}
17 changes: 16 additions & 1 deletion tests/ui/loop-match/const-continue-to-block.stderr
Original file line number Diff line number Diff line change
@@ -1,8 +1,23 @@
warning: label name `'blk` shadows a label name that is already in scope
--> $DIR/const-continue-to-block.rs:38:22
|
LL | state = 'blk: {
| ---- first declared here
...
LL | _ => 'blk: {
| ^^^^ label `'blk` already in scope

error: `#[const_continue]` must break to a labeled block that participates in a `#[loop_match]`
--> $DIR/const-continue-to-block.rs:20:27
|
LL | break 'b 2;
| ^^

error: aborting due to 1 previous error
error: `#[const_continue]` must break to a labeled block that participates in a `#[loop_match]`
--> $DIR/const-continue-to-block.rs:41:27
|
LL | break 'blk 2;
| ^^^^

error: aborting due to 2 previous errors; 1 warning emitted

19 changes: 19 additions & 0 deletions tests/ui/loop-match/invalid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,25 @@ fn break_without_value_unit() {
}
}

fn break_without_label() {
let mut state = State::A;
#[loop_match]
loop {
state = 'blk: {
match state {
_ => {
// The type error is because the break breaks from the
// loop, and hence attempts to return `State::A`.
#[const_continue]
break State::A;
//~^ ERROR unlabeled `break` inside of a labeled block
//~| ERROR mismatched types
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is mismatched types coming from?..

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the break is seen as a break from the loop with a value, and it's the final expression, so that value is returned, but the function's type says it'll return unit.

}
}
}
}
}

fn arm_has_guard(cond: bool) {
let mut state = State::A;
#[loop_match]
Expand Down
22 changes: 17 additions & 5 deletions tests/ui/loop-match/invalid.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,18 @@ help: give the `break` a value of the expected type
LL | break 'blk /* value */;
| +++++++++++

error[E0695]: unlabeled `break` inside of a labeled block
--> $DIR/invalid.rs:155:21
|
LL | break State::A;
| ^^^^^^^^^^^^^^ `break` statements that would diverge to or through a labeled block need to bear a label

error[E0308]: mismatched types
--> $DIR/invalid.rs:155:27
|
LL | break State::A;
| ^^^^^^^^ expected `()`, found `State`

error: invalid update of the `#[loop_match]` state
--> $DIR/invalid.rs:18:9
|
Expand Down Expand Up @@ -81,13 +93,13 @@ LL | break 'blk;
| ^^^^^^^^^^

error: match arms that are part of a `#[loop_match]` cannot have guards
--> $DIR/invalid.rs:155:29
--> $DIR/invalid.rs:174:29
|
LL | State::B if cond => break 'a,
| ^^^^

error[E0004]: non-exhaustive patterns: `State::B` and `State::C` not covered
--> $DIR/invalid.rs:168:19
--> $DIR/invalid.rs:187:19
|
LL | match state {
| ^^^^^ patterns `State::B` and `State::C` not covered
Expand All @@ -110,12 +122,12 @@ LL ~ State::B | State::C => todo!(),
|

error[E0579]: lower range bound must be less than upper
--> $DIR/invalid.rs:185:17
--> $DIR/invalid.rs:204:17
|
LL | 4.0..3.0 => {
| ^^^^^^^^

error: aborting due to 14 previous errors
error: aborting due to 16 previous errors

Some errors have detailed explanations: E0004, E0308, E0579.
Some errors have detailed explanations: E0004, E0308, E0579, E0695.
For more information about an error, try `rustc --explain E0004`.
Loading