about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorNiko Matsakis <niko@alum.mit.edu>2012-03-25 13:54:05 -0700
committerNiko Matsakis <niko@alum.mit.edu>2012-03-26 10:34:58 -0700
commit21111660ca7dbd95f9b0ee8c651062a607fe6345 (patch)
tree64443ae02ec95793bbad1fff3c3b22da7304ad47 /src
parent2112c391cd13a67cfbf9064b17a51640add5053e (diff)
Improve type inference to compute LUB/GLB
Diffstat (limited to 'src')
-rw-r--r--src/libcore/result.rs57
-rw-r--r--src/libcore/vec.rs14
-rw-r--r--src/rustc/middle/infer.rs863
-rw-r--r--src/test/compile-fail/fn-variance-3.rs13
-rw-r--r--src/test/compile-fail/mode-inference-fail.rs2
5 files changed, 765 insertions, 184 deletions
diff --git a/src/libcore/result.rs b/src/libcore/result.rs
index 37a55e97aa1..8c849051c6d 100644
--- a/src/libcore/result.rs
+++ b/src/libcore/result.rs
@@ -109,20 +109,21 @@ fn chain_err<T: copy, U: copy, V: copy>(
     }
 }
 
-// ______________________________________________________________________
-// Note:
-//
-// These helper functions are written in a "pre-chained" (a.k.a,
-// deforested) style because I have found that, in practice, this is
-// the most concise way to do things.  That means that they do not not
-// terminate with a call to `ok(v)` but rather `nxt(v)`.  If you would
-// like to just get the result, just pass in `ok` as `nxt`.
+impl methods<T:copy,E:copy> for result<T,E> {
+    fn chain<U:copy>(op: fn(T) -> result<U,E>) -> result<U,E> {
+        chain(self, op)
+    }
+
+    fn chain_err<F:copy>(op: fn(E) -> result<T,F>) -> result<T,F> {
+        chain_err(self, op)
+    }
+}
 
 #[doc = "
 Maps each element in the vector `ts` using the operation `op`.  Should an
 error occur, no further mappings are performed and the error is returned.
 Should no error occur, a vector containing the result of each map is
-passed to the `nxt` function.
+returned.
 
 Here is an example which increments every integer in a vector,
 checking for overflow:
@@ -131,27 +132,11 @@ checking for overflow:
         if x == uint::max_value { ret err(\"overflow\"); }
         else { ret ok(x+1u); }
     }
-    map([1u, 2u, 3u], inc_conditionally) {|incd|
-        assert incd == [2u, 3u, 4u];
-    }
-
-Note: if you have to combine a deforested style transform with map,
-you should use `ok` for the `nxt` operation, as shown here (this is an
-alternate version of the previous example where the
-`inc_conditionally()` routine is deforested):
-
-    fn inc_conditionally<T>(x: uint,
-                            nxt: fn(uint) -> result<T,str>) -> result<T,str> {
-        if x == uint::max_value { ret err(\"overflow\"); }
-        else { ret nxt(x+1u); }
-    }
-    map([1u, 2u, 3u], inc_conditionally(_, ok)) {|incd|
+    map([1u, 2u, 3u], inc_conditionally).chain {|incd|
         assert incd == [2u, 3u, 4u];
     }
 "]
-fn map<T,U:copy,V:copy,W>(ts: [T],
-                          op: fn(T) -> result<V,U>,
-                          nxt: fn([V]) -> result<W,U>) -> result<W,U> {
+fn map<T,U:copy,V:copy>(ts: [T], op: fn(T) -> result<V,U>) -> result<[V],U> {
     let mut vs: [V] = [];
     vec::reserve(vs, vec::len(ts));
     for t in ts {
@@ -160,7 +145,7 @@ fn map<T,U:copy,V:copy,W>(ts: [T],
           err(u) { ret err(u); }
         }
     }
-    ret nxt(vs);
+    ret ok(vs);
 }
 
 #[doc = "Same as map, but it operates over two parallel vectors.
@@ -170,11 +155,9 @@ length.  While we do not often use preconditions in the standard
 library, a precondition is used here because result::t is generally
 used in 'careful' code contexts where it is both appropriate and easy
 to accommodate an error like the vectors being of different lengths."]
-fn map2<S,T,U:copy,V:copy,W>(ss: [S], ts: [T],
-                             op: fn(S,T) -> result<V,U>,
-                             nxt: fn([V]) -> result<W,U>)
-    : vec::same_length(ss, ts)
-    -> result<W,U> {
+fn map2<S,T,U:copy,V:copy>(ss: [S], ts: [T], op: fn(S,T) -> result<V,U>)
+    : vec::same_length(ss, ts) -> result<[V],U> {
+
     let n = vec::len(ts);
     let mut vs = [];
     vec::reserve(vs, n);
@@ -186,13 +169,19 @@ fn map2<S,T,U:copy,V:copy,W>(ss: [S], ts: [T],
         }
         i += 1u;
     }
-    ret nxt(vs);
+    ret ok(vs);
 }
 
+#[doc = "
+Applies op to the pairwise elements from `ss` and `ts`, aborting on
+error.  This could be implemented using `map2()` but it is more efficient
+on its own as no result vector is built.
+"]
 fn iter2<S,T,U:copy>(ss: [S], ts: [T],
                      op: fn(S,T) -> result<(),U>)
     : vec::same_length(ss, ts)
     -> result<(),U> {
+
     let n = vec::len(ts);
     let mut i = 0u;
     while i < n {
diff --git a/src/libcore/vec.rs b/src/libcore/vec.rs
index c760813bf7f..c0bd70c270c 100644
--- a/src/libcore/vec.rs
+++ b/src/libcore/vec.rs
@@ -27,7 +27,6 @@ export rsplit;
 export rsplitn;
 export shift;
 export pop;
-export clear;
 export push;
 export grow;
 export grow_fn;
@@ -321,7 +320,7 @@ fn rsplitn<T: copy>(v: [const T], n: uint, f: fn(T) -> bool) -> [[T]] {
 // Mutators
 
 #[doc = "Removes the first element from a vector and return it"]
-fn shift<T: copy>(&v: [const T]) -> T {
+fn shift<T: copy>(&v: [T]) -> T {
     let ln = len::<T>(v);
     assert (ln > 0u);
     let e = v[0];
@@ -331,6 +330,9 @@ fn shift<T: copy>(&v: [const T]) -> T {
 
 #[doc = "Prepend an element to a vector"]
 fn unshift<T: copy>(&v: [const T], +t: T) {
+    // n.b.---for most callers, using unshift() ought not to type check, but
+    // it does. It's because the type system is unaware of the mutability of
+    // `v` and so allows the vector to be covariant.
     v = [const t] + v;
 }
 
@@ -344,14 +346,6 @@ fn pop<T>(&v: [const T]) -> T unsafe {
     val
 }
 
-#[doc = "
-Removes all elements from a vector without affecting
-how much space is reserved.
-"]
-fn clear<T>(&v: [const T]) unsafe {
-    unsafe::set_len(v, 0u);
-}
-
 #[doc = "Append an element to a vector"]
 fn push<T>(&v: [const T], +initval: T) {
     v += [initval];
diff --git a/src/rustc/middle/infer.rs b/src/rustc/middle/infer.rs
index ca4f696532f..320e2b349c6 100644
--- a/src/rustc/middle/infer.rs
+++ b/src/rustc/middle/infer.rs
@@ -3,8 +3,9 @@ import std::smallintmap::smallintmap;
 import std::smallintmap::map;
 import middle::ty;
 import syntax::ast;
+import syntax::ast::{ret_style};
 import util::ppaux::{ty_to_str, mt_to_str};
-import result::{result, chain, chain_err, ok, iter2};
+import result::{result, methods, chain, chain_err, ok, err, map, map2, iter2};
 import ty::type_is_bot;
 
 export infer_ctxt;
@@ -46,24 +47,22 @@ fn new_infer_ctxt(tcx: ty::ctxt) -> infer_ctxt {
 }
 
 fn mk_subty(cx: infer_ctxt, a: ty::t, b: ty::t) -> ures {
-    #debug[">> mk_subty(%s <: %s)", cx.ty_to_str(a), cx.ty_to_str(b)];
+    #debug[">> mk_subty(%s <: %s)", a.to_str(cx), b.to_str(cx)];
     cx.commit {||
         cx.tys(a, b)
     }
 }
 
 fn mk_eqty(cx: infer_ctxt, a: ty::t, b: ty::t) -> ures {
-    #debug["> mk_eqty(%s <: %s)", cx.ty_to_str(a), cx.ty_to_str(b)];
+    #debug[">> mk_eqty(%s <: %s)", a.to_str(cx), b.to_str(cx)];
     cx.commit {||
-        mk_subty(cx, a, b).then {||
-            mk_subty(cx, b, a)
-        }
+        cx.eq_tys(a, b)
     }
 }
 
 fn compare_tys(tcx: ty::ctxt, a: ty::t, b: ty::t) -> ures {
     let infcx = new_infer_ctxt(tcx);
-    #debug["> compare_tys(%s == %s)", infcx.ty_to_str(a), infcx.ty_to_str(b)];
+    #debug[">> compare_tys(%s == %s)", a.to_str(infcx), b.to_str(infcx)];
     infcx.commit {||
         mk_subty(infcx, a, b).then {||
             mk_subty(infcx, b, a)
@@ -90,6 +89,48 @@ impl methods for ures {
     }
 }
 
+iface to_str {
+    fn to_str(cx: infer_ctxt) -> str;
+}
+
+impl of to_str for ty::t {
+    fn to_str(cx: infer_ctxt) -> str {
+        ty_to_str(cx.tcx, self)
+    }
+}
+
+impl of to_str for ty::mt {
+    fn to_str(cx: infer_ctxt) -> str {
+        mt_to_str(cx.tcx, self)
+    }
+}
+
+impl<V:copy to_str> of to_str for bound<V> {
+    fn to_str(cx: infer_ctxt) -> str {
+        alt self {
+          some(v) { v.to_str(cx) }
+          none { "none " }
+        }
+    }
+}
+
+impl<V:copy to_str> of to_str for bounds<V> {
+    fn to_str(cx: infer_ctxt) -> str {
+        #fmt["{%s <: %s}",
+             self.lb.to_str(cx),
+             self.ub.to_str(cx)]
+    }
+}
+
+impl<V:copy to_str> of to_str for var_value<V> {
+    fn to_str(cx: infer_ctxt) -> str {
+        alt self {
+          redirect(id) { #fmt("redirect(%u)", id) }
+          bounded(bnds) { #fmt("bounded(%s)", bnds.to_str(cx)) }
+        }
+    }
+}
+
 // Most of these methods, like tys() and so forth, take two parameters
 // a and b and they are tasked with "ensuring that a is a subtype of
 // b".  They return success or failure.  They make changes in-place to
@@ -102,53 +143,32 @@ impl methods for ures {
 impl unify_methods for infer_ctxt {
     fn uok() -> ures {
         #debug["Unification OK"];
-        result::ok(())
+        ok(())
     }
 
     fn uerr(e: ty::type_err) -> ures {
         #debug["Unification error: %?", e];
-        result::err(e)
-    }
-
-    fn ty_to_str(t: ty::t) -> str {
-        ty_to_str(self.tcx, t)
-    }
-
-    fn ty_bound_to_str(b: bound<ty::t>) -> str {
-        alt b {
-          none { "none" }
-          some(t) { self.ty_to_str(t) }
-        }
-    }
-
-    fn ty_bounds_to_str(v: bounds<ty::t>) -> str {
-        #fmt["{%s <: X <: %s}",
-             self.ty_bound_to_str(v.lb),
-             self.ty_bound_to_str(v.ub)]
-    }
-
-    fn ty_var_value_to_str(v: var_value<ty::t>) -> str {
-        alt v {
-          redirect(v) { #fmt["redirect(%u)", v] }
-          bounded(b) { self.ty_bounds_to_str(b) }
-        }
+        err(e)
     }
 
-    fn set<T:copy>(vb: vals_and_bindings<T>, vid: uint,
-                   +new_v: var_value<T>) {
+    fn set<T:copy to_str>(
+        vb: vals_and_bindings<T>, vid: uint,
+        +new_v: var_value<T>) {
 
         let old_v = vb.vals.get(vid);
         vec::push(vb.bindings, (vid, old_v));
         vb.vals.insert(vid, new_v);
+
+        #debug["Updating variable <%u> from %s to %s",
+               vid, old_v.to_str(self), new_v.to_str(self)];
     }
 
     fn set_ty(vid: uint, +new_v: var_value<ty::t>) {
         let old_v = self.vb.vals.get(vid);
         self.set(self.vb, vid, new_v);
+
         #debug["Updating variable <T%u> from %s to %s",
-               vid,
-               self.ty_var_value_to_str(old_v),
-               self.ty_var_value_to_str(new_v)];
+               vid, old_v.to_str(self), new_v.to_str(self)];
     }
 
     fn rollback_to<T:copy>(vb: vals_and_bindings<T>, len: uint) {
@@ -170,6 +190,8 @@ impl unify_methods for infer_ctxt {
         self.vb.bindings = [];
         self.rb.bindings = [];
 
+        #debug[">> Commit result: %?", r];
+
         ret r;
     }
 
@@ -227,38 +249,47 @@ impl unify_methods for infer_ctxt {
         ret self.get(self.rb, rid);
     }
 
-    // Combines the two bounds.  Returns a bounds r where (r.lb <:
-    // a,b) and (a,b <: r.ub) (if such a bounds exists).
-    //
-    // TODO: Generalize this to region bounds too.
-    fn merge_bnds(a: bound<ty::t>, b: bound<ty::t>)
-            -> result<bounds<ty::t>, ty::type_err> {
+    // Combines the two bounds into a more general bound.
+    fn merge_bnd<V:copy to_str>(
+        a: bound<V>, b: bound<V>,
+        merge_op: fn(V,V) -> cres<V>) -> cres<bound<V>> {
 
         alt (a, b) {
           (none, none) {
-            ok({lb: none, ub: none})
+            ok(none)
           }
           (some(_), none) {
-            ok({lb: a, ub: a})
+            ok(a)
           }
           (none, some(_)) {
-            ok({lb: b, ub: b})
+            ok(b)
           }
-          (some(t_a), some(t_b)) {
-            let r1 = self.try {||
-                self.tys(t_a, t_b).then {||
-                    ok({lb: a, ub: b})
-                }
-            };
-            chain_err(r1) {|_e|
-                self.tys(t_b, t_a).then {||
-                    ok({lb: b, ub: a})
-                }
+          (some(v_a), some(v_b)) {
+            merge_op(v_a, v_b).chain {|v|
+                ok(some(v))
             }
           }
         }
     }
 
+    fn merge_bnds<V:copy to_str>(
+        a: bounds<V>, b: bounds<V>,
+        lub: fn(V,V) -> cres<V>,
+        glb: fn(V,V) -> cres<V>) -> cres<bounds<V>> {
+
+        self.merge_bnd(a.ub, b.ub, glb).chain {|ub|
+            #debug["glb of ubs %s and %s is %s",
+                   a.ub.to_str(self), b.ub.to_str(self),
+                   ub.to_str(self)];
+            self.merge_bnd(a.lb, b.lb, lub).chain {|lb|
+                #debug["lub of lbs %s and %s is %s",
+                       a.lb.to_str(self), b.lb.to_str(self),
+                       lb.to_str(self)];
+                ok({lb: lb, ub: ub})
+            }
+        }
+    }
+
     // Updates the bounds for the variable `v_id` to be the intersection
     // of `a` and `b`.  That is, the new bounds for `v_id` will be
     // a bounds c such that:
@@ -267,9 +298,9 @@ impl unify_methods for infer_ctxt {
     //    a.lb <: c.lb
     //    b.lb <: c.lb
     // If this cannot be achieved, the result is failure.
-    //
-    // TODO: Generalize to regions.
-    fn merge(v_id: uint, a: bounds<ty::t>, b: bounds<ty::t>) -> ures {
+    fn set_ty_var_to_merged_bounds(
+        v_id: uint, a: bounds<ty::t>, b: bounds<ty::t>) -> ures {
+
         // Think of the two diamonds, we want to find the
         // intersection.  There are basically four possibilities (you
         // can swap A/B in these pictures):
@@ -287,21 +318,33 @@ impl unify_methods for infer_ctxt {
 
         #debug["merge(<T%u>,%s,%s)",
                v_id,
-               self.ty_bounds_to_str(a),
-               self.ty_bounds_to_str(b)];
-
-        chain(self.merge_bnds(a.ub, b.ub)) {|ub|
-            chain(self.merge_bnds(a.lb, b.lb)) {|lb|
-                let bnds = {lb: lb.ub, ub: ub.lb};
-
-                // the new bounds must themselves
-                // be relatable:
-                self.bnds(lb.ub, ub.lb).then {||
-                    self.set(self.vb, v_id, bounded(bnds));
-                    self.uok()
-                }
+               a.to_str(self),
+               b.to_str(self)];
+
+        // First, relate the lower/upper bounds of A and B.
+        // Note that these relations *must* hold for us to
+        // to be able to merge A and B at all, and relating
+        // them explicitly gives the type inferencer more
+        // information and helps to produce tighter bounds
+        // when necessary.
+        self.bnds(a.lb, b.ub).then {||
+        self.bnds(b.lb, a.ub).then {||
+        self.merge_bnds(
+            a, b,
+            {|a_ty, b_ty| lub(self).c_tys(a_ty, b_ty) },
+            {|a_ty, b_ty| glb(self).c_tys(a_ty, b_ty) }).chain {|bnds|
+
+            #debug["merge(<T%u>): bnds=%s",
+                   v_id,
+                   bnds.to_str(self)];
+
+            // the new bounds must themselves
+            // be relatable:
+            self.bnds(bnds.lb, bnds.ub).then {||
+            self.set_ty(v_id, bounded(bnds));
+            self.uok()
             }
-        }
+        }}}
     }
 
     // TODO: Generalize to regions.
@@ -311,8 +354,8 @@ impl unify_methods for infer_ctxt {
         let {root: b_id, bounds: b_bounds} = self.get(self.vb, b_id);
 
         #debug["vars(<T%u>=%s <: <T%u>=%s)",
-               a_id, self.ty_bounds_to_str(a_bounds),
-               b_id, self.ty_bounds_to_str(b_bounds)];
+               a_id, a_bounds.to_str(self),
+               b_id, b_bounds.to_str(self)];
 
         if a_id == b_id { ret self.uok(); }
 
@@ -322,19 +365,21 @@ impl unify_methods for infer_ctxt {
           (some(a_ub), some(b_lb)) {
             let r = self.try {|| self.tys(a_ub, b_lb) };
             alt r {
-              result::ok(()) { ret result::ok(()); }
-              result::err(_) { /*fallthrough */ }
+              ok(()) { ret result::ok(()); }
+              err(_) { /*fallthrough */ }
             }
           }
           _ { /*fallthrough*/ }
         }
 
+        // For max perf, we should consider the rank here.  But for now,
+        // we always make b redirect to a.
+        self.set_ty(b_id, redirect(a_id));
+
         // Otherwise, we need to merge A and B so as to guarantee that
         // A remains a subtype of B.  Actually, there are other options,
         // but that's the route we choose to take.
-        self.merge(a_id, a_bounds, b_bounds).then {||
-            // For max perf, we should consider the rank here.
-            self.set(self.vb, b_id, redirect(a_id));
+        self.set_ty_var_to_merged_bounds(a_id, a_bounds, b_bounds).then {||
             self.uok()
         }
     }
@@ -342,24 +387,19 @@ impl unify_methods for infer_ctxt {
     fn varty(a_id: uint, b: ty::t) -> ures {
         let {root: a_id, bounds: a_bounds} = self.get(self.vb, a_id);
         #debug["varty(<T%u>=%s <: %s)",
-               a_id, self.ty_bounds_to_str(a_bounds),
-               self.ty_to_str(b)];
+               a_id, a_bounds.to_str(self),
+               b.to_str(self)];
         let b_bounds = {lb: none, ub: some(b)};
-        self.merge(a_id, a_bounds, b_bounds)
+        self.set_ty_var_to_merged_bounds(a_id, a_bounds, b_bounds)
     }
 
     fn tyvar(a: ty::t, b_id: uint) -> ures {
         let a_bounds = {lb: some(a), ub: none};
         let {root: b_id, bounds: b_bounds} = self.get(self.vb, b_id);
         #debug["tyvar(%s <: <T%u>=%s)",
-               self.ty_to_str(a),
-               b_id, self.ty_bounds_to_str(b_bounds)];
-        self.merge(b_id, a_bounds, b_bounds)
-    }
-
-    fn tyvecs(as: [ty::t], bs: [ty::t])
-        : vec::same_length(as, bs) -> ures {
-        iter2(as, bs) {|a,b| self.tys(a,b) }
+               a.to_str(self),
+               b_id, b_bounds.to_str(self)];
+        self.set_ty_var_to_merged_bounds(b_id, a_bounds, b_bounds)
     }
 
     fn regions(a: ty::region, b: ty::region) -> ures {
@@ -400,9 +440,7 @@ impl unify_methods for infer_ctxt {
     }
 
     fn mts(a: ty::mt, b: ty::mt) -> ures {
-        #debug("mts(%s <: %s)",
-               mt_to_str(self.tcx, a),
-               mt_to_str(self.tcx, b));
+        #debug("mts(%s <: %s)", a.to_str(self), b.to_str(self));
 
         if a.mutbl != b.mutbl && b.mutbl != ast::m_const {
             ret self.uerr(ty::terr_mutability);
@@ -410,11 +448,9 @@ impl unify_methods for infer_ctxt {
 
         alt b.mutbl {
           ast::m_mutbl {
-            // If supertype is mutable, subtype must mtach exactly
+            // If supertype is mutable, subtype must match exactly
             // (i.e., invariant if mutable):
-            self.tys(a.ty, b.ty).then {||
-                self.tys(b.ty, a.ty)
-            }
+            self.eq_tys(a.ty, b.ty)
           }
           ast::m_imm | ast::m_const {
             // Otherwise we can be covariant:
@@ -432,7 +468,7 @@ impl unify_methods for infer_ctxt {
 
     fn tps(as: [ty::t], bs: [ty::t]) -> ures {
         if check vec::same_length(as, bs) {
-            self.tyvecs(as, bs)
+            iter2(as, bs) {|a, b| self.tys(a, b) }
         } else {
             self.uerr(ty::terr_ty_param_size(as.len(), bs.len()))
         }
@@ -448,8 +484,8 @@ impl unify_methods for infer_ctxt {
     }
 
     fn ret_styles(
-        a_ret_style: ast::ret_style,
-        b_ret_style: ast::ret_style) -> ures {
+        a_ret_style: ret_style,
+        b_ret_style: ret_style) -> ures {
 
         if b_ret_style != ast::noreturn && b_ret_style != a_ret_style {
             /* even though typestate checking is mostly
@@ -465,8 +501,8 @@ impl unify_methods for infer_ctxt {
 
     fn modes(a: ast::mode, b: ast::mode) -> ures {
         alt ty::unify_mode(self.tcx, a, b) {
-          result::ok(_) { self.uok() }
-          result::err(e) { self.uerr(e) }
+          ok(_) { self.uok() }
+          err(e) { self.uerr(e) }
         }
     }
 
@@ -492,8 +528,10 @@ impl unify_methods for infer_ctxt {
             self.ret_styles(a_f.ret_style, b_f.ret_style).then {||
                 self.argvecs(a_f.inputs, b_f.inputs).then {||
                     self.tys(a_f.output, b_f.output).then {||
-                        // FIXME---constraints
-                        self.uok()
+                        //TODO self.constrvecs(a_f.constraints,
+                        //TODO                 b_f.constraints).then {||
+                            self.uok()
+                        //TODO }
                     }
                 }
             }
@@ -546,14 +584,36 @@ impl unify_methods for infer_ctxt {
     // TODO: Generalize this.
     fn bnds(a: bound<ty::t>, b: bound<ty::t>) -> ures {
         #debug("bnds(%s <: %s)",
-               self.ty_bound_to_str(a),
-               self.ty_bound_to_str(b));
+               a.to_str(self),
+               b.to_str(self));
 
         alt (a, b) {
           (none, none) |
           (some(_), none) |
-          (none, some(_)) { self.uok() }
-          (some(t_a), some(t_b)) { self.tys(t_a, t_b) }
+          (none, some(_)) {
+            self.uok()
+          }
+          (some(t_a), some(t_b)) {
+            self.tys(t_a, t_b)
+          }
+        }
+    }
+
+    fn constrvecs(
+        as: [@ty::type_constr], bs: [@ty::type_constr]) -> ures {
+
+        if check vec::same_length(as, bs) {
+            iter2(as, bs) {|a,b|
+                self.constrs(a, b)
+            }
+        } else {
+            self.uerr(ty::terr_constr_len(as.len(), bs.len()))
+        }
+    }
+
+    fn eq_tys(a: ty::t, b: ty::t) -> ures {
+        self.tys(a, b).then {||
+            self.tys(b, a)
         }
     }
 
@@ -637,7 +697,7 @@ impl unify_methods for infer_ctxt {
 
           (ty::ty_tup(a_tys), ty::ty_tup(b_tys)) {
             if check vec::same_length(a_tys, b_tys) {
-                self.tyvecs(a_tys, b_tys)
+                iter2(a_tys, b_tys) {|a,b| self.tys(a,b) }
             } else {
                 self.uerr(ty::terr_tuple_size(a_tys.len(), b_tys.len()))
             }
@@ -649,14 +709,7 @@ impl unify_methods for infer_ctxt {
 
           (ty::ty_constr(a_t, a_constrs), ty::ty_constr(b_t, b_constrs)) {
             self.tys(a_t, b_t).then {||
-                if check vec::same_length(a_constrs, b_constrs) {
-                    iter2(a_constrs, b_constrs) {|a,b|
-                        self.constrs(a, b)
-                    }
-                } else {
-                    self.uerr(ty::terr_constr_len(a_constrs.len(),
-                                                  b_constrs.len()))
-                }
+                self.constrvecs(a_constrs, b_constrs)
             }
           }
 
@@ -667,20 +720,24 @@ impl unify_methods for infer_ctxt {
 
 impl resolve_methods for infer_ctxt {
     fn rok(t: ty::t) -> fres<ty::t> {
-        #debug["Resolve OK: %s", self.ty_to_str(t)];
-        result::ok(t)
+        #debug["Resolve OK: %s", t.to_str(self)];
+        ok(t)
     }
 
     fn rerr<T>(v: int) -> fres<T> {
         #debug["Resolve error: %?", v];
-        result::err(v)
+        err(v)
     }
 
-    fn resolve_var<T:copy>(vb: vals_and_bindings<T>, bot_guard: fn(T)->bool,
-                           vid: int) -> fres<T> {
+    fn resolve_var<T:copy to_str>(
+        vb: vals_and_bindings<T>, bot_guard: fn(T)->bool,
+        vid: int) -> fres<T> {
 
         let {root:_, bounds} = self.get(vb, vid as uint);
 
+        #debug["resolve_var(%d) bounds=%s",
+               vid, bounds.to_str(self)];
+
         // Nonobvious: prefer the most specific type
         // (i.e., the lower bound) to the more general
         // one.  More general types in Rust (e.g., fn())
@@ -688,9 +745,9 @@ impl resolve_methods for infer_ctxt {
         // perf. penalties, so it pays to know more.
 
         alt bounds {
-          { ub:_, lb:some(t) } if !bot_guard(t) { result::ok(t) }
-          { ub:some(t), lb:_ } { result::ok(t) }
-          { ub:_, lb:some(t) } { result::ok(t) }
+          { ub:_, lb:some(t) } if !bot_guard(t) { ok(t) }
+          { ub:some(t), lb:_ } { ok(t) }
+          { ub:_, lb:some(t) } { ok(t) }
           { ub:none, lb:none } { self.rerr(vid) }
         }
     }
@@ -704,8 +761,8 @@ impl resolve_methods for infer_ctxt {
           ty::ty_var(vid) { self.resolve_ty_var(vid) }
           ty::ty_rptr(ty::re_var(rid), base_ty) {
             alt self.resolve_region(rid as int) {
-              result::err(terr)  { result::err(terr) }
-              result::ok(region) {
+              err(terr)  { err(terr) }
+              ok(region) {
                 self.rok(ty::mk_rptr(self.tcx, region, base_ty))
               }
             }
@@ -720,11 +777,11 @@ impl resolve_methods for infer_ctxt {
         // Should really return a fixup_result instead of a t, but fold_ty
         // doesn't allow returning anything but a t.
         alt self.resolve_ty_var(vid) {
-          result::err(vid) {
+          err(vid) {
             *unresolved = some(vid);
             ret ty::mk_var(self.tcx, vid);
           }
-          result::ok(rt) {
+          ok(rt) {
             let mut give_up = false;
             std::list::iter(vars_seen) {|v|
                 if v == vid {
@@ -776,7 +833,7 @@ impl resolve_methods for infer_ctxt {
         alt bounds {
           { ub:_, lb:some(r) } |
           { ub:some(r), lb:_ } |
-          { ub:_, lb:some(r) } { result::ok(r) }
+          { ub:_, lb:some(r) } { ok(r) }
           { ub:none, lb:none } { self.rerr(rid) }
         }
     }
@@ -787,11 +844,11 @@ impl resolve_methods for infer_ctxt {
         // Should really return a fixup_result instead of a t, but fold_ty
         // doesn't allow returning anything but a t.
         alt self.resolve_region(rid) {
-          result::err(rid) {
+          err(rid) {
             *unresolved = some(rid);
             ret ty::re_var(rid as uint);
           }
-          result::ok(rr) {
+          ok(rr) {
             let mut give_up = false;
             std::list::iter(regions_seen) {|r|
                 if r == rid {
@@ -822,3 +879,541 @@ impl resolve_methods for infer_ctxt {
         }
     }
 }
+
+// ______________________________________________________________________
+// Type combining
+//
+// There are two type combiners, lub and gub.  The first computes the
+// Least Upper Bound of two types `a` and `b`---that is, a mutual
+// supertype type `c` where `a <: c` and `a <: c`.  As the name
+// implies, it tries to pick the most precise `c` possible.  `glb`
+// computes the greatest lower bound---that is, it computes a mutual
+// subtype, aiming for the most general such type possible.  Both
+// computations may fail.
+//
+// There is a lot of common code for these operations, which is
+// abstracted out into functions named `c_X()` which take a combiner
+// instance as the first parameter.  This would be better implemented
+// using traits.
+//
+// In principle, the subtyping relation computed above could be built
+// on the combine framework---this would result in less code but would
+// be less efficient.  There is a significant performance gain from
+// not recreating types unless we need to.  Even so, we could write
+// the routines with a few more generics in there to mask type
+// construction (which is, after all, the significant expense) but I
+// haven't gotten around to it.
+
+type cres<T> = result<T,ty::type_err>;
+
+iface combine {
+    fn infcx() -> infer_ctxt;
+    fn tag() -> str;
+    fn bnd<V:copy>(b: bounds<V>) -> option<V>;
+    fn with_bnd<V:copy>(b: bounds<V>, v: V) -> bounds<V>;
+    fn c_bot(b: ty::t) -> cres<ty::t>;
+    fn c_regions(a: ty::region, b: ty::region) -> cres<ty::region>;
+    fn c_mts(a: ty::mt, b: ty::mt) -> cres<ty::mt>;
+    fn c_contratys(t1: ty::t, t2: ty::t) -> cres<ty::t>;
+    fn c_tys(t1: ty::t, t2: ty::t) -> cres<ty::t>;
+    fn c_protos(p1: ast::proto, p2: ast::proto) -> cres<ast::proto>;
+    fn c_ret_styles(r1: ret_style, r2: ret_style) -> cres<ret_style>;
+}
+
+enum lub = infer_ctxt;
+enum glb = infer_ctxt;
+
+fn c_ty_vars<C:combine>(self: C, a_id: uint, b_id: uint) -> cres<ty::t> {
+    // Need to find a type that is a supertype of both a and b:
+    let {root: a_id, bounds: a_bounds} = self.infcx().get_var(a_id);
+    let {root: b_id, bounds: b_bounds} = self.infcx().get_var(b_id);
+
+    #debug["%s.c_ty_vars(<T%u>=%s <: <T%u>=%s)",
+           self.tag(),
+           a_id, a_bounds.to_str(self.infcx()),
+           b_id, b_bounds.to_str(self.infcx())];
+
+    let tcx = self.infcx().tcx;
+
+    if a_id == b_id {
+        ret ok(ty::mk_var(tcx, a_id as int));
+    }
+
+    // The comments in this function are written for LUB, but they
+    // apply equally well to GLB if you inverse upper/lower/sub/super/etc.
+
+    // If both A and B have an UB type, then we can just compute the
+    // LUB of those types:
+    let a_bnd = self.bnd(a_bounds), b_bnd = self.bnd(b_bounds);
+    alt (a_bnd, b_bnd) {
+      (some(a_ty), some(b_ty)) {
+        alt self.infcx().try {|| self.c_tys(a_ty, b_ty) } {
+            ok(t) { ret ok(t); }
+            err(_) { /*fallthrough */ }
+        }
+      }
+      _ {/*fallthrough*/}
+    }
+
+    // Otherwise, we need to merge A and B into one variable.  We can
+    // then use either variable as an upper bound:
+    self.infcx().vars(a_id, b_id).then {||
+        ok(ty::mk_var(tcx, a_id as int))
+    }
+}
+
+fn c_ty_var_ty<C:combine>(self: C, a_id: uint, b: ty::t) -> cres<ty::t> {
+    let {root: a_id, bounds: a_bounds} = self.infcx().get_var(a_id);
+
+    // The comments in this function are written for LUB, but they
+    // apply equally well to GLB if you inverse upper/lower/sub/super/etc.
+
+    #debug["%s.c_ty_var_ty(<T%u>=%s <: %s)",
+           self.tag(),
+           a_id, a_bounds.to_str(self.infcx()),
+           b.to_str(self.infcx())];
+
+    alt self.bnd(a_bounds) {
+      some(a_ty) {
+        // If a has an upper bound, return it.
+        ret self.c_tys(a_ty, b);
+      }
+      none {
+        // If a does not have an upper bound, make b the upper bound of a
+        // and then return b.
+        let a_bounds = self.with_bnd(a_bounds, b);
+        self.infcx().bnds(a_bounds.lb, a_bounds.ub).then {||
+            self.infcx().set_ty(a_id, bounded(a_bounds));
+            ok(b)
+        }
+      }
+    }
+}
+
+fn c_tuptys<C:combine>(self: C, as: [ty::t], bs: [ty::t])
+    -> cres<[ty::t]> {
+
+    if check vec::same_length(as, bs) {
+        map2(as, bs) {|a, b| self.c_tys(a, b) }
+    } else {
+        err(ty::terr_tuple_size(as.len(), bs.len()))
+    }
+}
+
+fn c_tps<C:combine>(self: C, _did: ast::def_id, as: [ty::t], bs: [ty::t])
+    -> cres<[ty::t]> {
+    // FIXME #1973 lookup the declared variance of the type parameters
+    // based on did
+    if check vec::same_length(as, bs) {
+        map2(as, bs) {|a,b| self.c_tys(a, b) }
+    } else {
+        err(ty::terr_ty_param_size(as.len(), bs.len()))
+    }
+}
+
+fn c_fieldvecs<C:combine>(self: C, as: [ty::field], bs: [ty::field])
+    -> cres<[ty::field]> {
+
+    if check vec::same_length(as, bs) {
+        map2(as, bs) {|a,b| c_flds(self, a, b) }
+    } else {
+        err(ty::terr_record_size(as.len(), bs.len()))
+    }
+}
+
+fn c_flds<C:combine>(self: C, a: ty::field, b: ty::field) -> cres<ty::field> {
+    if a.ident == b.ident {
+        self.c_mts(a.mt, b.mt).chain {|mt|
+            ok({ident: a.ident, mt: mt})
+        }
+    } else {
+        err(ty::terr_record_fields(a.ident, b.ident))
+    }
+}
+
+fn c_modes<C:combine>(self: C, a: ast::mode, b: ast::mode)
+    -> cres<ast::mode> {
+
+    let tcx = self.infcx().tcx;
+    ty::unify_mode(tcx, a, b)
+}
+
+fn c_args<C:combine>(self: C, a: ty::arg, b: ty::arg)
+    -> cres<ty::arg> {
+
+    c_modes(self, a.mode, b.mode).chain {|m|
+        // Note: contravariant
+        self.c_contratys(b.ty, a.ty).chain {|t|
+            ok({mode: m, ty: t})
+        }
+    }
+}
+
+fn c_argvecs<C:combine>(
+    self: C, a_args: [ty::arg], b_args: [ty::arg]) -> cres<[ty::arg]> {
+
+    if check vec::same_length(a_args, b_args) {
+        map2(a_args, b_args) {|a, b| c_args(self, a, b) }
+    } else {
+        err(ty::terr_arg_count)
+    }
+}
+
+fn c_fns<C:combine>(
+    self: C, a_f: ty::fn_ty, b_f: ty::fn_ty) -> cres<ty::fn_ty> {
+
+    self.c_protos(a_f.proto, b_f.proto).chain {|p|
+        self.c_ret_styles(a_f.ret_style, b_f.ret_style).chain {|rs|
+            c_argvecs(self, a_f.inputs, b_f.inputs).chain {|inputs|
+                self.c_tys(a_f.output, b_f.output).chain {|output|
+                    //FIXME self.infcx().constrvecs(a_f.constraints,
+                    //FIXME                         b_f.constraints).then {||
+                        ok({proto: p,
+                            inputs: inputs,
+                            output: output,
+                            ret_style: rs,
+                            constraints: a_f.constraints})
+                    //FIXME }
+                }
+            }
+        }
+    }
+}
+
+fn c_tys<C:combine>(
+    self: C, a: ty::t, b: ty::t) -> cres<ty::t> {
+
+    let tcx = self.infcx().tcx;
+
+    #debug("%s.c_tys(%s, %s)",
+           self.tag(),
+           ty_to_str(tcx, a),
+           ty_to_str(tcx, b));
+
+    // Fast path.
+    if a == b { ret ok(a); }
+
+    alt (ty::get(a).struct, ty::get(b).struct) {
+      (ty::ty_bot, _) { self.c_bot(b) }
+      (_, ty::ty_bot) { self.c_bot(b) }
+
+      (ty::ty_var(a_id), ty::ty_var(b_id)) {
+        c_ty_vars(self, a_id as uint, b_id as uint)
+      }
+
+      // Note that the LUB/GLB operations are commutative:
+      (ty::ty_var(a_id), _) {
+        c_ty_var_ty(self, a_id as uint, b)
+      }
+      (_, ty::ty_var(b_id)) {
+        c_ty_var_ty(self, b_id as uint, a)
+      }
+
+      (ty::ty_nil, _) |
+      (ty::ty_bool, _) |
+      (ty::ty_int(_), _) |
+      (ty::ty_uint(_), _) |
+      (ty::ty_float(_), _) |
+      (ty::ty_str, _) {
+        let cfg = tcx.sess.targ_cfg;
+        if ty::mach_sty(cfg, a) == ty::mach_sty(cfg, b) {
+            ok(a)
+        } else {
+            err(ty::terr_mismatch)
+        }
+      }
+
+      (ty::ty_param(a_n, _), ty::ty_param(b_n, _)) if a_n == b_n {
+        ok(a)
+      }
+
+      (ty::ty_enum(a_id, a_tps), ty::ty_enum(b_id, b_tps))
+      if a_id == b_id {
+        c_tps(self, a_id, a_tps, b_tps).chain {|tps|
+            ok(ty::mk_enum(tcx, a_id, tps))
+        }
+      }
+
+      (ty::ty_iface(a_id, a_tps), ty::ty_iface(b_id, b_tps))
+      if a_id == b_id {
+        c_tps(self, a_id, a_tps, b_tps).chain {|tps|
+            ok(ty::mk_iface(tcx, a_id, tps))
+        }
+      }
+
+      (ty::ty_class(a_id, a_tps), ty::ty_class(b_id, b_tps))
+      if a_id == b_id {
+        // FIXME variance
+        c_tps(self, a_id, a_tps, b_tps).chain {|tps|
+            ok(ty::mk_class(tcx, a_id, tps))
+        }
+      }
+
+      (ty::ty_box(a_mt), ty::ty_box(b_mt)) {
+        self.c_mts(a_mt, b_mt).chain {|mt|
+            ok(ty::mk_box(tcx, mt))
+        }
+      }
+
+      (ty::ty_uniq(a_mt), ty::ty_uniq(b_mt)) {
+        self.c_mts(a_mt, b_mt).chain {|mt|
+            ok(ty::mk_uniq(tcx, mt))
+        }
+      }
+
+      (ty::ty_vec(a_mt), ty::ty_vec(b_mt)) {
+        self.c_mts(a_mt, b_mt).chain {|mt|
+            ok(ty::mk_vec(tcx, mt))
+        }
+      }
+
+      (ty::ty_ptr(a_mt), ty::ty_ptr(b_mt)) {
+        self.c_mts(a_mt, b_mt).chain {|mt|
+            ok(ty::mk_ptr(tcx, mt))
+        }
+      }
+
+      (ty::ty_rptr(a_r, a_mt), ty::ty_rptr(b_r, b_mt)) {
+        self.c_regions(a_r, b_r).chain {|r|
+            self.c_mts(a_mt, b_mt).chain {|mt|
+                ok(ty::mk_rptr(tcx, r, mt))
+            }
+        }
+      }
+
+      (ty::ty_res(a_id, a_t, a_tps), ty::ty_res(b_id, b_t, b_tps))
+      if a_id == b_id {
+        self.c_tys(a_t, b_t).chain {|t|
+            c_tps(self, a_id, a_tps, b_tps).chain {|tps|
+                ok(ty::mk_res(tcx, a_id, t, tps))
+            }
+        }
+      }
+
+      (ty::ty_rec(a_fields), ty::ty_rec(b_fields)) {
+        c_fieldvecs(self, a_fields, b_fields).chain {|fs|
+            ok(ty::mk_rec(tcx, fs))
+        }
+      }
+
+      (ty::ty_tup(a_tys), ty::ty_tup(b_tys)) {
+        c_tuptys(self, a_tys, b_tys).chain {|ts|
+            ok(ty::mk_tup(tcx, ts))
+        }
+      }
+
+      (ty::ty_fn(a_fty), ty::ty_fn(b_fty)) {
+        c_fns(self, a_fty, b_fty).chain {|fty|
+            ok(ty::mk_fn(tcx, fty))
+        }
+      }
+
+      (ty::ty_constr(a_t, a_constrs), ty::ty_constr(b_t, b_constrs)) {
+        self.c_tys(a_t, b_t).chain {|t|
+            self.infcx().constrvecs(a_constrs, b_constrs).then {||
+                ok(ty::mk_constr(tcx, t, a_constrs))
+            }
+        }
+      }
+
+      _ { err(ty::terr_mismatch) }
+    }
+}
+
+impl of combine for lub {
+    fn infcx() -> infer_ctxt { *self }
+
+    fn tag() -> str { "lub" }
+
+    fn bnd<V:copy>(b: bounds<V>) -> option<V> {
+        b.ub
+    }
+
+    fn with_bnd<V:copy>(b: bounds<V>, v: V) -> bounds<V> {
+        assert b.ub == none;
+        {ub: some(v) with b}
+    }
+
+    fn c_bot(b: ty::t) -> cres<ty::t> {
+        ok(b)
+    }
+
+    fn c_regions(a: ty::region, _b: ty::region) -> cres<ty::region> {
+        ok(a) // FIXME
+    }
+
+    fn c_mts(a: ty::mt, b: ty::mt) -> cres<ty::mt> {
+        let tcx = self.infcx().tcx;
+
+        #debug("%s.c_mts(%s, %s)",
+               self.tag(),
+               mt_to_str(tcx, a),
+               mt_to_str(tcx, b));
+
+        let m = if a.mutbl == b.mutbl {
+            a.mutbl
+        } else {
+            ast::m_const
+        };
+
+        alt m {
+          ast::m_imm | ast::m_const {
+            self.c_tys(a.ty, b.ty).chain {|t|
+                ok({ty: t, mutbl: m})
+            }
+          }
+
+          ast::m_mutbl {
+            self.infcx().try {||
+                self.infcx().eq_tys(a.ty, b.ty).then {||
+                    ok({ty: a.ty, mutbl: m})
+                }
+            }.chain_err {|_e|
+                self.c_tys(a.ty, b.ty).chain {|t|
+                    ok({ty: t, mutbl: ast::m_const})
+                }
+            }
+          }
+        }
+    }
+
+    fn c_contratys(a: ty::t, b: ty::t) -> cres<ty::t> {
+        glb(self.infcx()).c_tys(a, b)
+    }
+
+    fn c_tys(a: ty::t, b: ty::t) -> cres<ty::t> {
+        c_tys(self, a, b)
+    }
+
+    fn c_protos(p1: ast::proto, p2: ast::proto) -> cres<ast::proto> {
+        if p1 == ast::proto_bare {
+            ok(p2)
+        } else if p2 == ast::proto_bare {
+            ok(p1)
+        } else if p1 == p2 {
+            ok(p1)
+        } else {
+            ok(ast::proto_any)
+        }
+    }
+
+    fn c_ret_styles(r1: ret_style, r2: ret_style) -> cres<ret_style> {
+        alt (r1, r2) {
+          (ast::return_val, _) |
+          (_, ast::return_val) {
+            ok(ast::return_val)
+          }
+          (ast::noreturn, ast::noreturn) {
+            ok(ast::noreturn)
+          }
+        }
+    }
+}
+
+impl of combine for glb {
+    fn infcx() -> infer_ctxt { *self }
+
+    fn tag() -> str { "glb" }
+
+    fn bnd<V:copy>(b: bounds<V>) -> option<V> {
+        b.lb
+    }
+
+    fn with_bnd<V:copy>(b: bounds<V>, v: V) -> bounds<V> {
+        assert b.lb == none;
+        {lb: some(v) with b}
+    }
+
+    fn c_bot(_b: ty::t) -> cres<ty::t> {
+        ok(ty::mk_bot(self.infcx().tcx))
+    }
+
+    fn c_regions(a: ty::region, _b: ty::region) -> cres<ty::region> {
+        ok(a) // FIXME
+    }
+
+    fn c_mts(a: ty::mt, b: ty::mt) -> cres<ty::mt> {
+        let tcx = self.infcx().tcx;
+
+        #debug("%s.c_mts(%s, %s)",
+               self.tag(),
+               mt_to_str(tcx, a),
+               mt_to_str(tcx, b));
+
+        alt (a.mutbl, b.mutbl) {
+          // If one side or both is mutable, then the GLB must use
+          // the precise type from the mutable side.
+          (ast::m_mutbl, ast::m_const) {
+            self.infcx().tys(a.ty, b.ty).then {||
+                ok({ty: a.ty, mutbl: ast::m_mutbl})
+            }
+          }
+          (ast::m_const, ast::m_mutbl) {
+            self.infcx().tys(b.ty, a.ty).then {||
+                ok({ty: b.ty, mutbl: ast::m_mutbl})
+            }
+          }
+          (ast::m_mutbl, ast::m_mutbl) {
+            self.infcx().eq_tys(a.ty, b.ty).then {||
+                ok({ty: a.ty, mutbl: ast::m_mutbl})
+            }
+          }
+
+          // If one side or both is immutable, we can use the GLB of
+          // both sides but mutbl must be `m_imm`.
+          (ast::m_imm, ast::m_const) |
+          (ast::m_const, ast::m_imm) |
+          (ast::m_imm, ast::m_imm) {
+            self.c_tys(a.ty, b.ty).chain {|t|
+                ok({ty: t, mutbl: ast::m_imm})
+            }
+          }
+
+          // If both sides are const, then we can use GLB of both
+          // sides and mutbl of only `m_const`.
+          (ast::m_const, ast::m_const) {
+            self.c_tys(a.ty, b.ty).chain {|t|
+                ok({ty: t, mutbl: ast::m_const})
+            }
+          }
+
+          // There is no mutual subtype of these combinations.
+          (ast::m_mutbl, ast::m_imm) |
+          (ast::m_imm, ast::m_mutbl) {
+              err(ty::terr_mutability)
+          }
+        }
+    }
+
+    fn c_contratys(a: ty::t, b: ty::t) -> cres<ty::t> {
+        lub(self.infcx()).c_tys(a, b)
+    }
+
+    fn c_tys(a: ty::t, b: ty::t) -> cres<ty::t> {
+        c_tys(self, a, b)
+    }
+
+    fn c_protos(p1: ast::proto, p2: ast::proto) -> cres<ast::proto> {
+        if p1 == ast::proto_any {
+            ok(p2)
+        } else if p2 == ast::proto_any {
+            ok(p1)
+        } else if p1 == p2 {
+            ok(p1)
+        } else {
+            ok(ast::proto_bare)
+        }
+    }
+
+    fn c_ret_styles(r1: ret_style, r2: ret_style) -> cres<ret_style> {
+        alt (r1, r2) {
+          (ast::return_val, ast::return_val) {
+            ok(ast::return_val)
+          }
+          (ast::noreturn, _) |
+          (_, ast::noreturn) {
+            ok(ast::noreturn)
+          }
+        }
+    }
+}
diff --git a/src/test/compile-fail/fn-variance-3.rs b/src/test/compile-fail/fn-variance-3.rs
index ab5b3b897a7..ff8511aeae5 100644
--- a/src/test/compile-fail/fn-variance-3.rs
+++ b/src/test/compile-fail/fn-variance-3.rs
@@ -12,10 +12,13 @@ fn main() {
 
     // @int <: X
     //
-    // Note: this is really an inference failure.
-    // The correct answer would be to make X
-    // equal to @const int, but we are not (yet)
-    // smart enough.
-    r(@3); //! ERROR (values differ in mutability)
+    // This constraint forces X to be
+    // @const int.
+    r(@3);
 
+    // Here the type check succeeds but the
+    // mutability check will fail, because the
+    // type of r has been inferred to be
+    // fn(@const int) -> @const int
+    *r(@mut 3) = 4; //! ERROR assigning to immutable box
 }
diff --git a/src/test/compile-fail/mode-inference-fail.rs b/src/test/compile-fail/mode-inference-fail.rs
index c5a7cba4ea0..732c9a4311f 100644
--- a/src/test/compile-fail/mode-inference-fail.rs
+++ b/src/test/compile-fail/mode-inference-fail.rs
@@ -7,5 +7,5 @@ fn apply_int(f: fn(int) -> int, a: int) -> int { f(a) }
 fn main() {
     let f = {|i| i};
     assert apply_int(f, 2) == 2;
-    assert apply(f, 2) == 2; //! ERROR expected argument mode &&
+    assert apply(f, 2) == 2; //! ERROR expected argument mode ++
 }