// Copyright 2012 The Rust Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! # Standalone Tests for the Inference Module use std::path::PathBuf; use std::sync::mpsc; use driver; use rustc_lint; use rustc_resolve::MakeGlobMap; use rustc::middle::region; use rustc::ty::subst::Subst; use rustc::traits::ObligationCause; use rustc::ty::{self, Ty, TyCtxt, TypeFoldable}; use rustc::ty::query::OnDiskCache; use rustc::infer::{self, InferOk, InferResult}; use rustc::infer::outlives::env::OutlivesEnvironment; use rustc::infer::type_variable::TypeVariableOrigin; use rustc_metadata::cstore::CStore; use rustc::hir::map as hir_map; use rustc::session::{self, config}; use rustc::session::config::{OutputFilenames, OutputTypes}; use rustc_data_structures::sync::{self, Lrc}; use syntax; use syntax::ast; use rustc_target::spec::abi::Abi; use syntax::codemap::{CodeMap, FilePathMapping, FileName}; use errors; use errors::emitter::Emitter; use errors::{Level, DiagnosticBuilder}; use syntax::feature_gate::UnstableFeatures; use syntax::symbol::Symbol; use syntax_pos::DUMMY_SP; use rustc::hir; struct Env<'a, 'gcx: 'a + 'tcx, 'tcx: 'a> { infcx: &'a infer::InferCtxt<'a, 'gcx, 'tcx>, region_scope_tree: &'a mut region::ScopeTree, param_env: ty::ParamEnv<'tcx>, } struct RH<'a> { id: hir::ItemLocalId, sub: &'a [RH<'a>], } const EMPTY_SOURCE_STR: &'static str = "#![feature(no_core)] #![no_core]"; struct ExpectErrorEmitter { messages: Vec, } fn remove_message(e: &mut ExpectErrorEmitter, msg: &str, lvl: Level) { match lvl { Level::Bug | Level::Fatal | Level::Error => {} _ => { return; } } debug!("Error: {}", msg); match e.messages.iter().position(|m| msg.contains(m)) { Some(i) => { e.messages.remove(i); } None => { debug!("Unexpected error: {} Expected: {:?}", msg, e.messages); panic!("Unexpected error: {} Expected: {:?}", msg, e.messages); } } } impl Emitter for ExpectErrorEmitter { fn emit(&mut self, db: &DiagnosticBuilder) { remove_message(self, &db.message(), db.level); for child in &db.children { remove_message(self, &child.message(), child.level); } } } fn errors(msgs: &[&str]) -> (Box, usize) { let v = msgs.iter().map(|m| m.to_string()).collect(); (box ExpectErrorEmitter { messages: v } as Box, msgs.len()) } fn test_env(source_string: &str, args: (Box, usize), body: F) where F: FnOnce(Env) { syntax::with_globals(|| { let mut options = config::Options::default(); options.debugging_opts.verbose = true; options.unstable_features = UnstableFeatures::Allow; driver::spawn_thread_pool(options, |options| { test_env_with_pool(options, source_string, args, body) }) }); } fn test_env_with_pool( options: config::Options, source_string: &str, (emitter, expected_err_count): (Box, usize), body: F ) where F: FnOnce(Env) { let diagnostic_handler = errors::Handler::with_emitter(true, false, emitter); let sess = session::build_session_(options, None, diagnostic_handler, Lrc::new(CodeMap::new(FilePathMapping::empty()))); let cstore = CStore::new(::get_codegen_backend(&sess).metadata_loader()); rustc_lint::register_builtins(&mut sess.lint_store.borrow_mut(), Some(&sess)); let input = config::Input::Str { name: FileName::Anon, input: source_string.to_string(), }; let krate = driver::phase_1_parse_input(&driver::CompileController::basic(), &sess, &input).unwrap(); let driver::ExpansionResult { defs, resolutions, mut hir_forest, .. } = { driver::phase_2_configure_and_expand(&sess, &cstore, krate, None, "test", None, MakeGlobMap::No, |_| Ok(())) .expect("phase 2 aborted") }; let arenas = ty::AllArenas::new(); let hir_map = hir_map::map_crate(&sess, &cstore, &mut hir_forest, &defs); // run just enough stuff to build a tcx: let (tx, _rx) = mpsc::channel(); let outputs = OutputFilenames { out_directory: PathBuf::new(), out_filestem: String::new(), single_output_file: None, extra: String::new(), outputs: OutputTypes::new(&[]), }; TyCtxt::create_and_enter(&sess, &cstore, ty::query::Providers::default(), ty::query::Providers::default(), &arenas, resolutions, hir_map, OnDiskCache::new_empty(sess.codemap()), "test_crate", tx, &outputs, |tcx| { tcx.infer_ctxt().enter(|infcx| { let mut region_scope_tree = region::ScopeTree::default(); let param_env = ty::ParamEnv::empty(); body(Env { infcx: &infcx, region_scope_tree: &mut region_scope_tree, param_env: param_env, }); let outlives_env = OutlivesEnvironment::new(param_env); let def_id = tcx.hir.local_def_id(ast::CRATE_NODE_ID); infcx.resolve_regions_and_report_errors(def_id, ®ion_scope_tree, &outlives_env); assert_eq!(tcx.sess.err_count(), expected_err_count); }); }); } const D1: ty::DebruijnIndex = ty::INNERMOST; const D2: ty::DebruijnIndex = D1.shifted_in(1); impl<'a, 'gcx, 'tcx> Env<'a, 'gcx, 'tcx> { pub fn tcx(&self) -> TyCtxt<'a, 'gcx, 'tcx> { self.infcx.tcx } pub fn create_region_hierarchy(&mut self, rh: &RH, parent: (region::Scope, region::ScopeDepth)) { let me = region::Scope::Node(rh.id); self.region_scope_tree.record_scope_parent(me, Some(parent)); for child_rh in rh.sub { self.create_region_hierarchy(child_rh, (me, parent.1 + 1)); } } pub fn create_simple_region_hierarchy(&mut self) { // creates a region hierarchy where 1 is root, 10 and 11 are // children of 1, etc let dscope = region::Scope::Destruction(hir::ItemLocalId(1)); self.region_scope_tree.record_scope_parent(dscope, None); self.create_region_hierarchy(&RH { id: hir::ItemLocalId(1), sub: &[RH { id: hir::ItemLocalId(10), sub: &[], }, RH { id: hir::ItemLocalId(11), sub: &[], }], }, (dscope, 1)); } #[allow(dead_code)] // this seems like it could be useful, even if we don't use it now pub fn lookup_item(&self, names: &[String]) -> ast::NodeId { return match search_mod(self, &self.infcx.tcx.hir.krate().module, 0, names) { Some(id) => id, None => { panic!("no item found: `{}`", names.join("::")); } }; fn search_mod(this: &Env, m: &hir::Mod, idx: usize, names: &[String]) -> Option { assert!(idx < names.len()); for item in &m.item_ids { let item = this.infcx.tcx.hir.expect_item(item.id); if item.name.to_string() == names[idx] { return search(this, item, idx + 1, names); } } return None; } fn search(this: &Env, it: &hir::Item, idx: usize, names: &[String]) -> Option { if idx == names.len() { return Some(it.id); } return match it.node { hir::ItemKind::Use(..) | hir::ItemKind::ExternCrate(..) | hir::ItemKind::Const(..) | hir::ItemKind::Static(..) | hir::ItemKind::Fn(..) | hir::ItemKind::ForeignMod(..) | hir::ItemKind::GlobalAsm(..) | hir::ItemKind::Existential(..) | hir::ItemKind::Ty(..) => None, hir::ItemKind::Enum(..) | hir::ItemKind::Struct(..) | hir::ItemKind::Union(..) | hir::ItemKind::Trait(..) | hir::ItemKind::TraitAlias(..) | hir::ItemKind::Impl(..) => None, hir::ItemKind::Mod(ref m) => search_mod(this, m, idx, names), }; } } pub fn make_subtype(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> bool { match self.infcx.at(&ObligationCause::dummy(), self.param_env).sub(a, b) { Ok(_) => true, Err(ref e) => panic!("Encountered error: {}", e), } } pub fn is_subtype(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> bool { self.infcx.can_sub(self.param_env, a, b).is_ok() } pub fn assert_subtype(&self, a: Ty<'tcx>, b: Ty<'tcx>) { if !self.is_subtype(a, b) { panic!("{} is not a subtype of {}, but it should be", a, b); } } pub fn assert_eq(&self, a: Ty<'tcx>, b: Ty<'tcx>) { self.assert_subtype(a, b); self.assert_subtype(b, a); } pub fn t_fn(&self, input_tys: &[Ty<'tcx>], output_ty: Ty<'tcx>) -> Ty<'tcx> { self.infcx.tcx.mk_fn_ptr(ty::Binder::bind(self.infcx.tcx.mk_fn_sig( input_tys.iter().cloned(), output_ty, false, hir::Unsafety::Normal, Abi::Rust ))) } pub fn t_nil(&self) -> Ty<'tcx> { self.infcx.tcx.mk_nil() } pub fn t_pair(&self, ty1: Ty<'tcx>, ty2: Ty<'tcx>) -> Ty<'tcx> { self.infcx.tcx.intern_tup(&[ty1, ty2]) } pub fn t_param(&self, index: u32) -> Ty<'tcx> { let name = format!("T{}", index); self.infcx.tcx.mk_ty_param(index, Symbol::intern(&name).as_interned_str()) } pub fn re_early_bound(&self, index: u32, name: &'static str) -> ty::Region<'tcx> { let name = Symbol::intern(name).as_interned_str(); self.infcx.tcx.mk_region(ty::ReEarlyBound(ty::EarlyBoundRegion { def_id: self.infcx.tcx.hir.local_def_id(ast::CRATE_NODE_ID), index, name, })) } pub fn re_late_bound_with_debruijn(&self, id: u32, debruijn: ty::DebruijnIndex) -> ty::Region<'tcx> { self.infcx.tcx.mk_region(ty::ReLateBound(debruijn, ty::BrAnon(id))) } pub fn t_rptr(&self, r: ty::Region<'tcx>) -> Ty<'tcx> { self.infcx.tcx.mk_imm_ref(r, self.tcx().types.isize) } pub fn t_rptr_late_bound(&self, id: u32) -> Ty<'tcx> { let r = self.re_late_bound_with_debruijn(id, D1); self.infcx.tcx.mk_imm_ref(r, self.tcx().types.isize) } pub fn t_rptr_late_bound_with_debruijn(&self, id: u32, debruijn: ty::DebruijnIndex) -> Ty<'tcx> { let r = self.re_late_bound_with_debruijn(id, debruijn); self.infcx.tcx.mk_imm_ref(r, self.tcx().types.isize) } pub fn t_rptr_scope(&self, id: u32) -> Ty<'tcx> { let r = ty::ReScope(region::Scope::Node(hir::ItemLocalId(id))); self.infcx.tcx.mk_imm_ref(self.infcx.tcx.mk_region(r), self.tcx().types.isize) } pub fn re_free(&self, id: u32) -> ty::Region<'tcx> { self.infcx.tcx.mk_region(ty::ReFree(ty::FreeRegion { scope: self.infcx.tcx.hir.local_def_id(ast::CRATE_NODE_ID), bound_region: ty::BrAnon(id), })) } pub fn t_rptr_free(&self, id: u32) -> Ty<'tcx> { let r = self.re_free(id); self.infcx.tcx.mk_imm_ref(r, self.tcx().types.isize) } pub fn sub(&self, t1: Ty<'tcx>, t2: Ty<'tcx>) -> InferResult<'tcx, ()> { self.infcx.at(&ObligationCause::dummy(), self.param_env).sub(t1, t2) } /// Checks that `t1 <: t2` is true (this may register additional /// region checks). pub fn check_sub(&self, t1: Ty<'tcx>, t2: Ty<'tcx>) { match self.sub(t1, t2) { Ok(InferOk { obligations, value: () }) => { // None of these tests should require nested obligations: assert!(obligations.is_empty()); } Err(ref e) => { panic!("unexpected error computing sub({:?},{:?}): {}", t1, t2, e); } } } /// Checks that `t1 <: t2` is false (this may register additional /// region checks). pub fn check_not_sub(&self, t1: Ty<'tcx>, t2: Ty<'tcx>) { match self.sub(t1, t2) { Err(_) => {} Ok(_) => { panic!("unexpected success computing sub({:?},{:?})", t1, t2); } } } } #[test] fn contravariant_region_ptr_ok() { test_env(EMPTY_SOURCE_STR, errors(&[]), |mut env| { env.create_simple_region_hierarchy(); let t_rptr1 = env.t_rptr_scope(1); let t_rptr10 = env.t_rptr_scope(10); env.assert_eq(t_rptr1, t_rptr1); env.assert_eq(t_rptr10, t_rptr10); env.make_subtype(t_rptr1, t_rptr10); }) } #[test] fn contravariant_region_ptr_err() { test_env(EMPTY_SOURCE_STR, errors(&["mismatched types"]), |mut env| { env.create_simple_region_hierarchy(); let t_rptr1 = env.t_rptr_scope(1); let t_rptr10 = env.t_rptr_scope(10); env.assert_eq(t_rptr1, t_rptr1); env.assert_eq(t_rptr10, t_rptr10); // will cause an error when regions are resolved env.make_subtype(t_rptr10, t_rptr1); }) } #[test] fn sub_free_bound_false() { //! Test that: //! //! fn(&'a isize) <: for<'b> fn(&'b isize) //! //! does NOT hold. test_env(EMPTY_SOURCE_STR, errors(&[]), |mut env| { env.create_simple_region_hierarchy(); let t_rptr_free1 = env.t_rptr_free(1); let t_rptr_bound1 = env.t_rptr_late_bound(1); env.check_not_sub(env.t_fn(&[t_rptr_free1], env.tcx().types.isize), env.t_fn(&[t_rptr_bound1], env.tcx().types.isize)); }) } #[test] fn sub_bound_free_true() { //! Test that: //! //! for<'a> fn(&'a isize) <: fn(&'b isize) //! //! DOES hold. test_env(EMPTY_SOURCE_STR, errors(&[]), |mut env| { env.create_simple_region_hierarchy(); let t_rptr_bound1 = env.t_rptr_late_bound(1); let t_rptr_free1 = env.t_rptr_free(1); env.check_sub(env.t_fn(&[t_rptr_bound1], env.tcx().types.isize), env.t_fn(&[t_rptr_free1], env.tcx().types.isize)); }) } #[test] fn sub_free_bound_false_infer() { //! Test that: //! //! fn(_#1) <: for<'b> fn(&'b isize) //! //! does NOT hold for any instantiation of `_#1`. test_env(EMPTY_SOURCE_STR, errors(&[]), |env| { let t_infer1 = env.infcx.next_ty_var(TypeVariableOrigin::MiscVariable(DUMMY_SP)); let t_rptr_bound1 = env.t_rptr_late_bound(1); env.check_not_sub(env.t_fn(&[t_infer1], env.tcx().types.isize), env.t_fn(&[t_rptr_bound1], env.tcx().types.isize)); }) } /// Test substituting a bound region into a function, which introduces another level of binding. /// This requires adjusting the Debruijn index. #[test] fn subst_ty_renumber_bound() { test_env(EMPTY_SOURCE_STR, errors(&[]), |env| { // Situation: // Theta = [A -> &'a foo] let t_rptr_bound1 = env.t_rptr_late_bound(1); // t_source = fn(A) let t_source = { let t_param = env.t_param(0); env.t_fn(&[t_param], env.t_nil()) }; let substs = env.infcx.tcx.intern_substs(&[t_rptr_bound1.into()]); let t_substituted = t_source.subst(env.infcx.tcx, substs); // t_expected = fn(&'a isize) let t_expected = { let t_ptr_bound2 = env.t_rptr_late_bound_with_debruijn(1, D2); env.t_fn(&[t_ptr_bound2], env.t_nil()) }; debug!("subst_bound: t_source={:?} substs={:?} t_substituted={:?} t_expected={:?}", t_source, substs, t_substituted, t_expected); assert_eq!(t_substituted, t_expected); }) } /// Test substituting a bound region into a function, which introduces another level of binding. /// This requires adjusting the Debruijn index. #[test] fn subst_ty_renumber_some_bounds() { test_env(EMPTY_SOURCE_STR, errors(&[]), |env| { // Situation: // Theta = [A -> &'a foo] let t_rptr_bound1 = env.t_rptr_late_bound(1); // t_source = (A, fn(A)) let t_source = { let t_param = env.t_param(0); env.t_pair(t_param, env.t_fn(&[t_param], env.t_nil())) }; let substs = env.infcx.tcx.intern_substs(&[t_rptr_bound1.into()]); let t_substituted = t_source.subst(env.infcx.tcx, substs); // t_expected = (&'a isize, fn(&'a isize)) // // but not that the Debruijn index is different in the different cases. let t_expected = { let t_rptr_bound2 = env.t_rptr_late_bound_with_debruijn(1, D2); env.t_pair(t_rptr_bound1, env.t_fn(&[t_rptr_bound2], env.t_nil())) }; debug!("subst_bound: t_source={:?} substs={:?} t_substituted={:?} t_expected={:?}", t_source, substs, t_substituted, t_expected); assert_eq!(t_substituted, t_expected); }) } /// Test that we correctly compute whether a type has escaping regions or not. #[test] fn escaping() { test_env(EMPTY_SOURCE_STR, errors(&[]), |mut env| { // Situation: // Theta = [A -> &'a foo] env.create_simple_region_hierarchy(); assert!(!env.t_nil().has_escaping_regions()); let t_rptr_free1 = env.t_rptr_free(1); assert!(!t_rptr_free1.has_escaping_regions()); let t_rptr_bound1 = env.t_rptr_late_bound_with_debruijn(1, D1); assert!(t_rptr_bound1.has_escaping_regions()); let t_rptr_bound2 = env.t_rptr_late_bound_with_debruijn(1, D2); assert!(t_rptr_bound2.has_escaping_regions()); // t_fn = fn(A) let t_param = env.t_param(0); assert!(!t_param.has_escaping_regions()); let t_fn = env.t_fn(&[t_param], env.t_nil()); assert!(!t_fn.has_escaping_regions()); }) } /// Test applying a substitution where the value being substituted for an early-bound region is a /// late-bound region. #[test] fn subst_region_renumber_region() { test_env(EMPTY_SOURCE_STR, errors(&[]), |env| { let re_bound1 = env.re_late_bound_with_debruijn(1, D1); // type t_source<'a> = fn(&'a isize) let t_source = { let re_early = env.re_early_bound(0, "'a"); env.t_fn(&[env.t_rptr(re_early)], env.t_nil()) }; let substs = env.infcx.tcx.intern_substs(&[re_bound1.into()]); let t_substituted = t_source.subst(env.infcx.tcx, substs); // t_expected = fn(&'a isize) // // but not that the Debruijn index is different in the different cases. let t_expected = { let t_rptr_bound2 = env.t_rptr_late_bound_with_debruijn(1, D2); env.t_fn(&[t_rptr_bound2], env.t_nil()) }; debug!("subst_bound: t_source={:?} substs={:?} t_substituted={:?} t_expected={:?}", t_source, substs, t_substituted, t_expected); assert_eq!(t_substituted, t_expected); }) } #[test] fn walk_ty() { test_env(EMPTY_SOURCE_STR, errors(&[]), |env| { let tcx = env.infcx.tcx; let int_ty = tcx.types.isize; let usize_ty = tcx.types.usize; let tup1_ty = tcx.intern_tup(&[int_ty, usize_ty, int_ty, usize_ty]); let tup2_ty = tcx.intern_tup(&[tup1_ty, tup1_ty, usize_ty]); let walked: Vec<_> = tup2_ty.walk().collect(); assert_eq!(walked, [tup2_ty, tup1_ty, int_ty, usize_ty, int_ty, usize_ty, tup1_ty, int_ty, usize_ty, int_ty, usize_ty, usize_ty]); }) } #[test] fn walk_ty_skip_subtree() { test_env(EMPTY_SOURCE_STR, errors(&[]), |env| { let tcx = env.infcx.tcx; let int_ty = tcx.types.isize; let usize_ty = tcx.types.usize; let tup1_ty = tcx.intern_tup(&[int_ty, usize_ty, int_ty, usize_ty]); let tup2_ty = tcx.intern_tup(&[tup1_ty, tup1_ty, usize_ty]); // types we expect to see (in order), plus a boolean saying // whether to skip the subtree. let mut expected = vec![(tup2_ty, false), (tup1_ty, false), (int_ty, false), (usize_ty, false), (int_ty, false), (usize_ty, false), (tup1_ty, true), // skip the isize/usize/isize/usize (usize_ty, false)]; expected.reverse(); let mut walker = tup2_ty.walk(); while let Some(t) = walker.next() { debug!("walked to {:?}", t); let (expected_ty, skip) = expected.pop().unwrap(); assert_eq!(t, expected_ty); if skip { walker.skip_current_subtree(); } } assert!(expected.is_empty()); }) }