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

This commit is contained in:
Folkert de Vries 2025-07-25 15:18:51 +02:00
parent adcb3d3b4c
commit 040f71e812
No known key found for this signature in database
GPG key ID: 1F17F6FFD112B97C
9 changed files with 107 additions and 34 deletions

View file

@ -3016,7 +3016,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>,

View file

@ -2,7 +2,6 @@ use std::collections::BTreeMap;
use std::fmt;
use Context::*;
use rustc_ast::Label;
use rustc_hir as hir;
use rustc_hir::attrs::AttributeKind;
use rustc_hir::def::DefKind;
@ -42,8 +41,8 @@ enum Context {
ConstBlock,
/// E.g. `#[loop_match] loop { state = 'label: { /* ... */ } }`.
LoopMatch {
/// The label of the labeled block (not of the loop itself).
labeled_block: Label,
/// The destination pointing to the labeled block (not to the loop itself).
labeled_block: Destination,
},
}
@ -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) => {
@ -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 {
let is_target_label = |cx: &Context| match cx {
Context::LoopMatch { labeled_block } => {
break_label.ident.name == labeled_block.ident.name
}
_ => false,
};
let Some(label) = break_destination.label else {
let span = e.span;
self.tcx.dcx().emit_fatal(ConstContinueBadLabel { span });
};
if !self.cx_stack.iter().rev().any(is_target_label) {
let span = break_label.ident.span;
self.tcx.dcx().emit_fatal(ConstContinueBadLabel { span });
let is_target_label = |cx: &Context| match cx {
Context::LoopMatch { labeled_block } => {
// NOTE: with macro expansion, the label's span might be 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 = label.ident.span;
self.tcx.dcx().emit_fatal(ConstContinueBadLabel { span });
}
}
@ -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))
);
@ -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,
});
@ -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(),
};
@ -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;
}
@ -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) })
}
}

View file

@ -87,7 +87,7 @@ mir_build_confused = missing patterns are not covered because `{$variable}` is i
mir_build_const_continue_bad_const = could not determine the target branch for this `#[const_continue]`
.label = this value is too generic
mir_build_const_continue_missing_value = a `#[const_continue]` must break to a label with a value
mir_build_const_continue_missing_label_or_value = a `#[const_continue]` must break to a label with a value
mir_build_const_continue_not_const = could not determine the target branch for this `#[const_continue]`
.help = try extracting the expression into a `const` item

View file

@ -1254,8 +1254,8 @@ pub(crate) struct ConstContinueBadConst {
}
#[derive(Diagnostic)]
#[diag(mir_build_const_continue_missing_value)]
pub(crate) struct ConstContinueMissingValue {
#[diag(mir_build_const_continue_missing_label_or_value)]
pub(crate) struct ConstContinueMissingLabelOrValue {
#[primary_span]
pub span: Span,
}

View file

@ -852,9 +852,9 @@ impl<'tcx> ThirBuildCx<'tcx> {
if find_attr!(self.tcx.hir_attrs(expr.hir_id), AttributeKind::ConstContinue(_)) {
match dest.target_id {
Ok(target_id) => {
let Some(value) = value else {
let (Some(value), Some(_)) = (value, dest.label) else {
let span = expr.span;
self.tcx.dcx().emit_fatal(ConstContinueMissingValue { span })
self.tcx.dcx().emit_fatal(ConstContinueMissingLabelOrValue { span })
};
ExprKind::ConstContinue {

View file

@ -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]`
}
}
}
}
}

View file

@ -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

View file

@ -142,6 +142,25 @@ fn break_without_value_unit() {
}
}
fn break_without_label() {
let mut state = State::A;
let _ = {
#[loop_match]
loop {
state = 'blk: {
match state {
_ => {
#[const_continue]
break State::A;
//~^ ERROR unlabeled `break` inside of a labeled block
//~| ERROR a `#[const_continue]` must break to a label with a value
}
}
}
}
};
}
fn arm_has_guard(cond: bool) {
let mut state = State::A;
#[loop_match]

View file

@ -9,6 +9,12 @@ 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:154:25
|
LL | break State::A;
| ^^^^^^^^^^^^^^ `break` statements that would diverge to or through a labeled block need to bear a label
error: invalid update of the `#[loop_match]` state
--> $DIR/invalid.rs:18:9
|
@ -80,14 +86,20 @@ error: a `#[const_continue]` must break to a label with a value
LL | break 'blk;
| ^^^^^^^^^^
error: a `#[const_continue]` must break to a label with a value
--> $DIR/invalid.rs:154:25
|
LL | break State::A;
| ^^^^^^^^^^^^^^
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
@ -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`.