Implement new inference algorithm.
This commit is contained in:
parent
40443768b1
commit
042c532a08
@ -169,14 +169,14 @@ fn test_enumerate() {
|
||||
#[test]
|
||||
fn test_map_and_to_list() {
|
||||
let a = bind vec::iter([0, 1, 2], _);
|
||||
let b = bind map(a, {|i| i*2}, _);
|
||||
let b = bind map(a, {|i| 2*i}, _);
|
||||
let c = to_list(b);
|
||||
assert c == [0, 2, 4];
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_map_directly_on_vec() {
|
||||
let b = bind map([0, 1, 2], {|i| i*2}, _);
|
||||
let b = bind map([0, 1, 2], {|i| 2*i}, _);
|
||||
let c = to_list(b);
|
||||
assert c == [0, 2, 4];
|
||||
}
|
||||
|
@ -91,6 +91,24 @@ fn chain<T, U: copy, V: copy>(res: result<T, V>, op: fn(T) -> result<U, V>)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc = "
|
||||
Call a function based on a previous result
|
||||
|
||||
If `res` is `err` then the value is extracted and passed to `op`
|
||||
whereupon `op`s result is returned. if `res` is `ok` then it is
|
||||
immediately returned. This function can be used to pass through a
|
||||
successful result while handling an error.
|
||||
"]
|
||||
fn chain_err<T: copy, U: copy, V: copy>(
|
||||
res: result<T, V>,
|
||||
op: fn(V) -> result<T, U>)
|
||||
-> result<T, U> {
|
||||
alt res {
|
||||
ok(t) { ok(t) }
|
||||
err(v) { op(v) }
|
||||
}
|
||||
}
|
||||
|
||||
// ______________________________________________________________________
|
||||
// Note:
|
||||
//
|
||||
@ -171,6 +189,22 @@ fn map2<S,T,U:copy,V:copy,W>(ss: [S], ts: [T],
|
||||
ret nxt(vs);
|
||||
}
|
||||
|
||||
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 {
|
||||
alt op(ss[i],ts[i]) {
|
||||
ok(()) { }
|
||||
err(u) { ret err(u); }
|
||||
}
|
||||
i += 1u;
|
||||
}
|
||||
ret ok(());
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
fn op1() -> result::result<int, str> { result::ok(666) }
|
||||
|
@ -27,6 +27,7 @@ export rsplit;
|
||||
export rsplitn;
|
||||
export shift;
|
||||
export pop;
|
||||
export clear;
|
||||
export push;
|
||||
export grow;
|
||||
export grow_fn;
|
||||
@ -164,6 +165,13 @@ fn from_mut<T>(+v: [mutable T]) -> [T] unsafe {
|
||||
r
|
||||
}
|
||||
|
||||
// This function only exists to work around bugs in the type checker.
|
||||
fn from_const<T>(+v: [const T]) -> [T] unsafe {
|
||||
let r = ::unsafe::reinterpret_cast(v);
|
||||
::unsafe::forget(v);
|
||||
r
|
||||
}
|
||||
|
||||
// Accessors
|
||||
|
||||
#[doc = "Returns the first element of a vector"]
|
||||
@ -336,6 +344,14 @@ 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];
|
||||
@ -466,8 +482,8 @@ Concatenate a vector of vectors.
|
||||
Flattens a vector of vectors of T into a single vector of T.
|
||||
"]
|
||||
fn concat<T: copy>(v: [const [const T]]) -> [T] {
|
||||
let mut r: [T] = [];
|
||||
for inner: [T] in v { r += inner; }
|
||||
let mut r = [];
|
||||
for inner in v { r += from_const(inner); }
|
||||
ret r;
|
||||
}
|
||||
|
||||
@ -477,9 +493,9 @@ Concatenate a vector of vectors, placing a given separator between each
|
||||
fn connect<T: copy>(v: [const [const T]], sep: T) -> [T] {
|
||||
let mut r: [T] = [];
|
||||
let mut first = true;
|
||||
for inner: [T] in v {
|
||||
for inner in v {
|
||||
if first { first = false; } else { push(r, sep); }
|
||||
r += inner;
|
||||
r += from_const(inner);
|
||||
}
|
||||
ret r;
|
||||
}
|
||||
@ -885,7 +901,7 @@ fn as_mut_buf<E,T>(v: [mutable E], f: fn(*mutable E) -> T) -> T unsafe {
|
||||
}
|
||||
|
||||
#[doc = "An extension implementation providing a `len` method"]
|
||||
impl vec_len<T> for [T] {
|
||||
impl vec_len<T> for [const T] {
|
||||
#[doc = "Return the length of the vector"]
|
||||
#[inline(always)]
|
||||
fn len() -> uint { len(self) }
|
||||
|
@ -63,6 +63,7 @@ export opt_str;
|
||||
export opt_strs;
|
||||
export opt_maybe_str;
|
||||
export opt_default;
|
||||
export result; //NDM
|
||||
|
||||
enum name { long(str), short(char), }
|
||||
|
||||
|
@ -634,6 +634,7 @@ impl helpers for @e::encode_ctxt {
|
||||
fn ty_str_ctxt() -> @tyencode::ctxt {
|
||||
@{ds: e::def_to_str,
|
||||
tcx: self.ccx.tcx,
|
||||
reachable: self.ccx.reachable,
|
||||
abbrevs: tyencode::ac_use_abbrevs(self.type_abbrevs)}
|
||||
}
|
||||
}
|
||||
|
@ -221,6 +221,7 @@ fn encode_type_param_bounds(ebml_w: ebml::writer, ecx: @encode_ctxt,
|
||||
params: [ty_param]) {
|
||||
let ty_str_ctxt = @{ds: def_to_str,
|
||||
tcx: ecx.ccx.tcx,
|
||||
reachable: ecx.ccx.reachable,
|
||||
abbrevs: tyencode::ac_use_abbrevs(ecx.type_abbrevs)};
|
||||
for param in params {
|
||||
ebml_w.start_tag(tag_items_data_item_ty_param_bounds);
|
||||
@ -240,6 +241,7 @@ fn write_type(ecx: @encode_ctxt, ebml_w: ebml::writer, typ: ty::t) {
|
||||
let ty_str_ctxt =
|
||||
@{ds: def_to_str,
|
||||
tcx: ecx.ccx.tcx,
|
||||
reachable: ecx.ccx.reachable,
|
||||
abbrevs: tyencode::ac_use_abbrevs(ecx.type_abbrevs)};
|
||||
tyencode::enc_ty(ebml_w.writer, ty_str_ctxt, typ);
|
||||
}
|
||||
@ -966,7 +968,10 @@ fn encode_metadata(cx: @crate_ctxt, crate: @crate) -> [u8] {
|
||||
|
||||
// Get the encoded string for a type
|
||||
fn encoded_ty(tcx: ty::ctxt, t: ty::t) -> str {
|
||||
let cx = @{ds: def_to_str, tcx: tcx, abbrevs: tyencode::ac_no_abbrevs};
|
||||
let cx = @{ds: def_to_str,
|
||||
tcx: tcx,
|
||||
reachable: std::map::int_hash(),
|
||||
abbrevs: tyencode::ac_no_abbrevs};
|
||||
let buf = io::mem_buffer();
|
||||
tyencode::enc_ty(io::mem_buffer_writer(buf), cx, t);
|
||||
ret io::mem_buffer_str(buf);
|
||||
|
@ -6,6 +6,7 @@ import syntax::ast::*;
|
||||
import driver::session::session;
|
||||
import middle::ty;
|
||||
import syntax::print::pprust::*;
|
||||
import middle::trans::reachable;
|
||||
|
||||
export ctxt;
|
||||
export ty_abbrev;
|
||||
@ -18,7 +19,8 @@ export enc_mode;
|
||||
type ctxt =
|
||||
// Def -> str Callback:
|
||||
// The type context.
|
||||
{ds: fn@(def_id) -> str, tcx: ty::ctxt, abbrevs: abbrev_ctxt};
|
||||
{ds: fn@(def_id) -> str, tcx: ty::ctxt,
|
||||
reachable: reachable::map, abbrevs: abbrev_ctxt};
|
||||
|
||||
// Compact string representation for ty.t values. API ty_str & parse_from_str.
|
||||
// Extra parameters are for converting to/from def_ids in the string rep.
|
||||
@ -55,9 +57,14 @@ fn enc_ty(w: io::writer, cx: @ctxt, t: ty::t) {
|
||||
let pos = w.tell();
|
||||
alt ty::type_def_id(t) {
|
||||
some(def_id) {
|
||||
w.write_char('"');
|
||||
w.write_str(cx.ds(def_id));
|
||||
w.write_char('|');
|
||||
// Do not emit node ids that map to unexported names. Those
|
||||
// are not helpful.
|
||||
if def_id.crate != local_crate ||
|
||||
cx.reachable.contains_key(def_id.node) {
|
||||
w.write_char('"');
|
||||
w.write_str(cx.ds(def_id));
|
||||
w.write_char('|');
|
||||
}
|
||||
}
|
||||
_ {}
|
||||
}
|
||||
|
668
src/rustc/middle/infer.rs
Normal file
668
src/rustc/middle/infer.rs
Normal file
@ -0,0 +1,668 @@
|
||||
import std::smallintmap;
|
||||
import std::smallintmap::smallintmap;
|
||||
import std::smallintmap::map;
|
||||
import middle::ty;
|
||||
import syntax::ast;
|
||||
import util::ppaux::{ty_to_str, mt_to_str};
|
||||
import result::{result, chain, chain_err, ok, iter2};
|
||||
import ty::type_is_bot;
|
||||
|
||||
export infer_ctxt;
|
||||
export new_infer_ctxt;
|
||||
export mk_subty;
|
||||
export mk_eqty;
|
||||
export resolve_type_structure;
|
||||
export fixup_vars;
|
||||
export resolve_var;
|
||||
export compare_tys;
|
||||
|
||||
type bound = option<ty::t>;
|
||||
|
||||
type bounds = {lb: bound, ub: bound};
|
||||
|
||||
enum var_value {
|
||||
redirect(uint),
|
||||
bounded(bounds)
|
||||
}
|
||||
|
||||
enum infer_ctxt = @{
|
||||
tcx: ty::ctxt,
|
||||
vals: smallintmap<var_value>,
|
||||
mut bindings: [(uint, var_value)]
|
||||
};
|
||||
|
||||
type ures = result::result<(), ty::type_err>;
|
||||
type fres<T> = result::result<T,int>;
|
||||
|
||||
fn new_infer_ctxt(tcx: ty::ctxt) -> infer_ctxt {
|
||||
infer_ctxt(@{tcx: tcx,
|
||||
vals: smallintmap::mk(),
|
||||
mut bindings: []})
|
||||
}
|
||||
|
||||
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)];
|
||||
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)];
|
||||
cx.commit {||
|
||||
mk_subty(cx, a, b).then {||
|
||||
mk_subty(cx, b, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)];
|
||||
infcx.commit {||
|
||||
mk_subty(infcx, a, b).then {||
|
||||
mk_subty(infcx, b, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_type_structure(cx: infer_ctxt, a: ty::t) -> fres<ty::t> {
|
||||
cx.resolve_ty(a)
|
||||
}
|
||||
|
||||
fn resolve_var(cx: infer_ctxt, vid: int) -> fres<ty::t> {
|
||||
cx.fixup_vars(ty::mk_var(cx.tcx, vid))
|
||||
}
|
||||
|
||||
fn fixup_vars(cx: infer_ctxt, a: ty::t) -> fres<ty::t> {
|
||||
cx.fixup_vars(a)
|
||||
}
|
||||
|
||||
impl methods for ures {
|
||||
fn then<T:copy>(f: fn() -> result<T,ty::type_err>)
|
||||
-> result<T,ty::type_err> {
|
||||
chain(self) {|_i| f() }
|
||||
}
|
||||
}
|
||||
|
||||
impl unify_methods for infer_ctxt {
|
||||
fn uok() -> ures {
|
||||
#debug["Unification OK"];
|
||||
result::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 bound_to_str(b: bound) -> str {
|
||||
alt b {
|
||||
none { "none" }
|
||||
some(t) { self.ty_to_str(t) }
|
||||
}
|
||||
}
|
||||
|
||||
fn bounds_to_str(v: bounds) -> str {
|
||||
#fmt["{%s <: X <: %s}",
|
||||
self.bound_to_str(v.lb),
|
||||
self.bound_to_str(v.ub)]
|
||||
}
|
||||
|
||||
fn var_value_to_str(v: var_value) -> str {
|
||||
alt v {
|
||||
redirect(v) { #fmt["redirect(%u)", v] }
|
||||
bounded(b) { self.bounds_to_str(b) }
|
||||
}
|
||||
}
|
||||
|
||||
fn set(vid: uint, +new_v: var_value) {
|
||||
let old_v = self.vals.get(vid);
|
||||
vec::push(self.bindings, (vid, old_v));
|
||||
|
||||
#debug["Updating variable <T%u> from %s to %s",
|
||||
vid,
|
||||
self.var_value_to_str(old_v),
|
||||
self.var_value_to_str(new_v)];
|
||||
|
||||
self.vals.insert(vid, new_v);
|
||||
}
|
||||
|
||||
fn rollback_to(len: uint) {
|
||||
while self.bindings.len() != len {
|
||||
let (vid, old_v) = vec::pop(self.bindings);
|
||||
self.vals.insert(vid, old_v);
|
||||
}
|
||||
}
|
||||
|
||||
fn commit<T:copy,E:copy>(f: fn() -> result<T,E>) -> result<T,E> {
|
||||
assert self.bindings.len() == 0u;
|
||||
let r = self.try(f);
|
||||
vec::clear(self.bindings);
|
||||
ret r;
|
||||
}
|
||||
|
||||
fn try<T:copy,E:copy>(f: fn() -> result<T,E>) -> result<T,E> {
|
||||
let l = self.bindings.len();
|
||||
#debug["try(l=%u)", l];
|
||||
let r = f();
|
||||
alt r {
|
||||
result::ok(_) { #debug["try--ok"]; }
|
||||
result::err(_) { #debug["try--rollback"]; }
|
||||
}
|
||||
ret r;
|
||||
}
|
||||
|
||||
fn get(vid: uint) -> {root: uint, bounds:bounds} {
|
||||
alt self.vals.find(vid) {
|
||||
none {
|
||||
let bnds = {lb: none, ub: none};
|
||||
self.vals.insert(vid, bounded(bnds));
|
||||
{root: vid, bounds: bnds}
|
||||
}
|
||||
some(redirect(vid)) {
|
||||
let {root, bounds} = self.get(vid);
|
||||
if root != vid {
|
||||
self.vals.insert(vid, redirect(root));
|
||||
}
|
||||
{root: root, bounds: bounds}
|
||||
}
|
||||
some(bounded(bounds)) {
|
||||
{root: vid, bounds: bounds}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Take bound a if it is set, else take bound b.
|
||||
fn aelseb(a: bound, b: bound) -> bound {
|
||||
alt (a, b) {
|
||||
(none, none) { none }
|
||||
(some(_), none) { a }
|
||||
(none, some(_)) { b }
|
||||
(some(_), some(_)) { a }
|
||||
}
|
||||
}
|
||||
|
||||
// Combines the two bounds. Returns a bounds r where (r.lb <:
|
||||
// a,b) and (a,b <: r.ub).
|
||||
fn merge_bnds(a: bound, b: bound) -> result<bounds, ty::type_err> {
|
||||
alt (a, b) {
|
||||
(none, none) {
|
||||
ok({lb: none, ub: none})
|
||||
}
|
||||
(some(_), none) {
|
||||
ok({lb: a, ub: a})
|
||||
}
|
||||
(none, some(_)) {
|
||||
ok({lb: b, ub: 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})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Given a variable with bounds `a`, returns a new set of bounds
|
||||
// such that `a` <: `b`. The new bounds will always be a subset
|
||||
// of the old bounds. If this cannot be achieved, the result is
|
||||
// failure.
|
||||
fn merge(v_id: uint, a: bounds, b: bounds) -> 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):
|
||||
//
|
||||
// A A
|
||||
// / \ / \
|
||||
// / B \ / B \
|
||||
// / / \ \ / / \ \
|
||||
// * * * * * / * *
|
||||
// \ \ / / \ / /
|
||||
// \ B / / \ / /
|
||||
// \ / * \ /
|
||||
// A \ / A
|
||||
// B
|
||||
|
||||
#debug["merge(<T%u>,%s,%s)",
|
||||
v_id,
|
||||
self.bounds_to_str(a),
|
||||
self.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(v_id, bounded(bnds));
|
||||
self.uok()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn vars(a_id: uint, b_id: uint) -> ures {
|
||||
#debug["vars(<T%u> <: <T%u>)",
|
||||
a_id, b_id];
|
||||
|
||||
// Need to make sub_id a subtype of sup_id.
|
||||
let {root: a_id, bounds: a_bounds} = self.get(a_id);
|
||||
let {root: b_id, bounds: b_bounds} = self.get(b_id);
|
||||
|
||||
if a_id == b_id { ret self.uok(); }
|
||||
self.merge(a_id, a_bounds, b_bounds).then {||
|
||||
// For max perf, we should consider the rank here.
|
||||
self.set(b_id, redirect(a_id));
|
||||
self.uok()
|
||||
}
|
||||
}
|
||||
|
||||
fn varty(a_id: uint, b: ty::t) -> ures {
|
||||
#debug["varty(<T%u> <: %s)",
|
||||
a_id, self.ty_to_str(b)];
|
||||
let {root: a_id, bounds: a_bounds} = self.get(a_id);
|
||||
let b_bounds = {lb: none, ub: some(b)};
|
||||
self.merge(a_id, a_bounds, b_bounds)
|
||||
}
|
||||
|
||||
fn tyvar(a: ty::t, b_id: uint) -> ures {
|
||||
#debug["tyvar(%s <: <T%u>)",
|
||||
self.ty_to_str(a), b_id];
|
||||
let a_bounds = {lb: some(a), ub: none};
|
||||
let {root: b_id, bounds: b_bounds} = self.get(b_id);
|
||||
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) }
|
||||
}
|
||||
|
||||
fn regions(a: ty::region, b: ty::region) -> ures {
|
||||
// FIXME: This is wrong. We should be keeping a set of region
|
||||
// bindings around.
|
||||
alt (a, b) {
|
||||
(ty::re_param(_), _) | (_, ty::re_param(_)) {
|
||||
ret if a == b {
|
||||
self.uok()
|
||||
} else {
|
||||
self.uerr(ty::terr_regions_differ(true, b, a))
|
||||
};
|
||||
}
|
||||
_ { /* fall through */ }
|
||||
}
|
||||
|
||||
let bscope = region::region_to_scope(self.tcx.region_map, b);
|
||||
let ascope = region::region_to_scope(self.tcx.region_map, a);
|
||||
if region::scope_contains(self.tcx.region_map, ascope, bscope) {
|
||||
self.uok()
|
||||
} else {
|
||||
self.uerr(ty::terr_regions_differ(false, a, b))
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
if a.mutbl != b.mutbl && b.mutbl != ast::m_const {
|
||||
ret self.uerr(ty::terr_mutability);
|
||||
}
|
||||
|
||||
alt b.mutbl {
|
||||
ast::m_mutbl {
|
||||
// If supertype is mutable, subtype must mtach exactly
|
||||
// (i.e., invariant if mutable):
|
||||
self.tys(a.ty, b.ty).then {||
|
||||
self.tys(b.ty, a.ty)
|
||||
}
|
||||
}
|
||||
ast::m_imm | ast::m_const {
|
||||
// Otherwise we can be covariant:
|
||||
self.tys(a.ty, b.ty)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn flds(a: ty::field, b: ty::field) -> ures {
|
||||
if a.ident != b.ident {
|
||||
ret self.uerr(ty::terr_record_fields(a.ident, b.ident));
|
||||
}
|
||||
self.mts(a.mt, b.mt)
|
||||
}
|
||||
|
||||
fn tps(as: [ty::t], bs: [ty::t]) -> ures {
|
||||
if check vec::same_length(as, bs) {
|
||||
self.tyvecs(as, bs)
|
||||
} else {
|
||||
self.uerr(ty::terr_ty_param_size(as.len(), bs.len()))
|
||||
}
|
||||
}
|
||||
|
||||
fn protos(a: ast::proto, b: ast::proto) -> ures {
|
||||
alt (a, b) {
|
||||
(_, ast::proto_any) { self.uok() }
|
||||
(ast::proto_bare, _) { self.uok() }
|
||||
(_, _) if a == b { self.uok() }
|
||||
_ { self.uerr(ty::terr_proto_mismatch(a, b)) }
|
||||
}
|
||||
}
|
||||
|
||||
fn ret_styles(
|
||||
a_ret_style: ast::ret_style,
|
||||
b_ret_style: ast::ret_style) -> ures {
|
||||
|
||||
if b_ret_style != ast::noreturn && b_ret_style != a_ret_style {
|
||||
/* even though typestate checking is mostly
|
||||
responsible for checking control flow annotations,
|
||||
this check is necessary to ensure that the
|
||||
annotation in an object method matches the
|
||||
declared object type */
|
||||
self.uerr(ty::terr_ret_style_mismatch(a_ret_style, b_ret_style))
|
||||
} else {
|
||||
self.uok()
|
||||
}
|
||||
}
|
||||
|
||||
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) }
|
||||
}
|
||||
}
|
||||
|
||||
fn args(a: ty::arg, b: ty::arg) -> ures {
|
||||
self.modes(a.mode, b.mode).then {||
|
||||
self.tys(b.ty, a.ty) // Note: contravariant
|
||||
}
|
||||
}
|
||||
|
||||
fn argvecs(
|
||||
a_args: [ty::arg],
|
||||
b_args: [ty::arg]) -> ures {
|
||||
|
||||
if check vec::same_length(a_args, b_args) {
|
||||
iter2(a_args, b_args) {|a, b| self.args(a, b) }
|
||||
} else {
|
||||
ret self.uerr(ty::terr_arg_count);
|
||||
}
|
||||
}
|
||||
|
||||
fn fns(a_f: ty::fn_ty, b_f: ty::fn_ty) -> ures {
|
||||
self.protos(a_f.proto, b_f.proto).then {||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn constrs(
|
||||
expected: @ty::type_constr,
|
||||
actual_constr: @ty::type_constr) -> ures {
|
||||
|
||||
let err_res =
|
||||
self.uerr(ty::terr_constr_mismatch(expected, actual_constr));
|
||||
|
||||
if expected.node.id != actual_constr.node.id { ret err_res; }
|
||||
let expected_arg_len = vec::len(expected.node.args);
|
||||
let actual_arg_len = vec::len(actual_constr.node.args);
|
||||
if expected_arg_len != actual_arg_len { ret err_res; }
|
||||
let mut i = 0u;
|
||||
for a in expected.node.args {
|
||||
let actual = actual_constr.node.args[i];
|
||||
alt a.node {
|
||||
ast::carg_base {
|
||||
alt actual.node {
|
||||
ast::carg_base { }
|
||||
_ { ret err_res; }
|
||||
}
|
||||
}
|
||||
ast::carg_lit(l) {
|
||||
alt actual.node {
|
||||
ast::carg_lit(m) {
|
||||
if l != m { ret err_res; }
|
||||
}
|
||||
_ { ret err_res; }
|
||||
}
|
||||
}
|
||||
ast::carg_ident(p) {
|
||||
alt actual.node {
|
||||
ast::carg_ident(q) {
|
||||
if p.node != q.node { ret err_res; }
|
||||
}
|
||||
_ { ret err_res; }
|
||||
}
|
||||
}
|
||||
}
|
||||
i += 1u;
|
||||
}
|
||||
ret self.uok();
|
||||
}
|
||||
|
||||
fn bnds(a: bound, b: bound) -> ures {
|
||||
#debug("bnds(%s <: %s)",
|
||||
self.bound_to_str(a),
|
||||
self.bound_to_str(b));
|
||||
|
||||
alt (a, b) {
|
||||
(none, none) |
|
||||
(some(_), none) |
|
||||
(none, some(_)) { self.uok() }
|
||||
(some(t_a), some(t_b)) { self.tys(t_a, t_b) }
|
||||
}
|
||||
}
|
||||
|
||||
fn tys(a: ty::t, b: ty::t) -> ures {
|
||||
#debug("tys(%s <: %s)",
|
||||
ty_to_str(self.tcx, a),
|
||||
ty_to_str(self.tcx, b));
|
||||
|
||||
// Fast path.
|
||||
if a == b { ret self.uok(); }
|
||||
|
||||
alt (ty::get(a).struct, ty::get(b).struct) {
|
||||
(ty::ty_var(a_id), ty::ty_var(b_id)) {
|
||||
self.vars(a_id as uint, b_id as uint)
|
||||
}
|
||||
(ty::ty_var(a_id), _) {
|
||||
self.varty(a_id as uint, b)
|
||||
}
|
||||
(_, ty::ty_var(b_id)) {
|
||||
self.tyvar(a, b_id as uint)
|
||||
}
|
||||
|
||||
(_, ty::ty_bot) { self.uok() }
|
||||
(ty::ty_bot, _) { self.uok() }
|
||||
|
||||
(ty::ty_nil, _) |
|
||||
(ty::ty_bool, _) |
|
||||
(ty::ty_int(_), _) |
|
||||
(ty::ty_uint(_), _) |
|
||||
(ty::ty_float(_), _) |
|
||||
(ty::ty_str, _) {
|
||||
let cfg = self.tcx.sess.targ_cfg;
|
||||
if ty::mach_sty(cfg, a) == ty::mach_sty(cfg, b) {
|
||||
self.uok()
|
||||
} else {
|
||||
self.uerr(ty::terr_mismatch)
|
||||
}
|
||||
}
|
||||
|
||||
(ty::ty_param(a_n, _), ty::ty_param(b_n, _))
|
||||
if a_n == b_n {
|
||||
self.uok()
|
||||
}
|
||||
|
||||
(ty::ty_enum(a_id, a_tps), ty::ty_enum(b_id, b_tps)) |
|
||||
(ty::ty_iface(a_id, a_tps), ty::ty_iface(b_id, b_tps)) |
|
||||
(ty::ty_class(a_id, a_tps), ty::ty_class(b_id, b_tps))
|
||||
if a_id == b_id {
|
||||
self.tps(a_tps, b_tps)
|
||||
}
|
||||
|
||||
(ty::ty_box(a_mt), ty::ty_box(b_mt)) |
|
||||
(ty::ty_uniq(a_mt), ty::ty_uniq(b_mt)) |
|
||||
(ty::ty_vec(a_mt), ty::ty_vec(b_mt)) |
|
||||
(ty::ty_ptr(a_mt), ty::ty_ptr(b_mt)) {
|
||||
self.mts(a_mt, b_mt)
|
||||
}
|
||||
|
||||
(ty::ty_rptr(a_r, a_mt), ty::ty_rptr(b_r, b_mt)) {
|
||||
self.mts(a_mt, b_mt).then {||
|
||||
self.regions(a_r, b_r)
|
||||
}
|
||||
}
|
||||
|
||||
(ty::ty_res(a_id, a_t, a_tps), ty::ty_res(b_id, b_t, b_tps))
|
||||
if a_id == b_id {
|
||||
self.tys(a_t, b_t).then {||
|
||||
self.tps(a_tps, b_tps)
|
||||
}
|
||||
}
|
||||
|
||||
(ty::ty_rec(a_fields), ty::ty_rec(b_fields)) {
|
||||
if check vec::same_length(a_fields, b_fields) {
|
||||
iter2(a_fields, b_fields) {|a,b|
|
||||
self.flds(a, b)
|
||||
}
|
||||
} else {
|
||||
ret self.uerr(ty::terr_record_size(a_fields.len(),
|
||||
b_fields.len()));
|
||||
}
|
||||
}
|
||||
|
||||
(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)
|
||||
} else {
|
||||
self.uerr(ty::terr_tuple_size(a_tys.len(), b_tys.len()))
|
||||
}
|
||||
}
|
||||
|
||||
(ty::ty_fn(a_fty), ty::ty_fn(b_fty)) {
|
||||
self.fns(a_fty, b_fty)
|
||||
}
|
||||
|
||||
(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 {
|
||||
ret self.uerr(ty::terr_constr_len(a_constrs.len(),
|
||||
b_constrs.len()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ { self.uerr(ty::terr_mismatch) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
fn rerr(v: int) -> fres<ty::t> {
|
||||
#debug["Resolve error: %?", v];
|
||||
result::err(v)
|
||||
}
|
||||
|
||||
fn resolve_var(vid: int) -> fres<ty::t> {
|
||||
let {root:_, bounds} = self.get(vid as uint);
|
||||
|
||||
// Nonobvious: prefer the most specific type
|
||||
// (i.e., the lower bound) to the more general
|
||||
// one. More general types in Rust (e.g., fn())
|
||||
// tend to carry more restrictions or higher
|
||||
// perf. penalties, so it pays to know more.
|
||||
|
||||
alt bounds {
|
||||
{ ub:_, lb:some(t) } if !type_is_bot(t) { self.rok(t) }
|
||||
{ ub:some(t), lb:_ } { self.rok(t) }
|
||||
{ ub:_, lb:some(t) } { self.rok(t) }
|
||||
{ ub:none, lb:none } { self.rerr(vid) }
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_ty(typ: ty::t) -> fres<ty::t> {
|
||||
alt ty::get(typ).struct {
|
||||
ty::ty_var(vid) { self.resolve_var(vid) }
|
||||
_ { self.rok(typ) }
|
||||
}
|
||||
}
|
||||
|
||||
fn subst_vars(unresolved: @mutable option<int>,
|
||||
vars_seen: std::list::list<int>,
|
||||
vid: int) -> ty::t {
|
||||
// Should really return a fixup_result instead of a t, but fold_ty
|
||||
// doesn't allow returning anything but a t.
|
||||
alt self.resolve_var(vid) {
|
||||
result::err(vid) {
|
||||
*unresolved = some(vid);
|
||||
ret ty::mk_var(self.tcx, vid);
|
||||
}
|
||||
result::ok(rt) {
|
||||
let mut give_up = false;
|
||||
std::list::iter(vars_seen) {|v|
|
||||
if v == vid {
|
||||
*unresolved = some(-1); // hack: communicate inf ty
|
||||
give_up = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Return the type unchanged, so we can error out
|
||||
// downstream
|
||||
if give_up { ret rt; }
|
||||
ret ty::fold_ty(self.tcx,
|
||||
ty::fm_var(
|
||||
self.subst_vars(
|
||||
unresolved,
|
||||
std::list::cons(vid, @vars_seen),
|
||||
_)),
|
||||
rt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fixup_vars(typ: ty::t) -> fres<ty::t> {
|
||||
let unresolved = @mutable none::<int>;
|
||||
let rty =
|
||||
ty::fold_ty(self.tcx,
|
||||
ty::fm_var(
|
||||
self.subst_vars(
|
||||
unresolved,
|
||||
std::list::nil,
|
||||
_)),
|
||||
typ);
|
||||
|
||||
let ur = *unresolved;
|
||||
alt ur {
|
||||
none { ret self.rok(rty); }
|
||||
some(var_id) { ret self.rerr(var_id); }
|
||||
}
|
||||
}
|
||||
}
|
@ -37,7 +37,7 @@ export field;
|
||||
export field_idx;
|
||||
export get_field;
|
||||
export get_fields;
|
||||
export fm_general, fm_rptr;
|
||||
export fm_var, fm_general, fm_rptr;
|
||||
export get_element_type;
|
||||
export is_binopable;
|
||||
export is_pred_ty;
|
||||
@ -140,6 +140,7 @@ export item_path;
|
||||
export item_path_str;
|
||||
export ast_ty_to_ty_cache_entry;
|
||||
export atttce_unresolved, atttce_resolved;
|
||||
export mach_sty;
|
||||
|
||||
// Data types
|
||||
|
||||
@ -304,6 +305,8 @@ type constr = constr_general<uint>;
|
||||
enum type_err {
|
||||
terr_mismatch,
|
||||
terr_ret_style_mismatch(ast::ret_style, ast::ret_style),
|
||||
terr_mutability,
|
||||
terr_proto_mismatch(ast::proto, ast::proto),
|
||||
terr_box_mutability,
|
||||
terr_ptr_mutability,
|
||||
terr_ref_mutability,
|
||||
@ -2360,6 +2363,11 @@ fn type_err_to_str(cx: ctxt, err: type_err) -> str {
|
||||
ret to_str(actual) + " function found where " + to_str(expect) +
|
||||
" function was expected";
|
||||
}
|
||||
terr_proto_mismatch(e, a) {
|
||||
ret #fmt["closure protocol mismatch (%s vs %s)",
|
||||
proto_to_str(e), proto_to_str(a)];
|
||||
}
|
||||
terr_mutability { ret "values differ in mutability"; }
|
||||
terr_box_mutability { ret "boxed values differ in mutability"; }
|
||||
terr_vec_mutability { ret "vectors differ in mutability"; }
|
||||
terr_ptr_mutability { ret "pointers differ in mutability"; }
|
||||
|
@ -73,7 +73,7 @@ type fn_ctxt =
|
||||
{ret_ty: ty::t,
|
||||
purity: ast::purity,
|
||||
proto: ast::proto,
|
||||
var_bindings: @ty::unify::var_bindings,
|
||||
infcx: infer::infer_ctxt,
|
||||
locals: hashmap<ast::node_id, int>,
|
||||
next_var_id: @mutable int,
|
||||
ccx: @crate_ctxt};
|
||||
@ -206,7 +206,7 @@ fn instantiate_path(fcx: @fn_ctxt, pth: @ast::path,
|
||||
|
||||
// Type tests
|
||||
fn structurally_resolved_type(fcx: @fn_ctxt, sp: span, tp: ty::t) -> ty::t {
|
||||
alt ty::unify::resolve_type_structure(fcx.var_bindings, tp) {
|
||||
alt infer::resolve_type_structure(fcx.infcx, tp) {
|
||||
result::ok(typ_s) { ret typ_s; }
|
||||
result::err(_) {
|
||||
fcx.ccx.tcx.sess.span_fatal
|
||||
@ -225,7 +225,7 @@ fn structure_of(fcx: @fn_ctxt, sp: span, typ: ty::t) -> ty::sty {
|
||||
// is not known yet.
|
||||
fn structure_of_maybe(fcx: @fn_ctxt, _sp: span, typ: ty::t) ->
|
||||
option<ty::sty> {
|
||||
let r = ty::unify::resolve_type_structure(fcx.var_bindings, typ);
|
||||
let r = infer::resolve_type_structure(fcx.infcx, typ);
|
||||
alt r {
|
||||
result::ok(typ_s) { some(ty::get(typ_s).struct) }
|
||||
result::err(_) { none }
|
||||
@ -798,15 +798,15 @@ fn compare_impl_method(tcx: ty::ctxt, sp: span, impl_m: ty::method,
|
||||
if_fty = fixup_self_in_method_ty(tcx, if_fty, substs,
|
||||
self_full(self_ty, impl_tps));
|
||||
}
|
||||
alt ty::unify::unify(impl_fty, if_fty, ty::unify::precise, tcx) {
|
||||
alt infer::compare_tys(tcx, impl_fty, if_fty) {
|
||||
result::err(err) {
|
||||
tcx.sess.span_err(sp, "method `" + if_m.ident +
|
||||
"` has an incompatible type: " +
|
||||
ty::type_err_to_str(tcx, err));
|
||||
impl_fty
|
||||
}
|
||||
result::ok(tp) { tp }
|
||||
result::ok(()) { }
|
||||
}
|
||||
ret impl_fty;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1132,16 +1132,14 @@ mod unify {
|
||||
rb: @ty::unify::region_bindings,
|
||||
expected: ty::t,
|
||||
actual: ty::t)
|
||||
-> result<ty::t, ty::type_err> {
|
||||
let irb = ty::unify::in_region_bindings(fcx.var_bindings, rb);
|
||||
ret ty::unify::unify(expected, actual, irb, fcx.ccx.tcx);
|
||||
-> result<(), ty::type_err> {
|
||||
//let irb = ty::unify::in_region_bindings(fcx.var_bindings, rb);
|
||||
ret infer::mk_subty(fcx.infcx, actual, expected);
|
||||
}
|
||||
|
||||
fn unify(fcx: @fn_ctxt, expected: ty::t, actual: ty::t) ->
|
||||
result<ty::t, ty::type_err> {
|
||||
ret ty::unify::unify(expected, actual,
|
||||
ty::unify::in_bindings(fcx.var_bindings),
|
||||
fcx.ccx.tcx);
|
||||
result<(), ty::type_err> {
|
||||
ret infer::mk_subty(fcx.infcx, actual, expected);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1180,13 +1178,12 @@ fn do_autoderef(fcx: @fn_ctxt, sp: span, t: ty::t) -> ty::t {
|
||||
}
|
||||
|
||||
fn resolve_type_vars_if_possible(fcx: @fn_ctxt, typ: ty::t) -> ty::t {
|
||||
alt ty::unify::fixup_vars(fcx.ccx.tcx, none, fcx.var_bindings, typ) {
|
||||
alt infer::fixup_vars(fcx.infcx, typ) {
|
||||
result::ok(new_type) { ret new_type; }
|
||||
result::err(_) { ret typ; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Demands - procedures that require that two types unify and emit an error
|
||||
// message if they don't.
|
||||
type ty_param_substs_and_ty = {substs: [ty::t], ty: ty::t};
|
||||
@ -1197,6 +1194,11 @@ mod demand {
|
||||
full(fcx, sp, unify::unify, expected, actual, []).ty
|
||||
}
|
||||
|
||||
// n.b.: order of arguments is reversed.
|
||||
fn subty(fcx: @fn_ctxt, sp: span, actual: ty::t, expected: ty::t) {
|
||||
full(fcx, sp, unify::unify, expected, actual, []);
|
||||
}
|
||||
|
||||
fn with_region_bindings(fcx: @fn_ctxt,
|
||||
sp: span,
|
||||
rb: @ty::unify::region_bindings,
|
||||
@ -1217,7 +1219,7 @@ mod demand {
|
||||
fn full(fcx: @fn_ctxt,
|
||||
sp: span,
|
||||
unifier: fn@(@fn_ctxt, ty::t, ty::t)
|
||||
-> result<ty::t, ty::type_err>,
|
||||
-> result<(), ty::type_err>,
|
||||
expected: ty::t,
|
||||
actual: ty::t,
|
||||
ty_param_substs_0: [ty::t]) ->
|
||||
@ -1247,7 +1249,9 @@ mod demand {
|
||||
|
||||
|
||||
alt unifier(fcx, expected, actual) {
|
||||
result::ok(t) { ret mk_result(fcx, t, ty_param_subst_var_ids); }
|
||||
result::ok(()) {
|
||||
ret mk_result(fcx, expected, ty_param_subst_var_ids);
|
||||
}
|
||||
result::err(err) {
|
||||
let e_err = resolve_type_vars_if_possible(fcx, expected);
|
||||
let a_err = resolve_type_vars_if_possible(fcx, actual);
|
||||
@ -1311,9 +1315,14 @@ mod writeback {
|
||||
fn resolve_type_vars_in_type(fcx: @fn_ctxt, sp: span, typ: ty::t) ->
|
||||
option<ty::t> {
|
||||
if !ty::type_has_vars(typ) { ret some(typ); }
|
||||
alt ty::unify::fixup_vars(fcx.ccx.tcx, some(sp), fcx.var_bindings,
|
||||
typ) {
|
||||
alt infer::fixup_vars(fcx.infcx, typ) {
|
||||
result::ok(new_type) { ret some(new_type); }
|
||||
result::err(-1) {
|
||||
fcx.ccx.tcx.sess.span_err(
|
||||
sp,
|
||||
"can not instantiate infinite type");
|
||||
ret none;
|
||||
}
|
||||
result::err(vid) {
|
||||
if !fcx.ccx.tcx.sess.has_errors() {
|
||||
fcx.ccx.tcx.sess.span_err(sp, "cannot determine a type \
|
||||
@ -1396,16 +1405,29 @@ mod writeback {
|
||||
fn visit_pat(p: @ast::pat, wbcx: wb_ctxt, v: wb_vt) {
|
||||
if !wbcx.success { ret; }
|
||||
resolve_type_vars_for_node(wbcx, p.span, p.id);
|
||||
#debug["Type for pattern binding %s (id %d) resolved to %s",
|
||||
pat_to_str(p), p.id,
|
||||
ty_to_str(wbcx.fcx.ccx.tcx,
|
||||
ty::node_id_to_type(wbcx.fcx.ccx.tcx,
|
||||
p.id))];
|
||||
visit::visit_pat(p, wbcx, v);
|
||||
}
|
||||
fn visit_local(l: @ast::local, wbcx: wb_ctxt, v: wb_vt) {
|
||||
if !wbcx.success { ret; }
|
||||
let var_id = lookup_local(wbcx.fcx, l.span, l.node.id);
|
||||
let fix_rslt =
|
||||
ty::unify::resolve_type_var(wbcx.fcx.ccx.tcx, some(l.span),
|
||||
wbcx.fcx.var_bindings, var_id);
|
||||
alt fix_rslt {
|
||||
result::ok(lty) { write_ty(wbcx.fcx.ccx.tcx, l.node.id, lty); }
|
||||
alt infer::resolve_var(wbcx.fcx.infcx, var_id) {
|
||||
result::ok(lty) {
|
||||
#debug["Type for local %s (id %d) resolved to %s",
|
||||
pat_to_str(l.node.pat), l.node.id,
|
||||
ty_to_str(wbcx.fcx.ccx.tcx, lty)];
|
||||
write_ty(wbcx.fcx.ccx.tcx, l.node.id, lty);
|
||||
}
|
||||
result::err(-1) {
|
||||
wbcx.fcx.ccx.tcx.sess.span_err(
|
||||
l.span,
|
||||
"this local variable has a type of infinite size");
|
||||
wbcx.success = false;
|
||||
}
|
||||
result::err(_) {
|
||||
wbcx.fcx.ccx.tcx.sess.span_err(l.span,
|
||||
"cannot determine a type \
|
||||
@ -1490,7 +1512,7 @@ fn check_intrinsic_type(tcx: ty::ctxt, it: @ast::native_item) {
|
||||
// Local variable gathering. We gather up all locals and create variable IDs
|
||||
// for them before typechecking the function.
|
||||
type gather_result =
|
||||
{var_bindings: @ty::unify::var_bindings,
|
||||
{infcx: infer::infer_ctxt,
|
||||
locals: hashmap<ast::node_id, int>,
|
||||
next_var_id: @mutable int};
|
||||
|
||||
@ -1500,14 +1522,14 @@ fn gather_locals(ccx: @crate_ctxt,
|
||||
body: ast::blk,
|
||||
id: ast::node_id,
|
||||
old_fcx: option<@fn_ctxt>) -> gather_result {
|
||||
let {vb: vb, locals: locals, nvi: nvi} = alt old_fcx {
|
||||
let {infcx, locals, nvi} = alt old_fcx {
|
||||
none {
|
||||
{vb: ty::unify::mk_var_bindings(),
|
||||
{infcx: infer::new_infer_ctxt(ccx.tcx),
|
||||
locals: int_hash::<int>(),
|
||||
nvi: @mutable 0}
|
||||
}
|
||||
some(fcx) {
|
||||
{vb: fcx.var_bindings,
|
||||
{infcx: fcx.infcx,
|
||||
locals: fcx.locals,
|
||||
nvi: fcx.next_var_id}
|
||||
}
|
||||
@ -1522,8 +1544,7 @@ fn gather_locals(ccx: @crate_ctxt,
|
||||
alt ty_opt {
|
||||
none {/* nothing to do */ }
|
||||
some(typ) {
|
||||
ty::unify::unify(ty::mk_var(tcx, var_id), typ,
|
||||
ty::unify::in_bindings(vb), tcx);
|
||||
infer::mk_eqty(infcx, ty::mk_var(tcx, var_id), typ);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -1533,6 +1554,8 @@ fn gather_locals(ccx: @crate_ctxt,
|
||||
let mut i = 0u;
|
||||
for arg: ty::arg in args {
|
||||
assign(decl.inputs[i].id, some(arg.ty));
|
||||
#debug["Argument %s is assigned to <T%d>",
|
||||
decl.inputs[i].ident, locals.get(decl.inputs[i].id)];
|
||||
i += 1u;
|
||||
}
|
||||
|
||||
@ -1548,15 +1571,19 @@ fn gather_locals(ccx: @crate_ctxt,
|
||||
}
|
||||
|
||||
assign(local.node.id, local_ty_opt);
|
||||
#debug["Local variable %s is assigned to <T%d>",
|
||||
pat_to_str(local.node.pat), locals.get(local.node.id)];
|
||||
visit::visit_local(local, e, v);
|
||||
};
|
||||
|
||||
// Add pattern bindings.
|
||||
let visit_pat = fn@(p: @ast::pat, &&e: (), v: visit::vt<()>) {
|
||||
alt p.node {
|
||||
ast::pat_ident(_, _)
|
||||
ast::pat_ident(path, _)
|
||||
if !pat_util::pat_is_variant(ccx.tcx.def_map, p) {
|
||||
assign(p.id, none);
|
||||
#debug["Pattern binding %s is assigned to <T%d>",
|
||||
path.node.idents[0], locals.get(p.id)];
|
||||
}
|
||||
_ {}
|
||||
}
|
||||
@ -1577,7 +1604,7 @@ fn gather_locals(ccx: @crate_ctxt,
|
||||
with *visit::default_visitor()};
|
||||
|
||||
visit::visit_block(body, (), visit::mk_vt(visit));
|
||||
ret {var_bindings: vb,
|
||||
ret {infcx: infcx,
|
||||
locals: locals,
|
||||
next_var_id: nvi};
|
||||
}
|
||||
@ -2419,19 +2446,15 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, expr: @ast::expr, unify: unifier,
|
||||
element_ty: ty::t, body: ast::blk,
|
||||
node_id: ast::node_id) -> bool {
|
||||
let locid = lookup_local(fcx, local.span, local.node.id);
|
||||
let element_ty = demand::simple(fcx, local.span, element_ty,
|
||||
ty::mk_var(fcx.ccx.tcx, locid));
|
||||
demand::simple(fcx, local.span,
|
||||
ty::mk_var(fcx.ccx.tcx, locid),
|
||||
element_ty);
|
||||
let bot = check_decl_local(fcx, local);
|
||||
check_block_no_value(fcx, body);
|
||||
// Unify type of decl with element type of the seq
|
||||
demand::simple(fcx, local.span,
|
||||
ty::node_id_to_type(fcx.ccx.tcx, local.node.id),
|
||||
element_ty);
|
||||
write_nil(fcx.ccx.tcx, node_id);
|
||||
ret bot;
|
||||
}
|
||||
|
||||
|
||||
// A generic function for checking the then and else in an if
|
||||
// or if-check
|
||||
fn check_then_else(fcx: @fn_ctxt, thn: ast::blk,
|
||||
@ -2470,49 +2493,108 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, expr: @ast::expr, unify: unifier,
|
||||
}
|
||||
fn lookup_op_method(fcx: @fn_ctxt, op_ex: @ast::expr, self_t: ty::t,
|
||||
opname: str, args: [option<@ast::expr>])
|
||||
-> option<ty::t> {
|
||||
-> option<(ty::t, bool)> {
|
||||
let callee_id = ast_util::op_expr_callee_id(op_ex);
|
||||
alt lookup_method(fcx, op_ex, callee_id, opname, self_t, []) {
|
||||
some(origin) {
|
||||
let method_ty = ty::node_id_to_type(fcx.ccx.tcx, callee_id);
|
||||
check_call_or_bind(fcx, op_ex.span, method_ty, args);
|
||||
let r = check_call_or_bind(fcx, op_ex.span, method_ty, args);
|
||||
fcx.ccx.method_map.insert(op_ex.id, origin);
|
||||
some(ty::ty_fn_ret(method_ty))
|
||||
some((ty::ty_fn_ret(method_ty), r.bot))
|
||||
}
|
||||
_ { 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);
|
||||
// could be either a expr_binop or an expr_assign_binop
|
||||
fn check_binop(fcx: @fn_ctxt, expr: @ast::expr,
|
||||
op: ast::binop,
|
||||
lhs: @ast::expr,
|
||||
rhs: @ast::expr) -> bool {
|
||||
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 lhs_bot = check_expr(fcx, lhs);
|
||||
let lhs_t = expr_ty(tcx, lhs);
|
||||
let lhs_t = structurally_resolved_type(fcx, lhs.span, lhs_t);
|
||||
ret alt (op, ty::get(lhs_t).struct) {
|
||||
(ast::add, ty::ty_vec(lhs_mt)) {
|
||||
// For adding vectors with type L=[M TL] and R=[M TR], the result
|
||||
// is somewhat subtle. Let L_c=[const TL] and R_c=[const TR] be
|
||||
// const versions of the vectors in L and R. Next, let T be a
|
||||
// fresh type variable where TL <: T and TR <: T. Then the result
|
||||
// type is a fresh type variable T1 where T1 <: [const T]. This
|
||||
// allows the result to be either a mutable or immutable vector,
|
||||
// depending on external demands.
|
||||
let const_vec_t =
|
||||
ty::mk_vec(tcx, {ty: next_ty_var(fcx),
|
||||
mutbl: ast::m_const});
|
||||
demand::simple(fcx, lhs.span, const_vec_t, lhs_t);
|
||||
let rhs_bot = check_expr_with(fcx, rhs, const_vec_t);
|
||||
let result_var = next_ty_var(fcx);
|
||||
demand::simple(fcx, lhs.span, const_vec_t, result_var);
|
||||
write_ty(tcx, expr.id, result_var);
|
||||
lhs_bot | rhs_bot
|
||||
}
|
||||
|
||||
(_, _) if ty::type_is_integral(lhs_t) &&
|
||||
ast_util::is_shift_binop(op) {
|
||||
// Shift is a special case: rhs can be any integral type
|
||||
let rhs_bot = check_expr(fcx, rhs);
|
||||
let rhs_t = expr_ty(tcx, rhs);
|
||||
require_integral(fcx, rhs.span, rhs_t);
|
||||
write_ty(tcx, expr.id, lhs_t);
|
||||
lhs_bot | rhs_bot
|
||||
}
|
||||
|
||||
(_, _) if ty::is_binopable(tcx, lhs_t, op) {
|
||||
let rhs_bot = check_expr_with(fcx, rhs, lhs_t);
|
||||
let rhs_t = alt op {
|
||||
ast::eq | ast::lt | ast::le | ast::ne | ast::ge |
|
||||
ast::gt {
|
||||
// these comparison operators are handled in a
|
||||
// separate case below.
|
||||
tcx.sess.span_bug(
|
||||
expr.span,
|
||||
#fmt["Comparison operator in expr_binop: %s",
|
||||
ast_util::binop_to_str(op)]);
|
||||
}
|
||||
_ { lhs_t }
|
||||
};
|
||||
write_ty(tcx, expr.id, rhs_t);
|
||||
if !ast_util::lazy_binop(op) { lhs_bot | rhs_bot }
|
||||
else { lhs_bot }
|
||||
}
|
||||
|
||||
(_, _) {
|
||||
let (result, rhs_bot) =
|
||||
check_user_binop(fcx, expr, lhs_t, op, rhs);
|
||||
write_ty(tcx, expr.id, result);
|
||||
lhs_bot | rhs_bot
|
||||
}
|
||||
};
|
||||
}
|
||||
fn check_user_binop(fcx: @fn_ctxt, ex: @ast::expr, lhs_resolved_t: ty::t,
|
||||
op: ast::binop, rhs: @ast::expr) -> (ty::t, bool) {
|
||||
let tcx = fcx.ccx.tcx;
|
||||
alt binop_method(op) {
|
||||
some(name) {
|
||||
alt lookup_op_method(fcx, ex, resolved_t, name, [some(rhs)]) {
|
||||
some(ret_ty) { ret ret_ty; }
|
||||
alt lookup_op_method(fcx, ex, lhs_resolved_t, name, [some(rhs)]) {
|
||||
some(pair) { ret pair; }
|
||||
_ {}
|
||||
}
|
||||
}
|
||||
_ {}
|
||||
}
|
||||
check_expr(fcx, rhs);
|
||||
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) +
|
||||
" cannot be applied to type `" +
|
||||
ty_to_str(tcx, lhs_resolved_t) +
|
||||
"`");
|
||||
resolved_t
|
||||
(lhs_resolved_t, false)
|
||||
}
|
||||
fn check_user_unop(fcx: @fn_ctxt, op_str: str, mname: str,
|
||||
ex: @ast::expr, rhs_t: ty::t) -> ty::t {
|
||||
alt lookup_op_method(fcx, ex, rhs_t, mname, []) {
|
||||
some(ret_ty) { ret_ty }
|
||||
some((ret_ty, _)) { ret_ty }
|
||||
_ {
|
||||
fcx.ccx.tcx.sess.span_err(
|
||||
ex.span, #fmt["cannot apply unary operator `%s` to type `%s`",
|
||||
@ -2530,30 +2612,39 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, expr: @ast::expr, unify: unifier,
|
||||
let typ = check_lit(fcx.ccx, lit);
|
||||
write_ty(tcx, id, typ);
|
||||
}
|
||||
ast::expr_binary(binop, lhs, rhs) {
|
||||
let lhs_t = next_ty_var(fcx);
|
||||
bot = check_expr_with(fcx, lhs, lhs_t);
|
||||
|
||||
let rhs_bot = if !ast_util::is_shift_binop(binop) {
|
||||
check_expr_with(fcx, rhs, lhs_t)
|
||||
} else {
|
||||
let rhs_bot = check_expr(fcx, rhs);
|
||||
let rhs_t = expr_ty(tcx, rhs);
|
||||
require_integral(fcx, rhs.span, rhs_t);
|
||||
rhs_bot
|
||||
};
|
||||
|
||||
if !ast_util::lazy_binop(binop) { bot |= rhs_bot; }
|
||||
|
||||
let result = check_binop(fcx, expr, lhs_t, binop, rhs);
|
||||
write_ty(tcx, id, result);
|
||||
// Something of a hack: special rules for comparison operators that
|
||||
// simply unify LHS and RHS. This helps with inference as LHS and RHS
|
||||
// do not need to be "resolvable". Some tests, particularly those with
|
||||
// complicated iface requirements, fail without this---I think this code
|
||||
// can be removed if we improve iface resolution to be more eager when
|
||||
// possible.
|
||||
ast::expr_binary(ast::eq, lhs, rhs) |
|
||||
ast::expr_binary(ast::ne, lhs, rhs) |
|
||||
ast::expr_binary(ast::lt, lhs, rhs) |
|
||||
ast::expr_binary(ast::le, lhs, rhs) |
|
||||
ast::expr_binary(ast::gt, lhs, rhs) |
|
||||
ast::expr_binary(ast::ge, lhs, rhs) {
|
||||
let tcx = fcx.ccx.tcx;
|
||||
bot |= check_expr(fcx, lhs);
|
||||
let lhs_t = expr_ty(tcx, lhs);
|
||||
bot |= check_expr_with(fcx, rhs, lhs_t);
|
||||
write_ty(tcx, id, ty::mk_bool(tcx));
|
||||
}
|
||||
ast::expr_binary(op, lhs, rhs) {
|
||||
bot |= check_binop(fcx, expr, op, lhs, rhs);
|
||||
}
|
||||
ast::expr_assign_op(op, lhs, rhs) {
|
||||
require_impure(tcx.sess, fcx.purity, expr.span);
|
||||
bot = check_assignment(fcx, expr.span, lhs, rhs, id);
|
||||
bot |= check_binop(fcx, expr, op, lhs, rhs);
|
||||
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);
|
||||
let result_t = ty::expr_ty(tcx, expr);
|
||||
demand::simple(fcx, expr.span, result_t, lhs_t);
|
||||
|
||||
// Overwrite result of check_binop...this preserves existing behavior
|
||||
// but seems quite dubious with regard to user-defined methods
|
||||
// and so forth. - Niko
|
||||
write_nil(tcx, expr.id);
|
||||
}
|
||||
ast::expr_unary(unop, oper) {
|
||||
bot = check_expr(fcx, oper);
|
||||
@ -3036,7 +3127,7 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, expr: @ast::expr, unify: unifier,
|
||||
raw_base_t);
|
||||
alt lookup_op_method(fcx, expr, resolved, "[]",
|
||||
[some(idx)]) {
|
||||
some(ret_ty) { write_ty(tcx, id, ret_ty); }
|
||||
some((ret_ty, _)) { write_ty(tcx, id, ret_ty); }
|
||||
_ {
|
||||
tcx.sess.span_fatal(
|
||||
expr.span, "cannot index a value of type `" +
|
||||
@ -3241,7 +3332,7 @@ fn check_const(ccx: @crate_ctxt, _sp: span, e: @ast::expr, id: ast::node_id) {
|
||||
@{ret_ty: rty,
|
||||
purity: ast::pure_fn,
|
||||
proto: ast::proto_box,
|
||||
var_bindings: ty::unify::mk_var_bindings(),
|
||||
infcx: infer::new_infer_ctxt(ccx.tcx),
|
||||
locals: int_hash::<int>(),
|
||||
next_var_id: @mutable 0,
|
||||
ccx: ccx};
|
||||
@ -3260,7 +3351,7 @@ fn check_enum_variants(ccx: @crate_ctxt, sp: span, vs: [ast::variant],
|
||||
@{ret_ty: rty,
|
||||
purity: ast::pure_fn,
|
||||
proto: ast::proto_box,
|
||||
var_bindings: ty::unify::mk_var_bindings(),
|
||||
infcx: infer::new_infer_ctxt(ccx.tcx),
|
||||
locals: int_hash::<int>(),
|
||||
next_var_id: @mutable 0,
|
||||
ccx: ccx};
|
||||
@ -3438,7 +3529,7 @@ fn check_fn(ccx: @crate_ctxt,
|
||||
@{ret_ty: ty::ty_fn_ret(ty::node_id_to_type(ccx.tcx, id)),
|
||||
purity: purity,
|
||||
proto: proto,
|
||||
var_bindings: gather_result.var_bindings,
|
||||
infcx: gather_result.infcx,
|
||||
locals: gather_result.locals,
|
||||
next_var_id: gather_result.next_var_id,
|
||||
ccx: ccx};
|
||||
@ -3723,8 +3814,12 @@ mod vtable {
|
||||
|
||||
fn fixup_ty(fcx: @fn_ctxt, sp: span, ty: ty::t) -> ty::t {
|
||||
let tcx = fcx.ccx.tcx;
|
||||
alt ty::unify::fixup_vars(tcx, some(sp), fcx.var_bindings, ty) {
|
||||
alt infer::fixup_vars(fcx.infcx, ty) {
|
||||
result::ok(new_type) { new_type }
|
||||
result::err(-1) {
|
||||
tcx.sess.span_fatal(sp, "bounded type parameter with \
|
||||
cyclic type");
|
||||
}
|
||||
result::err(vid) {
|
||||
tcx.sess.span_fatal(sp, "could not determine a type for a \
|
||||
bounded type parameter");
|
||||
|
@ -31,6 +31,7 @@ mod middle {
|
||||
mod reachable;
|
||||
}
|
||||
mod ty;
|
||||
mod infer;
|
||||
mod ast_map;
|
||||
mod resolve;
|
||||
mod typeck;
|
||||
|
@ -28,6 +28,15 @@ fn region_to_str(cx: ctxt, region: region) -> str {
|
||||
}
|
||||
}
|
||||
|
||||
fn mt_to_str(cx: ctxt, m: mt) -> str {
|
||||
let mstr = alt m.mutbl {
|
||||
ast::m_mutbl { "mut " }
|
||||
ast::m_imm { "" }
|
||||
ast::m_const { "const " }
|
||||
};
|
||||
ret mstr + ty_to_str(cx, m.ty);
|
||||
}
|
||||
|
||||
fn ty_to_str(cx: ctxt, typ: t) -> str {
|
||||
fn fn_input_to_str(cx: ctxt, input: {mode: ast::mode, ty: t}) ->
|
||||
str {
|
||||
@ -72,14 +81,6 @@ fn ty_to_str(cx: ctxt, typ: t) -> str {
|
||||
fn field_to_str(cx: ctxt, f: field) -> str {
|
||||
ret f.ident + ": " + mt_to_str(cx, f.mt);
|
||||
}
|
||||
fn mt_to_str(cx: ctxt, m: mt) -> str {
|
||||
let mstr = alt m.mutbl {
|
||||
ast::m_mutbl { "mut " }
|
||||
ast::m_imm { "" }
|
||||
ast::m_const { "const " }
|
||||
};
|
||||
ret mstr + ty_to_str(cx, m.ty);
|
||||
}
|
||||
fn parameterized(cx: ctxt, base: str, tps: [ty::t]) -> str {
|
||||
if vec::len(tps) > 0u {
|
||||
let strs = vec::map(tps, {|t| ty_to_str(cx, t)});
|
||||
|
@ -170,7 +170,7 @@ fn build_reexport_path_map(srv: astsrv::srv, -def_map: def_map) -> path_map {
|
||||
let assoc_list = astsrv::exec(srv) {|ctxt|
|
||||
|
||||
let def_map = from_def_assoc_list(def_assoc_list);
|
||||
let path_map = map::str_hash();
|
||||
let path_map = map::str_hash::<[(str,doc::itemtag)]>();
|
||||
|
||||
ctxt.exp_map.items {|exp_id, defs|
|
||||
let path = alt check ctxt.ast_map.get(exp_id) {
|
||||
|
@ -90,7 +90,7 @@ fn sectionalize(desc: option<str>) -> (option<str>, [doc::section]) {
|
||||
|
||||
let lines = str::lines(option::get(desc));
|
||||
|
||||
let mut new_desc = none;
|
||||
let mut new_desc = none::<str>;
|
||||
let mut current_section = none;
|
||||
let mut sections = [];
|
||||
|
||||
|
3
src/test/auxiliary/noexporttypelib.rs
Normal file
3
src/test/auxiliary/noexporttypelib.rs
Normal file
@ -0,0 +1,3 @@
|
||||
export foo;
|
||||
type oint = option<int>;
|
||||
fn foo() -> oint { some(3) }
|
@ -1,4 +1,8 @@
|
||||
// error-pattern:mismatched types
|
||||
// issue #500
|
||||
|
||||
fn main() { let x = true; let y = 1; let z = x + y; }
|
||||
fn main() {
|
||||
let x = true;
|
||||
let y = 1;
|
||||
let z = x + y;
|
||||
//!^ ERROR binary operation + cannot be applied to type `bool`
|
||||
}
|
||||
|
@ -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 &&
|
||||
}
|
||||
|
@ -1,11 +1,12 @@
|
||||
// error-pattern: mismatched types
|
||||
|
||||
fn main() {
|
||||
let v = [mutable [0]];
|
||||
// Note: explicit type annot is required here
|
||||
// because otherwise the inference gets smart
|
||||
// and assigns a type of [mut [const int]].
|
||||
let v: [mut [int]] = [mutable [0]];
|
||||
|
||||
fn f(&&v: [mutable [const int]]) {
|
||||
v[0] = [mutable 3]
|
||||
}
|
||||
|
||||
f(v);
|
||||
f(v); //! ERROR (values differ in mutability)
|
||||
}
|
||||
|
@ -1,11 +1,12 @@
|
||||
// error-pattern: mismatched types
|
||||
|
||||
fn main() {
|
||||
let v = [mutable [mutable 0]];
|
||||
// Note: explicit type annot is required here
|
||||
// because otherwise the inference gets smart
|
||||
// and assigns a type of [mut [const int]].
|
||||
let v: [mut [mut int]] = [mutable [mutable 0]];
|
||||
|
||||
fn f(&&v: [mutable [const int]]) {
|
||||
v[0] = [3]
|
||||
}
|
||||
|
||||
f(v);
|
||||
f(v); //! ERROR (values differ in mutability)
|
||||
}
|
||||
|
@ -1,11 +1,12 @@
|
||||
// error-pattern: mismatched types
|
||||
|
||||
fn main() {
|
||||
let v = [mutable [mutable [0]]];
|
||||
// Note: explicit type annot is required here
|
||||
// because otherwise the inference gets smart
|
||||
// and assigns a type of [mut [const int]].
|
||||
let v: [mut[mut[int]]] = [mutable [mutable [0]]];
|
||||
|
||||
fn f(&&v: [mutable [mutable [const int]]]) {
|
||||
v[0][1] = [mutable 3]
|
||||
}
|
||||
|
||||
f(v);
|
||||
f(v); //! ERROR (values differ in mutability)
|
||||
}
|
||||
|
13
src/test/compile-fail/noexporttypeexe.rs
Normal file
13
src/test/compile-fail/noexporttypeexe.rs
Normal file
@ -0,0 +1,13 @@
|
||||
// aux-build:noexporttypelib.rs
|
||||
|
||||
use noexporttypelib;
|
||||
|
||||
fn main() {
|
||||
// Here, the type returned by foo() is not exported.
|
||||
// This used to cause internal errors when serializing
|
||||
// because the def_id associated with the type was
|
||||
// not convertible to a path.
|
||||
let x: int = noexporttypelib::foo();
|
||||
//!^ ERROR expected `int` but found `core::option::option<int>`
|
||||
}
|
||||
|
@ -1,2 +1,4 @@
|
||||
// error-pattern: can not instantiate infinite type
|
||||
fn main() { let f; f = @f; }
|
||||
fn main() {
|
||||
let f; //! ERROR this local variable has a type of infinite size
|
||||
f = @f;
|
||||
}
|
||||
|
@ -1,4 +1,8 @@
|
||||
// error-pattern:expected `bool` but found `int`
|
||||
// issue #516
|
||||
|
||||
fn main() { let x = true; let y = 1; let z = x + y; }
|
||||
fn main() {
|
||||
let x = true;
|
||||
let y = 1;
|
||||
let z = x + y;
|
||||
//!^ ERROR binary operation + cannot be applied to type `bool`
|
||||
}
|
||||
|
13
src/test/compile-fail/vec-concat-bug.rs
Normal file
13
src/test/compile-fail/vec-concat-bug.rs
Normal file
@ -0,0 +1,13 @@
|
||||
fn concat<T: copy>(v: [const [const T]]) -> [T] {
|
||||
let mut r = [];
|
||||
|
||||
// Earlier versions of our type checker accepted this:
|
||||
for inner: [T] in v {
|
||||
//!^ ERROR found `[const 'a]` (values differ in mutability)
|
||||
r += inner;
|
||||
}
|
||||
|
||||
ret r;
|
||||
}
|
||||
|
||||
fn main() {}
|
Loading…
x
Reference in New Issue
Block a user