More work on anonymous objects.

This commit is contained in:
Lindsey Kuper 2011-05-20 17:41:36 -07:00 committed by Graydon Hoare
parent c8d488b337
commit c3410bf927
11 changed files with 281 additions and 28 deletions

View file

@ -55,6 +55,8 @@ const int obj_field_box = 1;
const int obj_body_elt_tydesc = 0;
const int obj_body_elt_typarams = 1;
const int obj_body_elt_fields = 2;
const int obj_body_elt_with_obj = 3; /* The base object to which an anonymous
* object is attached */
const int fn_field_code = 0;
const int fn_field_box = 1;

View file

@ -379,7 +379,7 @@ type anon_obj = rec(
option::t[vec[obj_field]] fields,
vec[@method] methods,
// with_obj: the original object being extended, if it exists.
option::t[ident] with_obj);
option::t[@expr] with_obj);
type _mod = rec(vec[@view_item] view_items,
vec[@item] items);

View file

@ -820,13 +820,13 @@ fn parse_bottom_expr(&parser p) -> @ast::expr {
}
let vec[@ast::method] meths = [];
let option::t[ast::ident] with_obj = none[ast::ident];
let option::t[@ast::expr] with_obj = none[@ast::expr];
expect(p, token::LBRACE);
while (p.peek() != token::RBRACE) {
if (eat_word(p, "with")) {
with_obj = some[ast::ident](parse_ident(p));
with_obj = some[@ast::expr](parse_expr(p));
} else {
vec::push[@ast::method](meths,
parse_method(p));

View file

@ -336,7 +336,7 @@ type ast_fold[ENV] =
(fn(&ENV e,
&option::t[vec[ast::obj_field]] fields,
&vec[@ast::method] methods,
&option::t[ident] with_obj)
&option::t[@ast::expr] with_obj)
-> ast::anon_obj) fold_anon_obj,
// Env updates.
@ -1001,11 +1001,11 @@ fn fold_anon_obj[ENV](&ENV env, &ast_fold[ENV] fld, &ast::anon_obj ob)
}
// with_obj
let option::t[ast::ident] with_obj = none[ast::ident];
let option::t[@ast::expr] with_obj = none[@ast::expr];
alt (ob.with_obj) {
case (none[ast::ident]) { }
case (some[ast::ident](?i)) {
with_obj = some[ast::ident](i);
case (none[@ast::expr]) { }
case (some[@ast::expr](?e)) {
with_obj = some[@ast::expr](fold_expr(env, fld, e));
}
}
@ -1665,7 +1665,8 @@ fn identity_fold_obj[ENV](&ENV e,
fn identity_fold_anon_obj[ENV](&ENV e,
&option::t[vec[ast::obj_field]] fields,
&vec[@ast::method] methods,
&option::t[ident] with_obj) -> ast::anon_obj {
&option::t[@ast::expr] with_obj)
-> ast::anon_obj {
ret rec(fields=fields, methods=methods, with_obj=with_obj);
}

View file

@ -4576,7 +4576,8 @@ fn trans_lval(&@block_ctxt cx, &@ast::expr e) -> lval_result {
}
case (_) {
cx.fcx.lcx.ccx.sess.span_unimpl(e.span,
"expr variant in trans_lval");
"expr variant in trans_lval: "
+ util::common::expr_to_str(e));
}
}
fail;
@ -5547,6 +5548,10 @@ fn trans_expr(&@block_ctxt cx, &@ast::expr e) -> result {
ret trans_spawn(cx, dom, name, func, args, ann);
}
case (ast::expr_anon_obj(?anon_obj, ?tps, ?odid, ?ann)) {
ret trans_anon_obj(cx, e.span, anon_obj, tps, odid, ann);
}
case (_) {
// The expression is an lvalue. Fall through.
}
@ -6111,6 +6116,77 @@ fn recv_val(&@block_ctxt cx, ValueRef lhs, &@ast::expr rhs,
ret res(bcx, lhs);
}
/*
Suppose we create an anonymous object my_b from a regular object a:
obj a() {
fn foo() -> int {
ret 2;
}
fn bar() -> int {
ret self.foo();
}
}
auto my_a = a();
auto my_b = obj { fn baz() -> int { ret self.foo() } with my_a };
Here we're extending the my_a object with an additional method baz, creating
an object my_b. Since it's an object, my_b is a pair of a vtable pointer and
a body pointer:
my_b: [vtbl* | body*]
my_b's vtable has entries for foo, bar, and baz, whereas my_a's vtable has
only foo and bar. my_b's 3-entry vtable consists of two forwarding functions
and one real method.
my_b's body just contains the pair a: [ a_vtable | a_body ], wrapped up with
any additional fields that my_b added. None were added, so my_b is just the
wrapped inner object.
*/
fn trans_anon_obj(&@block_ctxt cx, &ast::span sp,
&ast::anon_obj anon_obj,
&vec[ast::ty_param] ty_params,
&ast::obj_def_ids oid,
&ast::ann ann) -> result {
let option::t[result] with_obj_val = none[result];
alt (anon_obj.with_obj) {
case (none[@ast::expr]) { }
case (some[@ast::expr](?e)) {
// Translating with_obj returns a pointer to a 2-word value. We
// want to allocate space for this value in our outer object, then
// copy it into the outer object.
with_obj_val = some[result](trans_expr(cx, e));
}
}
// For the anon obj's additional fields, if any exist, translate object
// constructor arguments to function arguments.
let option::t[vec[ast::obj_field]] addtl_fields
= none[vec[ast::obj_field]];
let vec[ast::arg] addtl_fn_args = [];
alt (anon_obj.fields) {
case (none[vec[ast::obj_field]]) { }
case (some[vec[ast::obj_field]](?fields)) {
for (ast::obj_field f in fields) {
addtl_fn_args += [rec(mode=ast::alias, ty=f.ty,
ident=f.ident, id=f.id)];
}
}
}
// TODO: everything else.
cx.fcx.lcx.ccx.sess.unimpl("support for anonymous objects");
fail;
}
fn init_local(&@block_ctxt cx, &@ast::local local) -> result {
// Make a note to drop this slot on the way out.

View file

@ -132,6 +132,7 @@ import front::ast::expr_assert;
import front::ast::expr_cast;
import front::ast::expr_for;
import front::ast::expr_for_each;
import front::ast::expr_anon_obj;
import front::ast::stmt_decl;
import front::ast::stmt_expr;
import front::ast::block;
@ -556,6 +557,17 @@ fn find_pre_post_expr(&fn_ctxt fcx, @expr e) -> () {
find_pre_post_expr(fcx, expanded);
copy_pre_post(fcx.ccx, a, expanded);
}
case (expr_anon_obj(?anon_obj, _, _, ?a)) {
alt (anon_obj.with_obj) {
case (some[@expr](?ex)) {
find_pre_post_expr(fcx, ex);
copy_pre_post(fcx.ccx, a, ex);
}
case (none[@expr]) {
clear_pp(expr_pp(fcx.ccx, e));
}
}
}
}
}

View file

@ -143,6 +143,7 @@ import front::ast::expr_assert;
import front::ast::expr_cast;
import front::ast::expr_for;
import front::ast::expr_for_each;
import front::ast::expr_anon_obj;
import front::ast::stmt_decl;
import front::ast::stmt_expr;
import front::ast::block;
@ -578,6 +579,20 @@ fn find_pre_post_state_expr(&fn_ctxt fcx, &prestate pres, @expr e) -> bool {
case (expr_self_method(_, ?a)) {
ret pure_exp(fcx.ccx, a, pres);
}
case (expr_anon_obj(?anon_obj, _, _,?a)) {
alt (anon_obj.with_obj) {
case (some[@expr](?e)) {
changed = find_pre_post_state_expr(fcx, pres, e);
changed = extend_prestate_ann(fcx.ccx, a, pres) || changed;
changed = extend_poststate_ann(fcx.ccx, a,
expr_poststate(fcx.ccx, e)) || changed;
ret changed;
}
case (none[@expr]) {
ret pure_exp(fcx.ccx, a, pres);
}
}
}
}
}

View file

@ -502,13 +502,13 @@ mod collect {
ret tpt;
}
fn ty_of_arg(@ctxt cx, &ast::arg a) -> arg {
fn ty_of_arg(@ctxt cx, &ast::arg a) -> ty::arg {
auto ty_mode = ast_mode_to_mode(a.mode);
auto f = bind getter(cx, _);
ret rec(mode=ty_mode, ty=ast_ty_to_ty(cx.tcx, f, a.ty));
}
fn ty_of_method(@ctxt cx, &@ast::method m) -> method {
fn ty_of_method(@ctxt cx, &@ast::method m) -> ty::method {
auto get = bind getter(cx, _);
auto convert = bind ast_ty_to_ty(cx.tcx, get, _);
auto f = bind ty_of_arg(cx, _);
@ -694,10 +694,10 @@ mod collect {
ret result;
}
fn get_obj_method_types(&@ctxt cx, &ast::_obj object) -> vec[method] {
fn get_obj_method_types(&@ctxt cx, &ast::_obj object) -> vec[ty::method] {
ret vec::map[@ast::method,method](bind ty_of_method(cx, _),
object.methods);
object.methods);
}
fn collect(ty::item_table id_to_ty_item, &@ast::item i) {
@ -1485,6 +1485,14 @@ mod Pushdown {
write::ty_only_fixup(scx, ann.id, t);
}
case (ast::expr_anon_obj(?anon_obj, ?tps, ?odid, ?ann)) {
// NB: Not sure if this is correct, but not worrying too much
// about it since pushdown is going away anyway.
auto t = Demand::autoderef(scx, e.span, expected,
ann_to_type(scx.fcx.ccx.tcx.node_types, ann), adk);
write::ty_only_fixup(scx, ann.id, t);
}
case (_) {
scx.fcx.ccx.tcx.sess.span_unimpl(e.span,
#fmt("type unification for expression variant: %s",
@ -1771,14 +1779,13 @@ fn require_pure_function(@crate_ctxt ccx, &ast::def_id d_id, &span sp) -> () {
}
fn check_expr(&@stmt_ctxt scx, &@ast::expr expr) {
//fcx.ccx.tcx.sess.span_warn(expr.span, "typechecking expr " +
// util::common::expr_to_str(expr));
// scx.fcx.ccx.tcx.sess.span_warn(expr.span, "typechecking expr " +
// util::common::expr_to_str(expr));
// A generic function to factor out common logic from call and bind
// expressions.
fn check_call_or_bind(&@stmt_ctxt scx, &@ast::expr f,
&vec[option::t[@ast::expr]] args) {
// Check the function.
check_expr(scx, f);
@ -2297,10 +2304,20 @@ fn check_expr(&@stmt_ctxt scx, &@ast::expr expr) {
auto t = ty::mk_nil(scx.fcx.ccx.tcx);
let ty::t this_obj_ty;
auto oinfo_opt = get_obj_info(scx.fcx.ccx);
auto this_obj_id = option::get[obj_info](oinfo_opt).this_obj;
this_obj_ty = ty::lookup_item_type(scx.fcx.ccx.tcx,
this_obj_id)._1;
let option::t[obj_info] this_obj_info = get_obj_info(scx.fcx.ccx);
alt (this_obj_info) {
// If we're inside a current object, grab its type.
case (some[obj_info](?obj_info)) {
// FIXME: In the case of anonymous objects with methods
// containing self-calls, this lookup fails because
// obj_info.this_obj is not in the type cache
this_obj_ty = ty::lookup_item_type(scx.fcx.ccx.tcx,
obj_info.this_obj)._1;
}
case (none[obj_info]) { fail; }
}
// Grab this method's type out of the current object type.
alt (struct(scx.fcx.ccx.tcx, this_obj_ty)) {
@ -2474,7 +2491,7 @@ fn check_expr(&@stmt_ctxt scx, &@ast::expr expr) {
case (ty::ty_rec(?fields)) {
let uint ix = ty::field_idx(scx.fcx.ccx.tcx.sess,
expr.span, field, fields);
if (ix >= vec::len[typeck::field](fields)) {
if (ix >= vec::len[ty::field](fields)) {
scx.fcx.ccx.tcx.sess.span_err(expr.span,
"bad index on record");
}
@ -2484,7 +2501,8 @@ fn check_expr(&@stmt_ctxt scx, &@ast::expr expr) {
case (ty::ty_obj(?methods)) {
let uint ix = ty::method_idx(scx.fcx.ccx.tcx.sess,
expr.span, field, methods);
if (ix >= vec::len[typeck::method](methods)) {
if (ix >= vec::len[ty::method](methods)) {
scx.fcx.ccx.tcx.sess.span_err(expr.span,
"bad index on obj");
}
@ -2560,6 +2578,75 @@ fn check_expr(&@stmt_ctxt scx, &@ast::expr expr) {
}
}
case (ast::expr_anon_obj(?anon_obj, ?tps, ?obj_def_ids, ?a)) {
// TODO: We probably need to do more work here to be able to
// handle additional methods that use 'self'
// We're entering an object, so gather up the info we need.
let vec[ast::obj_field] fields = [];
alt (anon_obj.fields) {
case (none[vec[ast::obj_field]]) { }
case (some[vec[ast::obj_field]](?v)) { fields = v; }
}
let ast::def_id di = obj_def_ids.ty;
vec::push[obj_info](scx.fcx.ccx.obj_infos,
rec(obj_fields=fields, this_obj=di));
// Typecheck 'with_obj', if it exists.
let option::t[@ast::expr] with_obj = none[@ast::expr];
alt (anon_obj.with_obj) {
case (none[@ast::expr]) { }
case (some[@ast::expr](?e)) {
// This had better have object type. TOOD: report an
// error if the user is trying to extend a non-object
// with_obj.
check_expr(scx, e);
}
}
// Typecheck the methods.
for (@ast::method method in anon_obj.methods) {
check_method(scx.fcx.ccx, method);
}
auto t = next_ty_var(scx);
// FIXME: These next three functions are largely ripped off from
// similar ones in collect::. Is there a better way to do this?
fn ty_of_arg(@crate_ctxt ccx, &ast::arg a) -> ty::arg {
auto ty_mode = ast_mode_to_mode(a.mode);
ret rec(mode=ty_mode, ty=ast_ty_to_ty_crate(ccx, a.ty));
}
fn ty_of_method(@crate_ctxt ccx, &@ast::method m) -> ty::method {
auto convert = bind ast_ty_to_ty_crate(ccx, _);
auto f = bind ty_of_arg(ccx, _);
auto inputs = vec::map[ast::arg,arg](f,
m.node.meth.decl.inputs);
auto output = convert(m.node.meth.decl.output);
ret rec(proto=m.node.meth.proto, ident=m.node.ident,
inputs=inputs, output=output, cf=m.node.meth.decl.cf);
}
fn get_anon_obj_method_types(@crate_ctxt ccx,
&ast::anon_obj anon_obj)
-> vec[ty::method] {
ret vec::map[@ast::method,method](bind ty_of_method(ccx, _),
anon_obj.methods);
}
auto methods = get_anon_obj_method_types(scx.fcx.ccx, anon_obj);
auto ot = ty::mk_obj(scx.fcx.ccx.tcx,
ty::sort_methods(methods));
write::ty_only_fixup(scx, a.id, ot);
// Now remove the info from the stack.
vec::pop[obj_info](scx.fcx.ccx.obj_infos);
}
case (_) {
scx.fcx.ccx.tcx.sess.unimpl("expr type in typeck::check_expr");
}
@ -2757,7 +2844,6 @@ fn check_item(@crate_ctxt ccx, &@ast::item it) {
}
}
// Utilities for the unification cache
fn hash_unify_cache_entry(&unify_cache_entry uce) -> uint {

View file

@ -444,7 +444,39 @@ fn walk_expr(&ast_visitor v, @ast::expr e) {
walk_expr(v, x);
}
case (ast::expr_anon_obj(_,_,_,_)) { }
case (ast::expr_anon_obj(?anon_obj,_,_,_)) {
// Fields
let option::t[vec[ast::obj_field]] fields
= none[vec[ast::obj_field]];
alt (anon_obj.fields) {
case (none[vec[ast::obj_field]]) { }
case (some[vec[ast::obj_field]](?fields)) {
for (ast::obj_field f in fields) {
walk_ty(v, f.ty);
}
}
}
// with_obj
let option::t[@ast::expr] with_obj = none[@ast::expr];
alt (anon_obj.with_obj) {
case (none[@ast::expr]) { }
case (some[@ast::expr](?e)) {
walk_expr(v, e);
}
}
// Methods
for (@ast::method m in anon_obj.methods) {
v.visit_method_pre(m);
walk_fn(v, m.node.meth, m.node.ident,
m.node.id, m.node.ann);
v.visit_method_post(m);
}
}
}
v.visit_expr_post(e);
}

View file

@ -712,8 +712,8 @@ fn print_expr(ps s, &@ast::expr expr) {
}
case (ast::expr_anon_obj(_,_,_,_)) {
wrd(s.s, "obj");
// TODO
wrd(s.s, "anon obj");
// TODO: nicer pretty-printing of anon objs
}
}

View file

@ -0,0 +1,29 @@
// xfail-stage0
// xfail-stage1
use std;
fn main() {
obj a() {
fn foo() -> int {
ret 2;
}
}
auto my_a = a();
// Extending an object with a new method
auto my_b = obj {
fn bar() -> int {
ret 3;
}
with my_a
};
assert my_a.foo() == 2;
// FIXME: these raise a runtime error
//assert my_b.foo() == 2;
assert my_b.bar() == 3;
}