Support an alternate for syntax that calls a higher-order function

The last argument of the call must be a block, and the type of this
argument must a function returning bool. `break` and `cont` are
supported in the body of the block, and return `false` or `true` from
the function. When the end of the function is reached, `true` is
implicitly returned.

    for vec::all([1, 2, 3]) {|elt|
        if elt == 2 { break; }
        log(error, elt);
    }

Issue #1619
This commit is contained in:
Marijn Haverbeke 2012-03-26 16:09:27 +02:00
parent 9638e7fece
commit f6e3738b9c
15 changed files with 123 additions and 78 deletions

View File

@ -156,7 +156,7 @@ fn compile_upto(sess: session, cfg: ast::crate_cfg,
time(time_passes, "block-use checking",
bind middle::block_use::check_crate(ty_cx, crate));
time(time_passes, "loop checking",
bind middle::check_loop::check_crate(sess, crate));
bind middle::check_loop::check_crate(ty_cx, crate));
time(time_passes, "function usage",
bind fn_usage::check_crate_fn_usage(ty_cx, crate));
time(time_passes, "alt checking",

View File

@ -33,6 +33,10 @@ fn visit_expr(ex: @expr, cx: ctx, v: visit::vt<ctx>) {
i += 1u;
}
}
expr_loop_body(body) {
cx.allow_block = true;
v.visit_expr(body, cx, v);
}
_ {
cx.allow_block = false;
visit::visit_expr(ex, cx, v);

View File

@ -4,7 +4,7 @@ import driver::session::session;
type ctx = {in_loop: bool, can_ret: bool};
fn check_crate(sess: session, crate: @crate) {
fn check_crate(tcx: ty::ctxt, crate: @crate) {
visit::visit_crate(*crate, {in_loop: false,can_ret: true}, visit::mk_vt(@{
visit_item: {|i, _cx, v|
visit::visit_item(i, {in_loop: false, can_ret: true}, v);
@ -21,28 +21,31 @@ fn check_crate(sess: session, crate: @crate) {
expr_fn(_, _, _, _) {
visit::visit_expr(e, {in_loop: false, can_ret: true}, v);
}
expr_fn_block(fn_decl, blk) {
visit::visit_expr(e, {in_loop: false, can_ret: false}, v);
expr_fn_block(_, b) {
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);
}
expr_break {
if !cx.in_loop {
sess.span_err(e.span, "`break` outside of loop");
tcx.sess.span_err(e.span, "`break` outside of loop");
}
}
expr_cont {
if !cx.in_loop {
sess.span_err(e.span, "`cont` outside of loop");
tcx.sess.span_err(e.span, "`cont` outside of loop");
}
}
expr_ret(oe) {
if !cx.can_ret {
sess.span_err(e.span, "`ret` in block function");
tcx.sess.span_err(e.span, "`ret` in block function");
}
visit::visit_expr_opt(oe, cx, v);
}
expr_be(re) {
if !cx.can_ret {
sess.span_err(e.span, "`be` in block function");
tcx.sess.span_err(e.span, "`be` in block function");
}
v.visit_expr(re, cx, v);
}

View File

@ -2990,20 +2990,24 @@ 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, dest);
ret closure::trans_expr_fn(bcx, proto, decl, body, e.span, e.id,
*cap_clause, false, dest);
}
ast::expr_fn_block(decl, body) {
alt ty::get(expr_ty(bcx, e)).struct {
alt check ty::get(expr_ty(bcx, e)).struct {
ty::ty_fn({proto, _}) {
#debug("translating fn_block %s with type %s",
expr_to_str(e), ty_to_str(tcx, expr_ty(bcx, e)));
let cap_clause = { copies: [], moves: [] };
ret closure::trans_expr_fn(
bcx, proto, decl, body, e.span, e.id, cap_clause, dest);
ret closure::trans_expr_fn(bcx, proto, decl, body, e.span, e.id,
{copies: [], moves: []}, false, dest);
}
_ {
fail "type of fn block is not a function!";
}
}
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);
}
}
}
@ -3375,7 +3379,16 @@ fn trans_break_cont(bcx: block, to_end: bool)
}
_ {}
}
unwind = alt check unwind.parent { parent_some(cx) { cx } };
unwind = alt unwind.parent {
parent_some(cx) { cx }
// This is a return from a loop body block
parent_none {
Store(bcx, C_bool(!to_end), bcx.fcx.llretptr);
cleanup_and_leave(bcx, none, some(bcx.fcx.llreturn));
Unreachable(bcx);
ret bcx;
}
};
}
cleanup_and_Br(bcx, unwind, target.llbb);
Unreachable(bcx);
@ -3895,7 +3908,8 @@ fn trans_closure(ccx: @crate_ctxt, path: path, decl: ast::fn_decl,
ty_self: self_arg,
param_substs: option<param_substs>,
id: ast::node_id,
maybe_load_env: fn(fn_ctxt)) {
maybe_load_env: fn(fn_ctxt),
finish: fn(block)) {
let _icx = ccx.insn_ctxt("trans_closure");
set_uwtable(llfndecl);
@ -3932,6 +3946,7 @@ fn trans_closure(ccx: @crate_ctxt, path: path, decl: ast::fn_decl,
} else {
bcx = trans_block(bcx, body, save_in(fcx.llretptr));
}
finish(bcx);
cleanup_and_Br(bcx, bcx_top, fcx.llreturn);
// Insert the mandatory first few basic blocks before lltop.
@ -3957,7 +3972,7 @@ fn trans_fn(ccx: @crate_ctxt,
if ccx.sess.opts.extra_debuginfo {
debuginfo::create_function(fcx);
}
});
}, {|_bcx|});
if do_time {
let end = time::get_time();
log_fn_time(ccx, path_str(path), start, end);

View File

@ -367,6 +367,7 @@ fn trans_expr_fn(bcx: block,
sp: span,
id: ast::node_id,
cap_clause: ast::capture_clause,
is_loop_body: bool,
dest: dest) -> block {
let _icx = bcx.insn_ctxt("closure::trans_expr_fn");
if dest == ignore { ret bcx; }
@ -385,6 +386,8 @@ fn trans_expr_fn(bcx: block,
trans_closure(ccx, sub_path, decl, body, llfn, no_self,
bcx.fcx.param_substs, id, {|fcx|
load_environment(fcx, cdata_ty, cap_vars, ck);
}, {|bcx|
if is_loop_body { Store(bcx, C_bool(true), bcx.fcx.llretptr); }
});
llbox
};
@ -395,7 +398,7 @@ fn trans_expr_fn(bcx: block,
ast::proto_uniq { trans_closure_env(ty::ck_uniq) }
ast::proto_bare {
trans_closure(ccx, sub_path, decl, body, llfn, no_self, none,
id, {|_fcx|});
id, {|_fcx|}, {|_bcx|});
C_null(T_opaque_box_ptr(ccx))
}
};

View File

@ -718,9 +718,7 @@ fn C_nil() -> ValueRef {
}
fn C_bool(b: bool) -> ValueRef {
if b {
ret C_integral(T_bool(), 1u64, False);
} else { ret C_integral(T_bool(), 0u64, False); }
C_integral(T_bool(), if b { 1u64 } else { 0u64 }, False)
}
fn C_i32(i: i32) -> ValueRef {

View File

@ -190,7 +190,7 @@ fn mark_for_expr(cx: ctx, e: @expr) {
expr_fail(_) | expr_break | expr_cont | expr_unary(_, _) |
expr_lit(_) | expr_assert(_) | expr_check(_, _) |
expr_if_check(_, _, _) | expr_mac(_) | expr_addr_of(_, _) |
expr_ret(_) | expr_loop(_) | expr_bind(_, _) {}
expr_ret(_) | expr_loop(_) | expr_bind(_, _) | expr_loop_body(_) {}
}
}

View File

@ -360,10 +360,6 @@ fn find_pre_post_expr(fcx: fn_ctxt, e: @expr) {
find_pre_post_exprs(fcx, es, e.id);
}
expr_tup(elts) { find_pre_post_exprs(fcx, elts, e.id); }
expr_copy(a) {
find_pre_post_expr(fcx, a);
copy_pre_post(fcx.ccx, e.id, a);
}
expr_move(lhs, rhs) { handle_update(fcx, e, lhs, rhs, oper_move); }
expr_swap(lhs, rhs) { handle_update(fcx, e, lhs, rhs, oper_swap); }
expr_assign(lhs, rhs) { handle_update(fcx, e, lhs, rhs, oper_assign); }
@ -408,17 +404,10 @@ fn find_pre_post_expr(fcx: fn_ctxt, e: @expr) {
expr_postcond(fcx.ccx, l));
} else { find_pre_post_exprs(fcx, [l, r], e.id); }
}
expr_unary(_, operand) {
find_pre_post_expr(fcx, operand);
copy_pre_post(fcx.ccx, e.id, operand);
}
expr_addr_of(_, operand) {
find_pre_post_expr(fcx, operand);
copy_pre_post(fcx.ccx, e.id, operand);
}
expr_cast(operand, _) {
find_pre_post_expr(fcx, operand);
copy_pre_post(fcx.ccx, e.id, operand);
expr_addr_of(_, x) | expr_cast(x, _) | expr_unary(_, x) |
expr_loop_body(x) | expr_assert(x) | expr_copy(x) {
find_pre_post_expr(fcx, x);
copy_pre_post(fcx.ccx, e.id, x);
}
expr_while(test, body) {
find_pre_post_expr(fcx, test);
@ -512,10 +501,6 @@ fn find_pre_post_expr(fcx: fn_ctxt, e: @expr) {
then everything is true! */
prestate, false_postcond(num_local_vars));
}
expr_assert(p) {
find_pre_post_expr(fcx, p);
copy_pre_post(fcx.ccx, e.id, p);
}
expr_check(_, p) {
find_pre_post_expr(fcx, p);
copy_pre_post(fcx.ccx, e.id, p);
@ -527,12 +512,6 @@ fn find_pre_post_expr(fcx: fn_ctxt, e: @expr) {
expr_if_check(p, conseq, maybe_alt) {
join_then_else(fcx, p, conseq, maybe_alt, e.id, if_check);
}
expr_bind(operator, maybe_args) {
let mut args = [];
let mut cmodes = callee_modes(fcx, operator.id);

View File

@ -422,7 +422,6 @@ fn find_pre_post_state_expr(fcx: fn_ctxt, pres: prestate, e: @expr) -> bool {
init_assign), elts,
return_val);
}
expr_copy(a) { ret find_pre_post_state_sub(fcx, pres, a, e.id, none); }
expr_move(lhs, rhs) {
ret find_pre_post_state_two(fcx, pres, lhs, rhs, e.id, oper_move);
}
@ -598,17 +597,10 @@ fn find_pre_post_state_expr(fcx: fn_ctxt, pres: prestate, e: @expr) -> bool {
}
ret changed | set_poststate_ann(fcx.ccx, e.id, a_post);
}
expr_field(val, _, _) {
ret find_pre_post_state_sub(fcx, pres, val, e.id, none);
}
expr_unary(_, operand) {
ret find_pre_post_state_sub(fcx, pres, operand, e.id, none);
}
expr_addr_of(_, operand) {
ret find_pre_post_state_sub(fcx, pres, operand, e.id, none);
}
expr_cast(operand, _) {
ret find_pre_post_state_sub(fcx, pres, operand, e.id, none);
expr_field(x, _, _) | expr_loop_body(x) | expr_unary(_, x) |
expr_addr_of(_, x) | expr_assert(x) | expr_cast(x, _) |
expr_copy(x) {
ret find_pre_post_state_sub(fcx, pres, x, e.id, none);
}
expr_fail(maybe_fail_val) {
// FIXME Should factor out this code,
@ -622,9 +614,6 @@ fn find_pre_post_state_expr(fcx: fn_ctxt, pres: prestate, e: @expr) -> bool {
option::maybe(maybe_fail_val, false, {|fail_val|
find_pre_post_state_expr(fcx, pres, fail_val)});
}
expr_assert(p) {
ret find_pre_post_state_sub(fcx, pres, p, e.id, none);
}
expr_check(_, p) {
/* predicate p holds after this expression executes */
let c: sp_constr = expr_to_constr(fcx.ccx.tcx, p);

View File

@ -2910,6 +2910,24 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, expr: @ast::expr, unify: unifier,
check_expr_fn_with_unifier(fcx, expr, proto, decl, body,
unify, expected);
}
ast::expr_loop_body(block) {
let rty = structurally_resolved_type(fcx, expr.span, expected);
let inner_ty = 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})
}
};
check_expr_with(fcx, block, inner_ty);
let block_ty = structurally_resolved_type(
fcx, expr.span, ty::node_id_to_type(tcx, block.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)
with fty}));
}
}
}
ast::expr_block(b) {
// If this is an unchecked block, turn off purity-checking
bot = check_block(fcx, b);

View File

@ -303,6 +303,7 @@ enum expr_ {
expr_alt(@expr, [arm], alt_mode),
expr_fn(proto, fn_decl, blk, @capture_clause),
expr_fn_block(fn_decl, blk),
expr_loop_body(@expr),
expr_block(blk),
/*

View File

@ -407,6 +407,7 @@ fn noop_fold_expr(e: expr_, fld: ast_fold) -> expr_ {
expr_binary(binop, fld.fold_expr(lhs), fld.fold_expr(rhs))
}
expr_unary(binop, ohs) { expr_unary(binop, fld.fold_expr(ohs)) }
expr_loop_body(f) { expr_loop_body(fld.fold_expr(f)) }
expr_lit(_) { e }
expr_cast(expr, ty) { expr_cast(fld.fold_expr(expr), ty) }
expr_addr_of(m, ohs) { expr_addr_of(m, fld.fold_expr(ohs)) }

View File

@ -1403,13 +1403,37 @@ fn parse_else_expr(p: parser) -> @ast::expr {
}
fn parse_for_expr(p: parser) -> @ast::expr {
let lo = p.last_span.lo;
let decl = parse_local(p, false, false);
expect_word(p, "in");
let seq = parse_expr(p);
let body = parse_block_no_value(p);
let mut hi = body.span.hi;
ret mk_expr(p, lo, hi, ast::expr_for(decl, seq, body));
let lo = p.last_span;
// FIXME remove this kludge after migration and snapshotting (#1619)
let new_style = alt p.token {
token::IDENT(_, false) { alt p.look_ahead(1u) {
token::DOT | token::LPAREN { true }
_ { false }
} }
token::IDENT(_, true) { true }
_ { false }
};
if new_style {
let call = parse_expr(p);
alt call.node {
ast::expr_call(f, args, true) {
let b_arg = vec::last(args);
let last = mk_expr(p, b_arg.span.lo, b_arg.span.hi,
ast::expr_loop_body(b_arg));
@{node: ast::expr_call(f, vec::init(args) + [last], true)
with *call}
}
_ {
p.span_fatal(lo, "`for` must be followed by a block call");
}
}
} else {
let decl = parse_local(p, false, false);
expect_word(p, "in");
let seq = parse_expr(p);
let body = parse_block_no_value(p);
mk_expr(p, lo.lo, body.span.hi, ast::expr_for(decl, seq, body))
}
}
fn parse_while_expr(p: parser) -> @ast::expr {

View File

@ -855,9 +855,16 @@ fn print_expr(s: ps, &&expr: @ast::expr) {
pclose(s);
}
ast::expr_call(func, args, has_block) {
let mut base_args = args;
let blk = if has_block {
let blk_arg = vec::pop(base_args);
alt blk_arg.node {
ast::expr_loop_body(_) { word_nbsp(s, "for"); }
_ {}
}
some(blk_arg)
} else { none };
print_expr_parens_if_not_bot(s, func);
let mut base_args = args, blk = none;
if has_block { blk = some(vec::pop(base_args)); }
if !has_block || vec::len(base_args) > 0u {
popen(s);
commasep_exprs(s, inconsistent, base_args);
@ -989,6 +996,9 @@ fn print_expr(s: ps, &&expr: @ast::expr) {
print_fn_block_args(s, decl);
print_possibly_embedded_block(s, body, block_block_fn, indent_unit);
}
ast::expr_loop_body(body) {
print_expr(s, body);
}
ast::expr_block(blk) {
// containing cbox, will be closed by print-block at }
cbox(s, indent_unit);

View File

@ -338,10 +338,12 @@ fn visit_expr<E>(ex: @expr, e: E, v: vt<E>) {
for eo: option<@expr> in args { visit_expr_opt(eo, e, v); }
}
expr_binary(_, a, b) { v.visit_expr(a, e, v); v.visit_expr(b, e, v); }
expr_unary(_, a) { v.visit_expr(a, e, v); }
expr_addr_of(_, x) | expr_unary(_, x) | expr_loop_body(x) |
expr_check(_, x) | expr_assert(x) {
v.visit_expr(x, e, v);
}
expr_lit(_) { }
expr_cast(x, t) { v.visit_expr(x, e, v); v.visit_ty(t, e, v); }
expr_addr_of(_, x) { v.visit_expr(x, e, v); }
expr_if(x, b, eo) {
v.visit_expr(x, e, v);
v.visit_block(b, e, v);
@ -353,7 +355,7 @@ fn visit_expr<E>(ex: @expr, e: E, v: vt<E>) {
visit_expr_opt(eo, e, v);
}
expr_while(x, b) { v.visit_expr(x, e, v); v.visit_block(b, e, v); }
expr_loop(b) { v.visit_block(b, e, v); }
expr_loop(b) { v.visit_block(b, e, v); }
expr_for(dcl, x, b) {
v.visit_local(dcl, e, v);
v.visit_expr(x, e, v);
@ -394,8 +396,6 @@ fn visit_expr<E>(ex: @expr, e: E, v: vt<E>) {
v.visit_expr(lv, e, v);
v.visit_expr(x, e, v);
}
expr_check(_, x) { v.visit_expr(x, e, v); }
expr_assert(x) { v.visit_expr(x, e, v); }
expr_mac(mac) { visit_mac(mac, e, v); }
}
}