about summary refs log tree commit diff
path: root/src/comp
diff options
context:
space:
mode:
authorMarijn Haverbeke <marijnh@gmail.com>2012-01-26 12:26:14 +0100
committerMarijn Haverbeke <marijnh@gmail.com>2012-01-26 14:25:06 +0100
commit87b064b249657c8e65079d01beb77409f69d49cd (patch)
treececb525f7ddf7312521d1a65e93ea8531f23426e /src/comp
parent1792d9ec96d680cb3ec257bfef84baffea352d80 (diff)
downloadrust-87b064b249657c8e65079d01beb77409f69d49cd.tar.gz
rust-87b064b249657c8e65079d01beb77409f69d49cd.zip
First stab at operator overloading
When no built-in interpretation is found for one of the operators
mentioned below, the typechecker will try to turn it into a method
call with the name written next to it. For binary operators, the
method will be called on the LHS with the RHS as only parameter.

Binary:

    +   op_add
    -   op_sub
    *   op_mul
    /   op_div
    %   op_rem
    &   op_and
    |   op_or
    ^   op_xor
    <<  op_shift_left
    >>  op_shift_right
    >>> op_ashift_right

Unary:

    -   op_neg
    !   op_not

Overloading of the indexing ([]) operator isn't finished yet.

Issue #1520
Diffstat (limited to 'src/comp')
-rw-r--r--src/comp/driver/driver.rs4
-rw-r--r--src/comp/middle/check_const.rs15
-rw-r--r--src/comp/middle/resolve.rs6
-rw-r--r--src/comp/middle/trans.rs90
-rw-r--r--src/comp/middle/trans_impl.rs37
-rw-r--r--src/comp/middle/typeck.rs231
-rw-r--r--src/comp/syntax/ast_util.rs5
-rw-r--r--src/comp/syntax/parse/parser.rs4
8 files changed, 251 insertions, 141 deletions
diff --git a/src/comp/driver/driver.rs b/src/comp/driver/driver.rs
index e7eafae197a..41130766035 100644
--- a/src/comp/driver/driver.rs
+++ b/src/comp/driver/driver.rs
@@ -162,12 +162,12 @@ fn compile_upto(sess: session, cfg: ast::crate_cfg,
     let freevars =
         time(time_passes, "freevar finding",
              bind freevars::annotate_freevars(def_map, crate));
-    time(time_passes, "const checking",
-         bind middle::check_const::check_crate(sess, crate));
     let ty_cx = ty::mk_ctxt(sess, def_map, ast_map, freevars);
     let (method_map, dict_map) =
         time(time_passes, "typechecking",
              bind typeck::check_crate(ty_cx, impl_map, crate));
+    time(time_passes, "const checking",
+         bind middle::check_const::check_crate(sess, crate, method_map));
 
     if upto == cu_typeck { ret {crate: crate, tcx: some(ty_cx)}; }
 
diff --git a/src/comp/middle/check_const.rs b/src/comp/middle/check_const.rs
index 78eec533e3f..eec014a47f7 100644
--- a/src/comp/middle/check_const.rs
+++ b/src/comp/middle/check_const.rs
@@ -2,11 +2,11 @@ import syntax::ast::*;
 import syntax::{visit, ast_util};
 import driver::session::session;
 
-fn check_crate(sess: session, crate: @crate) {
+fn check_crate(sess: session, crate: @crate, method_map: typeck::method_map) {
     visit::visit_crate(*crate, false, visit::mk_vt(@{
         visit_item: check_item,
         visit_pat: check_pat,
-        visit_expr: bind check_expr(sess, _, _, _)
+        visit_expr: bind check_expr(sess, method_map, _, _, _)
         with *visit::default_visitor()
     }));
     sess.abort_if_errors();
@@ -41,7 +41,8 @@ fn check_pat(p: @pat, &&_is_const: bool, v: visit::vt<bool>) {
     }
 }
 
-fn check_expr(sess: session, e: @expr, &&is_const: bool, v: visit::vt<bool>) {
+fn check_expr(sess: session, method_map: typeck::method_map, e: @expr,
+              &&is_const: bool, v: visit::vt<bool>) {
     if is_const {
         alt e.node {
           expr_unary(box(_), _) | expr_unary(uniq(_), _) |
@@ -54,7 +55,13 @@ fn check_expr(sess: session, e: @expr, &&is_const: bool, v: visit::vt<bool>) {
             sess.span_err(e.span,
                           "string constants are not supported");
           }
-          expr_lit(_) | expr_binary(_, _, _) | expr_unary(_, _) {}
+          expr_binary(_, _, _) | expr_unary(_, _) {
+            if method_map.contains_key(e.id) {
+                sess.span_err(e.span, "user-defined operators are not \
+                                       allowed in constant expressions");
+            }
+          }
+          expr_lit(_) {}
           _ {
             sess.span_err(e.span,
                           "constant contains unimplemented expression type");
diff --git a/src/comp/middle/resolve.rs b/src/comp/middle/resolve.rs
index 0781f2ef1a1..8c3375796f8 100644
--- a/src/comp/middle/resolve.rs
+++ b/src/comp/middle/resolve.rs
@@ -1983,9 +1983,9 @@ fn visit_mod_with_impl_scope(e: @env, m: ast::_mod, s: span, sc: iscopes,
 
 fn resolve_impl_in_expr(e: @env, x: @ast::expr, sc: iscopes, v: vt<iscopes>) {
     alt x.node {
-      ast::expr_field(_, _, _) | ast::expr_path(_) | ast::expr_cast(_, _) {
-        e.impl_map.insert(x.id, sc);
-      }
+      ast::expr_field(_, _, _) | ast::expr_path(_) | ast::expr_cast(_, _) |
+      ast::expr_binary(_, _, _) | ast::expr_unary(_, _) |
+      ast::expr_assign_op(_, _, _) { e.impl_map.insert(x.id, sc); }
       _ {}
     }
     visit::visit_expr(x, sc, v);
diff --git a/src/comp/middle/trans.rs b/src/comp/middle/trans.rs
index d3a81412457..045cbfdc28d 100644
--- a/src/comp/middle/trans.rs
+++ b/src/comp/middle/trans.rs
@@ -2072,7 +2072,19 @@ fn node_type(cx: @crate_ctxt, sp: span, id: ast::node_id) -> TypeRef {
 }
 
 fn trans_unary(bcx: @block_ctxt, op: ast::unop, e: @ast::expr,
-               id: ast::node_id, dest: dest) -> @block_ctxt {
+               un_expr: @ast::expr, dest: dest) -> @block_ctxt {
+    // Check for user-defined method call
+    alt bcx_ccx(bcx).method_map.find(un_expr.id) {
+      some(origin) {
+        let callee_id = ast_util::op_expr_callee_id(un_expr);
+        let fty = ty::node_id_to_monotype(bcx_tcx(bcx), callee_id);
+        ret trans_call_inner(bcx, fty, {|bcx|
+            trans_impl::trans_method_callee(bcx, callee_id, e, origin)
+        }, [], un_expr.id, dest);
+      }
+      _ {}
+    }
+
     if dest == ignore { ret trans_expr(bcx, e, ignore); }
     let e_ty = ty::expr_ty(bcx_tcx(bcx), e);
     alt op {
@@ -2104,7 +2116,7 @@ fn trans_unary(bcx: @block_ctxt, op: ast::unop, e: @ast::expr,
         ret store_in_dest(bcx, box, dest);
       }
       ast::uniq(_) {
-        ret trans_uniq::trans_uniq(bcx, e, id, dest);
+        ret trans_uniq::trans_uniq(bcx, e, un_expr.id, dest);
       }
       ast::deref {
         bcx_ccx(bcx).sess.bug("deref expressions should have been \
@@ -2193,12 +2205,26 @@ fn trans_eager_binop(cx: @block_ctxt, op: ast::binop, lhs: ValueRef,
     ret store_in_dest(cx, val, dest);
 }
 
-fn trans_assign_op(bcx: @block_ctxt, op: ast::binop, dst: @ast::expr,
-                   src: @ast::expr) -> @block_ctxt {
+fn trans_assign_op(bcx: @block_ctxt, ex: @ast::expr, op: ast::binop,
+                   dst: @ast::expr, src: @ast::expr) -> @block_ctxt {
     let tcx = bcx_tcx(bcx);
     let t = ty::expr_ty(tcx, src);
     let lhs_res = trans_lval(bcx, dst);
     assert (lhs_res.kind == owned);
+
+    // A user-defined operator method
+    alt bcx_ccx(bcx).method_map.find(ex.id) {
+      some(origin) {
+        let callee_id = ast_util::op_expr_callee_id(ex);
+        let fty = ty::node_id_to_monotype(bcx_tcx(bcx), callee_id);
+        ret trans_call_inner(bcx, fty, {|bcx|
+            // FIXME provide the already-computed address, not the expr
+            trans_impl::trans_method_callee(bcx, callee_id, src, origin)
+        }, [dst], ex.id, save_in(lhs_res.val));
+      }
+      _ {}
+    }
+
     // Special case for `+= [x]`
     alt ty::struct(tcx, t) {
       ty::ty_vec(_) {
@@ -2305,20 +2331,34 @@ fn trans_lazy_binop(bcx: @block_ctxt, op: ast::binop, a: @ast::expr,
     ret store_in_dest(join_cx, phi, dest);
 }
 
-fn trans_binary(cx: @block_ctxt, op: ast::binop, a: @ast::expr, b: @ast::expr,
-                dest: dest) -> @block_ctxt {
+
+
+fn trans_binary(bcx: @block_ctxt, op: ast::binop, a: @ast::expr,
+                b: @ast::expr, dest: dest, ex: @ast::expr) -> @block_ctxt {
+    // User-defined operators
+    alt bcx_ccx(bcx).method_map.find(ex.id) {
+      some(origin) {
+        let callee_id = ast_util::op_expr_callee_id(ex);
+        let fty = ty::node_id_to_monotype(bcx_tcx(bcx), callee_id);
+        ret trans_call_inner(bcx, fty, {|bcx|
+            trans_impl::trans_method_callee(bcx, callee_id, a, origin)
+        }, [b], ex.id, dest);
+      }
+      _ {}
+    }
+
     // First couple cases are lazy:
     alt op {
       ast::and | ast::or {
-        ret trans_lazy_binop(cx, op, a, b, dest);
+        ret trans_lazy_binop(bcx, op, a, b, dest);
       }
       _ {
         // Remaining cases are eager:
-        let lhs = trans_temp_expr(cx, a);
+        let lhs = trans_temp_expr(bcx, a);
         let rhs = trans_temp_expr(lhs.bcx, b);
         ret trans_eager_binop(rhs.bcx, op, lhs.val,
-                              ty::expr_ty(bcx_tcx(cx), a), rhs.val,
-                              ty::expr_ty(bcx_tcx(cx), b), dest);
+                              ty::expr_ty(bcx_tcx(bcx), a), rhs.val,
+                              ty::expr_ty(bcx_tcx(bcx), b), dest);
       }
     }
 }
@@ -2746,15 +2786,8 @@ fn trans_callee(bcx: @block_ctxt, e: @ast::expr) -> lval_maybe_callee {
         // Lval means this is a record field, so not a method
         if !expr_is_lval(bcx, e) {
             alt bcx_ccx(bcx).method_map.find(e.id) {
-              some(typeck::method_static(did)) { // An impl method
-                ret trans_impl::trans_static_callee(bcx, e, base, did);
-              }
-              some(typeck::method_param(iid, off, p, b)) {
-                ret trans_impl::trans_param_callee(
-                    bcx, e, base, iid, off, p, b);
-              }
-              some(typeck::method_iface(off)) {
-                ret trans_impl::trans_iface_callee(bcx, e, base, off);
+              some(origin) { // An impl method
+                ret trans_impl::trans_method_callee(bcx, e.id, base, origin);
               }
             }
         }
@@ -3132,15 +3165,22 @@ fn trans_args(cx: @block_ctxt, llenv: ValueRef,
 fn trans_call(in_cx: @block_ctxt, f: @ast::expr,
               args: [@ast::expr], id: ast::node_id, dest: dest)
     -> @block_ctxt {
+    trans_call_inner(in_cx, ty::expr_ty(bcx_tcx(in_cx), f),
+                     {|cx| trans_callee(cx, f)}, args, id, dest)
+}
+
+fn trans_call_inner(in_cx: @block_ctxt, fn_expr_ty: ty::t,
+                    get_callee: fn(@block_ctxt) -> lval_maybe_callee,
+                    args: [@ast::expr], id: ast::node_id, dest: dest)
+    -> @block_ctxt {
     // NB: 'f' isn't necessarily a function; it might be an entire self-call
     // expression because of the hack that allows us to process self-calls
     // with trans_call.
     let tcx = bcx_tcx(in_cx);
-    let fn_expr_ty = ty::expr_ty(tcx, f);
 
     let cx = new_scope_block_ctxt(in_cx, "call");
     Br(in_cx, cx.llbb);
-    let f_res = trans_callee(cx, f);
+    let f_res = get_callee(cx);
     let bcx = f_res.bcx;
 
     let faddr = f_res.val;
@@ -3478,10 +3518,12 @@ fn trans_expr(bcx: @block_ctxt, e: @ast::expr, dest: dest) -> @block_ctxt {
       ast::expr_tup(args) { ret trans_tup(bcx, args, e.id, dest); }
       ast::expr_lit(lit) { ret trans_lit(bcx, *lit, dest); }
       ast::expr_vec(args, _) { ret tvec::trans_vec(bcx, args, e.id, dest); }
-      ast::expr_binary(op, x, y) { ret trans_binary(bcx, op, x, y, dest); }
+      ast::expr_binary(op, x, y) {
+        ret trans_binary(bcx, op, x, y, dest, e);
+      }
       ast::expr_unary(op, x) {
         assert op != ast::deref; // lvals are handled above
-        ret trans_unary(bcx, op, x, e.id, dest);
+        ret trans_unary(bcx, op, x, e, dest);
       }
       ast::expr_fn(proto, decl, body, cap_clause) {
         ret trans_closure::trans_expr_fn(
@@ -3620,7 +3662,7 @@ fn trans_expr(bcx: @block_ctxt, e: @ast::expr, dest: dest) -> @block_ctxt {
       }
       ast::expr_assign_op(op, dst, src) {
         assert dest == ignore;
-        ret trans_assign_op(bcx, op, dst, src);
+        ret trans_assign_op(bcx, e, op, dst, src);
       }
     }
 }
diff --git a/src/comp/middle/trans_impl.rs b/src/comp/middle/trans_impl.rs
index ff4a8db24a7..b454ca729bb 100644
--- a/src/comp/middle/trans_impl.rs
+++ b/src/comp/middle/trans_impl.rs
@@ -62,11 +62,28 @@ fn trans_self_arg(bcx: @block_ctxt, base: @ast::expr) -> result {
     rslt(bcx, PointerCast(bcx, val, T_opaque_cbox_ptr(bcx_ccx(bcx))))
 }
 
+fn trans_method_callee(bcx: @block_ctxt, callee_id: ast::node_id,
+                       self: @ast::expr, origin: typeck::method_origin)
+    -> lval_maybe_callee {
+    alt origin {
+      typeck::method_static(did) {
+        trans_static_callee(bcx, callee_id, self, did)
+      }
+      typeck::method_param(iid, off, p, b) {
+        trans_param_callee(bcx, callee_id, self, iid, off, p, b)
+      }
+      typeck::method_iface(off) {
+        trans_iface_callee(bcx, callee_id, self, off)
+      }
+    }
+}
+
 // Method callee where the method is statically known
-fn trans_static_callee(bcx: @block_ctxt, e: @ast::expr, base: @ast::expr,
-                       did: ast::def_id) -> lval_maybe_callee {
+fn trans_static_callee(bcx: @block_ctxt, callee_id: ast::node_id,
+                       base: @ast::expr, did: ast::def_id)
+    -> lval_maybe_callee {
     let {bcx, val} = trans_self_arg(bcx, base);
-    {env: self_env(val) with lval_static_fn(bcx, did, e.id)}
+    {env: self_env(val) with lval_static_fn(bcx, did, callee_id)}
 }
 
 fn wrapper_fn_ty(ccx: @crate_ctxt, dict_ty: TypeRef, m: ty::method)
@@ -79,7 +96,7 @@ fn wrapper_fn_ty(ccx: @crate_ctxt, dict_ty: TypeRef, m: ty::method)
 }
 
 fn trans_vtable_callee(bcx: @block_ctxt, self: ValueRef, dict: ValueRef,
-                       fld_expr: @ast::expr, iface_id: ast::def_id,
+                       callee_id: ast::node_id, iface_id: ast::def_id,
                        n_method: uint) -> lval_maybe_callee {
     let bcx = bcx, ccx = bcx_ccx(bcx), tcx = ccx.tcx;
     let method = ty::iface_methods(tcx, iface_id)[n_method];
@@ -90,7 +107,7 @@ fn trans_vtable_callee(bcx: @block_ctxt, self: ValueRef, dict: ValueRef,
     let generic = none;
     if vec::len(*method.tps) > 0u || ty::type_contains_params(tcx, fty) {
         let tydescs = [], tis = [];
-        let tptys = ty::node_id_to_type_params(tcx, fld_expr.id);
+        let tptys = ty::node_id_to_type_params(tcx, callee_id);
         for t in vec::tail_n(tptys, vec::len(tptys) - vec::len(*method.tps)) {
             let ti = none;
             let td = get_tydesc(bcx, t, true, ti).result;
@@ -102,7 +119,7 @@ fn trans_vtable_callee(bcx: @block_ctxt, self: ValueRef, dict: ValueRef,
                         static_tis: tis,
                         tydescs: tydescs,
                         param_bounds: method.tps,
-                        origins: bcx_ccx(bcx).dict_map.find(fld_expr.id)});
+                        origins: bcx_ccx(bcx).dict_map.find(callee_id)});
     }
     {bcx: bcx, val: mptr, kind: owned,
      env: dict_env(dict, self),
@@ -110,16 +127,16 @@ fn trans_vtable_callee(bcx: @block_ctxt, self: ValueRef, dict: ValueRef,
 }
 
 // Method callee where the dict comes from a type param
-fn trans_param_callee(bcx: @block_ctxt, fld_expr: @ast::expr,
+fn trans_param_callee(bcx: @block_ctxt, callee_id: ast::node_id,
                       base: @ast::expr, iface_id: ast::def_id, n_method: uint,
                       n_param: uint, n_bound: uint) -> lval_maybe_callee {
     let {bcx, val} = trans_self_arg(bcx, base);
     let dict = option::get(bcx.fcx.lltyparams[n_param].dicts)[n_bound];
-    trans_vtable_callee(bcx, val, dict, fld_expr, iface_id, n_method)
+    trans_vtable_callee(bcx, val, dict, callee_id, iface_id, n_method)
 }
 
 // Method callee where the dict comes from a boxed iface
-fn trans_iface_callee(bcx: @block_ctxt, fld_expr: @ast::expr,
+fn trans_iface_callee(bcx: @block_ctxt, callee_id: ast::node_id,
                       base: @ast::expr, n_method: uint)
     -> lval_maybe_callee {
     let tcx = bcx_tcx(bcx);
@@ -133,7 +150,7 @@ fn trans_iface_callee(bcx: @block_ctxt, fld_expr: @ast::expr,
     let iface_id = alt ty::struct(tcx, ty::expr_ty(tcx, base)) {
         ty::ty_iface(did, _) { did }
     };
-    trans_vtable_callee(bcx, self, dict, fld_expr, iface_id, n_method)
+    trans_vtable_callee(bcx, self, dict, callee_id, iface_id, n_method)
 }
 
 fn llfn_arg_tys(ft: TypeRef) -> {inputs: [TypeRef], output: TypeRef} {
diff --git a/src/comp/middle/typeck.rs b/src/comp/middle/typeck.rs
index 2327d79008a..6ae92ee6538 100644
--- a/src/comp/middle/typeck.rs
+++ b/src/comp/middle/typeck.rs
@@ -429,14 +429,14 @@ fn ty_of_native_item(tcx: ty::ctxt, mode: mode, it: @ast::native_item)
     alt it.node {
       ast::native_item_fn(fn_decl, params) {
         ret ty_of_native_fn_decl(tcx, mode, fn_decl, params,
-                                 ast_util::local_def(it.id));
+                                 local_def(it.id));
       }
       ast::native_item_ty {
         alt tcx.tcache.find(local_def(it.id)) {
           some(tpt) { ret tpt; }
           none { }
         }
-        let t = ty::mk_native(tcx, ast_util::local_def(it.id));
+        let t = ty::mk_native(tcx, local_def(it.id));
         let t = ty::mk_named(tcx, t, @it.ident);
         let tpt = {bounds: @[], ty: t};
         tcx.tcache.insert(local_def(it.id), tpt);
@@ -1613,45 +1613,30 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, expr: @ast::expr, unify: unifier,
 
     // A generic function to factor out common logic from call and bind
     // expressions.
-    fn check_call_or_bind(fcx: @fn_ctxt, sp: span, f: @ast::expr,
+    fn check_call_or_bind(fcx: @fn_ctxt, sp: span, fty: ty::t,
                           args: [option::t<@ast::expr>]) -> bool {
-        // Check the function.
-        let bot = check_expr(fcx, f);
-
-        // Get the function type.
-        let fty = expr_ty(fcx.ccx.tcx, f);
-
         let sty = structure_of(fcx, sp, fty);
-
         // Grab the argument types
-        let arg_tys =
-            alt sty {
-              ty::ty_fn({inputs: arg_tys, _}) {
-                arg_tys
-              }
-              _ {
-                fcx.ccx.tcx.sess.span_fatal(f.span,
-                                            "mismatched types: \
-                     expected function or native \
-                     function but found "
-                                                + ty_to_str(fcx.ccx.tcx, fty))
-              }
-            };
+        let arg_tys = alt sty {
+          ty::ty_fn({inputs: arg_tys, _}) { arg_tys }
+          _ {
+            fcx.ccx.tcx.sess.span_fatal(sp, "mismatched types: \
+                                             expected function or native \
+                                             function but found "
+                                        + ty_to_str(fcx.ccx.tcx, fty))
+          }
+        };
 
         // Check that the correct number of arguments were supplied.
         let expected_arg_count = vec::len(arg_tys);
         let supplied_arg_count = vec::len(args);
         if expected_arg_count != supplied_arg_count {
-            fcx.ccx.tcx.sess.span_err(sp,
-                                      #fmt["this function takes %u \
-                      parameter%s but %u parameter%s supplied",
-                                           expected_arg_count,
-                                           if expected_arg_count == 1u {
-                                               ""
-                                           } else { "s" }, supplied_arg_count,
-                                           if supplied_arg_count == 1u {
-                                               " was"
-                                           } else { "s were" }]);
+            fcx.ccx.tcx.sess.span_err(
+                sp, #fmt["this function takes %u parameter%s but %u \
+                          parameter%s supplied", expected_arg_count,
+                         expected_arg_count == 1u ? "" : "s",
+                         supplied_arg_count,
+                         supplied_arg_count == 1u ? " was" : "s were"]);
             // HACK: build an arguments list with dummy arguments to
             // check against
             let dummy = {mode: ast::by_ref, ty: ty::mk_bot(fcx.ccx.tcx)};
@@ -1665,33 +1650,27 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, expr: @ast::expr, unify: unifier,
         // of arguments when we typecheck the functions. This isn't really the
         // right way to do this.
         let check_args = fn@(check_blocks: bool) -> bool {
-                let i = 0u;
-                let bot = false;
-                for a_opt: option::t<@ast::expr> in args {
-                    alt a_opt {
-                      some(a) {
-                        let is_block =
-                            alt a.node {
-                              ast::expr_fn_block(_, _) { true }
-                              _ { false }
-                            };
-                        if is_block == check_blocks {
-                            bot |=
-                                check_expr_with_unifier(fcx, a,
-                                                        demand::simple,
-                                                        arg_tys[i].ty);
-                        }
-                      }
-                      none { }
+            let i = 0u;
+            let bot = false;
+            for a_opt in args {
+                alt a_opt {
+                  some(a) {
+                    let is_block = alt a.node {
+                      ast::expr_fn_block(_, _) { true }
+                      _ { false }
+                    };
+                    if is_block == check_blocks {
+                        bot |= check_expr_with_unifier(
+                            fcx, a, demand::simple, arg_tys[i].ty);
                     }
-                    i += 1u;
+                  }
+                  none { }
                 }
-                ret bot;
-            };
-        bot |= check_args(false);
-        bot |= check_args(true);
-
-        ret bot;
+                i += 1u;
+            }
+            ret bot;
+        };
+        check_args(false) | check_args(true)
     }
 
     // A generic function for checking assignment expressions
@@ -1711,8 +1690,9 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, expr: @ast::expr, unify: unifier,
             args_opt_0 += [some::<@ast::expr>(arg)];
         }
 
+        let bot = check_expr(fcx, f);
         // Call the generic checker.
-        ret check_call_or_bind(fcx, sp, f, args_opt_0);
+        bot | check_call_or_bind(fcx, sp, expr_ty(fcx.ccx.tcx, f), args_opt_0)
     }
 
     // A generic function for doing all of the checking for call expressions
@@ -1784,18 +1764,76 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, expr: @ast::expr, unify: unifier,
         ret if_bot;
     }
 
-    // Checks the compatibility
-    fn check_binop_type_compat(fcx: @fn_ctxt, span: span, ty: ty::t,
-                               binop: ast::binop) {
-        let resolved_t = resolve_type_vars_if_possible(fcx, ty);
-        if !ty::is_binopable(fcx.ccx.tcx, resolved_t, binop) {
-            let binopstr = ast_util::binop_to_str(binop);
-            let t_str = ty_to_str(fcx.ccx.tcx, resolved_t);
-            let errmsg =
-                "binary operation " + binopstr +
-                    " cannot be applied to type `" + t_str + "`";
-            fcx.ccx.tcx.sess.span_err(span, errmsg);
+    fn binop_method(op: ast::binop) -> option::t<str> {
+        alt op {
+          ast::add { some("op_add") }
+          ast::subtract { some("op_sub") }
+          ast::mul { some("op_mul") }
+          ast::div { some("op_div") }
+          ast::rem { some("op_rem") }
+          ast::bitxor { some("op_xor") }
+          ast::bitand { some("op_and") }
+          ast::bitor { some("op_or") }
+          ast::lsl { some("op_shift_left") }
+          ast::lsr { some("op_shift_right") }
+          ast::asr { some("op_ashift_right") }
+          _ { none }
+        }
+    }
+    fn check_binop(fcx: @fn_ctxt, ex: @ast::expr, ty: ty::t,
+                   op: ast::binop, rhs: @ast::expr) -> ty::t {
+        let resolved_t = structurally_resolved_type(fcx, ex.span, ty);
+        let tcx = fcx.ccx.tcx;
+        if ty::is_binopable(tcx, resolved_t, op) {
+            ret alt op {
+              ast::eq | ast::lt | ast::le | ast::ne | ast::ge |
+              ast::gt { ty::mk_bool(tcx) }
+              _ { resolved_t }
+            };
+        }
+
+        let isc = fcx.ccx.impl_map.get(ex.id);
+        alt binop_method(op) {
+          some(name) {
+            alt lookup_method(fcx, isc, name, resolved_t, ex.span) {
+              some({method_ty, n_tps: 0u, substs, origin}) {
+                let callee_id = ast_util::op_expr_callee_id(ex);
+                write::ty_fixup(fcx, callee_id, {substs: some(substs),
+                                                 ty: method_ty});
+                check_call_or_bind(fcx, ex.span, method_ty, [some(rhs)]);
+                fcx.ccx.method_map.insert(ex.id, origin);
+                ret ty::ty_fn_ret(tcx, method_ty);
+              }
+              _ {}
+            }
+          }
+          none {}
+        }
+        tcx.sess.span_err(
+            ex.span, "binary operation " + ast_util::binop_to_str(op) +
+            " cannot be applied to type `" + ty_to_str(tcx, resolved_t) +
+            "`");
+        resolved_t
+    }
+    fn check_user_unop(fcx: @fn_ctxt, op_str: str, mname: str,
+                       ex: @ast::expr, rhs_t: ty::t) -> ty::t {
+        let isc = fcx.ccx.impl_map.get(ex.id);
+        let tcx = fcx.ccx.tcx;
+        alt lookup_method(fcx, isc, mname, rhs_t, ex.span) {
+          some({method_ty, n_tps: 0u, substs, origin}) {
+            let callee_id = ast_util::op_expr_callee_id(ex);
+            write::ty_fixup(fcx, callee_id, {substs: some(substs),
+                                             ty: method_ty});
+            check_call_or_bind(fcx, ex.span, method_ty, []);
+            fcx.ccx.method_map.insert(ex.id, origin);
+            ret ty::ty_fn_ret(tcx, method_ty);
+          }
+          _ {}
         }
+        tcx.sess.span_err(
+            ex.span, #fmt["can not apply unary operator `%s` to type `%s`",
+                          op_str, ty_to_str(tcx, rhs_t)]);
+        rhs_t
     }
 
     let tcx = fcx.ccx.tcx;
@@ -1813,14 +1851,15 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, expr: @ast::expr, unify: unifier,
         let rhs_bot = check_expr_with(fcx, rhs, lhs_t);
         if !ast_util::lazy_binop(binop) { bot |= rhs_bot; }
 
-        check_binop_type_compat(fcx, expr.span, lhs_t, binop);
-
-        let t = alt binop {
-          ast::eq | ast::lt | ast::le | ast::ne | ast::ge |
-          ast::gt { ty::mk_bool(tcx) }
-          _ { lhs_t }
-        };
-        write::ty_only_fixup(fcx, id, t);
+        let result = check_binop(fcx, expr, lhs_t, binop, rhs);
+        write::ty_only_fixup(fcx, id, result);
+      }
+      ast::expr_assign_op(op, lhs, rhs) {
+        require_impure(tcx.sess, fcx.purity, expr.span);
+        bot = check_assignment(fcx, expr.span, lhs, rhs, id);
+        let lhs_t = ty::expr_ty(tcx, lhs);
+        let result = check_binop(fcx, expr, lhs_t, op, rhs);
+        demand::simple(fcx, expr.span, result, lhs_t);
       }
       ast::expr_unary(unop, oper) {
         bot = check_expr(fcx, oper);
@@ -1860,22 +1899,17 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, expr: @ast::expr, unify: unifier,
             }
           }
           ast::not {
-            if !type_is_integral(fcx, oper.span, oper_t) &&
-                   structure_of(fcx, oper.span, oper_t) != ty::ty_bool {
-                tcx.sess.span_err(expr.span,
-                                  #fmt["mismatched types: expected `bool` \
-                          or `integer` but found `%s`",
-                                       ty_to_str(tcx, oper_t)]);
+            oper_t = structurally_resolved_type(fcx, oper.span, oper_t);
+            if !(ty::type_is_integral(tcx, oper_t) ||
+                 ty::struct(tcx, oper_t) == ty::ty_bool) {
+                oper_t = check_user_unop(fcx, "!", "op_not", expr, oper_t);
             }
           }
           ast::neg {
             oper_t = structurally_resolved_type(fcx, oper.span, oper_t);
             if !(ty::type_is_integral(tcx, oper_t) ||
-                     ty::type_is_fp(tcx, oper_t)) {
-                tcx.sess.span_err(expr.span,
-                                  "applying unary minus to \
-                   non-numeric type `"
-                                      + ty_to_str(tcx, oper_t) + "`");
+                 ty::type_is_fp(tcx, oper_t)) {
+                oper_t = check_user_unop(fcx, "-", "op_neg", expr, oper_t);
             }
           }
         }
@@ -1971,11 +2005,6 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, expr: @ast::expr, unify: unifier,
         require_impure(tcx.sess, fcx.purity, expr.span);
         bot = check_assignment(fcx, expr.span, lhs, rhs, id);
       }
-      ast::expr_assign_op(op, lhs, rhs) {
-        require_impure(tcx.sess, fcx.purity, expr.span);
-        bot = check_assignment(fcx, expr.span, lhs, rhs, id);
-        check_binop_type_compat(fcx, expr.span, expr_ty(tcx, lhs), op);
-      }
       ast::expr_if(cond, thn, elsopt) {
         bot =
             check_expr_with(fcx, cond, ty::mk_bool(tcx)) |
@@ -2069,7 +2098,8 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, expr: @ast::expr, unify: unifier,
       }
       ast::expr_bind(f, args) {
         // Call the generic checker.
-        bot = check_call_or_bind(fcx, expr.span, f, args);
+        bot = check_expr(fcx, f);
+        bot |= check_call_or_bind(fcx, expr.span, expr_ty(tcx, f), args);
 
         // Pull the argument and return types out.
         let proto, arg_tys, rt, cf, constrs;
@@ -2891,14 +2921,19 @@ mod dict {
             }
           }
           // Must resolve bounds on methods with bounded params
-          ast::expr_field(_, _, _) {
+          ast::expr_field(_, _, _) | ast::expr_binary(_, _, _) |
+          ast::expr_unary(_, _) | ast::expr_assign_op(_, _, _) {
             alt cx.method_map.find(ex.id) {
               some(method_static(did)) {
                 let bounds = ty::lookup_item_type(cx.tcx, did).bounds;
                 if has_iface_bounds(*bounds) {
-                    let ts = ty::node_id_to_type_params(cx.tcx, ex.id);
+                    let callee_id = alt ex.node {
+                      ast::expr_field(_, _, _) { ex.id }
+                      _ { ast_util::op_expr_callee_id(ex) }
+                    };
+                    let ts = ty::node_id_to_type_params(cx.tcx, callee_id);
                     let iscs = cx.impl_map.get(ex.id);
-                    cx.dict_map.insert(ex.id, lookup_dicts(
+                    cx.dict_map.insert(callee_id, lookup_dicts(
                         fcx, iscs, ex.span, bounds, ts));
                 }
               }
diff --git a/src/comp/syntax/ast_util.rs b/src/comp/syntax/ast_util.rs
index cb0545c07b7..65c117cfa6b 100644
--- a/src/comp/syntax/ast_util.rs
+++ b/src/comp/syntax/ast_util.rs
@@ -326,6 +326,11 @@ fn ident_to_path(s: span, i: ident) -> @path {
     @respan(s, {global: false, idents: [i], types: []})
 }
 
+// Provides an extra node_id to hang callee information on, in case the
+// operator is deferred to a user-supplied method. The parser is responsible
+// for reserving this id.
+fn op_expr_callee_id(e: @expr) -> node_id { e.id - 1 }
+
 // Local Variables:
 // mode: rust
 // fill-column: 78;
diff --git a/src/comp/syntax/parse/parser.rs b/src/comp/syntax/parse/parser.rs
index a66b223175c..1e892eb43b1 100644
--- a/src/comp/syntax/parse/parser.rs
+++ b/src/comp/syntax/parse/parser.rs
@@ -1048,6 +1048,7 @@ fn parse_prefix_expr(p: parser) -> pexpr {
         p.bump();
         let e = to_expr(parse_prefix_expr(p));
         hi = e.span.hi;
+        p.get_id(); // see ast_util::op_expr_callee_id
         ex = ast::expr_unary(ast::not, e);
       }
       token::BINOP(b) {
@@ -1056,6 +1057,7 @@ fn parse_prefix_expr(p: parser) -> pexpr {
             p.bump();
             let e = to_expr(parse_prefix_expr(p));
             hi = e.span.hi;
+            p.get_id(); // see ast_util::op_expr_callee_id
             ex = ast::expr_unary(ast::neg, e);
           }
           token::STAR {
@@ -1146,6 +1148,7 @@ fn parse_more_binops(p: parser, plhs: pexpr, min_prec: int) ->
             p.bump();
             let expr = parse_prefix_expr(p);
             let rhs = parse_more_binops(p, expr, cur.prec);
+            p.get_id(); // see ast_util::op_expr_callee_id
             let bin = mk_pexpr(p, lhs.span.lo, rhs.span.hi,
                               ast::expr_binary(cur.op, lhs, rhs));
             ret parse_more_binops(p, bin, min_prec);
@@ -1186,6 +1189,7 @@ fn parse_assign_expr(p: parser) -> @ast::expr {
           token::LSR { aop = ast::lsr; }
           token::ASR { aop = ast::asr; }
         }
+        p.get_id(); // see ast_util::op_expr_callee_id
         ret mk_expr(p, lo, rhs.span.hi, ast::expr_assign_op(aop, lhs, rhs));
       }
       token::LARROW {