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

Merged
Merged
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 @@ -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>,
Expand Down
50 changes: 28 additions & 22 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_hir as hir;
use rustc_hir::attrs::AttributeKind;
use rustc_hir::def::DefKind;
Expand Down Expand Up @@ -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,
},
}

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 {
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 });
}
}

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) })
}
}
2 changes: 1 addition & 1 deletion compiler/rustc_mir_build/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_mir_build/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_mir_build/src/thir/cx/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
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;
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]
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,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
|
Expand Down Expand Up @@ -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
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