Preliminary support for labeled break/continue for loop
s
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:
parent
46d4bbbae4
commit
dd66e7549b
@ -1180,7 +1180,10 @@ fn print_field(s: ps, field: ast::field) {
|
||||
ast::expr_loop(blk, opt_ident) => {
|
||||
head(s, ~"loop");
|
||||
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);
|
||||
}
|
||||
ast::expr_match(expr, arms) => {
|
||||
|
@ -95,9 +95,9 @@
|
||||
use dvec::DVec;
|
||||
use std::map::HashMap;
|
||||
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 syntax::codemap::span;
|
||||
use syntax::codemap::{span, span_to_str};
|
||||
use syntax::ast::*;
|
||||
use io::WriterUtil;
|
||||
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) }
|
||||
}
|
||||
|
||||
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,
|
||||
method_map: typeck::method_map,
|
||||
crate: @crate) -> last_use_map {
|
||||
@ -277,8 +287,8 @@ fn IrMaps(tcx: ty::ctxt, method_map: typeck::method_map,
|
||||
tcx: tcx,
|
||||
method_map: method_map,
|
||||
last_use_map: last_use_map,
|
||||
num_live_nodes: 0u,
|
||||
num_vars: 0u,
|
||||
num_live_nodes: 0,
|
||||
num_vars: 0,
|
||||
live_node_map: HashMap(),
|
||||
variable_map: HashMap(),
|
||||
capture_map: HashMap(),
|
||||
@ -291,9 +301,10 @@ impl IrMaps {
|
||||
fn add_live_node(lnk: LiveNodeKind) -> LiveNode {
|
||||
let ln = LiveNode(self.num_live_nodes);
|
||||
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
|
||||
}
|
||||
@ -308,7 +319,7 @@ fn add_live_node_for_node(node_id: node_id, lnk: LiveNodeKind) {
|
||||
fn add_variable(vk: VarKind) -> Variable {
|
||||
let v = Variable(self.num_vars);
|
||||
self.var_kinds.push(vk);
|
||||
self.num_vars += 1u;
|
||||
self.num_vars += 1;
|
||||
|
||||
match vk {
|
||||
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_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
|
||||
// being the location that the variable is used. This results
|
||||
// in better error messages than just pointing at the closure
|
||||
@ -571,14 +586,22 @@ fn invalid_users() -> users {
|
||||
const ACC_WRITE: uint = 2u;
|
||||
const ACC_USE: uint = 4u;
|
||||
|
||||
type LiveNodeMap = HashMap<node_id, LiveNode>;
|
||||
|
||||
struct Liveness {
|
||||
tcx: ty::ctxt,
|
||||
ir: @IrMaps,
|
||||
s: Specials,
|
||||
successors: ~[mut LiveNode],
|
||||
users: ~[mut users],
|
||||
mut break_ln: LiveNode,
|
||||
mut cont_ln: LiveNode,
|
||||
// The list of node IDs for the nested loop scopes
|
||||
// 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 {
|
||||
@ -594,8 +617,9 @@ fn Liveness(ir: @IrMaps, specials: Specials) -> Liveness {
|
||||
vec::to_mut(
|
||||
vec::from_elem(ir.num_live_nodes * ir.num_vars,
|
||||
invalid_users())),
|
||||
break_ln: invalid_node(),
|
||||
cont_ln: invalid_node()
|
||||
loop_scope: @DVec(),
|
||||
break_ln: HashMap(),
|
||||
cont_ln: HashMap()
|
||||
}
|
||||
}
|
||||
|
||||
@ -691,6 +715,9 @@ fn live_on_entry(ln: LiveNode, var: Variable)
|
||||
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)
|
||||
-> Option<LiveNodeKind> {
|
||||
|
||||
@ -717,8 +744,8 @@ fn assigned_on_exit(ln: LiveNode, var: Variable)
|
||||
}
|
||||
|
||||
fn indices(ln: LiveNode, op: fn(uint)) {
|
||||
let node_base_idx = self.idx(ln, Variable(0u));
|
||||
for uint::range(0u, self.ir.num_vars) |var_idx| {
|
||||
let node_base_idx = self.idx(ln, Variable(0));
|
||||
for uint::range(0, self.ir.num_vars) |var_idx| {
|
||||
op(node_base_idx + var_idx)
|
||||
}
|
||||
}
|
||||
@ -735,8 +762,8 @@ fn indices2(ln: LiveNode, succ_ln: LiveNode,
|
||||
fn write_vars(wr: io::Writer,
|
||||
ln: LiveNode,
|
||||
test: fn(uint) -> LiveNode) {
|
||||
let node_base_idx = self.idx(ln, Variable(0u));
|
||||
for uint::range(0u, self.ir.num_vars) |var_idx| {
|
||||
let node_base_idx = self.idx(ln, Variable(0));
|
||||
for uint::range(0, self.ir.num_vars) |var_idx| {
|
||||
let idx = node_base_idx + var_idx;
|
||||
if test(idx).is_valid() {
|
||||
wr.write_str(~" ");
|
||||
@ -745,6 +772,28 @@ fn write_vars(wr: io::Writer,
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
do io::with_str_writer |wr| {
|
||||
wr.write_str(~"[ln(");
|
||||
@ -833,18 +882,18 @@ fn acc(ln: LiveNode, var: Variable, acc: uint) {
|
||||
let idx = self.idx(ln, var);
|
||||
let user = &mut self.users[idx];
|
||||
|
||||
if (acc & ACC_WRITE) != 0u {
|
||||
if (acc & ACC_WRITE) != 0 {
|
||||
user.reader = invalid_node();
|
||||
user.writer = ln;
|
||||
}
|
||||
|
||||
// Important: if we both read/write, must do read second
|
||||
// or else the write will override.
|
||||
if (acc & ACC_READ) != 0u {
|
||||
if (acc & ACC_READ) != 0 {
|
||||
user.reader = ln;
|
||||
}
|
||||
|
||||
if (acc & ACC_USE) != 0u {
|
||||
if (acc & ACC_USE) != 0 {
|
||||
self.users[idx].used = true;
|
||||
}
|
||||
|
||||
@ -858,10 +907,13 @@ fn compute(decl: fn_decl, body: blk) -> LiveNode {
|
||||
// if there is a `break` or `again` at the top level, then it's
|
||||
// effectively a return---this only occurs in `for` loops,
|
||||
// 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 =
|
||||
self.with_loop_nodes(self.s.exit_ln, self.s.exit_ln, || {
|
||||
self.propagate_through_fn_block(decl, body)
|
||||
});
|
||||
self.with_loop_nodes(body.node.id, self.s.exit_ln, self.s.exit_ln,
|
||||
|| { self.propagate_through_fn_block(decl, body) });
|
||||
|
||||
// hack to skip the loop unless debug! is enabled:
|
||||
debug!("^^ liveness computation results for body %d (entry=%s)",
|
||||
@ -972,6 +1024,9 @@ fn propagate_through_opt_expr(opt_expr: Option<@expr>,
|
||||
}
|
||||
|
||||
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 {
|
||||
// Interesting cases with control flow or which gen/kill
|
||||
|
||||
@ -983,16 +1038,27 @@ fn propagate_through_expr(expr: @expr, succ: LiveNode) -> LiveNode {
|
||||
self.propagate_through_expr(e, succ)
|
||||
}
|
||||
|
||||
expr_fn(*) | expr_fn_block(*) => {
|
||||
// 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_fn(_, _, blk, _) | expr_fn_block(_, blk, _) => {
|
||||
debug!("%s is an expr_fn or expr_fn_block",
|
||||
expr_to_str(expr, self.tcx.sess.intr()));
|
||||
|
||||
/*
|
||||
The next-node for a break is the successor of the entire
|
||||
loop. The next-node for a continue is the top of this loop.
|
||||
*/
|
||||
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) => {
|
||||
@ -1021,6 +1087,8 @@ fn propagate_through_expr(expr: @expr, succ: LiveNode) -> LiveNode {
|
||||
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, _) => {
|
||||
self.propagate_through_loop(expr, None, blk, succ)
|
||||
}
|
||||
@ -1062,29 +1130,31 @@ fn propagate_through_expr(expr: @expr, succ: LiveNode) -> LiveNode {
|
||||
}
|
||||
|
||||
expr_break(opt_label) => {
|
||||
if !self.break_ln.is_valid() {
|
||||
self.tcx.sess.span_bug(
|
||||
expr.span, ~"break with invalid break_ln");
|
||||
}
|
||||
// Find which label this break jumps to
|
||||
let sc = self.find_loop_scope(opt_label, expr.id, expr.span);
|
||||
|
||||
if opt_label.is_some() {
|
||||
self.tcx.sess.span_unimpl(expr.span, ~"labeled break");
|
||||
}
|
||||
// Now that we know the label we're going to,
|
||||
// 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) => {
|
||||
if !self.cont_ln.is_valid() {
|
||||
self.tcx.sess.span_bug(
|
||||
expr.span, ~"cont with invalid cont_ln");
|
||||
}
|
||||
// Find which label this expr continues to to
|
||||
let sc = self.find_loop_scope(opt_label, expr.id, expr.span);
|
||||
|
||||
if opt_label.is_some() {
|
||||
self.tcx.sess.span_unimpl(expr.span, ~"labeled again");
|
||||
}
|
||||
// Now that we know the label we're going to,
|
||||
// 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) => {
|
||||
@ -1314,6 +1384,7 @@ fn propagate_through_loop(expr: @expr,
|
||||
|
||||
*/
|
||||
|
||||
|
||||
// first iteration:
|
||||
let mut first_merge = true;
|
||||
let ln = self.live_node(expr.id, expr.span);
|
||||
@ -1325,8 +1396,11 @@ fn propagate_through_loop(expr: @expr,
|
||||
self.merge_from_succ(ln, succ, first_merge);
|
||||
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 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)
|
||||
});
|
||||
|
||||
@ -1334,7 +1408,8 @@ fn propagate_through_loop(expr: @expr,
|
||||
while self.merge_from_succ(ln, body_ln, first_merge) {
|
||||
first_merge = false;
|
||||
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)
|
||||
});
|
||||
}
|
||||
@ -1342,15 +1417,16 @@ fn propagate_through_loop(expr: @expr,
|
||||
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,
|
||||
f: fn() -> R) -> R {
|
||||
let bl = self.break_ln, cl = self.cont_ln;
|
||||
self.break_ln = break_ln;
|
||||
self.cont_ln = cont_ln;
|
||||
let r <- f();
|
||||
self.break_ln = bl;
|
||||
self.cont_ln = cl;
|
||||
debug!("with_loop_nodes: %d %u", loop_node_id, *break_ln);
|
||||
self.loop_scope.push(loop_node_id);
|
||||
self.break_ln.insert(loop_node_id, break_ln);
|
||||
self.cont_ln.insert(loop_node_id, cont_ln);
|
||||
let r = f();
|
||||
self.loop_scope.pop();
|
||||
move r
|
||||
}
|
||||
}
|
||||
@ -1526,6 +1602,10 @@ fn check_ret(id: node_id, sp: span, _fk: visit::fn_kind,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
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) {
|
||||
debug!("check_move_from_var(%s, %s)",
|
||||
ln.to_str(), var.to_str());
|
||||
|
@ -1050,7 +1050,7 @@ fn new_block(cx: fn_ctxt, parent: Option<block>, +kind: 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})
|
||||
}
|
||||
|
||||
@ -1067,10 +1067,11 @@ fn scope_block(bcx: block,
|
||||
n, opt_node_info);
|
||||
}
|
||||
|
||||
fn loop_scope_block(bcx: block, loop_break: block, n: ~str,
|
||||
opt_node_info: Option<node_info>) -> block {
|
||||
fn loop_scope_block(bcx: block, loop_break: block, loop_label: Option<ident>,
|
||||
n: ~str, opt_node_info: Option<node_info>) -> block {
|
||||
return new_block(bcx.fcx, Some(bcx), block_scope({
|
||||
loop_break: Some(loop_break),
|
||||
loop_label: loop_label,
|
||||
mut cleanups: ~[],
|
||||
mut cleanup_paths: ~[],
|
||||
mut landing_pad: None
|
||||
|
@ -445,6 +445,7 @@ enum block_kind {
|
||||
|
||||
type scope_info = {
|
||||
loop_break: Option<block>,
|
||||
loop_label: Option<ident>,
|
||||
// A list of functions that must be run at when leaving this
|
||||
// block, cleaning up any variables that were introduced in the
|
||||
// block.
|
||||
|
@ -113,7 +113,9 @@ fn trans_while(bcx: block, cond: @ast::expr, body: ast::blk)
|
||||
// | body_bcx_out --+
|
||||
// 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 body_bcx_in = scope_block(loop_bcx, body.info(), ~"while loop body");
|
||||
Br(bcx, loop_bcx.llbb);
|
||||
@ -133,10 +135,11 @@ fn trans_while(bcx: block, cond: @ast::expr, body: ast::blk)
|
||||
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 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);
|
||||
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);
|
||||
@ -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 {
|
||||
let _icx = bcx.insn_ctxt("trans_break_cont");
|
||||
// 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;
|
||||
loop {
|
||||
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 {
|
||||
brk
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
|
||||
fn trans_break(bcx: block) -> block {
|
||||
return trans_break_cont(bcx, true);
|
||||
fn trans_break(bcx: block, label_opt: Option<ident>) -> block {
|
||||
return trans_break_cont(bcx, label_opt, true);
|
||||
}
|
||||
|
||||
fn trans_cont(bcx: block) -> block {
|
||||
return trans_break_cont(bcx, false);
|
||||
fn trans_cont(bcx: block, label_opt: Option<ident>) -> block {
|
||||
return trans_break_cont(bcx, label_opt, false);
|
||||
}
|
||||
|
||||
fn trans_ret(bcx: block, e: Option<@ast::expr>) -> block {
|
||||
|
@ -410,16 +410,10 @@ fn trans_rvalue_stmt_unadjusted(bcx: block, expr: @ast::expr) -> block {
|
||||
|
||||
match expr.node {
|
||||
ast::expr_break(label_opt) => {
|
||||
if label_opt.is_some() {
|
||||
bcx.tcx().sess.span_unimpl(expr.span, ~"labeled break");
|
||||
}
|
||||
return controlflow::trans_break(bcx);
|
||||
return controlflow::trans_break(bcx, label_opt);
|
||||
}
|
||||
ast::expr_again(label_opt) => {
|
||||
if label_opt.is_some() {
|
||||
bcx.tcx().sess.span_unimpl(expr.span, ~"labeled again");
|
||||
}
|
||||
return controlflow::trans_cont(bcx);
|
||||
return controlflow::trans_cont(bcx, label_opt);
|
||||
}
|
||||
ast::expr_ret(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) => {
|
||||
return controlflow::trans_while(bcx, cond, body);
|
||||
}
|
||||
ast::expr_loop(body, _) => {
|
||||
return controlflow::trans_loop(bcx, body);
|
||||
ast::expr_loop(body, opt_label) => {
|
||||
return controlflow::trans_loop(bcx, body, opt_label);
|
||||
}
|
||||
ast::expr_assign(dst, src) => {
|
||||
let src_datum = unpack_datum!(bcx, trans_to_datum(bcx, src));
|
||||
|
@ -46,7 +46,7 @@
|
||||
use syntax::ast_util::{local_def, respan, split_trait_methods};
|
||||
use syntax::visit;
|
||||
use metadata::csearch;
|
||||
use util::common::may_break;
|
||||
use util::common::{block_query, loop_query};
|
||||
use syntax::codemap::span;
|
||||
use pat_util::{pat_is_variant, pat_id_map, PatIdMap};
|
||||
use middle::ty;
|
||||
|
@ -1665,7 +1665,7 @@ fn check_field(fcx: @fn_ctxt, expr: @ast::expr, is_callee: bool,
|
||||
ast::expr_loop(body, _) => {
|
||||
check_block_no_value(fcx, body);
|
||||
fcx.write_ty(id, ty::mk_nil(tcx));
|
||||
bot = !may_break(body);
|
||||
bot = !may_break(tcx, expr.id, body);
|
||||
}
|
||||
ast::expr_match(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,
|
||||
span: span,
|
||||
tps: ~[ast::ty_param],
|
||||
|
@ -58,22 +58,19 @@ fn loop_query(b: ast::blk, p: fn@(ast::expr_) -> bool) -> bool {
|
||||
return *rs;
|
||||
}
|
||||
|
||||
fn has_nonlocal_exits(b: ast::blk) -> bool {
|
||||
do loop_query(b) |e| {
|
||||
match e {
|
||||
ast::expr_break(_) | ast::expr_again(_) => true,
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn may_break(b: ast::blk) -> bool {
|
||||
do loop_query(b) |e| {
|
||||
match e {
|
||||
ast::expr_break(_) => true,
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
// Takes a predicate p, returns true iff p is true for any subexpressions
|
||||
// of b -- skipping any inner loops (loop, while, loop_body)
|
||||
fn block_query(b: ast::blk, p: fn@(@ast::expr) -> bool) -> bool {
|
||||
let rs = @mut false;
|
||||
let visit_expr =
|
||||
|e: @ast::expr, &&flag: @mut bool, v: visit::vt<@mut bool>| {
|
||||
*flag |= p(e);
|
||||
visit::visit_expr(e, flag, v)
|
||||
};
|
||||
let v = visit::mk_vt(@{visit_expr: visit_expr
|
||||
,.. *visit::default_visitor()});
|
||||
visit::visit_block(b, rs, v);
|
||||
return *rs;
|
||||
}
|
||||
|
||||
fn local_rhs_span(l: @ast::local, def: span) -> span {
|
||||
|
22
src/test/run-pass/issue-2216.rs
Normal file
22
src/test/run-pass/issue-2216.rs
Normal 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);
|
||||
}
|
Loading…
Reference in New Issue
Block a user