Skip to content

Commit 63eb8e0

Browse files
committed
move purity checking into borrowck, addresses #1422
1 parent 2585384 commit 63eb8e0

File tree

7 files changed

+203
-88
lines changed

7 files changed

+203
-88
lines changed

src/libcore/vec.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -997,7 +997,7 @@ impl extensions<T> for [const T] {
997997
fn iteri(f: fn(uint, T)) { iteri(self, f) }
998998
#[doc = "Returns the length of a vector"]
999999
#[inline]
1000-
fn len() -> uint { len(self) }
1000+
pure fn len() -> uint { len(self) }
10011001
#[doc = "
10021002
Find the first index matching some predicate
10031003

src/rustc/middle/borrowck.rs

Lines changed: 141 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,13 @@ fn check_sup_mutbl(req_m: ast::mutability,
149149
}
150150
}
151151

152+
fn save_and_restore<T:copy,U>(&t: T, f: fn() -> U) -> U {
153+
let old_t = t;
154+
let u <- f();
155+
t = old_t;
156+
ret u;
157+
}
158+
152159
// ----------------------------------------------------------------------
153160
// Gathering loans
154161
//
@@ -398,15 +405,18 @@ enum check_loan_ctxt = @{
398405
// Keep track of whether we're inside a ctor, so as to
399406
// allow mutating immutable fields in the same class if
400407
// we are in a ctor, we track the self id
401-
mut in_ctor: bool
408+
mut in_ctor: bool,
409+
410+
mut is_pure: bool
402411
};
403412

404413
fn check_loans(bccx: borrowck_ctxt,
405414
req_loan_map: req_loan_map,
406415
crate: @ast::crate) {
407416
let clcx = check_loan_ctxt(@{bccx: bccx,
408417
req_loan_map: req_loan_map,
409-
mut in_ctor: false});
418+
mut in_ctor: false,
419+
mut is_pure: false});
410420
let vt = visit::mk_vt(@{visit_expr: check_loans_in_expr,
411421
visit_block: check_loans_in_block,
412422
visit_fn: check_loans_in_fn
@@ -465,6 +475,76 @@ impl methods for check_loan_ctxt {
465475
}
466476
}
467477

478+
// when we are in a pure context, we check each call to ensure
479+
// that the function which is invoked is itself pure.
480+
fn check_pure(expr: @ast::expr) {
481+
let tcx = self.tcx();
482+
alt ty::get(tcx.ty(expr)).struct {
483+
ty::ty_fn(_) {
484+
// Extract purity or unsafety based on what kind of callee
485+
// we've got. This would be cleaner if we just admitted
486+
// that we have an effect system and carried the purity
487+
// etc around in the type.
488+
489+
// First, check the def_map---if expr.id is present then
490+
// expr must be a path (at least I think that's the idea---NDM)
491+
let callee_purity = alt tcx.def_map.find(expr.id) {
492+
some(ast::def_fn(_, p)) { p }
493+
some(ast::def_variant(_, _)) { ast::pure_fn }
494+
_ {
495+
// otherwise it may be a method call that we can trace
496+
// to the def'n site:
497+
alt self.bccx.method_map.find(expr.id) {
498+
some(typeck::method_static(did)) {
499+
if did.crate == ast::local_crate {
500+
alt tcx.items.get(did.node) {
501+
ast_map::node_method(m, _, _) { m.decl.purity }
502+
_ { tcx.sess.span_bug(expr.span,
503+
"Node not bound \
504+
to a method") }
505+
}
506+
} else {
507+
metadata::csearch::lookup_method_purity(
508+
tcx.sess.cstore,
509+
did)
510+
}
511+
}
512+
some(typeck::method_param(iid, n_m, _, _)) |
513+
some(typeck::method_iface(iid, n_m)) {
514+
ty::iface_methods(tcx, iid)[n_m].purity
515+
}
516+
none {
517+
// otherwise it's just some dang thing. We know
518+
// it cannot be unsafe because we do not allow
519+
// unsafe functions to be used as values (or,
520+
// rather, we only allow that inside an unsafe
521+
// block, and then it's up to the user to keep
522+
// things confined).
523+
ast::impure_fn
524+
}
525+
}
526+
}
527+
};
528+
529+
alt callee_purity {
530+
ast::crust_fn | ast::pure_fn { /*ok*/ }
531+
ast::impure_fn {
532+
self.bccx.span_err(
533+
expr.span,
534+
"pure function calls function \
535+
not known to be pure");
536+
}
537+
ast::unsafe_fn {
538+
self.bccx.span_err(
539+
expr.span,
540+
"pure function calls unsafe function");
541+
}
542+
}
543+
}
544+
_ { /* not a fn, ok */ }
545+
}
546+
}
547+
468548
fn check_for_conflicting_loans(scope_id: ast::node_id) {
469549
let new_loanss = alt self.req_loan_map.find(scope_id) {
470550
none { ret; }
@@ -532,6 +612,16 @@ impl methods for check_loan_ctxt {
532612
}
533613
}
534614

615+
// if this is a pure function, only loan-able state can be
616+
// assigned, because it is uniquely tied to this function and
617+
// is not visible from the outside
618+
if self.is_pure && cmt.lp.is_none() {
619+
self.bccx.span_err(
620+
ex.span,
621+
#fmt["%s prohibited in pure functions",
622+
at.ing_form(self.bccx.cmt_to_str(cmt))]);
623+
}
624+
535625
// check for a conflicting loan as well, except in the case of
536626
// taking a mutable ref. that will create a loan of its own
537627
// 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,
617707
sp: span, id: ast::node_id, &&self: check_loan_ctxt,
618708
visitor: visit::vt<check_loan_ctxt>) {
619709

620-
let old_in_ctor = self.in_ctor;
621-
622-
// In principle, we could consider fk_anon(*) or fk_fn_block(*) to
623-
// be in a ctor, I suppose, but the purpose of the in_ctor flag is
624-
// to allow modifications of otherwise immutable fields and
625-
// typestate wouldn't be able to "see" into those functions
626-
// anyway, so it wouldn't be very helpful.
627-
alt fk {
628-
visit::fk_ctor(*) { self.in_ctor = true; }
629-
_ { self.in_ctor = false; }
630-
};
710+
save_and_restore(self.in_ctor) {||
711+
save_and_restore(self.is_pure) {||
712+
// In principle, we could consider fk_anon(*) or
713+
// fk_fn_block(*) to be in a ctor, I suppose, but the
714+
// purpose of the in_ctor flag is to allow modifications
715+
// of otherwise immutable fields and typestate wouldn't be
716+
// able to "see" into those functions anyway, so it
717+
// wouldn't be very helpful.
718+
alt fk {
719+
visit::fk_ctor(*) { self.in_ctor = true; }
720+
_ { self.in_ctor = false; }
721+
};
631722

632-
visit::visit_fn(fk, decl, body, sp, id, self, visitor);
723+
alt decl.purity {
724+
ast::pure_fn { self.is_pure = true; }
725+
_ { }
726+
}
633727

634-
self.in_ctor = old_in_ctor;
728+
visit::visit_fn(fk, decl, body, sp, id, self, visitor);
729+
}
730+
}
635731
}
636732

637733
fn check_loans_in_expr(expr: @ast::expr,
@@ -680,6 +776,10 @@ fn check_loans_in_expr(expr: @ast::expr,
680776
}
681777
}
682778
ast::expr_call(f, args, _) {
779+
if self.is_pure {
780+
self.check_pure(f);
781+
for args.each { |arg| self.check_pure(arg) }
782+
}
683783
let arg_tys = ty::ty_fn_args(ty::expr_ty(self.tcx(), f));
684784
vec::iter2(args, arg_tys) { |arg, arg_ty|
685785
alt ty::resolved_mode(self.tcx(), arg_ty.mode) {
@@ -696,14 +796,25 @@ fn check_loans_in_expr(expr: @ast::expr,
696796
}
697797
_ { }
698798
}
799+
699800
visit::visit_expr(expr, self, vt);
700801
}
701802

702803
fn check_loans_in_block(blk: ast::blk,
703804
&&self: check_loan_ctxt,
704805
vt: visit::vt<check_loan_ctxt>) {
705-
self.check_for_conflicting_loans(blk.node.id);
706-
visit::visit_block(blk, self, vt);
806+
save_and_restore(self.is_pure) {||
807+
self.check_for_conflicting_loans(blk.node.id);
808+
809+
alt blk.node.rules {
810+
ast::default_blk {
811+
}
812+
ast::unchecked_blk |
813+
ast::unsafe_blk { self.is_pure = false; }
814+
}
815+
816+
visit::visit_block(blk, self, vt);
817+
}
707818
}
708819

709820
// ----------------------------------------------------------------------
@@ -1022,20 +1133,25 @@ impl categorize_methods for borrowck_ctxt {
10221133
// lp: loan path, must be none for aliasable things
10231134
let {m,lp} = alt ty::resolved_mode(self.tcx, mode) {
10241135
ast::by_mutbl_ref {
1025-
{m:m_mutbl, lp:none}
1136+
{m: m_mutbl,
1137+
lp: none}
10261138
}
10271139
ast::by_move | ast::by_copy {
1028-
{m:m_mutbl, lp:some(@lp_arg(vid))}
1140+
{m: m_mutbl,
1141+
lp: some(@lp_arg(vid))}
10291142
}
10301143
ast::by_ref {
1031-
if TREAT_CONST_AS_IMM {
1032-
{m:m_imm, lp:some(@lp_arg(vid))}
1033-
} else {
1034-
{m:m_const, lp:none}
1035-
}
1144+
{m: if TREAT_CONST_AS_IMM {m_imm} else {m_const},
1145+
lp: none}
10361146
}
10371147
ast::by_val {
1038-
{m:m_imm, lp:some(@lp_arg(vid))}
1148+
// by-value is this hybrid mode where we have a
1149+
// pointer but we do not own it. This is not
1150+
// considered loanable because, for example, a by-ref
1151+
// and and by-val argument might both actually contain
1152+
// the same unique ptr.
1153+
{m: m_imm,
1154+
lp: none}
10391155
}
10401156
};
10411157
@{id:id, span:span,

src/rustc/middle/typeck.rs

Lines changed: 0 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1349,18 +1349,6 @@ impl methods for @fn_ctxt {
13491349
infer::mk_eqty(self.infcx, sub, sup)
13501350
}
13511351

1352-
fn require_impure(sp: span) {
1353-
alt self.purity {
1354-
ast::unsafe_fn { ret; }
1355-
ast::impure_fn | ast::crust_fn { ret; }
1356-
ast::pure_fn {
1357-
self.ccx.tcx.sess.span_err(
1358-
sp,
1359-
"found impure expression in pure function decl");
1360-
}
1361-
}
1362-
}
1363-
13641352
fn require_unsafe(sp: span, op: str) {
13651353
alt self.purity {
13661354
ast::unsafe_fn {/*ok*/}
@@ -2425,45 +2413,6 @@ fn check_pat(pcx: pat_ctxt, pat: @ast::pat, expected: ty::t) {
24252413
}
24262414
}
24272415

2428-
fn require_pure_call(ccx: @crate_ctxt, caller_purity: ast::purity,
2429-
callee: @ast::expr, sp: span) {
2430-
if caller_purity == ast::unsafe_fn { ret; }
2431-
let callee_purity = alt ccx.tcx.def_map.find(callee.id) {
2432-
some(ast::def_fn(_, p)) { p }
2433-
some(ast::def_variant(_, _)) { ast::pure_fn }
2434-
_ {
2435-
alt ccx.method_map.find(callee.id) {
2436-
some(method_static(did)) {
2437-
if did.crate == ast::local_crate {
2438-
alt ccx.tcx.items.get(did.node) {
2439-
ast_map::node_method(m, _, _) { m.decl.purity }
2440-
_ { ccx.tcx.sess.span_bug(sp,
2441-
"Node not bound to a method") }
2442-
}
2443-
} else {
2444-
csearch::lookup_method_purity(ccx.tcx.sess.cstore, did)
2445-
}
2446-
}
2447-
some(method_param(iid, n_m, _, _)) | some(method_iface(iid, n_m)) {
2448-
ty::iface_methods(ccx.tcx, iid)[n_m].purity
2449-
}
2450-
none { ast::impure_fn }
2451-
}
2452-
}
2453-
};
2454-
alt (caller_purity, callee_purity) {
2455-
(ast::impure_fn, ast::unsafe_fn) | (ast::crust_fn, ast::unsafe_fn) {
2456-
ccx.tcx.sess.span_err(sp, "safe function calls function marked \
2457-
unsafe");
2458-
}
2459-
(ast::pure_fn, ast::unsafe_fn) | (ast::pure_fn, ast::impure_fn) {
2460-
ccx.tcx.sess.span_err(sp, "pure function calls function not \
2461-
known to be pure");
2462-
}
2463-
_ {}
2464-
}
2465-
}
2466-
24672416
fn check_expr_with(fcx: @fn_ctxt, expr: @ast::expr, expected: ty::t) -> bool {
24682417
check_expr(fcx, expr, some(expected))
24692418
}
@@ -2989,10 +2938,6 @@ fn check_expr_with_unifier(fcx: @fn_ctxt,
29892938
r.fty
29902939
};
29912940

2992-
// Need to restrict oper to being an explicit expr_path if we're
2993-
// inside a pure function
2994-
require_pure_call(fcx.ccx, fcx.purity, f, sp);
2995-
29962941
// Pull the return type out of the type of the function.
29972942
alt structure_of(fcx, sp, fty) {
29982943
ty::ty_fn(f) {
@@ -3277,7 +3222,6 @@ fn check_expr_with_unifier(fcx: @fn_ctxt,
32773222
bot |= check_binop(fcx, expr, op, lhs, rhs);
32783223
}
32793224
ast::expr_assign_op(op, lhs, rhs) {
3280-
fcx.require_impure(expr.span);
32813225
bot |= check_binop(fcx, expr, op, lhs, rhs);
32823226
let lhs_t = fcx.expr_ty(lhs);
32833227
let result_t = fcx.expr_ty(expr);
@@ -3437,15 +3381,12 @@ fn check_expr_with_unifier(fcx: @fn_ctxt,
34373381
fcx.write_ty(id, fcx.expr_ty(a));
34383382
}
34393383
ast::expr_move(lhs, rhs) {
3440-
fcx.require_impure(expr.span);
34413384
bot = check_assignment(fcx, expr.span, lhs, rhs, id);
34423385
}
34433386
ast::expr_assign(lhs, rhs) {
3444-
fcx.require_impure(expr.span);
34453387
bot = check_assignment(fcx, expr.span, lhs, rhs, id);
34463388
}
34473389
ast::expr_swap(lhs, rhs) {
3448-
fcx.require_impure(expr.span);
34493390
bot = check_assignment(fcx, expr.span, lhs, rhs, id);
34503391
}
34513392
ast::expr_if(cond, thn, elsopt) {
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
// -*- rust -*-
2-
// error-pattern: safe function calls function marked unsafe
2+
33
#[abi = "cdecl"]
44
native mod test {
55
unsafe fn free();
66
}
77

88
fn main() {
99
test::free();
10+
//!^ ERROR access to unsafe function requires unsafe function or block
1011
}
1112

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Check that pure functions cannot modify aliased state.
2+
3+
pure fn modify_in_ref(&&sum: {mut f: int}) {
4+
sum.f = 3; //! ERROR assigning to mutable field prohibited in pure functions
5+
}
6+
7+
pure fn modify_in_box(sum: @mut {f: int}) {
8+
sum.f = 3; //! ERROR assigning to mutable field prohibited in pure functions
9+
}
10+
11+
impl foo for int {
12+
pure fn modify_in_box_rec(sum: @{mut f: int}) {
13+
sum.f = self; //! ERROR assigning to mutable field prohibited in pure functions
14+
}
15+
}
16+
17+
fn main() {
18+
}
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
// -*- rust -*-
2-
// error-pattern: safe function calls function marked unsafe
32

43
unsafe fn f() { ret; }
54

65
fn main() {
7-
f();
6+
f(); //! ERROR access to unsafe function requires unsafe function or block
87
}

0 commit comments

Comments
 (0)