Skip to content

Commit 9e675b0

Browse files
committed
emit error when #[const_continue] jumps to an invalid label
1 parent 40c73d5 commit 9e675b0

File tree

7 files changed

+186
-5
lines changed

7 files changed

+186
-5
lines changed

compiler/rustc_passes/messages.ftl

+12
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@ passes_const_continue_attr =
103103
`#[const_continue]` should be applied to a break expression
104104
.label = not a break expression
105105
106+
passes_const_continue_bad_label =
107+
`#[const_continue]` must break to a labeled block that participates in a `#[loop_match]`
108+
106109
passes_const_stable_not_stable =
107110
attribute `#[rustc_const_stable]` can only be applied to functions that are declared `#[stable]`
108111
.label = attribute specified here
@@ -466,6 +469,15 @@ passes_loop_match_attr =
466469
`#[loop_match]` should be applied to a loop
467470
.label = not a loop
468471
472+
passes_loop_match_bad_rhs =
473+
this expression must be a single `match` wrapped in a labeled block
474+
475+
passes_loop_match_bad_statements =
476+
statements are not allowed in this position within a `#[loop_match]`
477+
478+
passes_loop_match_missing_assignment =
479+
expected a single assignment expression
480+
469481
passes_macro_export =
470482
`#[macro_export]` only has an effect on macro definitions
471483

compiler/rustc_passes/src/errors.rs

+28
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,13 @@ pub(crate) struct ConstContinueAttr {
5050
pub node_span: Span,
5151
}
5252

53+
#[derive(Diagnostic)]
54+
#[diag(passes_const_continue_bad_label)]
55+
pub(crate) struct ConstContinueBadLabel {
56+
#[primary_span]
57+
pub span: Span,
58+
}
59+
5360
#[derive(LintDiagnostic)]
5461
#[diag(passes_outer_crate_level_attr)]
5562
pub(crate) struct OuterCrateLevelAttr;
@@ -1215,6 +1222,27 @@ pub(crate) struct UnlabeledCfInWhileCondition<'a> {
12151222
pub cf_type: &'a str,
12161223
}
12171224

1225+
#[derive(Diagnostic)]
1226+
#[diag(passes_loop_match_bad_statements)]
1227+
pub(crate) struct LoopMatchBadStatements {
1228+
#[primary_span]
1229+
pub span: Span,
1230+
}
1231+
1232+
#[derive(Diagnostic)]
1233+
#[diag(passes_loop_match_bad_rhs)]
1234+
pub(crate) struct LoopMatchBadRhs {
1235+
#[primary_span]
1236+
pub span: Span,
1237+
}
1238+
1239+
#[derive(Diagnostic)]
1240+
#[diag(passes_loop_match_missing_assignment)]
1241+
pub(crate) struct LoopMatchMissingAssignment {
1242+
#[primary_span]
1243+
pub span: Span,
1244+
}
1245+
12181246
#[derive(LintDiagnostic)]
12191247
#[diag(passes_undefined_naked_function_abi)]
12201248
pub(crate) struct UndefinedNakedFunctionAbi;

compiler/rustc_passes/src/loops.rs

+84-5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::collections::BTreeMap;
22
use std::fmt;
33

44
use Context::*;
5+
use rustc_ast::Label;
56
use rustc_hir as hir;
67
use rustc_hir::def_id::{LocalDefId, LocalModDefId};
78
use rustc_hir::intravisit::{self, Visitor};
@@ -11,11 +12,12 @@ use rustc_middle::query::Providers;
1112
use rustc_middle::span_bug;
1213
use rustc_middle::ty::TyCtxt;
1314
use rustc_span::hygiene::DesugaringKind;
14-
use rustc_span::{BytePos, Span};
15+
use rustc_span::{BytePos, Span, sym};
1516

1617
use crate::errors::{
17-
BreakInsideClosure, BreakInsideCoroutine, BreakNonLoop, ContinueLabeledBlock, OutsideLoop,
18-
OutsideLoopSuggestion, UnlabeledCfInWhileCondition, UnlabeledInLabeledBlock,
18+
BreakInsideClosure, BreakInsideCoroutine, BreakNonLoop, ConstContinueBadLabel,
19+
ContinueLabeledBlock, LoopMatchBadRhs, LoopMatchBadStatements, LoopMatchMissingAssignment,
20+
OutsideLoop, OutsideLoopSuggestion, UnlabeledCfInWhileCondition, UnlabeledInLabeledBlock,
1921
};
2022

2123
/// The context in which a block is encountered.
@@ -37,6 +39,11 @@ enum Context {
3739
AnonConst,
3840
/// E.g. `const { ... }`.
3941
ConstBlock,
42+
/// #[loop_match] loop { state = 'label: { /* ... */ } }
43+
LoopMatch {
44+
/// The label of the labeled block (so not the loop!)
45+
labeled_block: Label,
46+
},
4047
}
4148

4249
#[derive(Clone)]
@@ -160,7 +167,12 @@ impl<'hir> Visitor<'hir> for CheckLoopVisitor<'hir> {
160167
}
161168
}
162169
hir::ExprKind::Loop(ref b, _, source, _) => {
163-
self.with_context(Loop(source), |v| v.visit_block(b));
170+
let cx = match self.is_loop_match(e, b) {
171+
Some(labeled_block) => LoopMatch { labeled_block },
172+
None => Loop(source),
173+
};
174+
175+
self.with_context(cx, |v| v.visit_block(b));
164176
}
165177
hir::ExprKind::Closure(&hir::Closure {
166178
ref fn_decl, body, fn_decl_span, kind, ..
@@ -216,6 +228,22 @@ impl<'hir> Visitor<'hir> for CheckLoopVisitor<'hir> {
216228
Err(hir::LoopIdError::UnresolvedLabel) => None,
217229
};
218230

231+
// a #[const_continue] must be to a block that participates in #[loop_match]
232+
let attrs = self.tcx.hir_attrs(e.hir_id);
233+
if attrs.iter().any(|attr| attr.has_name(sym::const_continue)) {
234+
if let Some(break_label) = break_label.label {
235+
let is_target_label = |cx: &Context| match cx {
236+
Context::LoopMatch { labeled_block } => break_label == *labeled_block,
237+
_ => false,
238+
};
239+
240+
if !self.cx_stack.iter().rev().any(is_target_label) {
241+
let span = break_label.ident.span;
242+
self.tcx.dcx().emit_fatal(ConstContinueBadLabel { span });
243+
}
244+
}
245+
}
246+
219247
if let Some(Node::Block(_)) = loop_id.map(|id| self.tcx.hir_node(id)) {
220248
return;
221249
}
@@ -318,7 +346,7 @@ impl<'hir> CheckLoopVisitor<'hir> {
318346
cx_pos: usize,
319347
) {
320348
match self.cx_stack[cx_pos] {
321-
LabeledBlock | Loop(_) => {}
349+
LabeledBlock | Loop(_) | LoopMatch { .. } => {}
322350
Closure(closure_span) => {
323351
self.tcx.dcx().emit_err(BreakInsideClosure {
324352
span,
@@ -399,4 +427,55 @@ impl<'hir> CheckLoopVisitor<'hir> {
399427
});
400428
}
401429
}
430+
431+
/// Is this a loop annotated with #[loop_match] that looks syntactically sound?
432+
fn is_loop_match(
433+
&self,
434+
e: &'hir hir::Expr<'hir>,
435+
body: &'hir hir::Block<'hir>,
436+
) -> Option<Label> {
437+
if !self.tcx.hir_attrs(e.hir_id).iter().any(|attr| attr.has_name(sym::loop_match)) {
438+
return None;
439+
}
440+
441+
let dcx = self.tcx.dcx();
442+
443+
// accept either `state = expr` or `state = expr;`
444+
let loop_body_expr = match body.stmts {
445+
[] => match body.expr {
446+
Some(expr) => expr,
447+
None => {
448+
dcx.emit_err(LoopMatchMissingAssignment { span: body.span });
449+
return None;
450+
}
451+
},
452+
[single] if body.expr.is_none() => match single.kind {
453+
hir::StmtKind::Expr(expr) | hir::StmtKind::Semi(expr) => expr,
454+
_ => {
455+
dcx.emit_err(LoopMatchMissingAssignment { span: body.span });
456+
return None;
457+
}
458+
},
459+
[first @ last] | [first, .., last] => {
460+
dcx.emit_err(LoopMatchBadStatements { span: first.span.to(last.span) });
461+
return None;
462+
}
463+
};
464+
465+
let hir::ExprKind::Assign(_, rhs_expr, _) = loop_body_expr.kind else {
466+
dcx.emit_err(LoopMatchMissingAssignment { span: loop_body_expr.span });
467+
return None;
468+
};
469+
470+
let hir::ExprKind::Block(_, label) = rhs_expr.kind else {
471+
dcx.emit_err(LoopMatchBadRhs { span: rhs_expr.span });
472+
return None;
473+
};
474+
475+
if label.is_none() {
476+
todo!()
477+
}
478+
479+
label
480+
}
402481
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#![allow(incomplete_features)]
2+
#![feature(loop_match)]
3+
#![crate_type = "lib"]
4+
5+
fn const_continue_to_block() -> u8 {
6+
let state = 0;
7+
#[loop_match]
8+
loop {
9+
state = 'blk: {
10+
match state {
11+
0 => {
12+
#[const_continue]
13+
break 'blk 1;
14+
}
15+
_ => 'b: {
16+
#[const_continue]
17+
break 'b 2;
18+
//~^ ERROR `#[const_continue]` must break to a labeled block that participates in a `#[loop_match]`
19+
}
20+
}
21+
}
22+
}
23+
}
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-to-block.rs:17:27
3+
|
4+
LL | break 'b 2;
5+
| ^^
6+
7+
error: aborting due to 1 previous error
8+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#![allow(incomplete_features)]
2+
#![feature(loop_match)]
3+
#![crate_type = "lib"]
4+
5+
fn const_continue_to_loop() -> u8 {
6+
let state = 0;
7+
#[loop_match]
8+
'a: loop {
9+
state = 'blk: {
10+
match state {
11+
0 => {
12+
#[const_continue]
13+
break 'blk 1;
14+
}
15+
_ => {
16+
#[const_continue]
17+
break 'a 2;
18+
//~^ ERROR `#[const_continue]` must break to a labeled block that participates in a `#[loop_match]`
19+
}
20+
}
21+
}
22+
}
23+
}
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-to-loop.rs:17:27
3+
|
4+
LL | break 'a 2;
5+
| ^^
6+
7+
error: aborting due to 1 previous error
8+

0 commit comments

Comments
 (0)