From 2baaeab784a1f5c95deb8bd0c3a9745a969431cc Mon Sep 17 00:00:00 2001 From: Tim Chevalier Date: Wed, 3 Aug 2011 18:06:57 -0700 Subject: [PATCH] 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 --- src/comp/middle/ty.rs | 72 +++++++++++++++++++++---- src/comp/middle/typeck.rs | 20 +++++-- src/test/compile-fail/occurs-check-2.rs | 5 ++ src/test/compile-fail/occurs-check.rs | 4 ++ 4 files changed, 87 insertions(+), 14 deletions(-) create mode 100644 src/test/compile-fail/occurs-check-2.rs create mode 100644 src/test/compile-fail/occurs-check.rs diff --git a/src/comp/middle/ty.rs b/src/comp/middle/ty.rs index 556c9e36bea4..ba4910c36161 100644 --- a/src/comp/middle/ty.rs +++ b/src/comp/middle/ty.rs @@ -184,6 +184,7 @@ export type_param; export unify; export variant_info; export walk_ty; +export occurs_check_fails; // Data types tag mode { mo_val; mo_alias(bool); } @@ -655,6 +656,7 @@ fn walk_ty(cx: &ctxt, walker: ty_walk, ty: t) { ty_str. {/* no-op */ } ty_istr. {/* no-op */ } ty_type. {/* no-op */ } + ty_task. {/* no-op */ } ty_native(_) {/* no-op */ } ty_box(tm) { walk_ty(cx, walker, tm.ty); } ty_vec(tm) { walk_ty(cx, walker, tm.ty); } @@ -686,6 +688,9 @@ fn walk_ty(cx: &ctxt, walker: ty_walk, ty: t) { walk_ty(cx, walker, sub); for tp: t in tps { walk_ty(cx, walker, tp); } } + ty_constr(sub, _) { + walk_ty(cx, walker, sub); + } ty_var(_) {/* no-op */ } ty_param(_,_) {/* no-op */ } } @@ -1393,6 +1398,24 @@ fn type_param(cx: &ctxt, ty: &t) -> option::t[uint] { ret none; } +// Returns an ivec of all the type variables +// occurring in t. It may contain duplicates. +fn vars_in_type(cx:&ctxt, ty: &t) -> int[] { + fn collect_var(cx:&ctxt, vars: &@mutable int[], ty: t) { + alt struct(cx, ty) { + ty_var(v) { + *vars += ~[v]; + } + _ {} + } + } + let rslt: @mutable int[] = @mutable (~[]); + walk_ty(cx, bind collect_var(cx, rslt, _), ty); + // Works because of a "convenient" bug that lets us + // return a mutable ivec as if it's immutable + ret *rslt; +} + fn type_autoderef(cx: &ctxt, t: &ty::t) -> ty::t { let t1: ty::t = t; while true { @@ -1990,6 +2013,28 @@ fn is_lval(expr: &@ast::expr) -> bool { } } +fn occurs_check_fails(tcx: &ctxt, sp: &option::t[span], vid: int, rt: &t) + -> bool { + // Occurs check! + if ivec::member(vid, vars_in_type(tcx, rt)) { + alt sp { + some (s) { + // Maybe this should be span_err -- however, there's an + // assertion later on that the type doesn't contain + // variables, so in this case we have to be sure to die. + tcx.sess.span_fatal(s, + "Type inference failed because I \ + could not find a type\n that's both of the form " + + ty_to_str(tcx, ty::mk_var(tcx, (vid))) + + " and of the form " + ty_to_str(tcx, rt) + + ". Such a type would have to be infinitely \ + large."); + } + _ { ret true; } + } + } + else { ret false; } +} // Type unification via Robinson's algorithm (Robinson 1965). Implemented as // described in Hoder and Voronkov: @@ -2318,9 +2363,6 @@ mod unify { // TODO: rewrite this using tuple pattern matching when available, to // avoid all this rightward drift and spikiness. - // TODO: occurs check, to make sure we don't loop forever when - // unifying e.g. 'a and option['a] - // Fast path. if eq_ty(expected, actual) { ret ures_ok(expected); } @@ -2694,9 +2736,15 @@ mod unify { } // Fixups and substitutions - fn fixup_vars(tcx: ty_ctxt, vb: @var_bindings, typ: t) -> fixup_result { - fn subst_vars(tcx: ty_ctxt, vb: @var_bindings, + // Takes an optional span - complain about occurs check violations + // iff the span is present (so that if we already know we're going + // to error anyway, we don't complain) + fn fixup_vars(tcx: ty_ctxt, sp: &option::t[span], + vb: @var_bindings, typ: t) -> fixup_result { + fn subst_vars(tcx: ty_ctxt, sp: &option::t[span], vb: @var_bindings, unresolved: @mutable option::t[int], vid: int) -> t { + // Should really return a fixup_result instead of a t, but fold_ty + // doesn't allow returning anything but a t. if vid as uint >= ufindivec::set_count(vb.sets) { *unresolved = some(vid); ret ty::mk_var(tcx, vid); @@ -2705,15 +2753,18 @@ mod unify { alt smallintmap::find[t](vb.types, root_id) { none. { *unresolved = some(vid); ret ty::mk_var(tcx, vid); } some(rt) { + if occurs_check_fails(tcx, sp, vid, rt) { + // Return the type unchanged, so we can error out downstream + ret rt; + } ret fold_ty(tcx, - fm_var(bind subst_vars(tcx, vb, unresolved, _)), - rt); + fm_var(bind subst_vars(tcx, sp, vb, unresolved, _)), rt); } } } let unresolved = @mutable none[int]; let rty = - fold_ty(tcx, fm_var(bind subst_vars(tcx, vb, unresolved, _)), + fold_ty(tcx, fm_var(bind subst_vars(tcx, sp, vb, unresolved, _)), typ); let ur = *unresolved; alt ur { @@ -2721,13 +2772,14 @@ mod unify { some(var_id) { ret fix_err(var_id); } } } - fn resolve_type_var(tcx: &ty_ctxt, vb: &@var_bindings, vid: int) -> + fn resolve_type_var(tcx: &ty_ctxt, sp: &option::t[span], + vb: &@var_bindings, vid: int) -> fixup_result { if vid as uint >= ufindivec::set_count(vb.sets) { ret fix_err(vid); } let root_id = ufindivec::find(vb.sets, vid as uint); alt smallintmap::find[t](vb.types, root_id) { none. { ret fix_err(vid); } - some(rt) { ret fixup_vars(tcx, vb, rt); } + some(rt) { ret fixup_vars(tcx, sp, vb, rt); } } } } diff --git a/src/comp/middle/typeck.rs b/src/comp/middle/typeck.rs index d5bc8b0cbe32..e5a8f7eba8a4 100644 --- a/src/comp/middle/typeck.rs +++ b/src/comp/middle/typeck.rs @@ -879,7 +879,18 @@ fn do_autoderef(fcx: &@fn_ctxt, sp: &span, t: &ty::t) -> ty::t { let t1 = t; while true { alt structure_of(fcx, sp, t1) { - ty::ty_box(inner) { t1 = inner.ty; } + ty::ty_box(inner) { + alt ty::struct(fcx.ccx.tcx, t1) { + ty::ty_var(v1) { + if ty::occurs_check_fails(fcx.ccx.tcx, some(sp), v1, + ty::mk_box(fcx.ccx.tcx, inner)) { + break; + } + } + _ {} + } + t1 = inner.ty; + } ty::ty_res(_, inner, tps) { t1 = ty::substitute_type_params(fcx.ccx.tcx, tps, inner); } @@ -942,7 +953,7 @@ fn do_fn_block_coerce(fcx: &@fn_ctxt, sp: &span, actual: &ty::t, fn resolve_type_vars_if_possible(fcx: &@fn_ctxt, typ: ty::t) -> ty::t { - alt ty::unify::fixup_vars(fcx.ccx.tcx, fcx.var_bindings, typ) { + alt ty::unify::fixup_vars(fcx.ccx.tcx, none, fcx.var_bindings, typ) { fix_ok(new_type) { ret new_type; } fix_err(_) { ret typ; } } @@ -1073,7 +1084,8 @@ mod writeback { fn resolve_type_vars_in_type(fcx: &@fn_ctxt, sp: &span, typ: ty::t) -> option::t[ty::t] { if !ty::type_contains_vars(fcx.ccx.tcx, typ) { ret some(typ); } - alt ty::unify::fixup_vars(fcx.ccx.tcx, fcx.var_bindings, typ) { + alt ty::unify::fixup_vars(fcx.ccx.tcx, some(sp), + fcx.var_bindings, typ) { fix_ok(new_type) { ret some(new_type); } fix_err(vid) { fcx.ccx.tcx.sess.span_err(sp, @@ -1139,7 +1151,7 @@ mod writeback { 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, + ty::unify::resolve_type_var(wbcx.fcx.ccx.tcx, some(l.span), wbcx.fcx.var_bindings, var_id); alt fix_rslt { fix_ok(lty) { write::ty_only(wbcx.fcx.ccx.tcx, l.node.id, lty); } diff --git a/src/test/compile-fail/occurs-check-2.rs b/src/test/compile-fail/occurs-check-2.rs new file mode 100644 index 000000000000..9b5536e57c36 --- /dev/null +++ b/src/test/compile-fail/occurs-check-2.rs @@ -0,0 +1,5 @@ +// error-pattern: Type inference failed because I could not find +fn main() { + let f = @f; + f(); +} \ No newline at end of file diff --git a/src/test/compile-fail/occurs-check.rs b/src/test/compile-fail/occurs-check.rs new file mode 100644 index 000000000000..a2bd12fcc354 --- /dev/null +++ b/src/test/compile-fail/occurs-check.rs @@ -0,0 +1,4 @@ +// error-pattern: Type inference failed because I could not find +fn main() { + let f = @f; +}