about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/comp/middle/capture.rs132
-rw-r--r--src/comp/middle/trans.rs13
-rw-r--r--src/comp/middle/trans_closure.rs142
-rw-r--r--src/comp/middle/tstate/pre_post_conditions.rs7
-rw-r--r--src/comp/middle/typeck.rs7
-rw-r--r--src/comp/rustc.rc1
-rw-r--r--src/comp/syntax/ast.rs4
-rw-r--r--src/test/compile-fail/cap-clause-both-copy-and-move.rs5
-rw-r--r--src/test/compile-fail/cap-clause-double-copy.rs5
-rw-r--r--src/test/compile-fail/cap-clause-double-move.rs5
-rw-r--r--src/test/compile-fail/cap-clause-move-upvar.rs8
-rw-r--r--src/test/compile-fail/cap-clause-use-after-move.rs7
-rw-r--r--src/test/run-pass/cap-clause-move.rs17
-rw-r--r--src/test/run-pass/cap-clause-not-used.rs5
14 files changed, 305 insertions, 53 deletions
diff --git a/src/comp/middle/capture.rs b/src/comp/middle/capture.rs
new file mode 100644
index 00000000000..0dd1956ff41
--- /dev/null
+++ b/src/comp/middle/capture.rs
@@ -0,0 +1,132 @@
+import syntax::{ast, ast_util};
+import std::map;
+
+export capture_mode;
+export capture_var;
+export capture_map;
+export check_capture_clause;
+export compute_capture_vars;
+export cap_copy;
+export cap_move;
+export cap_drop;
+export cap_ref;
+
+tag capture_mode {
+    cap_copy; //< Copy the value into the closure.
+    cap_move; //< Move the value into the closure.
+    cap_drop; //< Drop value after creating closure.
+    cap_ref;  //< Reference directly from parent stack frame (block fn).
+}
+
+type capture_var = {
+    def: ast::def,     //< The variable being accessed free.
+    mode: capture_mode //< How is the variable being accessed.
+};
+
+type capture_map = map::hashmap<ast::def_id, capture_var>;
+
+// checks the capture clause for a fn_expr() and issues warnings or
+// errors for any irregularities which we identify.
+fn check_capture_clause(tcx: ty::ctxt,
+                        fn_expr_id: ast::node_id,
+                        fn_proto: ast::proto,
+                        cap_clause: ast::capture_clause) {
+    let freevars = freevars::get_freevars(tcx, fn_expr_id);
+    let seen_defs = map::new_int_hash();
+
+    let check_capture_item = lambda(&&cap_item: @ast::capture_item) {
+        let cap_def = tcx.def_map.get(cap_item.id);
+        if !vec::any(*freevars, {|fv| fv.def == cap_def}) {
+            tcx.sess.span_warn(
+                cap_item.span,
+                #fmt("Captured variable '%s' not used in closure",
+                     cap_item.name));
+        }
+
+        let cap_def_id = ast_util::def_id_of_def(cap_def).node;
+        if !seen_defs.insert(cap_def_id, ()) {
+            tcx.sess.span_err(
+                cap_item.span,
+                #fmt("Variable '%s' captured more than once",
+                     cap_item.name));
+        }
+    };
+
+    let check_not_upvar = lambda(&&cap_item: @ast::capture_item) {
+        alt tcx.def_map.get(cap_item.id) {
+          ast::def_upvar(_, _, _) {
+            tcx.sess.span_err(
+                cap_item.span,
+                #fmt("Upvars (like '%s') cannot be moved into a closure",
+                     cap_item.name));
+          }
+          _ {}
+        }
+    };
+
+    let check_block_captures = lambda(v: [@ast::capture_item]) {
+        if check vec::is_not_empty(v) {
+            let cap_item0 = vec::head(v);
+            tcx.sess.span_err(
+                cap_item0.span,
+                "Cannot capture values explicitly with a block closure");
+        }
+    };
+
+    alt fn_proto {
+      ast::proto_block. {
+        check_block_captures(cap_clause.copies);
+        check_block_captures(cap_clause.moves);
+      }
+      ast::proto_bare. | ast::proto_shared(_) | ast::proto_send. {
+        vec::iter(cap_clause.copies, check_capture_item);
+        vec::iter(cap_clause.moves, check_capture_item);
+        vec::iter(cap_clause.moves, check_not_upvar);
+      }
+    }
+}
+
+fn compute_capture_vars(tcx: ty::ctxt,
+                        fn_expr_id: ast::node_id,
+                        fn_proto: ast::proto,
+                        cap_clause: ast::capture_clause) -> [capture_var] {
+    let freevars = freevars::get_freevars(tcx, fn_expr_id);
+    let cap_map = map::new_int_hash();
+
+    vec::iter(cap_clause.copies) { |cap_item|
+        let cap_def = tcx.def_map.get(cap_item.id);
+        let cap_def_id = ast_util::def_id_of_def(cap_def).node;
+        if vec::any(*freevars, {|fv| fv.def == cap_def}) {
+            cap_map.insert(cap_def_id, { def:cap_def, mode:cap_copy });
+        }
+    }
+
+    vec::iter(cap_clause.moves) { |cap_item|
+        let cap_def = tcx.def_map.get(cap_item.id);
+        let cap_def_id = ast_util::def_id_of_def(cap_def).node;
+        if vec::any(*freevars, {|fv| fv.def == cap_def}) {
+            cap_map.insert(cap_def_id, { def:cap_def, mode:cap_move });
+        } else {
+            cap_map.insert(cap_def_id, { def:cap_def, mode:cap_drop });
+        }
+    }
+
+    let implicit_mode = alt fn_proto {
+      ast::proto_block. { cap_ref }
+      ast::proto_bare. | ast::proto_shared(_) | ast::proto_send. { cap_copy }
+    };
+
+    vec::iter(*freevars) { |fvar|
+        let fvar_def_id = ast_util::def_id_of_def(fvar.def).node;
+        alt cap_map.find(fvar_def_id) {
+          option::some(_) { /* was explicitly named, do nothing */ }
+          option::none. {
+            cap_map.insert(fvar_def_id, {def:fvar.def, mode:implicit_mode});
+          }
+        }
+    }
+
+    let result = [];
+    cap_map.values { |cap_var| result += [cap_var]; }
+    ret result;
+}
diff --git a/src/comp/middle/trans.rs b/src/comp/middle/trans.rs
index bb68175987e..aa72cb98ad3 100644
--- a/src/comp/middle/trans.rs
+++ b/src/comp/middle/trans.rs
@@ -2566,7 +2566,11 @@ type generic_info =
      static_tis: [option::t<@tydesc_info>],
      tydescs: [ValueRef]};
 
-tag lval_kind { temporary; owned; owned_imm; }
+tag lval_kind {
+    temporary; //< Temporary value passed by value if of immediate type
+    owned;     //< Non-temporary value passed by pointer
+    owned_imm; //< Non-temporary value passed by value
+}
 type local_var_result = {val: ValueRef, kind: lval_kind};
 type lval_result = {bcx: @block_ctxt, val: ValueRef, kind: lval_kind};
 tag callee_env { obj_env(ValueRef); null_env; is_closure; }
@@ -3550,12 +3554,13 @@ fn trans_expr(bcx: @block_ctxt, e: @ast::expr, dest: dest) -> @block_ctxt {
         assert op != ast::deref; // lvals are handled above
         ret trans_unary(bcx, op, x, e.id, dest);
       }
-      // NDM captures
       ast::expr_fn(f, cap_clause) {
-        ret trans_closure::trans_expr_fn(bcx, f, e.span, e.id, dest);
+        ret trans_closure::trans_expr_fn(
+            bcx, f, e.span, e.id, *cap_clause, dest);
       }
       ast::expr_bind(f, args) {
-        ret trans_closure::trans_bind(bcx, f, args, e.id, dest);
+        ret trans_closure::trans_bind(
+            bcx, f, args, e.id, dest);
       }
       ast::expr_copy(a) {
         if !expr_is_lval(bcx, a) {
diff --git a/src/comp/middle/trans_closure.rs b/src/comp/middle/trans_closure.rs
index cc72ef5b3e3..c61c7a4ff93 100644
--- a/src/comp/middle/trans_closure.rs
+++ b/src/comp/middle/trans_closure.rs
@@ -43,8 +43,10 @@ import trans::{
 //   };
 // };
 //
-// NB: this is defined in the code in T_closure_ptr and
-// closure_ty_to_tuple_ty (below).
+// NB: this struct is defined in the code in trans_common::T_closure()
+// and mk_closure_ty() below.  The former defines the LLVM version and
+// the latter the Rust equivalent.  It occurs to me that these could
+// perhaps be unified, but currently they are not.
 //
 // Note that the closure carries a type descriptor that describes
 // itself.  Trippy.  This is needed because the precise types of the
@@ -64,8 +66,17 @@ import trans::{
 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 tag environment_value {
+    // Evaluate expr and store result in env (used for bind).
     env_expr(@ast::expr);
-    env_direct(ValueRef, ty::t, bool);
+
+    // Copy the value from this llvm ValueRef into the environment.
+    env_copy(ValueRef, ty::t, lval_kind);
+
+    // Move the value from this llvm ValueRef into the environment.
+    env_move(ValueRef, ty::t, lval_kind);
+
+    // Access by reference (used for blocks).
+    env_ref(ValueRef, ty::t, lval_kind);
 }
 
 // Given a closure ty, emits a corresponding tuple ty
@@ -143,8 +154,10 @@ fn store_environment(
     let bound_tys = [];
     for bv in bound_values {
         bound_tys += [alt bv {
-          env_direct(_, t, _) { t }
-          env_expr(e) { ty::expr_ty(tcx, e) }
+            env_copy(_, t, _) { t }
+            env_move(_, t, _) { t }
+            env_ref(_, t, _) { t }
+            env_expr(e) { ty::expr_ty(tcx, e) }
         }];
     }
     let bound_data_ty = ty::mk_tup(tcx, bound_tys);
@@ -239,17 +252,29 @@ fn store_environment(
             add_clean_temp_mem(bcx, bound.val, bound_tys[i]);
             temp_cleanups += [bound.val];
           }
-          env_direct(val, ty, is_mem) {
-            alt ck {
-              ty::closure_shared. | ty::closure_send. {
-                let val1 = is_mem ? load_if_immediate(bcx, val, ty) : val;
-                bcx = trans::copy_val(bcx, INIT, bound.val, val1, ty);
-              }
-              ty::closure_block. {
-                let addr = is_mem ? val : do_spill_noroot(bcx, val);
-                Store(bcx, addr, bound.val);
-              }
-            }
+          env_copy(val, ty, owned.) {
+            let val1 = load_if_immediate(bcx, val, ty);
+            bcx = trans::copy_val(bcx, INIT, bound.val, val1, ty);
+          }
+          env_copy(val, ty, owned_imm.) {
+            bcx = trans::copy_val(bcx, INIT, bound.val, val, ty);
+          }
+          env_copy(_, _, temporary.) {
+            fail "Cannot capture temporary upvar";
+          }
+          env_move(val, ty, kind) {
+            let src = {bcx:bcx, val:val, kind:kind};
+            bcx = move_val(bcx, INIT, bound.val, src, ty);
+          }
+          env_ref(val, ty, owned.) {
+            Store(bcx, val, bound.val);
+          }
+          env_ref(val, ty, owned_imm.) {
+            let addr = do_spill_noroot(bcx, val);
+            Store(bcx, addr, bound.val);
+          }
+          env_ref(_, _, temporary.) {
+            fail "Cannot capture temporary upvar";
           }
         }
     }
@@ -260,25 +285,38 @@ fn store_environment(
 
 // Given a context and a list of upvars, build a closure. This just
 // collects the upvars and packages them up for store_environment.
-fn build_closure(cx: @block_ctxt,
-                 upvars: freevar_info,
+fn build_closure(bcx0: @block_ctxt,
+                 cap_vars: [capture::capture_var],
                  ck: ty::closure_kind)
     -> closure_result {
     // If we need to, package up the iterator body to call
     let env_vals = [];
-    let tcx = bcx_tcx(cx);
-    // Package up the upvars
-    vec::iter(*upvars) { |upvar|
-        let lv = trans_local_var(cx, upvar.def);
-        let nid = ast_util::def_id_of_def(upvar.def).node;
+    let bcx = bcx0;
+    let tcx = bcx_tcx(bcx);
+
+    // Package up the captured upvars
+    vec::iter(cap_vars) { |cap_var|
+        let lv = trans_local_var(bcx, cap_var.def);
+        let nid = ast_util::def_id_of_def(cap_var.def).node;
         let ty = ty::node_id_to_monotype(tcx, nid);
-        alt ck {
-          ty::closure_block. { ty = ty::mk_mut_ptr(tcx, ty); }
-          ty::closure_send. | ty::closure_shared. {}
+        alt cap_var.mode {
+          capture::cap_ref. {
+            assert ck == ty::closure_block;
+            ty = ty::mk_mut_ptr(tcx, ty);
+            env_vals += [env_ref(lv.val, ty, lv.kind)];
+          }
+          capture::cap_copy. {
+            env_vals += [env_copy(lv.val, ty, lv.kind)];
+          }
+          capture::cap_move. {
+            env_vals += [env_move(lv.val, ty, lv.kind)];
+          }
+          capture::cap_drop. {
+            bcx = drop_ty(bcx, lv.val, ty);
+          }
         }
-        env_vals += [env_direct(lv.val, ty, lv.kind == owned)];
     }
-    ret store_environment(cx, copy cx.fcx.lltydescs, env_vals, ck);
+    ret store_environment(bcx, copy bcx.fcx.lltydescs, env_vals, ck);
 }
 
 // Given an enclosing block context, a new function context, a closure type,
@@ -287,7 +325,7 @@ fn build_closure(cx: @block_ctxt,
 fn load_environment(enclosing_cx: @block_ctxt,
                     fcx: @fn_ctxt,
                     boxed_closure_ty: ty::t,
-                    upvars: freevar_info,
+                    cap_vars: [capture::capture_var],
                     ck: ty::closure_kind) {
     let bcx = new_raw_block_ctxt(fcx, fcx.llloadenv);
 
@@ -311,23 +349,34 @@ fn load_environment(enclosing_cx: @block_ctxt,
 
     // Populate the upvars from the environment.
     let path = [0, abi::box_rc_field_body, abi::closure_elt_bindings];
-    vec::iteri(*upvars) { |i, upvar|
-        check type_is_tup_like(bcx, boxed_closure_ty);
-        let upvarptr =
-            GEP_tup_like(bcx, boxed_closure_ty, llclosure, path + [i as int]);
-        bcx = upvarptr.bcx;
-        let llupvarptr = upvarptr.val;
-        alt ck {
-          ty::closure_block. { llupvarptr = Load(bcx, llupvarptr); }
-          ty::closure_send. | ty::closure_shared. { }
+    let i = 0u;
+    vec::iter(cap_vars) { |cap_var|
+        alt cap_var.mode {
+          capture::cap_drop. { /* ignore */ }
+          _ {
+            check type_is_tup_like(bcx, boxed_closure_ty);
+            let upvarptr = GEP_tup_like(
+                bcx, boxed_closure_ty, llclosure, path + [i as int]);
+            bcx = upvarptr.bcx;
+            let llupvarptr = upvarptr.val;
+            alt ck {
+              ty::closure_block. { llupvarptr = Load(bcx, llupvarptr); }
+              ty::closure_send. | ty::closure_shared. { }
+            }
+            let def_id = ast_util::def_id_of_def(cap_var.def);
+            fcx.llupvars.insert(def_id.node, llupvarptr);
+            i += 1u;
+          }
         }
-        let def_id = ast_util::def_id_of_def(upvar.def);
-        fcx.llupvars.insert(def_id.node, llupvarptr);
     }
 }
 
-fn trans_expr_fn(bcx: @block_ctxt, f: ast::_fn, sp: span,
-                 id: ast::node_id, dest: dest) -> @block_ctxt {
+fn trans_expr_fn(bcx: @block_ctxt,
+                 f: ast::_fn,
+                 sp: span,
+                 id: ast::node_id,
+                 cap_clause: ast::capture_clause,
+                 dest: dest) -> @block_ctxt {
     if dest == ignore { ret bcx; }
     let ccx = bcx_ccx(bcx), bcx = bcx;
     let fty = node_id_type(ccx, id);
@@ -339,10 +388,11 @@ fn trans_expr_fn(bcx: @block_ctxt, f: ast::_fn, sp: span,
     register_fn(ccx, sp, sub_cx.path, "anon fn", [], id);
 
     let trans_closure_env = lambda(ck: ty::closure_kind) -> ValueRef {
-        let upvars = get_freevars(ccx.tcx, id);
-        let {llbox, box_ty, bcx} = build_closure(bcx, upvars, ck);
+        let cap_vars = capture::compute_capture_vars(
+            ccx.tcx, id, f.proto, cap_clause);
+        let {llbox, box_ty, bcx} = build_closure(bcx, cap_vars, ck);
         trans_closure(sub_cx, sp, f, llfn, no_self, [], id, {|fcx|
-            load_environment(bcx, fcx, box_ty, upvars, ck);
+            load_environment(bcx, fcx, box_ty, cap_vars, ck);
         });
         llbox
     };
@@ -420,7 +470,7 @@ fn trans_bind_1(cx: @block_ctxt, outgoing_fty: ty::t,
         let sp = cx.sp;
         let llclosurety = T_ptr(type_of(ccx, sp, outgoing_fty));
         let src_loc = PointerCast(bcx, cl, llclosurety);
-        ([env_direct(src_loc, pair_ty, true)], none)
+        ([env_copy(src_loc, pair_ty, owned)], none)
       }
       none. { ([], some(f_res.val)) }
     };
diff --git a/src/comp/middle/tstate/pre_post_conditions.rs b/src/comp/middle/tstate/pre_post_conditions.rs
index 5cfdd0301a6..862c7867feb 100644
--- a/src/comp/middle/tstate/pre_post_conditions.rs
+++ b/src/comp/middle/tstate/pre_post_conditions.rs
@@ -347,6 +347,13 @@ fn find_pre_post_expr(fcx: fn_ctxt, e: @expr) {
             handle_var_def(fcx, rslt, def.def, "upvar");
         }
 
+        let use_cap_item = lambda(&&cap_item: @capture_item) {
+            let d = local_node_id_to_local_def_id(fcx, cap_item.id);
+            option::may(d, { |id| use_var(fcx, id) });
+        };
+        vec::iter(cap_clause.copies, use_cap_item);
+        vec::iter(cap_clause.moves, use_cap_item);
+
         vec::iter(cap_clause.moves) { |cap_item|
             log ("forget_in_postcond: ", cap_item);
             forget_in_postcond(fcx, e.id, cap_item.id);
diff --git a/src/comp/middle/typeck.rs b/src/comp/middle/typeck.rs
index 383a4387a5d..dc3cee75c23 100644
--- a/src/comp/middle/typeck.rs
+++ b/src/comp/middle/typeck.rs
@@ -1939,9 +1939,8 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, expr: @ast::expr, unify: unifier,
         if !arm_non_bot { result_ty = ty::mk_bot(tcx); }
         write::ty_only_fixup(fcx, id, result_ty);
       }
-      ast::expr_fn(f, captures) { // NDM captures
-        let cx = @{tcx: tcx};
-        let fty = ty_of_fn_decl(cx.tcx, m_check_tyvar(fcx), f.decl,
+      ast::expr_fn(f, captures) {
+        let fty = ty_of_fn_decl(tcx, m_check_tyvar(fcx), f.decl,
                                  f.proto, [], none).ty;
 
         write::ty_only_fixup(fcx, id, fty);
@@ -1956,6 +1955,8 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, expr: @ast::expr, unify: unifier,
         if f.proto == ast::proto_block {
             write::ty_only_fixup(fcx, id, expected);
         }
+
+        capture::check_capture_clause(tcx, expr.id, f.proto, *captures);
       }
       ast::expr_block(b) {
         // If this is an unchecked block, turn off purity-checking
diff --git a/src/comp/rustc.rc b/src/comp/rustc.rc
index 9f88b369749..0ddfb970ed1 100644
--- a/src/comp/rustc.rc
+++ b/src/comp/rustc.rc
@@ -38,6 +38,7 @@ mod middle {
     mod shape;
     mod gc;
     mod debuginfo;
+    mod capture;
 
     mod tstate {
         mod ck;
diff --git a/src/comp/syntax/ast.rs b/src/comp/syntax/ast.rs
index 608796696d6..4f6b5cf3744 100644
--- a/src/comp/syntax/ast.rs
+++ b/src/comp/syntax/ast.rs
@@ -261,6 +261,10 @@ tag expr_ {
     expr_mac(mac);
 }
 
+// AST nodes that represent a capture clause, which is used to declare
+// variables that are copied or moved explicitly into the closure.  In some
+// cases, local variables can also be copied implicitly into the closure if
+// they are used in the closure body.
 type capture_item = {
     id: int,
     name: ident, // Currently, can only capture a local var.
diff --git a/src/test/compile-fail/cap-clause-both-copy-and-move.rs b/src/test/compile-fail/cap-clause-both-copy-and-move.rs
new file mode 100644
index 00000000000..0758ad67bd5
--- /dev/null
+++ b/src/test/compile-fail/cap-clause-both-copy-and-move.rs
@@ -0,0 +1,5 @@
+// error-pattern:error: Variable 'x' captured more than once
+fn main() {
+    let x = 5;
+    let y = sendfn[move x; copy x]() -> int { x };
+}
\ No newline at end of file
diff --git a/src/test/compile-fail/cap-clause-double-copy.rs b/src/test/compile-fail/cap-clause-double-copy.rs
new file mode 100644
index 00000000000..2e1008bad1c
--- /dev/null
+++ b/src/test/compile-fail/cap-clause-double-copy.rs
@@ -0,0 +1,5 @@
+// error-pattern:error: Variable 'x' captured more than once
+fn main() {
+    let x = 5;
+    let y = sendfn[copy x, x]() -> int { x };
+}
diff --git a/src/test/compile-fail/cap-clause-double-move.rs b/src/test/compile-fail/cap-clause-double-move.rs
new file mode 100644
index 00000000000..89e1a4a5a51
--- /dev/null
+++ b/src/test/compile-fail/cap-clause-double-move.rs
@@ -0,0 +1,5 @@
+// error-pattern: error: Variable 'x' captured more than once
+fn main() {
+    let x = 5;
+    let y = sendfn[move x, x]() -> int { x };
+}
diff --git a/src/test/compile-fail/cap-clause-move-upvar.rs b/src/test/compile-fail/cap-clause-move-upvar.rs
new file mode 100644
index 00000000000..33b0a67a7e4
--- /dev/null
+++ b/src/test/compile-fail/cap-clause-move-upvar.rs
@@ -0,0 +1,8 @@
+// error-pattern: error: Upvars (like 'x') cannot be moved into a closure
+fn main() {
+    let x = 5;
+    let _y = sendfn[move x]() -> int {
+        let _z = sendfn[move x]() -> int { x };
+        22
+    };
+}
diff --git a/src/test/compile-fail/cap-clause-use-after-move.rs b/src/test/compile-fail/cap-clause-use-after-move.rs
new file mode 100644
index 00000000000..029b05ad2e1
--- /dev/null
+++ b/src/test/compile-fail/cap-clause-use-after-move.rs
@@ -0,0 +1,7 @@
+// error-pattern:Unsatisfied precondition constraint
+
+fn main() {
+    let x = 5;
+    let _y = sendfn[move x]() { };
+    let _z = x; //< error: Unsatisfied precondition constraint
+}
diff --git a/src/test/run-pass/cap-clause-move.rs b/src/test/run-pass/cap-clause-move.rs
new file mode 100644
index 00000000000..96b4b4d3aaa
--- /dev/null
+++ b/src/test/run-pass/cap-clause-move.rs
@@ -0,0 +1,17 @@
+// error-pattern: warning: Captured variable 'y' not used in closure
+fn main() {
+    let x = ~1;
+    let y = ptr::addr_of(*x) as uint;
+
+    let lam_copy = lambda[copy x]() -> uint { ptr::addr_of(*x) as uint };
+    let lam_move = lambda[move x]() -> uint { ptr::addr_of(*x) as uint };
+    assert lam_copy() != y;
+    assert lam_move() == y;
+
+    let x = ~2;
+    let y = ptr::addr_of(*x) as uint;
+    let snd_copy = sendfn[copy x]() -> uint { ptr::addr_of(*x) as uint };
+    let snd_move = sendfn[move x]() -> uint { ptr::addr_of(*x) as uint };
+    assert snd_copy() != y;
+    assert snd_move() == y;
+}
diff --git a/src/test/run-pass/cap-clause-not-used.rs b/src/test/run-pass/cap-clause-not-used.rs
new file mode 100644
index 00000000000..0bb43b9c6f5
--- /dev/null
+++ b/src/test/run-pass/cap-clause-not-used.rs
@@ -0,0 +1,5 @@
+// error-pattern: warning: Captured variable 'y' not used in closure
+fn main() {
+    let x = 5;
+    let _y = sendfn[copy x]() { };
+}