Further work on resolving and typechecking classes

Class tests aren't working yet, but they fail a little later :-)

Also, make the parser correctly set a constructor's result type to
its enclosing class type.
This commit is contained in:
Tim Chevalier 2012-02-20 15:42:21 -08:00
parent 5837e1e809
commit 2299d204e4
6 changed files with 200 additions and 28 deletions

View File

@ -151,7 +151,7 @@ enum dir { inside, outside, }
// when looking up a variable name that's not yet in scope to check
// if it's already bound to a enum.
enum namespace { ns_val(ns_value_type), ns_type, ns_module, }
enum ns_value_type { ns_a_enum, ns_any_value, }
enum ns_value_type { ns_an_enum, ns_any_value, }
fn resolve_crate(sess: session, amap: ast_map::map, crate: @ast::crate) ->
{def_map: def_map, exp_map: exp_map, impl_map: impl_map} {
@ -469,7 +469,7 @@ fn resolve_names(e: @env, c: @ast::crate) {
variable a refers to a nullary enum. */
ast::pat_ident(p, none) {
alt lookup_in_scope(*e, sc, p.span, path_to_ident(p),
ns_val(ns_a_enum)) {
ns_val(ns_an_enum)) {
some(fnd@ast::def_variant(_,_)) {
e.def_map.insert(pat.id, fnd);
}
@ -519,11 +519,16 @@ fn visit_item_with_scope(e: @env, i: @ast::item, sc: scopes, v: vt<scopes>) {
}
ast::item_class(tps, members, ctor_id, ctor_decl, ctor_block) {
visit::visit_ty_params(tps, sc, v);
let ctor_scope = cons(scope_fn_expr(ctor_decl, ctor_id, tps), @sc);
let class_scope = cons(scope_item(i), @sc);
/* visit the constructor... */
visit_fn_with_scope(e, visit::fk_item_fn(i.ident, tps), ctor_decl,
ctor_block, ctor_block.span, ctor_id,
class_scope, v);
/* visit the items */
for cm in members {
alt cm.node.decl {
class_method(i) { visit_item_with_scope(e, i, ctor_scope, v); }
_ { } // instance var -- nothing to do
class_method(i) { visit_item_with_scope(e, i, class_scope, v); }
instance_var(_,t,_,_) { v.visit_ty(t, class_scope, v); }
}
}
}
@ -622,10 +627,10 @@ fn visit_local_with_scope(e: @env, loc: @local, sc:scopes, v:vt<scopes>) {
// to enum foo, or is it binding a new name foo?)
alt loc.node.pat.node {
pat_ident(an_ident,_) {
// Be sure to pass ns_a_enum to lookup_in_scope so that
// Be sure to pass ns_an_enum to lookup_in_scope so that
// if this is a name that's being shadowed, we don't die
alt lookup_in_scope(*e, sc, loc.span,
path_to_ident(an_ident), ns_val(ns_a_enum)) {
path_to_ident(an_ident), ns_val(ns_an_enum)) {
some(ast::def_variant(enum_id,variant_id)) {
// Declaration shadows a enum that's in scope.
// That's an error.
@ -804,7 +809,7 @@ fn ns_name(ns: namespace) -> str {
ns_val(v) {
alt (v) {
ns_any_value { "name" }
ns_a_enum { "enum" }
ns_an_enum { "enum" }
}
}
ns_module { "modulename" }
@ -1000,6 +1005,21 @@ fn lookup_in_scope(e: env, sc: scopes, sp: span, name: ident, ns: namespace)
ast::item_native_mod(m) {
ret lookup_in_local_native_mod(e, it.id, sp, name, ns);
}
ast::item_class(tps, members, ctor_id, _, _) {
if ns == ns_type {
ret lookup_in_ty_params(e, name, tps);
}
if ns == ns_val(ns_any_value) && name == it.ident {
ret some(ast::def_fn(local_def(ctor_id),
ast::impure_fn));
}
if ns == ns_val(ns_any_value) {
ret lookup_in_class(local_def(it.id),
members, name);
}
// FIXME: AST allows other items to appear in a class,
// but that might not be wise
}
_ { }
}
}
@ -1071,7 +1091,7 @@ fn lookup_in_scope(e: env, sc: scopes, sp: span, name: ident, ns: namespace)
/* If we were looking for a enum, at this point
we know it's bound to a non-enum value, and
we can return none instead of failing */
ns_a_enum { ret none; }
ns_an_enum { ret none; }
_ { "attempted dynamic environment-capture" }
}
}
@ -1146,6 +1166,29 @@ fn lookup_in_fn(e: env, name: ident, decl: ast::fn_decl,
}
}
/*
FIXME: not sure about this code. maybe this should be handled
using the mod_index stuff
*/
fn lookup_in_class(parent_id: def_id,
members: [@class_item], name: ident)
-> option<def> {
for m in members {
alt m.node.decl {
instance_var(v_name,_,_,id) {
if v_name == name {
ret some(def_class_field(parent_id, local_def(id)));
}
}
class_method(i) {
if i.ident == name {
ret some(def_class_method(parent_id, local_def(i.id)));
}
}
}
}
ret none;
}
fn lookup_in_block(e: env, name: ident, sp: span, b: ast::blk_, pos: uint,
loc_pos: uint, ns: namespace) -> option<def> {
@ -1430,7 +1473,7 @@ fn lookup_in_globs(e: env, globs: [glob_imp_def], sp: span, id: ident,
if vec::len(matches) == 0u {
ret none;
}
else if vec::len(matches) == 1u || ns == ns_val(ns_a_enum) {
else if vec::len(matches) == 1u || ns == ns_val(ns_an_enum) {
ret some(matches[0].def);
} else {
for match: glob_imp_def in matches {
@ -1449,7 +1492,7 @@ fn lookup_glob_in_mod(e: env, info: @indexed_mod, sp: span, id: ident,
if !info.glob_imported_names.contains_key(id) {
info.glob_imported_names.insert(id, glob_resolving(sp));
// kludge
let val_ns = if wanted_ns == ns_val(ns_a_enum) { ns_val(ns_a_enum) }
let val_ns = if wanted_ns == ns_val(ns_an_enum) { ns_val(ns_an_enum) }
else { ns_val(ns_any_value) };
let globs = info.glob_imports;
let val = lookup_in_globs(e, globs, sp, id, val_ns, dr);
@ -1615,7 +1658,7 @@ fn index_nmod(md: ast::native_mod) -> mod_index {
// External lookups
fn ns_for_def(d: def) -> namespace {
alt d {
ast::def_variant(_, _) { ns_val(ns_a_enum) }
ast::def_variant(_, _) { ns_val(ns_an_enum) }
ast::def_fn(_, _) | ast::def_self(_) |
ast::def_const(_) | ast::def_arg(_, _) | ast::def_local(_) |
ast::def_upvar(_, _, _) | ast::def_self(_) |
@ -1632,7 +1675,7 @@ fn ns_for_def(d: def) -> namespace {
// a enum
fn ns_ok(wanted:namespace, actual:namespace) -> bool {
alt actual {
ns_val(ns_a_enum) {
ns_val(ns_an_enum) {
alt wanted {
ns_val(_) { true }
_ { false }

View File

@ -2183,6 +2183,21 @@ mod unify {
}
}
}
ty_class(expected_class, expected_tys) {
alt get(actual).struct {
ty_class(actual_class, actual_tys) {
if expected_class != actual_class {
ret ures_err(terr_mismatch);
}
ret unify_tps(cx, expected_tys, actual_tys, variance,
{|tps|
ures_ok(mk_class(cx.tcx, expected_class, tps))});
}
_ {
ret ures_err(terr_mismatch);
}
}
}
_ { cx.tcx.sess.bug("unify: unexpected type"); }
}
}
@ -2478,13 +2493,11 @@ fn enum_variant_with_id(cx: ctxt, enum_id: ast::def_id,
// If the given item is in an external crate, looks up its type and adds it to
// the type cache. Returns the type parameters and type.
// a precondition (did.crate != ast::local_crate) would be nice
fn lookup_item_type(cx: ctxt, did: ast::def_id) -> ty_param_bounds_and_ty {
alt cx.tcache.find(did) {
some(tpt) { ret tpt; }
none {
/* where do things get added to the cache?
Have to add class members */
// The item is in this crate. The caller should have added it to the
// type cache already
assert did.crate != ast::local_crate;

View File

@ -48,8 +48,17 @@ type crate_ctxt = {mutable self_infos: [self_info],
impl_map: resolve::impl_map,
method_map: method_map,
dict_map: dict_map,
// Not at all sure it's right to put these here
/* node_id for the class this fn is in --
none if it's not in a class */
enclosing_class_id: option<ast::node_id>,
/* map from node_ids for enclosing-class
vars and methods to types */
enclosing_class: class_map,
tcx: ty::ctxt};
type class_map = hashmap<ast::node_id, ty::t>;
type fn_ctxt =
// var_bindings, locals and next_var_id are shared
// with any nested functions that capture the environment
@ -121,7 +130,6 @@ fn ty_param_bounds_and_ty_for_def(fcx: @fn_ctxt, sp: span, defn: ast::def) ->
}
ast::def_fn(id, _) | ast::def_const(id) |
ast::def_variant(_, id) | ast::def_class(id)
| ast::def_class_method(_, id) | ast::def_class_field(_, id)
{ ret ty::lookup_item_type(fcx.ccx.tcx, id); }
ast::def_binding(id) {
assert (fcx.locals.contains_key(id.node));
@ -134,6 +142,20 @@ fn ty_param_bounds_and_ty_for_def(fcx: @fn_ctxt, sp: span, defn: ast::def) ->
ast::def_upvar(_, inner, _) {
ret ty_param_bounds_and_ty_for_def(fcx, sp, *inner);
}
ast::def_class_method(_, id) | ast::def_class_field(_, id) {
if id.crate != ast::local_crate {
fcx.ccx.tcx.sess.span_fatal(sp,
"class method or field referred to \
out of scope");
}
alt fcx.ccx.enclosing_class.find(id.node) {
some(a_ty) { ret {bounds: @[], ty: a_ty}; }
_ { fcx.ccx.tcx.sess.span_fatal(sp,
"class method or field referred to \
out of scope"); }
}
}
_ {
// FIXME: handle other names.
fcx.ccx.tcx.sess.unimpl("definition variant");
@ -316,7 +338,11 @@ fn ast_ty_to_ty(tcx: ty::ctxt, mode: mode, &&ast_ty: @ast::ty) -> ty::t {
ty::mk_fn(tcx, ty_of_fn_decl(tcx, mode, proto, decl))
}
ast::ty_path(path, id) {
alt tcx.def_map.get(id) {
let a_def = alt tcx.def_map.find(id) {
none { tcx.sess.span_fatal(ast_ty.span, #fmt("unbound path %s",
path_to_str(path))); }
some(d) { d }};
alt a_def {
ast::def_ty(id) {
instantiate(tcx, ast_ty.span, mode, id, path.node.types)
}
@ -349,6 +375,23 @@ fn ast_ty_to_ty(tcx: ty::ctxt, mode: mode, &&ast_ty: @ast::ty) -> ty::t {
}
}
}
ast::def_class(class_id) {
alt tcx.items.find(class_id.node) {
some(ast_map::node_item(
@{node: ast::item_class(tps, _, _, _, _), _}, _)) {
if vec::len(tps) != vec::len(path.node.types) {
tcx.sess.span_err(ast_ty.span, "incorrect number of \
type parameters to object type");
}
ty::mk_class(tcx, class_id, vec::map(path.node.types,
{|ast_ty| ast_ty_to_ty(tcx, mode, ast_ty)}))
}
_ {
tcx.sess.span_bug(ast_ty.span, "class id is unbound \
in items");
}
}
}
_ {
tcx.sess.span_fatal(ast_ty.span,
"found type name used as a variable");
@ -787,9 +830,20 @@ mod collect {
}
}
}
fn convert_class_item(_cx: @ctxt, _parent_ty: ty::t,
_ci: ast::class_member) {
/* TODO */
fn convert_class_item(cx: @ctxt, ci: ast::class_member) {
/* we want to do something here, b/c within the
scope of the class, it's ok to refer to fields &
methods unqualified */
/* they have these types *within the scope* of the
class. outside the class, it's done with expr_field */
alt ci {
ast::instance_var(_,t,_,id) {
let tt = ast_ty_to_ty(cx.tcx, m_collect, t);
write_ty(cx.tcx, id, tt);
}
ast::class_method(it) { convert(cx, it); }
}
}
fn convert(cx: @ctxt, it: @ast::item) {
alt it.node {
@ -890,16 +944,23 @@ mod collect {
ensure_iface_methods(cx.tcx, it.id);
}
ast::item_class(tps, members, ctor_id, ctor_decl, ctor_block) {
let parent_ty = ty::lookup_item_type(cx.tcx, local_def(it.id));
// Write the class type
let {bounds,params} = mk_ty_params(cx.tcx, tps);
let class_ty = ty::mk_class(cx.tcx, local_def(it.id), params);
let tpt = {bounds: bounds, ty: class_ty};
cx.tcx.tcache.insert(local_def(it.id), tpt);
write_ty(cx.tcx, it.id, class_ty);
// Write the ctor type
let t_ctor = ty::mk_fn(cx.tcx,
ty_of_fn_decl(cx.tcx, m_collect,
ast::proto_any, ctor_decl));
write_ty(cx.tcx, ctor_id, t_ctor);
cx.tcx.tcache.insert(local_def(ctor_id),
{bounds: bounds, ty: t_ctor});
/* FIXME: check for proper public/privateness */
// Write the type of each of the members
for m in members {
convert_class_item(cx, parent_ty.ty, m.node.decl);
convert_class_item(cx, m.node.decl);
}
}
_ {
@ -2252,7 +2313,7 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, expr: @ast::expr, unify: unifier,
}
ast::expr_fn(proto, decl, body, captures) {
check_expr_fn_with_unifier(fcx, expr, proto, decl, body,
unify, expected);
unify, expected);
capture::check_capture_clause(tcx, expr.id, proto, *captures);
}
ast::expr_fn_block(decl, body) {
@ -2446,6 +2507,12 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, expr: @ast::expr, unify: unifier,
_ {}
}
}
ty::ty_class(_id, _params) {
// TODO (classes)
tcx.sess.span_bug(expr.span,
#fmt("can't check class field accesses yet: %s",
ty_to_str(fcx.ccx.tcx, base_t)));
}
_ {}
}
if !handled {
@ -2874,6 +2941,30 @@ fn check_method(ccx: @crate_ctxt, method: @ast::method) {
check_fn(ccx, ast::proto_bare, method.decl, method.body, method.id, none);
}
fn class_types(ccx: @crate_ctxt, members: [@ast::class_item]) -> class_map {
let rslt = new_int_hash::<ty::t>();
for m in members {
alt m.node.decl {
ast::instance_var(_,t,_,id) {
rslt.insert(id, ast_ty_to_ty(ccx.tcx, m_collect, t));
}
ast::class_method(it) {
rslt.insert(it.id, ty_of_item(ccx.tcx, m_collect, it).ty);
}
}
}
rslt
}
fn check_class_member(ccx: @crate_ctxt, cm: ast::class_member) {
alt cm {
ast::instance_var(_,t,_,_) { // ??? Not sure
}
// not right yet -- need a scope
ast::class_method(i) { check_item(ccx, i); }
}
}
fn check_item(ccx: @crate_ctxt, it: @ast::item) {
alt it.node {
ast::item_const(_, e) { check_const(ccx, it.span, e, it.id); }
@ -2889,6 +2980,17 @@ fn check_item(ccx: @crate_ctxt, it: @ast::item) {
for m in ms { check_method(ccx, m); }
vec::pop(ccx.self_infos);
}
ast::item_class(tps, members, ctor_id, ctor_decl, ctor_body) {
let cid = some(it.id);
let members_info = class_types(ccx, members);
let class_ccx = @{enclosing_class_id:cid,
enclosing_class:members_info with *ccx};
// typecheck the ctor
check_fn(class_ccx, ast::proto_bare, ctor_decl, ctor_body, ctor_id,
none);
// typecheck the members
for m in members { check_class_member(class_ccx, m.node.decl); }
}
_ {/* nothing to do */ }
}
}
@ -3149,6 +3251,8 @@ fn check_crate(tcx: ty::ctxt, impl_map: resolve::impl_map,
impl_map: impl_map,
method_map: std::map::new_int_hash(),
dict_map: std::map::new_int_hash(),
enclosing_class_id: none,
enclosing_class: std::map::new_int_hash(),
tcx: tcx};
let visit = visit::mk_simple_visitor(@{
visit_item: bind check_item(ccx, _)

View File

@ -1972,13 +1972,14 @@ fn parse_item_res(p: parser, attrs: [ast::attribute]) -> @ast::item {
fn parse_item_class(p: parser, attrs: [ast::attribute]) -> @ast::item {
let lo = p.last_span.lo;
let class_name = parse_value_ident(p);
let class_path = ident_to_path(p.last_span, class_name);
let ty_params = parse_ty_params(p);
expect(p, token::LBRACE);
let items: [@ast::class_item] = [];
let ctor_id = p.get_id();
let the_ctor : option<(ast::fn_decl, ast::blk)> = none;
while p.token != token::RBRACE {
alt parse_class_item(p) {
alt parse_class_item(p, class_path) {
ctor_decl(a_fn_decl, blk) {
the_ctor = some((a_fn_decl, blk));
}
@ -2015,10 +2016,14 @@ enum class_contents { ctor_decl(ast::fn_decl, ast::blk),
// none of these are a ctor decl
priv_decls([ast::class_member])}
fn parse_class_item(p:parser) -> class_contents {
fn parse_class_item(p:parser, class_name:@ast::path) -> class_contents {
if eat_word(p, "new") {
// Can ctors have attrs?
let decl = parse_fn_decl(p, ast::impure_fn);
// result type is always the type of the class
let decl_ = parse_fn_decl(p, ast::impure_fn);
let decl = {output: @{node: ast::ty_path(class_name, p.get_id()),
span: decl_.output.span}
with decl_};
let body = parse_block(p);
ret ctor_decl(decl, body);
}

View File

@ -117,7 +117,7 @@ fn ty_to_str(cx: ctxt, typ: t) -> str {
ty_param(id, _) {
"'" + str::from_bytes([('a' as u8) + (id as u8)])
}
ty_enum(did, tps) | ty_res(did, _, tps) {
ty_enum(did, tps) | ty_res(did, _, tps) | ty_class(did, tps) {
// Not sure why, but under some circumstances enum or resource types
// do not have an associated id. I didn't investigate enough to know
// if there is a good reason for this. - Niko, 2012-02-10

View File

@ -7,4 +7,11 @@ class cat {
let how_hungry : int;
new(in_x : uint, in_y : int) { meows = in_x; how_hungry = in_y; }
}
fn main() {
let nyan : cat = cat(52u, 99);
let kitty = cat(1000u, 2);
log(debug, nyan.how_hungry);
log(debug, kitty.how_hungry);
}