1
Fork 0

Preliminary support for labeled break/continue for loops

This patch adds preliminary middle-end support (liveness and trans)
for breaks and `loop`s to `loop` constructs that have labels.

while and for loops can't have labels yet.

Progress on #2216
This commit is contained in:
Tim Chevalier 2012-10-18 12:20:18 -07:00
parent 46d4bbbae4
commit dd66e7549b
10 changed files with 232 additions and 98 deletions

View file

@ -1180,7 +1180,10 @@ fn print_expr(s: ps, &&expr: @ast::expr) {
ast::expr_loop(blk, opt_ident) => { ast::expr_loop(blk, opt_ident) => {
head(s, ~"loop"); head(s, ~"loop");
space(s.s); space(s.s);
opt_ident.iter(|ident| {print_ident(s, *ident); space(s.s)}); opt_ident.iter(|ident| {
print_ident(s, *ident);
word_space(s, ~":");
});
print_block(s, blk); print_block(s, blk);
} }
ast::expr_match(expr, arms) => { ast::expr_match(expr, arms) => {

View file

@ -95,9 +95,9 @@
use dvec::DVec; use dvec::DVec;
use std::map::HashMap; use std::map::HashMap;
use syntax::{visit, ast_util}; use syntax::{visit, ast_util};
use syntax::print::pprust::{expr_to_str}; use syntax::print::pprust::{expr_to_str, block_to_str};
use visit::vt; use visit::vt;
use syntax::codemap::span; use syntax::codemap::{span, span_to_str};
use syntax::ast::*; use syntax::ast::*;
use io::WriterUtil; use io::WriterUtil;
use capture::{cap_move, cap_drop, cap_copy, cap_ref}; use capture::{cap_move, cap_drop, cap_copy, cap_ref};
@ -167,6 +167,16 @@ impl LiveNodeKind : cmp::Eq {
pure fn ne(other: &LiveNodeKind) -> bool { !self.eq(other) } pure fn ne(other: &LiveNodeKind) -> bool { !self.eq(other) }
} }
fn live_node_kind_to_str(lnk: LiveNodeKind, cx: ty::ctxt) -> ~str {
let cm = cx.sess.codemap;
match lnk {
FreeVarNode(s) => fmt!("Free var node [%s]", span_to_str(s, cm)),
ExprNode(s) => fmt!("Expr node [%s]", span_to_str(s, cm)),
VarDefNode(s) => fmt!("Var def node [%s]", span_to_str(s, cm)),
ExitNode => ~"Exit node"
}
}
fn check_crate(tcx: ty::ctxt, fn check_crate(tcx: ty::ctxt,
method_map: typeck::method_map, method_map: typeck::method_map,
crate: @crate) -> last_use_map { crate: @crate) -> last_use_map {
@ -277,8 +287,8 @@ fn IrMaps(tcx: ty::ctxt, method_map: typeck::method_map,
tcx: tcx, tcx: tcx,
method_map: method_map, method_map: method_map,
last_use_map: last_use_map, last_use_map: last_use_map,
num_live_nodes: 0u, num_live_nodes: 0,
num_vars: 0u, num_vars: 0,
live_node_map: HashMap(), live_node_map: HashMap(),
variable_map: HashMap(), variable_map: HashMap(),
capture_map: HashMap(), capture_map: HashMap(),
@ -291,9 +301,10 @@ impl IrMaps {
fn add_live_node(lnk: LiveNodeKind) -> LiveNode { fn add_live_node(lnk: LiveNodeKind) -> LiveNode {
let ln = LiveNode(self.num_live_nodes); let ln = LiveNode(self.num_live_nodes);
self.lnks.push(lnk); self.lnks.push(lnk);
self.num_live_nodes += 1u; self.num_live_nodes += 1;
debug!("%s is of kind %?", ln.to_str(), lnk); debug!("%s is of kind %s", ln.to_str(),
live_node_kind_to_str(lnk, self.tcx));
ln ln
} }
@ -308,7 +319,7 @@ impl IrMaps {
fn add_variable(vk: VarKind) -> Variable { fn add_variable(vk: VarKind) -> Variable {
let v = Variable(self.num_vars); let v = Variable(self.num_vars);
self.var_kinds.push(vk); self.var_kinds.push(vk);
self.num_vars += 1u; self.num_vars += 1;
match vk { match vk {
Local(LocalInfo {id:node_id, _}) | Local(LocalInfo {id:node_id, _}) |
@ -491,6 +502,10 @@ fn visit_expr(expr: @expr, &&self: @IrMaps, vt: vt<@IrMaps>) {
} }
expr_fn(_, _, _, cap_clause) | expr_fn(_, _, _, cap_clause) |
expr_fn_block(_, _, cap_clause) => { expr_fn_block(_, _, cap_clause) => {
// Interesting control flow (for loops can contain labeled
// breaks or continues)
self.add_live_node_for_node(expr.id, ExprNode(expr.span));
// Make a live_node for each captured variable, with the span // Make a live_node for each captured variable, with the span
// being the location that the variable is used. This results // being the location that the variable is used. This results
// in better error messages than just pointing at the closure // in better error messages than just pointing at the closure
@ -571,14 +586,22 @@ const ACC_READ: uint = 1u;
const ACC_WRITE: uint = 2u; const ACC_WRITE: uint = 2u;
const ACC_USE: uint = 4u; const ACC_USE: uint = 4u;
type LiveNodeMap = HashMap<node_id, LiveNode>;
struct Liveness { struct Liveness {
tcx: ty::ctxt, tcx: ty::ctxt,
ir: @IrMaps, ir: @IrMaps,
s: Specials, s: Specials,
successors: ~[mut LiveNode], successors: ~[mut LiveNode],
users: ~[mut users], users: ~[mut users],
mut break_ln: LiveNode, // The list of node IDs for the nested loop scopes
mut cont_ln: LiveNode, // we're in.
mut loop_scope: @DVec<node_id>,
// mappings from loop node ID to LiveNode
// ("break" label should map to loop node ID,
// it probably doesn't now)
break_ln: LiveNodeMap,
cont_ln: LiveNodeMap
} }
fn Liveness(ir: @IrMaps, specials: Specials) -> Liveness { fn Liveness(ir: @IrMaps, specials: Specials) -> Liveness {
@ -594,8 +617,9 @@ fn Liveness(ir: @IrMaps, specials: Specials) -> Liveness {
vec::to_mut( vec::to_mut(
vec::from_elem(ir.num_live_nodes * ir.num_vars, vec::from_elem(ir.num_live_nodes * ir.num_vars,
invalid_users())), invalid_users())),
break_ln: invalid_node(), loop_scope: @DVec(),
cont_ln: invalid_node() break_ln: HashMap(),
cont_ln: HashMap()
} }
} }
@ -691,6 +715,9 @@ impl Liveness {
if reader.is_valid() {Some((*self.ir).lnk(reader))} else {None} if reader.is_valid() {Some((*self.ir).lnk(reader))} else {None}
} }
/*
Is this variable live on entry to any of its successor nodes?
*/
fn live_on_exit(ln: LiveNode, var: Variable) fn live_on_exit(ln: LiveNode, var: Variable)
-> Option<LiveNodeKind> { -> Option<LiveNodeKind> {
@ -717,8 +744,8 @@ impl Liveness {
} }
fn indices(ln: LiveNode, op: fn(uint)) { fn indices(ln: LiveNode, op: fn(uint)) {
let node_base_idx = self.idx(ln, Variable(0u)); let node_base_idx = self.idx(ln, Variable(0));
for uint::range(0u, self.ir.num_vars) |var_idx| { for uint::range(0, self.ir.num_vars) |var_idx| {
op(node_base_idx + var_idx) op(node_base_idx + var_idx)
} }
} }
@ -735,8 +762,8 @@ impl Liveness {
fn write_vars(wr: io::Writer, fn write_vars(wr: io::Writer,
ln: LiveNode, ln: LiveNode,
test: fn(uint) -> LiveNode) { test: fn(uint) -> LiveNode) {
let node_base_idx = self.idx(ln, Variable(0u)); let node_base_idx = self.idx(ln, Variable(0));
for uint::range(0u, self.ir.num_vars) |var_idx| { for uint::range(0, self.ir.num_vars) |var_idx| {
let idx = node_base_idx + var_idx; let idx = node_base_idx + var_idx;
if test(idx).is_valid() { if test(idx).is_valid() {
wr.write_str(~" "); wr.write_str(~" ");
@ -745,6 +772,28 @@ impl Liveness {
} }
} }
fn find_loop_scope(opt_label: Option<ident>, id: node_id, sp: span)
-> node_id {
match opt_label {
Some(_) => // Refers to a labeled loop. Use the results of resolve
// to find with one
match self.tcx.def_map.find(id) {
Some(def_label(loop_id)) => loop_id,
_ => self.tcx.sess.span_bug(sp, ~"Label on break/loop \
doesn't refer to a loop")
},
None =>
// Vanilla 'break' or 'loop', so use the enclosing
// loop scope
if self.loop_scope.len() == 0 {
self.tcx.sess.span_bug(sp, ~"break outside loop");
}
else {
self.loop_scope.last()
}
}
}
fn ln_str(ln: LiveNode) -> ~str { fn ln_str(ln: LiveNode) -> ~str {
do io::with_str_writer |wr| { do io::with_str_writer |wr| {
wr.write_str(~"[ln("); wr.write_str(~"[ln(");
@ -833,18 +882,18 @@ impl Liveness {
let idx = self.idx(ln, var); let idx = self.idx(ln, var);
let user = &mut self.users[idx]; let user = &mut self.users[idx];
if (acc & ACC_WRITE) != 0u { if (acc & ACC_WRITE) != 0 {
user.reader = invalid_node(); user.reader = invalid_node();
user.writer = ln; user.writer = ln;
} }
// Important: if we both read/write, must do read second // Important: if we both read/write, must do read second
// or else the write will override. // or else the write will override.
if (acc & ACC_READ) != 0u { if (acc & ACC_READ) != 0 {
user.reader = ln; user.reader = ln;
} }
if (acc & ACC_USE) != 0u { if (acc & ACC_USE) != 0 {
self.users[idx].used = true; self.users[idx].used = true;
} }
@ -858,10 +907,13 @@ impl Liveness {
// if there is a `break` or `again` at the top level, then it's // if there is a `break` or `again` at the top level, then it's
// effectively a return---this only occurs in `for` loops, // effectively a return---this only occurs in `for` loops,
// where the body is really a closure. // where the body is really a closure.
debug!("compute: using id for block, %s", block_to_str(body,
self.tcx.sess.intr()));
let entry_ln: LiveNode = let entry_ln: LiveNode =
self.with_loop_nodes(self.s.exit_ln, self.s.exit_ln, || { self.with_loop_nodes(body.node.id, self.s.exit_ln, self.s.exit_ln,
self.propagate_through_fn_block(decl, body) || { self.propagate_through_fn_block(decl, body) });
});
// hack to skip the loop unless debug! is enabled: // hack to skip the loop unless debug! is enabled:
debug!("^^ liveness computation results for body %d (entry=%s)", debug!("^^ liveness computation results for body %d (entry=%s)",
@ -972,6 +1024,9 @@ impl Liveness {
} }
fn propagate_through_expr(expr: @expr, succ: LiveNode) -> LiveNode { fn propagate_through_expr(expr: @expr, succ: LiveNode) -> LiveNode {
debug!("propagate_through_expr: %s",
expr_to_str(expr, self.tcx.sess.intr()));
match expr.node { match expr.node {
// Interesting cases with control flow or which gen/kill // Interesting cases with control flow or which gen/kill
@ -983,16 +1038,27 @@ impl Liveness {
self.propagate_through_expr(e, succ) self.propagate_through_expr(e, succ)
} }
expr_fn(*) | expr_fn_block(*) => { expr_fn(_, _, blk, _) | expr_fn_block(_, blk, _) => {
// the construction of a closure itself is not important, debug!("%s is an expr_fn or expr_fn_block",
// but we have to consider the closed over variables. expr_to_str(expr, self.tcx.sess.intr()));
let caps = (*self.ir).captures(expr);
do (*caps).foldr(succ) |cap, succ| { /*
self.init_from_succ(cap.ln, succ); The next-node for a break is the successor of the entire
let var = self.variable(cap.var_nid, expr.span); loop. The next-node for a continue is the top of this loop.
self.acc(cap.ln, var, ACC_READ | ACC_USE); */
cap.ln self.with_loop_nodes(blk.node.id, succ,
} self.live_node(expr.id, expr.span), || {
// the construction of a closure itself is not important,
// but we have to consider the closed over variables.
let caps = (*self.ir).captures(expr);
do (*caps).foldr(succ) |cap, succ| {
self.init_from_succ(cap.ln, succ);
let var = self.variable(cap.var_nid, expr.span);
self.acc(cap.ln, var, ACC_READ | ACC_USE);
cap.ln
}
})
} }
expr_if(cond, then, els) => { expr_if(cond, then, els) => {
@ -1021,6 +1087,8 @@ impl Liveness {
self.propagate_through_loop(expr, Some(cond), blk, succ) self.propagate_through_loop(expr, Some(cond), blk, succ)
} }
// Note that labels have been resolved, so we don't need to look
// at the label ident
expr_loop(blk, _) => { expr_loop(blk, _) => {
self.propagate_through_loop(expr, None, blk, succ) self.propagate_through_loop(expr, None, blk, succ)
} }
@ -1062,29 +1130,31 @@ impl Liveness {
} }
expr_break(opt_label) => { expr_break(opt_label) => {
if !self.break_ln.is_valid() { // Find which label this break jumps to
self.tcx.sess.span_bug( let sc = self.find_loop_scope(opt_label, expr.id, expr.span);
expr.span, ~"break with invalid break_ln");
}
if opt_label.is_some() { // Now that we know the label we're going to,
self.tcx.sess.span_unimpl(expr.span, ~"labeled break"); // look it up in the break loop nodes table
}
self.break_ln match self.break_ln.find(sc) {
Some(b) => b,
None => self.tcx.sess.span_bug(expr.span,
~"Break to unknown label")
}
} }
expr_again(opt_label) => { expr_again(opt_label) => {
if !self.cont_ln.is_valid() { // Find which label this expr continues to to
self.tcx.sess.span_bug( let sc = self.find_loop_scope(opt_label, expr.id, expr.span);
expr.span, ~"cont with invalid cont_ln");
}
if opt_label.is_some() { // Now that we know the label we're going to,
self.tcx.sess.span_unimpl(expr.span, ~"labeled again"); // look it up in the continue loop nodes table
}
self.cont_ln match self.cont_ln.find(sc) {
Some(b) => b,
None => self.tcx.sess.span_bug(expr.span,
~"Loop to unknown label")
}
} }
expr_move(l, r) | expr_assign(l, r) => { expr_move(l, r) | expr_assign(l, r) => {
@ -1314,6 +1384,7 @@ impl Liveness {
*/ */
// first iteration: // first iteration:
let mut first_merge = true; let mut first_merge = true;
let ln = self.live_node(expr.id, expr.span); let ln = self.live_node(expr.id, expr.span);
@ -1325,8 +1396,11 @@ impl Liveness {
self.merge_from_succ(ln, succ, first_merge); self.merge_from_succ(ln, succ, first_merge);
first_merge = false; first_merge = false;
} }
debug!("propagate_through_loop: using id for loop body %d %s",
expr.id, block_to_str(body, self.tcx.sess.intr()));
let cond_ln = self.propagate_through_opt_expr(cond, ln); let cond_ln = self.propagate_through_opt_expr(cond, ln);
let body_ln = self.with_loop_nodes(succ, ln, || { let body_ln = self.with_loop_nodes(expr.id, succ, ln, || {
self.propagate_through_block(body, cond_ln) self.propagate_through_block(body, cond_ln)
}); });
@ -1334,7 +1408,8 @@ impl Liveness {
while self.merge_from_succ(ln, body_ln, first_merge) { while self.merge_from_succ(ln, body_ln, first_merge) {
first_merge = false; first_merge = false;
assert cond_ln == self.propagate_through_opt_expr(cond, ln); assert cond_ln == self.propagate_through_opt_expr(cond, ln);
assert body_ln == self.with_loop_nodes(succ, ln, || { assert body_ln == self.with_loop_nodes(expr.id, succ, ln,
|| {
self.propagate_through_block(body, cond_ln) self.propagate_through_block(body, cond_ln)
}); });
} }
@ -1342,15 +1417,16 @@ impl Liveness {
cond_ln cond_ln
} }
fn with_loop_nodes<R>(break_ln: LiveNode, fn with_loop_nodes<R>(loop_node_id: node_id,
break_ln: LiveNode,
cont_ln: LiveNode, cont_ln: LiveNode,
f: fn() -> R) -> R { f: fn() -> R) -> R {
let bl = self.break_ln, cl = self.cont_ln; debug!("with_loop_nodes: %d %u", loop_node_id, *break_ln);
self.break_ln = break_ln; self.loop_scope.push(loop_node_id);
self.cont_ln = cont_ln; self.break_ln.insert(loop_node_id, break_ln);
let r <- f(); self.cont_ln.insert(loop_node_id, cont_ln);
self.break_ln = bl; let r = f();
self.cont_ln = cl; self.loop_scope.pop();
move r move r
} }
} }
@ -1526,6 +1602,10 @@ impl @Liveness {
} }
} }
/*
Checks whether <var> is live on entry to any of the successors of <ln>.
If it is, report an error.
*/
fn check_move_from_var(span: span, ln: LiveNode, var: Variable) { fn check_move_from_var(span: span, ln: LiveNode, var: Variable) {
debug!("check_move_from_var(%s, %s)", debug!("check_move_from_var(%s, %s)",
ln.to_str(), var.to_str()); ln.to_str(), var.to_str());

View file

@ -1050,7 +1050,7 @@ fn new_block(cx: fn_ctxt, parent: Option<block>, +kind: block_kind,
} }
fn simple_block_scope() -> block_kind { fn simple_block_scope() -> block_kind {
block_scope({loop_break: None, mut cleanups: ~[], block_scope({loop_break: None, loop_label: None, mut cleanups: ~[],
mut cleanup_paths: ~[], mut landing_pad: None}) mut cleanup_paths: ~[], mut landing_pad: None})
} }
@ -1067,10 +1067,11 @@ fn scope_block(bcx: block,
n, opt_node_info); n, opt_node_info);
} }
fn loop_scope_block(bcx: block, loop_break: block, n: ~str, fn loop_scope_block(bcx: block, loop_break: block, loop_label: Option<ident>,
opt_node_info: Option<node_info>) -> block { n: ~str, opt_node_info: Option<node_info>) -> block {
return new_block(bcx.fcx, Some(bcx), block_scope({ return new_block(bcx.fcx, Some(bcx), block_scope({
loop_break: Some(loop_break), loop_break: Some(loop_break),
loop_label: loop_label,
mut cleanups: ~[], mut cleanups: ~[],
mut cleanup_paths: ~[], mut cleanup_paths: ~[],
mut landing_pad: None mut landing_pad: None

View file

@ -445,6 +445,7 @@ enum block_kind {
type scope_info = { type scope_info = {
loop_break: Option<block>, loop_break: Option<block>,
loop_label: Option<ident>,
// A list of functions that must be run at when leaving this // A list of functions that must be run at when leaving this
// block, cleaning up any variables that were introduced in the // block, cleaning up any variables that were introduced in the
// block. // block.

View file

@ -113,7 +113,9 @@ fn trans_while(bcx: block, cond: @ast::expr, body: ast::blk)
// | body_bcx_out --+ // | body_bcx_out --+
// next_bcx // next_bcx
let loop_bcx = loop_scope_block(bcx, next_bcx, ~"`while`", body.info()); // tjc: while should have labels...
let loop_bcx = loop_scope_block(bcx, next_bcx, None, ~"`while`",
body.info());
let cond_bcx_in = scope_block(loop_bcx, cond.info(), ~"while loop cond"); let cond_bcx_in = scope_block(loop_bcx, cond.info(), ~"while loop cond");
let body_bcx_in = scope_block(loop_bcx, body.info(), ~"while loop body"); let body_bcx_in = scope_block(loop_bcx, body.info(), ~"while loop body");
Br(bcx, loop_bcx.llbb); Br(bcx, loop_bcx.llbb);
@ -133,10 +135,11 @@ fn trans_while(bcx: block, cond: @ast::expr, body: ast::blk)
return next_bcx; return next_bcx;
} }
fn trans_loop(bcx:block, body: ast::blk) -> block { fn trans_loop(bcx:block, body: ast::blk, opt_label: Option<ident>) -> block {
let _icx = bcx.insn_ctxt("trans_loop"); let _icx = bcx.insn_ctxt("trans_loop");
let next_bcx = sub_block(bcx, ~"next"); let next_bcx = sub_block(bcx, ~"next");
let body_bcx_in = loop_scope_block(bcx, next_bcx, ~"`loop`", body.info()); let body_bcx_in = loop_scope_block(bcx, next_bcx, opt_label, ~"`loop`",
body.info());
Br(bcx, body_bcx_in.llbb); Br(bcx, body_bcx_in.llbb);
let body_bcx_out = trans_block(body_bcx_in, body, expr::Ignore); let body_bcx_out = trans_block(body_bcx_in, body, expr::Ignore);
cleanup_and_Br(body_bcx_out, body_bcx_in, body_bcx_in.llbb); cleanup_and_Br(body_bcx_out, body_bcx_in, body_bcx_in.llbb);
@ -201,7 +204,7 @@ fn trans_log(log_ex: @ast::expr,
} }
} }
fn trans_break_cont(bcx: block, to_end: bool) fn trans_break_cont(bcx: block, opt_label: Option<ident>, to_end: bool)
-> block { -> block {
let _icx = bcx.insn_ctxt("trans_break_cont"); let _icx = bcx.insn_ctxt("trans_break_cont");
// Locate closest loop block, outputting cleanup as we go. // Locate closest loop block, outputting cleanup as we go.
@ -209,13 +212,22 @@ fn trans_break_cont(bcx: block, to_end: bool)
let mut target; let mut target;
loop { loop {
match unwind.kind { match unwind.kind {
block_scope({loop_break: Some(brk), _}) => { block_scope({loop_break: Some(brk), loop_label: l, _}) => {
// If we're looking for a labeled loop, check the label...
target = if to_end { target = if to_end {
brk brk
} else { } else {
unwind unwind
}; };
break; match opt_label {
Some(desired) => match l {
Some(actual) if actual == desired => break,
// If it doesn't match the one we want,
// don't break
_ => ()
},
None => break
}
} }
_ => () _ => ()
} }
@ -235,12 +247,12 @@ fn trans_break_cont(bcx: block, to_end: bool)
return bcx; return bcx;
} }
fn trans_break(bcx: block) -> block { fn trans_break(bcx: block, label_opt: Option<ident>) -> block {
return trans_break_cont(bcx, true); return trans_break_cont(bcx, label_opt, true);
} }
fn trans_cont(bcx: block) -> block { fn trans_cont(bcx: block, label_opt: Option<ident>) -> block {
return trans_break_cont(bcx, false); return trans_break_cont(bcx, label_opt, false);
} }
fn trans_ret(bcx: block, e: Option<@ast::expr>) -> block { fn trans_ret(bcx: block, e: Option<@ast::expr>) -> block {

View file

@ -410,16 +410,10 @@ fn trans_rvalue_stmt_unadjusted(bcx: block, expr: @ast::expr) -> block {
match expr.node { match expr.node {
ast::expr_break(label_opt) => { ast::expr_break(label_opt) => {
if label_opt.is_some() { return controlflow::trans_break(bcx, label_opt);
bcx.tcx().sess.span_unimpl(expr.span, ~"labeled break");
}
return controlflow::trans_break(bcx);
} }
ast::expr_again(label_opt) => { ast::expr_again(label_opt) => {
if label_opt.is_some() { return controlflow::trans_cont(bcx, label_opt);
bcx.tcx().sess.span_unimpl(expr.span, ~"labeled again");
}
return controlflow::trans_cont(bcx);
} }
ast::expr_ret(ex) => { ast::expr_ret(ex) => {
return controlflow::trans_ret(bcx, ex); return controlflow::trans_ret(bcx, ex);
@ -436,8 +430,8 @@ fn trans_rvalue_stmt_unadjusted(bcx: block, expr: @ast::expr) -> block {
ast::expr_while(cond, body) => { ast::expr_while(cond, body) => {
return controlflow::trans_while(bcx, cond, body); return controlflow::trans_while(bcx, cond, body);
} }
ast::expr_loop(body, _) => { ast::expr_loop(body, opt_label) => {
return controlflow::trans_loop(bcx, body); return controlflow::trans_loop(bcx, body, opt_label);
} }
ast::expr_assign(dst, src) => { ast::expr_assign(dst, src) => {
let src_datum = unpack_datum!(bcx, trans_to_datum(bcx, src)); let src_datum = unpack_datum!(bcx, trans_to_datum(bcx, src));

View file

@ -46,7 +46,7 @@ use syntax::ast_map::node_id_to_str;
use syntax::ast_util::{local_def, respan, split_trait_methods}; use syntax::ast_util::{local_def, respan, split_trait_methods};
use syntax::visit; use syntax::visit;
use metadata::csearch; use metadata::csearch;
use util::common::may_break; use util::common::{block_query, loop_query};
use syntax::codemap::span; use syntax::codemap::span;
use pat_util::{pat_is_variant, pat_id_map, PatIdMap}; use pat_util::{pat_is_variant, pat_id_map, PatIdMap};
use middle::ty; use middle::ty;

View file

@ -1665,7 +1665,7 @@ fn check_expr_with_unifier(fcx: @fn_ctxt,
ast::expr_loop(body, _) => { ast::expr_loop(body, _) => {
check_block_no_value(fcx, body); check_block_no_value(fcx, body);
fcx.write_ty(id, ty::mk_nil(tcx)); fcx.write_ty(id, ty::mk_nil(tcx));
bot = !may_break(body); bot = !may_break(tcx, expr.id, body);
} }
ast::expr_match(discrim, arms) => { ast::expr_match(discrim, arms) => {
bot = alt::check_alt(fcx, expr, discrim, arms); bot = alt::check_alt(fcx, expr, discrim, arms);
@ -2544,6 +2544,30 @@ fn ast_expr_vstore_to_vstore(fcx: @fn_ctxt, e: @ast::expr, n: uint,
} }
} }
// Returns true if b contains a break that can exit from b
fn may_break(cx: ty::ctxt, id: ast::node_id, b: ast::blk) -> bool {
// First: is there an unlabeled break immediately
// inside the loop?
(loop_query(b, |e| {
match e {
ast::expr_break(_) => true,
_ => false
}
})) ||
// Second: is there a labeled break with label
// <id> nested anywhere inside the loop?
(block_query(b, |e| {
match e.node {
ast::expr_break(Some(_)) =>
match cx.def_map.find(e.id) {
Some(ast::def_label(loop_id)) if id == loop_id => true,
_ => false,
},
_ => false
}
}))
}
fn check_bounds_are_used(ccx: @crate_ctxt, fn check_bounds_are_used(ccx: @crate_ctxt,
span: span, span: span,
tps: ~[ast::ty_param], tps: ~[ast::ty_param],

View file

@ -58,22 +58,19 @@ fn loop_query(b: ast::blk, p: fn@(ast::expr_) -> bool) -> bool {
return *rs; return *rs;
} }
fn has_nonlocal_exits(b: ast::blk) -> bool { // Takes a predicate p, returns true iff p is true for any subexpressions
do loop_query(b) |e| { // of b -- skipping any inner loops (loop, while, loop_body)
match e { fn block_query(b: ast::blk, p: fn@(@ast::expr) -> bool) -> bool {
ast::expr_break(_) | ast::expr_again(_) => true, let rs = @mut false;
_ => false let visit_expr =
} |e: @ast::expr, &&flag: @mut bool, v: visit::vt<@mut bool>| {
} *flag |= p(e);
} visit::visit_expr(e, flag, v)
};
fn may_break(b: ast::blk) -> bool { let v = visit::mk_vt(@{visit_expr: visit_expr
do loop_query(b) |e| { ,.. *visit::default_visitor()});
match e { visit::visit_block(b, rs, v);
ast::expr_break(_) => true, return *rs;
_ => false
}
}
} }
fn local_rhs_span(l: @ast::local, def: span) -> span { fn local_rhs_span(l: @ast::local, def: span) -> span {

View file

@ -0,0 +1,22 @@
fn main() {
let mut x = 0;
loop foo: {
loop bar: {
loop quux: {
if 1 == 2 {
break foo;
}
else {
break bar;
}
}
loop foo;
}
x = 42;
break;
}
error!("%?", x);
assert(x == 42);
}