diff options
| author | Niko Matsakis <niko@alum.mit.edu> | 2012-03-25 13:54:05 -0700 |
|---|---|---|
| committer | Niko Matsakis <niko@alum.mit.edu> | 2012-03-26 10:34:58 -0700 |
| commit | 21111660ca7dbd95f9b0ee8c651062a607fe6345 (patch) | |
| tree | 64443ae02ec95793bbad1fff3c3b22da7304ad47 /src | |
| parent | 2112c391cd13a67cfbf9064b17a51640add5053e (diff) | |
Improve type inference to compute LUB/GLB
Diffstat (limited to 'src')
| -rw-r--r-- | src/libcore/result.rs | 57 | ||||
| -rw-r--r-- | src/libcore/vec.rs | 14 | ||||
| -rw-r--r-- | src/rustc/middle/infer.rs | 863 | ||||
| -rw-r--r-- | src/test/compile-fail/fn-variance-3.rs | 13 | ||||
| -rw-r--r-- | src/test/compile-fail/mode-inference-fail.rs | 2 |
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 ++ } |
