Skip to content

Commit 040f71e

Browse files
committed
loop match: error on #[const_continue] outside #[loop_match]
1 parent adcb3d3 commit 040f71e

File tree

9 files changed

+107
-34
lines changed

9 files changed

+107
-34
lines changed

compiler/rustc_hir/src/hir.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3016,7 +3016,7 @@ impl fmt::Display for LoopIdError {
30163016
}
30173017
}
30183018

3019-
#[derive(Copy, Clone, Debug, HashStable_Generic)]
3019+
#[derive(Copy, Clone, Debug, PartialEq, HashStable_Generic)]
30203020
pub struct Destination {
30213021
/// This is `Some(_)` iff there is an explicit user-specified 'label
30223022
pub label: Option<Label>,

compiler/rustc_hir_typeck/src/loops.rs

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use std::collections::BTreeMap;
22
use std::fmt;
33

44
use Context::*;
5-
use rustc_ast::Label;
65
use rustc_hir as hir;
76
use rustc_hir::attrs::AttributeKind;
87
use rustc_hir::def::DefKind;
@@ -42,8 +41,8 @@ enum Context {
4241
ConstBlock,
4342
/// E.g. `#[loop_match] loop { state = 'label: { /* ... */ } }`.
4443
LoopMatch {
45-
/// The label of the labeled block (not of the loop itself).
46-
labeled_block: Label,
44+
/// The destination pointing to the labeled block (not to the loop itself).
45+
labeled_block: Destination,
4746
},
4847
}
4948

@@ -186,18 +185,18 @@ impl<'hir> Visitor<'hir> for CheckLoopVisitor<'hir> {
186185
{
187186
self.with_context(UnlabeledBlock(b.span.shrink_to_lo()), |v| v.visit_block(b));
188187
}
189-
hir::ExprKind::Break(break_label, ref opt_expr) => {
188+
hir::ExprKind::Break(break_destination, ref opt_expr) => {
190189
if let Some(e) = opt_expr {
191190
self.visit_expr(e);
192191
}
193192

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

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

213212
// A `#[const_continue]` must break to a block in a `#[loop_match]`.
214213
if find_attr!(self.tcx.hir_attrs(e.hir_id), AttributeKind::ConstContinue(_)) {
215-
if let Some(break_label) = break_label.label {
216-
let is_target_label = |cx: &Context| match cx {
217-
Context::LoopMatch { labeled_block } => {
218-
break_label.ident.name == labeled_block.ident.name
219-
}
220-
_ => false,
221-
};
214+
let Some(label) = break_destination.label else {
215+
let span = e.span;
216+
self.tcx.dcx().emit_fatal(ConstContinueBadLabel { span });
217+
};
222218

223-
if !self.cx_stack.iter().rev().any(is_target_label) {
224-
let span = break_label.ident.span;
225-
self.tcx.dcx().emit_fatal(ConstContinueBadLabel { span });
219+
let is_target_label = |cx: &Context| match cx {
220+
Context::LoopMatch { labeled_block } => {
221+
// NOTE: with macro expansion, the label's span might be different here
222+
// even though it does still refer to the same HIR node. A block
223+
// can't have two labels, so the hir_id is a unique identifier.
224+
assert!(labeled_block.target_id.is_ok()); // see `is_loop_match`.
225+
break_destination.target_id == labeled_block.target_id
226226
}
227+
_ => false,
228+
};
229+
230+
if !self.cx_stack.iter().rev().any(is_target_label) {
231+
let span = label.ident.span;
232+
self.tcx.dcx().emit_fatal(ConstContinueBadLabel { span });
227233
}
228234
}
229235

@@ -249,7 +255,7 @@ impl<'hir> Visitor<'hir> for CheckLoopVisitor<'hir> {
249255
Some(kind) => {
250256
let suggestion = format!(
251257
"break{}",
252-
break_label
258+
break_destination
253259
.label
254260
.map_or_else(String::new, |l| format!(" {}", l.ident))
255261
);
@@ -259,7 +265,7 @@ impl<'hir> Visitor<'hir> for CheckLoopVisitor<'hir> {
259265
kind: kind.name(),
260266
suggestion,
261267
loop_label,
262-
break_label: break_label.label,
268+
break_label: break_destination.label,
263269
break_expr_kind: &break_expr.kind,
264270
break_expr_span: break_expr.span,
265271
});
@@ -268,7 +274,7 @@ impl<'hir> Visitor<'hir> for CheckLoopVisitor<'hir> {
268274
}
269275

270276
let sp_lo = e.span.with_lo(e.span.lo() + BytePos("break".len() as u32));
271-
let label_sp = match break_label.label {
277+
let label_sp = match break_destination.label {
272278
Some(label) => sp_lo.with_hi(label.ident.span.hi()),
273279
None => sp_lo.shrink_to_lo(),
274280
};
@@ -416,7 +422,7 @@ impl<'hir> CheckLoopVisitor<'hir> {
416422
&self,
417423
e: &'hir hir::Expr<'hir>,
418424
body: &'hir hir::Block<'hir>,
419-
) -> Option<Label> {
425+
) -> Option<Destination> {
420426
if !find_attr!(self.tcx.hir_attrs(e.hir_id), AttributeKind::LoopMatch(_)) {
421427
return None;
422428
}
@@ -438,8 +444,8 @@ impl<'hir> CheckLoopVisitor<'hir> {
438444

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

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

443-
label
449+
Some(Destination { label, target_id: Ok(block.hir_id) })
444450
}
445451
}

compiler/rustc_mir_build/messages.ftl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ mir_build_confused = missing patterns are not covered because `{$variable}` is i
8787
mir_build_const_continue_bad_const = could not determine the target branch for this `#[const_continue]`
8888
.label = this value is too generic
8989
90-
mir_build_const_continue_missing_value = a `#[const_continue]` must break to a label with a value
90+
mir_build_const_continue_missing_label_or_value = a `#[const_continue]` must break to a label with a value
9191
9292
mir_build_const_continue_not_const = could not determine the target branch for this `#[const_continue]`
9393
.help = try extracting the expression into a `const` item

compiler/rustc_mir_build/src/errors.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1254,8 +1254,8 @@ pub(crate) struct ConstContinueBadConst {
12541254
}
12551255

12561256
#[derive(Diagnostic)]
1257-
#[diag(mir_build_const_continue_missing_value)]
1258-
pub(crate) struct ConstContinueMissingValue {
1257+
#[diag(mir_build_const_continue_missing_label_or_value)]
1258+
pub(crate) struct ConstContinueMissingLabelOrValue {
12591259
#[primary_span]
12601260
pub span: Span,
12611261
}

compiler/rustc_mir_build/src/thir/cx/expr.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -852,9 +852,9 @@ impl<'tcx> ThirBuildCx<'tcx> {
852852
if find_attr!(self.tcx.hir_attrs(expr.hir_id), AttributeKind::ConstContinue(_)) {
853853
match dest.target_id {
854854
Ok(target_id) => {
855-
let Some(value) = value else {
855+
let (Some(value), Some(_)) = (value, dest.label) else {
856856
let span = expr.span;
857-
self.tcx.dcx().emit_fatal(ConstContinueMissingValue { span })
857+
self.tcx.dcx().emit_fatal(ConstContinueMissingLabelOrValue { span })
858858
};
859859

860860
ExprKind::ConstContinue {

tests/ui/loop-match/const-continue-to-block.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,24 @@ fn const_continue_to_block() -> u8 {
2424
}
2525
}
2626
}
27+
28+
fn const_continue_to_shadowed_block() -> u8 {
29+
let state = 0;
30+
#[loop_match]
31+
loop {
32+
state = 'blk: {
33+
match state {
34+
0 => {
35+
#[const_continue]
36+
break 'blk 1;
37+
}
38+
_ => 'blk: {
39+
//~^ WARN label name `'blk` shadows a label name that is already in scope
40+
#[const_continue]
41+
break 'blk 2;
42+
//~^ ERROR `#[const_continue]` must break to a labeled block that participates in a `#[loop_match]`
43+
}
44+
}
45+
}
46+
}
47+
}
Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,23 @@
1+
warning: label name `'blk` shadows a label name that is already in scope
2+
--> $DIR/const-continue-to-block.rs:38:22
3+
|
4+
LL | state = 'blk: {
5+
| ---- first declared here
6+
...
7+
LL | _ => 'blk: {
8+
| ^^^^ label `'blk` already in scope
9+
110
error: `#[const_continue]` must break to a labeled block that participates in a `#[loop_match]`
211
--> $DIR/const-continue-to-block.rs:20:27
312
|
413
LL | break 'b 2;
514
| ^^
615

7-
error: aborting due to 1 previous error
16+
error: `#[const_continue]` must break to a labeled block that participates in a `#[loop_match]`
17+
--> $DIR/const-continue-to-block.rs:41:27
18+
|
19+
LL | break 'blk 2;
20+
| ^^^^
21+
22+
error: aborting due to 2 previous errors; 1 warning emitted
823

tests/ui/loop-match/invalid.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,25 @@ fn break_without_value_unit() {
142142
}
143143
}
144144

145+
fn break_without_label() {
146+
let mut state = State::A;
147+
let _ = {
148+
#[loop_match]
149+
loop {
150+
state = 'blk: {
151+
match state {
152+
_ => {
153+
#[const_continue]
154+
break State::A;
155+
//~^ ERROR unlabeled `break` inside of a labeled block
156+
//~| ERROR a `#[const_continue]` must break to a label with a value
157+
}
158+
}
159+
}
160+
}
161+
};
162+
}
163+
145164
fn arm_has_guard(cond: bool) {
146165
let mut state = State::A;
147166
#[loop_match]

tests/ui/loop-match/invalid.stderr

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ help: give the `break` a value of the expected type
99
LL | break 'blk /* value */;
1010
| +++++++++++
1111

12+
error[E0695]: unlabeled `break` inside of a labeled block
13+
--> $DIR/invalid.rs:154:25
14+
|
15+
LL | break State::A;
16+
| ^^^^^^^^^^^^^^ `break` statements that would diverge to or through a labeled block need to bear a label
17+
1218
error: invalid update of the `#[loop_match]` state
1319
--> $DIR/invalid.rs:18:9
1420
|
@@ -80,14 +86,20 @@ error: a `#[const_continue]` must break to a label with a value
8086
LL | break 'blk;
8187
| ^^^^^^^^^^
8288

89+
error: a `#[const_continue]` must break to a label with a value
90+
--> $DIR/invalid.rs:154:25
91+
|
92+
LL | break State::A;
93+
| ^^^^^^^^^^^^^^
94+
8395
error: match arms that are part of a `#[loop_match]` cannot have guards
84-
--> $DIR/invalid.rs:155:29
96+
--> $DIR/invalid.rs:174:29
8597
|
8698
LL | State::B if cond => break 'a,
8799
| ^^^^
88100

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

112124
error[E0579]: lower range bound must be less than upper
113-
--> $DIR/invalid.rs:185:17
125+
--> $DIR/invalid.rs:204:17
114126
|
115127
LL | 4.0..3.0 => {
116128
| ^^^^^^^^
117129

118-
error: aborting due to 14 previous errors
130+
error: aborting due to 16 previous errors
119131

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

0 commit comments

Comments
 (0)