0
0
mirror of https://github.com/rust-lang/rust.git synced 2024-12-01 13:18:54 +01:00

move purity checking into borrowck, addresses #1422

This commit is contained in:
Niko Matsakis 2012-05-11 13:09:19 -07:00
parent 2585384c97
commit 63eb8e0e87
7 changed files with 203 additions and 88 deletions

View File

@ -997,7 +997,7 @@ impl extensions<T> for [const T] {
fn iteri(f: fn(uint, T)) { iteri(self, f) }
#[doc = "Returns the length of a vector"]
#[inline]
fn len() -> uint { len(self) }
pure fn len() -> uint { len(self) }
#[doc = "
Find the first index matching some predicate

View File

@ -149,6 +149,13 @@ fn check_sup_mutbl(req_m: ast::mutability,
}
}
fn save_and_restore<T:copy,U>(&t: T, f: fn() -> U) -> U {
let old_t = t;
let u <- f();
t = old_t;
ret u;
}
// ----------------------------------------------------------------------
// Gathering loans
//
@ -398,7 +405,9 @@ enum check_loan_ctxt = @{
// Keep track of whether we're inside a ctor, so as to
// allow mutating immutable fields in the same class if
// we are in a ctor, we track the self id
mut in_ctor: bool
mut in_ctor: bool,
mut is_pure: bool
};
fn check_loans(bccx: borrowck_ctxt,
@ -406,7 +415,8 @@ fn check_loans(bccx: borrowck_ctxt,
crate: @ast::crate) {
let clcx = check_loan_ctxt(@{bccx: bccx,
req_loan_map: req_loan_map,
mut in_ctor: false});
mut in_ctor: false,
mut is_pure: false});
let vt = visit::mk_vt(@{visit_expr: check_loans_in_expr,
visit_block: check_loans_in_block,
visit_fn: check_loans_in_fn
@ -465,6 +475,76 @@ 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) {
let tcx = self.tcx();
alt ty::get(tcx.ty(expr)).struct {
ty::ty_fn(_) {
// Extract purity or unsafety based on what kind of callee
// we've got. This would be cleaner if we just admitted
// that we have an effect system and carried the purity
// etc around in the type.
// First, check the def_map---if expr.id is present then
// expr must be a path (at least I think that's the idea---NDM)
let callee_purity = alt tcx.def_map.find(expr.id) {
some(ast::def_fn(_, p)) { p }
some(ast::def_variant(_, _)) { ast::pure_fn }
_ {
// otherwise it may be a method call that we can trace
// to the def'n site:
alt self.bccx.method_map.find(expr.id) {
some(typeck::method_static(did)) {
if did.crate == ast::local_crate {
alt tcx.items.get(did.node) {
ast_map::node_method(m, _, _) { m.decl.purity }
_ { tcx.sess.span_bug(expr.span,
"Node not bound \
to a method") }
}
} else {
metadata::csearch::lookup_method_purity(
tcx.sess.cstore,
did)
}
}
some(typeck::method_param(iid, n_m, _, _)) |
some(typeck::method_iface(iid, n_m)) {
ty::iface_methods(tcx, iid)[n_m].purity
}
none {
// otherwise it's just some dang thing. We know
// it cannot be unsafe because we do not allow
// unsafe functions to be used as values (or,
// rather, we only allow that inside an unsafe
// block, and then it's up to the user to keep
// things confined).
ast::impure_fn
}
}
}
};
alt callee_purity {
ast::crust_fn | ast::pure_fn { /*ok*/ }
ast::impure_fn {
self.bccx.span_err(
expr.span,
"pure function calls function \
not known to be pure");
}
ast::unsafe_fn {
self.bccx.span_err(
expr.span,
"pure function calls unsafe function");
}
}
}
_ { /* not a fn, ok */ }
}
}
fn check_for_conflicting_loans(scope_id: ast::node_id) {
let new_loanss = alt self.req_loan_map.find(scope_id) {
none { ret; }
@ -532,6 +612,16 @@ 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 && cmt.lp.is_none() {
self.bccx.span_err(
ex.span,
#fmt["%s prohibited in pure functions",
at.ing_form(self.bccx.cmt_to_str(cmt))]);
}
// check for a conflicting loan as well, except in the case of
// taking a mutable ref. that will create a loan of its own
// which will be checked for compat separately in
@ -617,21 +707,27 @@ fn check_loans_in_fn(fk: visit::fn_kind, decl: ast::fn_decl, body: ast::blk,
sp: span, id: ast::node_id, &&self: check_loan_ctxt,
visitor: visit::vt<check_loan_ctxt>) {
let old_in_ctor = self.in_ctor;
save_and_restore(self.in_ctor) {||
save_and_restore(self.is_pure) {||
// 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
// of otherwise immutable fields and typestate wouldn't be
// able to "see" into those functions anyway, so it
// wouldn't be very helpful.
alt fk {
visit::fk_ctor(*) { self.in_ctor = true; }
_ { self.in_ctor = false; }
};
// 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 of otherwise immutable fields and
// typestate wouldn't be able to "see" into those functions
// anyway, so it wouldn't be very helpful.
alt fk {
visit::fk_ctor(*) { self.in_ctor = true; }
_ { self.in_ctor = false; }
};
alt decl.purity {
ast::pure_fn { self.is_pure = true; }
_ { }
}
visit::visit_fn(fk, decl, body, sp, id, self, visitor);
self.in_ctor = old_in_ctor;
visit::visit_fn(fk, decl, body, sp, id, self, visitor);
}
}
}
fn check_loans_in_expr(expr: @ast::expr,
@ -680,6 +776,10 @@ fn check_loans_in_expr(expr: @ast::expr,
}
}
ast::expr_call(f, args, _) {
if self.is_pure {
self.check_pure(f);
for args.each { |arg| self.check_pure(arg) }
}
let arg_tys = ty::ty_fn_args(ty::expr_ty(self.tcx(), f));
vec::iter2(args, arg_tys) { |arg, arg_ty|
alt ty::resolved_mode(self.tcx(), arg_ty.mode) {
@ -696,14 +796,25 @@ fn check_loans_in_expr(expr: @ast::expr,
}
_ { }
}
visit::visit_expr(expr, self, vt);
}
fn check_loans_in_block(blk: ast::blk,
&&self: check_loan_ctxt,
vt: visit::vt<check_loan_ctxt>) {
self.check_for_conflicting_loans(blk.node.id);
visit::visit_block(blk, self, vt);
save_and_restore(self.is_pure) {||
self.check_for_conflicting_loans(blk.node.id);
alt blk.node.rules {
ast::default_blk {
}
ast::unchecked_blk |
ast::unsafe_blk { self.is_pure = false; }
}
visit::visit_block(blk, self, vt);
}
}
// ----------------------------------------------------------------------
@ -1022,20 +1133,25 @@ impl categorize_methods for borrowck_ctxt {
// lp: loan path, must be none for aliasable things
let {m,lp} = alt ty::resolved_mode(self.tcx, mode) {
ast::by_mutbl_ref {
{m:m_mutbl, lp:none}
{m: m_mutbl,
lp: none}
}
ast::by_move | ast::by_copy {
{m:m_mutbl, lp:some(@lp_arg(vid))}
{m: m_mutbl,
lp: some(@lp_arg(vid))}
}
ast::by_ref {
if TREAT_CONST_AS_IMM {
{m:m_imm, lp:some(@lp_arg(vid))}
} else {
{m:m_const, lp:none}
}
{m: if TREAT_CONST_AS_IMM {m_imm} else {m_const},
lp: none}
}
ast::by_val {
{m:m_imm, lp:some(@lp_arg(vid))}
// by-value is this hybrid mode where we have a
// pointer but we do not own it. This is not
// considered loanable because, for example, a by-ref
// and and by-val argument might both actually contain
// the same unique ptr.
{m: m_imm,
lp: none}
}
};
@{id:id, span:span,

View File

@ -1349,18 +1349,6 @@ impl methods for @fn_ctxt {
infer::mk_eqty(self.infcx, sub, sup)
}
fn require_impure(sp: span) {
alt self.purity {
ast::unsafe_fn { ret; }
ast::impure_fn | ast::crust_fn { ret; }
ast::pure_fn {
self.ccx.tcx.sess.span_err(
sp,
"found impure expression in pure function decl");
}
}
}
fn require_unsafe(sp: span, op: str) {
alt self.purity {
ast::unsafe_fn {/*ok*/}
@ -2425,45 +2413,6 @@ fn check_pat(pcx: pat_ctxt, pat: @ast::pat, expected: ty::t) {
}
}
fn require_pure_call(ccx: @crate_ctxt, caller_purity: ast::purity,
callee: @ast::expr, sp: span) {
if caller_purity == ast::unsafe_fn { ret; }
let callee_purity = alt ccx.tcx.def_map.find(callee.id) {
some(ast::def_fn(_, p)) { p }
some(ast::def_variant(_, _)) { ast::pure_fn }
_ {
alt ccx.method_map.find(callee.id) {
some(method_static(did)) {
if did.crate == ast::local_crate {
alt ccx.tcx.items.get(did.node) {
ast_map::node_method(m, _, _) { m.decl.purity }
_ { ccx.tcx.sess.span_bug(sp,
"Node not bound to a method") }
}
} else {
csearch::lookup_method_purity(ccx.tcx.sess.cstore, did)
}
}
some(method_param(iid, n_m, _, _)) | some(method_iface(iid, n_m)) {
ty::iface_methods(ccx.tcx, iid)[n_m].purity
}
none { ast::impure_fn }
}
}
};
alt (caller_purity, callee_purity) {
(ast::impure_fn, ast::unsafe_fn) | (ast::crust_fn, ast::unsafe_fn) {
ccx.tcx.sess.span_err(sp, "safe function calls function marked \
unsafe");
}
(ast::pure_fn, ast::unsafe_fn) | (ast::pure_fn, ast::impure_fn) {
ccx.tcx.sess.span_err(sp, "pure function calls function not \
known to be pure");
}
_ {}
}
}
fn check_expr_with(fcx: @fn_ctxt, expr: @ast::expr, expected: ty::t) -> bool {
check_expr(fcx, expr, some(expected))
}
@ -2989,10 +2938,6 @@ fn check_expr_with_unifier(fcx: @fn_ctxt,
r.fty
};
// Need to restrict oper to being an explicit expr_path if we're
// inside a pure function
require_pure_call(fcx.ccx, fcx.purity, f, sp);
// Pull the return type out of the type of the function.
alt structure_of(fcx, sp, fty) {
ty::ty_fn(f) {
@ -3277,7 +3222,6 @@ fn check_expr_with_unifier(fcx: @fn_ctxt,
bot |= check_binop(fcx, expr, op, lhs, rhs);
}
ast::expr_assign_op(op, lhs, rhs) {
fcx.require_impure(expr.span);
bot |= check_binop(fcx, expr, op, lhs, rhs);
let lhs_t = fcx.expr_ty(lhs);
let result_t = fcx.expr_ty(expr);
@ -3437,15 +3381,12 @@ fn check_expr_with_unifier(fcx: @fn_ctxt,
fcx.write_ty(id, fcx.expr_ty(a));
}
ast::expr_move(lhs, rhs) {
fcx.require_impure(expr.span);
bot = check_assignment(fcx, expr.span, lhs, rhs, id);
}
ast::expr_assign(lhs, rhs) {
fcx.require_impure(expr.span);
bot = check_assignment(fcx, expr.span, lhs, rhs, id);
}
ast::expr_swap(lhs, rhs) {
fcx.require_impure(expr.span);
bot = check_assignment(fcx, expr.span, lhs, rhs, id);
}
ast::expr_if(cond, thn, elsopt) {

View File

@ -1,5 +1,5 @@
// -*- rust -*-
// error-pattern: safe function calls function marked unsafe
#[abi = "cdecl"]
native mod test {
unsafe fn free();
@ -7,5 +7,6 @@ native mod test {
fn main() {
test::free();
//!^ ERROR access to unsafe function requires unsafe function or block
}

View File

@ -0,0 +1,18 @@
// Check that pure functions cannot modify aliased state.
pure fn modify_in_ref(&&sum: {mut f: int}) {
sum.f = 3; //! ERROR assigning to mutable field prohibited in pure functions
}
pure fn modify_in_box(sum: @mut {f: int}) {
sum.f = 3; //! ERROR assigning to mutable field prohibited in pure functions
}
impl foo for int {
pure fn modify_in_box_rec(sum: @{mut f: int}) {
sum.f = self; //! ERROR assigning to mutable field prohibited in pure functions
}
}
fn main() {
}

View File

@ -1,8 +1,7 @@
// -*- rust -*-
// error-pattern: safe function calls function marked unsafe
unsafe fn f() { ret; }
fn main() {
f();
f(); //! ERROR access to unsafe function requires unsafe function or block
}

View File

@ -0,0 +1,40 @@
// Check that pure functions can modify local state.
pure fn sums_to(v: [int], sum: int) -> bool {
let mut i = 0u, sum0 = 0;
while i < v.len() {
sum0 += v[i];
i += 1u;
}
ret sum0 == sum;
}
pure fn sums_to_using_uniq(v: [int], sum: int) -> bool {
let mut i = 0u, sum0 = ~mut 0;
while i < v.len() {
*sum0 += v[i];
i += 1u;
}
ret *sum0 == sum;
}
pure fn sums_to_using_rec(v: [int], sum: int) -> bool {
let mut i = 0u, sum0 = {f: 0};
while i < v.len() {
sum0.f += v[i];
i += 1u;
}
ret sum0.f == sum;
}
pure fn sums_to_using_uniq_rec(v: [int], sum: int) -> bool {
let mut i = 0u, sum0 = {f: ~mut 0};
while i < v.len() {
*sum0.f += v[i];
i += 1u;
}
ret *sum0.f == sum;
}
fn main() {
}