From b38d7f65388fabf383d3e3a4e4c3a56403f57542 Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Thu, 29 Nov 2012 16:41:39 -0800 Subject: [PATCH] Remove more uses of #[merge] --- src/librustc/middle/typeck/check.rs | 2905 ---------------------- src/librustc/middle/typeck/check/mod.rs | 2906 +++++++++++++++++++++++ src/librustc/middle/typeck/infer.rs | 745 ------ src/librustc/middle/typeck/infer/mod.rs | 746 ++++++ src/librustc/middle/typeck/mod.rs | 4 +- 5 files changed, 3654 insertions(+), 3652 deletions(-) delete mode 100644 src/librustc/middle/typeck/check.rs delete mode 100644 src/librustc/middle/typeck/infer.rs diff --git a/src/librustc/middle/typeck/check.rs b/src/librustc/middle/typeck/check.rs deleted file mode 100644 index 5807e338ebd..00000000000 --- a/src/librustc/middle/typeck/check.rs +++ /dev/null @@ -1,2905 +0,0 @@ -/* - -# check.rs - -Within the check phase of type check, we check each item one at a time -(bodies of function expressions are checked as part of the containing -function). Inference is used to supply types wherever they are -unknown. - -By far the most complex case is checking the body of a function. This -can be broken down into several distinct phases: - -- gather: creates type variables to represent the type of each local - variable and pattern binding. - -- main: the main pass does the lion's share of the work: it - determines the types of all expressions, resolves - methods, checks for most invalid conditions, and so forth. In - some cases, where a type is unknown, it may create a type or region - variable and use that as the type of an expression. - - In the process of checking, various constraints will be placed on - these type variables through the subtyping relationships requested - through the `demand` module. The `typeck::infer` module is in charge - of resolving those constraints. - -- regionck: after main is complete, the regionck pass goes over all - types looking for regions and making sure that they did not escape - into places they are not in scope. This may also influence the - final assignments of the various region variables if there is some - flexibility. - -- vtable: find and records the impls to use for each trait bound that - appears on a type parameter. - -- writeback: writes the final types within a function body, replacing - type variables with their final inferred types. These final types - are written into the `tcx.node_types` table, which should *never* contain - any reference to a type variable. - -## Intermediate types - -While type checking a function, the intermediate types for the -expressions, blocks, and so forth contained within the function are -stored in `fcx.node_types` and `fcx.node_type_substs`. These types -may contain unresolved type variables. After type checking is -complete, the functions in the writeback module are used to take the -types from this table, resolve them, and then write them into their -permanent home in the type context `ccx.tcx`. - -This means that during inferencing you should use `fcx.write_ty()` -and `fcx.expr_ty()` / `fcx.node_ty()` to write/obtain the types of -nodes within the function. - -The types of top-level items, which never contain unbound type -variables, are stored directly into the `tcx` tables. - -n.b.: A type variable is not the same thing as a type parameter. A -type variable is rather an "instance" of a type parameter: that is, -given a generic function `fn foo(t: T)`: while checking the -function `foo`, the type `ty_param(0)` refers to the type `T`, which -is treated in abstract. When `foo()` is called, however, `T` will be -substituted for a fresh type variable `N`. This variable will -eventually be resolved to some concrete type (which might itself be -type parameter). - -*/ - -use astconv::{ast_conv, ast_path_to_ty, ast_ty_to_ty}; -use astconv::{ast_region_to_region}; -use middle::ty::{TyVid, vid, FnTyBase, FnMeta, FnSig}; -use regionmanip::{replace_bound_regions_in_fn_ty}; -use rscope::{anon_rscope, binding_rscope, empty_rscope, in_anon_rscope}; -use rscope::{in_binding_rscope, region_scope, type_rscope, - bound_self_region}; -use syntax::ast::ty_i; -use typeck::infer::{resolve_type, force_tvar}; -use result::{Result, Ok, Err}; -use syntax::print::pprust; -use syntax::parse::token::special_idents; -use vtable::{LocationInfo, VtableContext}; - -use std::map::HashMap; - -type self_info = { - self_ty: ty::t, - self_id: ast::node_id, - def_id: ast::def_id, - explicit_self: ast::self_ty -}; - -/// Fields that are part of a `fn_ctxt` which are inherited by -/// closures defined within the function. For example: -/// -/// fn foo() { -/// do bar() { ... } -/// } -/// -/// Here, the function `foo()` and the closure passed to -/// `bar()` will each have their own `fn_ctxt`, but they will -/// share the inherited fields. -struct inherited { - infcx: infer::infer_ctxt, - locals: HashMap, - node_types: HashMap, - node_type_substs: HashMap, - adjustments: HashMap -} - -struct fn_ctxt { - // var_bindings, locals and next_var_id are shared - // with any nested functions that capture the environment - // (and with any functions whose environment is being captured). - self_impl_def_id: Option, - ret_ty: ty::t, - // Used by loop bodies that return from the outer function - indirect_ret_ty: Option, - purity: ast::purity, - - // Sometimes we generate region pointers where the precise region - // to use is not known. For example, an expression like `&x.f` - // where `x` is of type `@T`: in this case, we will be rooting - // `x` onto the stack frame, and we could choose to root it until - // the end of (almost) any enclosing block or expression. We - // want to pick the narrowest block that encompasses all uses. - // - // What we do in such cases is to generate a region variable with - // `region_lb` as a lower bound. The regionck pass then adds - // other constriants based on how the variable is used and region - // inference selects the ultimate value. Finally, borrowck is - // charged with guaranteeing that the value whose address was taken - // can actually be made to live as long as it needs to live. - mut region_lb: ast::node_id, - - in_scope_regions: isr_alist, - - inh: @inherited, - - ccx: @crate_ctxt, -} - -fn blank_inherited(ccx: @crate_ctxt) -> @inherited { - @inherited { - infcx: infer::new_infer_ctxt(ccx.tcx), - locals: HashMap(), - node_types: map::HashMap(), - node_type_substs: map::HashMap(), - adjustments: map::HashMap() - } -} - -// Used by check_const and check_enum_variants -fn blank_fn_ctxt(ccx: @crate_ctxt, rty: ty::t, - region_bnd: ast::node_id) -> @fn_ctxt { -// It's kind of a kludge to manufacture a fake function context -// and statement context, but we might as well do write the code only once - @fn_ctxt { - self_impl_def_id: None, - ret_ty: rty, - indirect_ret_ty: None, - purity: ast::pure_fn, - mut region_lb: region_bnd, - in_scope_regions: @Nil, - inh: blank_inherited(ccx), - ccx: ccx - } -} - -fn check_item_types(ccx: @crate_ctxt, crate: @ast::crate) { - let visit = visit::mk_simple_visitor(@{ - visit_item: |a| check_item(ccx, a), - .. *visit::default_simple_visitor() - }); - visit::visit_crate(*crate, (), visit); -} - -fn check_bare_fn(ccx: @crate_ctxt, - decl: ast::fn_decl, - body: ast::blk, - id: ast::node_id, - self_info: Option) { - let fty = ty::node_id_to_type(ccx.tcx, id); - match ty::get(fty).sty { - ty::ty_fn(ref fn_ty) => { - check_fn(ccx, self_info, fn_ty, decl, body, false, None) - } - _ => ccx.tcx.sess.impossible_case(body.span, - "check_bare_fn: function type expected") - } -} - -fn check_fn(ccx: @crate_ctxt, - self_info: Option, - fn_ty: &ty::FnTy, - decl: ast::fn_decl, - body: ast::blk, - indirect_ret: bool, - old_fcx: Option<@fn_ctxt>) { - - let tcx = ccx.tcx; - - // ______________________________________________________________________ - // First, we have to replace any bound regions in the fn and self - // types with free ones. The free region references will be bound - // the node_id of the body block. - - let {isr, self_info, fn_ty} = { - let old_isr = option::map_default(&old_fcx, @Nil, - |fcx| fcx.in_scope_regions); - replace_bound_regions_in_fn_ty(tcx, old_isr, self_info, fn_ty, - |br| ty::re_free(body.node.id, br)) - }; - - let arg_tys = fn_ty.sig.inputs.map(|a| a.ty); - let ret_ty = fn_ty.sig.output; - - debug!("check_fn(arg_tys=%?, ret_ty=%?, self_info.self_ty=%?)", - arg_tys.map(|a| ty_to_str(tcx, *a)), - ty_to_str(tcx, ret_ty), - option::map(&self_info, |s| ty_to_str(tcx, s.self_ty))); - - // ______________________________________________________________________ - // Create the function context. This is either derived from scratch or, - // in the case of function expressions, based on the outer context. - let fcx: @fn_ctxt = { - let (purity, inherited) = match old_fcx { - None => { - (fn_ty.meta.purity, - blank_inherited(ccx)) - } - Some(fcx) => { - (ty::determine_inherited_purity(fcx.purity, fn_ty.meta.purity, - fn_ty.meta.proto), - fcx.inh) - } - }; - - let indirect_ret_ty = if indirect_ret { - let ofcx = old_fcx.get(); - match ofcx.indirect_ret_ty { - Some(t) => Some(t), - None => Some(ofcx.ret_ty) - } - } else { None }; - - @fn_ctxt { - self_impl_def_id: self_info.map(|self_info| self_info.def_id), - ret_ty: ret_ty, - indirect_ret_ty: indirect_ret_ty, - purity: purity, - mut region_lb: body.node.id, - in_scope_regions: isr, - inh: inherited, - ccx: ccx - } - }; - - // Update the self_info to contain an accurate self type (taking - // into account explicit self). - let self_info = do self_info.chain_ref |self_info| { - // If the self type is sty_static, we don't have a self ty. - if self_info.explicit_self.node == ast::sty_static { - None - } else { - let self_region = fcx.in_scope_regions.find(ty::br_self); - let ty = method::transform_self_type_for_method( - fcx.tcx(), self_region, - self_info.self_ty, self_info.explicit_self.node); - Some({self_ty: ty,.. *self_info}) - } - }; - - gather_locals(fcx, decl, body, arg_tys, self_info); - check_block(fcx, body); - - // We unify the tail expr's type with the - // function result type, if there is a tail expr. - match body.node.expr { - Some(tail_expr) => { - let tail_expr_ty = fcx.expr_ty(tail_expr); - demand::suptype(fcx, tail_expr.span, fcx.ret_ty, tail_expr_ty); - } - None => () - } - - for self_info.each |self_info| { - fcx.write_ty(self_info.self_id, self_info.self_ty); - } - for vec::each2(decl.inputs, arg_tys) |input, arg| { - fcx.write_ty(input.id, *arg); - } - - // If we don't have any enclosing function scope, it is time to - // force any remaining type vars to be resolved. - // If we have an enclosing function scope, our type variables will be - // resolved when the enclosing scope finishes up. - if old_fcx.is_none() { - vtable::resolve_in_block(fcx, body); - regionck::regionck_fn(fcx, decl, body); - writeback::resolve_type_vars_in_fn(fcx, decl, body, self_info); - } - - fn gather_locals(fcx: @fn_ctxt, - decl: ast::fn_decl, - body: ast::blk, - arg_tys: ~[ty::t], - self_info: Option) { - let tcx = fcx.ccx.tcx; - - let assign = fn@(span: span, nid: ast::node_id, - ty_opt: Option) { - let var_id = fcx.infcx().next_ty_var_id(); - fcx.inh.locals.insert(nid, var_id); - match ty_opt { - None => {/* nothing to do */ } - Some(typ) => { - infer::mk_eqty(fcx.infcx(), false, span, - ty::mk_var(tcx, var_id), typ); - } - } - }; - - // Add the self parameter - for self_info.each |self_info| { - assign(self_info.explicit_self.span, - self_info.self_id, - Some(self_info.self_ty)); - debug!("self is assigned to %s", - fcx.inh.locals.get(self_info.self_id).to_str()); - } - - // Add formal parameters. - for vec::each2(arg_tys, decl.inputs) |arg_ty, input| { - // Create type variables for each argument. - do pat_util::pat_bindings(tcx.def_map, input.pat) - |_bm, pat_id, _sp, _path| { - assign(input.ty.span, pat_id, None); - } - - // Check the pattern. - let region = fcx.block_region(); - let pcx = { - fcx: fcx, - map: pat_id_map(tcx.def_map, input.pat), - alt_region: region, - block_region: region, - }; - alt::check_pat(pcx, input.pat, *arg_ty); - } - - // Add explicitly-declared locals. - let visit_local = fn@(local: @ast::local, - &&e: (), v: visit::vt<()>) { - let o_ty = match local.node.ty.node { - ast::ty_infer => None, - _ => Some(fcx.to_ty(local.node.ty)) - }; - assign(local.span, local.node.id, o_ty); - debug!("Local variable %s is assigned to %s", - pat_to_str(local.node.pat, tcx.sess.intr()), - fcx.inh.locals.get(local.node.id).to_str()); - visit::visit_local(local, e, v); - }; - - // Add pattern bindings. - let visit_pat = fn@(p: @ast::pat, &&e: (), v: visit::vt<()>) { - match p.node { - ast::pat_ident(_, path, _) - if pat_util::pat_is_binding(fcx.ccx.tcx.def_map, p) => { - assign(p.span, p.id, None); - debug!("Pattern binding %s is assigned to %s", - tcx.sess.str_of(path.idents[0]), - fcx.inh.locals.get(p.id).to_str()); - } - _ => {} - } - visit::visit_pat(p, e, v); - }; - - let visit_block = fn@(b: ast::blk, &&e: (), v: visit::vt<()>) { - // non-obvious: the `blk` variable maps to region lb, so - // we have to keep this up-to-date. This - // is... unfortunate. It'd be nice to not need this. - do fcx.with_region_lb(b.node.id) { - visit::visit_block(b, e, v); - } - }; - - // Don't descend into fns and items - fn visit_fn(_fk: visit::fn_kind, _decl: ast::fn_decl, - _body: ast::blk, _sp: span, - _id: ast::node_id, &&_t: (), _v: visit::vt<()>) { - } - fn visit_item(_i: @ast::item, &&_e: (), _v: visit::vt<()>) { } - - let visit = visit::mk_vt(@{visit_local: visit_local, - visit_pat: visit_pat, - visit_fn: visit_fn, - visit_item: visit_item, - visit_block: visit_block, - .. *visit::default_visitor()}); - - visit.visit_block(body, (), visit); - } -} - -fn check_method(ccx: @crate_ctxt, method: @ast::method, - self_ty: ty::t, self_impl_def_id: ast::def_id) { - let self_info = {self_ty: self_ty, - self_id: method.self_id, - def_id: self_impl_def_id, - explicit_self: method.self_ty }; - check_bare_fn(ccx, method.decl, method.body, method.id, Some(self_info)); -} - -fn check_no_duplicate_fields(tcx: ty::ctxt, fields: - ~[(ast::ident, span)]) { - let field_names = HashMap(); - - for fields.each |p| { - let (id, sp) = *p; - match field_names.find(id) { - Some(orig_sp) => { - tcx.sess.span_err(sp, fmt!("Duplicate field \ - name %s in record type declaration", - tcx.sess.str_of(id))); - tcx.sess.span_note(orig_sp, ~"First declaration of \ - this field occurred here"); - break; - } - None => { - field_names.insert(id, sp); - } - } - } - -} - -fn check_struct(ccx: @crate_ctxt, struct_def: @ast::struct_def, - id: ast::node_id, span: span) { - let tcx = ccx.tcx; - let self_ty = ty::node_id_to_type(tcx, id); - - do option::iter(&struct_def.dtor) |dtor| { - let class_t = {self_ty: self_ty, - self_id: dtor.node.self_id, - def_id: local_def(id), - explicit_self: {node: ast::sty_by_ref, - span: ast_util::dummy_sp()}}; - // typecheck the dtor - check_bare_fn(ccx, ast_util::dtor_dec(), - dtor.node.body, dtor.node.id, - Some(class_t)); - }; - - // typecheck the methods - for struct_def.methods.each |m| { - check_method(ccx, *m, self_ty, local_def(id)); - } - // Check that the class is instantiable - check_instantiable(ccx.tcx, span, id); -} - -fn check_item(ccx: @crate_ctxt, it: @ast::item) { - debug!("check_item(it.id=%d, it.ident=%s)", - it.id, - ty::item_path_str(ccx.tcx, local_def(it.id))); - let _indenter = indenter(); - - match it.node { - ast::item_const(_, e) => check_const(ccx, it.span, e, it.id), - ast::item_enum(enum_definition, _) => { - check_enum_variants(ccx, it.span, enum_definition.variants, it.id); - } - ast::item_fn(decl, _, _, body) => { - check_bare_fn(ccx, decl, body, it.id, None); - } - ast::item_impl(_, _, ty, ms) => { - let rp = ccx.tcx.region_paramd_items.find(it.id); - debug!("item_impl %s with id %d rp %?", - ccx.tcx.sess.str_of(it.ident), it.id, rp); - let self_ty = ccx.to_ty(rscope::type_rscope(rp), ty); - for ms.each |m| { - check_method(ccx, *m, self_ty, local_def(it.id)); - } - } - ast::item_trait(_, _, trait_methods) => { - for trait_methods.each |trait_method| { - match *trait_method { - required(*) => { - // Nothing to do, since required methods don't have - // bodies to check. - } - provided(m) => { - check_method(ccx, m, ty::mk_self(ccx.tcx), local_def(it.id)); - } - } - } - } - ast::item_class(struct_def, _) => { - check_struct(ccx, struct_def, it.id, it.span); - } - ast::item_ty(t, tps) => { - let tpt_ty = ty::node_id_to_type(ccx.tcx, it.id); - check_bounds_are_used(ccx, t.span, tps, tpt_ty); - // If this is a record ty, check for duplicate fields - match t.node { - ast::ty_rec(fields) => { - check_no_duplicate_fields(ccx.tcx, fields.map(|f| - (f.node.ident, f.span))); - } - _ => () - } - } - ast::item_foreign_mod(m) => { - if syntax::attr::foreign_abi(it.attrs) == - either::Right(ast::foreign_abi_rust_intrinsic) { - for m.items.each |item| { - check_intrinsic_type(ccx, *item); - } - } else { - for m.items.each |item| { - let tpt = ty::lookup_item_type(ccx.tcx, local_def(item.id)); - if (*tpt.bounds).is_not_empty() { - ccx.tcx.sess.span_err( - item.span, - fmt!("foreign items may not have type parameters")); - } - } - } - } - _ => {/* nothing to do */ } - } -} - -impl @fn_ctxt: ast_conv { - fn infcx() -> infer::infer_ctxt { self.inh.infcx } - fn tcx() -> ty::ctxt { self.ccx.tcx } - fn ccx() -> @crate_ctxt { self.ccx } - - fn get_item_ty(id: ast::def_id) -> ty::ty_param_bounds_and_ty { - ty::lookup_item_type(self.tcx(), id) - } - - fn ty_infer(_span: span) -> ty::t { - self.infcx().next_ty_var() - } -} - -impl @fn_ctxt { - fn search_in_scope_regions(br: ty::bound_region) - -> Result - { - match self.in_scope_regions.find(br) { - Some(r) => result::Ok(r), - None => { - let blk_br = ty::br_named(special_idents::blk); - if br == blk_br { - result::Ok(self.block_region()) - } else { - result::Err(fmt!("named region `%s` not in scope here", - bound_region_to_str(self.tcx(), br))) - } - } - } - } -} - -impl @fn_ctxt: region_scope { - fn anon_region(span: span) -> Result { - result::Ok(self.infcx().next_region_var_nb(span)) - } - fn self_region(_span: span) -> Result { - self.search_in_scope_regions(ty::br_self) - } - fn named_region(_span: span, id: ast::ident) -> Result { - self.search_in_scope_regions(ty::br_named(id)) - } -} - -impl @fn_ctxt { - fn tag() -> ~str { fmt!("%x", ptr::addr_of(&(*self)) as uint) } - - fn expr_to_str(expr: @ast::expr) -> ~str { - fmt!("expr(%?:%s)", expr.id, - pprust::expr_to_str(expr, self.tcx().sess.intr())) - } - - fn block_region() -> ty::Region { - ty::re_scope(self.region_lb) - } - - #[inline(always)] - fn write_ty(node_id: ast::node_id, ty: ty::t) { - debug!("write_ty(%d, %s) in fcx %s", - node_id, ty_to_str(self.tcx(), ty), self.tag()); - self.inh.node_types.insert(node_id, ty); - } - - fn write_substs(node_id: ast::node_id, +substs: ty::substs) { - if !ty::substs_is_noop(&substs) { - debug!("write_substs(%d, %s) in fcx %s", - node_id, - ty::substs_to_str(self.tcx(), &substs), - self.tag()); - self.inh.node_type_substs.insert(node_id, substs); - } - } - - fn write_ty_substs(node_id: ast::node_id, ty: ty::t, - +substs: ty::substs) { - let ty = ty::subst(self.tcx(), &substs, ty); - self.write_ty(node_id, ty); - self.write_substs(node_id, substs); - } - - fn write_autoderef_adjustment(node_id: ast::node_id, derefs: uint) { - if derefs == 0 { return; } - self.write_adjustment(node_id, @{autoderefs: derefs, autoref: None}); - } - - fn write_adjustment(node_id: ast::node_id, adj: @ty::AutoAdjustment) { - debug!("write_adjustment(node_id=%?, adj=%?)", node_id, adj); - self.inh.adjustments.insert(node_id, adj); - } - - fn write_nil(node_id: ast::node_id) { - self.write_ty(node_id, ty::mk_nil(self.tcx())); - } - fn write_bot(node_id: ast::node_id) { - self.write_ty(node_id, ty::mk_bot(self.tcx())); - } - - fn to_ty(ast_t: @ast::Ty) -> ty::t { - ast_ty_to_ty(self, self, ast_t) - } - - fn expr_to_str(expr: @ast::expr) -> ~str { - expr_repr(self.tcx(), expr) - } - - fn expr_ty(ex: @ast::expr) -> ty::t { - match self.inh.node_types.find(ex.id) { - Some(t) => t, - None => { - self.tcx().sess.bug( - fmt!("no type for %s in fcx %s", - self.expr_to_str(ex), self.tag())); - } - } - } - fn node_ty(id: ast::node_id) -> ty::t { - match self.inh.node_types.find(id) { - Some(t) => t, - None => { - self.tcx().sess.bug( - fmt!("no type for node %d: %s in fcx %s", - id, ast_map::node_id_to_str( - self.tcx().items, id, - self.tcx().sess.parse_sess.interner), - self.tag())); - } - } - } - fn node_ty_substs(id: ast::node_id) -> ty::substs { - match self.inh.node_type_substs.find(id) { - Some(ts) => ts, - None => { - self.tcx().sess.bug( - fmt!("no type substs for node %d: %s in fcx %s", - id, ast_map::node_id_to_str( - self.tcx().items, id, - self.tcx().sess.parse_sess.interner), - self.tag())); - } - } - } - fn opt_node_ty_substs(id: ast::node_id) -> Option { - self.inh.node_type_substs.find(id) - } - - - fn mk_subty(a_is_expected: bool, span: span, - sub: ty::t, sup: ty::t) -> Result<(), ty::type_err> { - infer::mk_subty(self.infcx(), a_is_expected, span, sub, sup) - } - - fn can_mk_subty(sub: ty::t, sup: ty::t) -> Result<(), ty::type_err> { - infer::can_mk_subty(self.infcx(), sub, sup) - } - - fn mk_assignty(expr: @ast::expr, sub: ty::t, sup: ty::t) - -> Result<(), ty::type_err> - { - match infer::mk_assignty(self.infcx(), false, expr.span, sub, sup) { - Ok(None) => result::Ok(()), - Err(e) => result::Err(e), - Ok(Some(adjustment)) => { - self.write_adjustment(expr.id, adjustment); - Ok(()) - } - } - } - - fn can_mk_assignty(sub: ty::t, sup: ty::t) -> Result<(), ty::type_err> { - infer::can_mk_assignty(self.infcx(), sub, sup) - } - - fn mk_eqty(a_is_expected: bool, span: span, - sub: ty::t, sup: ty::t) -> Result<(), ty::type_err> { - infer::mk_eqty(self.infcx(), a_is_expected, span, sub, sup) - } - - fn mk_subr(a_is_expected: bool, span: span, - sub: ty::Region, sup: ty::Region) -> Result<(), ty::type_err> { - infer::mk_subr(self.infcx(), a_is_expected, span, sub, sup) - } - - fn require_unsafe(sp: span, op: ~str) { - match self.purity { - ast::unsafe_fn => {/*ok*/} - _ => { - self.ccx.tcx.sess.span_err( - sp, - fmt!("%s requires unsafe function or block", op)); - } - } - } - fn with_region_lb(lb: ast::node_id, f: fn() -> R) -> R { - let old_region_lb = self.region_lb; - self.region_lb = lb; - let v = f(); - self.region_lb = old_region_lb; - move v - } - - fn region_var_if_parameterized(rp: Option, - span: span, - lower_bound: ty::Region) - -> Option - { - rp.map( - |_rp| self.infcx().next_region_var_with_lb(span, lower_bound)) - } - - fn type_error_message(sp: span, mk_msg: fn(~str) -> ~str, - actual_ty: ty::t, err: Option<&ty::type_err>) { - self.infcx().type_error_message(sp, mk_msg, actual_ty, err); - } - - fn report_mismatched_types(sp: span, e: ty::t, a: ty::t, - err: &ty::type_err) { - self.infcx().report_mismatched_types(sp, e, a, err); - } - -} - -fn do_autoderef(fcx: @fn_ctxt, sp: span, t: ty::t) -> (ty::t, uint) { - /*! - * - * Autoderefs the type `t` as many times as possible, returning - * a new type and a counter for how many times the type was - * deref'd. If the counter is non-zero, the receiver is responsible - * for inserting an AutoAdjustment record into `tcx.adjustments` - * so that trans/borrowck/etc know about this autoderef. */ - - let mut t1 = t; - let mut enum_dids = ~[]; - let mut autoderefs = 0; - loop { - let sty = structure_of(fcx, sp, t1); - - // Some extra checks to detect weird cycles and so forth: - match sty { - ty::ty_box(inner) | ty::ty_uniq(inner) | - ty::ty_rptr(_, inner) => { - match ty::get(t1).sty { - ty::ty_infer(ty::TyVar(v1)) => { - ty::occurs_check(fcx.ccx.tcx, sp, v1, - ty::mk_box(fcx.ccx.tcx, inner)); - } - _ => () - } - } - ty::ty_enum(ref did, _) => { - // Watch out for a type like `enum t = @t`. Such a - // type would otherwise infinitely auto-deref. Only - // autoderef loops during typeck (basically, this one - // and the loops in typeck::check::method) need to be - // concerned with this, as an error will be reported - // on the enum definition as well because the enum is - // not instantiable. - if vec::contains(enum_dids, did) { - return (t1, autoderefs); - } - enum_dids.push(*did); - } - _ => { /*ok*/ } - } - - // Otherwise, deref if type is derefable: - match ty::deref_sty(fcx.ccx.tcx, &sty, false) { - None => { - return (t1, autoderefs); - } - Some(mt) => { - autoderefs += 1; - t1 = mt.ty - } - } - }; -} - -// AST fragment checking -fn check_lit(fcx: @fn_ctxt, lit: @ast::lit) -> ty::t { - let tcx = fcx.ccx.tcx; - - match lit.node { - ast::lit_str(*) => ty::mk_estr(tcx, ty::vstore_slice(ty::re_static)), - ast::lit_int(_, t) => ty::mk_mach_int(tcx, t), - ast::lit_uint(_, t) => ty::mk_mach_uint(tcx, t), - ast::lit_int_unsuffixed(_) => { - // An unsuffixed integer literal could have any integral type, - // so we create an integral type variable for it. - ty::mk_int_var(tcx, fcx.infcx().next_int_var_id()) - } - ast::lit_float(_, t) => ty::mk_mach_float(tcx, t), - ast::lit_float_unsuffixed(_) => { - // An unsuffixed floating point literal could have any floating point - // type, so we create a floating point type variable for it. - ty::mk_float_var(tcx, fcx.infcx().next_float_var_id()) - } - ast::lit_nil => ty::mk_nil(tcx), - ast::lit_bool(_) => ty::mk_bool(tcx) - } -} - -fn valid_range_bounds(ccx: @crate_ctxt, from: @ast::expr, to: @ast::expr) - -> bool { - const_eval::compare_lit_exprs(ccx.tcx, from, to) <= 0 -} - -fn check_expr_with(fcx: @fn_ctxt, expr: @ast::expr, expected: ty::t) -> bool { - check_expr(fcx, expr, Some(expected)) -} - -fn check_expr_with_assignability(fcx: @fn_ctxt, - expr: @ast::expr, - expected: ty::t) - -> bool { - do check_expr_with_unifier(fcx, expr, Some(expected)) { - demand::assign(fcx, expr.span, expected, expr) - } -} - - -fn check_expr(fcx: @fn_ctxt, expr: @ast::expr, - expected: Option) -> bool { - return do check_expr_with_unifier(fcx, expr, expected) { - for expected.each |t| { - demand::suptype(fcx, expr.span, *t, fcx.expr_ty(expr)); - } - }; -} - -// determine the `self` type, using fresh variables for all variables -// declared on the impl declaration e.g., `impl for ~[(A,B)]` -// would return ($0, $1) where $0 and $1 are freshly instantiated type -// variables. -fn impl_self_ty(vcx: &VtableContext, - location_info: &LocationInfo, // (potential) receiver for - // this impl - did: ast::def_id) -> ty_param_substs_and_ty { - let tcx = vcx.tcx(); - - let {n_tps, region_param, raw_ty} = if did.crate == ast::local_crate { - let region_param = tcx.region_paramd_items.find(did.node); - match tcx.items.find(did.node) { - Some(ast_map::node_item(@{node: ast::item_impl(ts, _, st, _), - _}, _)) => { - {n_tps: ts.len(), - region_param: region_param, - raw_ty: vcx.ccx.to_ty(rscope::type_rscope(region_param), st)} - } - Some(ast_map::node_item(@{node: ast::item_class(_, ts), - id: class_id, _},_)) => { - /* If the impl is a class, the self ty is just the class ty - (doing a no-op subst for the ty params; in the next step, - we substitute in fresh vars for them) - */ - {n_tps: ts.len(), - region_param: region_param, - raw_ty: ty::mk_class(tcx, local_def(class_id), - {self_r: rscope::bound_self_region(region_param), - self_ty: None, - tps: ty::ty_params_to_tys(tcx, ts)})} - } - _ => { tcx.sess.bug(~"impl_self_ty: unbound item or item that \ - doesn't have a self_ty"); } - } - } else { - let ity = ty::lookup_item_type(tcx, did); - {n_tps: vec::len(*ity.bounds), - region_param: ity.region_param, - raw_ty: ity.ty} - }; - - let self_r = if region_param.is_some() { - Some(vcx.infcx.next_region_var(location_info.span, - location_info.id)) - } else { - None - }; - let tps = vcx.infcx.next_ty_vars(n_tps); - - let substs = {self_r: self_r, self_ty: None, tps: tps}; - let substd_ty = ty::subst(tcx, &substs, raw_ty); - {substs: substs, ty: substd_ty} -} - -// Only for fields! Returns for methods> -// Indifferent to privacy flags -fn lookup_field_ty(tcx: ty::ctxt, - class_id: ast::def_id, - items: &[ty::field_ty], - fieldname: ast::ident, - substs: &ty::substs) -> Option { - - let o_field = vec::find(items, |f| f.ident == fieldname); - do o_field.map() |f| { - ty::lookup_field_type(tcx, class_id, f.id, substs) - } -} - -// Controls whether the arguments are automatically referenced. This is useful -// for overloaded binary and unary operators. -pub enum DerefArgs { - DontDerefArgs, - DoDerefArgs -} - -fn check_expr_with_unifier(fcx: @fn_ctxt, - expr: @ast::expr, - expected: Option, - unifier: fn()) -> bool { - - debug!(">> typechecking %s", fcx.expr_to_str(expr)); - - // A generic function to factor out common logic from call and - // overloaded operations - fn check_call_inner( - fcx: @fn_ctxt, - sp: span, - call_expr_id: ast::node_id, - in_fty: ty::t, - callee_expr: @ast::expr, - args: ~[@ast::expr], - deref_args: DerefArgs) -> {fty: ty::t, bot: bool} { - - let tcx = fcx.ccx.tcx; - let mut bot = false; - - // Replace all region parameters in the arguments and return - // type with fresh region variables. - - debug!("check_call_inner: before universal quant., in_fty=%s", - fcx.infcx().ty_to_str(in_fty)); - - let mut formal_tys; - - // This is subtle: we expect `fty` to be a function type, which - // normally introduce a level of binding. In this case, we want to - // process the types bound by the function but not by any nested - // functions. Therefore, we match one level of structure. - let fty = - match structure_of(fcx, sp, in_fty) { - ty::ty_fn(ref fn_ty) => { - let fn_ty = replace_bound_regions_in_fn_ty(tcx, @Nil, - None, fn_ty, |_br| fcx.infcx().next_region_var(sp, - call_expr_id)).fn_ty; - - let supplied_arg_count = args.len(); - - // Grab the argument types, supplying fresh type variables - // if the wrong number of arguments were supplied - let expected_arg_count = fn_ty.sig.inputs.len(); - formal_tys = if expected_arg_count == supplied_arg_count { - fn_ty.sig.inputs.map(|a| a.ty) - } else { - tcx.sess.span_err( - sp, fmt!("this function takes %u parameter%s but \ - %u parameter%s supplied", - expected_arg_count, - if expected_arg_count == 1 { - ~"" - } else { - ~"s" - }, - supplied_arg_count, - if supplied_arg_count == 1 { - ~" was" - } else { - ~"s were" - })); - fcx.infcx().next_ty_vars(supplied_arg_count) - }; - ty::mk_fn(tcx, fn_ty) - } - _ => { - fcx.type_error_message(sp, |actual| { - fmt!("expected function or foreign function but \ - found `%s`", actual) }, in_fty, None); - // check each arg against "error", in order to set up - // all the node type bindings - formal_tys = args.map(|_x| ty::mk_err(tcx)); - ty::mk_err(tcx) - } - }; - - debug!("check_call_inner: after universal quant., fty=%s", - fcx.infcx().ty_to_str(fty)); - - // Check the arguments. - // We do this in a pretty awful way: first we typecheck any arguments - // that are not anonymous functions, then we typecheck the anonymous - // functions. This is so that we have more information about the types - // of arguments when we typecheck the functions. This isn't really the - // right way to do this. - for [false, true].each |check_blocks| { - let check_blocks = *check_blocks; - debug!("check_blocks=%b", check_blocks); - - // More awful hacks: before we check the blocks, try to do - // an "opportunistic" vtable resolution of any trait - // bounds on the call. - if check_blocks { - vtable::early_resolve_expr(callee_expr, fcx, true); - } - - for args.eachi |i, arg| { - let is_block = match arg.node { - ast::expr_fn_block(*) | ast::expr_loop_body(*) | - ast::expr_do_body(*) => true, - _ => false - }; - - if is_block == check_blocks { - debug!("checking the argument"); - let mut formal_ty = formal_tys[i]; - - match deref_args { - DoDerefArgs => { - match ty::get(formal_ty).sty { - ty::ty_rptr(_, mt) => formal_ty = mt.ty, - _ => { - fcx.ccx.tcx.sess.span_bug(arg.span, - ~"no ref"); - } - } - } - DontDerefArgs => {} - } - - bot |= check_expr_with_assignability(fcx, - *arg, formal_ty); - fcx.write_ty(arg.id, fcx.expr_ty(*arg)); - - } - } - } - - {fty: fty, bot: bot} - } - - // A generic function for checking assignment expressions - fn check_assignment(fcx: @fn_ctxt, - lhs: @ast::expr, - rhs: @ast::expr, - id: ast::node_id) - -> bool { - let mut bot = check_expr(fcx, lhs, None); - let lhs_type = fcx.expr_ty(lhs); - bot |= check_expr_with_assignability(fcx, rhs, lhs_type); - fcx.write_ty(id, ty::mk_nil(fcx.ccx.tcx)); - return bot; - } - - // A generic function for doing all of the checking for call expressions - fn check_call(fcx: @fn_ctxt, sp: span, call_expr_id: ast::node_id, - f: @ast::expr, args: ~[@ast::expr]) -> bool { - - // Index expressions need to be handled seperately, to inform - // them that they appear in call position. - let mut bot = match f.node { - ast::expr_field(base, field, tys) => { - check_field(fcx, f, true, base, field, tys) - } - _ => check_expr(fcx, f, None) - }; - let fn_ty = fcx.expr_ty(f); - - // Call the generic checker. - let fty = { - let r = check_call_inner(fcx, sp, call_expr_id, - fn_ty, f, args, DontDerefArgs); - bot |= r.bot; - r.fty - }; - - // Pull the return type out of the type of the function. - match structure_of(fcx, sp, fty) { - ty::ty_fn(ref f) => { - bot |= (f.meta.ret_style == ast::noreturn); - fcx.write_ty(call_expr_id, f.sig.output); - return bot; - } - _ => { - fcx.write_ty(call_expr_id, ty::mk_err(fcx.ccx.tcx)); - fcx.type_error_message(sp, |_actual| { - ~"expected function"}, fty, None); - return bot; - } - } - } - - // A generic function for checking for or for-each loops - fn check_for(fcx: @fn_ctxt, local: @ast::local, - element_ty: ty::t, body: ast::blk, - node_id: ast::node_id) -> bool { - let locid = lookup_local(fcx, local.span, local.node.id); - demand::suptype(fcx, local.span, - ty::mk_var(fcx.ccx.tcx, locid), - element_ty); - let bot = check_decl_local(fcx, local); - check_block_no_value(fcx, body); - fcx.write_nil(node_id); - return bot; - } - - // A generic function for checking the then and else in an if - // or if-check - fn check_then_else(fcx: @fn_ctxt, thn: ast::blk, - elsopt: Option<@ast::expr>, id: ast::node_id, - _sp: span) -> bool { - let (if_t, if_bot) = - match elsopt { - Some(els) => { - let if_t = fcx.infcx().next_ty_var(); - let thn_bot = check_block(fcx, thn); - let thn_t = fcx.node_ty(thn.node.id); - demand::suptype(fcx, thn.span, if_t, thn_t); - let els_bot = check_expr_with(fcx, els, if_t); - (if_t, thn_bot & els_bot) - } - None => { - check_block_no_value(fcx, thn); - (ty::mk_nil(fcx.ccx.tcx), false) - } - }; - fcx.write_ty(id, if_t); - return if_bot; - } - - fn lookup_op_method(fcx: @fn_ctxt, op_ex: @ast::expr, - self_ex: @ast::expr, self_t: ty::t, - opname: ast::ident, args: ~[@ast::expr], - +deref_args: DerefArgs) - -> Option<(ty::t, bool)> - { - match method::lookup(fcx, op_ex, self_ex, - op_ex.callee_id, opname, self_t, ~[], - deref_args) { - Some(origin) => { - let {fty: method_ty, bot: bot} = { - let method_ty = fcx.node_ty(op_ex.callee_id); - check_call_inner(fcx, op_ex.span, op_ex.id, - method_ty, op_ex, args, deref_args) - }; - fcx.ccx.method_map.insert(op_ex.id, origin); - Some((ty::ty_fn_ret(method_ty), bot)) - } - _ => None - } - } - - // could be either a expr_binop or an expr_assign_binop - fn check_binop(fcx: @fn_ctxt, expr: @ast::expr, - op: ast::binop, - lhs: @ast::expr, - rhs: @ast::expr) -> bool { - let tcx = fcx.ccx.tcx; - - let lhs_bot = check_expr(fcx, lhs, None); - let lhs_t = fcx.expr_ty(lhs); - let lhs_t = structurally_resolved_type(fcx, lhs.span, lhs_t); - - if ty::type_is_integral(lhs_t) && ast_util::is_shift_binop(op) { - // Shift is a special case: rhs can be any integral type - let rhs_bot = check_expr(fcx, rhs, None); - let rhs_t = fcx.expr_ty(rhs); - require_integral(fcx, rhs.span, rhs_t); - fcx.write_ty(expr.id, lhs_t); - return lhs_bot | rhs_bot; - } - - if ty::is_binopable(tcx, lhs_t, op) { - let tvar = fcx.infcx().next_ty_var(); - demand::suptype(fcx, expr.span, tvar, lhs_t); - let rhs_bot = check_expr_with(fcx, rhs, tvar); - - let result_t = match op { - ast::eq | ast::ne | ast::lt | ast::le | ast::ge | - ast::gt => { - ty::mk_bool(tcx) - } - _ => { - lhs_t - } - }; - - fcx.write_ty(expr.id, result_t); - return { - if !ast_util::lazy_binop(op) { lhs_bot | rhs_bot } - else { lhs_bot } - }; - } - - // A hack, but this prevents multiple errors for the same code - // (since check_user_binop calls structurally_resolve_type) - let (result, rhs_bot) = - match ty::deref(fcx.tcx(), lhs_t, false).map( - |tt| structurally_resolved_type(fcx, - expr.span, tt.ty)) { - Some(t) if ty::get(t).sty == ty::ty_err => (t, false), - _ => check_user_binop(fcx, expr, lhs, lhs_t, op, rhs) - }; - fcx.write_ty(expr.id, result); - return lhs_bot | rhs_bot; - } - - fn check_user_binop(fcx: @fn_ctxt, ex: @ast::expr, - lhs_expr: @ast::expr, lhs_resolved_t: ty::t, - op: ast::binop, rhs: @ast::expr) -> (ty::t, bool) - { - let tcx = fcx.ccx.tcx; - match ast_util::binop_to_method_name(op) { - Some(name) => { - match lookup_op_method(fcx, ex, lhs_expr, lhs_resolved_t, - fcx.tcx().sess.ident_of(name), - ~[rhs], DoDerefArgs) { - Some(pair) => return pair, - _ => () - } - } - _ => () - } - check_expr(fcx, rhs, None); - fcx.type_error_message(ex.span, - |actual| { - fmt!("binary operation %s cannot be applied to type `%s`", - ast_util::binop_to_str(op), actual) - }, - lhs_resolved_t, None); - - // If the or operator is used it might be that the user forgot to - // supply the do keyword. Let's be more helpful in that situation. - if op == ast::or { - match ty::get(lhs_resolved_t).sty { - ty::ty_fn(_) => { - tcx.sess.span_note( - ex.span, ~"did you forget the 'do' keyword for the call?"); - } - _ => () - } - } - - (lhs_resolved_t, false) - } - - fn check_user_unop(fcx: @fn_ctxt, op_str: ~str, mname: ~str, - ex: @ast::expr, - rhs_expr: @ast::expr, rhs_t: ty::t) -> ty::t { - match lookup_op_method(fcx, ex, rhs_expr, rhs_t, - fcx.tcx().sess.ident_of(mname), ~[], - DontDerefArgs) { - Some((ret_ty, _)) => ret_ty, - _ => { - fcx.type_error_message(ex.span, |actual| { - fmt!("cannot apply unary operator `%s` to type `%s`", - op_str, actual) - }, rhs_t, None); - rhs_t - } - } - } - - // Resolves `expected` by a single level if it is a variable and passes it - // through the `unpack` function. It there is no expected type or - // resolution is not possible (e.g., no constraints yet present), just - // returns `none`. - fn unpack_expected(fcx: @fn_ctxt, expected: Option, - unpack: fn(ty::sty) -> Option) - -> Option { - match expected { - Some(t) => { - match resolve_type(fcx.infcx(), t, force_tvar) { - Ok(t) => unpack(ty::get(t).sty), - _ => None - } - } - _ => None - } - } - - fn check_expr_fn(fcx: @fn_ctxt, - expr: @ast::expr, - ast_proto_opt: Option, - decl: ast::fn_decl, - body: ast::blk, - is_loop_body: bool, - expected: Option) { - let tcx = fcx.ccx.tcx; - - // Find the expected input/output types (if any). Careful to - // avoid capture of bound regions in the expected type. See - // def'n of br_cap_avoid() for a more lengthy explanation of - // what's going on here. - // Also try to pick up inferred purity and proto, defaulting - // to impure and block. Note that we only will use those for - // block syntax lambdas; that is, lambdas without explicit - // protos. - let expected_sty = unpack_expected(fcx, expected, |x| Some(x)); - let (expected_tys, - expected_purity, - expected_proto, - expected_onceness) = { - match expected_sty { - Some(ty::ty_fn(ref fn_ty)) => { - let id = expr.id; - let {fn_ty, _} = - replace_bound_regions_in_fn_ty( - tcx, @Nil, None, fn_ty, - |br| ty::re_bound(ty::br_cap_avoid(id, @br))); - (Some({inputs: fn_ty.sig.inputs, - output: fn_ty.sig.output}), - fn_ty.meta.purity, - fn_ty.meta.proto, - fn_ty.meta.onceness) - } - _ => { - (None, ast::impure_fn, ast::ProtoBox, ast::Many) - } - } - }; - - // If the proto is specified, use that, otherwise select a - // proto based on inference. - let (proto, purity) = match ast_proto_opt { - Some(p) => (p, ast::impure_fn), - None => (expected_proto, expected_purity) - }; - - // construct the function type - let mut fn_ty = astconv::ty_of_fn_decl( - fcx, fcx, - proto, purity, expected_onceness, - /*bounds:*/ @~[], /*opt_region:*/ None, - decl, expected_tys, expr.span); - - let fty = ty::mk_fn(tcx, fn_ty); - - debug!("check_expr_fn_with_unifier %s fty=%s", - expr_to_str(expr, tcx.sess.intr()), - fcx.infcx().ty_to_str(fty)); - - fcx.write_ty(expr.id, fty); - - check_fn(fcx.ccx, None, &fn_ty, decl, body, - is_loop_body, Some(fcx)); - } - - - // Check field access expressions - fn check_field(fcx: @fn_ctxt, expr: @ast::expr, is_callee: bool, - base: @ast::expr, field: ast::ident, tys: ~[@ast::Ty]) - -> bool - { - let tcx = fcx.ccx.tcx; - let bot = check_expr(fcx, base, None); - let expr_t = structurally_resolved_type(fcx, expr.span, - fcx.expr_ty(base)); - let (base_t, derefs) = do_autoderef(fcx, expr.span, expr_t); - let n_tys = tys.len(); - - match structure_of(fcx, expr.span, base_t) { - ty::ty_rec(fields) => { - match ty::field_idx(field, fields) { - Some(ix) => { - if n_tys > 0 { - tcx.sess.span_err( - expr.span, - ~"can't provide type parameters \ - to a field access"); - } - fcx.write_ty(expr.id, fields[ix].mt.ty); - fcx.write_autoderef_adjustment(base.id, derefs); - return bot; - } - _ => () - } - } - ty::ty_class(base_id, substs) => { - // This is just for fields -- the same code handles - // methods in both classes and traits - - // (1) verify that the class id actually has a field called - // field - debug!("class named %s", ty_to_str(tcx, base_t)); - let cls_items = ty::lookup_class_fields(tcx, base_id); - match lookup_field_ty(tcx, base_id, cls_items, - field, &substs) { - Some(field_ty) => { - // (2) look up what field's type is, and return it - fcx.write_ty(expr.id, field_ty); - fcx.write_autoderef_adjustment(base.id, derefs); - return bot; - } - None => () - } - } - _ => () - } - - let tps = vec::map(tys, |ty| fcx.to_ty(*ty)); - - match method::lookup(fcx, expr, base, expr.id, - field, expr_t, tps, DontDerefArgs) { - Some(entry) => { - fcx.ccx.method_map.insert(expr.id, entry); - - // If we have resolved to a method but this is not in - // a callee position, error - if !is_callee { - tcx.sess.span_err( - expr.span, - ~"attempted to take value of method \ - (try writing an anonymous function)"); - } - } - None => { - fcx.type_error_message(expr.span, - |actual| { - fmt!("attempted access of field `%s` on type `%s`, but \ - no field or method with that name was found", - tcx.sess.str_of(field), actual) - }, - expr_t, None); - // Add error type for the result - fcx.write_ty(expr.id, ty::mk_err(tcx)); - } - } - - return bot; - } - - fn check_struct_or_variant_fields(fcx: @fn_ctxt, - span: span, - class_id: ast::def_id, - substitutions: &ty::substs, - field_types: ~[ty::field_ty], - ast_fields: ~[ast::field], - check_completeness: bool) -> bool { - let tcx = fcx.ccx.tcx; - let mut bot = false; - - debug!("%? %?", ast_fields.len(), field_types.len()); - - let class_field_map = HashMap(); - let mut fields_found = 0; - for field_types.each |field| { - // XXX: Check visibility here. - class_field_map.insert(field.ident, (field.id, false)); - } - - // Typecheck each field. - for ast_fields.each |field| { - match class_field_map.find(field.node.ident) { - None => { - tcx.sess.span_err( - field.span, - fmt!("structure has no field named `%s`", - tcx.sess.str_of(field.node.ident))); - } - Some((_, true)) => { - tcx.sess.span_err( - field.span, - fmt!("field `%s` specified more than once", - tcx.sess.str_of(field.node.ident))); - } - Some((field_id, false)) => { - let expected_field_type = - ty::lookup_field_type( - tcx, class_id, field_id, substitutions); - bot |= check_expr(fcx, - field.node.expr, - Some(expected_field_type)); - class_field_map.insert( - field.node.ident, (field_id, true)); - fields_found += 1; - } - } - } - - if check_completeness { - // Make sure the programmer specified all the fields. - assert fields_found <= field_types.len(); - if fields_found < field_types.len() { - let mut missing_fields = ~[]; - for field_types.each |class_field| { - let name = class_field.ident; - let (_, seen) = class_field_map.get(name); - if !seen { - missing_fields.push( - ~"`" + tcx.sess.str_of(name) + ~"`"); - } - } - - tcx.sess.span_err(span, - fmt!("missing field%s: %s", - if missing_fields.len() == 1 { - ~"" - } else { - ~"s" - }, - str::connect(missing_fields, ~", "))); - } - } - - return bot; - } - - fn check_struct_constructor(fcx: @fn_ctxt, - id: ast::node_id, - span: syntax::codemap::span, - class_id: ast::def_id, - fields: ~[ast::field], - base_expr: Option<@ast::expr>) -> bool { - let mut bot = false; - let tcx = fcx.ccx.tcx; - - // Look up the number of type parameters and the raw type, and - // determine whether the class is region-parameterized. - let type_parameter_count, region_parameterized, raw_type; - if class_id.crate == ast::local_crate { - region_parameterized = - tcx.region_paramd_items.find(class_id.node); - match tcx.items.find(class_id.node) { - Some(ast_map::node_item(@{ - node: ast::item_class(_, type_parameters), - _ - }, _)) => { - - type_parameter_count = type_parameters.len(); - - let self_region = - bound_self_region(region_parameterized); - - raw_type = ty::mk_class(tcx, class_id, { - self_r: self_region, - self_ty: None, - tps: ty::ty_params_to_tys(tcx, type_parameters) - }); - } - _ => { - tcx.sess.span_bug(span, - ~"resolve didn't map this to a class"); - } - } - } else { - let item_type = ty::lookup_item_type(tcx, class_id); - type_parameter_count = (*item_type.bounds).len(); - region_parameterized = item_type.region_param; - raw_type = item_type.ty; - } - - // Generate the struct type. - let self_region = - fcx.region_var_if_parameterized(region_parameterized, - span, - ty::re_scope(id)); - let type_parameters = fcx.infcx().next_ty_vars(type_parameter_count); - let substitutions = { - self_r: self_region, - self_ty: None, - tps: type_parameters - }; - - let struct_type = ty::subst(tcx, &substitutions, raw_type); - - // Look up and check the fields. - let class_fields = ty::lookup_class_fields(tcx, class_id); - bot = check_struct_or_variant_fields(fcx, - span, - class_id, - &substitutions, - class_fields, - fields, - base_expr.is_none()) || bot; - - // Check the base expression if necessary. - match base_expr { - None => {} - Some(base_expr) => { - bot = check_expr(fcx, base_expr, Some(struct_type)) || bot - } - } - - // Write in the resulting type. - fcx.write_ty(id, struct_type); - return bot; - } - - fn check_struct_enum_variant(fcx: @fn_ctxt, - id: ast::node_id, - span: syntax::codemap::span, - enum_id: ast::def_id, - variant_id: ast::def_id, - fields: ~[ast::field]) -> bool { - let mut bot = false; - let tcx = fcx.ccx.tcx; - - // Look up the number of type parameters and the raw type, and - // determine whether the enum is region-parameterized. - let type_parameter_count, region_parameterized, raw_type; - if enum_id.crate == ast::local_crate { - region_parameterized = - tcx.region_paramd_items.find(enum_id.node); - match tcx.items.find(enum_id.node) { - Some(ast_map::node_item(@{ - node: ast::item_enum(_, type_parameters), - _ - }, _)) => { - - type_parameter_count = type_parameters.len(); - - let self_region = - bound_self_region(region_parameterized); - - raw_type = ty::mk_enum(tcx, enum_id, { - self_r: self_region, - self_ty: None, - tps: ty::ty_params_to_tys(tcx, type_parameters) - }); - } - _ => { - tcx.sess.span_bug(span, - ~"resolve didn't map this to an enum"); - } - } - } else { - let item_type = ty::lookup_item_type(tcx, enum_id); - type_parameter_count = (*item_type.bounds).len(); - region_parameterized = item_type.region_param; - raw_type = item_type.ty; - } - - // Generate the enum type. - let self_region = - fcx.region_var_if_parameterized(region_parameterized, - span, - ty::re_scope(id)); - let type_parameters = fcx.infcx().next_ty_vars(type_parameter_count); - let substitutions = { - self_r: self_region, - self_ty: None, - tps: type_parameters - }; - - let enum_type = ty::subst(tcx, &substitutions, raw_type); - - // Look up and check the enum variant fields. - let variant_fields = ty::lookup_class_fields(tcx, variant_id); - bot = check_struct_or_variant_fields(fcx, - span, - variant_id, - &substitutions, - variant_fields, - fields, - true) || bot; - - // Write in the resulting type. - fcx.write_ty(id, enum_type); - return bot; - } - - let tcx = fcx.ccx.tcx; - let id = expr.id; - let mut bot = false; - match expr.node { - ast::expr_vstore(ev, vst) => { - let typ = match ev.node { - ast::expr_lit(@{node: ast::lit_str(s), span:_}) => { - let tt = ast_expr_vstore_to_vstore(fcx, ev, str::len(*s), vst); - ty::mk_estr(tcx, tt) - } - ast::expr_vec(args, mutbl) => { - let tt = ast_expr_vstore_to_vstore(fcx, ev, args.len(), vst); - let mutability; - match vst { - ast::expr_vstore_mut_box => mutability = ast::m_mutbl, - _ => mutability = mutbl - } - let t: ty::t = fcx.infcx().next_ty_var(); - for args.each |e| { bot |= check_expr_with(fcx, *e, t); } - ty::mk_evec(tcx, {ty: t, mutbl: mutability}, tt) - } - ast::expr_repeat(element, count_expr, mutbl) => { - let count = ty::eval_repeat_count(tcx, count_expr, expr.span); - fcx.write_ty(count_expr.id, ty::mk_uint(tcx)); - let tt = ast_expr_vstore_to_vstore(fcx, ev, count, vst); - let t: ty::t = fcx.infcx().next_ty_var(); - bot |= check_expr_with(fcx, element, t); - ty::mk_evec(tcx, {ty: t, mutbl: mutbl}, tt) - } - _ => - tcx.sess.span_bug(expr.span, ~"vstore modifier on non-sequence") - }; - fcx.write_ty(ev.id, typ); - fcx.write_ty(id, typ); - } - - ast::expr_lit(lit) => { - let typ = check_lit(fcx, lit); - fcx.write_ty(id, typ); - } - ast::expr_binary(op, lhs, rhs) => { - bot |= check_binop(fcx, expr, op, lhs, rhs); - } - ast::expr_assign_op(op, lhs, rhs) => { - bot |= check_binop(fcx, expr, op, lhs, rhs); - let lhs_t = fcx.expr_ty(lhs); - let result_t = fcx.expr_ty(expr); - demand::suptype(fcx, expr.span, result_t, lhs_t); - - // Overwrite result of check_binop...this preserves existing behavior - // but seems quite dubious with regard to user-defined methods - // and so forth. - Niko - fcx.write_nil(expr.id); - } - ast::expr_unary(unop, oprnd) => { - let exp_inner = do unpack_expected(fcx, expected) |sty| { - match unop { - ast::box(_) | ast::uniq(_) => match sty { - ty::ty_box(mt) | ty::ty_uniq(mt) => Some(mt.ty), - _ => None - }, - ast::not | ast::neg => expected, - ast::deref => None - } - }; - bot = check_expr(fcx, oprnd, exp_inner); - let mut oprnd_t = fcx.expr_ty(oprnd); - match unop { - ast::box(mutbl) => { - oprnd_t = ty::mk_box(tcx, {ty: oprnd_t, mutbl: mutbl}); - } - ast::uniq(mutbl) => { - oprnd_t = ty::mk_uniq(tcx, {ty: oprnd_t, mutbl: mutbl}); - } - ast::deref => { - let sty = structure_of(fcx, expr.span, oprnd_t); - - // deref'ing an unsafe pointer requires that we be in an unsafe - // context - match sty { - ty::ty_ptr(*) => { - fcx.require_unsafe( - expr.span, - ~"dereference of unsafe pointer"); - } - _ => { /*ok*/ } - } - - match ty::deref_sty(tcx, &sty, true) { - Some(mt) => { oprnd_t = mt.ty } - None => { - match sty { - ty::ty_enum(*) => { - tcx.sess.span_err( - expr.span, - ~"can only dereference enums \ - with a single variant which has a \ - single argument"); - } - ty::ty_class(*) => { - tcx.sess.span_err( - expr.span, - ~"can only dereference structs with one anonymous \ - field"); - } - _ => { - fcx.type_error_message(expr.span, |actual| { - fmt!("type %s cannot be dereferenced", actual) - }, oprnd_t, None); - } - } - } - } - } - ast::not => { - oprnd_t = structurally_resolved_type(fcx, oprnd.span, oprnd_t); - if !(ty::type_is_integral(oprnd_t) || - ty::get(oprnd_t).sty == ty::ty_bool) { - oprnd_t = check_user_unop(fcx, ~"!", ~"not", expr, - oprnd, oprnd_t); - } - } - ast::neg => { - oprnd_t = structurally_resolved_type(fcx, oprnd.span, oprnd_t); - if !(ty::type_is_integral(oprnd_t) || - ty::type_is_fp(oprnd_t)) { - oprnd_t = check_user_unop(fcx, ~"-", ~"neg", expr, - oprnd, oprnd_t); - } - } - } - fcx.write_ty(id, oprnd_t); - } - ast::expr_addr_of(mutbl, oprnd) => { - bot = check_expr(fcx, oprnd, unpack_expected(fcx, expected, |ty| - match ty { ty::ty_rptr(_, mt) => Some(mt.ty), _ => None } - )); - - // Note: at this point, we cannot say what the best lifetime - // is to use for resulting pointer. We want to use the - // shortest lifetime possible so as to avoid spurious borrowck - // errors. Moreover, the longest lifetime will depend on the - // precise details of the value whose address is being taken - // (and how long it is valid), which we don't know yet until type - // inference is complete. - // - // Therefore, here we simply generate a region variable with - // the current expression as a lower bound. The region - // inferencer will then select the ultimate value. Finally, - // borrowck is charged with guaranteeing that the value whose - // address was taken can actually be made to live as long as - // it needs to live. - let region = fcx.infcx().next_region_var(expr.span, expr.id); - - let tm = { ty: fcx.expr_ty(oprnd), mutbl: mutbl }; - let oprnd_t = ty::mk_rptr(tcx, region, tm); - fcx.write_ty(id, oprnd_t); - } - ast::expr_path(pth) => { - let defn = lookup_def(fcx, pth.span, id); - - let tpt = ty_param_bounds_and_ty_for_def(fcx, expr.span, defn); - let region_lb = ty::re_scope(expr.id); - instantiate_path(fcx, pth, tpt, expr.span, expr.id, region_lb); - } - ast::expr_mac(_) => tcx.sess.bug(~"unexpanded macro"), - ast::expr_fail(expr_opt) => { - bot = true; - match expr_opt { - None => {/* do nothing */ } - Some(e) => { - check_expr_with(fcx, e, - ty::mk_estr(tcx, ty::vstore_uniq)); - } - } - fcx.write_bot(id); - } - ast::expr_break(_) => { fcx.write_bot(id); bot = true; } - ast::expr_again(_) => { fcx.write_bot(id); bot = true; } - ast::expr_ret(expr_opt) => { - bot = true; - let ret_ty = match fcx.indirect_ret_ty { - Some(t) => t, None => fcx.ret_ty - }; - match expr_opt { - None => match fcx.mk_eqty(false, expr.span, - ret_ty, ty::mk_nil(tcx)) { - result::Ok(_) => { /* fall through */ } - result::Err(_) => { - tcx.sess.span_err( - expr.span, - ~"`return;` in function returning non-nil"); - } - }, - Some(e) => { check_expr_with(fcx, e, ret_ty); } - } - fcx.write_bot(id); - } - ast::expr_log(_, lv, e) => { - bot = check_expr_with(fcx, lv, ty::mk_mach_uint(tcx, ast::ty_u32)); - // Note: this does not always execute, so do not propagate bot: - check_expr(fcx, e, None); - fcx.write_nil(id); - } - ast::expr_assert(e) => { - bot = check_expr_with(fcx, e, ty::mk_bool(tcx)); - fcx.write_nil(id); - } - ast::expr_copy(a) | ast::expr_unary_move(a) => { - bot = check_expr(fcx, a, expected); - fcx.write_ty(id, fcx.expr_ty(a)); - } - ast::expr_paren(a) => { - bot = check_expr_with_unifier(fcx, a, expected, || ()); - fcx.write_ty(id, fcx.expr_ty(a)); - do expected.iter |i| { - demand::assign(fcx, expr.span, *i, expr); - demand::assign(fcx, a.span, *i, a); - }; - } - ast::expr_assign(lhs, rhs) => { - bot = check_assignment(fcx, lhs, rhs, id); - } - ast::expr_swap(lhs, rhs) => { - bot = check_assignment(fcx, lhs, rhs, id); - } - ast::expr_if(cond, thn, elsopt) => { - bot = check_expr_with(fcx, cond, ty::mk_bool(tcx)) | - check_then_else(fcx, thn, elsopt, id, expr.span); - } - ast::expr_while(cond, body) => { - bot = check_expr_with(fcx, cond, ty::mk_bool(tcx)); - check_block_no_value(fcx, body); - fcx.write_ty(id, ty::mk_nil(tcx)); - } - ast::expr_loop(body, _) => { - check_block_no_value(fcx, body); - fcx.write_ty(id, ty::mk_nil(tcx)); - bot = !may_break(tcx, expr.id, body); - } - ast::expr_match(discrim, arms) => { - bot = alt::check_alt(fcx, expr, discrim, arms); - } - ast::expr_fn(proto, decl, body, cap_clause) => { - check_expr_fn(fcx, expr, Some(proto), - decl, body, false, - expected); - capture::check_capture_clause(tcx, expr.id, cap_clause); - } - ast::expr_fn_block(decl, body, cap_clause) => { - check_expr_fn(fcx, expr, None, - decl, body, false, - expected); - capture::check_capture_clause(tcx, expr.id, cap_clause); - } - ast::expr_loop_body(b) => { - // a loop body is the special argument to a `for` loop. We know that - // there will be an expected type in this context because it can only - // appear in the context of a call, so we get the expected type of the - // parameter. The catch here is that we need to validate two things: - // 1. a closure that returns a bool is expected - // 2. the closure that was given returns unit - let expected_sty = unpack_expected(fcx, expected, |x| Some(x)); - let inner_ty = match expected_sty { - Some(ty::ty_fn(fty)) => { - match fcx.mk_subty(false, expr.span, - fty.sig.output, ty::mk_bool(tcx)) { - result::Ok(_) => (), - result::Err(_) => { - fcx.type_error_message(expr.span, - |actual| { - fmt!("a `loop` function's last argument \ - should return `bool`, not `%s`", actual) - }, - fty.sig.output, None); - fcx.write_ty(id, ty::mk_err(tcx)); - return true; - } - } - ty::mk_fn(tcx, FnTyBase { - meta: fty.meta, - sig: FnSig {output: ty::mk_nil(tcx), - ..fty.sig} - }) - } - _ => - match expected { - Some(expected_t) => { - fcx.type_error_message(expr.span, |actual| { - fmt!("a `loop` function's last \ - argument should be of function \ - type, not `%s`", - actual) - }, - expected_t, None); - fcx.write_ty(id, ty::mk_err(tcx)); - return true; - } - None => fcx.tcx().sess.impossible_case(expr.span, - ~"loop body must have an expected type") - } - }; - match b.node { - ast::expr_fn_block(decl, body, cap_clause) => { - check_expr_fn(fcx, b, None, - decl, body, true, - Some(inner_ty)); - demand::suptype(fcx, b.span, inner_ty, fcx.expr_ty(b)); - capture::check_capture_clause(tcx, b.id, cap_clause); - } - // argh - _ => fail ~"expr_fn_block" - } - let block_ty = structurally_resolved_type( - fcx, expr.span, fcx.node_ty(b.id)); - match ty::get(block_ty).sty { - ty::ty_fn(fty) => { - fcx.write_ty(expr.id, ty::mk_fn(tcx, FnTyBase { - meta: fty.meta, - sig: FnSig {output: ty::mk_bool(tcx), - ..fty.sig} - })) - } - _ => fail ~"expected fn type" - } - } - ast::expr_do_body(b) => { - let expected_sty = unpack_expected(fcx, expected, |x| Some(x)); - let inner_ty = match expected_sty { - Some(ty::ty_fn(fty)) => { - ty::mk_fn(tcx, fty) - } - _ => match expected { - Some(expected_t) => { - fcx.type_error_message(expr.span, |_actual| { - ~"Non-function passed to a `do` \ - function as its last argument, or wrong number \ - of arguments passed to a `do` function" - }, expected_t, None); - fcx.write_ty(id, ty::mk_err(tcx)); - return true; - } - None => fcx.tcx().sess.impossible_case(expr.span, - ~"do body must have expected type") - } - }; - match b.node { - ast::expr_fn_block(decl, body, cap_clause) => { - check_expr_fn(fcx, b, None, - decl, body, true, - Some(inner_ty)); - demand::suptype(fcx, b.span, inner_ty, fcx.expr_ty(b)); - capture::check_capture_clause(tcx, b.id, cap_clause); - } - // argh - _ => fail ~"expected fn ty" - } - let block_ty = structurally_resolved_type( - fcx, expr.span, fcx.node_ty(b.id)); - match ty::get(block_ty).sty { - ty::ty_fn(fty) => { - fcx.write_ty(expr.id, ty::mk_fn(tcx, fty)); - } - _ => fail ~"expected fn ty" - } - } - ast::expr_block(b) => { - // If this is an unchecked block, turn off purity-checking - bot = check_block(fcx, b); - let typ = - match b.node.expr { - Some(expr) => fcx.expr_ty(expr), - None => ty::mk_nil(tcx) - }; - fcx.write_ty(id, typ); - } - ast::expr_call(f, args, _) => { - bot = check_call(fcx, expr.span, expr.id, f, args); - } - ast::expr_cast(e, t) => { - bot = check_expr(fcx, e, None); - let t_1 = fcx.to_ty(t); - let t_e = fcx.expr_ty(e); - - debug!("t_1=%s", fcx.infcx().ty_to_str(t_1)); - debug!("t_e=%s", fcx.infcx().ty_to_str(t_e)); - - match ty::get(t_1).sty { - // This will be looked up later on - ty::ty_trait(*) => (), - - _ => { - if ty::type_is_nil(t_e) { - fcx.type_error_message(expr.span, |actual| { - fmt!("cast from nil: `%s` as `%s`", actual, - fcx.infcx().ty_to_str(t_1)) - }, t_e, None); - } else if ty::type_is_nil(t_1) { - fcx.type_error_message(expr.span, |actual| { - fmt!("cast to nil: `%s` as `%s`", actual, - fcx.infcx().ty_to_str(t_1)) - }, t_e, None); - } - - let t_1_is_scalar = type_is_scalar(fcx, expr.span, t_1); - if type_is_c_like_enum(fcx,expr.span,t_e) && t_1_is_scalar { - /* this case is allowed */ - } else if !(type_is_scalar(fcx,expr.span,t_e) && t_1_is_scalar) { - /* - If more type combinations should be supported than are - supported here, then file an enhancement issue and record the - issue number in this comment. - */ - fcx.type_error_message(expr.span, |actual| { - fmt!("non-scalar cast: `%s` as `%s`", actual, - fcx.infcx().ty_to_str(t_1)) - }, t_e, None); - } - } - } - fcx.write_ty(id, t_1); - } - ast::expr_vec(args, mutbl) => { - let t: ty::t = fcx.infcx().next_ty_var(); - for args.each |e| { bot |= check_expr_with(fcx, *e, t); } - let typ = ty::mk_evec(tcx, {ty: t, mutbl: mutbl}, - ty::vstore_fixed(args.len())); - fcx.write_ty(id, typ); - } - ast::expr_repeat(element, count_expr, mutbl) => { - let count = ty::eval_repeat_count(tcx, count_expr, expr.span); - fcx.write_ty(count_expr.id, ty::mk_uint(tcx)); - let t: ty::t = fcx.infcx().next_ty_var(); - bot |= check_expr_with(fcx, element, t); - let t = ty::mk_evec(tcx, {ty: t, mutbl: mutbl}, - ty::vstore_fixed(count)); - fcx.write_ty(id, t); - } - ast::expr_tup(elts) => { - let flds = unpack_expected(fcx, expected, |sty| { - match sty { ty::ty_tup(flds) => Some(flds), _ => None } - }); - let elt_ts = do elts.mapi |i, e| { - check_expr(fcx, *e, flds.map(|fs| fs[i])); - fcx.expr_ty(*e) - }; - let typ = ty::mk_tup(tcx, elt_ts); - fcx.write_ty(id, typ); - } - ast::expr_rec(fields, base) => { - option::iter(&base, |b| { check_expr(fcx, *b, expected); }); - let expected = if expected.is_none() && base.is_some() { - Some(fcx.expr_ty(base.get())) - } else { expected }; - let flds = unpack_expected(fcx, expected, |sty| - match sty { ty::ty_rec(flds) => Some(flds), _ => None } - ); - let fields_t = vec::map(fields, |f| { - bot |= check_expr(fcx, f.node.expr, flds.chain_ref(|flds| - vec::find(*flds, |tf| tf.ident == f.node.ident) - ).map(|tf| tf.mt.ty)); - let expr_t = fcx.expr_ty(f.node.expr); - let expr_mt = {ty: expr_t, mutbl: f.node.mutbl}; - // for the most precise error message, - // should be f.node.expr.span, not f.span - respan(f.node.expr.span, {ident: f.node.ident, mt: expr_mt}) - }); - match base { - None => { - fn get_node(f: &spanned) -> field { f.node } - let typ = ty::mk_rec(tcx, vec::map(fields_t, get_node)); - fcx.write_ty(id, typ); - /* Check for duplicate fields */ - /* Only do this check if there's no base expr -- the reason is - that we're extending a record we know has no dup fields, and - it would be ill-typed anyway if we duplicated one of its - fields */ - check_no_duplicate_fields(tcx, fields.map(|f| - (f.node.ident, f.span))); - } - Some(bexpr) => { - let bexpr_t = fcx.expr_ty(bexpr); - let base_fields = match structure_of(fcx, expr.span, bexpr_t) { - ty::ty_rec(flds) => flds, - _ => { - fcx.type_error_message(expr.span, |_actual| { - ~"record update has non-record base" - }, bexpr_t, None); - fcx.write_ty(id, ty::mk_err(tcx)); - return true; - } - }; - fcx.write_ty(id, bexpr_t); - for fields_t.each |f| { - let mut found = false; - for base_fields.each |bf| { - if f.node.ident == bf.ident { - demand::suptype(fcx, f.span, bf.mt.ty, f.node.mt.ty); - found = true; - } - } - if !found { - tcx.sess.span_err(f.span, - ~"unknown field in record update: " + - tcx.sess.str_of(f.node.ident)); - fcx.write_ty(id, ty::mk_err(tcx)); - return true; - } - } - } - } - } - ast::expr_struct(path, fields, base_expr) => { - // Resolve the path. - match tcx.def_map.find(id) { - Some(ast::def_class(type_def_id)) => { - check_struct_constructor(fcx, id, expr.span, type_def_id, - fields, base_expr); - } - Some(ast::def_variant(enum_id, variant_id)) => { - check_struct_enum_variant(fcx, id, expr.span, enum_id, - variant_id, fields); - } - _ => { - tcx.sess.span_bug(path.span, ~"structure constructor does \ - not name a structure type"); - } - } - } - ast::expr_field(base, field, tys) => { - bot = check_field(fcx, expr, false, base, field, tys); - } - ast::expr_index(base, idx) => { - bot |= check_expr(fcx, base, None); - let raw_base_t = fcx.expr_ty(base); - let (base_t, derefs) = do_autoderef(fcx, expr.span, raw_base_t); - bot |= check_expr(fcx, idx, None); - let idx_t = fcx.expr_ty(idx); - let base_sty = structure_of(fcx, expr.span, base_t); - match ty::index_sty(tcx, &base_sty) { - Some(mt) => { - require_integral(fcx, idx.span, idx_t); - fcx.write_ty(id, mt.ty); - fcx.write_autoderef_adjustment(base.id, derefs); - } - None => { - let resolved = structurally_resolved_type(fcx, expr.span, - raw_base_t); - match lookup_op_method(fcx, expr, base, resolved, - tcx.sess.ident_of(~"index"), - ~[idx], DontDerefArgs) { - Some((ret_ty, _)) => fcx.write_ty(id, ret_ty), - _ => { - fcx.type_error_message(expr.span, |actual| - fmt!("cannot index a value of type `%s`", - actual), base_t, None); - fcx.write_ty(id, ty::mk_err(tcx)); - return true; - } - } - } - } - } - } - if bot { fcx.write_bot(expr.id); } - - debug!("type of expr %s is %s, expected is %s", - syntax::print::pprust::expr_to_str(expr, tcx.sess.intr()), - ty_to_str(tcx, fcx.expr_ty(expr)), - match expected { - Some(t) => ty_to_str(tcx, t), - _ => ~"empty" - }); - - unifier(); - - debug!("<< bot=%b", bot); - return bot; -} - -fn require_integral(fcx: @fn_ctxt, sp: span, t: ty::t) { - if !type_is_integral(fcx, sp, t) { - fcx.type_error_message(sp, |actual| { - fmt!("mismatched types: expected integral type but found `%s`", - actual) - }, t, None); - } -} - -fn check_decl_initializer(fcx: @fn_ctxt, nid: ast::node_id, - init: @ast::expr) -> bool { - let lty = ty::mk_var(fcx.ccx.tcx, lookup_local(fcx, init.span, nid)); - return check_expr_with_assignability(fcx, init, lty); -} - -fn check_decl_local(fcx: @fn_ctxt, local: @ast::local) -> bool { - let mut bot = false; - let tcx = fcx.ccx.tcx; - - let t = ty::mk_var(tcx, fcx.inh.locals.get(local.node.id)); - fcx.write_ty(local.node.id, t); - - let is_lvalue; - match local.node.init { - Some(init) => { - bot = check_decl_initializer(fcx, local.node.id, init); - is_lvalue = ty::expr_is_lval(tcx, fcx.ccx.method_map, init); - } - _ => { - is_lvalue = true; - } - } - - let region = - ty::re_scope(tcx.region_map.get(local.node.id)); - let pcx = { - fcx: fcx, - map: pat_id_map(tcx.def_map, local.node.pat), - alt_region: region, - block_region: region, - }; - alt::check_pat(pcx, local.node.pat, t); - let has_guard = false; - alt::check_legality_of_move_bindings(fcx, - is_lvalue, - has_guard, - [local.node.pat]); - return bot; -} - -fn check_stmt(fcx: @fn_ctxt, stmt: @ast::stmt) -> bool { - let mut node_id; - let mut bot = false; - match stmt.node { - ast::stmt_decl(decl, id) => { - node_id = id; - match decl.node { - ast::decl_local(ls) => for ls.each |l| { - bot |= check_decl_local(fcx, *l); - }, - ast::decl_item(_) => {/* ignore for now */ } - } - } - ast::stmt_expr(expr, id) => { - node_id = id; - bot = check_expr_with(fcx, expr, ty::mk_nil(fcx.ccx.tcx)); - } - ast::stmt_semi(expr, id) => { - node_id = id; - bot = check_expr(fcx, expr, None); - } - ast::stmt_mac(*) => fcx.ccx.tcx.sess.bug(~"unexpanded macro") - } - fcx.write_nil(node_id); - return bot; -} - -fn check_block_no_value(fcx: @fn_ctxt, blk: ast::blk) -> bool { - let bot = check_block(fcx, blk); - if !bot { - let blkty = fcx.node_ty(blk.node.id); - let nilty = ty::mk_nil(fcx.ccx.tcx); - demand::suptype(fcx, blk.span, nilty, blkty); - } - return bot; -} - -fn check_block(fcx0: @fn_ctxt, blk: ast::blk) -> bool { - let fcx = match blk.node.rules { - ast::unsafe_blk => @fn_ctxt {purity: ast::unsafe_fn,.. *fcx0}, - ast::default_blk => fcx0 - }; - do fcx.with_region_lb(blk.node.id) { - let mut bot = false; - let mut warned = false; - for blk.node.stmts.each |s| { - if bot && !warned && - match s.node { - ast::stmt_decl(@{node: ast::decl_local(_), _}, _) | - ast::stmt_expr(_, _) | ast::stmt_semi(_, _) => { - true - } - _ => false - } { - fcx.ccx.tcx.sess.span_warn(s.span, ~"unreachable statement"); - warned = true; - } - bot |= check_stmt(fcx, *s); - } - match blk.node.expr { - None => fcx.write_nil(blk.node.id), - Some(e) => { - if bot && !warned { - fcx.ccx.tcx.sess.span_warn(e.span, ~"unreachable expression"); - } - bot |= check_expr(fcx, e, None); - let ety = fcx.expr_ty(e); - fcx.write_ty(blk.node.id, ety); - } - } - if bot { - fcx.write_bot(blk.node.id); - } - bot - } -} - -fn check_const(ccx: @crate_ctxt, _sp: span, e: @ast::expr, id: ast::node_id) { - let rty = ty::node_id_to_type(ccx.tcx, id); - let fcx = blank_fn_ctxt(ccx, rty, e.id); - let declty = fcx.ccx.tcx.tcache.get(local_def(id)).ty; - check_const_with_ty(fcx, _sp, e, declty); -} - -fn check_const_with_ty(fcx: @fn_ctxt, _sp: span, e: @ast::expr, - declty: ty::t) { - check_expr(fcx, e, None); - let cty = fcx.expr_ty(e); - demand::suptype(fcx, e.span, declty, cty); - regionck::regionck_expr(fcx, e); - writeback::resolve_type_vars_in_expr(fcx, e); -} - -/// Checks whether a type can be created without an instance of itself. -/// This is similar but different from the question of whether a type -/// can be represented. For example, the following type: -/// -/// enum foo { None, Some(foo) } -/// -/// is instantiable but is not representable. Similarly, the type -/// -/// enum foo { Some(@foo) } -/// -/// is representable, but not instantiable. -fn check_instantiable(tcx: ty::ctxt, - sp: span, - item_id: ast::node_id) { - let item_ty = ty::node_id_to_type(tcx, item_id); - if !ty::is_instantiable(tcx, item_ty) { - tcx.sess.span_err(sp, fmt!("this type cannot be instantiated \ - without an instance of itself; \ - consider using `option<%s>`", - ty_to_str(tcx, item_ty))); - } -} - -fn check_enum_variants(ccx: @crate_ctxt, - sp: span, - vs: ~[ast::variant], - id: ast::node_id) { - fn do_check(ccx: @crate_ctxt, sp: span, vs: ~[ast::variant], - id: ast::node_id, disr_vals: &mut ~[int], disr_val: &mut int, - variants: &mut ~[ty::variant_info]) { - let rty = ty::node_id_to_type(ccx.tcx, id); - for vs.each |v| { - do v.node.disr_expr.iter |e_ref| { - let e = *e_ref; - debug!("disr expr, checking %s", - expr_to_str(e, ccx.tcx.sess.intr())); - let declty = ty::mk_int(ccx.tcx); - let fcx = blank_fn_ctxt(ccx, rty, e.id); - check_const_with_ty(fcx, e.span, e, declty); - // check_expr (from check_const pass) doesn't guarantee - // that the expression is in an form that eval_const_expr can - // handle, so we may still get an internal compiler error - - match const_eval::eval_const_expr_partial(ccx.tcx, e) { - Ok(const_eval::const_int(val)) => { - *disr_val = val as int; - } - Ok(_) => { - ccx.tcx.sess.span_err(e.span, ~"expected signed integer \ - constant"); - } - Err(err) => { - ccx.tcx.sess.span_err(e.span, - fmt!("expected constant: %s", err)); - - } - } - } - if vec::contains(*disr_vals, &*disr_val) { - ccx.tcx.sess.span_err(v.span, - ~"discriminator value already exists"); - } - disr_vals.push(*disr_val); - let ctor_ty = ty::node_id_to_type(ccx.tcx, v.node.id); - let arg_tys; - - let this_disr_val = *disr_val; - *disr_val += 1; - - match v.node.kind { - ast::tuple_variant_kind(args) if args.len() > 0u => { - arg_tys = Some(ty::ty_fn_args(ctor_ty).map(|a| a.ty)); - } - ast::tuple_variant_kind(_) => { - arg_tys = Some(~[]); - } - ast::struct_variant_kind(_) => { - arg_tys = Some(ty::lookup_class_fields( - ccx.tcx, local_def(v.node.id)).map(|cf| - ty::node_id_to_type(ccx.tcx, cf.id.node))); - } - ast::enum_variant_kind(_) => { - arg_tys = None; - do_check(ccx, sp, vs, id, disr_vals, disr_val, variants); - } - } - - match arg_tys { - None => {} - Some(arg_tys) => { - variants.push( - @{args: arg_tys, ctor_ty: ctor_ty, - name: v.node.name, id: local_def(v.node.id), - disr_val: this_disr_val}); - } - } - } - } - - let rty = ty::node_id_to_type(ccx.tcx, id); - let mut disr_vals: ~[int] = ~[]; - let mut disr_val = 0; - let mut variants = ~[]; - - do_check(ccx, sp, vs, id, &mut disr_vals, &mut disr_val, &mut variants); - - // cache so that ty::enum_variants won't repeat this work - ccx.tcx.enum_var_cache.insert(local_def(id), @variants); - - // Check that it is possible to represent this enum: - let mut outer = true, did = local_def(id); - if ty::type_structurally_contains(ccx.tcx, rty, |sty| { - match *sty { - ty::ty_enum(id, _) if id == did => { - if outer { outer = false; false } - else { true } - } - _ => false - } - }) { - ccx.tcx.sess.span_err(sp, ~"illegal recursive enum type; \ - wrap the inner value in a box to \ - make it representable"); - } - - // Check that it is possible to instantiate this enum: - // - // This *sounds* like the same that as representable, but it's - // not. See def'n of `check_instantiable()` for details. - check_instantiable(ccx.tcx, sp, id); -} - -fn lookup_local(fcx: @fn_ctxt, sp: span, id: ast::node_id) -> TyVid { - match fcx.inh.locals.find(id) { - Some(x) => x, - _ => { - fcx.ccx.tcx.sess.span_fatal( - sp, - ~"internal error looking up a local var") - } - } -} - -fn lookup_def(fcx: @fn_ctxt, sp: span, id: ast::node_id) -> ast::def { - lookup_def_ccx(fcx.ccx, sp, id) -} - -// Returns the type parameter count and the type for the given definition. -fn ty_param_bounds_and_ty_for_def(fcx: @fn_ctxt, sp: span, defn: ast::def) -> - ty_param_bounds_and_ty { - - match defn { - ast::def_arg(nid, _) | ast::def_local(nid, _) | - ast::def_self(nid) | ast::def_binding(nid, _) => { - assert (fcx.inh.locals.contains_key(nid)); - let typ = ty::mk_var(fcx.ccx.tcx, lookup_local(fcx, sp, nid)); - return no_params(typ); - } - ast::def_fn(_, ast::extern_fn) => { - // extern functions are just u8 pointers - return { - bounds: @~[], - region_param: None, - ty: ty::mk_ptr( - fcx.ccx.tcx, - { - ty: ty::mk_mach_uint(fcx.ccx.tcx, ast::ty_u8), - mutbl: ast::m_imm - }) - }; - } - - ast::def_fn(id, ast::unsafe_fn) | - ast::def_static_method(id, _, ast::unsafe_fn) => { - // Unsafe functions can only be touched in an unsafe context - fcx.require_unsafe(sp, ~"access to unsafe function"); - return ty::lookup_item_type(fcx.ccx.tcx, id); - } - - ast::def_fn(id, _) | ast::def_static_method(id, _, _) | - ast::def_const(id) | ast::def_variant(_, id) | - ast::def_class(id) => { - return ty::lookup_item_type(fcx.ccx.tcx, id); - } - ast::def_upvar(_, inner, _, _) => { - return ty_param_bounds_and_ty_for_def(fcx, sp, *inner); - } - ast::def_ty(_) | ast::def_prim_ty(_) | ast::def_ty_param(*)=> { - fcx.ccx.tcx.sess.span_bug(sp, ~"expected value but found type"); - } - ast::def_mod(*) | ast::def_foreign_mod(*) => { - fcx.ccx.tcx.sess.span_bug(sp, ~"expected value but found module"); - } - ast::def_use(*) => { - fcx.ccx.tcx.sess.span_bug(sp, ~"expected value but found use"); - } - ast::def_region(*) => { - fcx.ccx.tcx.sess.span_bug(sp, ~"expected value but found region"); - } - ast::def_typaram_binder(*) => { - fcx.ccx.tcx.sess.span_bug(sp, ~"expected value but found type \ - parameter"); - } - ast::def_label(*) => { - fcx.ccx.tcx.sess.span_bug(sp, ~"expected value but found label"); - } - } -} - -// Instantiates the given path, which must refer to an item with the given -// number of type parameters and type. -fn instantiate_path(fcx: @fn_ctxt, - pth: @ast::path, - tpt: ty_param_bounds_and_ty, - span: span, - node_id: ast::node_id, - region_lb: ty::Region) { - let ty_param_count = vec::len(*tpt.bounds); - let ty_substs_len = vec::len(pth.types); - - // determine the region bound, using the value given by the user - // (if any) and otherwise using a fresh region variable - let self_r = match pth.rp { - Some(r) => { - match tpt.region_param { - None => { - fcx.ccx.tcx.sess.span_err - (span, ~"this item is not region-parameterized"); - None - } - Some(_) => { - Some(ast_region_to_region(fcx, fcx, span, r)) - } - } - } - None => { - fcx.region_var_if_parameterized( - tpt.region_param, span, region_lb) - } - }; - - // determine values for type parameters, using the values given by - // the user (if any) and otherwise using fresh type variables - let tps = if ty_substs_len == 0 { - fcx.infcx().next_ty_vars(ty_param_count) - } else if ty_param_count == 0 { - fcx.ccx.tcx.sess.span_err - (span, ~"this item does not take type parameters"); - fcx.infcx().next_ty_vars(ty_param_count) - } else if ty_substs_len > ty_param_count { - fcx.ccx.tcx.sess.span_err - (span, ~"too many type parameters provided for this item"); - fcx.infcx().next_ty_vars(ty_param_count) - } else if ty_substs_len < ty_param_count { - fcx.ccx.tcx.sess.span_err - (span, ~"not enough type parameters provided for this item"); - fcx.infcx().next_ty_vars(ty_param_count) - } else { - pth.types.map(|aty| fcx.to_ty(*aty)) - }; - - let substs = {self_r: self_r, self_ty: None, tps: tps}; - fcx.write_ty_substs(node_id, tpt.ty, substs); -} - -// Resolves `typ` by a single level if `typ` is a type variable. If no -// resolution is possible, then an error is reported. -fn structurally_resolved_type(fcx: @fn_ctxt, sp: span, tp: ty::t) -> ty::t { - match infer::resolve_type(fcx.infcx(), tp, force_tvar) { - Ok(t_s) if !ty::type_is_ty_var(t_s) => return t_s, - _ => { - fcx.type_error_message(sp, |_actual| { - ~"the type of this value must be known in this context" - }, tp, None); - return ty::mk_err(fcx.tcx()); - } - } -} - -// Returns the one-level-deep structure of the given type. -fn structure_of(fcx: @fn_ctxt, sp: span, typ: ty::t) -> ty::sty { - ty::get(structurally_resolved_type(fcx, sp, typ)).sty -} - -fn type_is_integral(fcx: @fn_ctxt, sp: span, typ: ty::t) -> bool { - let typ_s = structurally_resolved_type(fcx, sp, typ); - return ty::type_is_integral(typ_s); -} - -fn type_is_scalar(fcx: @fn_ctxt, sp: span, typ: ty::t) -> bool { - let typ_s = structurally_resolved_type(fcx, sp, typ); - return ty::type_is_scalar(typ_s); -} - -fn type_is_c_like_enum(fcx: @fn_ctxt, sp: span, typ: ty::t) -> bool { - let typ_s = structurally_resolved_type(fcx, sp, typ); - return ty::type_is_c_like_enum(fcx.ccx.tcx, typ_s); -} - -fn ast_expr_vstore_to_vstore(fcx: @fn_ctxt, e: @ast::expr, n: uint, - v: ast::expr_vstore) -> ty::vstore { - match v { - ast::expr_vstore_fixed(None) => ty::vstore_fixed(n), - ast::expr_vstore_fixed(Some(u)) => { - if n != u { - let s = fmt!("fixed-size sequence mismatch: %u vs. %u",u, n); - fcx.ccx.tcx.sess.span_err(e.span,s); - } - ty::vstore_fixed(u) - } - ast::expr_vstore_uniq => ty::vstore_uniq, - ast::expr_vstore_box | ast::expr_vstore_mut_box => ty::vstore_box, - ast::expr_vstore_slice => { - let r = fcx.infcx().next_region_var(e.span, e.id); - ty::vstore_slice(r) - } - } -} - -// Returns true if b contains a break that can exit from b -fn may_break(cx: ty::ctxt, id: ast::node_id, b: ast::blk) -> bool { - // First: is there an unlabeled break immediately - // inside the loop? - (loop_query(b, |e| { - match e { - ast::expr_break(_) => true, - _ => false - } - })) || - // Second: is there a labeled break with label - // nested anywhere inside the loop? - (block_query(b, |e| { - match e.node { - ast::expr_break(Some(_)) => - match cx.def_map.find(e.id) { - Some(ast::def_label(loop_id)) if id == loop_id => true, - _ => false, - }, - _ => false - }})) -} - -fn check_bounds_are_used(ccx: @crate_ctxt, - span: span, - tps: ~[ast::ty_param], - ty: ty::t) { - debug!("check_bounds_are_used(n_tps=%u, ty=%s)", - tps.len(), ty_to_str(ccx.tcx, ty)); - - // make a vector of booleans initially false, set to true when used - if tps.len() == 0u { return; } - let tps_used = vec::to_mut(vec::from_elem(tps.len(), false)); - - ty::walk_regions_and_ty( - ccx.tcx, ty, - |_r| {}, - |t| { - match ty::get(t).sty { - ty::ty_param({idx, _}) => { - debug!("Found use of ty param #%u", idx); - tps_used[idx] = true; - } - _ => () - } - true - }); - - for tps_used.eachi |i, b| { - if !*b { - ccx.tcx.sess.span_err( - span, fmt!("type parameter `%s` is unused", - ccx.tcx.sess.str_of(tps[i].ident))); - } - } -} - -fn check_intrinsic_type(ccx: @crate_ctxt, it: @ast::foreign_item) { - fn param(ccx: @crate_ctxt, n: uint) -> ty::t { - ty::mk_param(ccx.tcx, n, local_def(0)) - } - fn arg(m: ast::rmode, ty: ty::t) -> ty::arg { - {mode: ast::expl(m), ty: ty} - } - let tcx = ccx.tcx; - let (n_tps, inputs, output) = match ccx.tcx.sess.str_of(it.ident) { - ~"size_of" | - ~"pref_align_of" | ~"min_align_of" => (1u, ~[], ty::mk_uint(ccx.tcx)), - ~"init" => (1u, ~[], param(ccx, 0u)), - ~"forget" => (1u, ~[arg(ast::by_move, param(ccx, 0u))], - ty::mk_nil(tcx)), - ~"reinterpret_cast" => (2u, ~[arg(ast::by_ref, param(ccx, 0u))], - param(ccx, 1u)), - ~"addr_of" => (1u, ~[arg(ast::by_ref, param(ccx, 0u))], - ty::mk_imm_ptr(tcx, param(ccx, 0u))), - ~"move_val" | ~"move_val_init" => { - (1u, ~[arg(ast::by_copy, - ty::mk_mut_rptr(tcx, ty::re_bound(ty::br_anon(0)), - param(ccx, 0u))), - arg(ast::by_move, param(ccx, 0u))], - ty::mk_nil(tcx)) - } - ~"needs_drop" => (1u, ~[], ty::mk_bool(tcx)), - - ~"atomic_cxchg" | ~"atomic_cxchg_acq"| ~"atomic_cxchg_rel" => { - (0u, ~[arg(ast::by_copy, - ty::mk_mut_rptr(tcx, ty::re_bound(ty::br_anon(0)), - ty::mk_int(tcx))), - arg(ast::by_copy, ty::mk_int(tcx)), - arg(ast::by_copy, ty::mk_int(tcx))], - ty::mk_int(tcx)) - } - ~"atomic_xchg" | ~"atomic_xadd" | ~"atomic_xsub" | - ~"atomic_xchg_acq" | ~"atomic_xadd_acq" | ~"atomic_xsub_acq" | - ~"atomic_xchg_rel" | ~"atomic_xadd_rel" | ~"atomic_xsub_rel" => { - (0u, ~[arg(ast::by_copy, - ty::mk_mut_rptr(tcx, ty::re_bound(ty::br_anon(0)), - ty::mk_int(tcx))), - arg(ast::by_copy, ty::mk_int(tcx))], - ty::mk_int(tcx)) - } - - ~"get_tydesc" => { - // FIXME (#3730): return *intrinsic::tydesc, not *() - (1u, ~[], ty::mk_nil_ptr(tcx)) - } - ~"visit_tydesc" => { - let tydesc_name = syntax::parse::token::special_idents::tydesc; - let ty_visitor_name = tcx.sess.ident_of(~"TyVisitor"); - assert tcx.intrinsic_defs.contains_key(tydesc_name); - assert ccx.tcx.intrinsic_defs.contains_key(ty_visitor_name); - let (_, tydesc_ty) = tcx.intrinsic_defs.get(tydesc_name); - let (_, visitor_trait) = tcx.intrinsic_defs.get(ty_visitor_name); - let td_ptr = ty::mk_ptr(ccx.tcx, {ty: tydesc_ty, - mutbl: ast::m_imm}); - (0u, ~[arg(ast::by_val, td_ptr), - arg(ast::by_ref, visitor_trait)], ty::mk_nil(tcx)) - } - ~"frame_address" => { - let fty = ty::mk_fn(ccx.tcx, FnTyBase { - meta: FnMeta {purity: ast::impure_fn, - proto: ast::ProtoBorrowed, - onceness: ast::Once, - region: ty::re_bound(ty::br_anon(0)), - bounds: @~[], - ret_style: ast::return_val}, - sig: FnSig {inputs: ~[{mode: ast::expl(ast::by_val), - ty: ty::mk_imm_ptr( - ccx.tcx, - ty::mk_mach_uint(ccx.tcx, ast::ty_u8)) - }], - output: ty::mk_nil(ccx.tcx)} - }); - (0u, ~[arg(ast::by_ref, fty)], ty::mk_nil(tcx)) - } - ~"morestack_addr" => { - (0u, ~[], ty::mk_nil_ptr(tcx)) - } - other => { - tcx.sess.span_err(it.span, ~"unrecognized intrinsic function: `" + - other + ~"`"); - return; - } - }; - let fty = ty::mk_fn(tcx, FnTyBase { - meta: FnMeta {purity: ast::impure_fn, - proto: ast::ProtoBare, - onceness: ast::Many, - region: ty::re_static, - bounds: @~[], - ret_style: ast::return_val}, - sig: FnSig {inputs: inputs, - output: output} - }); - let i_ty = ty::lookup_item_type(ccx.tcx, local_def(it.id)); - let i_n_tps = (*i_ty.bounds).len(); - if i_n_tps != n_tps { - tcx.sess.span_err(it.span, fmt!("intrinsic has wrong number \ - of type parameters: found %u, \ - expected %u", i_n_tps, n_tps)); - } else { - require_same_types( - tcx, None, false, it.span, i_ty.ty, fty, - || fmt!("intrinsic has wrong type: \ - expected `%s`", - ty_to_str(ccx.tcx, fty))); - } -} diff --git a/src/librustc/middle/typeck/check/mod.rs b/src/librustc/middle/typeck/check/mod.rs index 6e999aeb0af..1c2cb4604e5 100644 --- a/src/librustc/middle/typeck/check/mod.rs +++ b/src/librustc/middle/typeck/check/mod.rs @@ -1,3 +1,87 @@ +/* + +# check.rs + +Within the check phase of type check, we check each item one at a time +(bodies of function expressions are checked as part of the containing +function). Inference is used to supply types wherever they are +unknown. + +By far the most complex case is checking the body of a function. This +can be broken down into several distinct phases: + +- gather: creates type variables to represent the type of each local + variable and pattern binding. + +- main: the main pass does the lion's share of the work: it + determines the types of all expressions, resolves + methods, checks for most invalid conditions, and so forth. In + some cases, where a type is unknown, it may create a type or region + variable and use that as the type of an expression. + + In the process of checking, various constraints will be placed on + these type variables through the subtyping relationships requested + through the `demand` module. The `typeck::infer` module is in charge + of resolving those constraints. + +- regionck: after main is complete, the regionck pass goes over all + types looking for regions and making sure that they did not escape + into places they are not in scope. This may also influence the + final assignments of the various region variables if there is some + flexibility. + +- vtable: find and records the impls to use for each trait bound that + appears on a type parameter. + +- writeback: writes the final types within a function body, replacing + type variables with their final inferred types. These final types + are written into the `tcx.node_types` table, which should *never* contain + any reference to a type variable. + +## Intermediate types + +While type checking a function, the intermediate types for the +expressions, blocks, and so forth contained within the function are +stored in `fcx.node_types` and `fcx.node_type_substs`. These types +may contain unresolved type variables. After type checking is +complete, the functions in the writeback module are used to take the +types from this table, resolve them, and then write them into their +permanent home in the type context `ccx.tcx`. + +This means that during inferencing you should use `fcx.write_ty()` +and `fcx.expr_ty()` / `fcx.node_ty()` to write/obtain the types of +nodes within the function. + +The types of top-level items, which never contain unbound type +variables, are stored directly into the `tcx` tables. + +n.b.: A type variable is not the same thing as a type parameter. A +type variable is rather an "instance" of a type parameter: that is, +given a generic function `fn foo(t: T)`: while checking the +function `foo`, the type `ty_param(0)` refers to the type `T`, which +is treated in abstract. When `foo()` is called, however, `T` will be +substituted for a fresh type variable `N`. This variable will +eventually be resolved to some concrete type (which might itself be +type parameter). + +*/ + +use astconv::{ast_conv, ast_path_to_ty, ast_ty_to_ty}; +use astconv::{ast_region_to_region}; +use middle::ty::{TyVid, vid, FnTyBase, FnMeta, FnSig}; +use regionmanip::{replace_bound_regions_in_fn_ty}; +use rscope::{anon_rscope, binding_rscope, empty_rscope, in_anon_rscope}; +use rscope::{in_binding_rscope, region_scope, type_rscope, + bound_self_region}; +use syntax::ast::ty_i; +use typeck::infer::{resolve_type, force_tvar}; +use result::{Result, Ok, Err}; +use syntax::print::pprust; +use syntax::parse::token::special_idents; +use vtable::{LocationInfo, VtableContext}; + +use std::map::HashMap; + #[legacy_exports] mod alt; #[legacy_exports] @@ -12,3 +96,2825 @@ mod regionck; mod demand; #[legacy_exports] pub mod method; + +type self_info = { + self_ty: ty::t, + self_id: ast::node_id, + def_id: ast::def_id, + explicit_self: ast::self_ty +}; + +/// Fields that are part of a `fn_ctxt` which are inherited by +/// closures defined within the function. For example: +/// +/// fn foo() { +/// do bar() { ... } +/// } +/// +/// Here, the function `foo()` and the closure passed to +/// `bar()` will each have their own `fn_ctxt`, but they will +/// share the inherited fields. +struct inherited { + infcx: infer::infer_ctxt, + locals: HashMap, + node_types: HashMap, + node_type_substs: HashMap, + adjustments: HashMap +} + +struct fn_ctxt { + // var_bindings, locals and next_var_id are shared + // with any nested functions that capture the environment + // (and with any functions whose environment is being captured). + self_impl_def_id: Option, + ret_ty: ty::t, + // Used by loop bodies that return from the outer function + indirect_ret_ty: Option, + purity: ast::purity, + + // Sometimes we generate region pointers where the precise region + // to use is not known. For example, an expression like `&x.f` + // where `x` is of type `@T`: in this case, we will be rooting + // `x` onto the stack frame, and we could choose to root it until + // the end of (almost) any enclosing block or expression. We + // want to pick the narrowest block that encompasses all uses. + // + // What we do in such cases is to generate a region variable with + // `region_lb` as a lower bound. The regionck pass then adds + // other constriants based on how the variable is used and region + // inference selects the ultimate value. Finally, borrowck is + // charged with guaranteeing that the value whose address was taken + // can actually be made to live as long as it needs to live. + mut region_lb: ast::node_id, + + in_scope_regions: isr_alist, + + inh: @inherited, + + ccx: @crate_ctxt, +} + +fn blank_inherited(ccx: @crate_ctxt) -> @inherited { + @inherited { + infcx: infer::new_infer_ctxt(ccx.tcx), + locals: HashMap(), + node_types: map::HashMap(), + node_type_substs: map::HashMap(), + adjustments: map::HashMap() + } +} + +// Used by check_const and check_enum_variants +fn blank_fn_ctxt(ccx: @crate_ctxt, rty: ty::t, + region_bnd: ast::node_id) -> @fn_ctxt { +// It's kind of a kludge to manufacture a fake function context +// and statement context, but we might as well do write the code only once + @fn_ctxt { + self_impl_def_id: None, + ret_ty: rty, + indirect_ret_ty: None, + purity: ast::pure_fn, + mut region_lb: region_bnd, + in_scope_regions: @Nil, + inh: blank_inherited(ccx), + ccx: ccx + } +} + +fn check_item_types(ccx: @crate_ctxt, crate: @ast::crate) { + let visit = visit::mk_simple_visitor(@{ + visit_item: |a| check_item(ccx, a), + .. *visit::default_simple_visitor() + }); + visit::visit_crate(*crate, (), visit); +} + +fn check_bare_fn(ccx: @crate_ctxt, + decl: ast::fn_decl, + body: ast::blk, + id: ast::node_id, + self_info: Option) { + let fty = ty::node_id_to_type(ccx.tcx, id); + match ty::get(fty).sty { + ty::ty_fn(ref fn_ty) => { + check_fn(ccx, self_info, fn_ty, decl, body, false, None) + } + _ => ccx.tcx.sess.impossible_case(body.span, + "check_bare_fn: function type expected") + } +} + +fn check_fn(ccx: @crate_ctxt, + self_info: Option, + fn_ty: &ty::FnTy, + decl: ast::fn_decl, + body: ast::blk, + indirect_ret: bool, + old_fcx: Option<@fn_ctxt>) { + + let tcx = ccx.tcx; + + // ______________________________________________________________________ + // First, we have to replace any bound regions in the fn and self + // types with free ones. The free region references will be bound + // the node_id of the body block. + + let {isr, self_info, fn_ty} = { + let old_isr = option::map_default(&old_fcx, @Nil, + |fcx| fcx.in_scope_regions); + replace_bound_regions_in_fn_ty(tcx, old_isr, self_info, fn_ty, + |br| ty::re_free(body.node.id, br)) + }; + + let arg_tys = fn_ty.sig.inputs.map(|a| a.ty); + let ret_ty = fn_ty.sig.output; + + debug!("check_fn(arg_tys=%?, ret_ty=%?, self_info.self_ty=%?)", + arg_tys.map(|a| ty_to_str(tcx, *a)), + ty_to_str(tcx, ret_ty), + option::map(&self_info, |s| ty_to_str(tcx, s.self_ty))); + + // ______________________________________________________________________ + // Create the function context. This is either derived from scratch or, + // in the case of function expressions, based on the outer context. + let fcx: @fn_ctxt = { + let (purity, inherited) = match old_fcx { + None => { + (fn_ty.meta.purity, + blank_inherited(ccx)) + } + Some(fcx) => { + (ty::determine_inherited_purity(fcx.purity, fn_ty.meta.purity, + fn_ty.meta.proto), + fcx.inh) + } + }; + + let indirect_ret_ty = if indirect_ret { + let ofcx = old_fcx.get(); + match ofcx.indirect_ret_ty { + Some(t) => Some(t), + None => Some(ofcx.ret_ty) + } + } else { None }; + + @fn_ctxt { + self_impl_def_id: self_info.map(|self_info| self_info.def_id), + ret_ty: ret_ty, + indirect_ret_ty: indirect_ret_ty, + purity: purity, + mut region_lb: body.node.id, + in_scope_regions: isr, + inh: inherited, + ccx: ccx + } + }; + + // Update the self_info to contain an accurate self type (taking + // into account explicit self). + let self_info = do self_info.chain_ref |self_info| { + // If the self type is sty_static, we don't have a self ty. + if self_info.explicit_self.node == ast::sty_static { + None + } else { + let self_region = fcx.in_scope_regions.find(ty::br_self); + let ty = method::transform_self_type_for_method( + fcx.tcx(), self_region, + self_info.self_ty, self_info.explicit_self.node); + Some({self_ty: ty,.. *self_info}) + } + }; + + gather_locals(fcx, decl, body, arg_tys, self_info); + check_block(fcx, body); + + // We unify the tail expr's type with the + // function result type, if there is a tail expr. + match body.node.expr { + Some(tail_expr) => { + let tail_expr_ty = fcx.expr_ty(tail_expr); + demand::suptype(fcx, tail_expr.span, fcx.ret_ty, tail_expr_ty); + } + None => () + } + + for self_info.each |self_info| { + fcx.write_ty(self_info.self_id, self_info.self_ty); + } + for vec::each2(decl.inputs, arg_tys) |input, arg| { + fcx.write_ty(input.id, *arg); + } + + // If we don't have any enclosing function scope, it is time to + // force any remaining type vars to be resolved. + // If we have an enclosing function scope, our type variables will be + // resolved when the enclosing scope finishes up. + if old_fcx.is_none() { + vtable::resolve_in_block(fcx, body); + regionck::regionck_fn(fcx, decl, body); + writeback::resolve_type_vars_in_fn(fcx, decl, body, self_info); + } + + fn gather_locals(fcx: @fn_ctxt, + decl: ast::fn_decl, + body: ast::blk, + arg_tys: ~[ty::t], + self_info: Option) { + let tcx = fcx.ccx.tcx; + + let assign = fn@(span: span, nid: ast::node_id, + ty_opt: Option) { + let var_id = fcx.infcx().next_ty_var_id(); + fcx.inh.locals.insert(nid, var_id); + match ty_opt { + None => {/* nothing to do */ } + Some(typ) => { + infer::mk_eqty(fcx.infcx(), false, span, + ty::mk_var(tcx, var_id), typ); + } + } + }; + + // Add the self parameter + for self_info.each |self_info| { + assign(self_info.explicit_self.span, + self_info.self_id, + Some(self_info.self_ty)); + debug!("self is assigned to %s", + fcx.inh.locals.get(self_info.self_id).to_str()); + } + + // Add formal parameters. + for vec::each2(arg_tys, decl.inputs) |arg_ty, input| { + // Create type variables for each argument. + do pat_util::pat_bindings(tcx.def_map, input.pat) + |_bm, pat_id, _sp, _path| { + assign(input.ty.span, pat_id, None); + } + + // Check the pattern. + let region = fcx.block_region(); + let pcx = { + fcx: fcx, + map: pat_id_map(tcx.def_map, input.pat), + alt_region: region, + block_region: region, + }; + alt::check_pat(pcx, input.pat, *arg_ty); + } + + // Add explicitly-declared locals. + let visit_local = fn@(local: @ast::local, + &&e: (), v: visit::vt<()>) { + let o_ty = match local.node.ty.node { + ast::ty_infer => None, + _ => Some(fcx.to_ty(local.node.ty)) + }; + assign(local.span, local.node.id, o_ty); + debug!("Local variable %s is assigned to %s", + pat_to_str(local.node.pat, tcx.sess.intr()), + fcx.inh.locals.get(local.node.id).to_str()); + visit::visit_local(local, e, v); + }; + + // Add pattern bindings. + let visit_pat = fn@(p: @ast::pat, &&e: (), v: visit::vt<()>) { + match p.node { + ast::pat_ident(_, path, _) + if pat_util::pat_is_binding(fcx.ccx.tcx.def_map, p) => { + assign(p.span, p.id, None); + debug!("Pattern binding %s is assigned to %s", + tcx.sess.str_of(path.idents[0]), + fcx.inh.locals.get(p.id).to_str()); + } + _ => {} + } + visit::visit_pat(p, e, v); + }; + + let visit_block = fn@(b: ast::blk, &&e: (), v: visit::vt<()>) { + // non-obvious: the `blk` variable maps to region lb, so + // we have to keep this up-to-date. This + // is... unfortunate. It'd be nice to not need this. + do fcx.with_region_lb(b.node.id) { + visit::visit_block(b, e, v); + } + }; + + // Don't descend into fns and items + fn visit_fn(_fk: visit::fn_kind, _decl: ast::fn_decl, + _body: ast::blk, _sp: span, + _id: ast::node_id, &&_t: (), _v: visit::vt<()>) { + } + fn visit_item(_i: @ast::item, &&_e: (), _v: visit::vt<()>) { } + + let visit = visit::mk_vt(@{visit_local: visit_local, + visit_pat: visit_pat, + visit_fn: visit_fn, + visit_item: visit_item, + visit_block: visit_block, + .. *visit::default_visitor()}); + + visit.visit_block(body, (), visit); + } +} + +fn check_method(ccx: @crate_ctxt, method: @ast::method, + self_ty: ty::t, self_impl_def_id: ast::def_id) { + let self_info = {self_ty: self_ty, + self_id: method.self_id, + def_id: self_impl_def_id, + explicit_self: method.self_ty }; + check_bare_fn(ccx, method.decl, method.body, method.id, Some(self_info)); +} + +fn check_no_duplicate_fields(tcx: ty::ctxt, fields: + ~[(ast::ident, span)]) { + let field_names = HashMap(); + + for fields.each |p| { + let (id, sp) = *p; + match field_names.find(id) { + Some(orig_sp) => { + tcx.sess.span_err(sp, fmt!("Duplicate field \ + name %s in record type declaration", + tcx.sess.str_of(id))); + tcx.sess.span_note(orig_sp, ~"First declaration of \ + this field occurred here"); + break; + } + None => { + field_names.insert(id, sp); + } + } + } + +} + +fn check_struct(ccx: @crate_ctxt, struct_def: @ast::struct_def, + id: ast::node_id, span: span) { + let tcx = ccx.tcx; + let self_ty = ty::node_id_to_type(tcx, id); + + do option::iter(&struct_def.dtor) |dtor| { + let class_t = {self_ty: self_ty, + self_id: dtor.node.self_id, + def_id: local_def(id), + explicit_self: {node: ast::sty_by_ref, + span: ast_util::dummy_sp()}}; + // typecheck the dtor + check_bare_fn(ccx, ast_util::dtor_dec(), + dtor.node.body, dtor.node.id, + Some(class_t)); + }; + + // typecheck the methods + for struct_def.methods.each |m| { + check_method(ccx, *m, self_ty, local_def(id)); + } + // Check that the class is instantiable + check_instantiable(ccx.tcx, span, id); +} + +fn check_item(ccx: @crate_ctxt, it: @ast::item) { + debug!("check_item(it.id=%d, it.ident=%s)", + it.id, + ty::item_path_str(ccx.tcx, local_def(it.id))); + let _indenter = indenter(); + + match it.node { + ast::item_const(_, e) => check_const(ccx, it.span, e, it.id), + ast::item_enum(enum_definition, _) => { + check_enum_variants(ccx, it.span, enum_definition.variants, it.id); + } + ast::item_fn(decl, _, _, body) => { + check_bare_fn(ccx, decl, body, it.id, None); + } + ast::item_impl(_, _, ty, ms) => { + let rp = ccx.tcx.region_paramd_items.find(it.id); + debug!("item_impl %s with id %d rp %?", + ccx.tcx.sess.str_of(it.ident), it.id, rp); + let self_ty = ccx.to_ty(rscope::type_rscope(rp), ty); + for ms.each |m| { + check_method(ccx, *m, self_ty, local_def(it.id)); + } + } + ast::item_trait(_, _, trait_methods) => { + for trait_methods.each |trait_method| { + match *trait_method { + required(*) => { + // Nothing to do, since required methods don't have + // bodies to check. + } + provided(m) => { + check_method(ccx, m, ty::mk_self(ccx.tcx), local_def(it.id)); + } + } + } + } + ast::item_class(struct_def, _) => { + check_struct(ccx, struct_def, it.id, it.span); + } + ast::item_ty(t, tps) => { + let tpt_ty = ty::node_id_to_type(ccx.tcx, it.id); + check_bounds_are_used(ccx, t.span, tps, tpt_ty); + // If this is a record ty, check for duplicate fields + match t.node { + ast::ty_rec(fields) => { + check_no_duplicate_fields(ccx.tcx, fields.map(|f| + (f.node.ident, f.span))); + } + _ => () + } + } + ast::item_foreign_mod(m) => { + if syntax::attr::foreign_abi(it.attrs) == + either::Right(ast::foreign_abi_rust_intrinsic) { + for m.items.each |item| { + check_intrinsic_type(ccx, *item); + } + } else { + for m.items.each |item| { + let tpt = ty::lookup_item_type(ccx.tcx, local_def(item.id)); + if (*tpt.bounds).is_not_empty() { + ccx.tcx.sess.span_err( + item.span, + fmt!("foreign items may not have type parameters")); + } + } + } + } + _ => {/* nothing to do */ } + } +} + +impl @fn_ctxt: ast_conv { + fn infcx() -> infer::infer_ctxt { self.inh.infcx } + fn tcx() -> ty::ctxt { self.ccx.tcx } + fn ccx() -> @crate_ctxt { self.ccx } + + fn get_item_ty(id: ast::def_id) -> ty::ty_param_bounds_and_ty { + ty::lookup_item_type(self.tcx(), id) + } + + fn ty_infer(_span: span) -> ty::t { + self.infcx().next_ty_var() + } +} + +impl @fn_ctxt { + fn search_in_scope_regions(br: ty::bound_region) + -> Result + { + match self.in_scope_regions.find(br) { + Some(r) => result::Ok(r), + None => { + let blk_br = ty::br_named(special_idents::blk); + if br == blk_br { + result::Ok(self.block_region()) + } else { + result::Err(fmt!("named region `%s` not in scope here", + bound_region_to_str(self.tcx(), br))) + } + } + } + } +} + +impl @fn_ctxt: region_scope { + fn anon_region(span: span) -> Result { + result::Ok(self.infcx().next_region_var_nb(span)) + } + fn self_region(_span: span) -> Result { + self.search_in_scope_regions(ty::br_self) + } + fn named_region(_span: span, id: ast::ident) -> Result { + self.search_in_scope_regions(ty::br_named(id)) + } +} + +impl @fn_ctxt { + fn tag() -> ~str { fmt!("%x", ptr::addr_of(&(*self)) as uint) } + + fn expr_to_str(expr: @ast::expr) -> ~str { + fmt!("expr(%?:%s)", expr.id, + pprust::expr_to_str(expr, self.tcx().sess.intr())) + } + + fn block_region() -> ty::Region { + ty::re_scope(self.region_lb) + } + + #[inline(always)] + fn write_ty(node_id: ast::node_id, ty: ty::t) { + debug!("write_ty(%d, %s) in fcx %s", + node_id, ty_to_str(self.tcx(), ty), self.tag()); + self.inh.node_types.insert(node_id, ty); + } + + fn write_substs(node_id: ast::node_id, +substs: ty::substs) { + if !ty::substs_is_noop(&substs) { + debug!("write_substs(%d, %s) in fcx %s", + node_id, + ty::substs_to_str(self.tcx(), &substs), + self.tag()); + self.inh.node_type_substs.insert(node_id, substs); + } + } + + fn write_ty_substs(node_id: ast::node_id, ty: ty::t, + +substs: ty::substs) { + let ty = ty::subst(self.tcx(), &substs, ty); + self.write_ty(node_id, ty); + self.write_substs(node_id, substs); + } + + fn write_autoderef_adjustment(node_id: ast::node_id, derefs: uint) { + if derefs == 0 { return; } + self.write_adjustment(node_id, @{autoderefs: derefs, autoref: None}); + } + + fn write_adjustment(node_id: ast::node_id, adj: @ty::AutoAdjustment) { + debug!("write_adjustment(node_id=%?, adj=%?)", node_id, adj); + self.inh.adjustments.insert(node_id, adj); + } + + fn write_nil(node_id: ast::node_id) { + self.write_ty(node_id, ty::mk_nil(self.tcx())); + } + fn write_bot(node_id: ast::node_id) { + self.write_ty(node_id, ty::mk_bot(self.tcx())); + } + + fn to_ty(ast_t: @ast::Ty) -> ty::t { + ast_ty_to_ty(self, self, ast_t) + } + + fn expr_to_str(expr: @ast::expr) -> ~str { + expr_repr(self.tcx(), expr) + } + + fn expr_ty(ex: @ast::expr) -> ty::t { + match self.inh.node_types.find(ex.id) { + Some(t) => t, + None => { + self.tcx().sess.bug( + fmt!("no type for %s in fcx %s", + self.expr_to_str(ex), self.tag())); + } + } + } + fn node_ty(id: ast::node_id) -> ty::t { + match self.inh.node_types.find(id) { + Some(t) => t, + None => { + self.tcx().sess.bug( + fmt!("no type for node %d: %s in fcx %s", + id, ast_map::node_id_to_str( + self.tcx().items, id, + self.tcx().sess.parse_sess.interner), + self.tag())); + } + } + } + fn node_ty_substs(id: ast::node_id) -> ty::substs { + match self.inh.node_type_substs.find(id) { + Some(ts) => ts, + None => { + self.tcx().sess.bug( + fmt!("no type substs for node %d: %s in fcx %s", + id, ast_map::node_id_to_str( + self.tcx().items, id, + self.tcx().sess.parse_sess.interner), + self.tag())); + } + } + } + fn opt_node_ty_substs(id: ast::node_id) -> Option { + self.inh.node_type_substs.find(id) + } + + + fn mk_subty(a_is_expected: bool, span: span, + sub: ty::t, sup: ty::t) -> Result<(), ty::type_err> { + infer::mk_subty(self.infcx(), a_is_expected, span, sub, sup) + } + + fn can_mk_subty(sub: ty::t, sup: ty::t) -> Result<(), ty::type_err> { + infer::can_mk_subty(self.infcx(), sub, sup) + } + + fn mk_assignty(expr: @ast::expr, sub: ty::t, sup: ty::t) + -> Result<(), ty::type_err> + { + match infer::mk_assignty(self.infcx(), false, expr.span, sub, sup) { + Ok(None) => result::Ok(()), + Err(e) => result::Err(e), + Ok(Some(adjustment)) => { + self.write_adjustment(expr.id, adjustment); + Ok(()) + } + } + } + + fn can_mk_assignty(sub: ty::t, sup: ty::t) -> Result<(), ty::type_err> { + infer::can_mk_assignty(self.infcx(), sub, sup) + } + + fn mk_eqty(a_is_expected: bool, span: span, + sub: ty::t, sup: ty::t) -> Result<(), ty::type_err> { + infer::mk_eqty(self.infcx(), a_is_expected, span, sub, sup) + } + + fn mk_subr(a_is_expected: bool, span: span, + sub: ty::Region, sup: ty::Region) -> Result<(), ty::type_err> { + infer::mk_subr(self.infcx(), a_is_expected, span, sub, sup) + } + + fn require_unsafe(sp: span, op: ~str) { + match self.purity { + ast::unsafe_fn => {/*ok*/} + _ => { + self.ccx.tcx.sess.span_err( + sp, + fmt!("%s requires unsafe function or block", op)); + } + } + } + fn with_region_lb(lb: ast::node_id, f: fn() -> R) -> R { + let old_region_lb = self.region_lb; + self.region_lb = lb; + let v = f(); + self.region_lb = old_region_lb; + move v + } + + fn region_var_if_parameterized(rp: Option, + span: span, + lower_bound: ty::Region) + -> Option + { + rp.map( + |_rp| self.infcx().next_region_var_with_lb(span, lower_bound)) + } + + fn type_error_message(sp: span, mk_msg: fn(~str) -> ~str, + actual_ty: ty::t, err: Option<&ty::type_err>) { + self.infcx().type_error_message(sp, mk_msg, actual_ty, err); + } + + fn report_mismatched_types(sp: span, e: ty::t, a: ty::t, + err: &ty::type_err) { + self.infcx().report_mismatched_types(sp, e, a, err); + } + +} + +fn do_autoderef(fcx: @fn_ctxt, sp: span, t: ty::t) -> (ty::t, uint) { + /*! + * + * Autoderefs the type `t` as many times as possible, returning + * a new type and a counter for how many times the type was + * deref'd. If the counter is non-zero, the receiver is responsible + * for inserting an AutoAdjustment record into `tcx.adjustments` + * so that trans/borrowck/etc know about this autoderef. */ + + let mut t1 = t; + let mut enum_dids = ~[]; + let mut autoderefs = 0; + loop { + let sty = structure_of(fcx, sp, t1); + + // Some extra checks to detect weird cycles and so forth: + match sty { + ty::ty_box(inner) | ty::ty_uniq(inner) | + ty::ty_rptr(_, inner) => { + match ty::get(t1).sty { + ty::ty_infer(ty::TyVar(v1)) => { + ty::occurs_check(fcx.ccx.tcx, sp, v1, + ty::mk_box(fcx.ccx.tcx, inner)); + } + _ => () + } + } + ty::ty_enum(ref did, _) => { + // Watch out for a type like `enum t = @t`. Such a + // type would otherwise infinitely auto-deref. Only + // autoderef loops during typeck (basically, this one + // and the loops in typeck::check::method) need to be + // concerned with this, as an error will be reported + // on the enum definition as well because the enum is + // not instantiable. + if vec::contains(enum_dids, did) { + return (t1, autoderefs); + } + enum_dids.push(*did); + } + _ => { /*ok*/ } + } + + // Otherwise, deref if type is derefable: + match ty::deref_sty(fcx.ccx.tcx, &sty, false) { + None => { + return (t1, autoderefs); + } + Some(mt) => { + autoderefs += 1; + t1 = mt.ty + } + } + }; +} + +// AST fragment checking +fn check_lit(fcx: @fn_ctxt, lit: @ast::lit) -> ty::t { + let tcx = fcx.ccx.tcx; + + match lit.node { + ast::lit_str(*) => ty::mk_estr(tcx, ty::vstore_slice(ty::re_static)), + ast::lit_int(_, t) => ty::mk_mach_int(tcx, t), + ast::lit_uint(_, t) => ty::mk_mach_uint(tcx, t), + ast::lit_int_unsuffixed(_) => { + // An unsuffixed integer literal could have any integral type, + // so we create an integral type variable for it. + ty::mk_int_var(tcx, fcx.infcx().next_int_var_id()) + } + ast::lit_float(_, t) => ty::mk_mach_float(tcx, t), + ast::lit_float_unsuffixed(_) => { + // An unsuffixed floating point literal could have any floating point + // type, so we create a floating point type variable for it. + ty::mk_float_var(tcx, fcx.infcx().next_float_var_id()) + } + ast::lit_nil => ty::mk_nil(tcx), + ast::lit_bool(_) => ty::mk_bool(tcx) + } +} + +fn valid_range_bounds(ccx: @crate_ctxt, from: @ast::expr, to: @ast::expr) + -> bool { + const_eval::compare_lit_exprs(ccx.tcx, from, to) <= 0 +} + +fn check_expr_with(fcx: @fn_ctxt, expr: @ast::expr, expected: ty::t) -> bool { + check_expr(fcx, expr, Some(expected)) +} + +fn check_expr_with_assignability(fcx: @fn_ctxt, + expr: @ast::expr, + expected: ty::t) + -> bool { + do check_expr_with_unifier(fcx, expr, Some(expected)) { + demand::assign(fcx, expr.span, expected, expr) + } +} + + +fn check_expr(fcx: @fn_ctxt, expr: @ast::expr, + expected: Option) -> bool { + return do check_expr_with_unifier(fcx, expr, expected) { + for expected.each |t| { + demand::suptype(fcx, expr.span, *t, fcx.expr_ty(expr)); + } + }; +} + +// determine the `self` type, using fresh variables for all variables +// declared on the impl declaration e.g., `impl for ~[(A,B)]` +// would return ($0, $1) where $0 and $1 are freshly instantiated type +// variables. +fn impl_self_ty(vcx: &VtableContext, + location_info: &LocationInfo, // (potential) receiver for + // this impl + did: ast::def_id) -> ty_param_substs_and_ty { + let tcx = vcx.tcx(); + + let {n_tps, region_param, raw_ty} = if did.crate == ast::local_crate { + let region_param = tcx.region_paramd_items.find(did.node); + match tcx.items.find(did.node) { + Some(ast_map::node_item(@{node: ast::item_impl(ts, _, st, _), + _}, _)) => { + {n_tps: ts.len(), + region_param: region_param, + raw_ty: vcx.ccx.to_ty(rscope::type_rscope(region_param), st)} + } + Some(ast_map::node_item(@{node: ast::item_class(_, ts), + id: class_id, _},_)) => { + /* If the impl is a class, the self ty is just the class ty + (doing a no-op subst for the ty params; in the next step, + we substitute in fresh vars for them) + */ + {n_tps: ts.len(), + region_param: region_param, + raw_ty: ty::mk_class(tcx, local_def(class_id), + {self_r: rscope::bound_self_region(region_param), + self_ty: None, + tps: ty::ty_params_to_tys(tcx, ts)})} + } + _ => { tcx.sess.bug(~"impl_self_ty: unbound item or item that \ + doesn't have a self_ty"); } + } + } else { + let ity = ty::lookup_item_type(tcx, did); + {n_tps: vec::len(*ity.bounds), + region_param: ity.region_param, + raw_ty: ity.ty} + }; + + let self_r = if region_param.is_some() { + Some(vcx.infcx.next_region_var(location_info.span, + location_info.id)) + } else { + None + }; + let tps = vcx.infcx.next_ty_vars(n_tps); + + let substs = {self_r: self_r, self_ty: None, tps: tps}; + let substd_ty = ty::subst(tcx, &substs, raw_ty); + {substs: substs, ty: substd_ty} +} + +// Only for fields! Returns for methods> +// Indifferent to privacy flags +fn lookup_field_ty(tcx: ty::ctxt, + class_id: ast::def_id, + items: &[ty::field_ty], + fieldname: ast::ident, + substs: &ty::substs) -> Option { + + let o_field = vec::find(items, |f| f.ident == fieldname); + do o_field.map() |f| { + ty::lookup_field_type(tcx, class_id, f.id, substs) + } +} + +// Controls whether the arguments are automatically referenced. This is useful +// for overloaded binary and unary operators. +pub enum DerefArgs { + DontDerefArgs, + DoDerefArgs +} + +fn check_expr_with_unifier(fcx: @fn_ctxt, + expr: @ast::expr, + expected: Option, + unifier: fn()) -> bool { + + debug!(">> typechecking %s", fcx.expr_to_str(expr)); + + // A generic function to factor out common logic from call and + // overloaded operations + fn check_call_inner( + fcx: @fn_ctxt, + sp: span, + call_expr_id: ast::node_id, + in_fty: ty::t, + callee_expr: @ast::expr, + args: ~[@ast::expr], + deref_args: DerefArgs) -> {fty: ty::t, bot: bool} { + + let tcx = fcx.ccx.tcx; + let mut bot = false; + + // Replace all region parameters in the arguments and return + // type with fresh region variables. + + debug!("check_call_inner: before universal quant., in_fty=%s", + fcx.infcx().ty_to_str(in_fty)); + + let mut formal_tys; + + // This is subtle: we expect `fty` to be a function type, which + // normally introduce a level of binding. In this case, we want to + // process the types bound by the function but not by any nested + // functions. Therefore, we match one level of structure. + let fty = + match structure_of(fcx, sp, in_fty) { + ty::ty_fn(ref fn_ty) => { + let fn_ty = replace_bound_regions_in_fn_ty(tcx, @Nil, + None, fn_ty, |_br| fcx.infcx().next_region_var(sp, + call_expr_id)).fn_ty; + + let supplied_arg_count = args.len(); + + // Grab the argument types, supplying fresh type variables + // if the wrong number of arguments were supplied + let expected_arg_count = fn_ty.sig.inputs.len(); + formal_tys = if expected_arg_count == supplied_arg_count { + fn_ty.sig.inputs.map(|a| a.ty) + } else { + tcx.sess.span_err( + sp, fmt!("this function takes %u parameter%s but \ + %u parameter%s supplied", + expected_arg_count, + if expected_arg_count == 1 { + ~"" + } else { + ~"s" + }, + supplied_arg_count, + if supplied_arg_count == 1 { + ~" was" + } else { + ~"s were" + })); + fcx.infcx().next_ty_vars(supplied_arg_count) + }; + ty::mk_fn(tcx, fn_ty) + } + _ => { + fcx.type_error_message(sp, |actual| { + fmt!("expected function or foreign function but \ + found `%s`", actual) }, in_fty, None); + // check each arg against "error", in order to set up + // all the node type bindings + formal_tys = args.map(|_x| ty::mk_err(tcx)); + ty::mk_err(tcx) + } + }; + + debug!("check_call_inner: after universal quant., fty=%s", + fcx.infcx().ty_to_str(fty)); + + // Check the arguments. + // We do this in a pretty awful way: first we typecheck any arguments + // that are not anonymous functions, then we typecheck the anonymous + // functions. This is so that we have more information about the types + // of arguments when we typecheck the functions. This isn't really the + // right way to do this. + for [false, true].each |check_blocks| { + let check_blocks = *check_blocks; + debug!("check_blocks=%b", check_blocks); + + // More awful hacks: before we check the blocks, try to do + // an "opportunistic" vtable resolution of any trait + // bounds on the call. + if check_blocks { + vtable::early_resolve_expr(callee_expr, fcx, true); + } + + for args.eachi |i, arg| { + let is_block = match arg.node { + ast::expr_fn_block(*) | ast::expr_loop_body(*) | + ast::expr_do_body(*) => true, + _ => false + }; + + if is_block == check_blocks { + debug!("checking the argument"); + let mut formal_ty = formal_tys[i]; + + match deref_args { + DoDerefArgs => { + match ty::get(formal_ty).sty { + ty::ty_rptr(_, mt) => formal_ty = mt.ty, + _ => { + fcx.ccx.tcx.sess.span_bug(arg.span, + ~"no ref"); + } + } + } + DontDerefArgs => {} + } + + bot |= check_expr_with_assignability(fcx, + *arg, formal_ty); + fcx.write_ty(arg.id, fcx.expr_ty(*arg)); + + } + } + } + + {fty: fty, bot: bot} + } + + // A generic function for checking assignment expressions + fn check_assignment(fcx: @fn_ctxt, + lhs: @ast::expr, + rhs: @ast::expr, + id: ast::node_id) + -> bool { + let mut bot = check_expr(fcx, lhs, None); + let lhs_type = fcx.expr_ty(lhs); + bot |= check_expr_with_assignability(fcx, rhs, lhs_type); + fcx.write_ty(id, ty::mk_nil(fcx.ccx.tcx)); + return bot; + } + + // A generic function for doing all of the checking for call expressions + fn check_call(fcx: @fn_ctxt, sp: span, call_expr_id: ast::node_id, + f: @ast::expr, args: ~[@ast::expr]) -> bool { + + // Index expressions need to be handled seperately, to inform + // them that they appear in call position. + let mut bot = match f.node { + ast::expr_field(base, field, tys) => { + check_field(fcx, f, true, base, field, tys) + } + _ => check_expr(fcx, f, None) + }; + let fn_ty = fcx.expr_ty(f); + + // Call the generic checker. + let fty = { + let r = check_call_inner(fcx, sp, call_expr_id, + fn_ty, f, args, DontDerefArgs); + bot |= r.bot; + r.fty + }; + + // Pull the return type out of the type of the function. + match structure_of(fcx, sp, fty) { + ty::ty_fn(ref f) => { + bot |= (f.meta.ret_style == ast::noreturn); + fcx.write_ty(call_expr_id, f.sig.output); + return bot; + } + _ => { + fcx.write_ty(call_expr_id, ty::mk_err(fcx.ccx.tcx)); + fcx.type_error_message(sp, |_actual| { + ~"expected function"}, fty, None); + return bot; + } + } + } + + // A generic function for checking for or for-each loops + fn check_for(fcx: @fn_ctxt, local: @ast::local, + element_ty: ty::t, body: ast::blk, + node_id: ast::node_id) -> bool { + let locid = lookup_local(fcx, local.span, local.node.id); + demand::suptype(fcx, local.span, + ty::mk_var(fcx.ccx.tcx, locid), + element_ty); + let bot = check_decl_local(fcx, local); + check_block_no_value(fcx, body); + fcx.write_nil(node_id); + return bot; + } + + // A generic function for checking the then and else in an if + // or if-check + fn check_then_else(fcx: @fn_ctxt, thn: ast::blk, + elsopt: Option<@ast::expr>, id: ast::node_id, + _sp: span) -> bool { + let (if_t, if_bot) = + match elsopt { + Some(els) => { + let if_t = fcx.infcx().next_ty_var(); + let thn_bot = check_block(fcx, thn); + let thn_t = fcx.node_ty(thn.node.id); + demand::suptype(fcx, thn.span, if_t, thn_t); + let els_bot = check_expr_with(fcx, els, if_t); + (if_t, thn_bot & els_bot) + } + None => { + check_block_no_value(fcx, thn); + (ty::mk_nil(fcx.ccx.tcx), false) + } + }; + fcx.write_ty(id, if_t); + return if_bot; + } + + fn lookup_op_method(fcx: @fn_ctxt, op_ex: @ast::expr, + self_ex: @ast::expr, self_t: ty::t, + opname: ast::ident, args: ~[@ast::expr], + +deref_args: DerefArgs) + -> Option<(ty::t, bool)> + { + match method::lookup(fcx, op_ex, self_ex, + op_ex.callee_id, opname, self_t, ~[], + deref_args) { + Some(origin) => { + let {fty: method_ty, bot: bot} = { + let method_ty = fcx.node_ty(op_ex.callee_id); + check_call_inner(fcx, op_ex.span, op_ex.id, + method_ty, op_ex, args, deref_args) + }; + fcx.ccx.method_map.insert(op_ex.id, origin); + Some((ty::ty_fn_ret(method_ty), bot)) + } + _ => None + } + } + + // could be either a expr_binop or an expr_assign_binop + fn check_binop(fcx: @fn_ctxt, expr: @ast::expr, + op: ast::binop, + lhs: @ast::expr, + rhs: @ast::expr) -> bool { + let tcx = fcx.ccx.tcx; + + let lhs_bot = check_expr(fcx, lhs, None); + let lhs_t = fcx.expr_ty(lhs); + let lhs_t = structurally_resolved_type(fcx, lhs.span, lhs_t); + + if ty::type_is_integral(lhs_t) && ast_util::is_shift_binop(op) { + // Shift is a special case: rhs can be any integral type + let rhs_bot = check_expr(fcx, rhs, None); + let rhs_t = fcx.expr_ty(rhs); + require_integral(fcx, rhs.span, rhs_t); + fcx.write_ty(expr.id, lhs_t); + return lhs_bot | rhs_bot; + } + + if ty::is_binopable(tcx, lhs_t, op) { + let tvar = fcx.infcx().next_ty_var(); + demand::suptype(fcx, expr.span, tvar, lhs_t); + let rhs_bot = check_expr_with(fcx, rhs, tvar); + + let result_t = match op { + ast::eq | ast::ne | ast::lt | ast::le | ast::ge | + ast::gt => { + ty::mk_bool(tcx) + } + _ => { + lhs_t + } + }; + + fcx.write_ty(expr.id, result_t); + return { + if !ast_util::lazy_binop(op) { lhs_bot | rhs_bot } + else { lhs_bot } + }; + } + + // A hack, but this prevents multiple errors for the same code + // (since check_user_binop calls structurally_resolve_type) + let (result, rhs_bot) = + match ty::deref(fcx.tcx(), lhs_t, false).map( + |tt| structurally_resolved_type(fcx, + expr.span, tt.ty)) { + Some(t) if ty::get(t).sty == ty::ty_err => (t, false), + _ => check_user_binop(fcx, expr, lhs, lhs_t, op, rhs) + }; + fcx.write_ty(expr.id, result); + return lhs_bot | rhs_bot; + } + + fn check_user_binop(fcx: @fn_ctxt, ex: @ast::expr, + lhs_expr: @ast::expr, lhs_resolved_t: ty::t, + op: ast::binop, rhs: @ast::expr) -> (ty::t, bool) + { + let tcx = fcx.ccx.tcx; + match ast_util::binop_to_method_name(op) { + Some(name) => { + match lookup_op_method(fcx, ex, lhs_expr, lhs_resolved_t, + fcx.tcx().sess.ident_of(name), + ~[rhs], DoDerefArgs) { + Some(pair) => return pair, + _ => () + } + } + _ => () + } + check_expr(fcx, rhs, None); + fcx.type_error_message(ex.span, + |actual| { + fmt!("binary operation %s cannot be applied to type `%s`", + ast_util::binop_to_str(op), actual) + }, + lhs_resolved_t, None); + + // If the or operator is used it might be that the user forgot to + // supply the do keyword. Let's be more helpful in that situation. + if op == ast::or { + match ty::get(lhs_resolved_t).sty { + ty::ty_fn(_) => { + tcx.sess.span_note( + ex.span, ~"did you forget the 'do' keyword for the call?"); + } + _ => () + } + } + + (lhs_resolved_t, false) + } + + fn check_user_unop(fcx: @fn_ctxt, op_str: ~str, mname: ~str, + ex: @ast::expr, + rhs_expr: @ast::expr, rhs_t: ty::t) -> ty::t { + match lookup_op_method(fcx, ex, rhs_expr, rhs_t, + fcx.tcx().sess.ident_of(mname), ~[], + DontDerefArgs) { + Some((ret_ty, _)) => ret_ty, + _ => { + fcx.type_error_message(ex.span, |actual| { + fmt!("cannot apply unary operator `%s` to type `%s`", + op_str, actual) + }, rhs_t, None); + rhs_t + } + } + } + + // Resolves `expected` by a single level if it is a variable and passes it + // through the `unpack` function. It there is no expected type or + // resolution is not possible (e.g., no constraints yet present), just + // returns `none`. + fn unpack_expected(fcx: @fn_ctxt, expected: Option, + unpack: fn(ty::sty) -> Option) + -> Option { + match expected { + Some(t) => { + match resolve_type(fcx.infcx(), t, force_tvar) { + Ok(t) => unpack(ty::get(t).sty), + _ => None + } + } + _ => None + } + } + + fn check_expr_fn(fcx: @fn_ctxt, + expr: @ast::expr, + ast_proto_opt: Option, + decl: ast::fn_decl, + body: ast::blk, + is_loop_body: bool, + expected: Option) { + let tcx = fcx.ccx.tcx; + + // Find the expected input/output types (if any). Careful to + // avoid capture of bound regions in the expected type. See + // def'n of br_cap_avoid() for a more lengthy explanation of + // what's going on here. + // Also try to pick up inferred purity and proto, defaulting + // to impure and block. Note that we only will use those for + // block syntax lambdas; that is, lambdas without explicit + // protos. + let expected_sty = unpack_expected(fcx, expected, |x| Some(x)); + let (expected_tys, + expected_purity, + expected_proto, + expected_onceness) = { + match expected_sty { + Some(ty::ty_fn(ref fn_ty)) => { + let id = expr.id; + let {fn_ty, _} = + replace_bound_regions_in_fn_ty( + tcx, @Nil, None, fn_ty, + |br| ty::re_bound(ty::br_cap_avoid(id, @br))); + (Some({inputs: fn_ty.sig.inputs, + output: fn_ty.sig.output}), + fn_ty.meta.purity, + fn_ty.meta.proto, + fn_ty.meta.onceness) + } + _ => { + (None, ast::impure_fn, ast::ProtoBox, ast::Many) + } + } + }; + + // If the proto is specified, use that, otherwise select a + // proto based on inference. + let (proto, purity) = match ast_proto_opt { + Some(p) => (p, ast::impure_fn), + None => (expected_proto, expected_purity) + }; + + // construct the function type + let mut fn_ty = astconv::ty_of_fn_decl( + fcx, fcx, + proto, purity, expected_onceness, + /*bounds:*/ @~[], /*opt_region:*/ None, + decl, expected_tys, expr.span); + + let fty = ty::mk_fn(tcx, fn_ty); + + debug!("check_expr_fn_with_unifier %s fty=%s", + expr_to_str(expr, tcx.sess.intr()), + fcx.infcx().ty_to_str(fty)); + + fcx.write_ty(expr.id, fty); + + check_fn(fcx.ccx, None, &fn_ty, decl, body, + is_loop_body, Some(fcx)); + } + + + // Check field access expressions + fn check_field(fcx: @fn_ctxt, expr: @ast::expr, is_callee: bool, + base: @ast::expr, field: ast::ident, tys: ~[@ast::Ty]) + -> bool + { + let tcx = fcx.ccx.tcx; + let bot = check_expr(fcx, base, None); + let expr_t = structurally_resolved_type(fcx, expr.span, + fcx.expr_ty(base)); + let (base_t, derefs) = do_autoderef(fcx, expr.span, expr_t); + let n_tys = tys.len(); + + match structure_of(fcx, expr.span, base_t) { + ty::ty_rec(fields) => { + match ty::field_idx(field, fields) { + Some(ix) => { + if n_tys > 0 { + tcx.sess.span_err( + expr.span, + ~"can't provide type parameters \ + to a field access"); + } + fcx.write_ty(expr.id, fields[ix].mt.ty); + fcx.write_autoderef_adjustment(base.id, derefs); + return bot; + } + _ => () + } + } + ty::ty_class(base_id, substs) => { + // This is just for fields -- the same code handles + // methods in both classes and traits + + // (1) verify that the class id actually has a field called + // field + debug!("class named %s", ty_to_str(tcx, base_t)); + let cls_items = ty::lookup_class_fields(tcx, base_id); + match lookup_field_ty(tcx, base_id, cls_items, + field, &substs) { + Some(field_ty) => { + // (2) look up what field's type is, and return it + fcx.write_ty(expr.id, field_ty); + fcx.write_autoderef_adjustment(base.id, derefs); + return bot; + } + None => () + } + } + _ => () + } + + let tps = vec::map(tys, |ty| fcx.to_ty(*ty)); + + match method::lookup(fcx, expr, base, expr.id, + field, expr_t, tps, DontDerefArgs) { + Some(entry) => { + fcx.ccx.method_map.insert(expr.id, entry); + + // If we have resolved to a method but this is not in + // a callee position, error + if !is_callee { + tcx.sess.span_err( + expr.span, + ~"attempted to take value of method \ + (try writing an anonymous function)"); + } + } + None => { + fcx.type_error_message(expr.span, + |actual| { + fmt!("attempted access of field `%s` on type `%s`, but \ + no field or method with that name was found", + tcx.sess.str_of(field), actual) + }, + expr_t, None); + // Add error type for the result + fcx.write_ty(expr.id, ty::mk_err(tcx)); + } + } + + return bot; + } + + fn check_struct_or_variant_fields(fcx: @fn_ctxt, + span: span, + class_id: ast::def_id, + substitutions: &ty::substs, + field_types: ~[ty::field_ty], + ast_fields: ~[ast::field], + check_completeness: bool) -> bool { + let tcx = fcx.ccx.tcx; + let mut bot = false; + + debug!("%? %?", ast_fields.len(), field_types.len()); + + let class_field_map = HashMap(); + let mut fields_found = 0; + for field_types.each |field| { + // XXX: Check visibility here. + class_field_map.insert(field.ident, (field.id, false)); + } + + // Typecheck each field. + for ast_fields.each |field| { + match class_field_map.find(field.node.ident) { + None => { + tcx.sess.span_err( + field.span, + fmt!("structure has no field named `%s`", + tcx.sess.str_of(field.node.ident))); + } + Some((_, true)) => { + tcx.sess.span_err( + field.span, + fmt!("field `%s` specified more than once", + tcx.sess.str_of(field.node.ident))); + } + Some((field_id, false)) => { + let expected_field_type = + ty::lookup_field_type( + tcx, class_id, field_id, substitutions); + bot |= check_expr(fcx, + field.node.expr, + Some(expected_field_type)); + class_field_map.insert( + field.node.ident, (field_id, true)); + fields_found += 1; + } + } + } + + if check_completeness { + // Make sure the programmer specified all the fields. + assert fields_found <= field_types.len(); + if fields_found < field_types.len() { + let mut missing_fields = ~[]; + for field_types.each |class_field| { + let name = class_field.ident; + let (_, seen) = class_field_map.get(name); + if !seen { + missing_fields.push( + ~"`" + tcx.sess.str_of(name) + ~"`"); + } + } + + tcx.sess.span_err(span, + fmt!("missing field%s: %s", + if missing_fields.len() == 1 { + ~"" + } else { + ~"s" + }, + str::connect(missing_fields, ~", "))); + } + } + + return bot; + } + + fn check_struct_constructor(fcx: @fn_ctxt, + id: ast::node_id, + span: syntax::codemap::span, + class_id: ast::def_id, + fields: ~[ast::field], + base_expr: Option<@ast::expr>) -> bool { + let mut bot = false; + let tcx = fcx.ccx.tcx; + + // Look up the number of type parameters and the raw type, and + // determine whether the class is region-parameterized. + let type_parameter_count, region_parameterized, raw_type; + if class_id.crate == ast::local_crate { + region_parameterized = + tcx.region_paramd_items.find(class_id.node); + match tcx.items.find(class_id.node) { + Some(ast_map::node_item(@{ + node: ast::item_class(_, type_parameters), + _ + }, _)) => { + + type_parameter_count = type_parameters.len(); + + let self_region = + bound_self_region(region_parameterized); + + raw_type = ty::mk_class(tcx, class_id, { + self_r: self_region, + self_ty: None, + tps: ty::ty_params_to_tys(tcx, type_parameters) + }); + } + _ => { + tcx.sess.span_bug(span, + ~"resolve didn't map this to a class"); + } + } + } else { + let item_type = ty::lookup_item_type(tcx, class_id); + type_parameter_count = (*item_type.bounds).len(); + region_parameterized = item_type.region_param; + raw_type = item_type.ty; + } + + // Generate the struct type. + let self_region = + fcx.region_var_if_parameterized(region_parameterized, + span, + ty::re_scope(id)); + let type_parameters = fcx.infcx().next_ty_vars(type_parameter_count); + let substitutions = { + self_r: self_region, + self_ty: None, + tps: type_parameters + }; + + let struct_type = ty::subst(tcx, &substitutions, raw_type); + + // Look up and check the fields. + let class_fields = ty::lookup_class_fields(tcx, class_id); + bot = check_struct_or_variant_fields(fcx, + span, + class_id, + &substitutions, + class_fields, + fields, + base_expr.is_none()) || bot; + + // Check the base expression if necessary. + match base_expr { + None => {} + Some(base_expr) => { + bot = check_expr(fcx, base_expr, Some(struct_type)) || bot + } + } + + // Write in the resulting type. + fcx.write_ty(id, struct_type); + return bot; + } + + fn check_struct_enum_variant(fcx: @fn_ctxt, + id: ast::node_id, + span: syntax::codemap::span, + enum_id: ast::def_id, + variant_id: ast::def_id, + fields: ~[ast::field]) -> bool { + let mut bot = false; + let tcx = fcx.ccx.tcx; + + // Look up the number of type parameters and the raw type, and + // determine whether the enum is region-parameterized. + let type_parameter_count, region_parameterized, raw_type; + if enum_id.crate == ast::local_crate { + region_parameterized = + tcx.region_paramd_items.find(enum_id.node); + match tcx.items.find(enum_id.node) { + Some(ast_map::node_item(@{ + node: ast::item_enum(_, type_parameters), + _ + }, _)) => { + + type_parameter_count = type_parameters.len(); + + let self_region = + bound_self_region(region_parameterized); + + raw_type = ty::mk_enum(tcx, enum_id, { + self_r: self_region, + self_ty: None, + tps: ty::ty_params_to_tys(tcx, type_parameters) + }); + } + _ => { + tcx.sess.span_bug(span, + ~"resolve didn't map this to an enum"); + } + } + } else { + let item_type = ty::lookup_item_type(tcx, enum_id); + type_parameter_count = (*item_type.bounds).len(); + region_parameterized = item_type.region_param; + raw_type = item_type.ty; + } + + // Generate the enum type. + let self_region = + fcx.region_var_if_parameterized(region_parameterized, + span, + ty::re_scope(id)); + let type_parameters = fcx.infcx().next_ty_vars(type_parameter_count); + let substitutions = { + self_r: self_region, + self_ty: None, + tps: type_parameters + }; + + let enum_type = ty::subst(tcx, &substitutions, raw_type); + + // Look up and check the enum variant fields. + let variant_fields = ty::lookup_class_fields(tcx, variant_id); + bot = check_struct_or_variant_fields(fcx, + span, + variant_id, + &substitutions, + variant_fields, + fields, + true) || bot; + + // Write in the resulting type. + fcx.write_ty(id, enum_type); + return bot; + } + + let tcx = fcx.ccx.tcx; + let id = expr.id; + let mut bot = false; + match expr.node { + ast::expr_vstore(ev, vst) => { + let typ = match ev.node { + ast::expr_lit(@{node: ast::lit_str(s), span:_}) => { + let tt = ast_expr_vstore_to_vstore(fcx, ev, str::len(*s), vst); + ty::mk_estr(tcx, tt) + } + ast::expr_vec(args, mutbl) => { + let tt = ast_expr_vstore_to_vstore(fcx, ev, args.len(), vst); + let mutability; + match vst { + ast::expr_vstore_mut_box => mutability = ast::m_mutbl, + _ => mutability = mutbl + } + let t: ty::t = fcx.infcx().next_ty_var(); + for args.each |e| { bot |= check_expr_with(fcx, *e, t); } + ty::mk_evec(tcx, {ty: t, mutbl: mutability}, tt) + } + ast::expr_repeat(element, count_expr, mutbl) => { + let count = ty::eval_repeat_count(tcx, count_expr, expr.span); + fcx.write_ty(count_expr.id, ty::mk_uint(tcx)); + let tt = ast_expr_vstore_to_vstore(fcx, ev, count, vst); + let t: ty::t = fcx.infcx().next_ty_var(); + bot |= check_expr_with(fcx, element, t); + ty::mk_evec(tcx, {ty: t, mutbl: mutbl}, tt) + } + _ => + tcx.sess.span_bug(expr.span, ~"vstore modifier on non-sequence") + }; + fcx.write_ty(ev.id, typ); + fcx.write_ty(id, typ); + } + + ast::expr_lit(lit) => { + let typ = check_lit(fcx, lit); + fcx.write_ty(id, typ); + } + ast::expr_binary(op, lhs, rhs) => { + bot |= check_binop(fcx, expr, op, lhs, rhs); + } + ast::expr_assign_op(op, lhs, rhs) => { + bot |= check_binop(fcx, expr, op, lhs, rhs); + let lhs_t = fcx.expr_ty(lhs); + let result_t = fcx.expr_ty(expr); + demand::suptype(fcx, expr.span, result_t, lhs_t); + + // Overwrite result of check_binop...this preserves existing behavior + // but seems quite dubious with regard to user-defined methods + // and so forth. - Niko + fcx.write_nil(expr.id); + } + ast::expr_unary(unop, oprnd) => { + let exp_inner = do unpack_expected(fcx, expected) |sty| { + match unop { + ast::box(_) | ast::uniq(_) => match sty { + ty::ty_box(mt) | ty::ty_uniq(mt) => Some(mt.ty), + _ => None + }, + ast::not | ast::neg => expected, + ast::deref => None + } + }; + bot = check_expr(fcx, oprnd, exp_inner); + let mut oprnd_t = fcx.expr_ty(oprnd); + match unop { + ast::box(mutbl) => { + oprnd_t = ty::mk_box(tcx, {ty: oprnd_t, mutbl: mutbl}); + } + ast::uniq(mutbl) => { + oprnd_t = ty::mk_uniq(tcx, {ty: oprnd_t, mutbl: mutbl}); + } + ast::deref => { + let sty = structure_of(fcx, expr.span, oprnd_t); + + // deref'ing an unsafe pointer requires that we be in an unsafe + // context + match sty { + ty::ty_ptr(*) => { + fcx.require_unsafe( + expr.span, + ~"dereference of unsafe pointer"); + } + _ => { /*ok*/ } + } + + match ty::deref_sty(tcx, &sty, true) { + Some(mt) => { oprnd_t = mt.ty } + None => { + match sty { + ty::ty_enum(*) => { + tcx.sess.span_err( + expr.span, + ~"can only dereference enums \ + with a single variant which has a \ + single argument"); + } + ty::ty_class(*) => { + tcx.sess.span_err( + expr.span, + ~"can only dereference structs with one anonymous \ + field"); + } + _ => { + fcx.type_error_message(expr.span, |actual| { + fmt!("type %s cannot be dereferenced", actual) + }, oprnd_t, None); + } + } + } + } + } + ast::not => { + oprnd_t = structurally_resolved_type(fcx, oprnd.span, oprnd_t); + if !(ty::type_is_integral(oprnd_t) || + ty::get(oprnd_t).sty == ty::ty_bool) { + oprnd_t = check_user_unop(fcx, ~"!", ~"not", expr, + oprnd, oprnd_t); + } + } + ast::neg => { + oprnd_t = structurally_resolved_type(fcx, oprnd.span, oprnd_t); + if !(ty::type_is_integral(oprnd_t) || + ty::type_is_fp(oprnd_t)) { + oprnd_t = check_user_unop(fcx, ~"-", ~"neg", expr, + oprnd, oprnd_t); + } + } + } + fcx.write_ty(id, oprnd_t); + } + ast::expr_addr_of(mutbl, oprnd) => { + bot = check_expr(fcx, oprnd, unpack_expected(fcx, expected, |ty| + match ty { ty::ty_rptr(_, mt) => Some(mt.ty), _ => None } + )); + + // Note: at this point, we cannot say what the best lifetime + // is to use for resulting pointer. We want to use the + // shortest lifetime possible so as to avoid spurious borrowck + // errors. Moreover, the longest lifetime will depend on the + // precise details of the value whose address is being taken + // (and how long it is valid), which we don't know yet until type + // inference is complete. + // + // Therefore, here we simply generate a region variable with + // the current expression as a lower bound. The region + // inferencer will then select the ultimate value. Finally, + // borrowck is charged with guaranteeing that the value whose + // address was taken can actually be made to live as long as + // it needs to live. + let region = fcx.infcx().next_region_var(expr.span, expr.id); + + let tm = { ty: fcx.expr_ty(oprnd), mutbl: mutbl }; + let oprnd_t = ty::mk_rptr(tcx, region, tm); + fcx.write_ty(id, oprnd_t); + } + ast::expr_path(pth) => { + let defn = lookup_def(fcx, pth.span, id); + + let tpt = ty_param_bounds_and_ty_for_def(fcx, expr.span, defn); + let region_lb = ty::re_scope(expr.id); + instantiate_path(fcx, pth, tpt, expr.span, expr.id, region_lb); + } + ast::expr_mac(_) => tcx.sess.bug(~"unexpanded macro"), + ast::expr_fail(expr_opt) => { + bot = true; + match expr_opt { + None => {/* do nothing */ } + Some(e) => { + check_expr_with(fcx, e, + ty::mk_estr(tcx, ty::vstore_uniq)); + } + } + fcx.write_bot(id); + } + ast::expr_break(_) => { fcx.write_bot(id); bot = true; } + ast::expr_again(_) => { fcx.write_bot(id); bot = true; } + ast::expr_ret(expr_opt) => { + bot = true; + let ret_ty = match fcx.indirect_ret_ty { + Some(t) => t, None => fcx.ret_ty + }; + match expr_opt { + None => match fcx.mk_eqty(false, expr.span, + ret_ty, ty::mk_nil(tcx)) { + result::Ok(_) => { /* fall through */ } + result::Err(_) => { + tcx.sess.span_err( + expr.span, + ~"`return;` in function returning non-nil"); + } + }, + Some(e) => { check_expr_with(fcx, e, ret_ty); } + } + fcx.write_bot(id); + } + ast::expr_log(_, lv, e) => { + bot = check_expr_with(fcx, lv, ty::mk_mach_uint(tcx, ast::ty_u32)); + // Note: this does not always execute, so do not propagate bot: + check_expr(fcx, e, None); + fcx.write_nil(id); + } + ast::expr_assert(e) => { + bot = check_expr_with(fcx, e, ty::mk_bool(tcx)); + fcx.write_nil(id); + } + ast::expr_copy(a) | ast::expr_unary_move(a) => { + bot = check_expr(fcx, a, expected); + fcx.write_ty(id, fcx.expr_ty(a)); + } + ast::expr_paren(a) => { + bot = check_expr_with_unifier(fcx, a, expected, || ()); + fcx.write_ty(id, fcx.expr_ty(a)); + do expected.iter |i| { + demand::assign(fcx, expr.span, *i, expr); + demand::assign(fcx, a.span, *i, a); + }; + } + ast::expr_assign(lhs, rhs) => { + bot = check_assignment(fcx, lhs, rhs, id); + } + ast::expr_swap(lhs, rhs) => { + bot = check_assignment(fcx, lhs, rhs, id); + } + ast::expr_if(cond, thn, elsopt) => { + bot = check_expr_with(fcx, cond, ty::mk_bool(tcx)) | + check_then_else(fcx, thn, elsopt, id, expr.span); + } + ast::expr_while(cond, body) => { + bot = check_expr_with(fcx, cond, ty::mk_bool(tcx)); + check_block_no_value(fcx, body); + fcx.write_ty(id, ty::mk_nil(tcx)); + } + ast::expr_loop(body, _) => { + check_block_no_value(fcx, body); + fcx.write_ty(id, ty::mk_nil(tcx)); + bot = !may_break(tcx, expr.id, body); + } + ast::expr_match(discrim, arms) => { + bot = alt::check_alt(fcx, expr, discrim, arms); + } + ast::expr_fn(proto, decl, body, cap_clause) => { + check_expr_fn(fcx, expr, Some(proto), + decl, body, false, + expected); + capture::check_capture_clause(tcx, expr.id, cap_clause); + } + ast::expr_fn_block(decl, body, cap_clause) => { + check_expr_fn(fcx, expr, None, + decl, body, false, + expected); + capture::check_capture_clause(tcx, expr.id, cap_clause); + } + ast::expr_loop_body(b) => { + // a loop body is the special argument to a `for` loop. We know that + // there will be an expected type in this context because it can only + // appear in the context of a call, so we get the expected type of the + // parameter. The catch here is that we need to validate two things: + // 1. a closure that returns a bool is expected + // 2. the closure that was given returns unit + let expected_sty = unpack_expected(fcx, expected, |x| Some(x)); + let inner_ty = match expected_sty { + Some(ty::ty_fn(fty)) => { + match fcx.mk_subty(false, expr.span, + fty.sig.output, ty::mk_bool(tcx)) { + result::Ok(_) => (), + result::Err(_) => { + fcx.type_error_message(expr.span, + |actual| { + fmt!("a `loop` function's last argument \ + should return `bool`, not `%s`", actual) + }, + fty.sig.output, None); + fcx.write_ty(id, ty::mk_err(tcx)); + return true; + } + } + ty::mk_fn(tcx, FnTyBase { + meta: fty.meta, + sig: FnSig {output: ty::mk_nil(tcx), + ..fty.sig} + }) + } + _ => + match expected { + Some(expected_t) => { + fcx.type_error_message(expr.span, |actual| { + fmt!("a `loop` function's last \ + argument should be of function \ + type, not `%s`", + actual) + }, + expected_t, None); + fcx.write_ty(id, ty::mk_err(tcx)); + return true; + } + None => fcx.tcx().sess.impossible_case(expr.span, + ~"loop body must have an expected type") + } + }; + match b.node { + ast::expr_fn_block(decl, body, cap_clause) => { + check_expr_fn(fcx, b, None, + decl, body, true, + Some(inner_ty)); + demand::suptype(fcx, b.span, inner_ty, fcx.expr_ty(b)); + capture::check_capture_clause(tcx, b.id, cap_clause); + } + // argh + _ => fail ~"expr_fn_block" + } + let block_ty = structurally_resolved_type( + fcx, expr.span, fcx.node_ty(b.id)); + match ty::get(block_ty).sty { + ty::ty_fn(fty) => { + fcx.write_ty(expr.id, ty::mk_fn(tcx, FnTyBase { + meta: fty.meta, + sig: FnSig {output: ty::mk_bool(tcx), + ..fty.sig} + })) + } + _ => fail ~"expected fn type" + } + } + ast::expr_do_body(b) => { + let expected_sty = unpack_expected(fcx, expected, |x| Some(x)); + let inner_ty = match expected_sty { + Some(ty::ty_fn(fty)) => { + ty::mk_fn(tcx, fty) + } + _ => match expected { + Some(expected_t) => { + fcx.type_error_message(expr.span, |_actual| { + ~"Non-function passed to a `do` \ + function as its last argument, or wrong number \ + of arguments passed to a `do` function" + }, expected_t, None); + fcx.write_ty(id, ty::mk_err(tcx)); + return true; + } + None => fcx.tcx().sess.impossible_case(expr.span, + ~"do body must have expected type") + } + }; + match b.node { + ast::expr_fn_block(decl, body, cap_clause) => { + check_expr_fn(fcx, b, None, + decl, body, true, + Some(inner_ty)); + demand::suptype(fcx, b.span, inner_ty, fcx.expr_ty(b)); + capture::check_capture_clause(tcx, b.id, cap_clause); + } + // argh + _ => fail ~"expected fn ty" + } + let block_ty = structurally_resolved_type( + fcx, expr.span, fcx.node_ty(b.id)); + match ty::get(block_ty).sty { + ty::ty_fn(fty) => { + fcx.write_ty(expr.id, ty::mk_fn(tcx, fty)); + } + _ => fail ~"expected fn ty" + } + } + ast::expr_block(b) => { + // If this is an unchecked block, turn off purity-checking + bot = check_block(fcx, b); + let typ = + match b.node.expr { + Some(expr) => fcx.expr_ty(expr), + None => ty::mk_nil(tcx) + }; + fcx.write_ty(id, typ); + } + ast::expr_call(f, args, _) => { + bot = check_call(fcx, expr.span, expr.id, f, args); + } + ast::expr_cast(e, t) => { + bot = check_expr(fcx, e, None); + let t_1 = fcx.to_ty(t); + let t_e = fcx.expr_ty(e); + + debug!("t_1=%s", fcx.infcx().ty_to_str(t_1)); + debug!("t_e=%s", fcx.infcx().ty_to_str(t_e)); + + match ty::get(t_1).sty { + // This will be looked up later on + ty::ty_trait(*) => (), + + _ => { + if ty::type_is_nil(t_e) { + fcx.type_error_message(expr.span, |actual| { + fmt!("cast from nil: `%s` as `%s`", actual, + fcx.infcx().ty_to_str(t_1)) + }, t_e, None); + } else if ty::type_is_nil(t_1) { + fcx.type_error_message(expr.span, |actual| { + fmt!("cast to nil: `%s` as `%s`", actual, + fcx.infcx().ty_to_str(t_1)) + }, t_e, None); + } + + let t_1_is_scalar = type_is_scalar(fcx, expr.span, t_1); + if type_is_c_like_enum(fcx,expr.span,t_e) && t_1_is_scalar { + /* this case is allowed */ + } else if !(type_is_scalar(fcx,expr.span,t_e) && t_1_is_scalar) { + /* + If more type combinations should be supported than are + supported here, then file an enhancement issue and record the + issue number in this comment. + */ + fcx.type_error_message(expr.span, |actual| { + fmt!("non-scalar cast: `%s` as `%s`", actual, + fcx.infcx().ty_to_str(t_1)) + }, t_e, None); + } + } + } + fcx.write_ty(id, t_1); + } + ast::expr_vec(args, mutbl) => { + let t: ty::t = fcx.infcx().next_ty_var(); + for args.each |e| { bot |= check_expr_with(fcx, *e, t); } + let typ = ty::mk_evec(tcx, {ty: t, mutbl: mutbl}, + ty::vstore_fixed(args.len())); + fcx.write_ty(id, typ); + } + ast::expr_repeat(element, count_expr, mutbl) => { + let count = ty::eval_repeat_count(tcx, count_expr, expr.span); + fcx.write_ty(count_expr.id, ty::mk_uint(tcx)); + let t: ty::t = fcx.infcx().next_ty_var(); + bot |= check_expr_with(fcx, element, t); + let t = ty::mk_evec(tcx, {ty: t, mutbl: mutbl}, + ty::vstore_fixed(count)); + fcx.write_ty(id, t); + } + ast::expr_tup(elts) => { + let flds = unpack_expected(fcx, expected, |sty| { + match sty { ty::ty_tup(flds) => Some(flds), _ => None } + }); + let elt_ts = do elts.mapi |i, e| { + check_expr(fcx, *e, flds.map(|fs| fs[i])); + fcx.expr_ty(*e) + }; + let typ = ty::mk_tup(tcx, elt_ts); + fcx.write_ty(id, typ); + } + ast::expr_rec(fields, base) => { + option::iter(&base, |b| { check_expr(fcx, *b, expected); }); + let expected = if expected.is_none() && base.is_some() { + Some(fcx.expr_ty(base.get())) + } else { expected }; + let flds = unpack_expected(fcx, expected, |sty| + match sty { ty::ty_rec(flds) => Some(flds), _ => None } + ); + let fields_t = vec::map(fields, |f| { + bot |= check_expr(fcx, f.node.expr, flds.chain_ref(|flds| + vec::find(*flds, |tf| tf.ident == f.node.ident) + ).map(|tf| tf.mt.ty)); + let expr_t = fcx.expr_ty(f.node.expr); + let expr_mt = {ty: expr_t, mutbl: f.node.mutbl}; + // for the most precise error message, + // should be f.node.expr.span, not f.span + respan(f.node.expr.span, {ident: f.node.ident, mt: expr_mt}) + }); + match base { + None => { + fn get_node(f: &spanned) -> field { f.node } + let typ = ty::mk_rec(tcx, vec::map(fields_t, get_node)); + fcx.write_ty(id, typ); + /* Check for duplicate fields */ + /* Only do this check if there's no base expr -- the reason is + that we're extending a record we know has no dup fields, and + it would be ill-typed anyway if we duplicated one of its + fields */ + check_no_duplicate_fields(tcx, fields.map(|f| + (f.node.ident, f.span))); + } + Some(bexpr) => { + let bexpr_t = fcx.expr_ty(bexpr); + let base_fields = match structure_of(fcx, expr.span, bexpr_t) { + ty::ty_rec(flds) => flds, + _ => { + fcx.type_error_message(expr.span, |_actual| { + ~"record update has non-record base" + }, bexpr_t, None); + fcx.write_ty(id, ty::mk_err(tcx)); + return true; + } + }; + fcx.write_ty(id, bexpr_t); + for fields_t.each |f| { + let mut found = false; + for base_fields.each |bf| { + if f.node.ident == bf.ident { + demand::suptype(fcx, f.span, bf.mt.ty, f.node.mt.ty); + found = true; + } + } + if !found { + tcx.sess.span_err(f.span, + ~"unknown field in record update: " + + tcx.sess.str_of(f.node.ident)); + fcx.write_ty(id, ty::mk_err(tcx)); + return true; + } + } + } + } + } + ast::expr_struct(path, fields, base_expr) => { + // Resolve the path. + match tcx.def_map.find(id) { + Some(ast::def_class(type_def_id)) => { + check_struct_constructor(fcx, id, expr.span, type_def_id, + fields, base_expr); + } + Some(ast::def_variant(enum_id, variant_id)) => { + check_struct_enum_variant(fcx, id, expr.span, enum_id, + variant_id, fields); + } + _ => { + tcx.sess.span_bug(path.span, ~"structure constructor does \ + not name a structure type"); + } + } + } + ast::expr_field(base, field, tys) => { + bot = check_field(fcx, expr, false, base, field, tys); + } + ast::expr_index(base, idx) => { + bot |= check_expr(fcx, base, None); + let raw_base_t = fcx.expr_ty(base); + let (base_t, derefs) = do_autoderef(fcx, expr.span, raw_base_t); + bot |= check_expr(fcx, idx, None); + let idx_t = fcx.expr_ty(idx); + let base_sty = structure_of(fcx, expr.span, base_t); + match ty::index_sty(tcx, &base_sty) { + Some(mt) => { + require_integral(fcx, idx.span, idx_t); + fcx.write_ty(id, mt.ty); + fcx.write_autoderef_adjustment(base.id, derefs); + } + None => { + let resolved = structurally_resolved_type(fcx, expr.span, + raw_base_t); + match lookup_op_method(fcx, expr, base, resolved, + tcx.sess.ident_of(~"index"), + ~[idx], DontDerefArgs) { + Some((ret_ty, _)) => fcx.write_ty(id, ret_ty), + _ => { + fcx.type_error_message(expr.span, |actual| + fmt!("cannot index a value of type `%s`", + actual), base_t, None); + fcx.write_ty(id, ty::mk_err(tcx)); + return true; + } + } + } + } + } + } + if bot { fcx.write_bot(expr.id); } + + debug!("type of expr %s is %s, expected is %s", + syntax::print::pprust::expr_to_str(expr, tcx.sess.intr()), + ty_to_str(tcx, fcx.expr_ty(expr)), + match expected { + Some(t) => ty_to_str(tcx, t), + _ => ~"empty" + }); + + unifier(); + + debug!("<< bot=%b", bot); + return bot; +} + +fn require_integral(fcx: @fn_ctxt, sp: span, t: ty::t) { + if !type_is_integral(fcx, sp, t) { + fcx.type_error_message(sp, |actual| { + fmt!("mismatched types: expected integral type but found `%s`", + actual) + }, t, None); + } +} + +fn check_decl_initializer(fcx: @fn_ctxt, nid: ast::node_id, + init: @ast::expr) -> bool { + let lty = ty::mk_var(fcx.ccx.tcx, lookup_local(fcx, init.span, nid)); + return check_expr_with_assignability(fcx, init, lty); +} + +fn check_decl_local(fcx: @fn_ctxt, local: @ast::local) -> bool { + let mut bot = false; + let tcx = fcx.ccx.tcx; + + let t = ty::mk_var(tcx, fcx.inh.locals.get(local.node.id)); + fcx.write_ty(local.node.id, t); + + let is_lvalue; + match local.node.init { + Some(init) => { + bot = check_decl_initializer(fcx, local.node.id, init); + is_lvalue = ty::expr_is_lval(tcx, fcx.ccx.method_map, init); + } + _ => { + is_lvalue = true; + } + } + + let region = + ty::re_scope(tcx.region_map.get(local.node.id)); + let pcx = { + fcx: fcx, + map: pat_id_map(tcx.def_map, local.node.pat), + alt_region: region, + block_region: region, + }; + alt::check_pat(pcx, local.node.pat, t); + let has_guard = false; + alt::check_legality_of_move_bindings(fcx, + is_lvalue, + has_guard, + [local.node.pat]); + return bot; +} + +fn check_stmt(fcx: @fn_ctxt, stmt: @ast::stmt) -> bool { + let mut node_id; + let mut bot = false; + match stmt.node { + ast::stmt_decl(decl, id) => { + node_id = id; + match decl.node { + ast::decl_local(ls) => for ls.each |l| { + bot |= check_decl_local(fcx, *l); + }, + ast::decl_item(_) => {/* ignore for now */ } + } + } + ast::stmt_expr(expr, id) => { + node_id = id; + bot = check_expr_with(fcx, expr, ty::mk_nil(fcx.ccx.tcx)); + } + ast::stmt_semi(expr, id) => { + node_id = id; + bot = check_expr(fcx, expr, None); + } + ast::stmt_mac(*) => fcx.ccx.tcx.sess.bug(~"unexpanded macro") + } + fcx.write_nil(node_id); + return bot; +} + +fn check_block_no_value(fcx: @fn_ctxt, blk: ast::blk) -> bool { + let bot = check_block(fcx, blk); + if !bot { + let blkty = fcx.node_ty(blk.node.id); + let nilty = ty::mk_nil(fcx.ccx.tcx); + demand::suptype(fcx, blk.span, nilty, blkty); + } + return bot; +} + +fn check_block(fcx0: @fn_ctxt, blk: ast::blk) -> bool { + let fcx = match blk.node.rules { + ast::unsafe_blk => @fn_ctxt {purity: ast::unsafe_fn,.. *fcx0}, + ast::default_blk => fcx0 + }; + do fcx.with_region_lb(blk.node.id) { + let mut bot = false; + let mut warned = false; + for blk.node.stmts.each |s| { + if bot && !warned && + match s.node { + ast::stmt_decl(@{node: ast::decl_local(_), _}, _) | + ast::stmt_expr(_, _) | ast::stmt_semi(_, _) => { + true + } + _ => false + } { + fcx.ccx.tcx.sess.span_warn(s.span, ~"unreachable statement"); + warned = true; + } + bot |= check_stmt(fcx, *s); + } + match blk.node.expr { + None => fcx.write_nil(blk.node.id), + Some(e) => { + if bot && !warned { + fcx.ccx.tcx.sess.span_warn(e.span, ~"unreachable expression"); + } + bot |= check_expr(fcx, e, None); + let ety = fcx.expr_ty(e); + fcx.write_ty(blk.node.id, ety); + } + } + if bot { + fcx.write_bot(blk.node.id); + } + bot + } +} + +fn check_const(ccx: @crate_ctxt, _sp: span, e: @ast::expr, id: ast::node_id) { + let rty = ty::node_id_to_type(ccx.tcx, id); + let fcx = blank_fn_ctxt(ccx, rty, e.id); + let declty = fcx.ccx.tcx.tcache.get(local_def(id)).ty; + check_const_with_ty(fcx, _sp, e, declty); +} + +fn check_const_with_ty(fcx: @fn_ctxt, _sp: span, e: @ast::expr, + declty: ty::t) { + check_expr(fcx, e, None); + let cty = fcx.expr_ty(e); + demand::suptype(fcx, e.span, declty, cty); + regionck::regionck_expr(fcx, e); + writeback::resolve_type_vars_in_expr(fcx, e); +} + +/// Checks whether a type can be created without an instance of itself. +/// This is similar but different from the question of whether a type +/// can be represented. For example, the following type: +/// +/// enum foo { None, Some(foo) } +/// +/// is instantiable but is not representable. Similarly, the type +/// +/// enum foo { Some(@foo) } +/// +/// is representable, but not instantiable. +fn check_instantiable(tcx: ty::ctxt, + sp: span, + item_id: ast::node_id) { + let item_ty = ty::node_id_to_type(tcx, item_id); + if !ty::is_instantiable(tcx, item_ty) { + tcx.sess.span_err(sp, fmt!("this type cannot be instantiated \ + without an instance of itself; \ + consider using `option<%s>`", + ty_to_str(tcx, item_ty))); + } +} + +fn check_enum_variants(ccx: @crate_ctxt, + sp: span, + vs: ~[ast::variant], + id: ast::node_id) { + fn do_check(ccx: @crate_ctxt, sp: span, vs: ~[ast::variant], + id: ast::node_id, disr_vals: &mut ~[int], disr_val: &mut int, + variants: &mut ~[ty::variant_info]) { + let rty = ty::node_id_to_type(ccx.tcx, id); + for vs.each |v| { + do v.node.disr_expr.iter |e_ref| { + let e = *e_ref; + debug!("disr expr, checking %s", + expr_to_str(e, ccx.tcx.sess.intr())); + let declty = ty::mk_int(ccx.tcx); + let fcx = blank_fn_ctxt(ccx, rty, e.id); + check_const_with_ty(fcx, e.span, e, declty); + // check_expr (from check_const pass) doesn't guarantee + // that the expression is in an form that eval_const_expr can + // handle, so we may still get an internal compiler error + + match const_eval::eval_const_expr_partial(ccx.tcx, e) { + Ok(const_eval::const_int(val)) => { + *disr_val = val as int; + } + Ok(_) => { + ccx.tcx.sess.span_err(e.span, ~"expected signed integer \ + constant"); + } + Err(err) => { + ccx.tcx.sess.span_err(e.span, + fmt!("expected constant: %s", err)); + + } + } + } + if vec::contains(*disr_vals, &*disr_val) { + ccx.tcx.sess.span_err(v.span, + ~"discriminator value already exists"); + } + disr_vals.push(*disr_val); + let ctor_ty = ty::node_id_to_type(ccx.tcx, v.node.id); + let arg_tys; + + let this_disr_val = *disr_val; + *disr_val += 1; + + match v.node.kind { + ast::tuple_variant_kind(args) if args.len() > 0u => { + arg_tys = Some(ty::ty_fn_args(ctor_ty).map(|a| a.ty)); + } + ast::tuple_variant_kind(_) => { + arg_tys = Some(~[]); + } + ast::struct_variant_kind(_) => { + arg_tys = Some(ty::lookup_class_fields( + ccx.tcx, local_def(v.node.id)).map(|cf| + ty::node_id_to_type(ccx.tcx, cf.id.node))); + } + ast::enum_variant_kind(_) => { + arg_tys = None; + do_check(ccx, sp, vs, id, disr_vals, disr_val, variants); + } + } + + match arg_tys { + None => {} + Some(arg_tys) => { + variants.push( + @{args: arg_tys, ctor_ty: ctor_ty, + name: v.node.name, id: local_def(v.node.id), + disr_val: this_disr_val}); + } + } + } + } + + let rty = ty::node_id_to_type(ccx.tcx, id); + let mut disr_vals: ~[int] = ~[]; + let mut disr_val = 0; + let mut variants = ~[]; + + do_check(ccx, sp, vs, id, &mut disr_vals, &mut disr_val, &mut variants); + + // cache so that ty::enum_variants won't repeat this work + ccx.tcx.enum_var_cache.insert(local_def(id), @variants); + + // Check that it is possible to represent this enum: + let mut outer = true, did = local_def(id); + if ty::type_structurally_contains(ccx.tcx, rty, |sty| { + match *sty { + ty::ty_enum(id, _) if id == did => { + if outer { outer = false; false } + else { true } + } + _ => false + } + }) { + ccx.tcx.sess.span_err(sp, ~"illegal recursive enum type; \ + wrap the inner value in a box to \ + make it representable"); + } + + // Check that it is possible to instantiate this enum: + // + // This *sounds* like the same that as representable, but it's + // not. See def'n of `check_instantiable()` for details. + check_instantiable(ccx.tcx, sp, id); +} + +fn lookup_local(fcx: @fn_ctxt, sp: span, id: ast::node_id) -> TyVid { + match fcx.inh.locals.find(id) { + Some(x) => x, + _ => { + fcx.ccx.tcx.sess.span_fatal( + sp, + ~"internal error looking up a local var") + } + } +} + +fn lookup_def(fcx: @fn_ctxt, sp: span, id: ast::node_id) -> ast::def { + lookup_def_ccx(fcx.ccx, sp, id) +} + +// Returns the type parameter count and the type for the given definition. +fn ty_param_bounds_and_ty_for_def(fcx: @fn_ctxt, sp: span, defn: ast::def) -> + ty_param_bounds_and_ty { + + match defn { + ast::def_arg(nid, _) | ast::def_local(nid, _) | + ast::def_self(nid) | ast::def_binding(nid, _) => { + assert (fcx.inh.locals.contains_key(nid)); + let typ = ty::mk_var(fcx.ccx.tcx, lookup_local(fcx, sp, nid)); + return no_params(typ); + } + ast::def_fn(_, ast::extern_fn) => { + // extern functions are just u8 pointers + return { + bounds: @~[], + region_param: None, + ty: ty::mk_ptr( + fcx.ccx.tcx, + { + ty: ty::mk_mach_uint(fcx.ccx.tcx, ast::ty_u8), + mutbl: ast::m_imm + }) + }; + } + + ast::def_fn(id, ast::unsafe_fn) | + ast::def_static_method(id, _, ast::unsafe_fn) => { + // Unsafe functions can only be touched in an unsafe context + fcx.require_unsafe(sp, ~"access to unsafe function"); + return ty::lookup_item_type(fcx.ccx.tcx, id); + } + + ast::def_fn(id, _) | ast::def_static_method(id, _, _) | + ast::def_const(id) | ast::def_variant(_, id) | + ast::def_class(id) => { + return ty::lookup_item_type(fcx.ccx.tcx, id); + } + ast::def_upvar(_, inner, _, _) => { + return ty_param_bounds_and_ty_for_def(fcx, sp, *inner); + } + ast::def_ty(_) | ast::def_prim_ty(_) | ast::def_ty_param(*)=> { + fcx.ccx.tcx.sess.span_bug(sp, ~"expected value but found type"); + } + ast::def_mod(*) | ast::def_foreign_mod(*) => { + fcx.ccx.tcx.sess.span_bug(sp, ~"expected value but found module"); + } + ast::def_use(*) => { + fcx.ccx.tcx.sess.span_bug(sp, ~"expected value but found use"); + } + ast::def_region(*) => { + fcx.ccx.tcx.sess.span_bug(sp, ~"expected value but found region"); + } + ast::def_typaram_binder(*) => { + fcx.ccx.tcx.sess.span_bug(sp, ~"expected value but found type \ + parameter"); + } + ast::def_label(*) => { + fcx.ccx.tcx.sess.span_bug(sp, ~"expected value but found label"); + } + } +} + +// Instantiates the given path, which must refer to an item with the given +// number of type parameters and type. +fn instantiate_path(fcx: @fn_ctxt, + pth: @ast::path, + tpt: ty_param_bounds_and_ty, + span: span, + node_id: ast::node_id, + region_lb: ty::Region) { + let ty_param_count = vec::len(*tpt.bounds); + let ty_substs_len = vec::len(pth.types); + + // determine the region bound, using the value given by the user + // (if any) and otherwise using a fresh region variable + let self_r = match pth.rp { + Some(r) => { + match tpt.region_param { + None => { + fcx.ccx.tcx.sess.span_err + (span, ~"this item is not region-parameterized"); + None + } + Some(_) => { + Some(ast_region_to_region(fcx, fcx, span, r)) + } + } + } + None => { + fcx.region_var_if_parameterized( + tpt.region_param, span, region_lb) + } + }; + + // determine values for type parameters, using the values given by + // the user (if any) and otherwise using fresh type variables + let tps = if ty_substs_len == 0 { + fcx.infcx().next_ty_vars(ty_param_count) + } else if ty_param_count == 0 { + fcx.ccx.tcx.sess.span_err + (span, ~"this item does not take type parameters"); + fcx.infcx().next_ty_vars(ty_param_count) + } else if ty_substs_len > ty_param_count { + fcx.ccx.tcx.sess.span_err + (span, ~"too many type parameters provided for this item"); + fcx.infcx().next_ty_vars(ty_param_count) + } else if ty_substs_len < ty_param_count { + fcx.ccx.tcx.sess.span_err + (span, ~"not enough type parameters provided for this item"); + fcx.infcx().next_ty_vars(ty_param_count) + } else { + pth.types.map(|aty| fcx.to_ty(*aty)) + }; + + let substs = {self_r: self_r, self_ty: None, tps: tps}; + fcx.write_ty_substs(node_id, tpt.ty, substs); +} + +// Resolves `typ` by a single level if `typ` is a type variable. If no +// resolution is possible, then an error is reported. +fn structurally_resolved_type(fcx: @fn_ctxt, sp: span, tp: ty::t) -> ty::t { + match infer::resolve_type(fcx.infcx(), tp, force_tvar) { + Ok(t_s) if !ty::type_is_ty_var(t_s) => return t_s, + _ => { + fcx.type_error_message(sp, |_actual| { + ~"the type of this value must be known in this context" + }, tp, None); + return ty::mk_err(fcx.tcx()); + } + } +} + +// Returns the one-level-deep structure of the given type. +fn structure_of(fcx: @fn_ctxt, sp: span, typ: ty::t) -> ty::sty { + ty::get(structurally_resolved_type(fcx, sp, typ)).sty +} + +fn type_is_integral(fcx: @fn_ctxt, sp: span, typ: ty::t) -> bool { + let typ_s = structurally_resolved_type(fcx, sp, typ); + return ty::type_is_integral(typ_s); +} + +fn type_is_scalar(fcx: @fn_ctxt, sp: span, typ: ty::t) -> bool { + let typ_s = structurally_resolved_type(fcx, sp, typ); + return ty::type_is_scalar(typ_s); +} + +fn type_is_c_like_enum(fcx: @fn_ctxt, sp: span, typ: ty::t) -> bool { + let typ_s = structurally_resolved_type(fcx, sp, typ); + return ty::type_is_c_like_enum(fcx.ccx.tcx, typ_s); +} + +fn ast_expr_vstore_to_vstore(fcx: @fn_ctxt, e: @ast::expr, n: uint, + v: ast::expr_vstore) -> ty::vstore { + match v { + ast::expr_vstore_fixed(None) => ty::vstore_fixed(n), + ast::expr_vstore_fixed(Some(u)) => { + if n != u { + let s = fmt!("fixed-size sequence mismatch: %u vs. %u",u, n); + fcx.ccx.tcx.sess.span_err(e.span,s); + } + ty::vstore_fixed(u) + } + ast::expr_vstore_uniq => ty::vstore_uniq, + ast::expr_vstore_box | ast::expr_vstore_mut_box => ty::vstore_box, + ast::expr_vstore_slice => { + let r = fcx.infcx().next_region_var(e.span, e.id); + ty::vstore_slice(r) + } + } +} + +// Returns true if b contains a break that can exit from b +fn may_break(cx: ty::ctxt, id: ast::node_id, b: ast::blk) -> bool { + // First: is there an unlabeled break immediately + // inside the loop? + (loop_query(b, |e| { + match e { + ast::expr_break(_) => true, + _ => false + } + })) || + // Second: is there a labeled break with label + // nested anywhere inside the loop? + (block_query(b, |e| { + match e.node { + ast::expr_break(Some(_)) => + match cx.def_map.find(e.id) { + Some(ast::def_label(loop_id)) if id == loop_id => true, + _ => false, + }, + _ => false + }})) +} + +fn check_bounds_are_used(ccx: @crate_ctxt, + span: span, + tps: ~[ast::ty_param], + ty: ty::t) { + debug!("check_bounds_are_used(n_tps=%u, ty=%s)", + tps.len(), ty_to_str(ccx.tcx, ty)); + + // make a vector of booleans initially false, set to true when used + if tps.len() == 0u { return; } + let tps_used = vec::to_mut(vec::from_elem(tps.len(), false)); + + ty::walk_regions_and_ty( + ccx.tcx, ty, + |_r| {}, + |t| { + match ty::get(t).sty { + ty::ty_param({idx, _}) => { + debug!("Found use of ty param #%u", idx); + tps_used[idx] = true; + } + _ => () + } + true + }); + + for tps_used.eachi |i, b| { + if !*b { + ccx.tcx.sess.span_err( + span, fmt!("type parameter `%s` is unused", + ccx.tcx.sess.str_of(tps[i].ident))); + } + } +} + +fn check_intrinsic_type(ccx: @crate_ctxt, it: @ast::foreign_item) { + fn param(ccx: @crate_ctxt, n: uint) -> ty::t { + ty::mk_param(ccx.tcx, n, local_def(0)) + } + fn arg(m: ast::rmode, ty: ty::t) -> ty::arg { + {mode: ast::expl(m), ty: ty} + } + let tcx = ccx.tcx; + let (n_tps, inputs, output) = match ccx.tcx.sess.str_of(it.ident) { + ~"size_of" | + ~"pref_align_of" | ~"min_align_of" => (1u, ~[], ty::mk_uint(ccx.tcx)), + ~"init" => (1u, ~[], param(ccx, 0u)), + ~"forget" => (1u, ~[arg(ast::by_move, param(ccx, 0u))], + ty::mk_nil(tcx)), + ~"reinterpret_cast" => (2u, ~[arg(ast::by_ref, param(ccx, 0u))], + param(ccx, 1u)), + ~"addr_of" => (1u, ~[arg(ast::by_ref, param(ccx, 0u))], + ty::mk_imm_ptr(tcx, param(ccx, 0u))), + ~"move_val" | ~"move_val_init" => { + (1u, ~[arg(ast::by_copy, + ty::mk_mut_rptr(tcx, ty::re_bound(ty::br_anon(0)), + param(ccx, 0u))), + arg(ast::by_move, param(ccx, 0u))], + ty::mk_nil(tcx)) + } + ~"needs_drop" => (1u, ~[], ty::mk_bool(tcx)), + + ~"atomic_cxchg" | ~"atomic_cxchg_acq"| ~"atomic_cxchg_rel" => { + (0u, ~[arg(ast::by_copy, + ty::mk_mut_rptr(tcx, ty::re_bound(ty::br_anon(0)), + ty::mk_int(tcx))), + arg(ast::by_copy, ty::mk_int(tcx)), + arg(ast::by_copy, ty::mk_int(tcx))], + ty::mk_int(tcx)) + } + ~"atomic_xchg" | ~"atomic_xadd" | ~"atomic_xsub" | + ~"atomic_xchg_acq" | ~"atomic_xadd_acq" | ~"atomic_xsub_acq" | + ~"atomic_xchg_rel" | ~"atomic_xadd_rel" | ~"atomic_xsub_rel" => { + (0u, ~[arg(ast::by_copy, + ty::mk_mut_rptr(tcx, ty::re_bound(ty::br_anon(0)), + ty::mk_int(tcx))), + arg(ast::by_copy, ty::mk_int(tcx))], + ty::mk_int(tcx)) + } + + ~"get_tydesc" => { + // FIXME (#3730): return *intrinsic::tydesc, not *() + (1u, ~[], ty::mk_nil_ptr(tcx)) + } + ~"visit_tydesc" => { + let tydesc_name = syntax::parse::token::special_idents::tydesc; + let ty_visitor_name = tcx.sess.ident_of(~"TyVisitor"); + assert tcx.intrinsic_defs.contains_key(tydesc_name); + assert ccx.tcx.intrinsic_defs.contains_key(ty_visitor_name); + let (_, tydesc_ty) = tcx.intrinsic_defs.get(tydesc_name); + let (_, visitor_trait) = tcx.intrinsic_defs.get(ty_visitor_name); + let td_ptr = ty::mk_ptr(ccx.tcx, {ty: tydesc_ty, + mutbl: ast::m_imm}); + (0u, ~[arg(ast::by_val, td_ptr), + arg(ast::by_ref, visitor_trait)], ty::mk_nil(tcx)) + } + ~"frame_address" => { + let fty = ty::mk_fn(ccx.tcx, FnTyBase { + meta: FnMeta {purity: ast::impure_fn, + proto: ast::ProtoBorrowed, + onceness: ast::Once, + region: ty::re_bound(ty::br_anon(0)), + bounds: @~[], + ret_style: ast::return_val}, + sig: FnSig {inputs: ~[{mode: ast::expl(ast::by_val), + ty: ty::mk_imm_ptr( + ccx.tcx, + ty::mk_mach_uint(ccx.tcx, ast::ty_u8)) + }], + output: ty::mk_nil(ccx.tcx)} + }); + (0u, ~[arg(ast::by_ref, fty)], ty::mk_nil(tcx)) + } + ~"morestack_addr" => { + (0u, ~[], ty::mk_nil_ptr(tcx)) + } + other => { + tcx.sess.span_err(it.span, ~"unrecognized intrinsic function: `" + + other + ~"`"); + return; + } + }; + let fty = ty::mk_fn(tcx, FnTyBase { + meta: FnMeta {purity: ast::impure_fn, + proto: ast::ProtoBare, + onceness: ast::Many, + region: ty::re_static, + bounds: @~[], + ret_style: ast::return_val}, + sig: FnSig {inputs: inputs, + output: output} + }); + let i_ty = ty::lookup_item_type(ccx.tcx, local_def(it.id)); + let i_n_tps = (*i_ty.bounds).len(); + if i_n_tps != n_tps { + tcx.sess.span_err(it.span, fmt!("intrinsic has wrong number \ + of type parameters: found %u, \ + expected %u", i_n_tps, n_tps)); + } else { + require_same_types( + tcx, None, false, it.span, i_ty.ty, fty, + || fmt!("intrinsic has wrong type: \ + expected `%s`", + ty_to_str(ccx.tcx, fty))); + } +} diff --git a/src/librustc/middle/typeck/infer.rs b/src/librustc/middle/typeck/infer.rs deleted file mode 100644 index b4ffd92c0a8..00000000000 --- a/src/librustc/middle/typeck/infer.rs +++ /dev/null @@ -1,745 +0,0 @@ -/*! - -# Type inference engine - -This is loosely based on standard HM-type inference, but with an -extension to try and accommodate subtyping. There is nothing -principled about this extension; it's sound---I hope!---but it's a -heuristic, ultimately, and does not guarantee that it finds a valid -typing even if one exists (in fact, there are known scenarios where it -fails, some of which may eventually become problematic). - -## Key idea - -The main change is that each type variable T is associated with a -lower-bound L and an upper-bound U. L and U begin as bottom and top, -respectively, but gradually narrow in response to new constraints -being introduced. When a variable is finally resolved to a concrete -type, it can (theoretically) select any type that is a supertype of L -and a subtype of U. - -There are several critical invariants which we maintain: - -- the upper-bound of a variable only becomes lower and the lower-bound - only becomes higher over time; -- the lower-bound L is always a subtype of the upper bound U; -- the lower-bound L and upper-bound U never refer to other type variables, - but only to types (though those types may contain type variables). - -> An aside: if the terms upper- and lower-bound confuse you, think of -> "supertype" and "subtype". The upper-bound is a "supertype" -> (super=upper in Latin, or something like that anyway) and the lower-bound -> is a "subtype" (sub=lower in Latin). I find it helps to visualize -> a simple class hierarchy, like Java minus interfaces and -> primitive types. The class Object is at the root (top) and other -> types lie in between. The bottom type is then the Null type. -> So the tree looks like: -> -> Object -> / \ -> String Other -> \ / -> (null) -> -> So the upper bound type is the "supertype" and the lower bound is the -> "subtype" (also, super and sub mean upper and lower in Latin, or something -> like that anyway). - -## Satisfying constraints - -At a primitive level, there is only one form of constraint that the -inference understands: a subtype relation. So the outside world can -say "make type A a subtype of type B". If there are variables -involved, the inferencer will adjust their upper- and lower-bounds as -needed to ensure that this relation is satisfied. (We also allow "make -type A equal to type B", but this is translated into "A <: B" and "B -<: A") - -As stated above, we always maintain the invariant that type bounds -never refer to other variables. This keeps the inference relatively -simple, avoiding the scenario of having a kind of graph where we have -to pump constraints along and reach a fixed point, but it does impose -some heuristics in the case where the user is relating two type -variables A <: B. - -Combining two variables such that variable A will forever be a subtype -of variable B is the trickiest part of the algorithm because there is -often no right choice---that is, the right choice will depend on -future constraints which we do not yet know. The problem comes about -because both A and B have bounds that can be adjusted in the future. -Let's look at some of the cases that can come up. - -Imagine, to start, the best case, where both A and B have an upper and -lower bound (that is, the bounds are not top nor bot respectively). In -that case, if we're lucky, A.ub <: B.lb, and so we know that whatever -A and B should become, they will forever have the desired subtyping -relation. We can just leave things as they are. - -### Option 1: Unify - -However, suppose that A.ub is *not* a subtype of B.lb. In -that case, we must make a decision. One option is to unify A -and B so that they are one variable whose bounds are: - - UB = GLB(A.ub, B.ub) - LB = LUB(A.lb, B.lb) - -(Note that we will have to verify that LB <: UB; if it does not, the -types are not intersecting and there is an error) In that case, A <: B -holds trivially because A==B. However, we have now lost some -flexibility, because perhaps the user intended for A and B to end up -as different types and not the same type. - -Pictorally, what this does is to take two distinct variables with -(hopefully not completely) distinct type ranges and produce one with -the intersection. - - B.ub B.ub - /\ / - A.ub / \ A.ub / - / \ / \ \ / - / X \ UB - / / \ \ / \ - / / / \ / / - \ \ / / \ / - \ X / LB - \ / \ / / \ - \ / \ / / \ - A.lb B.lb A.lb B.lb - - -### Option 2: Relate UB/LB - -Another option is to keep A and B as distinct variables but set their -bounds in such a way that, whatever happens, we know that A <: B will hold. -This can be achieved by ensuring that A.ub <: B.lb. In practice there -are two ways to do that, depicted pictorally here: - - Before Option #1 Option #2 - - B.ub B.ub B.ub - /\ / \ / \ - A.ub / \ A.ub /(B')\ A.ub /(B')\ - / \ / \ \ / / \ / / - / X \ __UB____/ UB / - / / \ \ / | | / - / / / \ / | | / - \ \ / / /(A')| | / - \ X / / LB ______LB/ - \ / \ / / / \ / (A')/ \ - \ / \ / \ / \ \ / \ - A.lb B.lb A.lb B.lb A.lb B.lb - -In these diagrams, UB and LB are defined as before. As you can see, -the new ranges `A'` and `B'` are quite different from the range that -would be produced by unifying the variables. - -### What we do now - -Our current technique is to *try* (transactionally) to relate the -existing bounds of A and B, if there are any (i.e., if `UB(A) != top -&& LB(B) != bot`). If that succeeds, we're done. If it fails, then -we merge A and B into same variable. - -This is not clearly the correct course. For example, if `UB(A) != -top` but `LB(B) == bot`, we could conceivably set `LB(B)` to `UB(A)` -and leave the variables unmerged. This is sometimes the better -course, it depends on the program. - -The main case which fails today that I would like to support is: - - fn foo(x: T, y: T) { ... } - - fn bar() { - let x: @mut int = @mut 3; - let y: @int = @3; - foo(x, y); - } - -In principle, the inferencer ought to find that the parameter `T` to -`foo(x, y)` is `@const int`. Today, however, it does not; this is -because the type variable `T` is merged with the type variable for -`X`, and thus inherits its UB/LB of `@mut int`. This leaves no -flexibility for `T` to later adjust to accommodate `@int`. - -### What to do when not all bounds are present - -In the prior discussion we assumed that A.ub was not top and B.lb was -not bot. Unfortunately this is rarely the case. Often type variables -have "lopsided" bounds. For example, if a variable in the program has -been initialized but has not been used, then its corresponding type -variable will have a lower bound but no upper bound. When that -variable is then used, we would like to know its upper bound---but we -don't have one! In this case we'll do different things depending on -how the variable is being used. - -## Transactional support - -Whenever we adjust merge variables or adjust their bounds, we always -keep a record of the old value. This allows the changes to be undone. - -## Regions - -I've only talked about type variables here, but region variables -follow the same principle. They have upper- and lower-bounds. A -region A is a subregion of a region B if A being valid implies that B -is valid. This basically corresponds to the block nesting structure: -the regions for outer block scopes are superregions of those for inner -block scopes. - -## Integral type variables - -There is a third variety of type variable that we use only for -inferring the types of unsuffixed integer literals. Integral type -variables differ from general-purpose type variables in that there's -no subtyping relationship among the various integral types, so instead -of associating each variable with an upper and lower bound, we -represent the set of possible integral types it can take on with an -`int_ty_set`, which is a bitvector with one bit for each integral -type. Because intersecting these sets with each other is simpler than -merging bounds, we don't need to do so transactionally as we do for -general-purpose type variables. - -We could conceivably define a subtyping relationship among integral -types based on their ranges, but we choose not to open that particular -can of worms. Our strategy is to treat integral type variables as -unknown until the typing context constrains them to a unique integral -type, at which point they take on that type. If the typing context -overconstrains the type, it's a type error; if we reach the point at -which type variables must be resolved and an integral type variable is -still underconstrained, it defaults to `int` as a last resort. - -Floating point types are handled similarly to integral types. - -## GLB/LUB - -Computing the greatest-lower-bound and least-upper-bound of two -types/regions is generally straightforward except when type variables -are involved. In that case, we follow a similar "try to use the bounds -when possible but otherwise merge the variables" strategy. In other -words, `GLB(A, B)` where `A` and `B` are variables will often result -in `A` and `B` being merged and the result being `A`. - -## Type assignment - -We have a notion of assignability which differs somewhat from -subtyping; in particular it may cause region borrowing to occur. See -the big comment later in this file on Type Assignment for specifics. - -### In conclusion - -I showed you three ways to relate `A` and `B`. There are also more, -of course, though I'm not sure if there are any more sensible options. -The main point is that there are various options, each of which -produce a distinct range of types for `A` and `B`. Depending on what -the correct values for A and B are, one of these options will be the -right choice: but of course we don't know the right values for A and B -yet, that's what we're trying to find! In our code, we opt to unify -(Option #1). - -# Implementation details - -We make use of a trait-like impementation strategy to consolidate -duplicated code between subtypes, GLB, and LUB computations. See the -section on "Type Combining" below for details. - -*/ - -#[warn(deprecated_mode)]; -#[warn(deprecated_pattern)]; - -use std::smallintmap; -use std::smallintmap::smallintmap; -use std::map::HashMap; -use middle::ty; -use middle::ty::{TyVid, IntVid, FloatVid, RegionVid, vid, - ty_int, ty_uint, get, terr_fn, TyVar, IntVar, FloatVar}; -use syntax::{ast, ast_util}; -use syntax::ast::{ret_style, purity}; -use util::ppaux::{ty_to_str, mt_to_str}; -use result::{Result, Ok, Err, map_vec, map_vec2, iter_vec2}; -use ty::{mk_fn, type_is_bot}; -use check::regionmanip::{replace_bound_regions_in_fn_ty}; -use util::common::{indent, indenter}; -use ast::{unsafe_fn, impure_fn, pure_fn, extern_fn}; -use ast::{m_const, m_imm, m_mutbl}; -use dvec::DVec; -use region_inference::{RegionVarBindings}; -use ast_util::dummy_sp; -use cmp::Eq; - -// From submodules: -use resolve::{resolve_nested_tvar, resolve_rvar, resolve_ivar, resolve_all, - force_tvar, force_rvar, force_ivar, force_all, not_regions, - resolve_and_force_all_but_regions, resolver}; -use unify::{vals_and_bindings, root}; -use integral::{int_ty_set, int_ty_set_all}; -use floating::{float_ty_set, float_ty_set_all}; -use combine::{combine_fields, eq_tys}; -use assignment::Assign; -use to_str::ToStr; - -use sub::Sub; -use lub::Lub; -use glb::Glb; - -export infer_ctxt; -export new_infer_ctxt; -export mk_subty, can_mk_subty; -export mk_subr; -export mk_eqty; -export mk_assignty, can_mk_assignty; -export resolve_nested_tvar, resolve_rvar, resolve_ivar, resolve_all; -export force_tvar, force_rvar, force_ivar, force_all; -export resolve_and_force_all_but_regions, not_regions; -export resolve_type, resolve_region; -export resolve_borrowings; -export methods; // for infer_ctxt -export unify_methods; // for infer_ctxt -export cres, fres, fixup_err, fixup_err_to_str; -export assignment; -export root, to_str; -export int_ty_set_all; - -type bound = Option; -type bounds = {lb: bound, ub: bound}; - -type cres = Result; // "combine result" -type ures = cres<()>; // "unify result" -type fres = Result; // "fixup result" -type ares = cres>; // "assignment result" - -enum infer_ctxt = @{ - tcx: ty::ctxt, - - // We instantiate vals_and_bindings with bounds because the - // types that might instantiate a general type variable have an - // order, represented by its upper and lower bounds. - ty_var_bindings: vals_and_bindings>, - - // The types that might instantiate an integral type variable are - // represented by an int_ty_set. - int_var_bindings: vals_and_bindings, - - // The types that might instantiate a floating-point type variable are - // represented by an float_ty_set. - float_var_bindings: vals_and_bindings, - - // For region variables. - region_vars: RegionVarBindings, - - // For keeping track of existing type and region variables. - ty_var_counter: @mut uint, - int_var_counter: @mut uint, - float_var_counter: @mut uint, - region_var_counter: @mut uint -}; - -enum fixup_err { - unresolved_int_ty(IntVid), - unresolved_ty(TyVid), - cyclic_ty(TyVid), - unresolved_region(RegionVid), - region_var_bound_by_region_var(RegionVid, RegionVid) -} - -fn fixup_err_to_str(f: fixup_err) -> ~str { - match f { - unresolved_int_ty(_) => ~"unconstrained integral type", - unresolved_ty(_) => ~"unconstrained type", - cyclic_ty(_) => ~"cyclic type of infinite size", - unresolved_region(_) => ~"unconstrained region", - region_var_bound_by_region_var(r1, r2) => { - fmt!("region var %? bound by another region var %?; this is \ - a bug in rustc", r1, r2) - } - } -} - -fn new_vals_and_bindings() -> vals_and_bindings { - vals_and_bindings { - vals: smallintmap::mk(), - mut bindings: ~[] - } -} - -fn new_infer_ctxt(tcx: ty::ctxt) -> infer_ctxt { - infer_ctxt(@{tcx: tcx, - ty_var_bindings: new_vals_and_bindings(), - int_var_bindings: new_vals_and_bindings(), - float_var_bindings: new_vals_and_bindings(), - region_vars: RegionVarBindings(tcx), - ty_var_counter: @mut 0u, - int_var_counter: @mut 0u, - float_var_counter: @mut 0u, - region_var_counter: @mut 0u})} - -fn mk_subty(cx: infer_ctxt, a_is_expected: bool, span: span, - a: ty::t, b: ty::t) -> ures { - debug!("mk_subty(%s <: %s)", a.to_str(cx), b.to_str(cx)); - do indent { - do cx.commit { - cx.sub(a_is_expected, span).tys(a, b) - } - }.to_ures() -} - -fn can_mk_subty(cx: infer_ctxt, a: ty::t, b: ty::t) -> ures { - debug!("can_mk_subty(%s <: %s)", a.to_str(cx), b.to_str(cx)); - do indent { - do cx.probe { - cx.sub(true, ast_util::dummy_sp()).tys(a, b) - } - }.to_ures() -} - -fn mk_subr(cx: infer_ctxt, a_is_expected: bool, span: span, - a: ty::Region, b: ty::Region) -> ures { - debug!("mk_subr(%s <: %s)", a.to_str(cx), b.to_str(cx)); - do indent { - do cx.commit { - cx.sub(a_is_expected, span).regions(a, b) - } - }.to_ures() -} - -fn mk_eqty(cx: infer_ctxt, a_is_expected: bool, span: span, - a: ty::t, b: ty::t) -> ures { - debug!("mk_eqty(%s <: %s)", a.to_str(cx), b.to_str(cx)); - do indent { - do cx.commit { - let suber = cx.sub(a_is_expected, span); - eq_tys(&suber, a, b) - } - }.to_ures() -} - -fn mk_assignty(cx: infer_ctxt, a_is_expected: bool, span: span, - a: ty::t, b: ty::t) -> ares { - debug!("mk_assignty(%s -> %s)", a.to_str(cx), b.to_str(cx)); - do indent { - do cx.commit { - Assign(cx.combine_fields(a_is_expected, span)).tys(a, b) - } - } -} - -fn can_mk_assignty(cx: infer_ctxt, a: ty::t, b: ty::t) -> ures { - debug!("can_mk_assignty(%s -> %s)", a.to_str(cx), b.to_str(cx)); - do indent { - do cx.probe { - let span = ast_util::dummy_sp(); - Assign(cx.combine_fields(true, span)).tys(a, b) - } - }.to_ures() -} - -// See comment on the type `resolve_state` below -fn resolve_type(cx: infer_ctxt, a: ty::t, modes: uint) - -> fres { - resolver(cx, modes).resolve_type_chk(a) -} - -fn resolve_region(cx: infer_ctxt, r: ty::Region, modes: uint) - -> fres { - resolver(cx, modes).resolve_region_chk(r) -} - -/* -fn resolve_borrowings(cx: infer_ctxt) { - for cx.borrowings.each |item| { - match resolve_region(cx, item.scope, resolve_all|force_all) { - Ok(region) => { - debug!("borrowing for expr %d resolved to region %?, mutbl %?", - item.expr_id, region, item.mutbl); - cx.tcx.borrowings.insert( - item.expr_id, {region: region, mutbl: item.mutbl}); - } - - Err(e) => { - let str = fixup_err_to_str(e); - cx.tcx.sess.span_err( - item.span, - fmt!("could not resolve lifetime for borrow: %s", str)); - } - } - } -} -*/ - -trait then { - fn then(f: fn() -> Result) - -> Result; -} - -impl ures: then { - fn then(f: fn() -> Result) - -> Result { - self.chain(|_i| f()) - } -} - -trait ToUres { - fn to_ures() -> ures; -} - -impl cres: ToUres { - fn to_ures() -> ures { - match self { - Ok(_v) => Ok(()), - Err(e) => Err(e) - } - } -} - -trait CresCompare { - fn compare(t: T, f: fn() -> ty::type_err) -> cres; -} - -impl cres: CresCompare { - fn compare(t: T, f: fn() -> ty::type_err) -> cres { - do self.chain |s| { - if s == t { - self - } else { - Err(f()) - } - } - } -} - -fn uok() -> ures { - Ok(()) -} - -fn rollback_to( - vb: &vals_and_bindings, len: uint) { - - while vb.bindings.len() != len { - let (vid, old_v) = vb.bindings.pop(); - vb.vals.insert(vid.to_uint(), old_v); - } -} - -struct Snapshot { - ty_var_bindings_len: uint, - int_var_bindings_len: uint, - region_vars_snapshot: uint, -} - -impl infer_ctxt { - fn combine_fields(a_is_expected: bool, - span: span) -> combine_fields { - combine_fields {infcx: self, - a_is_expected: a_is_expected, - span: span} - } - - fn sub(a_is_expected: bool, span: span) -> Sub { - Sub(self.combine_fields(a_is_expected, span)) - } - - fn in_snapshot() -> bool { - self.region_vars.in_snapshot() - } - - fn start_snapshot() -> Snapshot { - Snapshot { - ty_var_bindings_len: - self.ty_var_bindings.bindings.len(), - int_var_bindings_len: - self.int_var_bindings.bindings.len(), - region_vars_snapshot: - self.region_vars.start_snapshot(), - } - } - - fn rollback_to(snapshot: &Snapshot) { - debug!("rollback!"); - rollback_to(&self.ty_var_bindings, snapshot.ty_var_bindings_len); - - // FIXME(#3211) -- int_var not transactional - //rollback_to(&self.int_var_bindings, - // snapshot.int_var_bindings_len); - - self.region_vars.rollback_to( - snapshot.region_vars_snapshot); - } - - /// Execute `f` and commit the bindings if successful - fn commit(f: fn() -> Result) -> Result { - assert !self.in_snapshot(); - - debug!("commit()"); - do indent { - let r = self.try(f); - - self.ty_var_bindings.bindings.truncate(0); - self.int_var_bindings.bindings.truncate(0); - self.region_vars.commit(); - move r - } - } - - /// Execute `f`, unroll bindings on failure - fn try(f: fn() -> Result) -> Result { - debug!("try()"); - do indent { - let snapshot = self.start_snapshot(); - let r = f(); - match r { - Ok(_) => (), - Err(_) => self.rollback_to(&snapshot) - } - move r - } - } - - /// Execute `f` then unroll any bindings it creates - fn probe(f: fn() -> Result) -> Result { - debug!("probe()"); - do indent { - let snapshot = self.start_snapshot(); - let r = self.try(f); - self.rollback_to(&snapshot); - move r - } - } -} - -impl infer_ctxt { - fn next_ty_var_id() -> TyVid { - let id = *self.ty_var_counter; - *self.ty_var_counter += 1u; - self.ty_var_bindings.vals.insert(id, - root({lb: None, ub: None}, 0u)); - return TyVid(id); - } - - fn next_ty_var() -> ty::t { - ty::mk_var(self.tcx, self.next_ty_var_id()) - } - - fn next_ty_vars(n: uint) -> ~[ty::t] { - vec::from_fn(n, |_i| self.next_ty_var()) - } - - fn next_int_var_id() -> IntVid { - let id = *self.int_var_counter; - *self.int_var_counter += 1u; - - self.int_var_bindings.vals.insert(id, - root(int_ty_set_all(), 0u)); - return IntVid(id); - } - - fn next_int_var() -> ty::t { - ty::mk_int_var(self.tcx, self.next_int_var_id()) - } - - fn next_float_var_id() -> FloatVid { - let id = *self.float_var_counter; - *self.float_var_counter += 1; - - self.float_var_bindings.vals.insert(id, root(float_ty_set_all(), 0)); - return FloatVid(id); - } - - fn next_float_var() -> ty::t { - ty::mk_float_var(self.tcx, self.next_float_var_id()) - } - - fn next_region_var_nb(span: span) -> ty::Region { - ty::re_infer(ty::ReVar(self.region_vars.new_region_var(span))) - } - - fn next_region_var_with_lb(span: span, - lb_region: ty::Region) -> ty::Region { - let region_var = self.next_region_var_nb(span); - - // add lb_region as a lower bound on the newly built variable - assert self.region_vars.make_subregion(span, - lb_region, - region_var).is_ok(); - - return region_var; - } - - fn next_region_var(span: span, scope_id: ast::node_id) -> ty::Region { - self.next_region_var_with_lb(span, ty::re_scope(scope_id)) - } - - fn resolve_regions() { - self.region_vars.resolve_regions(); - } - - fn ty_to_str(t: ty::t) -> ~str { - ty_to_str(self.tcx, - self.resolve_type_vars_if_possible(t)) - } - - fn resolve_type_vars_if_possible(typ: ty::t) -> ty::t { - match resolve_type(self, typ, resolve_nested_tvar | resolve_ivar) { - result::Ok(new_type) => new_type, - result::Err(_) => typ - } - } - - fn type_error_message(sp: span, mk_msg: fn(~str) -> ~str, - actual_ty: ty::t, err: Option<&ty::type_err>) { - let actual_ty = self.resolve_type_vars_if_possible(actual_ty); - - // Don't report an error if actual type is ty_err. - match ty::get(actual_ty).sty { - ty::ty_err => return, - _ => () - } - let error_str = err.map_default(~"", |t_err| - fmt!(" (%s)", - ty::type_err_to_str(self.tcx, *t_err))); - self.tcx.sess.span_err(sp, - fmt!("%s%s", mk_msg(self.ty_to_str(actual_ty)), - error_str)); - err.iter(|err| - ty::note_and_explain_type_err(self.tcx, *err)); - } - - fn report_mismatched_types(sp: span, e: ty::t, a: ty::t, - err: &ty::type_err) { - // Don't report an error if expected is ty_err - let resolved_expected = - self.resolve_type_vars_if_possible(e); - let mk_msg = match ty::get(resolved_expected).sty { - ty::ty_err => return, - _ => { - // if I leave out : ~str, it infers &str and complains - |actual: ~str| { - fmt!("mismatched types: expected `%s` but found `%s`", - self.ty_to_str(resolved_expected), actual) - } - } - }; - self.type_error_message(sp, mk_msg, a, Some(err)); - } - - fn replace_bound_regions_with_fresh_regions( - &self, span: span, - fty: &ty::FnTy) -> (ty::FnTy, isr_alist) - { - let {fn_ty, isr, _} = - replace_bound_regions_in_fn_ty(self.tcx, @Nil, None, fty, |br| { - // N.B.: The name of the bound region doesn't have anything to - // do with the region variable that's created for it. The - // only thing we're doing with `br` here is using it in the - // debug message. - let rvar = self.next_region_var_nb(span); - debug!("Bound region %s maps to %?", - bound_region_to_str(self.tcx, br), - rvar); - rvar - }); - (fn_ty, isr) - } - -} - diff --git a/src/librustc/middle/typeck/infer/mod.rs b/src/librustc/middle/typeck/infer/mod.rs index 3ce09d454d8..ec637635f95 100644 --- a/src/librustc/middle/typeck/infer/mod.rs +++ b/src/librustc/middle/typeck/infer/mod.rs @@ -1,4 +1,307 @@ +/*! + +# Type inference engine + +This is loosely based on standard HM-type inference, but with an +extension to try and accommodate subtyping. There is nothing +principled about this extension; it's sound---I hope!---but it's a +heuristic, ultimately, and does not guarantee that it finds a valid +typing even if one exists (in fact, there are known scenarios where it +fails, some of which may eventually become problematic). + +## Key idea + +The main change is that each type variable T is associated with a +lower-bound L and an upper-bound U. L and U begin as bottom and top, +respectively, but gradually narrow in response to new constraints +being introduced. When a variable is finally resolved to a concrete +type, it can (theoretically) select any type that is a supertype of L +and a subtype of U. + +There are several critical invariants which we maintain: + +- the upper-bound of a variable only becomes lower and the lower-bound + only becomes higher over time; +- the lower-bound L is always a subtype of the upper bound U; +- the lower-bound L and upper-bound U never refer to other type variables, + but only to types (though those types may contain type variables). + +> An aside: if the terms upper- and lower-bound confuse you, think of +> "supertype" and "subtype". The upper-bound is a "supertype" +> (super=upper in Latin, or something like that anyway) and the lower-bound +> is a "subtype" (sub=lower in Latin). I find it helps to visualize +> a simple class hierarchy, like Java minus interfaces and +> primitive types. The class Object is at the root (top) and other +> types lie in between. The bottom type is then the Null type. +> So the tree looks like: +> +> Object +> / \ +> String Other +> \ / +> (null) +> +> So the upper bound type is the "supertype" and the lower bound is the +> "subtype" (also, super and sub mean upper and lower in Latin, or something +> like that anyway). + +## Satisfying constraints + +At a primitive level, there is only one form of constraint that the +inference understands: a subtype relation. So the outside world can +say "make type A a subtype of type B". If there are variables +involved, the inferencer will adjust their upper- and lower-bounds as +needed to ensure that this relation is satisfied. (We also allow "make +type A equal to type B", but this is translated into "A <: B" and "B +<: A") + +As stated above, we always maintain the invariant that type bounds +never refer to other variables. This keeps the inference relatively +simple, avoiding the scenario of having a kind of graph where we have +to pump constraints along and reach a fixed point, but it does impose +some heuristics in the case where the user is relating two type +variables A <: B. + +Combining two variables such that variable A will forever be a subtype +of variable B is the trickiest part of the algorithm because there is +often no right choice---that is, the right choice will depend on +future constraints which we do not yet know. The problem comes about +because both A and B have bounds that can be adjusted in the future. +Let's look at some of the cases that can come up. + +Imagine, to start, the best case, where both A and B have an upper and +lower bound (that is, the bounds are not top nor bot respectively). In +that case, if we're lucky, A.ub <: B.lb, and so we know that whatever +A and B should become, they will forever have the desired subtyping +relation. We can just leave things as they are. + +### Option 1: Unify + +However, suppose that A.ub is *not* a subtype of B.lb. In +that case, we must make a decision. One option is to unify A +and B so that they are one variable whose bounds are: + + UB = GLB(A.ub, B.ub) + LB = LUB(A.lb, B.lb) + +(Note that we will have to verify that LB <: UB; if it does not, the +types are not intersecting and there is an error) In that case, A <: B +holds trivially because A==B. However, we have now lost some +flexibility, because perhaps the user intended for A and B to end up +as different types and not the same type. + +Pictorally, what this does is to take two distinct variables with +(hopefully not completely) distinct type ranges and produce one with +the intersection. + + B.ub B.ub + /\ / + A.ub / \ A.ub / + / \ / \ \ / + / X \ UB + / / \ \ / \ + / / / \ / / + \ \ / / \ / + \ X / LB + \ / \ / / \ + \ / \ / / \ + A.lb B.lb A.lb B.lb + + +### Option 2: Relate UB/LB + +Another option is to keep A and B as distinct variables but set their +bounds in such a way that, whatever happens, we know that A <: B will hold. +This can be achieved by ensuring that A.ub <: B.lb. In practice there +are two ways to do that, depicted pictorally here: + + Before Option #1 Option #2 + + B.ub B.ub B.ub + /\ / \ / \ + A.ub / \ A.ub /(B')\ A.ub /(B')\ + / \ / \ \ / / \ / / + / X \ __UB____/ UB / + / / \ \ / | | / + / / / \ / | | / + \ \ / / /(A')| | / + \ X / / LB ______LB/ + \ / \ / / / \ / (A')/ \ + \ / \ / \ / \ \ / \ + A.lb B.lb A.lb B.lb A.lb B.lb + +In these diagrams, UB and LB are defined as before. As you can see, +the new ranges `A'` and `B'` are quite different from the range that +would be produced by unifying the variables. + +### What we do now + +Our current technique is to *try* (transactionally) to relate the +existing bounds of A and B, if there are any (i.e., if `UB(A) != top +&& LB(B) != bot`). If that succeeds, we're done. If it fails, then +we merge A and B into same variable. + +This is not clearly the correct course. For example, if `UB(A) != +top` but `LB(B) == bot`, we could conceivably set `LB(B)` to `UB(A)` +and leave the variables unmerged. This is sometimes the better +course, it depends on the program. + +The main case which fails today that I would like to support is: + + fn foo(x: T, y: T) { ... } + + fn bar() { + let x: @mut int = @mut 3; + let y: @int = @3; + foo(x, y); + } + +In principle, the inferencer ought to find that the parameter `T` to +`foo(x, y)` is `@const int`. Today, however, it does not; this is +because the type variable `T` is merged with the type variable for +`X`, and thus inherits its UB/LB of `@mut int`. This leaves no +flexibility for `T` to later adjust to accommodate `@int`. + +### What to do when not all bounds are present + +In the prior discussion we assumed that A.ub was not top and B.lb was +not bot. Unfortunately this is rarely the case. Often type variables +have "lopsided" bounds. For example, if a variable in the program has +been initialized but has not been used, then its corresponding type +variable will have a lower bound but no upper bound. When that +variable is then used, we would like to know its upper bound---but we +don't have one! In this case we'll do different things depending on +how the variable is being used. + +## Transactional support + +Whenever we adjust merge variables or adjust their bounds, we always +keep a record of the old value. This allows the changes to be undone. + +## Regions + +I've only talked about type variables here, but region variables +follow the same principle. They have upper- and lower-bounds. A +region A is a subregion of a region B if A being valid implies that B +is valid. This basically corresponds to the block nesting structure: +the regions for outer block scopes are superregions of those for inner +block scopes. + +## Integral type variables + +There is a third variety of type variable that we use only for +inferring the types of unsuffixed integer literals. Integral type +variables differ from general-purpose type variables in that there's +no subtyping relationship among the various integral types, so instead +of associating each variable with an upper and lower bound, we +represent the set of possible integral types it can take on with an +`int_ty_set`, which is a bitvector with one bit for each integral +type. Because intersecting these sets with each other is simpler than +merging bounds, we don't need to do so transactionally as we do for +general-purpose type variables. + +We could conceivably define a subtyping relationship among integral +types based on their ranges, but we choose not to open that particular +can of worms. Our strategy is to treat integral type variables as +unknown until the typing context constrains them to a unique integral +type, at which point they take on that type. If the typing context +overconstrains the type, it's a type error; if we reach the point at +which type variables must be resolved and an integral type variable is +still underconstrained, it defaults to `int` as a last resort. + +Floating point types are handled similarly to integral types. + +## GLB/LUB + +Computing the greatest-lower-bound and least-upper-bound of two +types/regions is generally straightforward except when type variables +are involved. In that case, we follow a similar "try to use the bounds +when possible but otherwise merge the variables" strategy. In other +words, `GLB(A, B)` where `A` and `B` are variables will often result +in `A` and `B` being merged and the result being `A`. + +## Type assignment + +We have a notion of assignability which differs somewhat from +subtyping; in particular it may cause region borrowing to occur. See +the big comment later in this file on Type Assignment for specifics. + +### In conclusion + +I showed you three ways to relate `A` and `B`. There are also more, +of course, though I'm not sure if there are any more sensible options. +The main point is that there are various options, each of which +produce a distinct range of types for `A` and `B`. Depending on what +the correct values for A and B are, one of these options will be the +right choice: but of course we don't know the right values for A and B +yet, that's what we're trying to find! In our code, we opt to unify +(Option #1). + +# Implementation details + +We make use of a trait-like impementation strategy to consolidate +duplicated code between subtypes, GLB, and LUB computations. See the +section on "Type Combining" below for details. + +*/ + #[legacy_exports]; +#[warn(deprecated_mode)]; +#[warn(deprecated_pattern)]; + +use std::smallintmap; +use std::smallintmap::smallintmap; +use std::map::HashMap; +use middle::ty; +use middle::ty::{TyVid, IntVid, FloatVid, RegionVid, vid, + ty_int, ty_uint, get, terr_fn, TyVar, IntVar, FloatVar}; +use syntax::{ast, ast_util}; +use syntax::ast::{ret_style, purity}; +use util::ppaux::{ty_to_str, mt_to_str}; +use result::{Result, Ok, Err, map_vec, map_vec2, iter_vec2}; +use ty::{mk_fn, type_is_bot}; +use check::regionmanip::{replace_bound_regions_in_fn_ty}; +use util::common::{indent, indenter}; +use ast::{unsafe_fn, impure_fn, pure_fn, extern_fn}; +use ast::{m_const, m_imm, m_mutbl}; +use dvec::DVec; +use region_inference::{RegionVarBindings}; +use ast_util::dummy_sp; +use cmp::Eq; + +// From submodules: +use resolve::{resolve_nested_tvar, resolve_rvar, resolve_ivar, resolve_all, + force_tvar, force_rvar, force_ivar, force_all, not_regions, + resolve_and_force_all_but_regions, resolver}; +use unify::{vals_and_bindings, root}; +use integral::{int_ty_set, int_ty_set_all}; +use floating::{float_ty_set, float_ty_set_all}; +use combine::{combine_fields, eq_tys}; +use assignment::Assign; +use to_str::ToStr; + +use sub::Sub; +use lub::Lub; +use glb::Glb; + +export infer_ctxt; +export new_infer_ctxt; +export mk_subty, can_mk_subty; +export mk_subr; +export mk_eqty; +export mk_assignty, can_mk_assignty; +export resolve_nested_tvar, resolve_rvar, resolve_ivar, resolve_all; +export force_tvar, force_rvar, force_ivar, force_all; +export resolve_and_force_all_but_regions, not_regions; +export resolve_type, resolve_region; +export resolve_borrowings; +export methods; // for infer_ctxt +export unify_methods; // for infer_ctxt +export cres, fres, fixup_err, fixup_err_to_str; +export assignment; +export root, to_str; +export int_ty_set_all; + #[legacy_exports] mod assignment; #[legacy_exports] @@ -22,3 +325,446 @@ mod sub; mod to_str; #[legacy_exports] mod unify; + +type bound = Option; +type bounds = {lb: bound, ub: bound}; + +type cres = Result; // "combine result" +type ures = cres<()>; // "unify result" +type fres = Result; // "fixup result" +type ares = cres>; // "assignment result" + +enum infer_ctxt = @{ + tcx: ty::ctxt, + + // We instantiate vals_and_bindings with bounds because the + // types that might instantiate a general type variable have an + // order, represented by its upper and lower bounds. + ty_var_bindings: vals_and_bindings>, + + // The types that might instantiate an integral type variable are + // represented by an int_ty_set. + int_var_bindings: vals_and_bindings, + + // The types that might instantiate a floating-point type variable are + // represented by an float_ty_set. + float_var_bindings: vals_and_bindings, + + // For region variables. + region_vars: RegionVarBindings, + + // For keeping track of existing type and region variables. + ty_var_counter: @mut uint, + int_var_counter: @mut uint, + float_var_counter: @mut uint, + region_var_counter: @mut uint +}; + +enum fixup_err { + unresolved_int_ty(IntVid), + unresolved_ty(TyVid), + cyclic_ty(TyVid), + unresolved_region(RegionVid), + region_var_bound_by_region_var(RegionVid, RegionVid) +} + +fn fixup_err_to_str(f: fixup_err) -> ~str { + match f { + unresolved_int_ty(_) => ~"unconstrained integral type", + unresolved_ty(_) => ~"unconstrained type", + cyclic_ty(_) => ~"cyclic type of infinite size", + unresolved_region(_) => ~"unconstrained region", + region_var_bound_by_region_var(r1, r2) => { + fmt!("region var %? bound by another region var %?; this is \ + a bug in rustc", r1, r2) + } + } +} + +fn new_vals_and_bindings() -> vals_and_bindings { + vals_and_bindings { + vals: smallintmap::mk(), + mut bindings: ~[] + } +} + +fn new_infer_ctxt(tcx: ty::ctxt) -> infer_ctxt { + infer_ctxt(@{tcx: tcx, + ty_var_bindings: new_vals_and_bindings(), + int_var_bindings: new_vals_and_bindings(), + float_var_bindings: new_vals_and_bindings(), + region_vars: RegionVarBindings(tcx), + ty_var_counter: @mut 0u, + int_var_counter: @mut 0u, + float_var_counter: @mut 0u, + region_var_counter: @mut 0u})} + +fn mk_subty(cx: infer_ctxt, a_is_expected: bool, span: span, + a: ty::t, b: ty::t) -> ures { + debug!("mk_subty(%s <: %s)", a.to_str(cx), b.to_str(cx)); + do indent { + do cx.commit { + cx.sub(a_is_expected, span).tys(a, b) + } + }.to_ures() +} + +fn can_mk_subty(cx: infer_ctxt, a: ty::t, b: ty::t) -> ures { + debug!("can_mk_subty(%s <: %s)", a.to_str(cx), b.to_str(cx)); + do indent { + do cx.probe { + cx.sub(true, ast_util::dummy_sp()).tys(a, b) + } + }.to_ures() +} + +fn mk_subr(cx: infer_ctxt, a_is_expected: bool, span: span, + a: ty::Region, b: ty::Region) -> ures { + debug!("mk_subr(%s <: %s)", a.to_str(cx), b.to_str(cx)); + do indent { + do cx.commit { + cx.sub(a_is_expected, span).regions(a, b) + } + }.to_ures() +} + +fn mk_eqty(cx: infer_ctxt, a_is_expected: bool, span: span, + a: ty::t, b: ty::t) -> ures { + debug!("mk_eqty(%s <: %s)", a.to_str(cx), b.to_str(cx)); + do indent { + do cx.commit { + let suber = cx.sub(a_is_expected, span); + eq_tys(&suber, a, b) + } + }.to_ures() +} + +fn mk_assignty(cx: infer_ctxt, a_is_expected: bool, span: span, + a: ty::t, b: ty::t) -> ares { + debug!("mk_assignty(%s -> %s)", a.to_str(cx), b.to_str(cx)); + do indent { + do cx.commit { + Assign(cx.combine_fields(a_is_expected, span)).tys(a, b) + } + } +} + +fn can_mk_assignty(cx: infer_ctxt, a: ty::t, b: ty::t) -> ures { + debug!("can_mk_assignty(%s -> %s)", a.to_str(cx), b.to_str(cx)); + do indent { + do cx.probe { + let span = ast_util::dummy_sp(); + Assign(cx.combine_fields(true, span)).tys(a, b) + } + }.to_ures() +} + +// See comment on the type `resolve_state` below +fn resolve_type(cx: infer_ctxt, a: ty::t, modes: uint) + -> fres { + resolver(cx, modes).resolve_type_chk(a) +} + +fn resolve_region(cx: infer_ctxt, r: ty::Region, modes: uint) + -> fres { + resolver(cx, modes).resolve_region_chk(r) +} + +/* +fn resolve_borrowings(cx: infer_ctxt) { + for cx.borrowings.each |item| { + match resolve_region(cx, item.scope, resolve_all|force_all) { + Ok(region) => { + debug!("borrowing for expr %d resolved to region %?, mutbl %?", + item.expr_id, region, item.mutbl); + cx.tcx.borrowings.insert( + item.expr_id, {region: region, mutbl: item.mutbl}); + } + + Err(e) => { + let str = fixup_err_to_str(e); + cx.tcx.sess.span_err( + item.span, + fmt!("could not resolve lifetime for borrow: %s", str)); + } + } + } +} +*/ + +trait then { + fn then(f: fn() -> Result) + -> Result; +} + +impl ures: then { + fn then(f: fn() -> Result) + -> Result { + self.chain(|_i| f()) + } +} + +trait ToUres { + fn to_ures() -> ures; +} + +impl cres: ToUres { + fn to_ures() -> ures { + match self { + Ok(_v) => Ok(()), + Err(e) => Err(e) + } + } +} + +trait CresCompare { + fn compare(t: T, f: fn() -> ty::type_err) -> cres; +} + +impl cres: CresCompare { + fn compare(t: T, f: fn() -> ty::type_err) -> cres { + do self.chain |s| { + if s == t { + self + } else { + Err(f()) + } + } + } +} + +fn uok() -> ures { + Ok(()) +} + +fn rollback_to( + vb: &vals_and_bindings, len: uint) { + + while vb.bindings.len() != len { + let (vid, old_v) = vb.bindings.pop(); + vb.vals.insert(vid.to_uint(), old_v); + } +} + +struct Snapshot { + ty_var_bindings_len: uint, + int_var_bindings_len: uint, + region_vars_snapshot: uint, +} + +impl infer_ctxt { + fn combine_fields(a_is_expected: bool, + span: span) -> combine_fields { + combine_fields {infcx: self, + a_is_expected: a_is_expected, + span: span} + } + + fn sub(a_is_expected: bool, span: span) -> Sub { + Sub(self.combine_fields(a_is_expected, span)) + } + + fn in_snapshot() -> bool { + self.region_vars.in_snapshot() + } + + fn start_snapshot() -> Snapshot { + Snapshot { + ty_var_bindings_len: + self.ty_var_bindings.bindings.len(), + int_var_bindings_len: + self.int_var_bindings.bindings.len(), + region_vars_snapshot: + self.region_vars.start_snapshot(), + } + } + + fn rollback_to(snapshot: &Snapshot) { + debug!("rollback!"); + rollback_to(&self.ty_var_bindings, snapshot.ty_var_bindings_len); + + // FIXME(#3211) -- int_var not transactional + //rollback_to(&self.int_var_bindings, + // snapshot.int_var_bindings_len); + + self.region_vars.rollback_to( + snapshot.region_vars_snapshot); + } + + /// Execute `f` and commit the bindings if successful + fn commit(f: fn() -> Result) -> Result { + assert !self.in_snapshot(); + + debug!("commit()"); + do indent { + let r = self.try(f); + + self.ty_var_bindings.bindings.truncate(0); + self.int_var_bindings.bindings.truncate(0); + self.region_vars.commit(); + move r + } + } + + /// Execute `f`, unroll bindings on failure + fn try(f: fn() -> Result) -> Result { + debug!("try()"); + do indent { + let snapshot = self.start_snapshot(); + let r = f(); + match r { + Ok(_) => (), + Err(_) => self.rollback_to(&snapshot) + } + move r + } + } + + /// Execute `f` then unroll any bindings it creates + fn probe(f: fn() -> Result) -> Result { + debug!("probe()"); + do indent { + let snapshot = self.start_snapshot(); + let r = self.try(f); + self.rollback_to(&snapshot); + move r + } + } +} + +impl infer_ctxt { + fn next_ty_var_id() -> TyVid { + let id = *self.ty_var_counter; + *self.ty_var_counter += 1u; + self.ty_var_bindings.vals.insert(id, + root({lb: None, ub: None}, 0u)); + return TyVid(id); + } + + fn next_ty_var() -> ty::t { + ty::mk_var(self.tcx, self.next_ty_var_id()) + } + + fn next_ty_vars(n: uint) -> ~[ty::t] { + vec::from_fn(n, |_i| self.next_ty_var()) + } + + fn next_int_var_id() -> IntVid { + let id = *self.int_var_counter; + *self.int_var_counter += 1u; + + self.int_var_bindings.vals.insert(id, + root(int_ty_set_all(), 0u)); + return IntVid(id); + } + + fn next_int_var() -> ty::t { + ty::mk_int_var(self.tcx, self.next_int_var_id()) + } + + fn next_float_var_id() -> FloatVid { + let id = *self.float_var_counter; + *self.float_var_counter += 1; + + self.float_var_bindings.vals.insert(id, root(float_ty_set_all(), 0)); + return FloatVid(id); + } + + fn next_float_var() -> ty::t { + ty::mk_float_var(self.tcx, self.next_float_var_id()) + } + + fn next_region_var_nb(span: span) -> ty::Region { + ty::re_infer(ty::ReVar(self.region_vars.new_region_var(span))) + } + + fn next_region_var_with_lb(span: span, + lb_region: ty::Region) -> ty::Region { + let region_var = self.next_region_var_nb(span); + + // add lb_region as a lower bound on the newly built variable + assert self.region_vars.make_subregion(span, + lb_region, + region_var).is_ok(); + + return region_var; + } + + fn next_region_var(span: span, scope_id: ast::node_id) -> ty::Region { + self.next_region_var_with_lb(span, ty::re_scope(scope_id)) + } + + fn resolve_regions() { + self.region_vars.resolve_regions(); + } + + fn ty_to_str(t: ty::t) -> ~str { + ty_to_str(self.tcx, + self.resolve_type_vars_if_possible(t)) + } + + fn resolve_type_vars_if_possible(typ: ty::t) -> ty::t { + match resolve_type(self, typ, resolve_nested_tvar | resolve_ivar) { + result::Ok(new_type) => new_type, + result::Err(_) => typ + } + } + + fn type_error_message(sp: span, mk_msg: fn(~str) -> ~str, + actual_ty: ty::t, err: Option<&ty::type_err>) { + let actual_ty = self.resolve_type_vars_if_possible(actual_ty); + + // Don't report an error if actual type is ty_err. + match ty::get(actual_ty).sty { + ty::ty_err => return, + _ => () + } + let error_str = err.map_default(~"", |t_err| + fmt!(" (%s)", + ty::type_err_to_str(self.tcx, *t_err))); + self.tcx.sess.span_err(sp, + fmt!("%s%s", mk_msg(self.ty_to_str(actual_ty)), + error_str)); + err.iter(|err| + ty::note_and_explain_type_err(self.tcx, *err)); + } + + fn report_mismatched_types(sp: span, e: ty::t, a: ty::t, + err: &ty::type_err) { + // Don't report an error if expected is ty_err + let resolved_expected = + self.resolve_type_vars_if_possible(e); + let mk_msg = match ty::get(resolved_expected).sty { + ty::ty_err => return, + _ => { + // if I leave out : ~str, it infers &str and complains + |actual: ~str| { + fmt!("mismatched types: expected `%s` but found `%s`", + self.ty_to_str(resolved_expected), actual) + } + } + }; + self.type_error_message(sp, mk_msg, a, Some(err)); + } + + fn replace_bound_regions_with_fresh_regions( + &self, span: span, + fty: &ty::FnTy) -> (ty::FnTy, isr_alist) + { + let {fn_ty, isr, _} = + replace_bound_regions_in_fn_ty(self.tcx, @Nil, None, fty, |br| { + // N.B.: The name of the bound region doesn't have anything to + // do with the region variable that's created for it. The + // only thing we're doing with `br` here is using it in the + // debug message. + let rvar = self.next_region_var_nb(span); + debug!("Bound region %s maps to %?", + bound_region_to_str(self.tcx, br), + rvar); + rvar + }); + (fn_ty, isr) + } + +} + diff --git a/src/librustc/middle/typeck/mod.rs b/src/librustc/middle/typeck/mod.rs index 6241cac2f5d..655c09f6846 100644 --- a/src/librustc/middle/typeck/mod.rs +++ b/src/librustc/middle/typeck/mod.rs @@ -79,13 +79,13 @@ export vtable_static, vtable_param, vtable_trait; export provided_methods_map; #[legacy_exports] -#[merge = "check/mod.rs"] +#[path = "check/mod.rs"] pub mod check; #[legacy_exports] mod rscope; #[legacy_exports] mod astconv; -#[merge = "infer/mod.rs"] +#[path = "infer/mod.rs"] mod infer; #[legacy_exports] mod collect;