diff options
| author | Folkert de Vries <folkert@folkertdev.nl> | 2025-07-25 15:18:51 +0200 |
|---|---|---|
| committer | Folkert de Vries <folkert@folkertdev.nl> | 2025-08-01 00:28:52 +0200 |
| commit | 040f71e8123b5994177f787e64aecd9b06cdfa7e (patch) | |
| tree | ad58842198cb15cea7ae812e8d2881ab5832312e | |
| parent | adcb3d3b4cd3b7c4cde642f3ed537037f293738e (diff) | |
| download | rust-040f71e8123b5994177f787e64aecd9b06cdfa7e.tar.gz rust-040f71e8123b5994177f787e64aecd9b06cdfa7e.zip | |
loop match: error on `#[const_continue]` outside `#[loop_match]`
| -rw-r--r-- | compiler/rustc_hir/src/hir.rs | 2 | ||||
| -rw-r--r-- | compiler/rustc_hir_typeck/src/loops.rs | 50 | ||||
| -rw-r--r-- | compiler/rustc_mir_build/messages.ftl | 2 | ||||
| -rw-r--r-- | compiler/rustc_mir_build/src/errors.rs | 4 | ||||
| -rw-r--r-- | compiler/rustc_mir_build/src/thir/cx/expr.rs | 4 | ||||
| -rw-r--r-- | tests/ui/loop-match/const-continue-to-block.rs | 21 | ||||
| -rw-r--r-- | tests/ui/loop-match/const-continue-to-block.stderr | 17 | ||||
| -rw-r--r-- | tests/ui/loop-match/invalid.rs | 19 | ||||
| -rw-r--r-- | tests/ui/loop-match/invalid.stderr | 22 |
9 files changed, 107 insertions, 34 deletions
diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index 1b1b3ced44d..08361718108 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -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>, diff --git a/compiler/rustc_hir_typeck/src/loops.rs b/compiler/rustc_hir_typeck/src/loops.rs index d47a3246964..acfa5c473aa 100644 --- a/compiler/rustc_hir_typeck/src/loops.rs +++ b/compiler/rustc_hir_typeck/src/loops.rs @@ -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) }) } } diff --git a/compiler/rustc_mir_build/messages.ftl b/compiler/rustc_mir_build/messages.ftl index abfe8eb66dd..287639de663 100644 --- a/compiler/rustc_mir_build/messages.ftl +++ b/compiler/rustc_mir_build/messages.ftl @@ -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 diff --git a/compiler/rustc_mir_build/src/errors.rs b/compiler/rustc_mir_build/src/errors.rs index f1fbd5c4a49..1a52c6c85cb 100644 --- a/compiler/rustc_mir_build/src/errors.rs +++ b/compiler/rustc_mir_build/src/errors.rs @@ -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, } diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs index a0d3913c159..81b0e21a5f5 100644 --- a/compiler/rustc_mir_build/src/thir/cx/expr.rs +++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs @@ -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 { diff --git a/tests/ui/loop-match/const-continue-to-block.rs b/tests/ui/loop-match/const-continue-to-block.rs index fd7ebeefeb6..a0f60aaec33 100644 --- a/tests/ui/loop-match/const-continue-to-block.rs +++ b/tests/ui/loop-match/const-continue-to-block.rs @@ -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]` + } + } + } + } +} diff --git a/tests/ui/loop-match/const-continue-to-block.stderr b/tests/ui/loop-match/const-continue-to-block.stderr index 3a5339a0394..f4e223bcff1 100644 --- a/tests/ui/loop-match/const-continue-to-block.stderr +++ b/tests/ui/loop-match/const-continue-to-block.stderr @@ -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 diff --git a/tests/ui/loop-match/invalid.rs b/tests/ui/loop-match/invalid.rs index 0c47b1e0057..08eaf832f56 100644 --- a/tests/ui/loop-match/invalid.rs +++ b/tests/ui/loop-match/invalid.rs @@ -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] diff --git a/tests/ui/loop-match/invalid.stderr b/tests/ui/loop-match/invalid.stderr index 70f246caa9c..9e9796a2ea7 100644 --- a/tests/ui/loop-match/invalid.stderr +++ b/tests/ui/loop-match/invalid.stderr @@ -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`. |
