about summary refs log tree commit diff
diff options
context:
space:
mode:
authorTim Chevalier <chevalier@alum.wellesley.edu>2012-10-18 12:20:18 -0700
committerTim Chevalier <chevalier@alum.wellesley.edu>2012-10-22 09:20:37 -0700
commitdd66e7549b72575d5d7e06c0173e702e28cdfba7 (patch)
treeb3ce10fb564b8b5fb4dc4af73bd380e6b39d32dd
parent46d4bbbae4e52b79c23136b926c1e3b1f187ce4b (diff)
downloadrust-dd66e7549b72575d5d7e06c0173e702e28cdfba7.tar.gz
rust-dd66e7549b72575d5d7e06c0173e702e28cdfba7.zip
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
-rw-r--r--src/libsyntax/print/pprust.rs5
-rw-r--r--src/rustc/middle/liveness.rs192
-rw-r--r--src/rustc/middle/trans/base.rs7
-rw-r--r--src/rustc/middle/trans/common.rs1
-rw-r--r--src/rustc/middle/trans/controlflow.rs32
-rw-r--r--src/rustc/middle/trans/expr.rs14
-rw-r--r--src/rustc/middle/typeck.rs2
-rw-r--r--src/rustc/middle/typeck/check.rs26
-rw-r--r--src/rustc/util/common.rs29
-rw-r--r--src/test/run-pass/issue-2216.rs22
10 files changed, 232 insertions, 98 deletions
diff --git a/src/libsyntax/print/pprust.rs b/src/libsyntax/print/pprust.rs
index 909f59552d4..33915c8c0c9 100644
--- a/src/libsyntax/print/pprust.rs
+++ b/src/libsyntax/print/pprust.rs
@@ -1180,7 +1180,10 @@ fn print_expr(s: ps, &&expr: @ast::expr) {
       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) => {
diff --git a/src/rustc/middle/liveness.rs b/src/rustc/middle/liveness.rs
index 89d5c842a9f..184d694a50c 100644
--- a/src/rustc/middle/liveness.rs
+++ b/src/rustc/middle/liveness.rs
@@ -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 @@ impl IrMaps {
     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 @@ const ACC_READ: uint = 1u;
 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 @@ impl Liveness {
         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 @@ impl Liveness {
     }
 
     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 @@ impl Liveness {
     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 @@ 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 {
         do io::with_str_writer |wr| {
             wr.write_str(~"[ln(");
@@ -833,18 +882,18 @@ impl Liveness {
         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 @@ impl Liveness {
         // 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 @@ impl Liveness {
     }
 
     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 @@ impl Liveness {
               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 @@ impl Liveness {
             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 @@ impl Liveness {
           }
 
           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 @@ impl Liveness {
 
         */
 
+
         // first iteration:
         let mut first_merge = true;
         let ln = self.live_node(expr.id, expr.span);
@@ -1325,8 +1396,11 @@ impl Liveness {
             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 @@ impl Liveness {
         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 @@ impl Liveness {
         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 @@ 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) {
         debug!("check_move_from_var(%s, %s)",
                ln.to_str(), var.to_str());
diff --git a/src/rustc/middle/trans/base.rs b/src/rustc/middle/trans/base.rs
index 4c9a006007e..d2cde0420fb 100644
--- a/src/rustc/middle/trans/base.rs
+++ b/src/rustc/middle/trans/base.rs
@@ -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
diff --git a/src/rustc/middle/trans/common.rs b/src/rustc/middle/trans/common.rs
index 554a533e110..7f234349d71 100644
--- a/src/rustc/middle/trans/common.rs
+++ b/src/rustc/middle/trans/common.rs
@@ -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.
diff --git a/src/rustc/middle/trans/controlflow.rs b/src/rustc/middle/trans/controlflow.rs
index ce32cd0a2dd..b1ca93f8f8a 100644
--- a/src/rustc/middle/trans/controlflow.rs
+++ b/src/rustc/middle/trans/controlflow.rs
@@ -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 {
diff --git a/src/rustc/middle/trans/expr.rs b/src/rustc/middle/trans/expr.rs
index 30bea1376c0..b0702c80b39 100644
--- a/src/rustc/middle/trans/expr.rs
+++ b/src/rustc/middle/trans/expr.rs
@@ -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));
diff --git a/src/rustc/middle/typeck.rs b/src/rustc/middle/typeck.rs
index 8d10343d78e..90aacb4b7ce 100644
--- a/src/rustc/middle/typeck.rs
+++ b/src/rustc/middle/typeck.rs
@@ -46,7 +46,7 @@ use syntax::ast_map::node_id_to_str;
 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;
diff --git a/src/rustc/middle/typeck/check.rs b/src/rustc/middle/typeck/check.rs
index 5f9e584fa7b..e37c90d98b4 100644
--- a/src/rustc/middle/typeck/check.rs
+++ b/src/rustc/middle/typeck/check.rs
@@ -1665,7 +1665,7 @@ fn check_expr_with_unifier(fcx: @fn_ctxt,
       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],
diff --git a/src/rustc/util/common.rs b/src/rustc/util/common.rs
index 123905adba1..0448b022602 100644
--- a/src/rustc/util/common.rs
+++ b/src/rustc/util/common.rs
@@ -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 {
diff --git a/src/test/run-pass/issue-2216.rs b/src/test/run-pass/issue-2216.rs
new file mode 100644
index 00000000000..11815db1763
--- /dev/null
+++ b/src/test/run-pass/issue-2216.rs
@@ -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);
+}
\ No newline at end of file