about summary refs log tree commit diff
diff options
context:
space:
mode:
authorFolkert de Vries <folkert@folkertdev.nl>2025-07-25 15:18:51 +0200
committerFolkert de Vries <folkert@folkertdev.nl>2025-08-01 00:28:52 +0200
commit040f71e8123b5994177f787e64aecd9b06cdfa7e (patch)
treead58842198cb15cea7ae812e8d2881ab5832312e
parentadcb3d3b4cd3b7c4cde642f3ed537037f293738e (diff)
downloadrust-040f71e8123b5994177f787e64aecd9b06cdfa7e.tar.gz
rust-040f71e8123b5994177f787e64aecd9b06cdfa7e.zip
loop match: error on `#[const_continue]` outside `#[loop_match]`
-rw-r--r--compiler/rustc_hir/src/hir.rs2
-rw-r--r--compiler/rustc_hir_typeck/src/loops.rs50
-rw-r--r--compiler/rustc_mir_build/messages.ftl2
-rw-r--r--compiler/rustc_mir_build/src/errors.rs4
-rw-r--r--compiler/rustc_mir_build/src/thir/cx/expr.rs4
-rw-r--r--tests/ui/loop-match/const-continue-to-block.rs21
-rw-r--r--tests/ui/loop-match/const-continue-to-block.stderr17
-rw-r--r--tests/ui/loop-match/invalid.rs19
-rw-r--r--tests/ui/loop-match/invalid.stderr22
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`.