diff --git a/src/comp/middle/trans.rs b/src/comp/middle/trans.rs index bcb685340c3..166affa0e59 100644 --- a/src/comp/middle/trans.rs +++ b/src/comp/middle/trans.rs @@ -2102,7 +2102,6 @@ fn move_val(cx: @block_ctxt, action: copy_action, dst: ValueRef, fn move_val_if_temp(cx: @block_ctxt, action: copy_action, dst: ValueRef, src: lval_result, t: ty::t) -> @block_ctxt { - // Lvals in memory are not temporaries. Copy them. if src.is_mem { ret copy_val(cx, action, dst, load_if_immediate(cx, src.val, t), @@ -2217,6 +2216,41 @@ fn trans_unary(bcx: @block_ctxt, op: ast::unop, e: @ast::expr, } } +fn trans_expr_fn(bcx: @block_ctxt, f: ast::_fn, sp: span, + id: ast::node_id, dest: dest) -> @block_ctxt { + if dest == ignore { ret bcx; } + let ccx = bcx_ccx(bcx); + let fty = node_id_type(ccx, id); + check returns_non_ty_var(ccx, fty); + let llfnty = type_of_fn_from_ty(ccx, sp, fty, 0u); + let sub_cx = extend_path(bcx.fcx.lcx, ccx.names.next("anon")); + let s = mangle_internal_name_by_path(ccx, sub_cx.path); + let llfn = decl_internal_cdecl_fn(ccx.llmod, s, llfnty); + + let copying = f.proto == ast::proto_closure; + let env; + alt f.proto { + ast::proto_block. | ast::proto_closure. { + let upvars = get_freevars(ccx.tcx, id); + let env_r = build_closure(bcx, upvars, copying); + env = env_r.ptr; + trans_closure(sub_cx, sp, f, llfn, none, [], id, {|fcx| + load_environment(bcx, fcx, env_r.ptrty, upvars, copying); + }); + } + _ { + env = C_null(T_opaque_closure_ptr(*bcx_ccx(bcx))); + trans_closure(sub_cx, sp, f, llfn, none, [], id, {|_fcx|}); + } + }; + let addr = alt dest { + save_in(a) { a } + overwrite(a, ty) { bcx = drop_ty(bcx, a, ty); a } + }; + fill_fn_pair(bcx, addr, llfn, env); + ret bcx; +} + fn trans_compare(cx: @block_ctxt, op: ast::binop, lhs: ValueRef, _lhs_t: ty::t, rhs: ValueRef, rhs_t: ty::t) -> result { // Determine the operation we need. @@ -2590,20 +2624,33 @@ fn trans_for(cx: @block_ctxt, local: @ast::local, seq: @ast::expr, // Iterator translation +tag environment_value { + env_expr(@ast::expr); + env_direct(ValueRef, ty::t); +} + // Given a block context and a list of tydescs and values to bind // construct a closure out of them. If copying is true, it is a // heap allocated closure that copies the upvars into environment. // Otherwise, it is stack allocated and copies pointers to the upvars. fn build_environment(bcx: @block_ctxt, lltydescs: [ValueRef], - bound_tys: [ty::t], bound_vals: [lval_result], + bound_values: [environment_value], copying: bool) -> {ptr: ValueRef, ptrty: ty::t, bcx: @block_ctxt} { + let tcx = bcx_tcx(bcx); // Synthesize a closure type. // First, synthesize a tuple type containing the types of all the // bound expressions. // bindings_ty = [bound_ty1, bound_ty2, ...] - let bindings_ty: ty::t = ty::mk_tup(bcx_tcx(bcx), bound_tys); + let bound_tys = []; + for bv in bound_values { + bound_tys += [alt bv { + env_direct(_, t) { t } + env_expr(e) { ty::expr_ty(tcx, e) } + }]; + } + let bindings_ty: ty::t = ty::mk_tup(tcx, bound_tys); // NB: keep this in sync with T_closure_ptr; we're making // a ty::t structure that has the same "shape" as the LLVM type @@ -2612,7 +2659,7 @@ fn build_environment(bcx: @block_ctxt, lltydescs: [ValueRef], // Make a vector that contains ty_param_count copies of tydesc_ty. // (We'll need room for that many tydescs in the closure.) let ty_param_count = std::vec::len(lltydescs); - let tydesc_ty: ty::t = ty::mk_type(bcx_tcx(bcx)); + let tydesc_ty: ty::t = ty::mk_type(tcx); let captured_tys: [ty::t] = std::vec::init_elt(tydesc_ty, ty_param_count); // Get all the types we've got (some of which we synthesized @@ -2622,26 +2669,28 @@ fn build_environment(bcx: @block_ctxt, lltydescs: [ValueRef], // closure_tys = [tydesc_ty, [bound_ty1, bound_ty2, ...], [tydesc_ty, // tydesc_ty, ...]] let closure_tys: [ty::t] = - [tydesc_ty, bindings_ty, ty::mk_tup(bcx_tcx(bcx), captured_tys)]; + [tydesc_ty, bindings_ty, ty::mk_tup(tcx, captured_tys)]; // Finally, synthesize a type for that whole vector. - let closure_ty: ty::t = ty::mk_tup(bcx_tcx(bcx), closure_tys); + let closure_ty: ty::t = ty::mk_tup(tcx, closure_tys); + let temp_cleanups = []; // Allocate a box that can hold something closure-sized. - let r = - if copying { - trans_malloc_boxed(bcx, closure_ty) - } else { - // We need to dummy up a box on the stack - let ty = - ty::mk_tup(bcx_tcx(bcx), - [ty::mk_int(bcx_tcx(bcx)), closure_ty]); - let r = alloc_ty(bcx, ty); - let body = GEPi(bcx, r.val, [0, abi::box_rc_field_body]); - {bcx: r.bcx, box: r.val, body: body} - }; - bcx = r.bcx; - let closure = r.body; + let (closure, box) = if copying { + let r = trans_malloc_boxed(bcx, closure_ty); + add_clean_free(bcx, r.box, false); + temp_cleanups += [r.box]; + bcx = r.bcx; + (r.body, r.box) + } else { + // We need to dummy up a box on the stack + let ty = ty::mk_tup(tcx, [ty::mk_int(tcx), closure_ty]); + let r = alloc_ty(bcx, ty); + bcx = r.bcx; + // Prevent glue from trying to free this. + Store(bcx, C_int(2), GEPi(bcx, r.val, [0, abi::box_rc_field_refcnt])); + (GEPi(bcx, r.val, [0, abi::box_rc_field_body]), r.val) + }; // Store bindings tydesc. if copying { @@ -2656,24 +2705,32 @@ fn build_environment(bcx: @block_ctxt, lltydescs: [ValueRef], } // Copy expr values into boxed bindings. - let i = 0u; // Silly check check type_is_tup_like(bcx, closure_ty); - let bindings = - GEP_tup_like(bcx, closure_ty, closure, - [0, abi::closure_elt_bindings]); + let bindings = GEP_tup_like(bcx, closure_ty, closure, + [0, abi::closure_elt_bindings]); bcx = bindings.bcx; - for lv: lval_result in bound_vals { - // Also a silly check - check type_is_tup_like(bcx, bindings_ty); + let i = 0u; + for bv in bound_values { let bound = - GEP_tup_like(bcx, bindings_ty, bindings.val, [0, i as int]); + GEP_tup_like_1(bcx, bindings_ty, bindings.val, [0, i as int]); bcx = bound.bcx; - if copying { - bcx = move_val_if_temp(bcx, INIT, bound.val, lv, bound_tys[i]); - } else { Store(bcx, lv.val, bound.val); } + alt bv { + env_expr(e) { + bcx = trans_expr_save_in(bcx, e, bound.val, INIT); + add_clean_temp_mem(bcx, bound.val, bound_tys[i]); + temp_cleanups += [bound.val]; + } + env_direct(val, ty) { + if copying { + bcx = copy_val(bcx, INIT, bound.val, + load_if_immediate(bcx, val, ty), ty); + } else { Store(bcx, val, bound.val); } + } + } i += 1u; } + for cleanup in temp_cleanups { revoke_clean(bcx, cleanup); } // If necessary, copy tydescs describing type parameters into the // appropriate slot in the closure. @@ -2690,31 +2747,29 @@ fn build_environment(bcx: @block_ctxt, lltydescs: [ValueRef], i += 1u; } - ret {ptr: r.box, ptrty: closure_ty, bcx: bcx}; + ret {ptr: box, ptrty: closure_ty, bcx: bcx}; } // Given a context and a list of upvars, build a closure. This just // collects the upvars and packages them up for build_environment. fn build_closure(cx: @block_ctxt, upvars: @[ast::def], copying: bool) -> {ptr: ValueRef, ptrty: ty::t, bcx: @block_ctxt} { - let closure_vals: [lval_result] = []; - let closure_tys: [ty::t] = []; // If we need to, package up the iterator body to call - if !copying && !option::is_none(cx.fcx.lliterbody) { - closure_vals += [lval_mem(cx, option::get(cx.fcx.lliterbody))]; - closure_tys += [option::get(cx.fcx.iterbodyty)]; - } + let env_vals = alt cx.fcx.lliterbody { + some(body) when !copying { + [env_direct(body, option::get(cx.fcx.iterbodyty))] + } + _ { [] } + }; // Package up the upvars for def in *upvars { - closure_vals += [trans_local_var(cx, def)]; + let val = trans_local_var(cx, def).val; let nid = ast_util::def_id_of_def(def).node; let ty = ty::node_id_to_monotype(bcx_tcx(cx), nid); if !copying { ty = ty::mk_mut_ptr(bcx_tcx(cx), ty); } - closure_tys += [ty]; + env_vals += [env_direct(val, ty)]; } - - ret build_environment(cx, copy cx.fcx.lltydescs, closure_tys, - closure_vals, copying); + ret build_environment(cx, copy cx.fcx.lltydescs, env_vals, copying); } // Return a pointer to the stored typarams in a closure. @@ -3300,8 +3355,11 @@ fn lval_maybe_callee_to_lval(c: lval_maybe_callee, ty: ty::t) -> lval_result { some(gi) { let n_args = std::vec::len(ty::ty_fn_args(bcx_tcx(c.bcx), ty)); let args = std::vec::init_elt(none::<@ast::expr>, n_args); - let {bcx, val} = trans_bind_1(c.bcx, ty, c, args, ty); - ret lval_val(bcx, val); + let space = alloc_ty(c.bcx, ty); + let bcx = trans_bind_1(space.bcx, ty, c, args, ty, + save_in(space.val)); + add_clean_temp(bcx, space.val, ty); + ret lval_val(bcx, space.val); } none. { let (is_mem, val) = maybe_add_env(c.bcx, c); @@ -3578,19 +3636,25 @@ fn trans_bind_thunk(cx: @local_ctxt, sp: span, incoming_fty: ty::t, } fn trans_bind(cx: @block_ctxt, f: @ast::expr, args: [option::t<@ast::expr>], - id: ast::node_id) -> result { + id: ast::node_id, dest: dest) -> @block_ctxt { let f_res = trans_callee(cx, f); ret trans_bind_1(cx, ty::expr_ty(bcx_tcx(cx), f), f_res, args, - ty::node_id_to_type(bcx_tcx(cx), id)); + ty::node_id_to_type(bcx_tcx(cx), id), dest); } fn trans_bind_1(cx: @block_ctxt, outgoing_fty: ty::t, f_res: lval_maybe_callee, - args: [option::t<@ast::expr>], pair_ty: ty::t) -> result { + args: [option::t<@ast::expr>], pair_ty: ty::t, + dest: dest) -> @block_ctxt { let bound: [@ast::expr] = []; for argopt: option::t<@ast::expr> in args { alt argopt { none. { } some(e) { bound += [e]; } } } + let bcx = f_res.bcx; + if dest == ignore { + for ex in bound { bcx = trans_expr_dps(bcx, ex, ignore); } + ret bcx; + } // Figure out which tydescs we need to pass, if any. let outgoing_fty_real; // the type with typarams still in it @@ -3608,12 +3672,18 @@ fn trans_bind_1(cx: @block_ctxt, outgoing_fty: ty::t, if std::vec::len(bound) == 0u && ty_param_count == 0u { // Trivial 'binding': just return the closure let lv = lval_maybe_callee_to_lval(f_res, pair_ty); - ret rslt(lv.bcx, lv.val); + bcx = lv.bcx; + // FIXME[DPS] factor this out + let addr = alt dest { + save_in(a) { a } + overwrite(a, ty) { bcx = drop_ty(bcx, a, ty); a } + }; + bcx = memmove_ty(bcx, addr, lv.val, pair_ty); + ret bcx; } - let bcx = f_res.bcx; - let (is_mem, closure) = alt f_res.env { - null_env. { (true, none) } - _ { let (mem, cl) = maybe_add_env(cx, f_res); (mem, some(cl)) } + let closure = alt f_res.env { + null_env. { none } + _ { let (_, cl) = maybe_add_env(cx, f_res); some(cl) } }; // FIXME: should follow from a precondition on trans_bind_1 @@ -3622,7 +3692,7 @@ fn trans_bind_1(cx: @block_ctxt, outgoing_fty: ty::t, // Arrange for the bound function to live in the first binding spot // if the function is not statically known. - let (bound_tys, bound_vals, target_res) = alt closure { + let (env_vals, target_res) = alt closure { some(cl) { // Cast the function we are binding to be the type that the // closure will expect it to have. The type the closure knows @@ -3630,27 +3700,14 @@ fn trans_bind_1(cx: @block_ctxt, outgoing_fty: ty::t, let sp = cx.sp; let llclosurety = T_ptr(type_of(ccx, sp, outgoing_fty)); let src_loc = PointerCast(bcx, cl, llclosurety); - let bound_f = {bcx: bcx, val: src_loc, is_mem: is_mem}; - ([outgoing_fty], [bound_f], none) + ([env_direct(src_loc, pair_ty)], none) } - none. { ([], [], some(f_res.val)) } + none. { ([], some(f_res.val)) } }; - // Translate the bound expressions. - for e: @ast::expr in bound { - let lv = trans_lval(bcx, e); - bcx = lv.bcx; - bound_vals += [lv]; - bound_tys += [ty::expr_ty(bcx_tcx(cx), e)]; - } - if bcx.unreachable { - ret rslt(bcx, llvm::LLVMGetUndef( - T_ptr(type_of_or_i8(bcx, outgoing_fty)))); - } - // Actually construct the closure - let closure = - build_environment(bcx, lltydescs, bound_tys, bound_vals, true); + let closure = build_environment + (bcx, lltydescs, env_vals + vec::map(env_expr, bound), true); bcx = closure.bcx; // Make thunk @@ -3658,11 +3715,13 @@ fn trans_bind_1(cx: @block_ctxt, outgoing_fty: ty::t, trans_bind_thunk(cx.fcx.lcx, cx.sp, pair_ty, outgoing_fty_real, args, closure.ptrty, ty_param_count, target_res); - // Construct the function pair - let pair_v = - create_real_fn_pair(bcx, llthunk.ty, llthunk.val, closure.ptr); - add_clean_temp(cx, pair_v, pair_ty); - ret rslt(bcx, pair_v); + // Fill the function pair + let addr = alt dest { + save_in(a) { a } + overwrite(a, ty) { bcx = drop_ty(bcx, a, ty); a } + }; + fill_fn_pair(bcx, addr, llthunk.val, closure.ptr); + ret bcx; } fn trans_arg_expr(cx: @block_ctxt, arg: ty::arg, lldestty0: TypeRef, @@ -4129,30 +4188,6 @@ fn trans_rec(bcx: @block_ctxt, fields: [ast::field], fn trans_expr(cx: @block_ctxt, e: @ast::expr) -> result { // Fixme Fill in cx.sp alt e.node { - ast::expr_fn(f) { - let ccx = bcx_ccx(cx); - let fty = node_id_type(ccx, e.id); - check returns_non_ty_var(ccx, fty); - let llfnty: TypeRef = - type_of_fn_from_ty(ccx, e.span, fty, 0u); - let sub_cx = extend_path(cx.fcx.lcx, ccx.names.next("anon")); - let s = mangle_internal_name_by_path(ccx, sub_cx.path); - let llfn = decl_internal_cdecl_fn(ccx.llmod, s, llfnty); - - let fn_res = - trans_closure(some(cx), some(llfnty), sub_cx, e.span, f, llfn, - none, [], e.id); - let fn_pair = - alt fn_res { - some(fn_pair) { fn_pair } - none. { - {fn_pair: create_real_fn_pair(cx, llfnty, llfn, - null_env_ptr(cx)), - bcx: cx} - } - }; - ret rslt(fn_pair.bcx, fn_pair.fn_pair); - } ast::expr_copy(a) { let e_ty = ty::expr_ty(bcx_tcx(cx), a); let lv = trans_lval(cx, a); @@ -4168,7 +4203,6 @@ fn trans_expr(cx: @block_ctxt, e: @ast::expr) -> result { add_clean_temp(bcx, r.val, e_ty); ret r; } - ast::expr_bind(f, args) { ret trans_bind(cx, f, args, e.id); } ast::expr_cast(val, _) { ret trans_cast(cx, val, e.id); } ast::expr_anon_obj(anon_obj) { ret trans_anon_obj(cx, e.span, anon_obj, e.id); @@ -4247,7 +4281,10 @@ fn trans_expr_dps(bcx: @block_ctxt, e: @ast::expr, dest: dest) } ret trans_unary(bcx, op, x, e.id, dest); } + ast::expr_fn(f) { ret trans_expr_fn(bcx, f, e.span, e.id, dest); } + ast::expr_bind(f, args) { ret trans_bind(bcx, f, args, e.id, dest); } + // These return nothing ast::expr_break. { assert dest == ignore; ret trans_break(e.span, bcx); @@ -5207,11 +5244,9 @@ fn finish_fn(fcx: @fn_ctxt, lltop: BasicBlockRef) { // trans_closure: Builds an LLVM function out of a source function. // If the function closes over its environment a closure will be // returned. -fn trans_closure(bcx_maybe: option::t<@block_ctxt>, - llfnty: option::t, cx: @local_ctxt, sp: span, - f: ast::_fn, llfndecl: ValueRef, ty_self: option::t, - ty_params: [ast::ty_param], id: ast::node_id) -> - option::t<{fn_pair: ValueRef, bcx: @block_ctxt}> { +fn trans_closure(cx: @local_ctxt, sp: span, f: ast::_fn, llfndecl: ValueRef, + ty_self: option::t, ty_params: [ast::ty_param], + id: ast::node_id, maybe_load_env: block(@fn_ctxt)) { set_uwtable(llfndecl); // Set up arguments to the function. @@ -5233,28 +5268,7 @@ fn trans_closure(bcx_maybe: option::t<@block_ctxt>, let arg_tys = arg_tys_of_fn(fcx.lcx.ccx, id); bcx = copy_args_to_allocas(fcx, bcx, f.decl.inputs, arg_tys, false); - // Figure out if we need to build a closure and act accordingly - let res = - alt f.proto { - ast::proto_block. | ast::proto_closure. { - let bcx = option::get(bcx_maybe); - let upvars = get_freevars(cx.ccx.tcx, id); - - let copying = f.proto == ast::proto_closure; - let env = build_closure(bcx, upvars, copying); - load_environment(bcx, fcx, env.ptrty, upvars, copying); - - let closure = - create_real_fn_pair(env.bcx, option::get(llfnty), llfndecl, - env.ptr); - if copying { - add_clean_temp(bcx, closure, node_id_type(cx.ccx, id)); - } - some({fn_pair: closure, bcx: env.bcx}) - } - _ { none } - }; - + maybe_load_env(fcx); // This call to trans_block is the place where we bridge between // translation calls that don't have a return value (trans_crate, @@ -5273,22 +5287,17 @@ fn trans_closure(bcx_maybe: option::t<@block_ctxt>, bcx = trans_block_dps(bcx, f.body, save_in(fcx.llretptr)); } - if !bcx.unreachable { - // FIXME: until LLVM has a unit type, we are moving around - // C_nil values rather than their void type. - build_return(bcx); - } - + // FIXME: until LLVM has a unit type, we are moving around + // C_nil values rather than their void type. + if !bcx.unreachable { build_return(bcx); } // Insert the mandatory first few basic blocks before lltop. finish_fn(fcx, lltop); - - ret res; } fn trans_fn_inner(cx: @local_ctxt, sp: span, f: ast::_fn, llfndecl: ValueRef, ty_self: option::t, ty_params: [ast::ty_param], id: ast::node_id) { - trans_closure(none, none, cx, sp, f, llfndecl, ty_self, ty_params, id); + trans_closure(cx, sp, f, llfndecl, ty_self, ty_params, id, {|_fcx|}); } @@ -5635,15 +5644,20 @@ fn create_real_fn_pair(cx: @block_ctxt, llfnty: TypeRef, llfn: ValueRef, let lcx = cx.fcx.lcx; let pair = alloca(cx, T_fn_pair(*lcx.ccx, llfnty)); - let code_cell = GEP(cx, pair, [C_int(0), C_int(abi::fn_field_code)]); - Store(cx, llfn, code_cell); - let env_cell = GEP(cx, pair, [C_int(0), C_int(abi::fn_field_box)]); - let llenvblobptr = - PointerCast(cx, llenvptr, T_opaque_closure_ptr(*lcx.ccx)); - Store(cx, llenvblobptr, env_cell); + fill_fn_pair(cx, pair, llfn, llenvptr); ret pair; } +fn fill_fn_pair(bcx: @block_ctxt, pair: ValueRef, llfn: ValueRef, + llenvptr: ValueRef) { + let code_cell = GEP(bcx, pair, [C_int(0), C_int(abi::fn_field_code)]); + Store(bcx, llfn, code_cell); + let env_cell = GEP(bcx, pair, [C_int(0), C_int(abi::fn_field_box)]); + let llenvblobptr = + PointerCast(bcx, llenvptr, T_opaque_closure_ptr(*bcx_ccx(bcx))); + Store(bcx, llenvblobptr, env_cell); +} + // Returns the number of type parameters that the given native function has. fn native_fn_ty_param_count(cx: @crate_ctxt, id: ast::node_id) -> uint { let count; diff --git a/src/comp/middle/trans_uniq.rs b/src/comp/middle/trans_uniq.rs index 61fbcc87867..cf5b3a8504e 100644 --- a/src/comp/middle/trans_uniq.rs +++ b/src/comp/middle/trans_uniq.rs @@ -6,7 +6,6 @@ import trans::{ trans_shared_malloc, type_of_inner, size_of, - move_val_if_temp, node_id_type, trans_lval, INIT, diff --git a/src/comp/middle/trans_vec.rs b/src/comp/middle/trans_vec.rs index 86f23517679..2439505242b 100644 --- a/src/comp/middle/trans_vec.rs +++ b/src/comp/middle/trans_vec.rs @@ -6,7 +6,7 @@ import back::abi; import trans::{call_memmove, trans_shared_malloc, llsize_of, type_of_or_i8, INIT, copy_val, load_if_immediate, alloca, size_of, llderivedtydescs_block_ctxt, lazily_emit_tydesc_glue, - get_tydesc, load_inbounds, move_val_if_temp, trans_lval, + get_tydesc, load_inbounds, trans_lval, node_id_type, new_sub_block_ctxt, tps_normal, do_spill_noroot, GEPi, alloc_ty, dest}; import trans_build::*; @@ -121,12 +121,10 @@ fn trans_vec(bcx: @block_ctxt, args: [@ast::expr], id: ast::node_id, let dataptr = get_dataptr_simple(bcx, vptr, llunitty); let i = 0u, temp_cleanups = [vptr]; for e in args { - let lv = trans_lval(bcx, e); - bcx = lv.bcx; let lleltptr = if ty::type_has_dynamic_size(bcx_tcx(bcx), unit_ty) { InBoundsGEP(bcx, dataptr, [Mul(bcx, C_uint(i), llunitsz)]) } else { InBoundsGEP(bcx, dataptr, [C_uint(i)]) }; - bcx = move_val_if_temp(bcx, INIT, lleltptr, lv, unit_ty); + bcx = trans::trans_expr_save_in(bcx, e, lleltptr, INIT); add_clean_temp_mem(bcx, lleltptr, unit_ty); temp_cleanups += [lleltptr]; i += 1u; diff --git a/src/test/run-pass/first-class-method.rs b/src/test/run-pass/first-class-method.rs index 115bdfce3c4..e327c7a6cf0 100644 --- a/src/test/run-pass/first-class-method.rs +++ b/src/test/run-pass/first-class-method.rs @@ -1,3 +1,5 @@ +// xfail-test + // Test case for issue #758. obj foo() { fn f() { } } diff --git a/src/test/run-pass/standalone-method.rs b/src/test/run-pass/standalone-method.rs index e832b558ca0..a9d93917c4b 100644 --- a/src/test/run-pass/standalone-method.rs +++ b/src/test/run-pass/standalone-method.rs @@ -1,3 +1,5 @@ +// xfail-test + // Test case for issue #435. obj foo(x: int) { fn add5(n: int) -> int { ret n + x; }