Pass all arguments by reference, make immut alias mode equiv to value

Arguments that can't be safely referenced will be implicitly copied.

(Warnings for expensive copies will be forthcoming.)

This will allow us to get rid of most of the ampersands in function
signatures. See [1].

[1] https://mail.mozilla.org/pipermail/rust-dev/2011-September/000759.html
This commit is contained in:
Marijn Haverbeke 2011-09-07 15:13:19 +02:00
parent 476bbca87a
commit 4be7e1e5cd
7 changed files with 188 additions and 158 deletions

View File

@ -158,14 +158,14 @@ fn compile_input(sess: session::session, cfg: ast::crate_cfg, input: &str,
let mut_map =
time(time_passes, "mutability checking",
bind middle::mut::check_crate(ty_cx, crate));
time(time_passes, "alias checking",
bind middle::alias::check_crate(ty_cx, crate));
let copy_map = time(time_passes, "alias checking",
bind middle::alias::check_crate(ty_cx, crate));
time(time_passes, "kind checking", bind kind::check_crate(ty_cx, crate));
if sess.get_opts().no_trans { ret; }
let llmod =
time(time_passes, "translation",
bind trans::trans_crate(sess, crate, ty_cx, output, ast_map,
mut_map));
mut_map, copy_map));
time(time_passes, "LLVM passes",
bind link::write::run_passes(sess, llmod, output));
}

View File

@ -24,28 +24,35 @@ tag valid { valid; overwritten(span, ast::path); val_taken(span, ast::path); }
type restrict =
@{root_var: option::t<node_id>,
node_id: node_id,
ty: ty::t,
local_id: uint,
bindings: [node_id],
unsafe_ty: option::t<ty::t>,
depends_on: [uint],
mutable ok: valid};
mutable ok: valid,
mutable given_up: bool};
type scope = @[restrict];
tag local_info { local(uint); }
type copy_map = std::map::hashmap<node_id, ()>;
type ctx =
{tcx: ty::ctxt,
local_map: std::map::hashmap<node_id, local_info>,
mutable next_local: uint};
mutable next_local: uint,
copy_map: copy_map};
fn check_crate(tcx: ty::ctxt, crate: &@ast::crate) {
fn check_crate(tcx: ty::ctxt, crate: &@ast::crate) -> copy_map {
// Stores information about object fields and function
// arguments that's otherwise not easily available.
let cx =
@{tcx: tcx,
local_map: std::map::new_int_hash(),
mutable next_local: 0u};
mutable next_local: 0u,
copy_map: std::map::new_int_hash()};
let v =
@{visit_fn: visit_fn,
visit_expr: bind visit_expr(cx, _, _, _),
@ -53,6 +60,7 @@ fn check_crate(tcx: ty::ctxt, crate: &@ast::crate) {
with *visit::default_visitor::<scope>()};
visit::visit_crate(*crate, @[], visit::mk_vt(v));
tcx.sess.abort_if_errors();
ret cx.copy_map;
}
fn visit_fn(f: &ast::_fn, _tp: &[ast::ty_param], _sp: &span, _name: &fn_ident,
@ -148,37 +156,53 @@ fn visit_decl(cx: &@ctx, d: &@ast::decl, sc: &scope, v: &vt<scope>) {
}
}
fn check_call(cx: &ctx, f: &@ast::expr, args: &[@ast::expr], sc: &scope) ->
[restrict] {
fn cant_copy(cx: &ctx, r: &restrict) -> bool {
if r.given_up { ret false; }
// FIXME alt contexts copying not supported yet
if r.node_id == 0 { ret true; }
// FIXME warn when copy is expensive
if ty::type_allows_implicit_copy(cx.tcx, r.ty) {
r.given_up = true;
cx.copy_map.insert(r.node_id, ());
ret false;
} else {
ret true;
}
}
fn check_call(cx: &ctx, f: &@ast::expr, args: &[@ast::expr], sc: &scope)
-> [restrict] {
let fty = ty::type_autoderef(cx.tcx, ty::expr_ty(cx.tcx, f));
let arg_ts = ty::ty_fn_args(cx.tcx, fty);
let mut_roots: [{arg: uint, node: node_id}] = [];
let restricts = [];
let i = 0u;
for arg_t: ty::arg in arg_ts {
if arg_t.mode != ty::mo_val {
let arg = args[i];
let root = expr_root(cx.tcx, arg, false);
if arg_t.mode == ty::mo_alias(true) {
alt path_def(cx, arg) {
some(def) {
let dnum = ast_util::def_id_of_def(def).node;
mut_roots += [{arg: i, node: dnum}];
}
_ { }
}
let arg = args[i];
let root = expr_root(cx.tcx, arg, false);
if arg_t.mode == ty::mo_alias(true) {
alt path_def(cx, arg) {
some(def) {
let dnum = ast_util::def_id_of_def(def).node;
mut_roots += [{arg: i, node: dnum}];
}
_ { }
}
let root_var = path_def_id(cx, root.ex);
let unsafe_t =
alt inner_mut(root.ds) { some(t) { some(t) } _ { none } };
restricts +=
[@{root_var: root_var,
local_id: cx.next_local,
bindings: [arg.id],
unsafe_ty: unsafe_t,
depends_on: deps(sc, root_var),
mutable ok: valid}];
}
let root_var = path_def_id(cx, root.ex);
let unsafe_t =
alt inner_mut(root.ds) { some(t) { some(t) } _ { none } };
restricts +=
[@{root_var: root_var,
node_id: arg_t.mode == ast::by_mut_ref ? 0 : arg.id,
ty: arg_t.ty,
local_id: cx.next_local,
bindings: [arg.id],
unsafe_ty: unsafe_t,
depends_on: deps(sc, root_var),
mutable ok: valid,
// FIXME kludge
mutable given_up: arg_t.mode == ty::mo_move}];
i += 1u;
}
let f_may_close =
@ -189,7 +213,7 @@ fn check_call(cx: &ctx, f: &@ast::expr, args: &[@ast::expr], sc: &scope) ->
if f_may_close {
let i = 0u;
for r in restricts {
if !option::is_none(r.unsafe_ty) {
if !option::is_none(r.unsafe_ty) && cant_copy(cx, r) {
cx.tcx.sess.span_err(f.span,
#fmt["function may alias with argument \
%u, which is not immutably rooted",
@ -199,19 +223,19 @@ fn check_call(cx: &ctx, f: &@ast::expr, args: &[@ast::expr], sc: &scope) ->
}
}
let j = 0u;
for @{unsafe_ty: unsafe_ty, _} in restricts {
alt unsafe_ty {
for r in restricts {
alt r.unsafe_ty {
some(ty) {
let i = 0u;
for arg_t: ty::arg in arg_ts {
let mut_alias = arg_t.mode == ty::mo_alias(true);
if i != j &&
ty_can_unsafely_include(cx, ty, arg_t.ty, mut_alias) {
ty_can_unsafely_include(cx, ty, arg_t.ty, mut_alias) &&
cant_copy(cx, r) {
cx.tcx.sess.span_err(
args[i].span,
#fmt["argument %u may alias with argument %u, \
which is not immutably rooted",
i, j]);
which is not immutably rooted", i, j]);
}
i += 1u;
}
@ -223,27 +247,22 @@ fn check_call(cx: &ctx, f: &@ast::expr, args: &[@ast::expr], sc: &scope) ->
// Ensure we're not passing a root by mutable alias.
for {node: node, arg: arg} in mut_roots {
let mut_alias_to_root = false;
let mut_alias_to_root_count = 0u;
for @{root_var: root_var, _} in restricts {
alt root_var {
some(root) {
if node == root {
mut_alias_to_root_count += 1u;
if mut_alias_to_root_count > 1u {
mut_alias_to_root = true;
let i = 0u;
for r in restricts {
if i != arg {
alt r.root_var {
some(root) {
if node == root && cant_copy(cx, r) {
cx.tcx.sess.span_err(
args[arg].span, "passing a mutable alias to a \
variable that roots another alias");
break;
}
}
none. { }
}
}
none. { }
}
}
if mut_alias_to_root {
cx.tcx.sess.span_err(args[arg].span,
"passing a mutable alias to a variable \
that roots another alias");
i += 1u;
}
}
ret restricts;
@ -261,11 +280,15 @@ fn check_alt(cx: &ctx, input: &@ast::expr, arms: &[ast::arm], sc: &scope,
new_sc =
@(*sc +
[@{root_var: root_var,
// FIXME need to use separate restrict for each binding
node_id: 0,
ty: ty::mk_int(cx.tcx),
local_id: cx.next_local,
bindings: dnums,
unsafe_ty: inner_mut(root.ds),
depends_on: deps(sc, root_var),
mutable ok: valid}]);
mutable ok: valid,
mutable given_up: false}]);
}
register_locals(cx, a.pats[0]);
visit::visit_arm(a, new_sc, v);
@ -292,23 +315,28 @@ fn check_for(cx: &ctx, local: &@ast::local, seq: &@ast::expr, blk: &ast::blk,
// If this is a mutable vector, don't allow it to be touched.
let seq_t = ty::expr_ty(cx.tcx, seq);
let elt_t;
alt ty::struct(cx.tcx, seq_t) {
ty::ty_vec(mt) { if mt.mut != ast::imm { unsafe = some(seq_t); } }
ty::ty_str. {/* no-op */ }
_ {
cx.tcx.sess.span_unimpl(seq.span,
"unknown seq type " +
util::ppaux::ty_to_str(cx.tcx, seq_t));
ty::ty_vec(mt) {
if mt.mut != ast::imm { unsafe = some(seq_t); }
elt_t = mt.ty;
}
ty::ty_str. {
elt_t = ty::mk_mach(cx.tcx, ast::ty_u8);
}
}
let root_var = path_def_id(cx, root.ex);
let new_sc =
@{root_var: root_var,
// FIXME reenable when trans knows how to copy for vars
node_id: 0, // blk.node.id,
ty: elt_t,
local_id: cx.next_local,
bindings: ast_util::pat_binding_ids(local.node.pat),
unsafe_ty: unsafe,
depends_on: deps(sc, root_var),
mutable ok: valid};
mutable ok: valid,
mutable given_up: false};
register_locals(cx, local.node.pat);
visit::visit_block(blk, @(*sc + [new_sc]), v);
}
@ -322,7 +350,6 @@ fn check_var(cx: &ctx, ex: &@ast::expr, p: &ast::path, id: ast::node_id,
alt cx.local_map.find(my_defnum) { some(local(id)) { id } _ { 0u } };
let var_t = ty::expr_ty(cx.tcx, ex);
for r: restrict in *sc {
// excludes variables introduced since the alias was made
if my_local_id < r.local_id {
alt r.unsafe_ty {
@ -364,17 +391,15 @@ fn test_scope(cx: &ctx, sc: &scope, r: &restrict, p: &ast::path) {
if prob != valid { break; }
prob = sc[dep].ok;
}
if prob != valid {
let msg =
alt prob {
overwritten(sp, wpt) {
{span: sp, msg: "overwriting " + ast_util::path_name(wpt)}
}
val_taken(sp, vpt) {
{span: sp,
msg: "taking the value of " + ast_util::path_name(vpt)}
}
};
if prob != valid && cant_copy(cx, r) {
let msg = alt prob {
overwritten(sp, wpt) {
{span: sp, msg: "overwriting " + ast_util::path_name(wpt)}
}
val_taken(sp, vpt) {
{span: sp, msg: "taking the value of " + ast_util::path_name(vpt)}
}
};
cx.tcx.sess.span_err(msg.span,
msg.msg + " will invalidate alias " +
ast_util::path_name(p) +

View File

@ -68,10 +68,14 @@ fn expr_root(tcx: &ty::ctxt, ex: @expr, autoderef: bool) ->
let auto_unbox = maybe_auto_unbox(tcx, ty::expr_ty(tcx, base));
alt ty::struct(tcx, auto_unbox.t) {
ty::ty_vec(mt) {
ds +=
[@{mut: mt.mut != imm,
kind: index,
outer_t: auto_unbox.t}];
ds += [@{mut: mt.mut != imm,
kind: index,
outer_t: auto_unbox.t}];
}
ty::ty_str. {
ds += [@{mut: false,
kind: index,
outer_t: auto_unbox.t}];
}
}
ds += auto_unbox.ds;

View File

@ -79,20 +79,9 @@ fn type_of(cx: &@crate_ctxt, sp: &span, t: ty::t)
fn type_of_explicit_args(cx: &@crate_ctxt, sp: &span, inputs: &[ty::arg]) ->
[TypeRef] {
let atys: [TypeRef] = [];
for arg: ty::arg in inputs {
let t: TypeRef = type_of_inner(cx, sp, arg.ty);
t =
alt arg.mode {
ty::mo_alias(_) { T_ptr(t) }
ty::mo_move. { T_ptr(t) }
_ {
if ty::type_is_structural(cx.tcx, arg.ty) {
T_ptr(t)
} else { t }
}
};
atys += [t];
let atys = [];
for arg in inputs {
atys += [T_ptr(type_of_inner(cx, sp, arg.ty))];
}
ret atys;
}
@ -2038,12 +2027,6 @@ fn copy_val(cx: &@block_ctxt, action: copy_action, dst: ValueRef,
fn copy_val_no_check(cx: &@block_ctxt, action: copy_action, dst: ValueRef,
src: ValueRef, t: ty::t) -> @block_ctxt {
let ccx = bcx_ccx(cx);
// FIXME this is just a clunky stopgap. we should do proper checking in an
// earlier pass.
if !ty::type_is_copyable(ccx.tcx, t) {
ccx.sess.span_fatal(cx.sp, "Copying a non-copyable type.");
}
if ty::type_is_scalar(ccx.tcx, t) || ty::type_is_native(ccx.tcx, t) {
Store(cx, src, dst);
ret cx;
@ -3387,14 +3370,12 @@ fn trans_bind_thunk(cx: &@local_ctxt, sp: &span, incoming_fty: ty::t,
for arg: option::t<@ast::expr> in args {
let out_arg = outgoing_args[outgoing_arg_index];
let llout_arg_ty = llout_arg_tys[outgoing_arg_index];
let is_val = out_arg.mode == ty::mo_val;
alt arg {
// Arg provided at binding time; thunk copies it from
// closure.
some(e) {
let e_ty = ty::expr_ty(cx.ccx.tcx, e);
let bound_arg =
GEP_tup_like(bcx, closure_ty, llclosure,
[0, abi::box_rc_field_body,
@ -3404,20 +3385,12 @@ fn trans_bind_thunk(cx: &@local_ctxt, sp: &span, incoming_fty: ty::t,
// If the type is parameterized, then we need to cast the
// type we actually have to the parameterized out type.
if ty::type_contains_params(cx.ccx.tcx, out_arg.ty) {
let ty =
if is_val { T_ptr(llout_arg_ty) } else { llout_arg_ty };
val = PointerCast(bcx, val, ty);
}
if is_val && (type_is_immediate(cx.ccx, e_ty) ||
ty::type_is_unique(cx.ccx.tcx, e_ty)) {
val = Load(bcx, val);
val = PointerCast(bcx, val, llout_arg_ty);
}
llargs += [val];
b += 1;
}
// Arg will be provided when the thunk is invoked.
none. {
let arg: ValueRef = llvm::LLVMGetParam(llthunk, a);
@ -3543,27 +3516,22 @@ fn trans_arg_expr(cx: &@block_ctxt, arg: &ty::arg, lldestty0: TypeRef,
// be inspected. It's important for the value
// to have type lldestty0 (the callee's expected type).
val = llvm::LLVMGetUndef(lldestty0);
} else if arg.mode == ty::mo_val {
if ty::type_is_vec(ccx.tcx, e_ty) {
let r = do_spill(bcx, Load(bcx, val), e_ty);
bcx = r.bcx;
let arg_copy = r.val;
bcx = take_ty(bcx, arg_copy, e_ty);
val = Load(bcx, arg_copy);
add_clean_temp(bcx, arg_copy, e_ty);
} else if !lv.is_mem {
// Do nothing for non-vector temporaries; just give them to the
// callee.
} else if type_is_structural_or_param(ccx.tcx, e_ty) {
let dst = alloc_ty(bcx, e_ty);
bcx = copy_val(dst.bcx, INIT, dst.val, val, e_ty);
val = dst.val;
add_clean_temp(bcx, val, e_ty);
} else {
bcx = take_ty(bcx, val, e_ty);
val = load_if_immediate(bcx, val, e_ty);
add_clean_temp(bcx, val, e_ty);
} else if arg.mode == ty::mo_val || arg.mode == ty::mo_alias(false) {
let copied = false;
if !lv.is_mem && type_is_immediate(ccx, e_ty) {
val = do_spill_noroot(bcx, val);
copied = true;
}
if ccx.copy_map.contains_key(e.id) && lv.is_mem {
if !copied {
let alloc = alloc_ty(bcx, e_ty);
bcx = copy_val(alloc.bcx, INIT, alloc.val,
load_if_immediate(alloc.bcx, val, e_ty), e_ty);
val = alloc.val;
} else {
bcx = take_ty(bcx, val, e_ty);
}
add_clean(bcx, val, e_ty);
}
} else if type_is_immediate(ccx, e_ty) && !lv.is_mem {
let r = do_spill(bcx, val, e_ty);
@ -4768,7 +4736,7 @@ fn mk_standard_basic_blocks(llfn: ValueRef) ->
da: BasicBlockRef,
rt: BasicBlockRef} {
ret {sa:
str::as_buf("statuc_allocas",
str::as_buf("static_allocas",
{|buf| llvm::LLVMAppendBasicBlock(llfn, buf) }),
ca:
str::as_buf("copy_args",
@ -4892,33 +4860,25 @@ fn create_llargs_for_fn_args(cx: &@fn_ctxt, proto: ast::proto,
}
fn copy_args_to_allocas(fcx: @fn_ctxt, scope: @block_ctxt, args: &[ast::arg],
arg_tys: &[ty::arg]) {
arg_tys: &[ty::arg], ignore_mut: bool) {
let llcopyargs = new_raw_block_ctxt(fcx, fcx.llcopyargs);
let bcx = llcopyargs;
let arg_n: uint = 0u;
for aarg: ast::arg in args {
let arg_ty = arg_tys[arg_n].ty;
alt aarg.mode {
ast::val. {
// Structural types are passed by pointer, and we use the
// pointed-to memory for the local.
if !type_is_structural_or_param(fcx_tcx(fcx), arg_ty) {
// Overwrite the llargs entry for this arg with its alloca.
let aval = bcx.fcx.llargs.get(aarg.id);
let r = do_spill(bcx, aval, arg_ty);
bcx = r.bcx;
let addr = r.val;
bcx.fcx.llargs.insert(aarg.id, addr);
// Args that are locally assigned to need to do a local
// take/drop
if fcx.lcx.ccx.mut_map.contains_key(aarg.id) {
bcx = take_ty(bcx, addr, arg_ty);
add_clean(scope, addr, arg_ty);
}
ast::val. | ast::alias(false) {
let mutated = !ignore_mut &&
fcx.lcx.ccx.mut_map.contains_key(aarg.id);
// Overwrite the llargs entry for locally mutated params
// with a local alloca.
if mutated {
let aptr = bcx.fcx.llargs.get(aarg.id);
let {bcx, val: alloc} = alloc_ty(bcx, arg_ty);
bcx = copy_val(bcx, INIT, alloc,
load_if_immediate(bcx, aptr, arg_ty), arg_ty);
bcx.fcx.llargs.insert(aarg.id, alloc);
add_clean(scope, alloc, arg_ty);
}
}
ast::move. {
@ -5027,7 +4987,7 @@ fn trans_closure(bcx_maybe: &option::t<@block_ctxt>,
let block_ty = node_id_type(cx.ccx, f.body.node.id);
let arg_tys = arg_tys_of_fn(fcx.lcx.ccx, id);
copy_args_to_allocas(fcx, bcx, f.decl.inputs, arg_tys);
copy_args_to_allocas(fcx, bcx, f.decl.inputs, arg_tys, false);
// Figure out if we need to build a closure and act accordingly
let res =
@ -5175,7 +5135,7 @@ fn trans_tag_variant(cx: @local_ctxt, tag_id: ast::node_id,
}
let arg_tys = arg_tys_of_fn(cx.ccx, variant.node.id);
let bcx = new_top_block_ctxt(fcx);
copy_args_to_allocas(fcx, bcx, fn_args, arg_tys);
copy_args_to_allocas(fcx, bcx, fn_args, arg_tys, true);
let lltop = bcx.llbb;
// Cast the tag to a type we can GEP into.
@ -5389,9 +5349,15 @@ fn create_main_wrapper(ccx: &@crate_ctxt, sp: &span, main_llfn: ValueRef,
let lloutputarg = llvm::LLVMGetParam(llfdecl, 0u);
let lltaskarg = llvm::LLVMGetParam(llfdecl, 1u);
let llenvarg = llvm::LLVMGetParam(llfdecl, 2u);
let llargvarg = llvm::LLVMGetParam(llfdecl, 3u);
let args = [lloutputarg, lltaskarg, llenvarg];
if takes_argv { args += [llargvarg]; }
if takes_argv {
let llargvarg = llvm::LLVMGetParam(llfdecl, 3u);
// The runtime still passes the arg vector by value, this kludge
// makes sure it becomes a pointer (to a pointer to a vec).
let minus_ptr = llvm::LLVMGetElementType(val_ty(llargvarg));
llargvarg = PointerCast(bcx, llargvarg, minus_ptr);
args += [do_spill_noroot(bcx, llargvarg)];
}
FastCall(bcx, main_llfn, args);
build_return(bcx);
@ -5616,6 +5582,9 @@ fn decl_native_fn_and_pair(ccx: &@crate_ctxt, sp: &span, path: &[str],
let i = arg_n;
for arg: ty::arg in args {
let llarg = llvm::LLVMGetParam(fcx.llfn, i);
if arg.mode == ty::mo_val {
llarg = load_if_immediate(bcx, llarg, arg.ty);
}
assert (llarg as int != 0);
if cast_to_i32 {
let llarg_i32 = convert_arg_to_i32(bcx, llarg, arg.ty, arg.mode);
@ -5981,8 +5950,8 @@ fn write_abi_version(ccx: &@crate_ctxt) {
}
fn trans_crate(sess: &session::session, crate: &@ast::crate, tcx: &ty::ctxt,
output: &str, amap: &ast_map::map, mut_map: mut::mut_map) ->
ModuleRef {
output: &str, amap: &ast_map::map, mut_map: mut::mut_map,
copy_map: alias::copy_map) -> ModuleRef {
let llmod =
str::as_buf("rust_out", {|buf|
llvm::LLVMModuleCreateWithNameInContext(
@ -6039,6 +6008,7 @@ fn trans_crate(sess: &session::session, crate: &@ast::crate, tcx: &ty::ctxt,
type_short_names: short_names,
tcx: tcx,
mut_map: mut_map,
copy_map: copy_map,
stats:
{mutable n_static_tydescs: 0u,
mutable n_derived_tydescs: 0u,

View File

@ -144,6 +144,7 @@ type crate_ctxt =
type_short_names: hashmap<ty::t, str>,
tcx: ty::ctxt,
mut_map: mut::mut_map,
copy_map: alias::copy_map,
stats: stats,
upcalls: @upcall::upcalls,
rust_object_type: TypeRef,

View File

@ -63,7 +63,7 @@ fn trans_obj(cx: @local_ctxt, sp: &span, ob: &ast::_obj,
ty::ret_ty_of_fn(ccx.tcx, ctor_id), fn_args,
ty_params);
let arg_tys: [ty::arg] = arg_tys_of_fn(ccx, ctor_id);
copy_args_to_allocas(fcx, bcx, fn_args, arg_tys);
copy_args_to_allocas(fcx, bcx, fn_args, arg_tys, true);
// Pick up the type of this object by looking at our own output type, that
// is, the output type of the object constructor we're building.

View File

@ -155,6 +155,7 @@ export type_is_box;
export type_is_boxed;
export type_is_vec;
export type_is_fp;
export type_allows_implicit_copy;
export type_is_integral;
export type_is_native;
export type_is_nil;
@ -167,6 +168,7 @@ export type_is_copyable;
export type_is_tup_like;
export type_is_str;
export type_is_unique;
export type_structurally_contains_uniques;
export type_autoderef;
export type_param;
export unify;
@ -1161,6 +1163,34 @@ pure fn type_has_dynamic_size(cx: &ctxt, ty: t) -> bool {
}
}
// Returns true for types where a copy of a value can be distinguished from
// the value itself. I.e. types with mutable content that's not shared through
// a pointer.
fn type_allows_implicit_copy(cx: &ctxt, ty: t) -> bool {
ret !type_structurally_contains(cx, ty, fn(sty: &sty) -> bool {
ret alt sty {
ty_param(_, _) { true }
ty_vec(mt) { mt.mut != ast::imm }
ty_rec(fields) {
for field in fields { if field.mt.mut != ast::imm { ret true; } }
false
}
_ { false }
};
});
}
fn type_structurally_contains_uniques(cx: &ctxt, ty: t) -> bool {
ret type_structurally_contains(cx, ty, fn(sty: &sty) -> bool {
ret alt sty {
ty_uniq(_) { ret true; }
ty_vec(_) { true }
ty_str. { true }
_ { ret false; }
};
});
}
fn type_is_integral(cx: &ctxt, ty: t) -> bool {
alt struct(cx, ty) {
ty_int. { ret true; }