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:
parent
5837e1e809
commit
2299d204e4
6 changed files with 200 additions and 28 deletions
|
@ -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 }
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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, _)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
Loading…
Reference in a new issue