diff --git a/src/rustc/middle/typeck/check.rs b/src/rustc/middle/typeck/check.rs index 14b7e847a2a3..87d51b33420a 100644 --- a/src/rustc/middle/typeck/check.rs +++ b/src/rustc/middle/typeck/check.rs @@ -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); diff --git a/src/rustc/middle/typeck/check/method.rs b/src/rustc/middle/typeck/check/method.rs index 143a477302bd..4af075e8b794 100644 --- a/src/rustc/middle/typeck/check/method.rs +++ b/src/rustc/middle/typeck/check/method.rs @@ -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::(...) - 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; + 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::(...) + 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 { #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 { + 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 { + 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 { + 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 { + 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; } } diff --git a/src/test/run-pass/autoderef-method-newtype.rs b/src/test/run-pass/autoderef-method-newtype.rs new file mode 100644 index 000000000000..6d1d44283483 --- /dev/null +++ b/src/test/run-pass/autoderef-method-newtype.rs @@ -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; +} diff --git a/src/test/run-pass/autoderef-method-priority.rs b/src/test/run-pass/autoderef-method-priority.rs new file mode 100644 index 000000000000..4f9dd19abbd8 --- /dev/null +++ b/src/test/run-pass/autoderef-method-priority.rs @@ -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; +} diff --git a/src/test/run-pass/autoderef-method.rs b/src/test/run-pass/autoderef-method.rs new file mode 100644 index 000000000000..7ea03d0d7e4e --- /dev/null +++ b/src/test/run-pass/autoderef-method.rs @@ -0,0 +1,8 @@ +impl methods for uint { + fn double() -> uint { self * 2u } +} + +fn main() { + let x = @3u; + assert x.double() == 6u; +}