about summary refs log tree commit diff
diff options
context:
space:
mode:
authorNiko Matsakis <niko@alum.mit.edu>2012-06-19 20:36:01 -0700
committerNiko Matsakis <niko@alum.mit.edu>2012-06-19 20:46:48 -0700
commit773a640303db0995868f8739857b0df16afa1969 (patch)
treeb6cef607db972ad2e450cd400ea9be4bb473b94a
parent514e8ded2fc4666365d99d959626130bab6e6b6e (diff)
downloadrust-773a640303db0995868f8739857b0df16afa1969.tar.gz
rust-773a640303db0995868f8739857b0df16afa1969.zip
support autoderef on method calls
-rw-r--r--src/rustc/middle/typeck/check.rs35
-rw-r--r--src/rustc/middle/typeck/check/method.rs346
-rw-r--r--src/test/run-pass/autoderef-method-newtype.rs10
-rw-r--r--src/test/run-pass/autoderef-method-priority.rs12
-rw-r--r--src/test/run-pass/autoderef-method.rs8
5 files changed, 241 insertions, 170 deletions
diff --git a/src/rustc/middle/typeck/check.rs b/src/rustc/middle/typeck/check.rs
index 14b7e847a2a..87d51b33420 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 143a477302b..4af075e8b79 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
-
-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
+import dvec::{dvec, extensions};
+
+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);
+              }
+              _ { }
+            }
+
+            // 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;
+            }
 
-        alt pass1 {
-          some(r) { some(r) }
-          none { self.method_from_scope() }
+            // 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)];
-              }
-            }
-        }
-
-        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)]);
+                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}));
+              }
             }
         }
 
-        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];
-
-        // 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.
+        #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];
+
+        // 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 00000000000..6d1d4428348
--- /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 00000000000..4f9dd19abbd
--- /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 00000000000..7ea03d0d7e4
--- /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;
+}