make it illegal to implicitly capture mutable variables
this is the final part of #1273
This commit is contained in:
parent
d709ed2542
commit
8a9df5aa38
32 changed files with 258 additions and 122 deletions
|
@ -362,7 +362,7 @@ type capture_item = {
|
||||||
};
|
};
|
||||||
|
|
||||||
#[auto_serialize]
|
#[auto_serialize]
|
||||||
type capture_clause = [capture_item];
|
type capture_clause = @[capture_item];
|
||||||
|
|
||||||
/*
|
/*
|
||||||
// Says whether this is a block the user marked as
|
// Says whether this is a block the user marked as
|
||||||
|
|
|
@ -1187,7 +1187,8 @@ fn parse_fn_expr(p: parser, proto: ast::proto) -> @ast::expr {
|
||||||
|
|
||||||
let body = parse_block(p);
|
let body = parse_block(p);
|
||||||
ret mk_expr(p, lo, body.span.hi,
|
ret mk_expr(p, lo, body.span.hi,
|
||||||
ast::expr_fn(proto, decl, body, capture_clause + cc_old));
|
ast::expr_fn(proto, decl, body,
|
||||||
|
@(*capture_clause + cc_old)));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_fn_block_expr(p: parser) -> @ast::expr {
|
fn parse_fn_block_expr(p: parser) -> @ast::expr {
|
||||||
|
@ -1679,7 +1680,7 @@ fn parse_ty_params(p: parser) -> [ast::ty_param] {
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME Remove after snapshot
|
// FIXME Remove after snapshot
|
||||||
fn parse_old_skool_capture_clause(p: parser) -> ast::capture_clause {
|
fn parse_old_skool_capture_clause(p: parser) -> [ast::capture_item] {
|
||||||
fn expect_opt_trailing_semi(p: parser) {
|
fn expect_opt_trailing_semi(p: parser) {
|
||||||
if !eat(p, token::SEMI) {
|
if !eat(p, token::SEMI) {
|
||||||
if p.token != token::RBRACKET {
|
if p.token != token::RBRACKET {
|
||||||
|
@ -1739,7 +1740,7 @@ fn parse_fn_decl(p: parser, purity: ast::purity,
|
||||||
parse_arg_fn, p).node;
|
parse_arg_fn, p).node;
|
||||||
|
|
||||||
let inputs = either::lefts(args_or_capture_items);
|
let inputs = either::lefts(args_or_capture_items);
|
||||||
let capture_clause = either::rights(args_or_capture_items);
|
let capture_clause = @either::rights(args_or_capture_items);
|
||||||
|
|
||||||
// Use the args list to translate each bound variable
|
// Use the args list to translate each bound variable
|
||||||
// mentioned in a constraint to an arg index.
|
// mentioned in a constraint to an arg index.
|
||||||
|
@ -1778,7 +1779,7 @@ fn parse_fn_block_decl(p: parser) -> (ast::fn_decl, ast::capture_clause) {
|
||||||
purity: ast::impure_fn,
|
purity: ast::impure_fn,
|
||||||
cf: ast::return_val,
|
cf: ast::return_val,
|
||||||
constraints: []},
|
constraints: []},
|
||||||
either::rights(inputs_captures));
|
@either::rights(inputs_captures));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_fn_header(p: parser) -> {ident: ast::ident, tps: [ast::ty_param]} {
|
fn parse_fn_header(p: parser) -> {ident: ast::ident, tps: [ast::ty_param]} {
|
||||||
|
|
|
@ -1018,7 +1018,7 @@ fn print_expr(s: ps, &&expr: @ast::expr) {
|
||||||
// head-box, will be closed by print-block at start
|
// head-box, will be closed by print-block at start
|
||||||
ibox(s, 0u);
|
ibox(s, 0u);
|
||||||
word(s.s, proto_to_str(proto));
|
word(s.s, proto_to_str(proto));
|
||||||
print_fn_args_and_ret(s, decl, cap_clause);
|
print_fn_args_and_ret(s, decl, *cap_clause);
|
||||||
space(s.s);
|
space(s.s);
|
||||||
print_block(s, body);
|
print_block(s, body);
|
||||||
}
|
}
|
||||||
|
@ -1028,7 +1028,7 @@ fn print_expr(s: ps, &&expr: @ast::expr) {
|
||||||
// head-box, will be closed by print-block at start
|
// head-box, will be closed by print-block at start
|
||||||
ibox(s, 0u);
|
ibox(s, 0u);
|
||||||
word(s.s, "{");
|
word(s.s, "{");
|
||||||
print_fn_block_args(s, decl, cap_clause);
|
print_fn_block_args(s, decl, *cap_clause);
|
||||||
print_possibly_embedded_block(s, body, block_block_fn, indent_unit);
|
print_possibly_embedded_block(s, body, block_block_fn, indent_unit);
|
||||||
}
|
}
|
||||||
ast::expr_loop_body(body) {
|
ast::expr_loop_body(body) {
|
||||||
|
|
|
@ -16,8 +16,8 @@ enum fn_kind {
|
||||||
fk_item_fn(ident, [ty_param]), //< an item declared with fn()
|
fk_item_fn(ident, [ty_param]), //< an item declared with fn()
|
||||||
fk_method(ident, [ty_param], @method),
|
fk_method(ident, [ty_param], @method),
|
||||||
fk_res(ident, [ty_param], region_param),
|
fk_res(ident, [ty_param], region_param),
|
||||||
fk_anon(proto), //< an anonymous function like fn@(...)
|
fk_anon(proto, capture_clause), //< an anonymous function like fn@(...)
|
||||||
fk_fn_block, //< a block {||...}
|
fk_fn_block(capture_clause), //< a block {||...}
|
||||||
fk_ctor(ident, [ty_param], node_id /* self id */,
|
fk_ctor(ident, [ty_param], node_id /* self id */,
|
||||||
def_id /* parent class id */) // class constructor
|
def_id /* parent class id */) // class constructor
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ fn name_of_fn(fk: fn_kind) -> ident {
|
||||||
alt fk {
|
alt fk {
|
||||||
fk_item_fn(name, _) | fk_method(name, _, _) | fk_res(name, _, _)
|
fk_item_fn(name, _) | fk_method(name, _, _) | fk_res(name, _, _)
|
||||||
| fk_ctor(name, _, _, _) { name }
|
| fk_ctor(name, _, _, _) { name }
|
||||||
fk_anon(_) | fk_fn_block { "anon" }
|
fk_anon(*) | fk_fn_block(*) { "anon" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ fn tps_of_fn(fk: fn_kind) -> [ty_param] {
|
||||||
alt fk {
|
alt fk {
|
||||||
fk_item_fn(_, tps) | fk_method(_, tps, _) | fk_res(_, tps, _)
|
fk_item_fn(_, tps) | fk_method(_, tps, _) | fk_res(_, tps, _)
|
||||||
| fk_ctor(_, tps, _, _) { tps }
|
| fk_ctor(_, tps, _, _) { tps }
|
||||||
fk_anon(_) | fk_fn_block { [] }
|
fk_anon(*) | fk_fn_block(*) { [] }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -381,11 +381,13 @@ fn visit_expr<E>(ex: @expr, e: E, v: vt<E>) {
|
||||||
v.visit_expr(x, e, v);
|
v.visit_expr(x, e, v);
|
||||||
for arms.each {|a| v.visit_arm(a, e, v); }
|
for arms.each {|a| v.visit_arm(a, e, v); }
|
||||||
}
|
}
|
||||||
expr_fn(proto, decl, body, _) {
|
expr_fn(proto, decl, body, cap_clause) {
|
||||||
v.visit_fn(fk_anon(proto), decl, body, ex.span, ex.id, e, v);
|
v.visit_fn(fk_anon(proto, cap_clause), decl, body,
|
||||||
|
ex.span, ex.id, e, v);
|
||||||
}
|
}
|
||||||
expr_fn_block(decl, body, _) {
|
expr_fn_block(decl, body, cap_clause) {
|
||||||
v.visit_fn(fk_fn_block, decl, body, ex.span, ex.id, e, v);
|
v.visit_fn(fk_fn_block(cap_clause), decl, body,
|
||||||
|
ex.span, ex.id, e, v);
|
||||||
}
|
}
|
||||||
expr_block(b) { v.visit_block(b, e, v); }
|
expr_block(b) { v.visit_block(b, e, v); }
|
||||||
expr_assign(a, b) { v.visit_expr(b, e, v); v.visit_expr(a, e, v); }
|
expr_assign(a, b) { v.visit_expr(b, e, v); v.visit_expr(a, e, v); }
|
||||||
|
|
|
@ -368,7 +368,7 @@ fn mk_test_wrapper(cx: test_ctxt,
|
||||||
let wrapper_expr: ast::expr = {
|
let wrapper_expr: ast::expr = {
|
||||||
id: cx.sess.next_node_id(),
|
id: cx.sess.next_node_id(),
|
||||||
node: ast::expr_fn(ast::proto_bare, wrapper_decl,
|
node: ast::expr_fn(ast::proto_bare, wrapper_decl,
|
||||||
wrapper_body, []),
|
wrapper_body, @[]),
|
||||||
span: span
|
span: span
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -223,8 +223,7 @@ fn visit_ids(item: ast::inlined_item, vfn: fn@(ast::node_id)) {
|
||||||
vfn(m.self_id);
|
vfn(m.self_id);
|
||||||
vec::iter(tps) {|tp| vfn(tp.id)}
|
vec::iter(tps) {|tp| vfn(tp.id)}
|
||||||
}
|
}
|
||||||
visit::fk_anon(_) |
|
visit::fk_anon(*) | visit::fk_fn_block(*) {
|
||||||
visit::fk_fn_block {
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,12 +31,11 @@ type capture_map = map::hashmap<ast::def_id, capture_var>;
|
||||||
// errors for any irregularities which we identify.
|
// errors for any irregularities which we identify.
|
||||||
fn check_capture_clause(tcx: ty::ctxt,
|
fn check_capture_clause(tcx: ty::ctxt,
|
||||||
fn_expr_id: ast::node_id,
|
fn_expr_id: ast::node_id,
|
||||||
fn_proto: ast::proto,
|
|
||||||
cap_clause: ast::capture_clause) {
|
cap_clause: ast::capture_clause) {
|
||||||
let freevars = freevars::get_freevars(tcx, fn_expr_id);
|
let freevars = freevars::get_freevars(tcx, fn_expr_id);
|
||||||
let seen_defs = map::int_hash();
|
let seen_defs = map::int_hash();
|
||||||
|
|
||||||
let check_capture_item = fn@(cap_item: ast::capture_item) {
|
for (*cap_clause).each { |cap_item|
|
||||||
let cap_def = tcx.def_map.get(cap_item.id);
|
let cap_def = tcx.def_map.get(cap_item.id);
|
||||||
if !vec::any(*freevars, {|fv| fv.def == cap_def}) {
|
if !vec::any(*freevars, {|fv| fv.def == cap_def}) {
|
||||||
tcx.sess.span_warn(
|
tcx.sess.span_warn(
|
||||||
|
@ -52,22 +51,6 @@ fn check_capture_clause(tcx: ty::ctxt,
|
||||||
#fmt("variable '%s' captured more than once",
|
#fmt("variable '%s' captured more than once",
|
||||||
cap_item.name));
|
cap_item.name));
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
alt fn_proto {
|
|
||||||
ast::proto_any | ast::proto_block {
|
|
||||||
if vec::is_not_empty(cap_clause) {
|
|
||||||
let cap_item0 = vec::head(cap_clause);
|
|
||||||
tcx.sess.span_err(
|
|
||||||
cap_item0.span,
|
|
||||||
"cannot capture values explicitly with a block closure");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ast::proto_bare | ast::proto_box | ast::proto_uniq {
|
|
||||||
for cap_clause.each { |cap_item|
|
|
||||||
check_capture_item(cap_item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,7 +63,7 @@ fn compute_capture_vars(tcx: ty::ctxt,
|
||||||
|
|
||||||
// first add entries for anything explicitly named in the cap clause
|
// first add entries for anything explicitly named in the cap clause
|
||||||
|
|
||||||
for cap_clause.each { |cap_item|
|
for (*cap_clause).each { |cap_item|
|
||||||
let cap_def = tcx.def_map.get(cap_item.id);
|
let cap_def = tcx.def_map.get(cap_item.id);
|
||||||
let cap_def_id = ast_util::def_id_of_def(cap_def).node;
|
let cap_def_id = ast_util::def_id_of_def(cap_def).node;
|
||||||
if cap_item.is_move {
|
if cap_item.is_move {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import ty::{kind, kind_copyable, kind_sendable, kind_noncopyable};
|
||||||
import driver::session::session;
|
import driver::session::session;
|
||||||
import std::map::hashmap;
|
import std::map::hashmap;
|
||||||
import syntax::print::pprust::expr_to_str;
|
import syntax::print::pprust::expr_to_str;
|
||||||
|
import freevars::freevar_entry;
|
||||||
|
|
||||||
// Kind analysis pass. There are three kinds:
|
// Kind analysis pass. There are three kinds:
|
||||||
//
|
//
|
||||||
|
@ -56,17 +57,61 @@ fn check_crate(tcx: ty::ctxt, method_map: typeck::method_map,
|
||||||
ret ctx.rval_map;
|
ret ctx.rval_map;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type check_fn = fn@(ctx, option<@freevar_entry>, bool, ty::t, sp: span);
|
||||||
|
|
||||||
// Yields the appropriate function to check the kind of closed over
|
// Yields the appropriate function to check the kind of closed over
|
||||||
// variables. `id` is the node_id for some expression that creates the
|
// variables. `id` is the node_id for some expression that creates the
|
||||||
// closure.
|
// closure.
|
||||||
fn with_appropriate_checker(cx: ctx, id: node_id,
|
fn with_appropriate_checker(cx: ctx, id: node_id, b: fn(check_fn)) {
|
||||||
b: fn(fn@(ctx, ty::t, sp: span))) {
|
fn check_for_uniq(cx: ctx, fv: option<@freevar_entry>, is_move: bool,
|
||||||
|
var_t: ty::t, sp: span) {
|
||||||
|
// all captured data must be sendable, regardless of whether it is
|
||||||
|
// moved in or copied in
|
||||||
|
check_send(cx, var_t, sp);
|
||||||
|
|
||||||
|
// check that only immutable variables are implicitly copied in
|
||||||
|
if !is_move {
|
||||||
|
for fv.each { |fv|
|
||||||
|
check_imm_free_var(cx, fv.def, fv.span);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_for_box(cx: ctx, fv: option<@freevar_entry>, is_move: bool,
|
||||||
|
var_t: ty::t, sp: span) {
|
||||||
|
// copied in data must be copyable, but moved in data can be anything
|
||||||
|
if !is_move { check_copy(cx, var_t, sp); }
|
||||||
|
|
||||||
|
// check that only immutable variables are implicitly copied in
|
||||||
|
if !is_move {
|
||||||
|
for fv.each { |fv|
|
||||||
|
check_imm_free_var(cx, fv.def, fv.span);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_for_block(cx: ctx, fv: option<@freevar_entry>, _is_move: bool,
|
||||||
|
_var_t: ty::t, sp: span) {
|
||||||
|
// only restriction: no capture clauses (we would have to take
|
||||||
|
// ownership of the moved/copied in data).
|
||||||
|
if fv.is_none() {
|
||||||
|
cx.tcx.sess.span_err(
|
||||||
|
sp,
|
||||||
|
"cannot capture values explicitly with a block closure");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_for_bare(cx: ctx, _fv: option<@freevar_entry>, _is_move: bool,
|
||||||
|
_var_t: ty::t, sp: span) {
|
||||||
|
cx.tcx.sess.span_err(sp, "attempted dynamic environment capture");
|
||||||
|
}
|
||||||
|
|
||||||
let fty = ty::node_id_to_type(cx.tcx, id);
|
let fty = ty::node_id_to_type(cx.tcx, id);
|
||||||
alt ty::ty_fn_proto(fty) {
|
alt ty::ty_fn_proto(fty) {
|
||||||
proto_uniq { b(check_send); }
|
proto_uniq { b(check_for_uniq) }
|
||||||
proto_box { b(check_copy); }
|
proto_box { b(check_for_box) }
|
||||||
proto_bare { b(check_none); }
|
proto_bare { b(check_for_bare) }
|
||||||
proto_any | proto_block { /* no check needed */ }
|
proto_any | proto_block { b(check_for_block) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,59 +120,54 @@ fn with_appropriate_checker(cx: ctx, id: node_id,
|
||||||
fn check_fn(fk: visit::fn_kind, decl: fn_decl, body: blk, sp: span,
|
fn check_fn(fk: visit::fn_kind, decl: fn_decl, body: blk, sp: span,
|
||||||
fn_id: node_id, cx: ctx, v: visit::vt<ctx>) {
|
fn_id: node_id, cx: ctx, v: visit::vt<ctx>) {
|
||||||
|
|
||||||
// n.b.: This could be the body of either a fn decl or a fn expr. In the
|
// Find the check function that enforces the appropriate bounds for this
|
||||||
// former case, the prototype will be proto_bare and no check occurs. In
|
// kind of function:
|
||||||
// the latter case, we do not check the variables that in the capture
|
with_appropriate_checker(cx, fn_id) { |chk|
|
||||||
// clause (as we don't have access to that here) but just those that
|
|
||||||
// appear free. The capture clauses are checked below, in check_expr().
|
|
||||||
//
|
|
||||||
// We could do this check also in check_expr(), but it seems more
|
|
||||||
// "future-proof" to do it this way, as check_fn_body() is supposed to be
|
|
||||||
// the common flow point for all functions that appear in the AST.
|
|
||||||
|
|
||||||
with_appropriate_checker(cx, fn_id) { |checker|
|
// Begin by checking the variables in the capture clause, if any.
|
||||||
|
// Here we slightly abuse the map function to both check and report
|
||||||
|
// errors and produce a list of the def id's for all capture
|
||||||
|
// variables. This list is used below to avoid checking and reporting
|
||||||
|
// on a given variable twice.
|
||||||
|
let cap_clause = alt fk {
|
||||||
|
visit::fk_anon(_, cc) | visit::fk_fn_block(cc) { cc }
|
||||||
|
visit::fk_item_fn(*) | visit::fk_method(*) |
|
||||||
|
visit::fk_res(*) | visit::fk_ctor(*) { @[] }
|
||||||
|
};
|
||||||
|
let captured_vars = (*cap_clause).map { |cap_item|
|
||||||
|
let cap_def = cx.tcx.def_map.get(cap_item.id);
|
||||||
|
let cap_def_id = ast_util::def_id_of_def(cap_def).node;
|
||||||
|
let ty = ty::node_id_to_type(cx.tcx, cap_def_id);
|
||||||
|
chk(cx, none, cap_item.is_move, ty, cap_item.span);
|
||||||
|
cap_def_id
|
||||||
|
};
|
||||||
|
|
||||||
|
// Iterate over any free variables that may not have appeared in the
|
||||||
|
// capture list. Ensure that they too are of the appropriate kind.
|
||||||
for vec::each(*freevars::get_freevars(cx.tcx, fn_id)) {|fv|
|
for vec::each(*freevars::get_freevars(cx.tcx, fn_id)) {|fv|
|
||||||
let id = ast_util::def_id_of_def(fv.def).node;
|
let id = ast_util::def_id_of_def(fv.def).node;
|
||||||
if checker == check_copy {
|
|
||||||
|
// skip over free variables that appear in the cap clause
|
||||||
|
if captured_vars.contains(id) { cont; }
|
||||||
|
|
||||||
|
// if this is the last use of the variable, then it will be
|
||||||
|
// a move and not a copy
|
||||||
|
let is_move = {
|
||||||
let last_uses = alt check cx.last_uses.find(fn_id) {
|
let last_uses = alt check cx.last_uses.find(fn_id) {
|
||||||
some(last_use::closes_over(vars)) { vars }
|
some(last_use::closes_over(vars)) { vars }
|
||||||
none { [] }
|
none { [] }
|
||||||
};
|
};
|
||||||
if option::is_some(
|
last_uses.contains(id)
|
||||||
vec::position_elem(last_uses, id)) { cont; }
|
};
|
||||||
}
|
|
||||||
let ty = ty::node_id_to_type(cx.tcx, id);
|
let ty = ty::node_id_to_type(cx.tcx, id);
|
||||||
checker(cx, ty, fv.span);
|
chk(cx, some(fv), is_move, ty, fv.span);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
visit::visit_fn(fk, decl, body, sp, fn_id, cx, v);
|
visit::visit_fn(fk, decl, body, sp, fn_id, cx, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_fn_cap_clause(cx: ctx,
|
|
||||||
id: node_id,
|
|
||||||
cap_clause: capture_clause) {
|
|
||||||
// Check that the variables named in the clause which are not free vars
|
|
||||||
// (if any) are also legal. freevars are checked above in check_fn().
|
|
||||||
// This is kind of a degenerate case, as captured variables will generally
|
|
||||||
// appear free in the body.
|
|
||||||
let freevars = freevars::get_freevars(cx.tcx, id);
|
|
||||||
let freevar_ids = vec::map(*freevars, { |freevar|
|
|
||||||
ast_util::def_id_of_def(freevar.def).node
|
|
||||||
});
|
|
||||||
//log("freevar_ids", freevar_ids);
|
|
||||||
with_appropriate_checker(cx, id) { |checker|
|
|
||||||
for cap_clause.each { |cap_item|
|
|
||||||
let cap_def = cx.tcx.def_map.get(cap_item.id);
|
|
||||||
let cap_def_id = ast_util::def_id_of_def(cap_def).node;
|
|
||||||
if !vec::contains(freevar_ids, cap_def_id) {
|
|
||||||
let ty = ty::node_id_to_type(cx.tcx, cap_def_id);
|
|
||||||
checker(cx, ty, cap_item.span);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_block(b: blk, cx: ctx, v: visit::vt<ctx>) {
|
fn check_block(b: blk, cx: ctx, v: visit::vt<ctx>) {
|
||||||
alt b.node.expr {
|
alt b.node.expr {
|
||||||
some(ex) { maybe_copy(cx, ex); }
|
some(ex) { maybe_copy(cx, ex); }
|
||||||
|
@ -225,9 +265,6 @@ fn check_expr(e: @expr, cx: ctx, v: visit::vt<ctx>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
expr_fn(_, _, _, cap_clause) | expr_fn_block(_, _, cap_clause) {
|
|
||||||
check_fn_cap_clause(cx, e.id, cap_clause);
|
|
||||||
}
|
|
||||||
_ { }
|
_ { }
|
||||||
}
|
}
|
||||||
visit::visit_expr(e, cx, v);
|
visit::visit_expr(e, cx, v);
|
||||||
|
@ -307,6 +344,35 @@ fn check_copy_ex(cx: ctx, ex: @expr, _warn: bool) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn check_imm_free_var(cx: ctx, def: def, sp: span) {
|
||||||
|
let msg = "mutable variables cannot be implicitly captured; \
|
||||||
|
use a capture clause";
|
||||||
|
alt def {
|
||||||
|
def_local(_, is_mutbl) {
|
||||||
|
if is_mutbl {
|
||||||
|
cx.tcx.sess.span_err(sp, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
def_arg(_, mode) {
|
||||||
|
alt ty::resolved_mode(cx.tcx, mode) {
|
||||||
|
by_ref | by_val { /* ok */ }
|
||||||
|
by_mutbl_ref | by_move | by_copy {
|
||||||
|
cx.tcx.sess.span_err(sp, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
def_upvar(_, def1, _) {
|
||||||
|
check_imm_free_var(cx, *def1, sp);
|
||||||
|
}
|
||||||
|
def_binding(*) | def_self(*) { /*ok*/ }
|
||||||
|
_ {
|
||||||
|
cx.tcx.sess.span_bug(
|
||||||
|
sp,
|
||||||
|
#fmt["unknown def for free variable: %?", def]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn check_copy(cx: ctx, ty: ty::t, sp: span) {
|
fn check_copy(cx: ctx, ty: ty::t, sp: span) {
|
||||||
if !ty::kind_can_be_copied(ty::type_kind(cx.tcx, ty)) {
|
if !ty::kind_can_be_copied(ty::type_kind(cx.tcx, ty)) {
|
||||||
cx.tcx.sess.span_err(sp, "copying a noncopyable value");
|
cx.tcx.sess.span_err(sp, "copying a noncopyable value");
|
||||||
|
@ -319,10 +385,6 @@ fn check_send(cx: ctx, ty: ty::t, sp: span) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_none(cx: ctx, _ty: ty::t, sp: span) {
|
|
||||||
cx.tcx.sess.span_err(sp, "attempted dynamic environment capture");
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Local Variables:
|
// Local Variables:
|
||||||
// mode: rust
|
// mode: rust
|
||||||
|
|
|
@ -159,7 +159,7 @@ fn visit_expr(ex: @expr, cx: ctx, v: visit::vt<ctx>) {
|
||||||
// n.b.: safe to ignore copies, as if they are unused
|
// n.b.: safe to ignore copies, as if they are unused
|
||||||
// then they are ignored, otherwise they will show up
|
// then they are ignored, otherwise they will show up
|
||||||
// as freevars in the body.
|
// as freevars in the body.
|
||||||
for cap_clause.each { |ci|
|
for (*cap_clause).each { |ci|
|
||||||
if ci.is_move {
|
if ci.is_move {
|
||||||
clear_def_if_local(cx, cx.def_map.get(ci.id), false);
|
clear_def_if_local(cx, cx.def_map.get(ci.id), false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -200,7 +200,7 @@ fn visit_expr(ex: @expr, &&cx: @ctx, v: visit::vt<@ctx>) {
|
||||||
check_lval(cx, dest, msg_assign);
|
check_lval(cx, dest, msg_assign);
|
||||||
}
|
}
|
||||||
expr_fn(_, _, _, cap_clause) | expr_fn_block(_, _, cap_clause) {
|
expr_fn(_, _, _, cap_clause) | expr_fn_block(_, _, cap_clause) {
|
||||||
for cap_clause.each { |cap_item|
|
for (*cap_clause).each { |cap_item|
|
||||||
if cap_item.is_move {
|
if cap_item.is_move {
|
||||||
let def = cx.tcx.def_map.get(cap_item.id);
|
let def = cx.tcx.def_map.get(cap_item.id);
|
||||||
alt is_illegal_to_modify_def(cx, def, msg_move_out) {
|
alt is_illegal_to_modify_def(cx, def, msg_move_out) {
|
||||||
|
|
|
@ -455,7 +455,7 @@ fn resolve_names(e: @env, c: @ast::crate) {
|
||||||
}
|
}
|
||||||
ast::expr_fn(_, _, _, cap_clause) |
|
ast::expr_fn(_, _, _, cap_clause) |
|
||||||
ast::expr_fn_block(_, _, cap_clause) {
|
ast::expr_fn_block(_, _, cap_clause) {
|
||||||
for cap_clause.each { |ci|
|
for (*cap_clause).each { |ci|
|
||||||
resolve_capture_item(e, sc, ci);
|
resolve_capture_item(e, sc, ci);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -615,10 +615,12 @@ fn visit_fn_with_scope(e: @env, fk: visit::fn_kind, decl: ast::fn_decl,
|
||||||
for decl.constraints.each {|c| resolve_constr(e, c, sc, v); }
|
for decl.constraints.each {|c| resolve_constr(e, c, sc, v); }
|
||||||
let scope = alt fk {
|
let scope = alt fk {
|
||||||
visit::fk_item_fn(_, tps) | visit::fk_res(_, tps, _) |
|
visit::fk_item_fn(_, tps) | visit::fk_res(_, tps, _) |
|
||||||
visit::fk_method(_, tps, _) | visit::fk_ctor(_, tps, _, _)
|
visit::fk_method(_, tps, _) | visit::fk_ctor(_, tps, _, _) {
|
||||||
{ scope_bare_fn(decl, id, tps) }
|
scope_bare_fn(decl, id, tps) }
|
||||||
visit::fk_anon(ast::proto_bare) { scope_bare_fn(decl, id, []) }
|
visit::fk_anon(ast::proto_bare, _) {
|
||||||
visit::fk_anon(_) | visit::fk_fn_block { scope_fn_expr(decl, id, []) }
|
scope_bare_fn(decl, id, []) }
|
||||||
|
visit::fk_anon(_, _) | visit::fk_fn_block(_) {
|
||||||
|
scope_fn_expr(decl, id, []) }
|
||||||
};
|
};
|
||||||
|
|
||||||
visit::visit_fn(fk, decl, body, sp, id, cons(scope, @sc), v);
|
visit::visit_fn(fk, decl, body, sp, id, cons(scope, @sc), v);
|
||||||
|
|
|
@ -343,12 +343,12 @@ fn find_pre_post_expr(fcx: fn_ctxt, e: @expr) {
|
||||||
expr_fn(_, _, _, cap_clause) | expr_fn_block(_, _, cap_clause) {
|
expr_fn(_, _, _, cap_clause) | expr_fn_block(_, _, cap_clause) {
|
||||||
find_pre_post_expr_fn_upvars(fcx, e);
|
find_pre_post_expr_fn_upvars(fcx, e);
|
||||||
|
|
||||||
for cap_clause.each { |cap_item|
|
for (*cap_clause).each { |cap_item|
|
||||||
let d = local_node_id_to_local_def_id(fcx, cap_item.id);
|
let d = local_node_id_to_local_def_id(fcx, cap_item.id);
|
||||||
option::iter(d, { |id| use_var(fcx, id) });
|
option::iter(d, { |id| use_var(fcx, id) });
|
||||||
}
|
}
|
||||||
|
|
||||||
for cap_clause.each { |cap_item|
|
for (*cap_clause).each { |cap_item|
|
||||||
if cap_item.is_move {
|
if cap_item.is_move {
|
||||||
log(debug, ("forget_in_postcond: ", cap_item));
|
log(debug, ("forget_in_postcond: ", cap_item));
|
||||||
forget_in_postcond(fcx, e.id, cap_item.id);
|
forget_in_postcond(fcx, e.id, cap_item.id);
|
||||||
|
|
|
@ -363,7 +363,7 @@ fn find_pre_post_state_cap_clause(fcx: fn_ctxt, e_id: node_id,
|
||||||
let ccx = fcx.ccx;
|
let ccx = fcx.ccx;
|
||||||
let pres_changed = set_prestate_ann(ccx, e_id, pres);
|
let pres_changed = set_prestate_ann(ccx, e_id, pres);
|
||||||
let post = tritv_clone(pres);
|
let post = tritv_clone(pres);
|
||||||
for cap_clause.each { |cap_item|
|
for (*cap_clause).each { |cap_item|
|
||||||
if cap_item.is_move {
|
if cap_item.is_move {
|
||||||
forget_in_poststate(fcx, post, cap_item.id);
|
forget_in_poststate(fcx, post, cap_item.id);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3565,7 +3565,7 @@ fn check_expr_with_unifier(fcx: @fn_ctxt,
|
||||||
}
|
}
|
||||||
ast::expr_fn(proto, decl, body, cap_clause) {
|
ast::expr_fn(proto, decl, body, cap_clause) {
|
||||||
check_expr_fn(fcx, expr, proto, decl, body, false, expected);
|
check_expr_fn(fcx, expr, proto, decl, body, false, expected);
|
||||||
capture::check_capture_clause(tcx, expr.id, proto, cap_clause);
|
capture::check_capture_clause(tcx, expr.id, cap_clause);
|
||||||
}
|
}
|
||||||
ast::expr_fn_block(decl, body, cap_clause) {
|
ast::expr_fn_block(decl, body, cap_clause) {
|
||||||
// Take the prototype from the expected type, but default to block:
|
// Take the prototype from the expected type, but default to block:
|
||||||
|
@ -3573,7 +3573,7 @@ fn check_expr_with_unifier(fcx: @fn_ctxt,
|
||||||
alt sty { ty::ty_fn({proto, _}) { some(proto) } _ { none } }
|
alt sty { ty::ty_fn({proto, _}) { some(proto) } _ { none } }
|
||||||
}).get_default(ast::proto_box);
|
}).get_default(ast::proto_box);
|
||||||
check_expr_fn(fcx, expr, proto, decl, body, false, expected);
|
check_expr_fn(fcx, expr, proto, decl, body, false, expected);
|
||||||
capture::check_capture_clause(tcx, expr.id, proto, cap_clause);
|
capture::check_capture_clause(tcx, expr.id, cap_clause);
|
||||||
}
|
}
|
||||||
ast::expr_loop_body(b) {
|
ast::expr_loop_body(b) {
|
||||||
// a loop body is the special argument to a `for` loop. We know that
|
// a loop body is the special argument to a `for` loop. We know that
|
||||||
|
@ -3605,7 +3605,7 @@ fn check_expr_with_unifier(fcx: @fn_ctxt,
|
||||||
ast::expr_fn_block(decl, body, cap_clause) {
|
ast::expr_fn_block(decl, body, cap_clause) {
|
||||||
check_expr_fn(fcx, b, proto, decl, body, true, some(inner_ty));
|
check_expr_fn(fcx, b, proto, decl, body, true, some(inner_ty));
|
||||||
demand::suptype(fcx, b.span, inner_ty, fcx.expr_ty(b));
|
demand::suptype(fcx, b.span, inner_ty, fcx.expr_ty(b));
|
||||||
capture::check_capture_clause(tcx, b.id, proto, cap_clause);
|
capture::check_capture_clause(tcx, b.id, cap_clause);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let block_ty = structurally_resolved_type(
|
let block_ty = structurally_resolved_type(
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// error-pattern:variable 'x' captured more than once
|
// error-pattern:variable 'x' captured more than once
|
||||||
fn main() {
|
fn main() {
|
||||||
let x = 5;
|
let x = 5;
|
||||||
let y = fn~[move x; copy x]() -> int { x };
|
let y = fn~(move x, copy x) -> int { x };
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// error-pattern:variable 'x' captured more than once
|
// error-pattern:variable 'x' captured more than once
|
||||||
fn main() {
|
fn main() {
|
||||||
let x = 5;
|
let x = 5;
|
||||||
let y = fn~[copy x, x]() -> int { x };
|
let y = fn~(copy x, copy x) -> int { x };
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// error-pattern:variable 'x' captured more than once
|
// error-pattern:variable 'x' captured more than once
|
||||||
fn main() {
|
fn main() {
|
||||||
let x = 5;
|
let x = 5;
|
||||||
let y = fn~[move x, x]() -> int { x };
|
let y = fn~(move x, move x) -> int { x };
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
fn to_lambda2(b: fn(uint) -> uint) -> fn@(uint) -> uint {
|
fn to_lambda2(b: fn(uint) -> uint) -> fn@(uint) -> uint {
|
||||||
// test case where copy clause specifies a value that is not used
|
// test case where copy clause specifies a value that is not used
|
||||||
// in fn@ body, but value is illegal to copy:
|
// in fn@ body, but value is illegal to copy:
|
||||||
ret fn@[copy b](u: uint) -> uint { 22u };
|
ret fn@(u: uint, copy b) -> uint { 22u };
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// error-pattern:unresolved name: z
|
// error-pattern:unresolved name: z
|
||||||
fn main() {
|
fn main() {
|
||||||
let x = 5;
|
let x = 5;
|
||||||
let y = fn~[copy z, x]() {
|
let y = fn~(copy z, copy x) {
|
||||||
};
|
};
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
// error-pattern:unresolved name: z
|
// error-pattern:unresolved name: z
|
||||||
fn main() {
|
fn main() {
|
||||||
let x = 5;
|
let x = 5;
|
||||||
let y = fn~[move z, x]() {
|
let y = fn~(move z, move x) {
|
||||||
};
|
};
|
||||||
}
|
}
|
|
@ -2,6 +2,6 @@
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let x = 5;
|
let x = 5;
|
||||||
let _y = fn~[move x]() { };
|
let _y = fn~(move x) { };
|
||||||
let _z = x; //< error: Unsatisfied precondition constraint
|
let _z = x; //< error: Unsatisfied precondition constraint
|
||||||
}
|
}
|
||||||
|
|
14
src/test/compile-fail/cap-clause-with-stack-closure.rs
Normal file
14
src/test/compile-fail/cap-clause-with-stack-closure.rs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
fn foo(_f: fn()) {}
|
||||||
|
fn bar(_f: @int) {}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let x = @3;
|
||||||
|
foo {|| bar(x); }
|
||||||
|
|
||||||
|
let x = @3;
|
||||||
|
foo {|copy x| bar(x); } //! ERROR cannot capture values explicitly with a block closure
|
||||||
|
|
||||||
|
let x = @3;
|
||||||
|
foo {|move x| bar(x); } //! ERROR cannot capture values explicitly with a block closure
|
||||||
|
}
|
||||||
|
|
35
src/test/compile-fail/kindck-implicit-close-over-mut-var.rs
Normal file
35
src/test/compile-fail/kindck-implicit-close-over-mut-var.rs
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
fn use(_i: int) {}
|
||||||
|
|
||||||
|
fn foo() {
|
||||||
|
// Here, i is *moved* into the closure: OK
|
||||||
|
let mut i = 0;
|
||||||
|
task::spawn {||
|
||||||
|
use(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bar() {
|
||||||
|
// Here, i would be implicitly *copied* but it
|
||||||
|
// is mutable: bad
|
||||||
|
let mut i = 0;
|
||||||
|
while i < 10 {
|
||||||
|
task::spawn {||
|
||||||
|
use(i); //! ERROR mutable variables cannot be implicitly captured
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn car() {
|
||||||
|
// Here, i is mutable, but *explicitly* copied:
|
||||||
|
let mut i = 0;
|
||||||
|
while i < 10 {
|
||||||
|
task::spawn {|copy i|
|
||||||
|
use(i);
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
}
|
8
src/test/compile-fail/kindck-nonsendable-1.rs
Normal file
8
src/test/compile-fail/kindck-nonsendable-1.rs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
fn foo(_x: @uint) {}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let x = @3u;
|
||||||
|
let _ = fn~() { foo(x); }; //! ERROR not a sendable value
|
||||||
|
let _ = fn~(copy x) { foo(x); }; //! ERROR not a sendable value
|
||||||
|
let _ = fn~(move x) { foo(x); }; //! ERROR not a sendable value
|
||||||
|
}
|
17
src/test/compile-fail/kindck-nonsendable-2.rs
Normal file
17
src/test/compile-fail/kindck-nonsendable-2.rs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
fn foo(_x: r) {}
|
||||||
|
|
||||||
|
resource r(_x: ()) {}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let x = r(());
|
||||||
|
let _ = fn~() {
|
||||||
|
// Error even though this is the last use:
|
||||||
|
foo(x); //! ERROR not a sendable value
|
||||||
|
};
|
||||||
|
|
||||||
|
let x = r(());
|
||||||
|
let _ = fn@() {
|
||||||
|
// OK in fn@ because this is the last use:
|
||||||
|
foo(x);
|
||||||
|
};
|
||||||
|
}
|
4
src/test/compile-fail/omitted-arg-in-item-fn.rs
Normal file
4
src/test/compile-fail/omitted-arg-in-item-fn.rs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
// xfail-test
|
||||||
|
|
||||||
|
fn foo(x) { //! ERROR expecting ':' but found ')'
|
||||||
|
}
|
11
src/test/compile-fail/omitted-arg-wrong-types.rs
Normal file
11
src/test/compile-fail/omitted-arg-wrong-types.rs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
// xfail-test
|
||||||
|
|
||||||
|
fn let_in<T>(x: T, f: fn(T)) {}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let_in(3u, fn&(i) { assert i == 3; });
|
||||||
|
//!^ ERROR expected `uint` but found `int`
|
||||||
|
|
||||||
|
let_in(3, fn&(i) { assert i == 3u; });
|
||||||
|
//!^ ERROR expected `int` but found `uint`
|
||||||
|
}
|
|
@ -1,6 +0,0 @@
|
||||||
// error-pattern: not a sendable value
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let x = @3u;
|
|
||||||
let _f = fn~(y: uint) -> uint { ret *x+y; };
|
|
||||||
}
|
|
|
@ -10,7 +10,7 @@ fn main() {
|
||||||
let mut i = 10;
|
let mut i = 10;
|
||||||
while (i > 0) {
|
while (i > 0) {
|
||||||
log(debug, i);
|
log(debug, i);
|
||||||
task::spawn {|| child(i, ch); };
|
task::spawn {|copy i| child(i, ch); };
|
||||||
i = i - 1;
|
i = i - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ fn test00() {
|
||||||
while i < number_of_tasks {
|
while i < number_of_tasks {
|
||||||
let builder = task::builder();
|
let builder = task::builder();
|
||||||
results += [task::future_result(builder)];
|
results += [task::future_result(builder)];
|
||||||
task::run(builder) {||
|
task::run(builder) {|copy i|
|
||||||
test00_start(ch, i, number_of_messages)
|
test00_start(ch, i, number_of_messages)
|
||||||
}
|
}
|
||||||
i = i + 1;
|
i = i + 1;
|
||||||
|
|
|
@ -43,7 +43,9 @@ fn test00() {
|
||||||
i = i + 1;
|
i = i + 1;
|
||||||
let builder = task::builder();
|
let builder = task::builder();
|
||||||
results += [task::future_result(builder)];
|
results += [task::future_result(builder)];
|
||||||
task::run(builder) {|| test00_start(ch, i, number_of_messages);}
|
task::run(builder) {|copy i|
|
||||||
|
test00_start(ch, i, number_of_messages);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let mut sum: int = 0;
|
let mut sum: int = 0;
|
||||||
for results.each {|r|
|
for results.each {|r|
|
||||||
|
@ -128,7 +130,9 @@ fn test06() {
|
||||||
i = i + 1;
|
i = i + 1;
|
||||||
let builder = task::builder();
|
let builder = task::builder();
|
||||||
results += [task::future_result(builder)];
|
results += [task::future_result(builder)];
|
||||||
task::run(builder) {|| test06_start(i);};
|
task::run(builder) {|copy i|
|
||||||
|
test06_start(i);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import task;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut i = 10;
|
let mut i = 10;
|
||||||
while i > 0 { task::spawn {|| child(i); }; i = i - 1; }
|
while i > 0 { task::spawn {|copy i| child(i); }; i = i - 1; }
|
||||||
#debug("main thread exiting");
|
#debug("main thread exiting");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue