Try to do some resolution of vtables earlier, in a fairly ad-hoc way. Closes #3156.

This commit is contained in:
Michael Sullivan 2012-08-20 18:47:16 -07:00
parent bd736a0f9b
commit 71ec545614
3 changed files with 107 additions and 36 deletions

View file

@ -820,6 +820,7 @@ fn check_expr_with_unifier(fcx: @fn_ctxt,
// overloaded operations // overloaded operations
fn check_call_inner( fn check_call_inner(
fcx: @fn_ctxt, sp: span, call_expr_id: ast::node_id, in_fty: ty::t, fcx: @fn_ctxt, sp: span, call_expr_id: ast::node_id, in_fty: ty::t,
callee_expr: @ast::expr,
args: ~[@ast::expr]) -> {fty: ty::t, bot: bool} { args: ~[@ast::expr]) -> {fty: ty::t, bot: bool} {
let mut bot = false; let mut bot = false;
@ -888,9 +889,17 @@ fn check_expr_with_unifier(fcx: @fn_ctxt,
// of arguments when we typecheck the functions. This isn't really the // of arguments when we typecheck the functions. This isn't really the
// right way to do this. // right way to do this.
for [false, true]/_.each |check_blocks| { for [false, true]/_.each |check_blocks| {
// More awful hacks: before we check the blocks, try to do
// an "opportunistic" vtable resolution of any trait
// bounds on the call.
if check_blocks {
vtable::early_resolve_expr(callee_expr, fcx, true);
}
for args.eachi |i, a| { for args.eachi |i, a| {
let is_block = match a.node { let is_block = match a.node {
ast::expr_fn_block(*) => true, ast::expr_fn_block(*) | ast::expr_loop_body(*) |
ast::expr_do_body(*) => true,
_ => false _ => false
}; };
if is_block == check_blocks { if is_block == check_blocks {
@ -933,7 +942,7 @@ fn check_expr_with_unifier(fcx: @fn_ctxt,
// Call the generic checker. // Call the generic checker.
let fty = { let fty = {
let r = check_call_inner(fcx, sp, call_expr_id, let r = check_call_inner(fcx, sp, call_expr_id,
fn_ty, args); fn_ty, f, args);
bot |= r.bot; bot |= r.bot;
r.fty r.fty
}; };
@ -998,7 +1007,7 @@ fn check_expr_with_unifier(fcx: @fn_ctxt,
let {fty: method_ty, bot: bot} = { let {fty: method_ty, bot: bot} = {
let method_ty = fcx.node_ty(op_ex.callee_id); let method_ty = fcx.node_ty(op_ex.callee_id);
check_call_inner(fcx, op_ex.span, op_ex.id, check_call_inner(fcx, op_ex.span, op_ex.id,
method_ty, args) method_ty, op_ex, args)
}; };
fcx.ccx.method_map.insert(op_ex.id, origin); fcx.ccx.method_map.insert(op_ex.id, origin);
some((ty::ty_fn_ret(method_ty), bot)) some((ty::ty_fn_ret(method_ty), bot))

View file

@ -2,6 +2,22 @@ import check::{fn_ctxt, impl_self_ty};
import infer::{resolve_type, resolve_all, force_all, fixup_err_to_str}; import infer::{resolve_type, resolve_all, force_all, fixup_err_to_str};
import ast_util::new_def_hash; import ast_util::new_def_hash;
// vtable resolution looks for places where trait bounds are
// subsituted in and figures out which vtable is used. There is some
// extra complication thrown in to support early "opportunistic"
// vtable resolution. This is a hacky mechanism that is invoked while
// typechecking function calls (after typechecking non-closure
// arguments and before typechecking closure arguments) in the hope of
// solving for the trait parameters from the impl. (For example,
// determining that if a parameter bounded by BaseIter<A> is
// instantiated with option<int>, that A = int.)
//
// In early resolution mode, no vtables are recorded, and a number of
// errors are ignored. Early resolution only works if a type is
// *fully* resolved. (We could be less restrictive than that, but it
// would require much more care, and this seems to work decently in
// practice.)
fn has_trait_bounds(tps: ~[ty::param_bounds]) -> bool { fn has_trait_bounds(tps: ~[ty::param_bounds]) -> bool {
vec::any(tps, |bs| { vec::any(tps, |bs| {
vec::any(*bs, |b| { vec::any(*bs, |b| {
@ -14,7 +30,8 @@ fn lookup_vtables(fcx: @fn_ctxt,
sp: span, sp: span,
bounds: @~[ty::param_bounds], bounds: @~[ty::param_bounds],
substs: &ty::substs, substs: &ty::substs,
allow_unsafe: bool) -> vtable_res { allow_unsafe: bool,
is_early: bool) -> vtable_res {
let tcx = fcx.ccx.tcx; let tcx = fcx.ccx.tcx;
let mut result = ~[], i = 0u; let mut result = ~[], i = 0u;
for substs.tps.each |ty| { for substs.tps.each |ty| {
@ -23,7 +40,7 @@ fn lookup_vtables(fcx: @fn_ctxt,
ty::bound_trait(i_ty) => { ty::bound_trait(i_ty) => {
let i_ty = ty::subst(tcx, substs, i_ty); let i_ty = ty::subst(tcx, substs, i_ty);
vec::push(result, lookup_vtable(fcx, sp, ty, i_ty, vec::push(result, lookup_vtable(fcx, sp, ty, i_ty,
allow_unsafe)); allow_unsafe, is_early));
} }
_ => () _ => ()
} }
@ -34,13 +51,15 @@ fn lookup_vtables(fcx: @fn_ctxt,
} }
fn fixup_substs(fcx: @fn_ctxt, sp: span, fn fixup_substs(fcx: @fn_ctxt, sp: span,
id: ast::def_id, substs: ty::substs) -> ty::substs { id: ast::def_id, substs: ty::substs,
is_early: bool) -> option<ty::substs> {
let tcx = fcx.ccx.tcx; let tcx = fcx.ccx.tcx;
// use a dummy type just to package up the substs that need fixing up // use a dummy type just to package up the substs that need fixing up
let t = ty::mk_trait(tcx, id, substs, ty::vstore_slice(ty::re_static)); let t = ty::mk_trait(tcx, id, substs, ty::vstore_slice(ty::re_static));
let t_f = fixup_ty(fcx, sp, t); do fixup_ty(fcx, sp, t, is_early).map |t_f| {
match check ty::get(t_f).struct { match check ty::get(t_f).struct {
ty::ty_trait(_, substs_f, _) => substs_f, ty::ty_trait(_, substs_f, _) => substs_f,
}
} }
} }
@ -54,7 +73,7 @@ Look up the vtable to use when treating an item of type <t>
as if it has type <trait_ty> as if it has type <trait_ty>
*/ */
fn lookup_vtable(fcx: @fn_ctxt, sp: span, ty: ty::t, trait_ty: ty::t, fn lookup_vtable(fcx: @fn_ctxt, sp: span, ty: ty::t, trait_ty: ty::t,
allow_unsafe: bool) allow_unsafe: bool, is_early: bool)
-> vtable_origin { -> vtable_origin {
debug!{"lookup_vtable(ty=%s, trait_ty=%s)", debug!{"lookup_vtable(ty=%s, trait_ty=%s)",
@ -65,7 +84,18 @@ fn lookup_vtable(fcx: @fn_ctxt, sp: span, ty: ty::t, trait_ty: ty::t,
let (trait_id, trait_substs) = match check ty::get(trait_ty).struct { let (trait_id, trait_substs) = match check ty::get(trait_ty).struct {
ty::ty_trait(did, substs, _) => (did, substs) ty::ty_trait(did, substs, _) => (did, substs)
}; };
let ty = fixup_ty(fcx, sp, ty); let ty = match fixup_ty(fcx, sp, ty, is_early) {
some(ty) => ty,
none => {
// fixup_ty can only fail if this is early resolution
assert is_early;
// The type has unconstrained type variables in it, so we can't
// do early resolution on it. Return some completely bogus vtable
// information: we aren't storing it anyways.
return vtable_param(0, 0);
}
};
match ty::get(ty).struct { match ty::get(ty).struct {
ty::ty_param({idx: n, def_id: did}) => { ty::ty_param({idx: n, def_id: did}) => {
let mut n_bound = 0u; let mut n_bound = 0u;
@ -97,7 +127,7 @@ fn lookup_vtable(fcx: @fn_ctxt, sp: span, ty: ty::t, trait_ty: ty::t,
did}; did};
relate_trait_tys(fcx, sp, trait_ty, ty); relate_trait_tys(fcx, sp, trait_ty, ty);
if !allow_unsafe { if !allow_unsafe && !is_early {
for vec::each(*ty::trait_methods(tcx, did)) |m| { for vec::each(*ty::trait_methods(tcx, did)) |m| {
if ty::type_has_self(ty::mk_fn(tcx, m.fty)) { if ty::type_has_self(ty::mk_fn(tcx, m.fty)) {
tcx.sess.span_err( tcx.sess.span_err(
@ -154,21 +184,30 @@ fn lookup_vtable(fcx: @fn_ctxt, sp: span, ty: ty::t, trait_ty: ty::t,
} }
// check that desired trait type unifies // check that desired trait type unifies
debug!{"(checking vtable) @2 relating trait ty %s to \ debug!("(checking vtable) @2 relating trait ty %s to \
of_ty %s", of_ty %s",
fcx.infcx.ty_to_str(trait_ty), fcx.infcx.ty_to_str(trait_ty),
fcx.infcx.ty_to_str(of_ty)}; fcx.infcx.ty_to_str(of_ty));
let of_ty = ty::subst(tcx, &substs, of_ty); let of_ty = ty::subst(tcx, &substs, of_ty);
relate_trait_tys(fcx, sp, trait_ty, of_ty); relate_trait_tys(fcx, sp, trait_ty, of_ty);
// recursively process the bounds // recursively process the bounds.
let trait_tps = trait_substs.tps; let trait_tps = trait_substs.tps;
let substs_f = fixup_substs(fcx, sp, trait_id, // see comments around the earlier call to fixup_ty
substs); let substs_f = match fixup_substs(fcx, sp, trait_id,
substs, is_early) {
some(substs) => substs,
none => {
assert is_early;
// Bail out with a bogus answer
return vtable_param(0, 0);
}
};
connect_trait_tps(fcx, sp, substs_f.tps, connect_trait_tps(fcx, sp, substs_f.tps,
trait_tps, im.did); trait_tps, im.did);
let subres = lookup_vtables(fcx, sp, im_bs, &substs_f, let subres = lookup_vtables(fcx, sp, im_bs, &substs_f,
false); false, is_early);
vec::push(found, vec::push(found,
vtable_static(im.did, substs_f.tps, vtable_static(im.did, substs_f.tps,
subres)); subres));
@ -181,8 +220,10 @@ fn lookup_vtable(fcx: @fn_ctxt, sp: span, ty: ty::t, trait_ty: ty::t,
0u => { /* fallthrough */ } 0u => { /* fallthrough */ }
1u => { return found[0]; } 1u => { return found[0]; }
_ => { _ => {
fcx.ccx.tcx.sess.span_err( if !is_early {
sp, ~"multiple applicable methods in scope"); fcx.ccx.tcx.sess.span_err(
sp, ~"multiple applicable methods in scope");
}
return found[0]; return found[0];
} }
} }
@ -195,17 +236,21 @@ fn lookup_vtable(fcx: @fn_ctxt, sp: span, ty: ty::t, trait_ty: ty::t,
ty_to_str(tcx, ty)); ty_to_str(tcx, ty));
} }
fn fixup_ty(fcx: @fn_ctxt, sp: span, ty: ty::t) -> ty::t { fn fixup_ty(fcx: @fn_ctxt, sp: span, ty: ty::t, is_early: bool)
-> option<ty::t> {
let tcx = fcx.ccx.tcx; let tcx = fcx.ccx.tcx;
match resolve_type(fcx.infcx, ty, resolve_all | force_all) { match resolve_type(fcx.infcx, ty, resolve_all | force_all) {
result::ok(new_type) => new_type, result::ok(new_type) => some(new_type),
result::err(e) => { result::err(e) if !is_early => {
tcx.sess.span_fatal( tcx.sess.span_fatal(
sp, sp,
fmt!{"cannot determine a type \ fmt!{"cannot determine a type \
for this bounded type parameter: %s", for this bounded type parameter: %s",
fixup_err_to_str(e)}) fixup_err_to_str(e)})
} }
result::err(e) => {
none
}
} }
} }
@ -226,7 +271,7 @@ fn connect_trait_tps(fcx: @fn_ctxt, sp: span, impl_tys: ~[ty::t],
} }
} }
fn resolve_expr(ex: @ast::expr, &&fcx: @fn_ctxt, v: visit::vt<@fn_ctxt>) { fn early_resolve_expr(ex: @ast::expr, &&fcx: @fn_ctxt, is_early: bool) {
let cx = fcx.ccx; let cx = fcx.ccx;
match ex.node { match ex.node {
ast::expr_path(*) => { ast::expr_path(*) => {
@ -236,11 +281,9 @@ fn resolve_expr(ex: @ast::expr, &&fcx: @fn_ctxt, v: visit::vt<@fn_ctxt>) {
let did = ast_util::def_id_of_def(cx.tcx.def_map.get(ex.id)); let did = ast_util::def_id_of_def(cx.tcx.def_map.get(ex.id));
let item_ty = ty::lookup_item_type(cx.tcx, did); let item_ty = ty::lookup_item_type(cx.tcx, did);
if has_trait_bounds(*item_ty.bounds) { if has_trait_bounds(*item_ty.bounds) {
cx.vtable_map.insert(ex.id, lookup_vtables(fcx, let vtbls = lookup_vtables(fcx, ex.span, item_ty.bounds,
ex.span, substs, false, is_early);
item_ty.bounds, if !is_early { cx.vtable_map.insert(ex.id, vtbls); }
substs,
false));
} }
} }
_ => () _ => ()
@ -260,11 +303,9 @@ fn resolve_expr(ex: @ast::expr, &&fcx: @fn_ctxt, v: visit::vt<@fn_ctxt>) {
_ => ex.callee_id _ => ex.callee_id
}; };
let substs = fcx.node_ty_substs(callee_id); let substs = fcx.node_ty_substs(callee_id);
cx.vtable_map.insert(callee_id, lookup_vtables(fcx, let vtbls = lookup_vtables(fcx, ex.span, bounds,
ex.span, &substs, false, is_early);
bounds, if !is_early { cx.vtable_map.insert(callee_id, vtbls); }
&substs,
false));
} }
} }
_ => () _ => ()
@ -280,18 +321,22 @@ fn resolve_expr(ex: @ast::expr, &&fcx: @fn_ctxt, v: visit::vt<@fn_ctxt>) {
passing in the source and target type passing in the source and target type
*/ */
let vtable = lookup_vtable(fcx, ex.span, fcx.expr_ty(src), let vtable = lookup_vtable(fcx, ex.span, fcx.expr_ty(src),
target_ty, true); target_ty, true, is_early);
/* /*
Map this expression to that vtable (that is: "ex has Map this expression to that vtable (that is: "ex has
vtable <vtable>") vtable <vtable>")
*/ */
cx.vtable_map.insert(ex.id, @~[vtable]); if !is_early { cx.vtable_map.insert(ex.id, @~[vtable]); }
} }
_ => () _ => ()
} }
} }
_ => () _ => ()
} }
}
fn resolve_expr(ex: @ast::expr, &&fcx: @fn_ctxt, v: visit::vt<@fn_ctxt>) {
early_resolve_expr(ex, fcx, false);
visit::visit_expr(ex, fcx, v); visit::visit_expr(ex, fcx, v);
} }

View file

@ -0,0 +1,17 @@
trait thing<A> {
fn foo() -> option<A>;
}
impl<A> int: thing<A> {
fn foo() -> option<A> { none }
}
fn foo_func<A, B: thing<A>>(x: B) -> option<A> { x.foo() }
fn main() {
for iter::eachi(some({a: 0})) |i, a| {
#debug["%u %d", i, a.a];
}
let _x: option<float> = foo_func(0);
}