about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2013-06-29 02:34:43 -0700
committerbors <bors@rust-lang.org>2013-06-29 02:34:43 -0700
commitc80e3bac3e676abb106f7ef8ac7da5b153c65c8e (patch)
treedc1a7abef3d983d7a13e9a94f987e63b2fb78e44
parenta229c980c8c8e628571008882bd7da71f2b3ef7a (diff)
parente0788c7f523dc13609179b8f40c09af5c8668df6 (diff)
downloadrust-c80e3bac3e676abb106f7ef8ac7da5b153c65c8e.tar.gz
rust-c80e3bac3e676abb106f7ef8ac7da5b153c65c8e.zip
auto merge of #7244 : bblum/rust/once, r=nikomatsakis
@graydon suggested that once closures not be part of the language for 1.0, but that they might be hidden behind a -Z compile flag as an "experimental feature" in case people decide they need them.

Regardless of whether ```-Z once-fns``` is set, this PR will parse the ```once``` keyword and will prevent closures labelled with it from being called more than once. It will also permit moving out of captured vars in heap closures, just to let the runtime writers stop using ```Cell``` sooner. Setting ```-Z once-fns``` only toggles whether the move-out-from-capture privilege is also given for stack closures.

r? @nikomatsakis
-rw-r--r--src/librustc/driver/session.rs55
-rw-r--r--src/librustc/middle/borrowck/gather_loans/gather_moves.rs23
-rw-r--r--src/librustc/middle/mem_categorization.rs59
-rw-r--r--src/librustc/middle/moves.rs23
-rw-r--r--src/test/compile-fail/once-cant-call-twice-on-heap.rs29
-rw-r--r--src/test/compile-fail/once-cant-call-twice-on-stack.rs30
-rw-r--r--src/test/compile-fail/once-cant-copy-stack-once-fn-copy.rs20
-rw-r--r--src/test/compile-fail/once-cant-move-out-of-non-once-on-heap.rs29
-rw-r--r--src/test/compile-fail/once-cant-move-out-of-non-once-on-stack.rs29
-rw-r--r--src/test/run-pass/once-move-out-on-heap.rs29
-rw-r--r--src/test/run-pass/once-move-out-on-stack.rs30
11 files changed, 304 insertions, 52 deletions
diff --git a/src/librustc/driver/session.rs b/src/librustc/driver/session.rs
index ec86b43ffa3..9459116307d 100644
--- a/src/librustc/driver/session.rs
+++ b/src/librustc/driver/session.rs
@@ -45,32 +45,33 @@ pub struct config {
     float_type: float_ty
 }
 
-pub static verbose: uint = 1 << 0;
-pub static time_passes: uint = 1 << 1;
-pub static count_llvm_insns: uint = 1 << 2;
-pub static time_llvm_passes: uint = 1 << 3;
-pub static trans_stats: uint = 1 << 4;
-pub static asm_comments: uint = 1 << 5;
-pub static no_verify: uint = 1 << 6;
-pub static trace: uint = 1 << 7;
-pub static coherence: uint = 1 << 8;
-pub static borrowck_stats: uint = 1 << 9;
-pub static borrowck_note_pure: uint = 1 << 10;
-pub static borrowck_note_loan: uint = 1 << 11;
-pub static no_landing_pads: uint = 1 << 12;
-pub static debug_llvm: uint = 1 << 13;
-pub static count_type_sizes: uint = 1 << 14;
-pub static meta_stats: uint = 1 << 15;
-pub static no_opt: uint = 1 << 16;
+pub static verbose:                 uint = 1 <<  0;
+pub static time_passes:             uint = 1 <<  1;
+pub static count_llvm_insns:        uint = 1 <<  2;
+pub static time_llvm_passes:        uint = 1 <<  3;
+pub static trans_stats:             uint = 1 <<  4;
+pub static asm_comments:            uint = 1 <<  5;
+pub static no_verify:               uint = 1 <<  6;
+pub static trace:                   uint = 1 <<  7;
+pub static coherence:               uint = 1 <<  8;
+pub static borrowck_stats:          uint = 1 <<  9;
+pub static borrowck_note_pure:      uint = 1 << 10;
+pub static borrowck_note_loan:      uint = 1 << 11;
+pub static no_landing_pads:         uint = 1 << 12;
+pub static debug_llvm:              uint = 1 << 13;
+pub static count_type_sizes:        uint = 1 << 14;
+pub static meta_stats:              uint = 1 << 15;
+pub static no_opt:                  uint = 1 << 16;
 pub static no_monomorphic_collapse: uint = 1 << 17;
-pub static gc: uint = 1 << 18;
-pub static jit: uint = 1 << 19;
-pub static debug_info: uint = 1 << 20;
-pub static extra_debug_info: uint = 1 << 21;
-pub static statik: uint = 1 << 22;
-pub static print_link_args: uint = 1 << 23;
-pub static no_debug_borrows: uint = 1 << 24;
-pub static lint_llvm : uint = 1 << 25;
+pub static gc:                      uint = 1 << 18;
+pub static jit:                     uint = 1 << 19;
+pub static debug_info:              uint = 1 << 20;
+pub static extra_debug_info:        uint = 1 << 21;
+pub static statik:                  uint = 1 << 22;
+pub static print_link_args:         uint = 1 << 23;
+pub static no_debug_borrows:        uint = 1 << 24;
+pub static lint_llvm:               uint = 1 << 25;
+pub static once_fns:                uint = 1 << 26;
 
 pub fn debugging_opts_map() -> ~[(~str, ~str, uint)] {
     ~[(~"verbose", ~"in general, enable more debug printouts", verbose),
@@ -112,6 +113,9 @@ pub fn debugging_opts_map() -> ~[(~str, ~str, uint)] {
      (~"lint-llvm",
       ~"Run the LLVM lint pass on the pre-optimization IR",
       lint_llvm),
+     (~"once-fns",
+      ~"Allow 'once fn' closures to deinitialize captured variables",
+      once_fns),
     ]
 }
 
@@ -293,6 +297,7 @@ impl Session_ {
     pub fn debug_borrows(@self) -> bool {
         self.opts.optimize == No && !self.debugging_opt(no_debug_borrows)
     }
+    pub fn once_fns(@self) -> bool { self.debugging_opt(once_fns) }
 
     // pointless function, now...
     pub fn str_of(@self, id: ast::ident) -> @str {
diff --git a/src/librustc/middle/borrowck/gather_loans/gather_moves.rs b/src/librustc/middle/borrowck/gather_loans/gather_moves.rs
index c7bad60e90e..e950610cce6 100644
--- a/src/librustc/middle/borrowck/gather_loans/gather_moves.rs
+++ b/src/librustc/middle/borrowck/gather_loans/gather_moves.rs
@@ -101,9 +101,7 @@ fn check_is_legal_to_move_from(bccx: @BorrowckCtxt,
                                cmt0: mc::cmt,
                                cmt: mc::cmt) -> bool {
     match cmt.cat {
-        mc::cat_stack_upvar(*) |
         mc::cat_implicit_self(*) |
-        mc::cat_copied_upvar(*) |
         mc::cat_deref(_, _, mc::region_ptr(*)) |
         mc::cat_deref(_, _, mc::gc_ptr(*)) |
         mc::cat_deref(_, _, mc::unsafe_ptr(*)) => {
@@ -114,6 +112,27 @@ fn check_is_legal_to_move_from(bccx: @BorrowckCtxt,
             false
         }
 
+        // These are separate from the above cases for a better error message.
+        mc::cat_stack_upvar(*) |
+        mc::cat_copied_upvar(mc::CopiedUpvar { onceness: ast::Many, _ }) => {
+            let once_hint = if bccx.tcx.sess.once_fns() {
+                " (unless the destination closure type is `once fn')"
+            } else {
+                ""
+            };
+            bccx.span_err(
+                cmt0.span,
+                fmt!("cannot move out of %s%s", bccx.cmt_to_str(cmt), once_hint));
+            false
+        }
+
+        // Can move out of captured upvars only if the destination closure
+        // type is 'once'. 1-shot stack closures emit the copied_upvar form
+        // (see mem_categorization.rs).
+        mc::cat_copied_upvar(mc::CopiedUpvar { onceness: ast::Once, _ }) => {
+            true
+        }
+
         // It seems strange to allow a move out of a static item,
         // but what happens in practice is that you have a
         // reference to a constant with a type that should be
diff --git a/src/librustc/middle/mem_categorization.rs b/src/librustc/middle/mem_categorization.rs
index 52c01fa7647..07e65e8af56 100644
--- a/src/librustc/middle/mem_categorization.rs
+++ b/src/librustc/middle/mem_categorization.rs
@@ -78,7 +78,7 @@ pub enum categorization {
 }
 
 #[deriving(Eq)]
-struct CopiedUpvar {
+pub struct CopiedUpvar {
     upvar_id: ast::node_id,
     onceness: ast::Onceness,
 }
@@ -507,30 +507,41 @@ impl mem_categorization_ctxt {
               let ty = ty::node_id_to_type(self.tcx, fn_node_id);
               match ty::get(ty).sty {
                   ty::ty_closure(ref closure_ty) => {
-                      let sigil = closure_ty.sigil;
-                      match sigil {
-                          ast::BorrowedSigil => {
-                              let upvar_cmt =
-                                  self.cat_def(id, span, expr_ty, *inner);
-                              @cmt_ {
-                                  id:id,
-                                  span:span,
-                                  cat:cat_stack_upvar(upvar_cmt),
-                                  mutbl:upvar_cmt.mutbl.inherit(),
-                                  ty:upvar_cmt.ty
-                              }
+                      // Decide whether to use implicit reference or by copy/move
+                      // capture for the upvar. This, combined with the onceness,
+                      // determines whether the closure can move out of it.
+                      let var_is_refd = match (closure_ty.sigil, closure_ty.onceness) {
+                          // Many-shot stack closures can never move out.
+                          (ast::BorrowedSigil, ast::Many) => true,
+                          // 1-shot stack closures can move out with "-Z once-fns".
+                          (ast::BorrowedSigil, ast::Once)
+                              if self.tcx.sess.once_fns() => false,
+                          (ast::BorrowedSigil, ast::Once) => true,
+                          // Heap closures always capture by copy/move, and can
+                          // move out iff they are once.
+                          (ast::OwnedSigil, _) | (ast::ManagedSigil, _) => false,
+
+                      };
+                      if var_is_refd {
+                          let upvar_cmt =
+                              self.cat_def(id, span, expr_ty, *inner);
+                          @cmt_ {
+                              id:id,
+                              span:span,
+                              cat:cat_stack_upvar(upvar_cmt),
+                              mutbl:upvar_cmt.mutbl.inherit(),
+                              ty:upvar_cmt.ty
                           }
-                          ast::OwnedSigil | ast::ManagedSigil => {
-                              // FIXME #2152 allow mutation of moved upvars
-                              @cmt_ {
-                                  id:id,
-                                  span:span,
-                                  cat:cat_copied_upvar(CopiedUpvar {
-                                      upvar_id: upvar_id,
-                                      onceness: closure_ty.onceness}),
-                                  mutbl:McImmutable,
-                                  ty:expr_ty
-                              }
+                      } else {
+                          // FIXME #2152 allow mutation of moved upvars
+                          @cmt_ {
+                              id:id,
+                              span:span,
+                              cat:cat_copied_upvar(CopiedUpvar {
+                                  upvar_id: upvar_id,
+                                  onceness: closure_ty.onceness}),
+                              mutbl:McImmutable,
+                              ty:expr_ty
                           }
                       }
                   }
diff --git a/src/librustc/middle/moves.rs b/src/librustc/middle/moves.rs
index 68307a49d3b..cd9d8738026 100644
--- a/src/librustc/middle/moves.rs
+++ b/src/librustc/middle/moves.rs
@@ -183,6 +183,7 @@ struct VisitContext {
     move_maps: MoveMaps
 }
 
+#[deriving(Eq)]
 enum UseMode {
     Move,        // This value or something owned by it is moved.
     Read         // Read no matter what the type.
@@ -335,7 +336,27 @@ impl VisitContext {
             }
 
             expr_call(callee, ref args, _) => {    // callee(args)
-                self.use_expr(callee, Read, visitor);
+                // Figure out whether the called function is consumed.
+                let mode = match ty::get(ty::expr_ty(self.tcx, callee)).sty {
+                    ty::ty_closure(ref cty) => {
+                        match cty.onceness {
+                        Once => Move,
+                        Many => Read,
+                        }
+                    },
+                    ty::ty_bare_fn(*) => Read,
+                    ref x =>
+                        self.tcx.sess.span_bug(callee.span,
+                            fmt!("non-function type in moves for expr_call: %?", x)),
+                };
+                // Note we're not using consume_expr, which uses type_moves_by_default
+                // to determine the mode, for this. The reason is that while stack
+                // closures should be noncopyable, they shouldn't move by default;
+                // calling a closure should only consume it if it's once.
+                if mode == Move {
+                    self.move_maps.moves_map.insert(callee.id);
+                }
+                self.use_expr(callee, mode, visitor);
                 self.use_fn_args(callee.id, *args, visitor);
             }
 
diff --git a/src/test/compile-fail/once-cant-call-twice-on-heap.rs b/src/test/compile-fail/once-cant-call-twice-on-heap.rs
new file mode 100644
index 00000000000..4436675d69a
--- /dev/null
+++ b/src/test/compile-fail/once-cant-call-twice-on-heap.rs
@@ -0,0 +1,29 @@
+// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// Testing guarantees provided by once functions.
+// This program would segfault if it were legal.
+
+extern mod extra;
+use extra::arc;
+use std::util;
+
+fn foo(blk: ~once fn()) {
+    blk();
+    blk(); //~ ERROR use of moved value
+}
+
+fn main() {
+    let x = arc::ARC(true);
+    do foo {
+        assert!(*x.get());
+        util::ignore(x);
+    }
+}
diff --git a/src/test/compile-fail/once-cant-call-twice-on-stack.rs b/src/test/compile-fail/once-cant-call-twice-on-stack.rs
new file mode 100644
index 00000000000..10877be549e
--- /dev/null
+++ b/src/test/compile-fail/once-cant-call-twice-on-stack.rs
@@ -0,0 +1,30 @@
+// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// Testing guarantees provided by once functions.
+// This program would segfault if it were legal.
+
+// compile-flags:-Z once-fns
+extern mod extra;
+use extra::arc;
+use std::util;
+
+fn foo(blk: &once fn()) {
+    blk();
+    blk(); //~ ERROR use of moved value
+}
+
+fn main() {
+    let x = arc::ARC(true);
+    do foo {
+        assert!(*x.get());
+        util::ignore(x);
+    }
+}
diff --git a/src/test/compile-fail/once-cant-copy-stack-once-fn-copy.rs b/src/test/compile-fail/once-cant-copy-stack-once-fn-copy.rs
new file mode 100644
index 00000000000..6f524c0068b
--- /dev/null
+++ b/src/test/compile-fail/once-cant-copy-stack-once-fn-copy.rs
@@ -0,0 +1,20 @@
+// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// Though it should be legal to copy a heap-allocated "once fn:Copy",
+// stack closures are not deep-copied, so (counterintuitively) it should be
+// illegal to copy them.
+
+fn foo<'r>(blk: &'r once fn:Copy()) -> (&'r once fn:Copy(), &'r once fn:Copy()) {
+    (copy blk, blk) //~ ERROR copying a value of non-copyable type
+}
+
+fn main() {
+}
diff --git a/src/test/compile-fail/once-cant-move-out-of-non-once-on-heap.rs b/src/test/compile-fail/once-cant-move-out-of-non-once-on-heap.rs
new file mode 100644
index 00000000000..61f158cec27
--- /dev/null
+++ b/src/test/compile-fail/once-cant-move-out-of-non-once-on-heap.rs
@@ -0,0 +1,29 @@
+// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// Testing guarantees provided by once functions.
+// This program would segfault if it were legal.
+
+extern mod extra;
+use extra::arc;
+use std::util;
+
+fn foo(blk: ~fn()) {
+    blk();
+    blk();
+}
+
+fn main() {
+    let x = arc::ARC(true);
+    do foo {
+        assert!(*x.get());
+        util::ignore(x); //~ ERROR cannot move out of captured outer variable
+    }
+}
diff --git a/src/test/compile-fail/once-cant-move-out-of-non-once-on-stack.rs b/src/test/compile-fail/once-cant-move-out-of-non-once-on-stack.rs
new file mode 100644
index 00000000000..42c8b9a9998
--- /dev/null
+++ b/src/test/compile-fail/once-cant-move-out-of-non-once-on-stack.rs
@@ -0,0 +1,29 @@
+// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// Testing guarantees provided by once functions.
+// This program would segfault if it were legal.
+
+extern mod extra;
+use extra::arc;
+use std::util;
+
+fn foo(blk: &fn()) {
+    blk();
+    blk();
+}
+
+fn main() {
+    let x = arc::ARC(true);
+    do foo {
+        assert!(*x.get());
+        util::ignore(x); //~ ERROR cannot move out of captured outer variable
+    }
+}
diff --git a/src/test/run-pass/once-move-out-on-heap.rs b/src/test/run-pass/once-move-out-on-heap.rs
new file mode 100644
index 00000000000..38b23fd128d
--- /dev/null
+++ b/src/test/run-pass/once-move-out-on-heap.rs
@@ -0,0 +1,29 @@
+// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// Testing guarantees provided by once functions.
+
+// xfail-fast
+
+extern mod extra;
+use extra::arc;
+use std::util;
+
+fn foo(blk: ~once fn()) {
+    blk();
+}
+
+fn main() {
+    let x = arc::ARC(true);
+    do foo {
+        assert!(*x.get());
+        util::ignore(x);
+    }
+}
diff --git a/src/test/run-pass/once-move-out-on-stack.rs b/src/test/run-pass/once-move-out-on-stack.rs
new file mode 100644
index 00000000000..e881f576673
--- /dev/null
+++ b/src/test/run-pass/once-move-out-on-stack.rs
@@ -0,0 +1,30 @@
+// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// Testing guarantees provided by once functions.
+
+// xfail-fast
+
+// compile-flags:-Z once-fns
+extern mod extra;
+use extra::arc;
+use std::util;
+
+fn foo(blk: &once fn()) {
+    blk();
+}
+
+fn main() {
+    let x = arc::ARC(true);
+    do foo {
+        assert!(*x.get());
+        util::ignore(x);
+    }
+}