Skip to content

Commit 2baaeab

Browse files
committed
Implement the occurs check
In the writeback phase, the typechecker now checks that it isn't replacing a type variable T with a type that contains T. It also does an occurs check in do_autoderef in order to avoid getting into an infinite chain of derefs. I'm a bit worried that there are more places where the occurs check needs to happen where I'm not doing it now, though. Closes #768
1 parent d7d4b4f commit 2baaeab

File tree

4 files changed

+87
-14
lines changed

4 files changed

+87
-14
lines changed

src/comp/middle/ty.rs

+62-10
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ export type_param;
184184
export unify;
185185
export variant_info;
186186
export walk_ty;
187+
export occurs_check_fails;
187188

188189
// Data types
189190
tag mode { mo_val; mo_alias(bool); }
@@ -655,6 +656,7 @@ fn walk_ty(cx: &ctxt, walker: ty_walk, ty: t) {
655656
ty_str. {/* no-op */ }
656657
ty_istr. {/* no-op */ }
657658
ty_type. {/* no-op */ }
659+
ty_task. {/* no-op */ }
658660
ty_native(_) {/* no-op */ }
659661
ty_box(tm) { walk_ty(cx, walker, tm.ty); }
660662
ty_vec(tm) { walk_ty(cx, walker, tm.ty); }
@@ -686,6 +688,9 @@ fn walk_ty(cx: &ctxt, walker: ty_walk, ty: t) {
686688
walk_ty(cx, walker, sub);
687689
for tp: t in tps { walk_ty(cx, walker, tp); }
688690
}
691+
ty_constr(sub, _) {
692+
walk_ty(cx, walker, sub);
693+
}
689694
ty_var(_) {/* no-op */ }
690695
ty_param(_,_) {/* no-op */ }
691696
}
@@ -1393,6 +1398,24 @@ fn type_param(cx: &ctxt, ty: &t) -> option::t[uint] {
13931398
ret none;
13941399
}
13951400

1401+
// Returns an ivec of all the type variables
1402+
// occurring in t. It may contain duplicates.
1403+
fn vars_in_type(cx:&ctxt, ty: &t) -> int[] {
1404+
fn collect_var(cx:&ctxt, vars: &@mutable int[], ty: t) {
1405+
alt struct(cx, ty) {
1406+
ty_var(v) {
1407+
*vars += ~[v];
1408+
}
1409+
_ {}
1410+
}
1411+
}
1412+
let rslt: @mutable int[] = @mutable (~[]);
1413+
walk_ty(cx, bind collect_var(cx, rslt, _), ty);
1414+
// Works because of a "convenient" bug that lets us
1415+
// return a mutable ivec as if it's immutable
1416+
ret *rslt;
1417+
}
1418+
13961419
fn type_autoderef(cx: &ctxt, t: &ty::t) -> ty::t {
13971420
let t1: ty::t = t;
13981421
while true {
@@ -1990,6 +2013,28 @@ fn is_lval(expr: &@ast::expr) -> bool {
19902013
}
19912014
}
19922015

2016+
fn occurs_check_fails(tcx: &ctxt, sp: &option::t[span], vid: int, rt: &t)
2017+
-> bool {
2018+
// Occurs check!
2019+
if ivec::member(vid, vars_in_type(tcx, rt)) {
2020+
alt sp {
2021+
some (s) {
2022+
// Maybe this should be span_err -- however, there's an
2023+
// assertion later on that the type doesn't contain
2024+
// variables, so in this case we have to be sure to die.
2025+
tcx.sess.span_fatal(s,
2026+
"Type inference failed because I \
2027+
could not find a type\n that's both of the form " +
2028+
ty_to_str(tcx, ty::mk_var(tcx, (vid)))
2029+
+ " and of the form " + ty_to_str(tcx, rt)
2030+
+ ". Such a type would have to be infinitely \
2031+
large.");
2032+
}
2033+
_ { ret true; }
2034+
}
2035+
}
2036+
else { ret false; }
2037+
}
19932038

19942039
// Type unification via Robinson's algorithm (Robinson 1965). Implemented as
19952040
// described in Hoder and Voronkov:
@@ -2318,9 +2363,6 @@ mod unify {
23182363
// TODO: rewrite this using tuple pattern matching when available, to
23192364
// avoid all this rightward drift and spikiness.
23202365

2321-
// TODO: occurs check, to make sure we don't loop forever when
2322-
// unifying e.g. 'a and option['a]
2323-
23242366
// Fast path.
23252367

23262368
if eq_ty(expected, actual) { ret ures_ok(expected); }
@@ -2694,9 +2736,15 @@ mod unify {
26942736
}
26952737

26962738
// Fixups and substitutions
2697-
fn fixup_vars(tcx: ty_ctxt, vb: @var_bindings, typ: t) -> fixup_result {
2698-
fn subst_vars(tcx: ty_ctxt, vb: @var_bindings,
2739+
// Takes an optional span - complain about occurs check violations
2740+
// iff the span is present (so that if we already know we're going
2741+
// to error anyway, we don't complain)
2742+
fn fixup_vars(tcx: ty_ctxt, sp: &option::t[span],
2743+
vb: @var_bindings, typ: t) -> fixup_result {
2744+
fn subst_vars(tcx: ty_ctxt, sp: &option::t[span], vb: @var_bindings,
26992745
unresolved: @mutable option::t[int], vid: int) -> t {
2746+
// Should really return a fixup_result instead of a t, but fold_ty
2747+
// doesn't allow returning anything but a t.
27002748
if vid as uint >= ufindivec::set_count(vb.sets) {
27012749
*unresolved = some(vid);
27022750
ret ty::mk_var(tcx, vid);
@@ -2705,29 +2753,33 @@ mod unify {
27052753
alt smallintmap::find[t](vb.types, root_id) {
27062754
none. { *unresolved = some(vid); ret ty::mk_var(tcx, vid); }
27072755
some(rt) {
2756+
if occurs_check_fails(tcx, sp, vid, rt) {
2757+
// Return the type unchanged, so we can error out downstream
2758+
ret rt;
2759+
}
27082760
ret fold_ty(tcx,
2709-
fm_var(bind subst_vars(tcx, vb, unresolved, _)),
2710-
rt);
2761+
fm_var(bind subst_vars(tcx, sp, vb, unresolved, _)), rt);
27112762
}
27122763
}
27132764
}
27142765
let unresolved = @mutable none[int];
27152766
let rty =
2716-
fold_ty(tcx, fm_var(bind subst_vars(tcx, vb, unresolved, _)),
2767+
fold_ty(tcx, fm_var(bind subst_vars(tcx, sp, vb, unresolved, _)),
27172768
typ);
27182769
let ur = *unresolved;
27192770
alt ur {
27202771
none. { ret fix_ok(rty); }
27212772
some(var_id) { ret fix_err(var_id); }
27222773
}
27232774
}
2724-
fn resolve_type_var(tcx: &ty_ctxt, vb: &@var_bindings, vid: int) ->
2775+
fn resolve_type_var(tcx: &ty_ctxt, sp: &option::t[span],
2776+
vb: &@var_bindings, vid: int) ->
27252777
fixup_result {
27262778
if vid as uint >= ufindivec::set_count(vb.sets) { ret fix_err(vid); }
27272779
let root_id = ufindivec::find(vb.sets, vid as uint);
27282780
alt smallintmap::find[t](vb.types, root_id) {
27292781
none. { ret fix_err(vid); }
2730-
some(rt) { ret fixup_vars(tcx, vb, rt); }
2782+
some(rt) { ret fixup_vars(tcx, sp, vb, rt); }
27312783
}
27322784
}
27332785
}

src/comp/middle/typeck.rs

+16-4
Original file line numberDiff line numberDiff line change
@@ -879,7 +879,18 @@ fn do_autoderef(fcx: &@fn_ctxt, sp: &span, t: &ty::t) -> ty::t {
879879
let t1 = t;
880880
while true {
881881
alt structure_of(fcx, sp, t1) {
882-
ty::ty_box(inner) { t1 = inner.ty; }
882+
ty::ty_box(inner) {
883+
alt ty::struct(fcx.ccx.tcx, t1) {
884+
ty::ty_var(v1) {
885+
if ty::occurs_check_fails(fcx.ccx.tcx, some(sp), v1,
886+
ty::mk_box(fcx.ccx.tcx, inner)) {
887+
break;
888+
}
889+
}
890+
_ {}
891+
}
892+
t1 = inner.ty;
893+
}
883894
ty::ty_res(_, inner, tps) {
884895
t1 = ty::substitute_type_params(fcx.ccx.tcx, tps, inner);
885896
}
@@ -942,7 +953,7 @@ fn do_fn_block_coerce(fcx: &@fn_ctxt, sp: &span, actual: &ty::t,
942953

943954

944955
fn resolve_type_vars_if_possible(fcx: &@fn_ctxt, typ: ty::t) -> ty::t {
945-
alt ty::unify::fixup_vars(fcx.ccx.tcx, fcx.var_bindings, typ) {
956+
alt ty::unify::fixup_vars(fcx.ccx.tcx, none, fcx.var_bindings, typ) {
946957
fix_ok(new_type) { ret new_type; }
947958
fix_err(_) { ret typ; }
948959
}
@@ -1073,7 +1084,8 @@ mod writeback {
10731084
fn resolve_type_vars_in_type(fcx: &@fn_ctxt, sp: &span, typ: ty::t) ->
10741085
option::t[ty::t] {
10751086
if !ty::type_contains_vars(fcx.ccx.tcx, typ) { ret some(typ); }
1076-
alt ty::unify::fixup_vars(fcx.ccx.tcx, fcx.var_bindings, typ) {
1087+
alt ty::unify::fixup_vars(fcx.ccx.tcx, some(sp),
1088+
fcx.var_bindings, typ) {
10771089
fix_ok(new_type) { ret some(new_type); }
10781090
fix_err(vid) {
10791091
fcx.ccx.tcx.sess.span_err(sp,
@@ -1139,7 +1151,7 @@ mod writeback {
11391151
if !wbcx.success { ret; }
11401152
let var_id = lookup_local(wbcx.fcx, l.span, l.node.id);
11411153
let fix_rslt =
1142-
ty::unify::resolve_type_var(wbcx.fcx.ccx.tcx,
1154+
ty::unify::resolve_type_var(wbcx.fcx.ccx.tcx, some(l.span),
11431155
wbcx.fcx.var_bindings, var_id);
11441156
alt fix_rslt {
11451157
fix_ok(lty) { write::ty_only(wbcx.fcx.ccx.tcx, l.node.id, lty); }
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// error-pattern: Type inference failed because I could not find
2+
fn main() {
3+
let f = @f;
4+
f();
5+
}

src/test/compile-fail/occurs-check.rs

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// error-pattern: Type inference failed because I could not find
2+
fn main() {
3+
let f = @f;
4+
}

0 commit comments

Comments
 (0)