rewrite purity check to search through scope chain

This commit is contained in:
Niko Matsakis 2012-05-23 20:55:56 -07:00
parent 6ca6a3b80b
commit c9eb9ee612

View File

@ -505,14 +505,11 @@ enum check_loan_ctxt = @{
// we are in a ctor, we track the self id
mut in_ctor: bool,
mut is_pure: purity_cause
mut declared_purity: ast::purity
};
// if we are enforcing purity, why are we doing so?
enum purity_cause {
// not enforcing purity:
pc_impure,
// enforcing purity because fn was declared pure:
pc_declaration,
@ -529,7 +526,7 @@ fn check_loans(bccx: borrowck_ctxt,
req_maps: req_maps,
reported: int_hash(),
mut in_ctor: false,
mut is_pure: pc_impure});
mut declared_purity: ast::impure_fn});
let vt = visit::mk_vt(@{visit_expr: check_loans_in_expr,
visit_block: check_loans_in_block,
visit_fn: check_loans_in_fn
@ -556,6 +553,37 @@ impl methods for assignment_type {
impl methods for check_loan_ctxt {
fn tcx() -> ty::ctxt { self.bccx.tcx }
fn purity(scope_id: ast::node_id) -> option<purity_cause> {
let default_purity = alt self.declared_purity {
// an unsafe declaration overrides all
ast::unsafe_fn { ret none; }
// otherwise, remember what was declared as the
// default, but we must scan for requirements
// imposed by the borrow check
ast::pure_fn { some(pc_declaration) }
ast::crust_fn | ast::impure_fn { none }
};
// scan to see if this scope or any enclosing scope requires
// purity. if so, that overrides the declaration.
let mut scope_id = scope_id;
let region_map = self.tcx().region_map;
let pure_map = self.req_maps.pure_map;
loop {
alt pure_map.find(scope_id) {
none {}
some(e) {ret some(pc_cmt(e));}
}
alt region_map.find(scope_id) {
none { ret default_purity; }
some(next_scope_id) { scope_id = next_scope_id; }
}
}
}
fn walk_loans(scope_id: ast::node_id,
f: fn(loan) -> bool) {
let mut scope_id = scope_id;
@ -590,7 +618,7 @@ impl methods for check_loan_ctxt {
// when we are in a pure context, we check each call to ensure
// that the function which is invoked is itself pure.
fn check_pure(expr: @ast::expr) {
fn check_pure(pc: purity_cause, expr: @ast::expr) {
let tcx = self.tcx();
alt ty::get(tcx.ty(expr)).struct {
ty::ty_fn(_) {
@ -645,7 +673,7 @@ impl methods for check_loan_ctxt {
}
ast::impure_fn | ast::unsafe_fn {
self.report_purity_error(
expr.span,
pc, expr.span,
"access to non-pure functions");
}
}
@ -654,21 +682,6 @@ impl methods for check_loan_ctxt {
}
}
fn check_for_purity_requirement(scope_id: ast::node_id) {
// if we are not already enforcing purity, check whether the
// gather pass thought we needed to enforce purity for this
// scope.
alt self.is_pure {
pc_declaration | pc_cmt(*) { }
pc_impure {
alt self.req_maps.pure_map.find(scope_id) {
none {}
some(e) {self.is_pure = pc_cmt(e)}
}
}
}
}
fn check_for_conflicting_loans(scope_id: ast::node_id) {
let new_loanss = alt self.req_maps.req_loan_map.find(scope_id) {
none { ret; }
@ -739,10 +752,14 @@ impl methods for check_loan_ctxt {
// if this is a pure function, only loan-able state can be
// assigned, because it is uniquely tied to this function and
// is not visible from the outside
if self.is_pure != pc_impure && cmt.lp.is_none() {
self.report_purity_error(
ex.span,
at.ing_form(self.bccx.cmt_to_str(cmt)));
alt self.purity(ex.id) {
none {}
some(pc) {
if cmt.lp.is_none() {
self.report_purity_error(
pc, ex.span, at.ing_form(self.bccx.cmt_to_str(cmt)));
}
}
}
// check for a conflicting loan as well, except in the case of
@ -775,11 +792,8 @@ impl methods for check_loan_ctxt {
self.bccx.add_to_mutbl_map(cmt);
}
fn report_purity_error(sp: span, msg: str) {
alt copy self.is_pure {
pc_impure {
self.tcx().sess.bug("report_purity_error() called when impure");
}
fn report_purity_error(pc: purity_cause, sp: span, msg: str) {
alt pc {
pc_declaration {
self.tcx().sess.span_err(
sp,
@ -855,7 +869,7 @@ fn check_loans_in_fn(fk: visit::fn_kind, decl: ast::fn_decl, body: ast::blk,
visitor: visit::vt<check_loan_ctxt>) {
save_and_restore(self.in_ctor) {||
save_and_restore(self.is_pure) {||
save_and_restore(self.declared_purity) {||
// In principle, we could consider fk_anon(*) or
// fk_fn_block(*) to be in a ctor, I suppose, but the
// purpose of the in_ctor flag is to allow modifications
@ -870,10 +884,7 @@ fn check_loans_in_fn(fk: visit::fn_kind, decl: ast::fn_decl, body: ast::blk,
// NDM this doesn't seem algother right, what about fn items
// nested in pure fns? etc?
alt decl.purity {
ast::pure_fn { self.is_pure = pc_declaration; }
_ { }
}
self.declared_purity = decl.purity;
visit::visit_fn(fk, decl, body, sp, id, self, visitor);
}
@ -884,16 +895,7 @@ fn check_loans_in_expr(expr: @ast::expr,
&&self: check_loan_ctxt,
vt: visit::vt<check_loan_ctxt>) {
self.check_for_conflicting_loans(expr.id);
save_and_restore(self.is_pure) {||
self.check_for_purity_requirement(expr.id);
check_loans_in_expr_1(expr, self, vt);
}
}
// avoid rightward drift by breaking this out into its own fn
fn check_loans_in_expr_1(expr: @ast::expr,
&&self: check_loan_ctxt,
vt: visit::vt<check_loan_ctxt>) {
alt expr.node {
ast::expr_swap(l, r) {
self.check_assignment(at_swap, l);
@ -936,9 +938,12 @@ fn check_loans_in_expr_1(expr: @ast::expr,
}
}
ast::expr_call(f, args, _) {
if self.is_pure != pc_impure {
self.check_pure(f);
for args.each { |arg| self.check_pure(arg) }
alt self.purity(expr.id) {
none {}
some(pc) {
self.check_pure(pc, f);
for args.each { |arg| self.check_pure(pc, arg) }
}
}
let arg_tys = ty::ty_fn_args(ty::expr_ty(self.tcx(), f));
vec::iter2(args, arg_tys) { |arg, arg_ty|
@ -963,28 +968,17 @@ fn check_loans_in_expr_1(expr: @ast::expr,
fn check_loans_in_block(blk: ast::blk,
&&self: check_loan_ctxt,
vt: visit::vt<check_loan_ctxt>) {
save_and_restore(self.is_pure) {||
save_and_restore(self.declared_purity) {||
self.check_for_conflicting_loans(blk.node.id);
self.check_for_purity_requirement(blk.node.id);
alt blk.node.rules {
ast::default_blk {
}
ast::unchecked_blk {
alt self.is_pure {
pc_impure | pc_declaration {
self.is_pure = pc_impure;
}
pc_cmt(_) {
// unchecked does not override purity requirements due
// to borrows; unchecked didn't seem strong enough to
// justify potential memory unsafety to me
}
}
self.declared_purity = ast::impure_fn;
}
ast::unsafe_blk {
// unsafe blocks override everything
self.is_pure = pc_impure;
self.declared_purity = ast::unsafe_fn;
}
}
@ -1472,6 +1466,15 @@ impl categorize_methods for borrowck_ctxt {
ty_to_str(self.tcx, cmt.ty)]
}
fn pk_to_sigil(pk: ptr_kind) -> str {
alt pk {
uniq_ptr {"~"}
gc_ptr {"@"}
region_ptr {"&"}
unsafe_ptr {"*"}
}
}
fn cmt_to_str(cmt: cmt) -> str {
let mut_str = self.mut_to_str(cmt.mutbl);
alt cmt.cat {
@ -1482,7 +1485,8 @@ impl categorize_methods for borrowck_ctxt {
cat_rvalue { "non-lvalue" }
cat_local(_) { mut_str + " local variable" }
cat_arg(_) { mut_str + " argument" }
cat_deref(*) { "dereference of " + mut_str + " pointer" }
cat_deref(_, _, pk) { #fmt["dereference of %s %s pointer",
mut_str, self.pk_to_sigil(pk)] }
cat_stack_upvar(_) { mut_str + " upvar" }
cat_comp(_, comp_field(_)) { mut_str + " field" }
cat_comp(_, comp_tuple) { "tuple content" }