rust/src/rustc/middle/kind.rs

544 lines
19 KiB
Rust
Raw Normal View History

import syntax::{visit, ast_util};
import syntax::ast::*;
import syntax::codemap::span;
import ty::{kind, kind_copyable, kind_noncopyable, kind_const, operators};
import driver::session::session;
import std::map::hashmap;
import util::ppaux::{ty_to_str, tys_to_str};
import syntax::print::pprust::expr_to_str;
import freevars::freevar_entry;
2012-05-25 02:14:40 -05:00
import dvec::extensions;
import lint::{non_implicitly_copyable_typarams,implicit_copies};
// Kind analysis pass.
//
// There are several kinds defined by various operations. The most restrictive
// kind is noncopyable. The noncopyable kind can be extended with any number
// of the following attributes.
//
// send: Things that can be sent on channels or included in spawned closures.
// copy: Things that can be copied.
// const: Things thare are deeply immutable. They are guaranteed never to
// change, and can be safely shared without copying between tasks.
// owned: Things that do not contain borrowed pointers.
//
// Send includes scalar types as well as classes and unique types containing
// only sendable types.
//
// Copy includes boxes, closure and unique types containing copyable types.
//
// Const include scalar types, things without non-const fields, and pointers
// to const things.
//
// This pass ensures that type parameters are only instantiated with types
// whose kinds are equal or less general than the way the type parameter was
// annotated (with the `send`, `copy` or `const` keyword).
//
// It also verifies that noncopyable kinds are not copied. Sendability is not
// applied, since none of our language primitives send. Instead, the sending
// primitives in the stdlib are explicitly annotated to only take sendable
// types.
fn kind_to_str(k: kind) -> ~str {
let mut kinds = ~[];
if ty::kind_lteq(kind_const(), k) {
vec::push(kinds, ~"const");
}
if ty::kind_can_be_copied(k) {
vec::push(kinds, ~"copy");
}
if ty::kind_can_be_sent(k) {
vec::push(kinds, ~"send");
} else if ty::kind_is_owned(k) {
vec::push(kinds, ~"owned");
}
str::connect(kinds, ~" ")
}
type rval_map = std::map::hashmap<node_id, ()>;
type ctx = {tcx: ty::ctxt,
method_map: typeck::method_map,
last_use_map: liveness::last_use_map,
current_item: node_id};
fn check_crate(tcx: ty::ctxt,
method_map: typeck::method_map,
last_use_map: liveness::last_use_map,
crate: @crate) {
let ctx = {tcx: tcx,
method_map: method_map,
last_use_map: last_use_map,
current_item: -1};
let visit = visit::mk_vt(@{
visit_expr: check_expr,
2011-12-20 21:39:33 -06:00
visit_stmt: check_stmt,
visit_block: check_block,
visit_fn: check_fn,
visit_ty: check_ty,
visit_item: fn@(i: @item, cx: ctx, v: visit::vt<ctx>) {
visit::visit_item(i, {current_item: i.id with cx}, v);
}
with *visit::default_visitor()
});
visit::visit_crate(*crate, ctx, visit);
tcx.sess.abort_if_errors();
}
type check_fn = fn@(ctx, node_id, option<@freevar_entry>,
bool, ty::t, sp: span);
2011-12-20 21:39:33 -06:00
// Yields the appropriate function to check the kind of closed over
// variables. `id` is the node_id for some expression that creates the
// closure.
fn with_appropriate_checker(cx: ctx, id: node_id, b: fn(check_fn)) {
fn check_for_uniq(cx: ctx, id: node_id, 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. Note that send implies owned.
2012-08-01 19:30:05 -05:00
if !check_send(cx, var_t, sp) { return; }
// copied in data must be copyable, but moved in data can be anything
let is_implicit = fv.is_some();
if !is_move { check_copy(cx, id, var_t, sp, is_implicit); }
// check that only immutable variables are implicitly copied in
2012-06-30 18:19:07 -05:00
for fv.each |fv| {
check_imm_free_var(cx, fv.def, fv.span);
}
}
fn check_for_box(cx: ctx, id: node_id, fv: option<@freevar_entry>,
is_move: bool, var_t: ty::t, sp: span) {
// all captured data must be owned
2012-08-01 19:30:05 -05:00
if !check_owned(cx.tcx, var_t, sp) { return; }
// copied in data must be copyable, but moved in data can be anything
let is_implicit = fv.is_some();
if !is_move { check_copy(cx, id, var_t, sp, is_implicit); }
// check that only immutable variables are implicitly copied in
2012-06-30 18:19:07 -05:00
for fv.each |fv| {
check_imm_free_var(cx, fv.def, fv.span);
}
}
fn check_for_block(cx: ctx, _id: node_id, 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, _id: node_id, _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);
2012-08-06 14:34:08 -05:00
match ty::ty_fn_proto(fty) {
2012-08-03 21:59:04 -05:00
proto_uniq => b(check_for_uniq),
proto_box => b(check_for_box),
proto_bare => b(check_for_bare),
proto_block => b(check_for_block)
2011-12-20 21:39:33 -06:00
}
}
// Check that the free variables used in a shared/sendable closure conform
// to the copy/move kind bounds. Then recursively check the function body.
fn check_fn(fk: visit::fn_kind, decl: fn_decl, body: blk, sp: span,
fn_id: node_id, cx: ctx, v: visit::vt<ctx>) {
// Find the check function that enforces the appropriate bounds for this
// kind of function:
2012-06-30 18:19:07 -05:00
do with_appropriate_checker(cx, fn_id) |chk| {
// 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.
2012-08-06 14:34:08 -05:00
let cap_clause = match fk {
2012-08-03 21:59:04 -05:00
visit::fk_anon(_, cc) | visit::fk_fn_block(cc) => cc,
visit::fk_item_fn(*) | visit::fk_method(*) |
2012-08-03 21:59:04 -05:00
visit::fk_ctor(*) | visit::fk_dtor(*) => @~[]
};
2012-06-30 18:19:07 -05:00
let captured_vars = do (*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, fn_id, 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.
2012-06-30 18:19:07 -05:00
for vec::each(*freevars::get_freevars(cx.tcx, fn_id)) |fv| {
let id = ast_util::def_id_of_def(fv.def).node;
// skip over free variables that appear in the cap clause
if captured_vars.contains(id) { again; }
// if this is the last use of the variable, then it will be
// a move and not a copy
let is_move = {
2012-08-06 14:34:08 -05:00
match check cx.last_use_map.find(fn_id) {
2012-08-03 21:59:04 -05:00
some(vars) => (*vars).contains(id),
none => false
2012-05-25 02:14:40 -05:00
}
};
let ty = ty::node_id_to_type(cx.tcx, id);
chk(cx, fn_id, some(fv), is_move, ty, fv.span);
}
}
visit::visit_fn(fk, decl, body, sp, fn_id, cx, v);
2011-12-20 21:39:33 -06:00
}
fn check_block(b: blk, cx: ctx, v: visit::vt<ctx>) {
2012-08-06 14:34:08 -05:00
match b.node.expr {
2012-08-03 21:59:04 -05:00
some(ex) => maybe_copy(cx, ex),
_ => ()
}
visit::visit_block(b, cx, v);
}
2011-12-20 21:39:33 -06:00
fn check_expr(e: @expr, cx: ctx, v: visit::vt<ctx>) {
debug!{"kind::check_expr(%s)", expr_to_str(e)};
2012-08-06 14:34:08 -05:00
match e.node {
expr_assign(_, ex) |
expr_unary(box(_), ex) | expr_unary(uniq(_), ex) |
2012-08-03 21:59:04 -05:00
expr_ret(some(ex)) => {
maybe_copy(cx, ex);
}
2012-08-03 21:59:04 -05:00
expr_cast(source, _) => {
maybe_copy(cx, source);
check_cast_for_escaping_regions(cx, source, e);
}
2012-08-03 21:59:04 -05:00
expr_copy(expr) => check_copy_ex(cx, expr, false),
// Vector add copies, but not "implicitly"
2012-08-03 21:59:04 -05:00
expr_assign_op(_, _, ex) => check_copy_ex(cx, ex, false),
expr_binary(add, ls, rs) => {
check_copy_ex(cx, ls, false);
check_copy_ex(cx, rs, false);
}
2012-08-03 21:59:04 -05:00
expr_rec(fields, def) => {
2012-06-30 18:19:07 -05:00
for fields.each |field| { maybe_copy(cx, field.node.expr); }
2012-08-06 14:34:08 -05:00
match def {
2012-08-03 21:59:04 -05:00
some(ex) => {
// All noncopyable fields must be overridden
let t = ty::expr_ty(cx.tcx, ex);
2012-08-06 14:34:08 -05:00
let ty_fields = match ty::get(t).struct {
2012-08-03 21:59:04 -05:00
ty::ty_rec(f) => f,
_ => cx.tcx.sess.span_bug(ex.span, ~"bad expr type in record")
};
2012-06-30 18:19:07 -05:00
for ty_fields.each |tf| {
if !vec::any(fields, |f| f.node.ident == tf.ident ) &&
!ty::kind_can_be_copied(ty::type_kind(cx.tcx, tf.mt.ty)) {
cx.tcx.sess.span_err(ex.span,
~"copying a noncopyable value");
}
}
}
2012-08-03 21:59:04 -05:00
_ => {}
}
}
2012-08-03 21:59:04 -05:00
expr_tup(exprs) | expr_vec(exprs, _) => {
2012-06-30 18:19:07 -05:00
for exprs.each |expr| { maybe_copy(cx, expr); }
}
2012-08-03 21:59:04 -05:00
expr_call(f, args, _) => {
let mut i = 0u;
2012-06-30 18:19:07 -05:00
for ty::ty_fn_args(ty::expr_ty(cx.tcx, f)).each |arg_t| {
2012-08-06 14:34:08 -05:00
match ty::arg_mode(cx.tcx, arg_t) {
2012-08-03 21:59:04 -05:00
by_copy => maybe_copy(cx, args[i]),
by_ref | by_val | by_mutbl_ref | by_move => ()
}
i += 1u;
}
}
2012-08-03 21:59:04 -05:00
expr_path(_) | expr_field(_, _, _) => {
2012-06-30 18:19:07 -05:00
do option::iter(cx.tcx.node_type_substs.find(e.id)) |ts| {
2012-08-06 14:34:08 -05:00
let bounds = match check e.node {
2012-08-03 21:59:04 -05:00
expr_path(_) => {
let did = ast_util::def_id_of_def(cx.tcx.def_map.get(e.id));
ty::lookup_item_type(cx.tcx, did).bounds
}
2012-08-03 21:59:04 -05:00
expr_field(base, _, _) => {
2012-08-06 14:34:08 -05:00
match cx.method_map.get(e.id).origin {
2012-08-03 21:59:04 -05:00
typeck::method_static(did) => {
// n.b.: When we encode class/impl methods, the bounds
// that we encode include both the class/impl bounds
// and then the method bounds themselves...
ty::lookup_item_type(cx.tcx, did).bounds
}
typeck::method_param({trait_id:trt_id,
method_num:n_mth, _}) |
2012-08-03 21:59:04 -05:00
typeck::method_trait(trt_id, n_mth) => {
// ...trait methods bounds, in contrast, include only the
// method bounds, so we must preprend the tps from the
// trait itself. This ought to be harmonized.
let trt_bounds =
ty::lookup_item_type(cx.tcx, trt_id).bounds;
let mth = ty::trait_methods(cx.tcx, trt_id)[n_mth];
@(vec::append(*trt_bounds, *mth.tps))
}
}
}
};
if vec::len(ts) != vec::len(*bounds) {
// Fail earlier to make debugging easier
fail fmt!{"Internal error: in kind::check_expr, length \
mismatch between actual and declared bounds: actual = \
%s (%u tys), declared = %? (%u tys)",
tys_to_str(cx.tcx, ts), ts.len(), *bounds, (*bounds).len()};
}
2012-06-30 18:19:07 -05:00
do vec::iter2(ts, *bounds) |ty, bound| {
check_bounds(cx, e.id, e.span, ty, bound)
}
}
}
2012-08-03 21:59:04 -05:00
_ => { }
}
visit::visit_expr(e, cx, v);
}
fn check_stmt(stmt: @stmt, cx: ctx, v: visit::vt<ctx>) {
2012-08-06 14:34:08 -05:00
match stmt.node {
2012-08-03 21:59:04 -05:00
stmt_decl(@{node: decl_local(locals), _}, _) => {
2012-06-30 18:19:07 -05:00
for locals.each |local| {
2012-08-06 14:34:08 -05:00
match local.node.init {
2012-08-03 21:59:04 -05:00
some({op: init_assign, expr}) => maybe_copy(cx, expr),
_ => {}
}
}
}
2012-08-03 21:59:04 -05:00
_ => {}
}
visit::visit_stmt(stmt, cx, v);
}
fn check_ty(aty: @ty, cx: ctx, v: visit::vt<ctx>) {
2012-08-06 14:34:08 -05:00
match aty.node {
2012-08-03 21:59:04 -05:00
ty_path(_, id) => {
2012-06-30 18:19:07 -05:00
do option::iter(cx.tcx.node_type_substs.find(id)) |ts| {
let did = ast_util::def_id_of_def(cx.tcx.def_map.get(id));
let bounds = ty::lookup_item_type(cx.tcx, did).bounds;
2012-06-30 18:19:07 -05:00
do vec::iter2(ts, *bounds) |ty, bound| {
check_bounds(cx, aty.id, aty.span, ty, bound)
}
}
}
2012-08-03 21:59:04 -05:00
_ => {}
}
visit::visit_ty(aty, cx, v);
}
fn check_bounds(cx: ctx, id: node_id, sp: span,
ty: ty::t, bounds: ty::param_bounds) {
let kind = ty::type_kind(cx.tcx, ty);
let p_kind = ty::param_bounds_to_kind(bounds);
if !ty::kind_lteq(p_kind, kind) {
// If the only reason the kind check fails is because the
// argument type isn't implicitly copyable, consult the warning
// settings to figure out what to do.
let implicit = ty::kind_implicitly_copyable() - ty::kind_copyable();
if ty::kind_lteq(p_kind, kind | implicit) {
cx.tcx.sess.span_lint(
non_implicitly_copyable_typarams,
id, cx.current_item, sp,
~"instantiating copy type parameter with a \
not implicitly copyable type");
} else {
cx.tcx.sess.span_err(
sp,
~"instantiating a type parameter with an incompatible type " +
~"(needs `" + kind_to_str(p_kind) +
~"`, got `" + kind_to_str(kind) +
~"`, missing `" + kind_to_str(p_kind - kind) + ~"`)");
}
}
}
fn maybe_copy(cx: ctx, ex: @expr) {
check_copy_ex(cx, ex, true);
}
fn is_nullary_variant(cx: ctx, ex: @expr) -> bool {
2012-08-06 14:34:08 -05:00
match ex.node {
2012-08-03 21:59:04 -05:00
expr_path(_) => {
2012-08-06 14:34:08 -05:00
match cx.tcx.def_map.get(ex.id) {
2012-08-03 21:59:04 -05:00
def_variant(edid, vdid) => {
vec::len(ty::enum_variant_with_id(cx.tcx, edid, vdid).args) == 0u
}
2012-08-03 21:59:04 -05:00
_ => false
}
}
2012-08-03 21:59:04 -05:00
_ => false
}
}
fn check_copy_ex(cx: ctx, ex: @expr, implicit_copy: bool) {
if ty::expr_is_lval(cx.method_map, ex) &&
2012-05-25 02:14:40 -05:00
!cx.last_use_map.contains_key(ex.id) &&
!is_nullary_variant(cx, ex) {
let ty = ty::expr_ty(cx.tcx, ex);
check_copy(cx, ex.id, ty, ex.span, implicit_copy);
}
}
fn check_imm_free_var(cx: ctx, def: def, sp: span) {
let msg = ~"mutable variables cannot be implicitly captured; \
use a capture clause";
2012-08-06 14:34:08 -05:00
match def {
2012-08-03 21:59:04 -05:00
def_local(_, is_mutbl) => {
if is_mutbl {
cx.tcx.sess.span_err(sp, msg);
}
}
2012-08-03 21:59:04 -05:00
def_arg(_, mode) => {
2012-08-06 14:34:08 -05:00
match ty::resolved_mode(cx.tcx, mode) {
2012-08-03 21:59:04 -05:00
by_ref | by_val | by_move | by_copy => { /* ok */ }
by_mutbl_ref => {
cx.tcx.sess.span_err(sp, msg);
}
}
}
2012-08-03 21:59:04 -05:00
def_upvar(_, def1, _) => {
check_imm_free_var(cx, *def1, sp);
}
2012-08-03 21:59:04 -05:00
def_binding(*) | def_self(*) => { /*ok*/ }
_ => {
cx.tcx.sess.span_bug(
sp,
fmt!{"unknown def for free variable: %?", def});
}
}
}
fn check_copy(cx: ctx, id: node_id, ty: ty::t, sp: span,
implicit_copy: bool) {
let k = ty::type_kind(cx.tcx, ty);
if !ty::kind_can_be_copied(k) {
cx.tcx.sess.span_err(sp, ~"copying a noncopyable value");
} else if implicit_copy && !ty::kind_can_be_implicitly_copied(k) {
cx.tcx.sess.span_lint(
implicit_copies, id, cx.current_item,
sp,
~"implicitly copying a non-implicitly-copyable value");
}
}
fn check_send(cx: ctx, ty: ty::t, sp: span) -> bool {
if !ty::kind_can_be_sent(ty::type_kind(cx.tcx, ty)) {
cx.tcx.sess.span_err(sp, ~"not a sendable value");
false
} else {
true
}
}
// note: also used from middle::typeck::regionck!
fn check_owned(tcx: ty::ctxt, ty: ty::t, sp: span) -> bool {
if !ty::kind_is_owned(ty::type_kind(tcx, ty)) {
2012-08-06 14:34:08 -05:00
match ty::get(ty).struct {
2012-08-03 21:59:04 -05:00
ty::ty_param(*) => {
tcx.sess.span_err(sp, ~"value may contain borrowed \
pointers; use `owned` bound");
}
2012-08-03 21:59:04 -05:00
_ => {
tcx.sess.span_err(sp, ~"value may contain borrowed \
pointers");
}
}
false
} else {
true
}
}
/// This is rather subtle. When we are casting a value to a
/// instantiated trait like `a as trait/&r`, regionck already ensures
/// that any borrowed pointers that appear in the type of `a` are
/// bounded by `&r`. However, it is possible that there are *type
/// parameters* in the type of `a`, and those *type parameters* may
/// have borrowed pointers within them. We have to guarantee that the
/// regions which appear in those type parameters are not obscured.
///
/// Therefore, we ensure that one of three conditions holds:
///
/// (1) The trait instance cannot escape the current fn. This is
/// guaranteed if the region bound `&r` is some scope within the fn
/// itself. This case is safe because whatever borrowed pointers are
/// found within the type parameter, they must enclose the fn body
/// itself.
///
/// (2) The type parameter appears in the type of the trait. For
/// example, if the type parameter is `T` and the trait type is
/// `deque<T>`, then whatever borrowed ptrs may appear in `T` also
/// appear in `deque<T>`.
///
/// (3) The type parameter is owned (and therefore does not contain
/// borrowed ptrs).
fn check_cast_for_escaping_regions(
cx: ctx,
source: @expr,
target: @expr) {
// Determine what type we are casting to; if it is not an trait, then no
// worries.
let target_ty = ty::expr_ty(cx.tcx, target);
2012-08-06 14:34:08 -05:00
let target_substs = match ty::get(target_ty).struct {
ty::ty_trait(_, substs) => {substs}
2012-08-01 19:30:05 -05:00
_ => { return; /* not a cast to a trait */ }
};
// Check, based on the region associated with the trait, whether it can
// possibly escape the enclosing fn item (note that all type parameters
// must have been declared on the enclosing fn item):
2012-08-06 14:34:08 -05:00
match target_substs.self_r {
2012-08-01 19:30:05 -05:00
some(ty::re_scope(*)) => { return; /* case (1) */ }
none | some(ty::re_static) | some(ty::re_free(*)) => {}
some(ty::re_bound(*)) | some(ty::re_var(*)) => {
cx.tcx.sess.span_bug(
source.span,
fmt!{"bad region found in kind: %?", target_substs.self_r});
}
}
// Assuming the trait instance can escape, then ensure that each parameter
// either appears in the trait type or is owned:
let target_params = ty::param_tys_in_type(target_ty);
let source_ty = ty::expr_ty(cx.tcx, source);
do ty::walk_ty(source_ty) |ty| {
2012-08-06 14:34:08 -05:00
match ty::get(ty).struct {
ty::ty_param(source_param) => {
if target_params.contains(source_param) {
/* case (2) */
} else {
check_owned(cx.tcx, ty, source.span); /* case (3) */
}
}
_ => {}
}
}
}
//
// Local Variables:
// mode: rust
// fill-column: 78;
// indent-tabs-mode: nil
// c-basic-offset: 4
// buffer-file-coding-system: utf-8-unix
// End:
//