about summary refs log tree commit diff
diff options
context:
space:
mode:
authorNiko Matsakis <niko@alum.mit.edu>2012-05-11 13:09:19 -0700
committerNiko Matsakis <niko@alum.mit.edu>2012-05-11 14:05:58 -0700
commit63eb8e0e8784ad8cc126bf80a9267c30b4a3a82a (patch)
treef82e34007301b2bb272260440b1c3b498bc43044
parent2585384c977046a58e140c598ac5d4270501568d (diff)
downloadrust-63eb8e0e8784ad8cc126bf80a9267c30b4a3a82a.tar.gz
rust-63eb8e0e8784ad8cc126bf80a9267c30b4a3a82a.zip
move purity checking into borrowck, addresses #1422
-rw-r--r--src/libcore/vec.rs2
-rw-r--r--src/rustc/middle/borrowck.rs166
-rw-r--r--src/rustc/middle/typeck.rs59
-rw-r--r--src/test/compile-fail/native-unsafe-fn-called.rs3
-rw-r--r--src/test/compile-fail/pure-modifies-aliased.rs18
-rw-r--r--src/test/compile-fail/unsafe-fn-called-from-safe.rs3
-rw-r--r--src/test/run-pass/pure-sum.rs40
7 files changed, 203 insertions, 88 deletions
diff --git a/src/libcore/vec.rs b/src/libcore/vec.rs
index cddb1346c01..7a316507912 100644
--- a/src/libcore/vec.rs
+++ b/src/libcore/vec.rs
@@ -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
 
diff --git a/src/rustc/middle/borrowck.rs b/src/rustc/middle/borrowck.rs
index 1d79ea41fb8..cd881f1c731 100644
--- a/src/rustc/middle/borrowck.rs
+++ b/src/rustc/middle/borrowck.rs
@@ -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;
-
-    // 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; }
-    };
+    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; }
+            };
 
-    visit::visit_fn(fk, decl, body, sp, id, self, visitor);
+            alt decl.purity {
+              ast::pure_fn { self.is_pure = true; }
+              _ { }
+            }
 
-    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,
diff --git a/src/rustc/middle/typeck.rs b/src/rustc/middle/typeck.rs
index 7e132ffbee6..6f77fc957b2 100644
--- a/src/rustc/middle/typeck.rs
+++ b/src/rustc/middle/typeck.rs
@@ -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) {
diff --git a/src/test/compile-fail/native-unsafe-fn-called.rs b/src/test/compile-fail/native-unsafe-fn-called.rs
index 6459a423611..ee44500fee3 100644
--- a/src/test/compile-fail/native-unsafe-fn-called.rs
+++ b/src/test/compile-fail/native-unsafe-fn-called.rs
@@ -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
 }
 
diff --git a/src/test/compile-fail/pure-modifies-aliased.rs b/src/test/compile-fail/pure-modifies-aliased.rs
new file mode 100644
index 00000000000..9e825b9daca
--- /dev/null
+++ b/src/test/compile-fail/pure-modifies-aliased.rs
@@ -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() {
+}
\ No newline at end of file
diff --git a/src/test/compile-fail/unsafe-fn-called-from-safe.rs b/src/test/compile-fail/unsafe-fn-called-from-safe.rs
index 62fcfa689d9..2353be31c2b 100644
--- a/src/test/compile-fail/unsafe-fn-called-from-safe.rs
+++ b/src/test/compile-fail/unsafe-fn-called-from-safe.rs
@@ -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
 }
diff --git a/src/test/run-pass/pure-sum.rs b/src/test/run-pass/pure-sum.rs
new file mode 100644
index 00000000000..f5bc71d94b0
--- /dev/null
+++ b/src/test/run-pass/pure-sum.rs
@@ -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() {
+}
\ No newline at end of file