From c3410bf927c863cd33057184e97e6a6169475059 Mon Sep 17 00:00:00 2001 From: Lindsey Kuper Date: Fri, 20 May 2011 17:41:36 -0700 Subject: [PATCH] More work on anonymous objects. --- src/comp/back/abi.rs | 2 + src/comp/front/ast.rs | 2 +- src/comp/front/parser.rs | 4 +- src/comp/middle/fold.rs | 13 +- src/comp/middle/trans.rs | 78 +++++++++++- src/comp/middle/tstate/pre_post_conditions.rs | 12 ++ src/comp/middle/tstate/states.rs | 15 +++ src/comp/middle/typeck.rs | 116 +++++++++++++++--- src/comp/middle/walk.rs | 34 ++++- src/comp/pretty/pprust.rs | 4 +- src/test/run-pass/simple-anon-objs.rs | 29 +++++ 11 files changed, 281 insertions(+), 28 deletions(-) create mode 100644 src/test/run-pass/simple-anon-objs.rs diff --git a/src/comp/back/abi.rs b/src/comp/back/abi.rs index 54dc5e0d01d..54f800c1667 100644 --- a/src/comp/back/abi.rs +++ b/src/comp/back/abi.rs @@ -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; diff --git a/src/comp/front/ast.rs b/src/comp/front/ast.rs index db625124be4..0653e45d268 100644 --- a/src/comp/front/ast.rs +++ b/src/comp/front/ast.rs @@ -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); diff --git a/src/comp/front/parser.rs b/src/comp/front/parser.rs index 80397f2a65b..8bd02aea74b 100644 --- a/src/comp/front/parser.rs +++ b/src/comp/front/parser.rs @@ -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)); diff --git a/src/comp/middle/fold.rs b/src/comp/middle/fold.rs index 9d48d50b4e6..1173c11729e 100644 --- a/src/comp/middle/fold.rs +++ b/src/comp/middle/fold.rs @@ -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); } diff --git a/src/comp/middle/trans.rs b/src/comp/middle/trans.rs index 9bcfd77d251..4f9dd2f9a34 100644 --- a/src/comp/middle/trans.rs +++ b/src/comp/middle/trans.rs @@ -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. diff --git a/src/comp/middle/tstate/pre_post_conditions.rs b/src/comp/middle/tstate/pre_post_conditions.rs index a837bd9f9a2..308c99ac620 100644 --- a/src/comp/middle/tstate/pre_post_conditions.rs +++ b/src/comp/middle/tstate/pre_post_conditions.rs @@ -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)); + } + } + } } } diff --git a/src/comp/middle/tstate/states.rs b/src/comp/middle/tstate/states.rs index c04ad500574..ef1e86275ce 100644 --- a/src/comp/middle/tstate/states.rs +++ b/src/comp/middle/tstate/states.rs @@ -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); + } + } + } } } diff --git a/src/comp/middle/typeck.rs b/src/comp/middle/typeck.rs index de47fce619d..cb95a5e6f01 100644 --- a/src/comp/middle/typeck.rs +++ b/src/comp/middle/typeck.rs @@ -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 { diff --git a/src/comp/middle/walk.rs b/src/comp/middle/walk.rs index cac5fd0ea35..3580df7e3f9 100644 --- a/src/comp/middle/walk.rs +++ b/src/comp/middle/walk.rs @@ -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); } diff --git a/src/comp/pretty/pprust.rs b/src/comp/pretty/pprust.rs index d104e862dc8..0f825c33934 100644 --- a/src/comp/pretty/pprust.rs +++ b/src/comp/pretty/pprust.rs @@ -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 } } diff --git a/src/test/run-pass/simple-anon-objs.rs b/src/test/run-pass/simple-anon-objs.rs new file mode 100644 index 00000000000..135d12d243c --- /dev/null +++ b/src/test/run-pass/simple-anon-objs.rs @@ -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; + +}