about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/rustc/middle/check_loop.rs3
-rw-r--r--src/rustc/middle/trans/base.rs121
-rw-r--r--src/rustc/middle/trans/closure.rs40
-rw-r--r--src/rustc/middle/trans/common.rs3
-rw-r--r--src/rustc/middle/trans/impl.rs2
-rw-r--r--src/rustc/middle/typeck.rs65
-rw-r--r--src/rustc/syntax/ast.rs3
-rw-r--r--src/test/run-pass/ret-break-cont-in-block.rs57
8 files changed, 243 insertions, 51 deletions
diff --git a/src/rustc/middle/check_loop.rs b/src/rustc/middle/check_loop.rs
index b145ae0d75a..4d69154aac8 100644
--- a/src/rustc/middle/check_loop.rs
+++ b/src/rustc/middle/check_loop.rs
@@ -25,7 +25,8 @@ fn check_crate(tcx: ty::ctxt, crate: @crate) {
                 v.visit_block(b, {in_loop: false, can_ret: false}, v);
               }
               expr_loop_body(@{node: expr_fn_block(_, b), _}) {
-                v.visit_block(b, {in_loop: true, can_ret: false}, v);
+                let blk = is_blockish(ty::ty_fn_proto(ty::expr_ty(tcx, e)));
+                v.visit_block(b, {in_loop: true, can_ret: blk}, v);
               }
               expr_break {
                 if !cx.in_loop {
diff --git a/src/rustc/middle/trans/base.rs b/src/rustc/middle/trans/base.rs
index e1aeb2abc5e..75416b6dae9 100644
--- a/src/rustc/middle/trans/base.rs
+++ b/src/rustc/middle/trans/base.rs
@@ -2516,15 +2516,43 @@ fn trans_cast(cx: block, e: @ast::expr, id: ast::node_id,
     ret store_in_dest(e_res.bcx, newval, dest);
 }
 
+fn trans_loop_body(bcx: block, e: @ast::expr, ret_flag: option<ValueRef>,
+                   dest: dest) -> block {
+    alt check e.node {
+      ast::expr_loop_body(b@@{node: ast::expr_fn_block(decl, body), _}) {
+        alt check ty::get(expr_ty(bcx, e)).struct {
+          ty::ty_fn({proto, _}) {
+            closure::trans_expr_fn(bcx, proto, decl, body, e.span, b.id,
+                                   {copies: [], moves: []}, some(ret_flag),
+                                   dest)
+          }
+        }
+      }
+    }
+}
+
 // temp_cleanups: cleanups that should run only if failure occurs before the
 // call takes place:
 fn trans_arg_expr(cx: block, arg: ty::arg, lldestty: TypeRef, e: @ast::expr,
-                  &temp_cleanups: [ValueRef]) -> result {
+                  &temp_cleanups: [ValueRef], ret_flag: option<ValueRef>)
+    -> result {
     let _icx = cx.insn_ctxt("trans_arg_expr");
     let ccx = cx.ccx();
     let e_ty = expr_ty(cx, e);
     let is_bot = ty::type_is_bot(e_ty);
-    let lv = trans_temp_lval(cx, e);
+    let lv = alt ret_flag {
+      // If there is a ret_flag, this *must* be a loop body
+      some(ptr) {
+        alt check e.node {
+          ast::expr_loop_body(blk) {
+            let scratch = alloc_ty(cx, expr_ty(cx, blk));
+            let bcx = trans_loop_body(cx, e, ret_flag, save_in(scratch));
+            {bcx: bcx, val: scratch, kind: temporary}
+          }
+        }
+      }
+      none { trans_temp_lval(cx, e) }
+    };
     let mut bcx = lv.bcx;
     let mut val = lv.val;
     let arg_mode = ty::resolved_mode(ccx.tcx, arg.mode);
@@ -2595,7 +2623,7 @@ enum call_args {
 //  - new_fn_ctxt
 //  - trans_args
 fn trans_args(cx: block, llenv: ValueRef, args: call_args, fn_ty: ty::t,
-              dest: dest)
+              dest: dest, ret_flag: option<ValueRef>)
     -> {bcx: block, args: [ValueRef], retslot: ValueRef} {
     let _icx = cx.insn_ctxt("trans_args");
     let mut temp_cleanups = [];
@@ -2630,13 +2658,13 @@ fn trans_args(cx: block, llenv: ValueRef, args: call_args, fn_ty: ty::t,
     alt args {
       arg_exprs(es) {
         let llarg_tys = type_of_explicit_args(ccx, arg_tys);
-        let mut i = 0u;
-        for e: @ast::expr in es {
+        let last = es.len() - 1u;
+        vec::iteri(es) {|i, e|
             let r = trans_arg_expr(bcx, arg_tys[i], llarg_tys[i],
-                                   e, temp_cleanups);
+                                   e, temp_cleanups, if i == last { ret_flag }
+                                                     else { none });
             bcx = r.bcx;
             llargs += [r.val];
-            i += 1u;
         }
       }
       arg_vals(vs) {
@@ -2664,14 +2692,44 @@ fn trans_call(in_cx: block, f: @ast::expr,
                      {|cx| trans_callee(cx, f)}, args, dest)
 }
 
+fn body_contains_ret(body: ast::blk) -> bool {
+    let cx = {mut found: false};
+    visit::visit_block(body, cx, visit::mk_vt(@{
+        visit_item: {|_i, _cx, _v|},
+        visit_expr: {|e: @ast::expr, cx: {mut found: bool}, v|
+            if !cx.found {
+                alt e.node {
+                  ast::expr_ret(_) { cx.found = true; }
+                  _ { visit::visit_expr(e, cx, v); }
+                }
+            }
+        } with *visit::default_visitor()
+    }));
+    cx.found
+}
+
 fn trans_call_inner(in_cx: block, fn_expr_ty: ty::t, ret_ty: ty::t,
                     get_callee: fn(block) -> lval_maybe_callee,
                     args: call_args, dest: dest)
     -> block {
+    let ret_in_loop = alt args {
+      arg_exprs(args) { args.len() > 0u && alt vec::last(args).node {
+        ast::expr_loop_body(@{node: ast::expr_fn_block(_, body), _}) {
+          body_contains_ret(body)
+        }
+        _ { false }
+      } }
+      _ { false }
+    };
     with_scope(in_cx, "call") {|cx|
         let f_res = get_callee(cx);
         let mut bcx = f_res.bcx;
         let ccx = cx.ccx();
+        let ret_flag = if ret_in_loop {
+            let flag = alloca(in_cx, T_bool());
+            Store(cx, C_bool(false), flag);
+            some(flag)
+        } else { none };
 
         let mut faddr = f_res.val;
         let llenv = alt f_res.env {
@@ -2695,7 +2753,7 @@ fn trans_call_inner(in_cx: block, fn_expr_ty: ty::t, ret_ty: ty::t,
         };
 
         let args_res = {
-            trans_args(bcx, llenv, args, fn_expr_ty, dest)
+            trans_args(bcx, llenv, args, fn_expr_ty, dest, ret_flag)
         };
         bcx = args_res.bcx;
         let mut llargs = args_res.args;
@@ -2718,7 +2776,19 @@ fn trans_call_inner(in_cx: block, fn_expr_ty: ty::t, ret_ty: ty::t,
             *cell = Load(bcx, llretslot);
           }
         }
-        if ty::type_is_bot(ret_ty) { Unreachable(bcx); }
+        if ty::type_is_bot(ret_ty) {
+            Unreachable(bcx);
+        } else if ret_in_loop {
+            bcx = with_cond(bcx, Load(bcx, option::get(ret_flag))) {|bcx|
+                option::may(bcx.fcx.loop_ret) {|lret|
+                    Store(bcx, C_bool(true), lret.flagptr);
+                    Store(bcx, C_bool(false), bcx.fcx.llretptr);
+                }
+                cleanup_and_leave(bcx, none, some(bcx.fcx.llreturn));
+                Unreachable(bcx);
+                bcx
+            }
+        }
         bcx
     }
 }
@@ -2991,7 +3061,7 @@ fn trans_expr(bcx: block, e: @ast::expr, dest: dest) -> block {
       ast::expr_addr_of(_, x) { ret trans_addr_of(bcx, x, dest); }
       ast::expr_fn(proto, decl, body, cap_clause) {
         ret closure::trans_expr_fn(bcx, proto, decl, body, e.span, e.id,
-                                   *cap_clause, false, dest);
+                                   *cap_clause, none, dest);
       }
       ast::expr_fn_block(decl, body) {
         alt check ty::get(expr_ty(bcx, e)).struct {
@@ -2999,17 +3069,12 @@ fn trans_expr(bcx: block, e: @ast::expr, dest: dest) -> block {
             #debug("translating fn_block %s with type %s",
                    expr_to_str(e), ty_to_str(tcx, expr_ty(bcx, e)));
             ret closure::trans_expr_fn(bcx, proto, decl, body, e.span, e.id,
-                                       {copies: [], moves: []}, false, dest);
+                                       {copies: [], moves: []}, none, dest);
           }
         }
       }
-      ast::expr_loop_body(b@@{node: ast::expr_fn_block(decl, body), _}) {
-        alt check ty::get(expr_ty(bcx, e)).struct {
-          ty::ty_fn({proto, _}) {
-            ret closure::trans_expr_fn(bcx, proto, decl, body, e.span, b.id,
-                                       {copies: [], moves: []}, true, dest);
-          }
-        }
+      ast::expr_loop_body(blk) {
+        ret trans_loop_body(bcx, e, none, dest);
       }
       ast::expr_bind(f, args) {
         ret closure::trans_bind(
@@ -3406,8 +3471,25 @@ fn trans_cont(cx: block) -> block {
 fn trans_ret(bcx: block, e: option<@ast::expr>) -> block {
     let _icx = bcx.insn_ctxt("trans_ret");
     let mut bcx = bcx;
+    let retptr = alt bcx.fcx.loop_ret {
+      some({flagptr, retptr}) {
+        // This is a loop body return. Must set continue flag (our retptr)
+        // to false, return flag to true, and then store the value in the
+        // parent's retptr.
+        Store(bcx, C_bool(true), flagptr);
+        Store(bcx, C_bool(false), bcx.fcx.llretptr);
+        alt e {
+          some(x) { PointerCast(bcx, retptr,
+                                T_ptr(type_of(bcx.ccx(), expr_ty(bcx, x)))) }
+          none { retptr }
+        }
+      }
+      none { bcx.fcx.llretptr }
+    };
     alt e {
-      some(x) { bcx = trans_expr_save_in(bcx, x, bcx.fcx.llretptr); }
+      some(x) {
+        bcx = trans_expr_save_in(bcx, x, retptr);
+      }
       _ {}
     }
     cleanup_and_leave(bcx, none, some(bcx.fcx.llreturn));
@@ -3793,6 +3875,7 @@ fn new_fn_ctxt_w_id(ccx: @crate_ctxt, path: path,
           mut llreturn: llbbs.rt,
           mut llself: none,
           mut personality: none,
+          mut loop_ret: none,
           llargs: int_hash::<local_val>(),
           lllocals: int_hash::<local_val>(),
           llupvars: int_hash::<ValueRef>(),
diff --git a/src/rustc/middle/trans/closure.rs b/src/rustc/middle/trans/closure.rs
index 0ac132562fe..e614c4e8f0f 100644
--- a/src/rustc/middle/trans/closure.rs
+++ b/src/rustc/middle/trans/closure.rs
@@ -287,7 +287,8 @@ fn store_environment(bcx: block,
 fn build_closure(bcx0: block,
                  cap_vars: [capture::capture_var],
                  ck: ty::closure_kind,
-                 id: ast::node_id) -> closure_result {
+                 id: ast::node_id,
+                 include_ret_handle: option<ValueRef>) -> closure_result {
     let _icx = bcx0.insn_ctxt("closure::build_closure");
     // If we need to, package up the iterator body to call
     let mut env_vals = [];
@@ -324,6 +325,16 @@ fn build_closure(bcx0: block,
           }
         }
     }
+    option::may(include_ret_handle) {|flagptr|
+        let our_ret = alt bcx.fcx.loop_ret {
+          some({retptr, _}) { retptr }
+          none { bcx.fcx.llretptr }
+        };
+        let nil_ret = PointerCast(bcx, our_ret, T_ptr(T_nil()));
+        env_vals +=
+            [env_ref(flagptr, ty::mk_mut_ptr(tcx, ty::mk_bool(tcx)), owned),
+             env_ref(nil_ret, ty::mk_nil_ptr(tcx), owned)];
+    }
     ret store_environment(bcx, env_vals, ck);
 }
 
@@ -333,6 +344,7 @@ fn build_closure(bcx0: block,
 fn load_environment(fcx: fn_ctxt,
                     cdata_ty: ty::t,
                     cap_vars: [capture::capture_var],
+                    load_ret_handle: bool,
                     ck: ty::closure_kind) {
     let _icx = fcx.insn_ctxt("closure::load_environment");
     let bcx = raw_block(fcx, fcx.llloadenv);
@@ -341,23 +353,30 @@ fn load_environment(fcx: fn_ctxt,
     let llcdata = base::opaque_box_body(bcx, cdata_ty, fcx.llenv);
 
     // Populate the upvars from the environment.
-    let mut i = 0u;
+    let mut i = 0;
     vec::iter(cap_vars) { |cap_var|
         alt cap_var.mode {
           capture::cap_drop { /* ignore */ }
           _ {
             let mut upvarptr =
-                GEPi(bcx, llcdata, [0, abi::closure_body_bindings, i as int]);
+                GEPi(bcx, llcdata, [0, abi::closure_body_bindings, i]);
             alt ck {
               ty::ck_block { upvarptr = Load(bcx, upvarptr); }
               ty::ck_uniq | ty::ck_box { }
             }
             let def_id = ast_util::def_id_of_def(cap_var.def);
             fcx.llupvars.insert(def_id.node, upvarptr);
-            i += 1u;
+            i += 1;
           }
         }
     }
+    if load_ret_handle {
+        let flagptr = Load(bcx, GEPi(bcx, llcdata,
+                                     [0, abi::closure_body_bindings, i]));
+        let retptr = Load(bcx, GEPi(bcx, llcdata,
+                                    [0, abi::closure_body_bindings, i+1]));
+        fcx.loop_ret = some({flagptr: flagptr, retptr: retptr});
+    }
 }
 
 fn trans_expr_fn(bcx: block,
@@ -367,7 +386,7 @@ fn trans_expr_fn(bcx: block,
                  sp: span,
                  id: ast::node_id,
                  cap_clause: ast::capture_clause,
-                 is_loop_body: bool,
+                 is_loop_body: option<option<ValueRef>>,
                  dest: dest) -> block {
     let _icx = bcx.insn_ctxt("closure::trans_expr_fn");
     if dest == ignore { ret bcx; }
@@ -382,12 +401,17 @@ fn trans_expr_fn(bcx: block,
     let trans_closure_env = fn@(ck: ty::closure_kind) -> ValueRef {
         let cap_vars = capture::compute_capture_vars(
             ccx.tcx, id, proto, cap_clause);
-        let {llbox, cdata_ty, bcx} = build_closure(bcx, cap_vars, ck, id);
+        let ret_handle = alt is_loop_body { some(x) { x } none { none } };
+        let {llbox, cdata_ty, bcx} = build_closure(bcx, cap_vars, ck, id,
+                                                   ret_handle);
         trans_closure(ccx, sub_path, decl, body, llfn, no_self,
                       bcx.fcx.param_substs, id, {|fcx|
-            load_environment(fcx, cdata_ty, cap_vars, ck);
+            load_environment(fcx, cdata_ty, cap_vars,
+                             option::is_some(ret_handle), ck);
         }, {|bcx|
-            if is_loop_body { Store(bcx, C_bool(true), bcx.fcx.llretptr); }
+            if option::is_some(is_loop_body) {
+                Store(bcx, C_bool(true), bcx.fcx.llretptr);
+            }
         });
         llbox
     };
diff --git a/src/rustc/middle/trans/common.rs b/src/rustc/middle/trans/common.rs
index 5714ef23a5b..af3dd12690e 100644
--- a/src/rustc/middle/trans/common.rs
+++ b/src/rustc/middle/trans/common.rs
@@ -165,6 +165,9 @@ type fn_ctxt = @{
     // The a value alloca'd for calls to upcalls.rust_personality. Used when
     // outputting the resume instruction.
     mut personality: option<ValueRef>,
+    // If this is a for-loop body that returns, this holds the pointers needed
+    // for that
+    mut loop_ret: option<{flagptr: ValueRef, retptr: ValueRef}>,
 
     // Maps arguments to allocas created for them in llallocas.
     llargs: hashmap<ast::node_id, local_val>,
diff --git a/src/rustc/middle/trans/impl.rs b/src/rustc/middle/trans/impl.rs
index 2f2aaeaef14..8cfa7c8433c 100644
--- a/src/rustc/middle/trans/impl.rs
+++ b/src/rustc/middle/trans/impl.rs
@@ -36,7 +36,7 @@ fn trans_self_arg(bcx: block, base: @ast::expr) -> result {
     let mut temp_cleanups = [];
     let result = trans_arg_expr(bcx, {mode: m_by_ref, ty: basety},
                                 T_ptr(type_of::type_of(bcx.ccx(), basety)),
-                                base, temp_cleanups);
+                                base, temp_cleanups, none);
 
     // by-ref self argument should not require cleanup in the case of
     // other arguments failing:
diff --git a/src/rustc/middle/typeck.rs b/src/rustc/middle/typeck.rs
index 86cf9d34815..721b846e052 100644
--- a/src/rustc/middle/typeck.rs
+++ b/src/rustc/middle/typeck.rs
@@ -71,6 +71,8 @@ type fn_ctxt =
     // with any nested functions that capture the environment
     // (and with any functions whose environment is being captured).
     {ret_ty: ty::t,
+     // Used by loop bodies that return from the outer function
+     indirect_ret_ty: option<ty::t>,
      purity: ast::purity,
      proto: ast::proto,
      infcx: infer::infer_ctxt,
@@ -2286,7 +2288,8 @@ fn check_expr_fn_with_unifier(fcx: @fn_ctxt,
                               decl: ast::fn_decl,
                               body: ast::blk,
                               unify: unifier,
-                              expected: ty::t) {
+                              expected: ty::t,
+                              is_loop_body: bool) {
     let tcx = fcx.ccx.tcx;
     let fty = ty::mk_fn(tcx,
                         ty_of_fn_decl(tcx, m_check_tyvar(fcx), proto, decl));
@@ -2303,7 +2306,7 @@ fn check_expr_fn_with_unifier(fcx: @fn_ctxt,
     // record projection work on type inferred arguments.
     unify(fcx, expr.span, expected, fty);
 
-    check_fn(fcx.ccx, proto, decl, body, expr.id, some(fcx));
+    check_fn(fcx.ccx, proto, decl, body, expr.id, is_loop_body, some(fcx));
 }
 
 type check_call_or_bind_result = {
@@ -2766,15 +2769,18 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, expr: @ast::expr, unify: unifier,
       ast::expr_cont { write_bot(tcx, id); bot = true; }
       ast::expr_ret(expr_opt) {
         bot = true;
+        let ret_ty = alt fcx.indirect_ret_ty {
+          some(t) { t } none { fcx.ret_ty }
+        };
         alt expr_opt {
           none {
             let nil = ty::mk_nil(tcx);
-            if !are_compatible(fcx, fcx.ret_ty, nil) {
+            if !are_compatible(fcx, ret_ty, nil) {
                 tcx.sess.span_err(expr.span,
                                   "ret; in function returning non-nil");
             }
           }
-          some(e) { check_expr_with(fcx, e, fcx.ret_ty); }
+          some(e) { check_expr_with(fcx, e, ret_ty); }
         }
         write_bot(tcx, id);
       }
@@ -2895,7 +2901,7 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, expr: @ast::expr, unify: unifier,
       }
       ast::expr_fn(proto, decl, body, captures) {
         check_expr_fn_with_unifier(fcx, expr, proto, decl, body,
-                          unify, expected);
+                                   unify, expected, false);
         capture::check_capture_clause(tcx, expr.id, proto, *captures);
       }
       ast::expr_fn_block(decl, body) {
@@ -2908,19 +2914,25 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, expr: @ast::expr, unify: unifier,
                expr_to_str(expr),
                ty_to_str(tcx, expected));
         check_expr_fn_with_unifier(fcx, expr, proto, decl, body,
-                                   unify, expected);
+                                   unify, expected, false);
       }
-      ast::expr_loop_body(block) {
+      ast::expr_loop_body(b) {
         let rty = structurally_resolved_type(fcx, expr.span, expected);
-        let inner_ty = alt check ty::get(rty).struct {
+        let (inner_ty, proto) = alt check ty::get(rty).struct {
           ty::ty_fn(fty) {
             demand::simple(fcx, expr.span, fty.output, ty::mk_bool(tcx));
-            ty::mk_fn(tcx, {output: ty::mk_nil(tcx) with fty})
+            (ty::mk_fn(tcx, {output: ty::mk_nil(tcx) with fty}),
+             fty.proto)
           }
         };
-        check_expr_with(fcx, block, inner_ty);
+        alt check b.node {
+          ast::expr_fn_block(decl, body) {
+            check_expr_fn_with_unifier(fcx, b, proto, decl, body,
+                                       unify, inner_ty, true);
+          }
+        }
         let block_ty = structurally_resolved_type(
-            fcx, expr.span, ty::node_id_to_type(tcx, block.id));
+            fcx, expr.span, ty::node_id_to_type(tcx, b.id));
         alt check ty::get(block_ty).struct {
           ty::ty_fn(fty) {
             write_ty(tcx, expr.id, ty::mk_fn(tcx, {output: ty::mk_bool(tcx)
@@ -3045,18 +3057,15 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, expr: @ast::expr, unify: unifier,
         write_ty(tcx, id, typ);
       }
       ast::expr_rec(fields, base) {
-        alt base { none {/* no-op */ } some(b_0) { check_expr(fcx, b_0); } }
-        let mut fields_t: [spanned<field>] = [];
-        for f: ast::field in fields {
+        option::may(base) {|b| check_expr(fcx, b); }
+        let fields_t = vec::map(fields, {|f|
             bot |= check_expr(fcx, f.node.expr);
             let expr_t = expr_ty(tcx, f.node.expr);
             let expr_mt = {ty: expr_t, mutbl: f.node.mutbl};
             // for the most precise error message,
             // should be f.node.expr.span, not f.span
-            fields_t +=
-                [respan(f.node.expr.span,
-                        {ident: f.node.ident, mt: expr_mt})];
-        }
+            respan(f.node.expr.span, {ident: f.node.ident, mt: expr_mt})
+        });
         alt base {
           none {
             fn get_node(f: spanned<field>) -> field { f.node }
@@ -3384,6 +3393,7 @@ fn check_const(ccx: @crate_ctxt, _sp: span, e: @ast::expr, id: ast::node_id) {
     let rty = node_id_to_type(ccx.tcx, id);
     let fcx: @fn_ctxt =
         @{ret_ty: rty,
+          indirect_ret_ty: none,
           purity: ast::pure_fn,
           proto: ast::proto_box,
           infcx: infer::new_infer_ctxt(ccx.tcx),
@@ -3403,6 +3413,7 @@ fn check_enum_variants(ccx: @crate_ctxt, sp: span, vs: [ast::variant],
     let rty = node_id_to_type(ccx.tcx, id);
     let fcx: @fn_ctxt =
         @{ret_ty: rty,
+          indirect_ret_ty: none,
           purity: ast::pure_fn,
           proto: ast::proto_box,
           infcx: infer::new_infer_ctxt(ccx.tcx),
@@ -3570,6 +3581,7 @@ fn check_fn(ccx: @crate_ctxt,
             decl: ast::fn_decl,
             body: ast::blk,
             id: ast::node_id,
+            indirect_ret: bool,
             old_fcx: option<@fn_ctxt>) {
     // If old_fcx is some(...), this is a block fn { |x| ... }.
     // In that case, the purity is inherited from the context.
@@ -3579,8 +3591,16 @@ fn check_fn(ccx: @crate_ctxt,
     };
 
     let gather_result = gather_locals(ccx, decl, body, id, old_fcx);
+    let indirect_ret_ty = if indirect_ret {
+        let ofcx = option::get(old_fcx);
+        alt ofcx.indirect_ret_ty {
+          some(t) { some(t) }
+          none { some(ofcx.ret_ty) }
+        }
+    } else { none };
     let fcx: @fn_ctxt =
         @{ret_ty: ty::ty_fn_ret(ty::node_id_to_type(ccx.tcx, id)),
+          indirect_ret_ty: indirect_ret_ty,
           purity: purity,
           proto: proto,
           infcx: gather_result.infcx,
@@ -3619,7 +3639,8 @@ fn check_fn(ccx: @crate_ctxt,
 }
 
 fn check_method(ccx: @crate_ctxt, method: @ast::method) {
-    check_fn(ccx, ast::proto_bare, method.decl, method.body, method.id, none);
+    check_fn(ccx, ast::proto_bare, method.decl, method.body, method.id,
+             false, none);
 }
 
 fn class_types(ccx: @crate_ctxt, members: [@ast::class_item]) -> class_map {
@@ -3651,10 +3672,10 @@ fn check_item(ccx: @crate_ctxt, it: @ast::item) {
       ast::item_const(_, e) { check_const(ccx, it.span, e, it.id); }
       ast::item_enum(vs, _) { check_enum_variants(ccx, it.span, vs, it.id); }
       ast::item_fn(decl, tps, body) {
-        check_fn(ccx, ast::proto_bare, decl, body, it.id, none);
+        check_fn(ccx, ast::proto_bare, decl, body, it.id, false, none);
       }
       ast::item_res(decl, tps, body, dtor_id, _) {
-        check_fn(ccx, ast::proto_bare, decl, body, dtor_id, none);
+        check_fn(ccx, ast::proto_bare, decl, body, dtor_id, false, none);
       }
       ast::item_impl(tps, _, ty, ms) {
         let mut self_ty = ast_ty_to_ty(ccx.tcx, m_check, ty);
@@ -3671,7 +3692,7 @@ fn check_item(ccx: @crate_ctxt, it: @ast::item) {
                             enclosing_class:members_info with *ccx};
           // typecheck the ctor
           check_fn(class_ccx, ast::proto_bare, ctor.node.dec,
-                   ctor.node.body, ctor.node.id, none);
+                   ctor.node.body, ctor.node.id, false, none);
           // typecheck the members
           for m in members { check_class_member(class_ccx, m.node.decl); }
       }
diff --git a/src/rustc/syntax/ast.rs b/src/rustc/syntax/ast.rs
index e657b6a6fe4..95c5b607cd4 100644
--- a/src/rustc/syntax/ast.rs
+++ b/src/rustc/syntax/ast.rs
@@ -303,6 +303,9 @@ enum expr_ {
     expr_alt(@expr, [arm], alt_mode),
     expr_fn(proto, fn_decl, blk, @capture_clause),
     expr_fn_block(fn_decl, blk),
+    // Inner expr is always an expr_fn_block. We need the wrapping node to
+    // sanely type this (a function returning nil on the inside but bool on
+    // the outside).
     expr_loop_body(@expr),
     expr_block(blk),
 
diff --git a/src/test/run-pass/ret-break-cont-in-block.rs b/src/test/run-pass/ret-break-cont-in-block.rs
new file mode 100644
index 00000000000..e93ce8a32e9
--- /dev/null
+++ b/src/test/run-pass/ret-break-cont-in-block.rs
@@ -0,0 +1,57 @@
+fn iter<T>(v: [T], it: fn(T) -> bool) {
+    let mut i = 0u, l = v.len();
+    while i < l {
+        if !it(v[i]) { break; }
+        i += 1u;
+    }
+}
+
+fn find_pos<T>(n: T, h: [T]) -> option<uint> {
+    let mut i = 0u;
+    for iter(h) {|e|
+        if e == n { ret some(i); }
+        i += 1u;
+    }
+    none
+}
+
+fn bail_deep(x: [[bool]]) {
+    let mut seen = false;
+    for iter(x) {|x|
+        for iter(x) {|x|
+            assert !seen;
+            if x { seen = true; ret; }
+        }
+    }
+    assert !seen;
+}
+
+fn ret_deep() -> str {
+    for iter([1, 2]) {|e|
+        for iter([3, 4]) {|x|
+            if e + x > 4 { ret "hi"; }
+        }
+    }
+    ret "bye";
+}
+
+fn main() {
+    let mut last = 0;
+    for vec::all([1, 2, 3, 4, 5, 6, 7]) {|e|
+        last = e;
+        if e == 5 { break; }
+        if e % 2 == 1 { cont; }
+        assert e % 2 == 0;
+    };
+    assert last == 5;
+
+    assert find_pos(1, [0, 1, 2, 3]) == some(1u);
+    assert find_pos(1, [0, 4, 2, 3]) == none;
+    assert find_pos("hi", ["foo", "bar", "baz", "hi"]) == some(3u);
+
+    bail_deep([[false, false], [true, true], [false, true]]);
+    bail_deep([[true]]);
+    bail_deep([[false, false, false]]);
+
+    assert ret_deep() == "hi";
+}