Skip to content

Commit 4d65622

Browse files
committed
Properly implement labeled breaks in while conditions
1 parent 5205e2f commit 4d65622

File tree

12 files changed

+247
-58
lines changed

12 files changed

+247
-58
lines changed

src/librustc/cfg/construct.rs

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -220,15 +220,24 @@ impl<'a, 'tcx> CFGBuilder<'a, 'tcx> {
220220
// Note that `break` and `continue` statements
221221
// may cause additional edges.
222222

223-
// Is the condition considered part of the loop?
224223
let loopback = self.add_dummy_node(&[pred]); // 1
225-
let cond_exit = self.expr(&cond, loopback); // 2
226-
let expr_exit = self.add_ast_node(expr.id, &[cond_exit]); // 3
224+
225+
// Create expr_exit without pred (cond_exit)
226+
let expr_exit = self.add_ast_node(expr.id, &[]); // 3
227+
228+
// The LoopScope needs to be on the loop_scopes stack while evaluating the
229+
// condition and the body of the loop (both can break out of the loop)
227230
self.loop_scopes.push(LoopScope {
228231
loop_id: expr.id,
229232
continue_index: loopback,
230233
break_index: expr_exit
231234
});
235+
236+
let cond_exit = self.expr(&cond, loopback); // 2
237+
238+
// Add pred (cond_exit) to expr_exit
239+
self.add_contained_edge(cond_exit, expr_exit);
240+
232241
let body_exit = self.block(&body, cond_exit); // 4
233242
self.add_contained_edge(body_exit, loopback); // 5
234243
self.loop_scopes.pop();
@@ -580,11 +589,17 @@ impl<'a, 'tcx> CFGBuilder<'a, 'tcx> {
580589
fn find_scope(&self,
581590
expr: &hir::Expr,
582591
label: hir::Label) -> LoopScope {
583-
for l in &self.loop_scopes {
584-
if l.loop_id == label.loop_id {
585-
return *l;
592+
593+
match label.loop_id.into() {
594+
Ok(loop_id) => {
595+
for l in &self.loop_scopes {
596+
if l.loop_id == loop_id {
597+
return *l;
598+
}
599+
}
600+
span_bug!(expr.span, "no loop scope for id {}", loop_id);
586601
}
602+
Err(err) => span_bug!(expr.span, "loop scope error: {}", err)
587603
}
588-
span_bug!(expr.span, "no loop scope for id {}", label.loop_id);
589604
}
590605
}

src/librustc/hir/intravisit.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1008,14 +1008,18 @@ pub fn walk_expr<'v, V: Visitor<'v>>(visitor: &mut V, expression: &'v Expr) {
10081008
}
10091009
ExprBreak(label, ref opt_expr) => {
10101010
label.ident.map(|ident| {
1011-
visitor.visit_def_mention(Def::Label(label.loop_id));
1011+
if let Ok(loop_id) = label.loop_id.into() {
1012+
visitor.visit_def_mention(Def::Label(loop_id));
1013+
}
10121014
visitor.visit_name(ident.span, ident.node.name);
10131015
});
10141016
walk_list!(visitor, visit_expr, opt_expr);
10151017
}
10161018
ExprAgain(label) => {
10171019
label.ident.map(|ident| {
1018-
visitor.visit_def_mention(Def::Label(label.loop_id));
1020+
if let Ok(loop_id) = label.loop_id.into() {
1021+
visitor.visit_def_mention(Def::Label(loop_id));
1022+
}
10191023
visitor.visit_name(ident.span, ident.node.name);
10201024
});
10211025
}

src/librustc/hir/lowering.rs

Lines changed: 59 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ pub struct LoweringContext<'a> {
8181
bodies: FxHashMap<hir::BodyId, hir::Body>,
8282

8383
loop_scopes: Vec<NodeId>,
84+
is_in_loop_condition: bool,
8485

8586
type_def_lifetime_params: DefIdMap<usize>,
8687
}
@@ -116,6 +117,7 @@ pub fn lower_crate(sess: &Session,
116117
impl_items: BTreeMap::new(),
117118
bodies: FxHashMap(),
118119
loop_scopes: Vec::new(),
120+
is_in_loop_condition: false,
119121
type_def_lifetime_params: DefIdMap(),
120122
}.lower_crate(krate)
121123
}
@@ -251,21 +253,49 @@ impl<'a> LoweringContext<'a> {
251253
fn with_loop_scope<T, F>(&mut self, loop_id: NodeId, f: F) -> T
252254
where F: FnOnce(&mut LoweringContext) -> T
253255
{
256+
// We're no longer in the base loop's condition; we're in another loop.
257+
let was_in_loop_condition = self.is_in_loop_condition;
258+
self.is_in_loop_condition = false;
259+
254260
let len = self.loop_scopes.len();
255261
self.loop_scopes.push(loop_id);
262+
256263
let result = f(self);
257264
assert_eq!(len + 1, self.loop_scopes.len(),
258265
"Loop scopes should be added and removed in stack order");
266+
259267
self.loop_scopes.pop().unwrap();
268+
269+
self.is_in_loop_condition = was_in_loop_condition;
270+
271+
result
272+
}
273+
274+
fn with_loop_condition_scope<T, F>(&mut self, f: F) -> T
275+
where F: FnOnce(&mut LoweringContext) -> T
276+
{
277+
let was_in_loop_condition = self.is_in_loop_condition;
278+
self.is_in_loop_condition = true;
279+
280+
let result = f(self);
281+
282+
self.is_in_loop_condition = was_in_loop_condition;
283+
260284
result
261285
}
262286

263287
fn with_new_loop_scopes<T, F>(&mut self, f: F) -> T
264288
where F: FnOnce(&mut LoweringContext) -> T
265289
{
290+
let was_in_loop_condition = self.is_in_loop_condition;
291+
self.is_in_loop_condition = false;
292+
266293
let loop_scopes = mem::replace(&mut self.loop_scopes, Vec::new());
267294
let result = f(self);
268295
mem::replace(&mut self.loop_scopes, loop_scopes);
296+
297+
self.is_in_loop_condition = was_in_loop_condition;
298+
269299
result
270300
}
271301

@@ -300,17 +330,16 @@ impl<'a> LoweringContext<'a> {
300330
match label {
301331
Some((id, label_ident)) => hir::Label {
302332
ident: Some(label_ident),
303-
loop_id: match self.expect_full_def(id) {
304-
Def::Label(loop_id) => loop_id,
305-
_ => DUMMY_NODE_ID
333+
loop_id: if let Def::Label(loop_id) = self.expect_full_def(id) {
334+
hir::LoopIdResult::Ok(loop_id)
335+
} else {
336+
hir::LoopIdResult::Err(hir::LoopIdError::UnresolvedLabel)
306337
}
307338
},
308339
None => hir::Label {
309340
ident: None,
310-
loop_id: match self.loop_scopes.last() {
311-
Some(innermost_loop_id) => *innermost_loop_id,
312-
_ => DUMMY_NODE_ID
313-
}
341+
loop_id: self.loop_scopes.last().map(|innermost_loop_id| Ok(*innermost_loop_id))
342+
.unwrap_or(Err(hir::LoopIdError::OutsideLoopScope)).into()
314343
}
315344
}
316345
}
@@ -1597,7 +1626,7 @@ impl<'a> LoweringContext<'a> {
15971626
ExprKind::While(ref cond, ref body, opt_ident) => {
15981627
self.with_loop_scope(e.id, |this|
15991628
hir::ExprWhile(
1600-
P(this.lower_expr(cond)),
1629+
this.with_loop_condition_scope(|this| P(this.lower_expr(cond))),
16011630
this.lower_block(body),
16021631
this.lower_opt_sp_ident(opt_ident)))
16031632
}
@@ -1699,13 +1728,29 @@ impl<'a> LoweringContext<'a> {
16991728
hir::ExprPath(self.lower_qpath(e.id, qself, path, ParamMode::Optional))
17001729
}
17011730
ExprKind::Break(opt_ident, ref opt_expr) => {
1731+
let label_result = if self.is_in_loop_condition && opt_ident.is_none() {
1732+
hir::Label {
1733+
ident: opt_ident,
1734+
loop_id: Err(hir::LoopIdError::UnlabeledCfInWhileCondition).into(),
1735+
}
1736+
} else {
1737+
self.lower_label(opt_ident.map(|ident| (e.id, ident)))
1738+
};
17021739
hir::ExprBreak(
1703-
self.lower_label(opt_ident.map(|ident| (e.id, ident))),
1704-
opt_expr.as_ref().map(|x| P(self.lower_expr(x))))
1740+
label_result,
1741+
opt_expr.as_ref().map(|x| P(self.lower_expr(x))))
17051742
}
17061743
ExprKind::Continue(opt_ident) =>
17071744
hir::ExprAgain(
1708-
self.lower_label(opt_ident.map(|ident| (e.id, ident)))),
1745+
if self.is_in_loop_condition && opt_ident.is_none() {
1746+
hir::Label {
1747+
ident: opt_ident,
1748+
loop_id: Err(
1749+
hir::LoopIdError::UnlabeledCfInWhileCondition).into(),
1750+
}
1751+
} else {
1752+
self.lower_label(opt_ident.map( | ident| (e.id, ident)))
1753+
}),
17091754
ExprKind::Ret(ref e) => hir::ExprRet(e.as_ref().map(|x| P(self.lower_expr(x)))),
17101755
ExprKind::InlineAsm(ref asm) => {
17111756
let hir_asm = hir::InlineAsm {
@@ -1846,10 +1891,12 @@ impl<'a> LoweringContext<'a> {
18461891
// }
18471892
// }
18481893

1894+
// Note that the block AND the condition are evaluated in the loop scope.
1895+
// This is done to allow `break` from inside the condition of the loop.
18491896
let (body, break_expr, sub_expr) = self.with_loop_scope(e.id, |this| (
18501897
this.lower_block(body),
18511898
this.expr_break(e.span, ThinVec::new()),
1852-
P(this.lower_expr(sub_expr)),
1899+
this.with_loop_condition_scope(|this| P(this.lower_expr(sub_expr))),
18531900
));
18541901

18551902
// `<pat> => <body>`

src/librustc/hir/mod.rs

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1030,11 +1030,56 @@ pub enum LoopSource {
10301030
ForLoop,
10311031
}
10321032

1033+
#[derive(Clone, PartialEq, Eq, RustcEncodable, RustcDecodable, Hash, Debug, Copy)]
1034+
pub enum LoopIdError {
1035+
OutsideLoopScope,
1036+
UnlabeledCfInWhileCondition,
1037+
UnresolvedLabel,
1038+
}
1039+
1040+
impl fmt::Display for LoopIdError {
1041+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1042+
fmt::Display::fmt(match *self {
1043+
LoopIdError::OutsideLoopScope => "not inside loop scope",
1044+
LoopIdError::UnlabeledCfInWhileCondition =>
1045+
"unlabeled control flow (break or continue) in while condition",
1046+
LoopIdError::UnresolvedLabel => "label not found",
1047+
}, f)
1048+
}
1049+
}
1050+
1051+
// FIXME(cramertj) this should use `Result` once master compiles w/ a vesion of Rust where
1052+
// `Result` implements `Encodable`/`Decodable`
1053+
#[derive(Clone, PartialEq, Eq, RustcEncodable, RustcDecodable, Hash, Debug, Copy)]
1054+
pub enum LoopIdResult {
1055+
Ok(NodeId),
1056+
Err(LoopIdError),
1057+
}
1058+
impl Into<Result<NodeId, LoopIdError>> for LoopIdResult {
1059+
fn into(self) -> Result<NodeId, LoopIdError> {
1060+
match self {
1061+
LoopIdResult::Ok(ok) => Ok(ok),
1062+
LoopIdResult::Err(err) => Err(err),
1063+
}
1064+
}
1065+
}
1066+
impl From<Result<NodeId, LoopIdError>> for LoopIdResult {
1067+
fn from(res: Result<NodeId, LoopIdError>) -> Self {
1068+
match res {
1069+
Ok(ok) => LoopIdResult::Ok(ok),
1070+
Err(err) => LoopIdResult::Err(err),
1071+
}
1072+
}
1073+
}
10331074

10341075
#[derive(Clone, PartialEq, Eq, RustcEncodable, RustcDecodable, Hash, Debug, Copy)]
10351076
pub struct Label {
1077+
// This is `Some(_)` iff there is an explicit user-specified `label
10361078
pub ident: Option<Spanned<Ident>>,
1037-
pub loop_id: NodeId
1079+
1080+
// These errors are caught and then reported during the diagnostics pass in
1081+
// librustc_passes/loops.rs
1082+
pub loop_id: LoopIdResult,
10381083
}
10391084

10401085
#[derive(Clone, PartialEq, Eq, RustcEncodable, RustcDecodable, Hash, Debug, Copy)]

src/librustc/middle/liveness.rs

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1003,7 +1003,10 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
10031003

10041004
hir::ExprBreak(label, ref opt_expr) => {
10051005
// Find which label this break jumps to
1006-
let sc = label.loop_id;
1006+
let sc = match label.loop_id.into() {
1007+
Ok(loop_id) => loop_id,
1008+
Err(err) => span_bug!(expr.span, "loop scope error: {}", err),
1009+
};
10071010

10081011
// Now that we know the label we're going to,
10091012
// look it up in the break loop nodes table
@@ -1016,7 +1019,11 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
10161019

10171020
hir::ExprAgain(label) => {
10181021
// Find which label this expr continues to
1019-
let sc = label.loop_id;
1022+
let sc = match label.loop_id.into() {
1023+
Ok(loop_id) => loop_id,
1024+
Err(err) => span_bug!(expr.span, "loop scope error: {}", err),
1025+
};
1026+
10201027

10211028
// Now that we know the label we're going to,
10221029
// look it up in the continue loop nodes table
@@ -1280,12 +1287,13 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
12801287
debug!("propagate_through_loop: using id for loop body {} {}",
12811288
expr.id, self.ir.tcx.hir.node_to_pretty_string(body.id));
12821289

1283-
let cond_ln = match kind {
1284-
LoopLoop => ln,
1285-
WhileLoop(ref cond) => self.propagate_through_expr(&cond, ln),
1286-
};
1287-
let body_ln = self.with_loop_nodes(expr.id, succ, ln, |this| {
1288-
this.propagate_through_block(body, cond_ln)
1290+
let (cond_ln, body_ln) = self.with_loop_nodes(expr.id, succ, ln, |this| {
1291+
let cond_ln = match kind {
1292+
LoopLoop => ln,
1293+
WhileLoop(ref cond) => this.propagate_through_expr(&cond, ln),
1294+
};
1295+
let body_ln = this.propagate_through_block(body, cond_ln);
1296+
(cond_ln, body_ln)
12891297
});
12901298

12911299
// repeat until fixed point is reached:

src/librustc_mir/build/scope.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -389,9 +389,9 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
389389
-> &mut LoopScope<'tcx> {
390390
// find the loop-scope with the correct id
391391
self.loop_scopes.iter_mut()
392-
.rev()
393-
.filter(|loop_scope| loop_scope.extent == label)
394-
.next()
392+
.rev()
393+
.filter(|loop_scope| loop_scope.extent == label)
394+
.next()
395395
.unwrap_or_else(|| span_bug!(span, "no enclosing loop scope found?"))
396396
}
397397

src/librustc_mir/hair/cx/expr.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -605,14 +605,21 @@ fn make_mirror_unadjusted<'a, 'gcx, 'tcx>(cx: &mut Cx<'a, 'gcx, 'tcx>,
605605
}
606606
hir::ExprRet(ref v) => ExprKind::Return { value: v.to_ref() },
607607
hir::ExprBreak(label, ref value) => {
608-
ExprKind::Break {
609-
label: cx.tcx.region_maps.node_extent(label.loop_id),
610-
value: value.to_ref(),
608+
match label.loop_id.into() {
609+
Ok(loop_id) => ExprKind::Break {
610+
label: cx.tcx.region_maps.node_extent(loop_id),
611+
value: value.to_ref(),
612+
},
613+
Err(err) => bug!("invalid loop id for break: {}", err)
611614
}
615+
612616
}
613617
hir::ExprAgain(label) => {
614-
ExprKind::Continue {
615-
label: cx.tcx.region_maps.node_extent(label.loop_id),
618+
match label.loop_id.into() {
619+
Ok(loop_id) => ExprKind::Continue {
620+
label: cx.tcx.region_maps.node_extent(loop_id),
621+
},
622+
Err(err) => bug!("invalid loop id for continue: {}", err)
616623
}
617624
}
618625
hir::ExprMatch(ref discr, ref arms, _) => {

src/librustc_passes/diagnostics.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,22 @@ match 5u32 {
241241
}
242242
```
243243
"##,
244+
245+
E0583: r##"
246+
`break` or `continue` must include a label when used in the condition of a
247+
`while` loop.
248+
249+
Example of erroneous code:
250+
251+
```compile_fail
252+
while break {}
253+
```
254+
255+
To fix this, add a label specifying which loop is being broken out of:
256+
```
257+
`foo: while break `foo {}
258+
```
259+
"##
244260
}
245261

246262
register_diagnostics! {

0 commit comments

Comments
 (0)