support autoderef on method calls

This commit is contained in:
Niko Matsakis 2012-06-19 20:36:01 -07:00
parent 514e8ded2f
commit 773a640303
5 changed files with 239 additions and 168 deletions

View file

@ -68,7 +68,6 @@ type parameter).
import astconv::{ast_conv, ast_ty_to_ty};
import collect::{methods}; // ccx.to_ty()
import method::{methods}; // methods for method::lookup
import middle::ty::{tv_vid, vid};
import regionmanip::{replace_bound_regions_in_fn_ty, region_of};
import rscope::{anon_rscope, binding_rscope, empty_rscope, in_anon_rscope};
@ -905,15 +904,8 @@ fn check_expr_with_unifier(fcx: @fn_ctxt,
opname: str, args: [option<@ast::expr>])
-> option<(ty::t, bool)> {
let callee_id = ast_util::op_expr_callee_id(op_ex);
let lkup = method::lookup({fcx: fcx,
expr: op_ex,
self_expr: self_ex,
borrow_scope: op_ex.id,
node_id: callee_id,
m_name: @opname,
self_ty: self_t,
supplied_tps: [],
include_private: false});
let lkup = method::lookup(fcx, op_ex, self_ex, op_ex.id,
callee_id, @opname, self_t, [], false);
alt lkup.method() {
some(origin) {
let {fty: method_ty, bot: bot} = {
@ -1629,15 +1621,9 @@ fn check_expr_with_unifier(fcx: @fn_ctxt,
// encloses the method call
let borrow_scope = fcx.tcx().region_map.get(expr.id);
let lkup = method::lookup({fcx: fcx,
expr: expr,
self_expr: base,
borrow_scope: borrow_scope,
node_id: expr.id,
m_name: field,
self_ty: expr_t,
supplied_tps: tps,
include_private: is_self_ref});
let lkup = method::lookup(fcx, expr, base, borrow_scope,
expr.id, field, expr_t, tps,
is_self_ref);
alt lkup.method() {
some(entry) {
fcx.ccx.method_map.insert(id, entry);
@ -1686,15 +1672,8 @@ fn check_expr_with_unifier(fcx: @fn_ctxt,
let p_ty = fcx.expr_ty(p);
let lkup = method::lookup({fcx: fcx,
expr: p,
self_expr: p,
borrow_scope: expr.id,
node_id: alloc_id,
m_name: @"alloc",
self_ty: p_ty,
supplied_tps: [],
include_private: false});
let lkup = method::lookup(fcx, p, p, expr.id, alloc_id,
@"alloc", p_ty, [], false);
alt lkup.method() {
some(entry) {
fcx.ccx.method_map.insert(alloc_id, entry);

View file

@ -2,56 +2,162 @@
import syntax::ast_map;
import middle::typeck::infer::methods; // next_ty_vars
import dvec::{dvec, extensions};
enum lookup = {
fcx: @fn_ctxt,
expr: @ast::expr, // expr for a.b in a.b()
self_expr: @ast::expr, // a in a.b(...)
borrow_scope: ast::node_id, // if we have to borrow the expr, what scope?
node_id: ast::node_id, // node id of call (not always expr.id)
m_name: ast::ident, // b in a.b(...)
self_ty: ty::t, // type of a in a.b(...)
supplied_tps: [ty::t], // Xs in a.b::<Xs>(...)
include_private: bool
type candidate = {
self_ty: ty::t, // type of a in a.b()
self_substs: ty::substs, // values for any tvars def'd on the class
rcvr_ty: ty::t, // type of receiver in the method def
n_tps_m: uint, // number of tvars defined on the method
fty: ty::t, // type of the method
entry: method_map_entry
};
impl methods for lookup {
class lookup {
let fcx: @fn_ctxt;
let expr: @ast::expr;
let self_expr: @ast::expr;
let borrow_scope: ast::node_id;
let node_id: ast::node_id;
let m_name: ast::ident;
let mut self_ty: ty::t;
let mut derefs: uint;
let candidates: dvec<candidate>;
let supplied_tps: [ty::t];
let include_private: bool;
new(fcx: @fn_ctxt,
expr: @ast::expr, //expr for a.b in a.b()
self_expr: @ast::expr, //a in a.b(...)
borrow_scope: ast::node_id, //scope to borrow the expr for
node_id: ast::node_id, //node id where to store type of fn
m_name: ast::ident, //b in a.b(...)
self_ty: ty::t, //type of a in a.b(...)
supplied_tps: [ty::t], //Xs in a.b::<Xs>(...)
include_private: bool) {
self.fcx = fcx;
self.expr = expr;
self.self_expr = self_expr;
self.borrow_scope = borrow_scope;
self.node_id = node_id;
self.m_name = m_name;
self.self_ty = self_ty;
self.derefs = 0u;
self.candidates = dvec();
self.supplied_tps = supplied_tps;
self.include_private = include_private;
}
// Entrypoint:
fn method() -> option<method_map_entry> {
#debug["method lookup(m_name=%s, self_ty=%s)",
*self.m_name, self.fcx.infcx.ty_to_str(self.self_ty)];
// First, see whether this is an interface-bounded parameter
let pass1 = alt ty::get(self.self_ty).struct {
ty::ty_param(n, did) {
self.method_from_param(n, did)
}
ty::ty_iface(did, substs) {
self.method_from_iface(did, substs)
}
ty::ty_class(did, substs) {
self.method_from_class(did, substs)
}
_ {
none
}
};
loop {
// First, see whether this is an interface-bounded parameter
alt ty::get(self.self_ty).struct {
ty::ty_param(n, did) {
self.add_candidates_from_param(n, did);
}
ty::ty_iface(did, substs) {
self.add_candidates_from_iface(did, substs);
}
ty::ty_class(did, substs) {
self.add_candidates_from_class(did, substs);
}
_ { }
}
alt pass1 {
some(r) { some(r) }
none { self.method_from_scope() }
// if we found anything, stop now. otherwise continue to
// loop for impls in scope. Note: I don't love these
// semantics, but that's what we had so I am preserving
// it.
if self.candidates.len() > 0u {
break;
}
self.add_candidates_from_scope();
// if we found anything, stop before attempting auto-deref.
if self.candidates.len() > 0u {
break;
}
// check whether we can autoderef and if so loop around again.
alt ty::deref(self.tcx(), self.self_ty, false) {
none { break; }
some(mt) {
self.self_ty = mt.ty;
self.derefs += 1u;
}
}
}
if self.candidates.len() == 0u { ret none; }
if self.candidates.len() > 1u {
self.tcx().sess.span_err(
self.expr.span,
"multiple applicable methods in scope");
for self.candidates.eachi { |i, candidate|
alt candidate.entry.origin {
method_static(did) {
self.report_static_candidate(i, did);
}
method_param(p) {
self.report_param_candidate(i, p.iface_id);
}
method_iface(did, _) {
self.report_iface_candidate(i, did);
}
}
}
}
some(self.write_mty_from_candidate(self.candidates[0u]))
}
fn tcx() -> ty::ctxt { self.fcx.ccx.tcx }
fn method_from_param(n: uint, did: ast::def_id)
-> option<method_map_entry> {
fn report_static_candidate(idx: uint, did: ast::def_id) {
let span = if did.crate == ast::local_crate {
alt check self.tcx().items.get(did.node) {
ast_map::node_method(m, _, _) { m.span }
}
} else {
self.expr.span
};
self.tcx().sess.span_note(
span,
#fmt["candidate #%u is `%s`",
(idx+1u),
ty::item_path_str(self.tcx(), did)]);
}
fn report_param_candidate(idx: uint, did: ast::def_id) {
self.tcx().sess.span_note(
self.expr.span,
#fmt["candidate #%u derives from the bound `%s`",
(idx+1u),
ty::item_path_str(self.tcx(), did)]);
}
fn report_iface_candidate(idx: uint, did: ast::def_id) {
self.tcx().sess.span_note(
self.expr.span,
#fmt["candidate #%u derives from the type of the receiver, \
which is the iface `%s`",
(idx+1u),
ty::item_path_str(self.tcx(), did)]);
}
fn add_candidates_from_param(n: uint, did: ast::def_id) {
let tcx = self.tcx();
let mut iface_bnd_idx = 0u; // count only iface bounds
let bounds = tcx.ty_param_bounds.get(did.node);
let mut candidates = [];
for vec::each(*bounds) {|bound|
let (iid, bound_substs) = alt bound {
ty::bound_copy | ty::bound_send | ty::bound_const {
@ -81,41 +187,20 @@ impl methods for lookup {
// permitted).
let substs = {self_ty: some(self.self_ty)
with bound_substs};
candidates += [(substs, ifce_methods[pos],
iid, pos, n, iface_bnd_idx)];
self.add_candidates_from_m(
substs, ifce_methods[pos],
method_param({iface_id:iid,
method_num:pos,
param_num:n,
bound_num:iface_bnd_idx}));
}
}
}
if candidates.len() == 0u {
ret none;
}
if candidates.len() > 1u {
self.tcx().sess.span_err(
self.expr.span,
"multiple applicable methods in scope");
for candidates.eachi { |i, candidate|
let (_, _, iid, _, _, _) = candidate;
self.tcx().sess.span_note(
self.expr.span,
#fmt["candidate #%u derives from the bound `%s`",
(i+1u), ty::item_path_str(self.tcx(), iid)]);
}
}
let (substs, mty, iid, pos, n, iface_bnd_idx) = candidates[0u];
ret some(self.write_mty_from_m(
substs, mty, method_param({iface_id:iid,
method_num:pos,
param_num:n,
bound_num:iface_bnd_idx})));
}
fn method_from_iface(
did: ast::def_id, iface_substs: ty::substs)
-> option<method_map_entry> {
fn add_candidates_from_iface(did: ast::def_id, iface_substs: ty::substs) {
let ms = *ty::iface_methods(self.tcx(), did);
for ms.eachi {|i, m|
@ -143,15 +228,12 @@ impl methods for lookup {
let substs = {self_ty: some(self.self_ty)
with iface_substs};
ret some(self.write_mty_from_m(
substs, m, method_iface(did, i)));
self.add_candidates_from_m(
substs, m, method_iface(did, i));
}
ret none;
}
fn method_from_class(did: ast::def_id, class_substs: ty::substs)
-> option<method_map_entry> {
fn add_candidates_from_class(did: ast::def_id, class_substs: ty::substs) {
let ms = *ty::iface_methods(self.tcx(), did);
@ -169,12 +251,9 @@ impl methods for lookup {
let m_declared = ty::lookup_class_method_by_name(
self.tcx(), did, self.m_name, self.expr.span);
ret some(self.write_mty_from_m(
class_substs, m,
method_static(m_declared)));
self.add_candidates_from_m(
class_substs, m, method_static(m_declared));
}
ret none;
}
fn ty_from_did(did: ast::def_id) -> ty::t {
@ -202,11 +281,11 @@ impl methods for lookup {
*/
}
fn method_from_scope() -> option<method_map_entry> {
fn add_candidates_from_scope() {
let impls_vecs = self.fcx.ccx.impl_map.get(self.expr.id);
let mut added_any = false;
for list::each(impls_vecs) {|impls|
let mut results = [];
for vec::each(*impls) {|im|
// Check whether this impl has a method with the right name.
for im.methods.find({|m| m.ident == self.m_name}).each {|m|
@ -223,89 +302,72 @@ impl methods for lookup {
self.self_ty, impl_ty) {
result::err(_) { /* keep looking */ }
result::ok(_) {
results += [(impl_ty, impl_substs, m.n_tps, m.did)];
let fty = self.ty_from_did(m.did);
self.candidates.push(
{self_ty: self.self_ty,
self_substs: impl_substs,
rcvr_ty: impl_ty,
n_tps_m: m.n_tps,
fty: fty,
entry: {derefs: self.derefs,
origin: method_static(m.did)}});
added_any = true;
}
}
}
}
if results.len() >= 1u {
if results.len() > 1u {
self.tcx().sess.span_err(
self.expr.span,
"multiple applicable methods in scope");
// I would like to print out how each impl was imported,
// but I cannot for the life of me figure out how to
// annotate resolve to preserve this information.
for results.eachi { |i, result|
let (_, _, _, did) = result;
let span = if did.crate == ast::local_crate {
alt check self.tcx().items.get(did.node) {
ast_map::node_method(m, _, _) { m.span }
}
} else {
self.expr.span
};
self.tcx().sess.span_note(
span,
#fmt["candidate #%u is `%s`",
(i+1u),
ty::item_path_str(self.tcx(), did)]);
}
}
let (impl_ty, impl_substs, n_tps, did) = results[0];
alt self.fcx.mk_assignty(self.self_expr, self.borrow_scope,
self.self_ty, impl_ty) {
result::ok(_) {}
result::err(_) {
self.tcx().sess.span_bug(
self.expr.span,
#fmt["%s was assignable to %s but now is not?",
self.fcx.infcx.ty_to_str(self.self_ty),
self.fcx.infcx.ty_to_str(impl_ty)]);
}
}
let fty = self.ty_from_did(did);
ret some(self.write_mty_from_fty(
impl_substs, n_tps, fty,
method_static(did)));
}
// we want to find the innermost scope that has any
// matches and then ignore outer scopes
if added_any {ret;}
}
ret none;
}
fn write_mty_from_m(self_substs: ty::substs,
m: ty::method,
origin: method_origin) -> method_map_entry {
fn add_candidates_from_m(self_substs: ty::substs,
m: ty::method,
origin: method_origin) {
let tcx = self.fcx.ccx.tcx;
// a bit hokey, but the method unbound has a bare protocol, whereas
// a.b has a protocol like fn@() (perhaps eventually fn&()):
let fty = ty::mk_fn(tcx, {proto: ast::proto_box with m.fty});
ret self.write_mty_from_fty(self_substs, (*m.tps).len(),
fty, origin);
self.candidates.push(
{self_ty: self.self_ty,
self_substs: self_substs,
rcvr_ty: self.self_ty,
n_tps_m: (*m.tps).len(),
fty: fty,
entry: {derefs: self.derefs, origin: origin}});
}
fn write_mty_from_fty(self_substs: ty::substs,
n_tps_m: uint,
fty: ty::t,
origin: method_origin) -> method_map_entry {
fn write_mty_from_candidate(cand: candidate) -> method_map_entry {
let tcx = self.fcx.ccx.tcx;
#debug["write_mty_from_fty(n_tps_m=%u, fty=%s, origin=%?)",
n_tps_m,
self.fcx.infcx.ty_to_str(fty),
origin];
#debug["write_mty_from_candidate(n_tps_m=%u, fty=%s, entry=%?)",
cand.n_tps_m,
self.fcx.infcx.ty_to_str(cand.fty),
cand.entry];
// Here I will use the "c_" prefix to refer to the method's
// owner. You can read it as class, but it may also be an iface.
// Make the actual receiver type (cand.self_ty) assignable to the
// required receiver type (cand.rcvr_ty). If this method is not
// from an impl, this'll basically be a no-nop.
alt self.fcx.mk_assignty(self.self_expr, self.borrow_scope,
cand.self_ty, cand.rcvr_ty) {
result::ok(_) {}
result::err(_) {
self.tcx().sess.span_bug(
self.expr.span,
#fmt["%s was assignable to %s but now is not?",
self.fcx.infcx.ty_to_str(cand.self_ty),
self.fcx.infcx.ty_to_str(cand.rcvr_ty)]);
}
}
// Construct the full set of type parameters for the method,
// which is equal to the class tps + the method tps.
let n_tps_supplied = self.supplied_tps.len();
let n_tps_m = cand.n_tps_m;
let m_substs = {
if n_tps_supplied == 0u {
self.fcx.infcx.next_ty_vars(n_tps_m)
@ -325,12 +387,12 @@ impl methods for lookup {
}
};
let all_substs = {tps: self_substs.tps + m_substs
with self_substs};
let all_substs = {tps: cand.self_substs.tps + m_substs
with cand.self_substs};
self.fcx.write_ty_substs(self.node_id, fty, all_substs);
self.fcx.write_ty_substs(self.node_id, cand.fty, all_substs);
ret {derefs:0u, origin:origin};
ret cand.entry;
}
}

View file

@ -0,0 +1,10 @@
impl methods for uint {
fn double() -> uint { self * 2u }
}
enum foo = uint;
fn main() {
let x = foo(3u);
assert x.double() == 6u;
}

View file

@ -0,0 +1,12 @@
impl methods for uint {
fn double() -> uint { self }
}
impl methods for @uint {
fn double() -> uint { *self * 2u }
}
fn main() {
let x = @3u;
assert x.double() == 6u;
}

View file

@ -0,0 +1,8 @@
impl methods for uint {
fn double() -> uint { self * 2u }
}
fn main() {
let x = @3u;
assert x.double() == 6u;
}