Change the kind checker to ignore results of last-use
and require explicit moves. Also provide more info in some error messages. Also: check that non-copyable struct fields don't get copied. Closes #3481
This commit is contained in:
parent
9abc7f0a1c
commit
c4155f5ea3
1 changed files with 49 additions and 37 deletions
|
@ -89,8 +89,11 @@ fn check_crate(tcx: ty::ctxt,
|
||||||
tcx.sess.abort_if_errors();
|
tcx.sess.abort_if_errors();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// bool flag is only used for checking closures,
|
||||||
|
// where it refers to whether a var is 'move' in the
|
||||||
|
// capture clause
|
||||||
type check_fn = fn@(ctx, node_id, Option<@freevar_entry>,
|
type check_fn = fn@(ctx, node_id, Option<@freevar_entry>,
|
||||||
bool, ty::t, sp: span);
|
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
|
||||||
|
@ -111,7 +114,6 @@ fn with_appropriate_checker(cx: ctx, id: node_id, b: fn(check_fn)) {
|
||||||
"to copy values into a ~fn closure, use a \
|
"to copy values into a ~fn closure, use a \
|
||||||
capture clause: `fn~(copy x)` or `|copy x|`")));
|
capture clause: `fn~(copy x)` or `|copy x|`")));
|
||||||
}
|
}
|
||||||
|
|
||||||
// check that only immutable variables are implicitly copied in
|
// check that only immutable variables are implicitly copied in
|
||||||
for fv.each |fv| {
|
for fv.each |fv| {
|
||||||
check_imm_free_var(cx, fv.def, fv.span);
|
check_imm_free_var(cx, fv.def, fv.span);
|
||||||
|
@ -132,7 +134,6 @@ fn with_appropriate_checker(cx: ctx, id: node_id, b: fn(check_fn)) {
|
||||||
"to copy values into a @fn closure, use a \
|
"to copy values into a @fn closure, use a \
|
||||||
capture clause: `fn~(copy x)` or `|copy x|`")));
|
capture clause: `fn~(copy x)` or `|copy x|`")));
|
||||||
}
|
}
|
||||||
|
|
||||||
// check that only immutable variables are implicitly copied in
|
// check that only immutable variables are implicitly copied in
|
||||||
for fv.each |fv| {
|
for fv.each |fv| {
|
||||||
check_imm_free_var(cx, fv.def, fv.span);
|
check_imm_free_var(cx, fv.def, fv.span);
|
||||||
|
@ -151,7 +152,7 @@ fn with_appropriate_checker(cx: ctx, id: node_id, b: fn(check_fn)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_for_bare(cx: ctx, _id: node_id, _fv: Option<@freevar_entry>,
|
fn check_for_bare(cx: ctx, _id: node_id, _fv: Option<@freevar_entry>,
|
||||||
_is_move: bool,_var_t: ty::t, sp: span) {
|
_is_move: bool, _var_t: ty::t, sp: span) {
|
||||||
cx.tcx.sess.span_err(sp, ~"attempted dynamic environment capture");
|
cx.tcx.sess.span_err(sp, ~"attempted dynamic environment capture");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,6 +190,7 @@ fn check_fn(fk: visit::fn_kind, decl: fn_decl, body: blk, sp: span,
|
||||||
let cap_def = cx.tcx.def_map.get(cap_item.id);
|
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 cap_def_id = ast_util::def_id_of_def(cap_def).node;
|
||||||
let ty = ty::node_id_to_type(cx.tcx, cap_def_id);
|
let ty = ty::node_id_to_type(cx.tcx, cap_def_id);
|
||||||
|
// Here's where is_move isn't always false...
|
||||||
chk(cx, fn_id, None, cap_item.is_move, ty, cap_item.span);
|
chk(cx, fn_id, None, cap_item.is_move, ty, cap_item.span);
|
||||||
cap_def_id
|
cap_def_id
|
||||||
};
|
};
|
||||||
|
@ -201,17 +203,10 @@ fn check_fn(fk: visit::fn_kind, decl: fn_decl, body: blk, sp: span,
|
||||||
// skip over free variables that appear in the cap clause
|
// skip over free variables that appear in the cap clause
|
||||||
if captured_vars.contains(&id) { loop; }
|
if captured_vars.contains(&id) { loop; }
|
||||||
|
|
||||||
// if this is the last use of the variable, then it will be
|
|
||||||
// a move and not a copy
|
|
||||||
let is_move = {
|
|
||||||
match cx.last_use_map.find(fn_id) {
|
|
||||||
Some(vars) => (*vars).contains(&id),
|
|
||||||
None => false
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let ty = ty::node_id_to_type(cx.tcx, id);
|
let ty = ty::node_id_to_type(cx.tcx, id);
|
||||||
chk(cx, fn_id, Some(*fv), is_move, ty, fv.span);
|
// is_move is always false here. See the let captured_vars...
|
||||||
|
// code above for where it's not always false.
|
||||||
|
chk(cx, fn_id, Some(*fv), false, ty, fv.span);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,7 +215,9 @@ fn check_fn(fk: visit::fn_kind, decl: fn_decl, body: blk, sp: span,
|
||||||
|
|
||||||
fn check_block(b: blk, cx: ctx, v: visit::vt<ctx>) {
|
fn check_block(b: blk, cx: ctx, v: visit::vt<ctx>) {
|
||||||
match b.node.expr {
|
match b.node.expr {
|
||||||
Some(ex) => maybe_copy(cx, ex, None),
|
Some(ex) => maybe_copy(cx, ex,
|
||||||
|
Some(("Tail expressions in blocks must be copyable",
|
||||||
|
"(Try adding a move)"))),
|
||||||
_ => ()
|
_ => ()
|
||||||
}
|
}
|
||||||
visit::visit_block(b, cx, v);
|
visit::visit_block(b, cx, v);
|
||||||
|
@ -281,33 +278,45 @@ fn check_expr(e: @expr, cx: ctx, v: visit::vt<ctx>) {
|
||||||
expr_assign(_, ex) |
|
expr_assign(_, ex) |
|
||||||
expr_unary(box(_), ex) | expr_unary(uniq(_), ex) |
|
expr_unary(box(_), ex) | expr_unary(uniq(_), ex) |
|
||||||
expr_ret(Some(ex)) => {
|
expr_ret(Some(ex)) => {
|
||||||
maybe_copy(cx, ex, None);
|
maybe_copy(cx, ex, Some(("Returned values must be copyable",
|
||||||
|
"Try adding a move")));
|
||||||
}
|
}
|
||||||
expr_cast(source, _) => {
|
expr_cast(source, _) => {
|
||||||
maybe_copy(cx, source, None);
|
maybe_copy(cx, source, Some(("Casted values must be copyable",
|
||||||
|
"Try adding a move")));
|
||||||
check_cast_for_escaping_regions(cx, source, e);
|
check_cast_for_escaping_regions(cx, source, e);
|
||||||
}
|
}
|
||||||
expr_copy(expr) => check_copy_ex(cx, expr, false, None),
|
expr_copy(expr) => check_copy_ex(cx, expr, false,
|
||||||
|
Some(("Explicit copy requires a copyable argument", ""))),
|
||||||
// Vector add copies, but not "implicitly"
|
// Vector add copies, but not "implicitly"
|
||||||
expr_assign_op(_, _, ex) => check_copy_ex(cx, ex, false, None),
|
expr_assign_op(_, _, ex) => check_copy_ex(cx, ex, false,
|
||||||
|
Some(("Assignment with operation requires \
|
||||||
|
a copyable argument", ""))),
|
||||||
expr_binary(add, ls, rs) => {
|
expr_binary(add, ls, rs) => {
|
||||||
check_copy_ex(cx, ls, false, None);
|
let reason = Some(("Binary operators require copyable arguments",
|
||||||
check_copy_ex(cx, rs, false, None);
|
""));
|
||||||
|
check_copy_ex(cx, ls, false, reason);
|
||||||
|
check_copy_ex(cx, rs, false, reason);
|
||||||
}
|
}
|
||||||
expr_rec(fields, def) => {
|
expr_rec(fields, def) | expr_struct(_, fields, def) => {
|
||||||
for fields.each |field| { maybe_copy(cx, field.node.expr, None); }
|
for fields.each |field| { maybe_copy(cx, field.node.expr,
|
||||||
|
Some(("Record or struct fields require \
|
||||||
|
copyable arguments", ""))); }
|
||||||
match def {
|
match def {
|
||||||
Some(ex) => {
|
Some(ex) => {
|
||||||
// All noncopyable fields must be overridden
|
// All noncopyable fields must be overridden
|
||||||
let t = ty::expr_ty(cx.tcx, ex);
|
let t = ty::expr_ty(cx.tcx, ex);
|
||||||
let ty_fields = match ty::get(t).sty {
|
let ty_fields = match ty::get(t).sty {
|
||||||
ty::ty_rec(f) => f,
|
ty::ty_rec(f) => f,
|
||||||
_ => cx.tcx.sess.span_bug(ex.span, ~"bad expr type in record")
|
ty::ty_class(did, substs) =>
|
||||||
|
ty::class_items_as_fields(cx.tcx, did, &substs),
|
||||||
|
_ => cx.tcx.sess.span_bug(ex.span,
|
||||||
|
~"bad base expr type in record")
|
||||||
};
|
};
|
||||||
for ty_fields.each |tf| {
|
for ty_fields.each |tf| {
|
||||||
if !vec::any(fields, |f| f.node.ident == tf.ident ) &&
|
if !vec::any(fields, |f| f.node.ident == tf.ident ) &&
|
||||||
!ty::kind_can_be_copied(ty::type_kind(cx.tcx, tf.mt.ty)) {
|
!ty::kind_can_be_copied(ty::type_kind(cx.tcx, tf.mt.ty)) {
|
||||||
cx.tcx.sess.span_err(ex.span,
|
cx.tcx.sess.span_err(e.span,
|
||||||
~"copying a noncopyable value");
|
~"copying a noncopyable value");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -316,16 +325,16 @@ fn check_expr(e: @expr, cx: ctx, v: visit::vt<ctx>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
expr_tup(exprs) | expr_vec(exprs, _) => {
|
expr_tup(exprs) | expr_vec(exprs, _) => {
|
||||||
for exprs.each |expr| { maybe_copy(cx, *expr, None); }
|
for exprs.each |expr| { maybe_copy(cx, *expr,
|
||||||
|
Some(("Tuple or vec elements must be copyable", ""))); }
|
||||||
}
|
}
|
||||||
expr_call(f, args, _) => {
|
expr_call(f, args, _) => {
|
||||||
let mut i = 0u;
|
for ty::ty_fn_args(ty::expr_ty(cx.tcx, f)).eachi |i, arg_t| {
|
||||||
for ty::ty_fn_args(ty::expr_ty(cx.tcx, f)).each |arg_t| {
|
|
||||||
match ty::arg_mode(cx.tcx, *arg_t) {
|
match ty::arg_mode(cx.tcx, *arg_t) {
|
||||||
by_copy => maybe_copy(cx, args[i], None),
|
by_copy => maybe_copy(cx, args[i],
|
||||||
|
Some(("Callee takes its argument by copy", ""))),
|
||||||
by_ref | by_val | by_move => ()
|
by_ref | by_val | by_move => ()
|
||||||
}
|
}
|
||||||
i += 1u;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
expr_field(lhs, _, _) => {
|
expr_field(lhs, _, _) => {
|
||||||
|
@ -334,7 +343,9 @@ fn check_expr(e: @expr, cx: ctx, v: visit::vt<ctx>) {
|
||||||
match cx.method_map.find(e.id) {
|
match cx.method_map.find(e.id) {
|
||||||
Some(ref mme) => {
|
Some(ref mme) => {
|
||||||
match ty::arg_mode(cx.tcx, mme.self_arg) {
|
match ty::arg_mode(cx.tcx, mme.self_arg) {
|
||||||
by_copy => maybe_copy(cx, lhs, None),
|
by_copy => maybe_copy(cx, lhs,
|
||||||
|
Some(("Method call takes its self argument by copy",
|
||||||
|
""))),
|
||||||
by_ref | by_val | by_move => ()
|
by_ref | by_val | by_move => ()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -344,10 +355,12 @@ fn check_expr(e: @expr, cx: ctx, v: visit::vt<ctx>) {
|
||||||
expr_repeat(element, count_expr, _) => {
|
expr_repeat(element, count_expr, _) => {
|
||||||
let count = ty::eval_repeat_count(cx.tcx, count_expr, e.span);
|
let count = ty::eval_repeat_count(cx.tcx, count_expr, e.span);
|
||||||
if count == 1 {
|
if count == 1 {
|
||||||
maybe_copy(cx, element, None);
|
maybe_copy(cx, element, Some(("Trivial repeat takes its element \
|
||||||
|
by copy", "")));
|
||||||
} else {
|
} else {
|
||||||
let element_ty = ty::expr_ty(cx.tcx, element);
|
let element_ty = ty::expr_ty(cx.tcx, element);
|
||||||
check_copy(cx, element.id, element_ty, element.span, true, None);
|
check_copy(cx, element.id, element_ty, element.span, true,
|
||||||
|
Some(("Repeat takes its elements by copy", "")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => { }
|
_ => { }
|
||||||
|
@ -360,7 +373,9 @@ fn check_stmt(stmt: @stmt, cx: ctx, v: visit::vt<ctx>) {
|
||||||
stmt_decl(@{node: decl_local(locals), _}, _) => {
|
stmt_decl(@{node: decl_local(locals), _}, _) => {
|
||||||
for locals.each |local| {
|
for locals.each |local| {
|
||||||
match local.node.init {
|
match local.node.init {
|
||||||
Some({op: init_assign, expr}) => maybe_copy(cx, expr, None),
|
Some({op: init_assign, expr}) =>
|
||||||
|
maybe_copy(cx, expr, Some(("Initializer statement \
|
||||||
|
takes its right-hand side by copy", ""))),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -434,9 +449,6 @@ fn check_copy_ex(cx: ctx, ex: @expr, implicit_copy: bool,
|
||||||
why: Option<(&str,&str)>) {
|
why: Option<(&str,&str)>) {
|
||||||
if ty::expr_is_lval(cx.tcx, cx.method_map, ex) &&
|
if ty::expr_is_lval(cx.tcx, cx.method_map, ex) &&
|
||||||
|
|
||||||
// this is a move
|
|
||||||
!cx.last_use_map.contains_key(ex.id) &&
|
|
||||||
|
|
||||||
// a reference to a constant like `none`... no need to warn
|
// a reference to a constant like `none`... no need to warn
|
||||||
// about *this* even if the type is Option<~int>
|
// about *this* even if the type is Option<~int>
|
||||||
!is_nullary_variant(cx, ex) &&
|
!is_nullary_variant(cx, ex) &&
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue