move regionck into typeck, in the process fixing a bug or two

This commit is contained in:
Niko Matsakis 2012-05-15 19:04:33 -07:00
parent fa5cc5bcd0
commit ab735320b4
12 changed files with 229 additions and 138 deletions

View file

@ -193,8 +193,6 @@ fn compile_upto(sess: session, cfg: ast::crate_cfg,
let (root_map, mutbl_map) = time(
time_passes, "borrow checking",
bind middle::borrowck::check_crate(ty_cx, method_map, crate));
time(time_passes, "region checking",
bind middle::regionck::check_crate(ty_cx, crate));
let (copy_map, ref_map) =
time(time_passes, "alias checking",
bind middle::alias::check_crate(ty_cx, crate));

View file

@ -364,8 +364,7 @@ fn resolve_local(local: @ast::local, cx: ctxt, visitor: visit::vt<ctxt>) {
fn resolve_item(item: @ast::item, cx: ctxt, visitor: visit::vt<ctxt>) {
// Items create a new outer block scope as far as we're concerned.
let new_cx: ctxt = {closure_parent: some(item.id),
parent: some(item.id) with cx};
let new_cx: ctxt = {closure_parent: none, parent: none with cx};
visit::visit_item(item, new_cx, visitor);
}

View file

@ -1,49 +0,0 @@
/*
* The region checking pass. Ensures that region-annotated pointers never
* outlive their referents.
*/
import driver::session::session;
import middle::ty;
import std::map::hashmap;
import syntax::{ast, visit};
import util::ppaux;
fn check_expr(expr: @ast::expr,
&&tcx: ty::ctxt,
visitor: visit::vt<ty::ctxt>) {
visit::visit_expr(expr, tcx, visitor);
let t = ty::expr_ty(tcx, expr);
if !ty::type_has_regions(t) { ret; }
ty::walk_ty(t) { |t|
alt ty::get(t).struct {
ty::ty_rptr(region, _) {
alt region {
ty::re_bound(_) | ty::re_free(_, _) | ty::re_static |
ty::re_var(_) {
/* ok */
}
ty::re_scope(id) {
if !region::scope_contains(tcx.region_map, id, expr.id) {
tcx.sess.span_err(
expr.span,
#fmt["reference is not valid outside of %s",
ppaux::re_scope_id_to_str(tcx, id)]);
}
}
}
}
_ { /* no-op */ }
}
}
}
fn check_crate(tcx: ty::ctxt, crate: @ast::crate) {
let visitor = visit::mk_vt(@{
visit_expr: check_expr
with *visit::default_visitor()
});
visit::visit_crate(*crate, tcx, visitor);
}

View file

@ -101,7 +101,7 @@ export ty_uint, mk_uint, mk_mach_uint;
export ty_uniq, mk_uniq, mk_imm_uniq, type_is_unique_box;
export ty_var, mk_var, type_is_var;
export ty_self, mk_self, type_has_self;
export region, bound_region;
export region, bound_region, encl_region;
export get, type_has_params, type_needs_infer, type_has_regions;
export type_has_resources, type_id;
export tbox_has_flag;
@ -685,6 +685,15 @@ fn default_arg_mode_for_ty(ty: ty::t) -> ast::rmode {
else { ast::by_ref }
}
// Returns the narrowest lifetime enclosing the evaluation of the expression
// with id `id`.
fn encl_region(cx: ctxt, id: ast::node_id) -> ty::region {
alt cx.region_map.parents.find(id) {
some(encl_scope) {ty::re_scope(encl_scope)}
none {ty::re_static}
}
}
fn walk_ty(ty: t, f: fn(t)) {
maybe_walk_ty(ty, {|t| f(t); true});
}

View file

@ -630,6 +630,7 @@ impl methods for @fn_ctxt {
fn next_region_var() -> ty::region {
ret ty::re_var(self.next_region_var_id());
}
fn report_mismatched_types(sp: span, e: ty::t, a: ty::t,
err: ty::type_err) {
self.ccx.tcx.sess.span_err(
@ -648,6 +649,10 @@ impl methods for @fn_ctxt {
infer::mk_eqty(self.infcx, sub, sup)
}
fn mk_subr(sub: ty::region, sup: ty::region) -> result<(), ty::type_err> {
infer::mk_subr(self.infcx, sub, sup)
}
fn require_unsafe(sp: span, op: str) {
alt self.purity {
ast::unsafe_fn {/*ok*/}
@ -796,47 +801,6 @@ fn require_same_types(
}
}
mod demand {
// Requires that the two types unify, and prints an error message if they
// don't.
fn suptype(fcx: @fn_ctxt, sp: span,
expected: ty::t, actual: ty::t) {
// n.b.: order of actual, expected is reversed
alt infer::mk_subty(fcx.infcx, actual, expected) {
result::ok(()) { /* ok */ }
result::err(err) {
fcx.report_mismatched_types(sp, expected, actual, err);
}
}
}
fn eqtype(fcx: @fn_ctxt, sp: span,
expected: ty::t, actual: ty::t) {
alt infer::mk_eqty(fcx.infcx, actual, expected) {
result::ok(()) { /* ok */ }
result::err(err) {
fcx.report_mismatched_types(sp, expected, actual, err);
}
}
}
// Checks that the type `actual` can be assigned to `expected`.
fn assign(fcx: @fn_ctxt, sp: span, borrow_scope: ast::node_id,
expected: ty::t, expr: @ast::expr) {
let expr_ty = fcx.expr_ty(expr);
let anmnt = {expr_id: expr.id, borrow_scope: borrow_scope};
alt infer::mk_assignty(fcx.infcx, anmnt, expr_ty, expected) {
result::ok(()) { /* ok */ }
result::err(err) {
fcx.report_mismatched_types(sp, expected, expr_ty, err);
}
}
}
}
// Returns true if the two types unify and false if they don't.
fn are_compatible(fcx: @fn_ctxt, expected: ty::t, actual: ty::t) -> bool {
alt fcx.mk_eqty(expected, actual) {
@ -1594,8 +1558,7 @@ fn lookup_field_ty(tcx: ty::ctxt, class_id: ast::def_id,
*/
fn region_of(fcx: @fn_ctxt, expr: @ast::expr) -> ty::region {
fn borrow(fcx: @fn_ctxt, expr: @ast::expr) -> ty::region {
let parent_id = fcx.ccx.tcx.region_map.parents.get(expr.id);
ret ty::re_scope(parent_id);
ty::encl_region(fcx.ccx.tcx, expr.id)
}
fn deref(fcx: @fn_ctxt, base: @ast::expr) -> ty::region {
@ -1970,7 +1933,7 @@ fn check_expr_with_unifier(fcx: @fn_ctxt,
let ret_ty = ty::ty_fn_ret(fty);
let arg_tys = vec::map(ty::ty_fn_args(fty)) {|a| a.ty };
check_fn(fcx.ccx, proto, decl, body, expr.id,
check_fn(fcx.ccx, proto, decl, body,
ret_ty, arg_tys, is_loop_body, some(fcx),
fcx.self_ty);
}
@ -2780,6 +2743,7 @@ fn check_const(ccx: @crate_ctxt, _sp: span, e: @ast::expr, id: ast::node_id) {
let cty = fcx.expr_ty(e);
let declty = fcx.ccx.tcx.tcache.get(local_def(id)).ty;
demand::suptype(fcx, e.span, declty, cty);
regionck::regionck_expr(fcx, e);
writeback::resolve_type_vars_in_expr(fcx, e);
}
@ -2984,7 +2948,7 @@ fn check_bare_fn(ccx: @crate_ctxt,
let fty = ty::node_id_to_type(ccx.tcx, id);
let ret_ty = ty::ty_fn_ret(fty);
let arg_tys = vec::map(ty::ty_fn_args(fty)) {|a| a.ty };
check_fn(ccx, ast::proto_bare, decl, body, id,
check_fn(ccx, ast::proto_bare, decl, body,
ret_ty, arg_tys, false, none, self_ty);
}
@ -2992,7 +2956,6 @@ fn check_fn(ccx: @crate_ctxt,
proto: ast::proto,
decl: ast::fn_decl,
body: ast::blk,
fid: ast::node_id,
ret_ty: ty::t,
arg_tys: [ty::t],
indirect_ret: bool,
@ -3012,7 +2975,7 @@ fn check_fn(ccx: @crate_ctxt,
let old_isr = option::map_default(old_fcx, @nil) {
|fcx| fcx.in_scope_regions };
collect_bound_regions_in_tys(tcx, old_isr, all_tys) {
|br| ty::re_free(fid, br) }
|br| ty::re_free(body.node.id, br) }
};
// Replace the bound regions that appear in the arg tys, ret ty, etc with
@ -3108,6 +3071,7 @@ fn check_fn(ccx: @crate_ctxt,
// resolved when the enclosing scope finishes up.
if option::is_none(old_fcx) {
vtable::resolve_in_block(fcx, body);
regionck::regionck_fn(fcx, decl, body);
writeback::resolve_type_vars_in_fn(fcx, decl, body);
}

View file

@ -0,0 +1,39 @@
// Requires that the two types unify, and prints an error message if they
// don't.
fn suptype(fcx: @fn_ctxt, sp: span,
expected: ty::t, actual: ty::t) {
// n.b.: order of actual, expected is reversed
alt infer::mk_subty(fcx.infcx, actual, expected) {
result::ok(()) { /* ok */ }
result::err(err) {
fcx.report_mismatched_types(sp, expected, actual, err);
}
}
}
fn eqtype(fcx: @fn_ctxt, sp: span,
expected: ty::t, actual: ty::t) {
alt infer::mk_eqty(fcx.infcx, actual, expected) {
result::ok(()) { /* ok */ }
result::err(err) {
fcx.report_mismatched_types(sp, expected, actual, err);
}
}
}
// Checks that the type `actual` can be assigned to `expected`.
fn assign(fcx: @fn_ctxt, sp: span, borrow_scope: ast::node_id,
expected: ty::t, expr: @ast::expr) {
let expr_ty = fcx.expr_ty(expr);
let anmnt = {expr_id: expr.id, borrow_scope: borrow_scope};
alt infer::mk_assignty(fcx.infcx, anmnt, expr_ty, expected) {
result::ok(()) { /* ok */ }
result::err(err) {
fcx.report_mismatched_types(sp, expected, expr_ty, err);
}
}
}

View file

@ -1512,20 +1512,18 @@ impl of combine for lub {
(f @ ty::re_free(f_id, _), ty::re_scope(s_id)) |
(ty::re_scope(s_id), f @ ty::re_free(f_id, _)) {
// For LUB, generally the scope is within the fn and
// the free region is a parameter to the fn. In that case,
// the free region will always live as long as the fn,
// which is longer than the scope.
//
// However, with nested fns, it can happen that the
// scope surrounds the fn itself. In that case, we do
// not know which will live longer---it depends on the
// value provided for the free region in any given
// call. And so we must just back off to re_static as
// the LUB.
// A "free" region can be interpreted as "some region
// at least as big as the block f_id". So, we can
// reasonably compare free regions and scopes:
let rm = self.infcx().tcx.region_map;
alt region::nearest_common_ancestor(rm, f_id, s_id) {
// if the free region's scope `f_id` is bigger than
// the scope region `s_id`, then the LUB is the free
// region itself:
some(r_id) if r_id == f_id { ok(f) }
// otherwise, we don't know what the free region is,
// so we must conservatively say the LUB is static:
_ { ok(ty::re_static) }
}
}
@ -1703,15 +1701,11 @@ impl of combine for glb {
(ty::re_free(f_id, _), s @ ty::re_scope(s_id)) |
(s @ ty::re_scope(s_id), ty::re_free(f_id, _)) {
// For GLB, generally the scope is within the fn and
// the free region is a parameter to the fn. In that case,
// the scope is always shorter than the free region.
//
// However, with nested fns, it can happen that the
// scope surrounds the fn itself. In that case, we do
// not know which will live longer---it depends on the
// value provided for the free region in any given
// call. And so we cannot give a GLB.
// Free region is something "at least as big as
// `f_id`." If we find that the scope `f_id` is bigger
// than the scope `s_id`, then we can say that the GLB
// is the scope `s_id`. Otherwise, as we do not know
// big the free region is precisely, the GLB is undefined.
let rm = self.infcx().tcx.region_map;
alt region::nearest_common_ancestor(rm, f_id, s_id) {
some(r_id) if r_id == f_id { ok(s) }

View file

@ -0,0 +1,132 @@
/*
The region check is a final pass that runs over the AST after we have
inferred the type constraints but before we have actually finalized
the types. It's purpose is to embed some final region constraints.
The reason that this is not done earlier is that sometimes we don't
know whether a given type will be a region pointer or not until this
phase.
In particular, we ensure that, if the type of an expression or
variable is `&r.T`, then the expression or variable must occur within
the region scope `r`.
*/
import util::ppaux;
import syntax::print::pprust;
fn regionck_expr(fcx: @fn_ctxt, e: @ast::expr) {
let v = regionck_visitor(fcx);
v.visit_expr(e, fcx, v);
}
fn regionck_fn(fcx: @fn_ctxt,
_decl: ast::fn_decl,
blk: ast::blk) {
let v = regionck_visitor(fcx);
v.visit_block(blk, fcx, v);
}
type rvt = visit::vt<@fn_ctxt>;
fn regionck_visitor(_fcx: @fn_ctxt) -> rvt {
visit::mk_vt(@{visit_item: visit_item,
visit_stmt: visit_stmt,
visit_expr: visit_expr,
visit_block: visit_block,
visit_pat: visit_pat,
visit_local: visit_local
with *visit::default_visitor()})
}
fn visit_item(_item: @ast::item, &&_fcx: @fn_ctxt, _v: rvt) {
// Ignore items
}
fn visit_local(l: @ast::local, &&fcx: @fn_ctxt, v: rvt) {
visit::visit_local(l, fcx, v);
}
fn visit_pat(p: @ast::pat, &&fcx: @fn_ctxt, v: rvt) {
visit::visit_pat(p, fcx, v);
}
fn visit_block(b: ast::blk, &&fcx: @fn_ctxt, v: rvt) {
visit::visit_block(b, fcx, v);
}
fn visit_expr(e: @ast::expr, &&fcx: @fn_ctxt, v: rvt) {
#debug["visit_expr(e=%s)", pprust::expr_to_str(e)];
visit_ty(fcx.expr_ty(e), e.id, e.span, fcx);
visit::visit_expr(e, fcx, v);
}
fn visit_stmt(s: @ast::stmt, &&fcx: @fn_ctxt, v: rvt) {
visit::visit_stmt(s, fcx, v);
}
fn visit_ty(ty: ty::t,
id: ast::node_id,
span: span,
fcx: @fn_ctxt) {
// Try to resolve the type. If we encounter an error, then typeck
// is going to fail anyway, so just stop here and let typeck
// report errors later on in the writeback phase.
let ty = alt infer::resolve_deep(fcx.infcx, ty, false) {
result::err(_) { ret; }
result::ok(ty) { ty }
};
// find the region where this expr evaluation is taking place
let tcx = fcx.ccx.tcx;
let encl_region = ty::encl_region(tcx, id);
#debug["visit_ty(ty=%s, id=%d, encl_region=%s)",
ppaux::ty_to_str(tcx, ty),
id,
ppaux::region_to_str(tcx, encl_region)];
// Otherwise, look at the type and see if it is a region pointer.
if !ty::type_has_regions(ty) { ret; }
ty::walk_regions_and_ty(
tcx, ty,
{ |r| constrain_region(fcx, encl_region, span, r); },
{ |t| ty::type_has_regions(t) });
fn constrain_region(fcx: @fn_ctxt,
encl_region: ty::region,
span: span,
region: ty::region) {
let tcx = fcx.ccx.tcx;
#debug["constrain_region(encl_region=%s, region=%s)",
ppaux::region_to_str(tcx, encl_region),
ppaux::region_to_str(tcx, region)];
alt region {
ty::re_bound(_) {
// a bound region is one which appears inside an fn type.
// (e.g., the `&` in `fn(&T)`). Such regions need not be
// constrained by `encl_region` as they are placeholders
// for regions that are as-yet-unknown.
ret;
}
_ {}
}
alt fcx.mk_subr(encl_region, region) {
result::err(_) {
tcx.sess.span_err(
span,
#fmt["reference is not valid outside \
of its lifetime, %s",
ppaux::region_to_str(tcx, region)]);
}
result::ok(()) {
}
}
}
}

View file

@ -150,16 +150,19 @@ fn visit_item(_item: @ast::item, _wbcx: wb_ctxt, _v: wb_vt) {
// Ignore items
}
fn mk_visitor() -> visit::vt<wb_ctxt> {
visit::mk_vt(@{visit_item: visit_item,
visit_stmt: visit_stmt,
visit_expr: visit_expr,
visit_block: visit_block,
visit_pat: visit_pat,
visit_local: visit_local
with *visit::default_visitor()})
}
fn resolve_type_vars_in_expr(fcx: @fn_ctxt, e: @ast::expr) -> bool {
let wbcx = {fcx: fcx, mut success: true};
let visit =
visit::mk_vt(@{visit_item: visit_item,
visit_stmt: visit_stmt,
visit_expr: visit_expr,
visit_block: visit_block,
visit_pat: visit_pat,
visit_local: visit_local
with *visit::default_visitor()});
let visit = mk_visitor();
visit.visit_expr(e, wbcx, visit);
ret wbcx.success;
}
@ -168,14 +171,7 @@ fn resolve_type_vars_in_fn(fcx: @fn_ctxt,
decl: ast::fn_decl,
blk: ast::blk) -> bool {
let wbcx = {fcx: fcx, mut success: true};
let visit =
visit::mk_vt(@{visit_item: visit_item,
visit_stmt: visit_stmt,
visit_expr: visit_expr,
visit_block: visit_block,
visit_pat: visit_pat,
visit_local: visit_local
with *visit::default_visitor()});
let visit = mk_visitor();
visit.visit_block(blk, wbcx, visit);
for decl.inputs.each {|arg|
resolve_type_vars_for_node(wbcx, arg.ty.span, arg.id);

View file

@ -52,6 +52,8 @@ mod middle {
mod ast_map;
mod resolve;
mod typeck {
mod regionck;
mod demand;
mod infer;
mod astconv;
mod collect;
@ -72,7 +74,6 @@ mod middle {
mod capture;
mod pat_util;
mod region;
mod regionck;
mod const_eval;
mod astencode;

View file

@ -3,6 +3,7 @@ fn foo(x: &uint) -> &uint { x }
fn main() {
let p = @3u;
let r = foo(p);
//!^ ERROR reference is not valid
assert *p == *r;
//!^ ERROR reference is not valid
}

View file

@ -0,0 +1,7 @@
fn foo(x: &uint) -> &uint { x }
fn bar(x: &uint) -> uint { *x }
fn main() {
let p = @3u;
bar(foo(p)); //! ERROR reference is not valid
}