Allow static method calls to be bound

This allows you to take the value of, for example, `[1].len`, or bind
it with `bind x.map(_)` syntax.

I'm holding off on implementing this for dynamic methods (those on
bounded type parameters or iface types) until it's clearer what we
will do with monomorphization.

Issue #435
This commit is contained in:
Marijn Haverbeke 2012-02-14 10:47:39 +01:00
parent f2e880b750
commit 57c7645cb8
5 changed files with 107 additions and 69 deletions

View file

@ -2188,7 +2188,7 @@ type lval_result = {bcx: @block_ctxt, val: ValueRef, kind: lval_kind};
enum callee_env { enum callee_env {
null_env, null_env,
is_closure, is_closure,
self_env(ValueRef), self_env(ValueRef, ty::t),
dict_env(ValueRef, ValueRef), dict_env(ValueRef, ValueRef),
} }
type lval_maybe_callee = {bcx: @block_ctxt, type lval_maybe_callee = {bcx: @block_ctxt,
@ -2597,36 +2597,28 @@ fn trans_lval(cx: @block_ctxt, e: @ast::expr) -> lval_result {
} }
} }
fn maybe_add_env(bcx: @block_ctxt, c: lval_maybe_callee)
-> (lval_kind, ValueRef) {
alt c.env {
is_closure { (c.kind, c.val) }
self_env(_) | dict_env(_, _) {
fail "Taking the value of a method does not work yet (issue #435)";
}
null_env {
let llfnty = llvm::LLVMGetElementType(val_ty(c.val));
(temporary, create_real_fn_pair(bcx, llfnty, c.val,
null_env_ptr(bcx)))
}
}
}
fn lval_maybe_callee_to_lval(c: lval_maybe_callee, ty: ty::t) -> lval_result { fn lval_maybe_callee_to_lval(c: lval_maybe_callee, ty: ty::t) -> lval_result {
alt c.generic { let must_bind = alt c.generic { generic_full(_) { true } _ { false } } ||
generic_full(gi) { alt c.env { self_env(_, _) | dict_env(_, _) { true } _ { false } };
if must_bind {
let n_args = ty::ty_fn_args(ty).len(); let n_args = ty::ty_fn_args(ty).len();
let args = vec::init_elt(n_args, none::<@ast::expr>); let args = vec::init_elt(n_args, none);
let space = alloc_ty(c.bcx, ty); let space = alloc_ty(c.bcx, ty);
let bcx = closure::trans_bind_1(space.bcx, ty, c, args, ty, let bcx = closure::trans_bind_1(space.bcx, ty, c, args, ty,
save_in(space.val)); save_in(space.val));
add_clean_temp(bcx, space.val, ty); add_clean_temp(bcx, space.val, ty);
ret {bcx: bcx, val: space.val, kind: temporary}; {bcx: bcx, val: space.val, kind: temporary}
} } else {
_ { alt c.env {
let (kind, val) = maybe_add_env(c.bcx, c); is_closure { {bcx: c.bcx, val: c.val, kind: c.kind} }
ret {bcx: c.bcx, val: val, kind: kind}; null_env {
} let llfnty = llvm::LLVMGetElementType(val_ty(c.val));
let llfn = create_real_fn_pair(c.bcx, llfnty, c.val,
null_env_ptr(c.bcx));
{bcx: c.bcx, val: llfn, kind: temporary}
}
_ { fail; }
}
} }
} }
@ -2762,7 +2754,7 @@ fn trans_arg_expr(cx: @block_ctxt, arg: ty::arg, lldestty: TypeRef,
if arg_mode == ast::by_val && (lv.kind == owned || !imm) { if arg_mode == ast::by_val && (lv.kind == owned || !imm) {
val = Load(bcx, val); val = Load(bcx, val);
} }
} else if arg_mode == ast::by_copy { } else if arg_mode == ast::by_copy {
let {bcx: cx, val: alloc} = alloc_ty(bcx, e_ty); let {bcx: cx, val: alloc} = alloc_ty(bcx, e_ty);
let last_use = ccx.last_uses.contains_key(e.id); let last_use = ccx.last_uses.contains_key(e.id);
bcx = cx; bcx = cx;
@ -2926,16 +2918,21 @@ fn trans_call_inner(in_cx: @block_ctxt, fn_expr_ty: ty::t,
let cx = new_scope_block_ctxt(in_cx, "call"); let cx = new_scope_block_ctxt(in_cx, "call");
Br(in_cx, cx.llbb); Br(in_cx, cx.llbb);
let f_res = get_callee(cx); let f_res = get_callee(cx);
let bcx = f_res.bcx; let bcx = f_res.bcx, ccx = bcx_ccx(cx);
let faddr = f_res.val; let faddr = f_res.val;
let llenv, dict_param = none; let llenv, dict_param = none;
alt f_res.env { alt f_res.env {
null_env { null_env {
llenv = llvm::LLVMGetUndef(T_opaque_box_ptr(bcx_ccx(cx))); llenv = llvm::LLVMGetUndef(T_opaque_box_ptr(ccx));
}
self_env(e, _) {
llenv = PointerCast(bcx, e, T_opaque_box_ptr(ccx));
}
dict_env(dict, e) {
llenv = PointerCast(bcx, e, T_opaque_box_ptr(ccx));
dict_param = some(dict);
} }
self_env(e) { llenv = e; }
dict_env(dict, e) { llenv = e; dict_param = some(dict); }
is_closure { is_closure {
// It's a closure. Have to fetch the elements // It's a closure. Have to fetch the elements
if f_res.kind == owned { if f_res.kind == owned {
@ -3306,7 +3303,9 @@ fn trans_expr(bcx: @block_ctxt, e: @ast::expr, dest: dest) -> @block_ctxt {
ret trans_call(bcx, f, args, e.id, dest); ret trans_call(bcx, f, args, e.id, dest);
} }
ast::expr_field(_, _, _) { ast::expr_field(_, _, _) {
fail "Taking the value of a method does not work yet (issue #435)"; let callee = trans_callee(bcx, e), ty = expr_ty(bcx, e);
let lv = lval_maybe_callee_to_lval(callee, ty);
ret memmove_ty(lv.bcx, get_dest_addr(dest), lv.val, ty);
} }
ast::expr_index(base, idx) { ast::expr_index(base, idx) {
// If it is here, it's not an lval, so this is a user-defined index op // If it is here, it's not an lval, so this is a user-defined index op

View file

@ -494,6 +494,7 @@ fn trans_bind_1(cx: @block_ctxt, outgoing_fty: ty::t,
f_res: lval_maybe_callee, f_res: lval_maybe_callee,
args: [option<@ast::expr>], pair_ty: ty::t, args: [option<@ast::expr>], pair_ty: ty::t,
dest: dest) -> @block_ctxt { dest: dest) -> @block_ctxt {
let ccx = bcx_ccx(cx);
let bound: [@ast::expr] = []; let bound: [@ast::expr] = [];
for argopt: option<@ast::expr> in args { for argopt: option<@ast::expr> in args {
alt argopt { none { } some(e) { bound += [e]; } } alt argopt { none { } some(e) { bound += [e]; } }
@ -523,39 +524,37 @@ fn trans_bind_1(cx: @block_ctxt, outgoing_fty: ty::t,
} }
} }
} }
lazily_emit_all_generic_info_tydesc_glues(bcx_ccx(cx), ginfo); lazily_emit_all_generic_info_tydesc_glues(ccx, ginfo);
(ginfo.item_type, tds, ginfo.param_bounds) (ginfo.item_type, tds, ginfo.param_bounds)
} }
_ { (outgoing_fty, [], @[]) } _ { (outgoing_fty, [], @[]) }
}; };
if bound.len() == 0u && lltydescs.len() == 0u { if bound.len() == 0u && lltydescs.len() == 0u &&
(f_res.env == null_env || f_res.env == is_closure) {
// Trivial 'binding': just return the closure // Trivial 'binding': just return the closure
let lv = lval_maybe_callee_to_lval(f_res, pair_ty); let lv = lval_maybe_callee_to_lval(f_res, pair_ty);
bcx = lv.bcx; ret memmove_ty(lv.bcx, get_dest_addr(dest), lv.val, pair_ty);
ret memmove_ty(bcx, get_dest_addr(dest), lv.val, pair_ty);
} }
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
let ccx = bcx_ccx(cx);
check (type_has_static_size(ccx, outgoing_fty));
// Arrange for the bound function to live in the first binding spot // Arrange for the bound function to live in the first binding spot
// if the function is not statically known. // if the function is not statically known.
let (env_vals, target_res) = alt closure { let (env_vals, target_info) = alt f_res.env {
some(cl) { null_env { ([], target_static(f_res.val)) }
is_closure {
// Cast the function we are binding to be the type that the // Cast the function we are binding to be the type that the
// closure will expect it to have. The type the closure knows // closure will expect it to have. The type the closure knows
// about has the type parameters substituted with the real types. // about has the type parameters substituted with the real types.
let llclosurety = T_ptr(type_of(ccx, outgoing_fty)); let llclosurety = T_ptr(type_of(ccx, outgoing_fty));
let src_loc = PointerCast(bcx, cl, llclosurety); let src_loc = PointerCast(bcx, f_res.val, llclosurety);
([env_copy(src_loc, pair_ty, owned)], none) ([env_copy(src_loc, pair_ty, owned)], target_closure)
}
self_env(slf, slf_t) {
([env_copy(slf, slf_t, owned)], target_self(f_res.val))
}
dict_env(_, _) {
ccx.sess.unimpl("binding of dynamic method calls");
} }
none { ([], some(f_res.val)) }
}; };
// Actually construct the closure // Actually construct the closure
@ -567,7 +566,7 @@ fn trans_bind_1(cx: @block_ctxt, outgoing_fty: ty::t,
// Make thunk // Make thunk
let llthunk = trans_bind_thunk( let llthunk = trans_bind_thunk(
cx.fcx.ccx, cx.fcx.path, pair_ty, outgoing_fty_real, args, cx.fcx.ccx, cx.fcx.path, pair_ty, outgoing_fty_real, args,
cdata_ty, *param_bounds, target_res); cdata_ty, *param_bounds, target_info);
// Fill the function pair // Fill the function pair
fill_fn_pair(bcx, get_dest_addr(dest), llthunk.val, llbox); fill_fn_pair(bcx, get_dest_addr(dest), llthunk.val, llbox);
@ -722,6 +721,12 @@ fn make_opaque_cbox_free_glue(
} }
} }
enum target_info {
target_closure,
target_static(ValueRef),
target_self(ValueRef),
}
// pth is cx.path // pth is cx.path
fn trans_bind_thunk(ccx: @crate_ctxt, fn trans_bind_thunk(ccx: @crate_ctxt,
path: path, path: path,
@ -730,7 +735,7 @@ fn trans_bind_thunk(ccx: @crate_ctxt,
args: [option<@ast::expr>], args: [option<@ast::expr>],
cdata_ty: ty::t, cdata_ty: ty::t,
param_bounds: [ty::param_bounds], param_bounds: [ty::param_bounds],
target_fn: option<ValueRef>) target_info: target_info)
-> {val: ValueRef, ty: TypeRef} { -> {val: ValueRef, ty: TypeRef} {
// If we supported constraints on record fields, we could make the // If we supported constraints on record fields, we could make the
@ -800,11 +805,11 @@ fn trans_bind_thunk(ccx: @crate_ctxt,
// creating. (In our running example, target is the function f.) Pick // creating. (In our running example, target is the function f.) Pick
// out the pointer to the target function from the environment. The // out the pointer to the target function from the environment. The
// target function lives in the first binding spot. // target function lives in the first binding spot.
let (lltargetfn, lltargetenv, starting_idx) = alt target_fn { let (lltargetfn, lltargetenv, starting_idx) = alt target_info {
some(fptr) { target_static(fptr) {
(fptr, llvm::LLVMGetUndef(T_opaque_cbox_ptr(ccx)), 0) (fptr, llvm::LLVMGetUndef(T_opaque_cbox_ptr(ccx)), 0)
} }
none { target_closure {
let {bcx: cx, val: pair} = let {bcx: cx, val: pair} =
GEP_tup_like(bcx, cdata_ty, llcdata, GEP_tup_like(bcx, cdata_ty, llcdata,
[0, abi::closure_body_bindings, 0]); [0, abi::closure_body_bindings, 0]);
@ -815,6 +820,12 @@ fn trans_bind_thunk(ccx: @crate_ctxt,
bcx = cx; bcx = cx;
(lltargetfn, lltargetenv, 1) (lltargetfn, lltargetenv, 1)
} }
target_self(fptr) {
let rs = GEP_tup_like(bcx, cdata_ty, llcdata,
[0, abi::closure_body_bindings, 0]);
bcx = rs.bcx;
(fptr, PointerCast(bcx, rs.val, T_opaque_cbox_ptr(ccx)), 1)
}
}; };
// And then, pick out the target function's own environment. That's what // And then, pick out the target function's own environment. That's what

View file

@ -63,11 +63,9 @@ fn trans_self_arg(bcx: @block_ctxt, base: @ast::expr) -> result {
let tz = [], tr = []; let tz = [], tr = [];
let basety = expr_ty(bcx, base); let basety = expr_ty(bcx, base);
let m_by_ref = ast::expl(ast::by_ref); let m_by_ref = ast::expl(ast::by_ref);
let {bcx, val} = trans_arg_expr(bcx, {mode: m_by_ref, ty: basety},
trans_arg_expr(bcx, {mode: m_by_ref, ty: basety}, T_ptr(type_of_or_i8(bcx_ccx(bcx), basety)), tz,
T_ptr(type_of_or_i8(bcx_ccx(bcx), basety)), tz, tr, base)
tr, base);
rslt(bcx, PointerCast(bcx, val, T_opaque_cbox_ptr(bcx_ccx(bcx))))
} }
fn trans_method_callee(bcx: @block_ctxt, callee_id: ast::node_id, fn trans_method_callee(bcx: @block_ctxt, callee_id: ast::node_id,
@ -100,7 +98,8 @@ fn trans_static_callee(bcx: @block_ctxt, callee_id: ast::node_id,
substs: option<([ty::t], typeck::dict_res)>) substs: option<([ty::t], typeck::dict_res)>)
-> lval_maybe_callee { -> lval_maybe_callee {
let {bcx, val} = trans_self_arg(bcx, base); let {bcx, val} = trans_self_arg(bcx, base);
{env: self_env(val) with lval_static_fn(bcx, did, callee_id, substs)} {env: self_env(val, node_id_type(bcx, base.id))
with lval_static_fn(bcx, did, callee_id, substs)}
} }
fn wrapper_fn_ty(ccx: @crate_ctxt, dict_ty: TypeRef, fty: ty::t, fn wrapper_fn_ty(ccx: @crate_ctxt, dict_ty: TypeRef, fty: ty::t,
@ -110,7 +109,7 @@ fn wrapper_fn_ty(ccx: @crate_ctxt, dict_ty: TypeRef, fty: ty::t,
{ty: fty, llty: T_fn([dict_ty] + inputs, output)} {ty: fty, llty: T_fn([dict_ty] + inputs, output)}
} }
fn trans_vtable_callee(bcx: @block_ctxt, self: ValueRef, dict: ValueRef, fn trans_vtable_callee(bcx: @block_ctxt, env: callee_env, dict: ValueRef,
callee_id: ast::node_id, iface_id: ast::def_id, callee_id: ast::node_id, iface_id: ast::def_id,
n_method: uint) -> lval_maybe_callee { n_method: uint) -> lval_maybe_callee {
let bcx = bcx, ccx = bcx_ccx(bcx), tcx = ccx.tcx; let bcx = bcx, ccx = bcx_ccx(bcx), tcx = ccx.tcx;
@ -139,7 +138,7 @@ fn trans_vtable_callee(bcx: @block_ctxt, self: ValueRef, dict: ValueRef,
origins: ccx.dict_map.find(callee_id)}); origins: ccx.dict_map.find(callee_id)});
} }
{bcx: bcx, val: mptr, kind: owned, {bcx: bcx, val: mptr, kind: owned,
env: dict_env(dict, self), env: env,
generic: generic} generic: generic}
} }
@ -181,7 +180,8 @@ fn trans_param_callee(bcx: @block_ctxt, callee_id: ast::node_id,
n_param: uint, n_bound: uint) -> lval_maybe_callee { n_param: uint, n_bound: uint) -> lval_maybe_callee {
let {bcx, val} = trans_self_arg(bcx, base); let {bcx, val} = trans_self_arg(bcx, base);
let dict = option::get(bcx.fcx.lltyparams[n_param].dicts)[n_bound]; let dict = option::get(bcx.fcx.lltyparams[n_param].dicts)[n_bound];
trans_vtable_callee(bcx, val, dict, callee_id, iface_id, n_method) trans_vtable_callee(bcx, dict_env(dict, val), dict,
callee_id, iface_id, n_method)
} }
// Method callee where the dict comes from a boxed iface // Method callee where the dict comes from a boxed iface
@ -193,9 +193,9 @@ fn trans_iface_callee(bcx: @block_ctxt, callee_id: ast::node_id,
T_ptr(T_ptr(T_dict())))); T_ptr(T_ptr(T_dict()))));
let box = Load(bcx, GEPi(bcx, val, [0, 1])); let box = Load(bcx, GEPi(bcx, val, [0, 1]));
// FIXME[impl] I doubt this is alignment-safe // FIXME[impl] I doubt this is alignment-safe
let self = PointerCast(bcx, GEPi(bcx, box, [0, abi::box_field_body]), let self = GEPi(bcx, box, [0, abi::box_field_body]);
T_opaque_cbox_ptr(bcx_ccx(bcx))); trans_vtable_callee(bcx, dict_env(dict, self), dict,
trans_vtable_callee(bcx, self, dict, callee_id, iface_id, n_method) callee_id, iface_id, n_method)
} }
fn llfn_arg_tys(ft: TypeRef) -> {inputs: [TypeRef], output: TypeRef} { fn llfn_arg_tys(ft: TypeRef) -> {inputs: [TypeRef], output: TypeRef} {

View file

@ -1674,7 +1674,8 @@ fn lookup_method_inner(fcx: @fn_ctxt, expr: @ast::expr,
alt vec::position(*ifce_methods, {|m| m.ident == name}) { alt vec::position(*ifce_methods, {|m| m.ident == name}) {
some(pos) { some(pos) {
let m = ifce_methods[pos]; let m = ifce_methods[pos];
ret some({method_ty: ty::mk_fn(tcx, m.fty), ret some({method_ty: ty::mk_fn(tcx, {proto: ast::proto_box
with m.fty}),
n_tps: vec::len(*m.tps), n_tps: vec::len(*m.tps),
substs: tps, substs: tps,
origin: method_param(iid, pos, n, bound_n), origin: method_param(iid, pos, n, bound_n),
@ -1694,7 +1695,7 @@ fn lookup_method_inner(fcx: @fn_ctxt, expr: @ast::expr,
let i = 0u; let i = 0u;
for m in *ty::iface_methods(tcx, did) { for m in *ty::iface_methods(tcx, did) {
if m.ident == name { if m.ident == name {
let fty = ty::mk_fn(tcx, m.fty); let fty = ty::mk_fn(tcx, {proto: ast::proto_box with m.fty});
if ty::type_has_vars(fty) { if ty::type_has_vars(fty) {
tcx.sess.span_fatal( tcx.sess.span_fatal(
expr.span, "can not call a method that contains a \ expr.span, "can not call a method that contains a \
@ -1717,13 +1718,20 @@ fn lookup_method_inner(fcx: @fn_ctxt, expr: @ast::expr,
alt tcx.items.get(did.node) { alt tcx.items.get(did.node) {
ast_map::node_method(m, _, _) { ast_map::node_method(m, _, _) {
let mt = ty_of_method(tcx, m_check, m); let mt = ty_of_method(tcx, m_check, m);
ty::mk_fn(tcx, mt.fty) ty::mk_fn(tcx, {proto: ast::proto_box with mt.fty})
} }
_ { _ {
tcx.sess.bug("Undocumented invariant in ty_from_did"); tcx.sess.bug("Undocumented invariant in ty_from_did");
} }
} }
} else { csearch::get_type(tcx, did).ty } } else {
alt ty::get(csearch::get_type(tcx, did).ty).struct {
ty::ty_fn(fty) {
ty::mk_fn(tcx, {proto: ast::proto_box with fty})
}
_ { fail; }
}
}
} }
let result = none, complained = false; let result = none, complained = false;

View file

@ -0,0 +1,20 @@
iface foo {
fn foo() -> int;
fn bar(p: int) -> int;
}
impl of foo for int {
fn foo() -> int { self }
fn bar(p: int) -> int { p * self.foo() }
}
impl <T: foo> of foo for [T] {
fn foo() -> int { vec::foldl(0, self, {|a, b| a + b.foo()}) }
fn bar(p: int) -> int { p + self.len() as int }
}
fn main() {
let x = [1, 2, 3];
let y = x.foo, z = [4, 5, 6].foo;
assert y() + z() == 21;
let a = x.bar, b = bind [4, 5, 6].bar(_);
assert a(1) + b(2) + z() == 24;
}