Skip to content

Commit 771373e

Browse files
committed
loop match: error on #[const_continue] outside #[loop_match]
1 parent 6268d0a commit 771373e

File tree

8 files changed

+113
-21
lines changed

8 files changed

+113
-21
lines changed

compiler/rustc_hir/src/hir.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2992,7 +2992,7 @@ impl fmt::Display for LoopIdError {
29922992
}
29932993
}
29942994

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

compiler/rustc_hir_typeck/src/loops.rs

Lines changed: 20 additions & 14 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_attr_data_structures::{AttributeKind, find_attr};
76
use rustc_hir as hir;
87
use rustc_hir::def::DefKind;
@@ -43,7 +42,7 @@ enum Context {
4342
/// E.g. `#[loop_match] loop { state = 'label: { /* ... */ } }`.
4443
LoopMatch {
4544
/// The label of the labeled block (not of the loop itself).
46-
labeled_block: Label,
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 {
214+
if let Some(label) = break_destination.label {
216215
let is_target_label = |cx: &Context| match cx {
217216
Context::LoopMatch { labeled_block } => {
218-
break_label.ident.name == labeled_block.ident.name
217+
// NOTE: with macro expansion, the label might look different here
218+
// even though it does still refer to the same HIR node. A block
219+
// can't have two labels, so the hir_id is a unique identifier.
220+
assert!(labeled_block.target_id.is_ok()); // see `is_loop_match`.
221+
break_destination.target_id == labeled_block.target_id
219222
}
220223
_ => false,
221224
};
222225

223226
if !self.cx_stack.iter().rev().any(is_target_label) {
224-
let span = break_label.ident.span;
227+
let span = label.ident.span;
225228
self.tcx.dcx().emit_fatal(ConstContinueBadLabel { span });
226229
}
230+
} else {
231+
let span = e.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
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Test that the correct error is emitted when `#[const_continue]` occurs outside of
2+
// a loop match. See also https://github.com/rust-lang/rust/issues/143165.
3+
#![allow(incomplete_features)]
4+
#![feature(loop_match)]
5+
#![crate_type = "lib"]
6+
7+
fn main() {
8+
loop {
9+
#[const_continue]
10+
break ();
11+
//~^ ERROR `#[const_continue]` must break to a labeled block that participates in a `#[loop_match]`
12+
}
13+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
error: `#[const_continue]` must break to a labeled block that participates in a `#[loop_match]`
2+
--> $DIR/const-continue-outside-loop-match.rs:10:9
3+
|
4+
LL | break ();
5+
| ^^^^^^^^
6+
7+
error: aborting due to 1 previous error
8+

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: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,23 @@ fn break_without_value_unit() {
142142
}
143143
}
144144

145+
fn break_without_label() {
146+
let mut state = State::A;
147+
#[loop_match]
148+
'a: loop {
149+
state = 'blk: {
150+
match state {
151+
_ => {
152+
#[const_continue]
153+
break State::A;
154+
//~^ ERROR unlabeled `break` inside of a labeled block
155+
//~| ERROR mismatched types
156+
}
157+
}
158+
}
159+
}
160+
}
161+
145162
fn arm_has_guard(cond: bool) {
146163
let mut state = State::A;
147164
#[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,18 @@ 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:153:21
14+
|
15+
LL | break State::A;
16+
| ^^^^^^^^^^^^^^ `break` statements that would diverge to or through a labeled block need to bear a label
17+
18+
error[E0308]: mismatched types
19+
--> $DIR/invalid.rs:153:27
20+
|
21+
LL | break State::A;
22+
| ^^^^^^^^ expected `()`, found `State`
23+
1224
error: invalid update of the `#[loop_match]` state
1325
--> $DIR/invalid.rs:18:9
1426
|
@@ -81,13 +93,13 @@ LL | break 'blk;
8193
| ^^^^^^^^^^
8294

8395
error: match arms that are part of a `#[loop_match]` cannot have guards
84-
--> $DIR/invalid.rs:155:29
96+
--> $DIR/invalid.rs:172: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:185: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:202: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)