diff options
| author | bors <bors@rust-lang.org> | 2014-09-17 05:56:15 +0000 |
|---|---|---|
| committer | bors <bors@rust-lang.org> | 2014-09-17 05:56:15 +0000 |
| commit | 88cb454b91b16fdf8395bc4859b65aff8303acb5 (patch) | |
| tree | 35f84503624d7c0973be64e046552c1c2a03ae2d /src/libsyntax | |
| parent | ff613abaa2bbd7b443b17059b4927d8b4b1e82ff (diff) | |
| parent | 3a01d0f1e3ab88a8b891a5cfeb552a78cbf69670 (diff) | |
| download | rust-88cb454b91b16fdf8395bc4859b65aff8303acb5.tar.gz rust-88cb454b91b16fdf8395bc4859b65aff8303acb5.zip | |
auto merge of #17160 : nick29581/rust/front, r=pcwalton
r?
Diffstat (limited to 'src/libsyntax')
| -rw-r--r-- | src/libsyntax/config.rs | 253 | ||||
| -rw-r--r-- | src/libsyntax/feature_gate.rs | 461 | ||||
| -rw-r--r-- | src/libsyntax/lib.rs | 5 | ||||
| -rw-r--r-- | src/libsyntax/parse/mod.rs | 21 | ||||
| -rw-r--r-- | src/libsyntax/show_span.rs | 39 | ||||
| -rw-r--r-- | src/libsyntax/std_inject.rs | 235 | ||||
| -rw-r--r-- | src/libsyntax/test.rs | 573 |
7 files changed, 1586 insertions, 1 deletions
diff --git a/src/libsyntax/config.rs b/src/libsyntax/config.rs new file mode 100644 index 00000000000..cb96cd911b5 --- /dev/null +++ b/src/libsyntax/config.rs @@ -0,0 +1,253 @@ +// Copyright 2012-2014 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 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use fold::Folder; +use {ast, fold, attr}; +use codemap::Spanned; +use ptr::P; + +/// A folder that strips out items that do not belong in the current +/// configuration. +struct Context<'a> { + in_cfg: |attrs: &[ast::Attribute]|: 'a -> bool, +} + +// Support conditional compilation by transforming the AST, stripping out +// any items that do not belong in the current configuration +pub fn strip_unconfigured_items(krate: ast::Crate) -> ast::Crate { + let config = krate.config.clone(); + strip_items(krate, |attrs| in_cfg(config.as_slice(), attrs)) +} + +impl<'a> fold::Folder for Context<'a> { + fn fold_mod(&mut self, module: ast::Mod) -> ast::Mod { + fold_mod(self, module) + } + fn fold_block(&mut self, block: P<ast::Block>) -> P<ast::Block> { + fold_block(self, block) + } + fn fold_foreign_mod(&mut self, foreign_mod: ast::ForeignMod) -> ast::ForeignMod { + fold_foreign_mod(self, foreign_mod) + } + fn fold_item_underscore(&mut self, item: ast::Item_) -> ast::Item_ { + fold_item_underscore(self, item) + } + fn fold_expr(&mut self, expr: P<ast::Expr>) -> P<ast::Expr> { + fold_expr(self, expr) + } + fn fold_mac(&mut self, mac: ast::Mac) -> ast::Mac { + fold::noop_fold_mac(mac, self) + } +} + +pub fn strip_items(krate: ast::Crate, + in_cfg: |attrs: &[ast::Attribute]| -> bool) + -> ast::Crate { + let mut ctxt = Context { + in_cfg: in_cfg, + }; + ctxt.fold_crate(krate) +} + +fn filter_view_item(cx: &mut Context, view_item: ast::ViewItem) -> Option<ast::ViewItem> { + if view_item_in_cfg(cx, &view_item) { + Some(view_item) + } else { + None + } +} + +fn fold_mod(cx: &mut Context, ast::Mod {inner, view_items, items}: ast::Mod) -> ast::Mod { + ast::Mod { + inner: inner, + view_items: view_items.into_iter().filter_map(|a| { + filter_view_item(cx, a).map(|x| cx.fold_view_item(x)) + }).collect(), + items: items.into_iter().filter_map(|a| { + if item_in_cfg(cx, &*a) { + Some(cx.fold_item(a)) + } else { + None + } + }).flat_map(|x| x.into_iter()).collect() + } +} + +fn filter_foreign_item(cx: &mut Context, item: P<ast::ForeignItem>) + -> Option<P<ast::ForeignItem>> { + if foreign_item_in_cfg(cx, &*item) { + Some(item) + } else { + None + } +} + +fn fold_foreign_mod(cx: &mut Context, ast::ForeignMod {abi, view_items, items}: ast::ForeignMod) + -> ast::ForeignMod { + ast::ForeignMod { + abi: abi, + view_items: view_items.into_iter().filter_map(|a| { + filter_view_item(cx, a).map(|x| cx.fold_view_item(x)) + }).collect(), + items: items.into_iter() + .filter_map(|a| filter_foreign_item(cx, a)) + .collect() + } +} + +fn fold_item_underscore(cx: &mut Context, item: ast::Item_) -> ast::Item_ { + let item = match item { + ast::ItemImpl(a, b, c, impl_items) => { + let impl_items = impl_items.into_iter() + .filter(|ii| impl_item_in_cfg(cx, ii)) + .collect(); + ast::ItemImpl(a, b, c, impl_items) + } + ast::ItemTrait(a, b, c, methods) => { + let methods = methods.into_iter() + .filter(|m| trait_method_in_cfg(cx, m)) + .collect(); + ast::ItemTrait(a, b, c, methods) + } + ast::ItemStruct(def, generics) => { + ast::ItemStruct(fold_struct(cx, def), generics) + } + ast::ItemEnum(def, generics) => { + let mut variants = def.variants.into_iter().filter_map(|v| { + if !(cx.in_cfg)(v.node.attrs.as_slice()) { + None + } else { + Some(v.map(|Spanned {node: ast::Variant_ {id, name, attrs, kind, + disr_expr, vis}, span}| { + Spanned { + node: ast::Variant_ { + id: id, + name: name, + attrs: attrs, + kind: match kind { + ast::TupleVariantKind(..) => kind, + ast::StructVariantKind(def) => { + ast::StructVariantKind(fold_struct(cx, def)) + } + }, + disr_expr: disr_expr, + vis: vis + }, + span: span + } + })) + } + }); + ast::ItemEnum(ast::EnumDef { + variants: variants.collect(), + }, generics) + } + item => item, + }; + + fold::noop_fold_item_underscore(item, cx) +} + +fn fold_struct(cx: &mut Context, def: P<ast::StructDef>) -> P<ast::StructDef> { + def.map(|ast::StructDef {fields, ctor_id, super_struct, is_virtual}| { + ast::StructDef { + fields: fields.into_iter().filter(|m| { + (cx.in_cfg)(m.node.attrs.as_slice()) + }).collect(), + ctor_id: ctor_id, + super_struct: super_struct, + is_virtual: is_virtual, + } + }) +} + +fn retain_stmt(cx: &mut Context, stmt: &ast::Stmt) -> bool { + match stmt.node { + ast::StmtDecl(ref decl, _) => { + match decl.node { + ast::DeclItem(ref item) => { + item_in_cfg(cx, &**item) + } + _ => true + } + } + _ => true + } +} + +fn fold_block(cx: &mut Context, b: P<ast::Block>) -> P<ast::Block> { + b.map(|ast::Block {id, view_items, stmts, expr, rules, span}| { + let resulting_stmts: Vec<P<ast::Stmt>> = + stmts.into_iter().filter(|a| retain_stmt(cx, &**a)).collect(); + let resulting_stmts = resulting_stmts.into_iter() + .flat_map(|stmt| cx.fold_stmt(stmt).into_iter()) + .collect(); + let filtered_view_items = view_items.into_iter().filter_map(|a| { + filter_view_item(cx, a).map(|x| cx.fold_view_item(x)) + }).collect(); + ast::Block { + id: id, + view_items: filtered_view_items, + stmts: resulting_stmts, + expr: expr.map(|x| cx.fold_expr(x)), + rules: rules, + span: span, + } + }) +} + +fn fold_expr(cx: &mut Context, expr: P<ast::Expr>) -> P<ast::Expr> { + expr.map(|ast::Expr {id, span, node}| { + fold::noop_fold_expr(ast::Expr { + id: id, + node: match node { + ast::ExprMatch(m, arms) => { + ast::ExprMatch(m, arms.into_iter() + .filter(|a| (cx.in_cfg)(a.attrs.as_slice())) + .collect()) + } + _ => node + }, + span: span + }, cx) + }) +} + +fn item_in_cfg(cx: &mut Context, item: &ast::Item) -> bool { + return (cx.in_cfg)(item.attrs.as_slice()); +} + +fn foreign_item_in_cfg(cx: &mut Context, item: &ast::ForeignItem) -> bool { + return (cx.in_cfg)(item.attrs.as_slice()); +} + +fn view_item_in_cfg(cx: &mut Context, item: &ast::ViewItem) -> bool { + return (cx.in_cfg)(item.attrs.as_slice()); +} + +fn trait_method_in_cfg(cx: &mut Context, meth: &ast::TraitItem) -> bool { + match *meth { + ast::RequiredMethod(ref meth) => (cx.in_cfg)(meth.attrs.as_slice()), + ast::ProvidedMethod(ref meth) => (cx.in_cfg)(meth.attrs.as_slice()) + } +} + +fn impl_item_in_cfg(cx: &mut Context, impl_item: &ast::ImplItem) -> bool { + match *impl_item { + ast::MethodImplItem(ref meth) => (cx.in_cfg)(meth.attrs.as_slice()), + } +} + +// Determine if an item should be translated in the current crate +// configuration based on the item's attributes +fn in_cfg(cfg: &[P<ast::MetaItem>], attrs: &[ast::Attribute]) -> bool { + attr::test_cfg(cfg, attrs.iter()) +} + diff --git a/src/libsyntax/feature_gate.rs b/src/libsyntax/feature_gate.rs new file mode 100644 index 00000000000..e22e55193fc --- /dev/null +++ b/src/libsyntax/feature_gate.rs @@ -0,0 +1,461 @@ +// Copyright 2013 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 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Feature gating +//! +//! This modules implements the gating necessary for preventing certain compiler +//! features from being used by default. This module will crawl a pre-expanded +//! AST to ensure that there are no features which are used that are not +//! enabled. +//! +//! Features are enabled in programs via the crate-level attributes of +//! `#![feature(...)]` with a comma-separated list of features. + +use abi::RustIntrinsic; +use ast::NodeId; +use ast; +use attr; +use attr::AttrMetaMethods; +use codemap::Span; +use diagnostic::SpanHandler; +use visit; +use visit::Visitor; +use parse::token; + +use std::slice; + +/// This is a list of all known features since the beginning of time. This list +/// can never shrink, it may only be expanded (in order to prevent old programs +/// from failing to compile). The status of each feature may change, however. +static KNOWN_FEATURES: &'static [(&'static str, Status)] = &[ + ("globs", Active), + ("macro_rules", Active), + ("struct_variant", Active), + ("once_fns", Active), + ("asm", Active), + ("managed_boxes", Active), + ("non_ascii_idents", Active), + ("thread_local", Active), + ("link_args", Active), + ("phase", Active), + ("plugin_registrar", Active), + ("log_syntax", Active), + ("trace_macros", Active), + ("concat_idents", Active), + ("unsafe_destructor", Active), + ("intrinsics", Active), + ("lang_items", Active), + + ("simd", Active), + ("default_type_params", Active), + ("quote", Active), + ("linkage", Active), + ("struct_inherit", Active), + ("overloaded_calls", Active), + ("unboxed_closure_sugar", Active), + + ("quad_precision_float", Removed), + + ("rustc_diagnostic_macros", Active), + ("unboxed_closures", Active), + ("import_shadowing", Active), + ("advanced_slice_patterns", Active), + ("tuple_indexing", Active), + + // if you change this list without updating src/doc/rust.md, cmr will be sad + + // A temporary feature gate used to enable parser extensions needed + // to bootstrap fix for #5723. + ("issue_5723_bootstrap", Accepted), + + // These are used to test this portion of the compiler, they don't actually + // mean anything + ("test_accepted_feature", Accepted), + ("test_removed_feature", Removed), +]; + +enum Status { + /// Represents an active feature that is currently being implemented or + /// currently being considered for addition/removal. + Active, + + /// Represents a feature which has since been removed (it was once Active) + Removed, + + /// This language feature has since been Accepted (it was once Active) + Accepted, +} + +/// A set of features to be used by later passes. +pub struct Features { + pub default_type_params: bool, + pub overloaded_calls: bool, + pub rustc_diagnostic_macros: bool, + pub import_shadowing: bool, +} + +impl Features { + pub fn new() -> Features { + Features { + default_type_params: false, + overloaded_calls: false, + rustc_diagnostic_macros: false, + import_shadowing: false, + } + } +} + +struct Context<'a> { + features: Vec<&'static str>, + span_handler: &'a SpanHandler, +} + +impl<'a> Context<'a> { + fn gate_feature(&self, feature: &str, span: Span, explain: &str) { + if !self.has_feature(feature) { + self.span_handler.span_err(span, explain); + self.span_handler.span_note(span, format!("add #![feature({})] to the \ + crate attributes to enable", + feature).as_slice()); + } + } + + fn gate_box(&self, span: Span) { + self.gate_feature("managed_boxes", span, + "The managed box syntax is being replaced by the \ + `std::gc::Gc` and `std::rc::Rc` types. Equivalent \ + functionality to managed trait objects will be \ + implemented but is currently missing."); + } + + fn has_feature(&self, feature: &str) -> bool { + self.features.iter().any(|n| n.as_slice() == feature) + } +} + +impl<'a, 'v> Visitor<'v> for Context<'a> { + fn visit_ident(&mut self, sp: Span, id: ast::Ident) { + if !token::get_ident(id).get().is_ascii() { + self.gate_feature("non_ascii_idents", sp, + "non-ascii idents are not fully supported."); + } + } + + fn visit_view_item(&mut self, i: &ast::ViewItem) { + match i.node { + ast::ViewItemUse(ref path) => { + match path.node { + ast::ViewPathGlob(..) => { + self.gate_feature("globs", path.span, + "glob import statements are \ + experimental and possibly buggy"); + } + _ => {} + } + } + ast::ViewItemExternCrate(..) => { + for attr in i.attrs.iter() { + if attr.name().get() == "phase"{ + self.gate_feature("phase", attr.span, + "compile time crate loading is \ + experimental and possibly buggy"); + } + } + } + } + visit::walk_view_item(self, i) + } + + fn visit_item(&mut self, i: &ast::Item) { + for attr in i.attrs.iter() { + if attr.name().equiv(&("thread_local")) { + self.gate_feature("thread_local", i.span, + "`#[thread_local]` is an experimental feature, and does not \ + currently handle destructors. There is no corresponding \ + `#[task_local]` mapping to the task model"); + } + } + match i.node { + ast::ItemEnum(ref def, _) => { + for variant in def.variants.iter() { + match variant.node.kind { + ast::StructVariantKind(..) => { + self.gate_feature("struct_variant", variant.span, + "enum struct variants are \ + experimental and possibly buggy"); + } + _ => {} + } + } + } + + ast::ItemForeignMod(ref foreign_module) => { + if attr::contains_name(i.attrs.as_slice(), "link_args") { + self.gate_feature("link_args", i.span, + "the `link_args` attribute is not portable \ + across platforms, it is recommended to \ + use `#[link(name = \"foo\")]` instead") + } + if foreign_module.abi == RustIntrinsic { + self.gate_feature("intrinsics", + i.span, + "intrinsics are subject to change") + } + } + + ast::ItemFn(..) => { + if attr::contains_name(i.attrs.as_slice(), "plugin_registrar") { + self.gate_feature("plugin_registrar", i.span, + "compiler plugins are experimental and possibly buggy"); + } + } + + ast::ItemStruct(ref struct_definition, _) => { + if attr::contains_name(i.attrs.as_slice(), "simd") { + self.gate_feature("simd", i.span, + "SIMD types are experimental and possibly buggy"); + } + match struct_definition.super_struct { + Some(ref path) => self.gate_feature("struct_inherit", path.span, + "struct inheritance is experimental \ + and possibly buggy"), + None => {} + } + if struct_definition.is_virtual { + self.gate_feature("struct_inherit", i.span, + "struct inheritance (`virtual` keyword) is \ + experimental and possibly buggy"); + } + } + + ast::ItemImpl(..) => { + if attr::contains_name(i.attrs.as_slice(), + "unsafe_destructor") { + self.gate_feature("unsafe_destructor", + i.span, + "`#[unsafe_destructor]` allows too \ + many unsafe patterns and may be \ + removed in the future"); + } + } + + _ => {} + } + + visit::walk_item(self, i); + } + + fn visit_mac(&mut self, macro: &ast::Mac) { + let ast::MacInvocTT(ref path, _, _) = macro.node; + let id = path.segments.last().unwrap().identifier; + let quotes = ["quote_tokens", "quote_expr", "quote_ty", + "quote_item", "quote_pat", "quote_stmt"]; + let msg = " is not stable enough for use and are subject to change"; + + + if id == token::str_to_ident("macro_rules") { + self.gate_feature("macro_rules", path.span, "macro definitions are \ + not stable enough for use and are subject to change"); + } + + else if id == token::str_to_ident("asm") { + self.gate_feature("asm", path.span, "inline assembly is not \ + stable enough for use and is subject to change"); + } + + else if id == token::str_to_ident("log_syntax") { + self.gate_feature("log_syntax", path.span, "`log_syntax!` is not \ + stable enough for use and is subject to change"); + } + + else if id == token::str_to_ident("trace_macros") { + self.gate_feature("trace_macros", path.span, "`trace_macros` is not \ + stable enough for use and is subject to change"); + } + + else if id == token::str_to_ident("concat_idents") { + self.gate_feature("concat_idents", path.span, "`concat_idents` is not \ + stable enough for use and is subject to change"); + } + + else { + for "e in quotes.iter() { + if id == token::str_to_ident(quote) { + self.gate_feature("quote", + path.span, + format!("{}{}", quote, msg).as_slice()); + } + } + } + } + + fn visit_foreign_item(&mut self, i: &ast::ForeignItem) { + if attr::contains_name(i.attrs.as_slice(), "linkage") { + self.gate_feature("linkage", i.span, + "the `linkage` attribute is experimental \ + and not portable across platforms") + } + visit::walk_foreign_item(self, i) + } + + fn visit_ty(&mut self, t: &ast::Ty) { + match t.node { + ast::TyClosure(ref closure) if closure.onceness == ast::Once => { + self.gate_feature("once_fns", t.span, + "once functions are \ + experimental and likely to be removed"); + + }, + ast::TyBox(_) => { self.gate_box(t.span); } + ast::TyUnboxedFn(..) => { + self.gate_feature("unboxed_closure_sugar", + t.span, + "unboxed closure trait sugar is experimental"); + } + _ => {} + } + + visit::walk_ty(self, t); + } + + fn visit_expr(&mut self, e: &ast::Expr) { + match e.node { + ast::ExprUnary(ast::UnBox, _) => { + self.gate_box(e.span); + } + ast::ExprUnboxedFn(..) => { + self.gate_feature("unboxed_closures", + e.span, + "unboxed closures are a work-in-progress \ + feature with known bugs"); + } + ast::ExprTupField(..) => { + self.gate_feature("tuple_indexing", + e.span, + "tuple indexing is experimental"); + } + _ => {} + } + visit::walk_expr(self, e); + } + + fn visit_generics(&mut self, generics: &ast::Generics) { + for type_parameter in generics.ty_params.iter() { + match type_parameter.default { + Some(ref ty) => { + self.gate_feature("default_type_params", ty.span, + "default type parameters are \ + experimental and possibly buggy"); + } + None => {} + } + } + visit::walk_generics(self, generics); + } + + fn visit_attribute(&mut self, attr: &ast::Attribute) { + if attr::contains_name(slice::ref_slice(attr), "lang") { + self.gate_feature("lang_items", + attr.span, + "language items are subject to change"); + } + } + + fn visit_pat(&mut self, pattern: &ast::Pat) { + match pattern.node { + ast::PatVec(_, Some(_), ref last) if !last.is_empty() => { + self.gate_feature("advanced_slice_patterns", + pattern.span, + "multiple-element slice matches anywhere \ + but at the end of a slice (e.g. \ + `[0, ..xs, 0]` are experimental") + } + _ => {} + } + visit::walk_pat(self, pattern) + } + + fn visit_fn(&mut self, + fn_kind: visit::FnKind<'v>, + fn_decl: &'v ast::FnDecl, + block: &'v ast::Block, + span: Span, + _: NodeId) { + match fn_kind { + visit::FkItemFn(_, _, _, abi) if abi == RustIntrinsic => { + self.gate_feature("intrinsics", + span, + "intrinsics are subject to change") + } + _ => {} + } + visit::walk_fn(self, fn_kind, fn_decl, block, span); + } +} + +pub fn check_crate(span_handler: &SpanHandler, krate: &ast::Crate) -> (Features, Vec<Span>) { + let mut cx = Context { + features: Vec::new(), + span_handler: span_handler, + }; + + let mut unknown_features = Vec::new(); + + for attr in krate.attrs.iter() { + if !attr.check_name("feature") { + continue + } + + match attr.meta_item_list() { + None => { + span_handler.span_err(attr.span, "malformed feature attribute, \ + expected #![feature(...)]"); + } + Some(list) => { + for mi in list.iter() { + let name = match mi.node { + ast::MetaWord(ref word) => (*word).clone(), + _ => { + span_handler.span_err(mi.span, + "malformed feature, expected just \ + one word"); + continue + } + }; + match KNOWN_FEATURES.iter() + .find(|& &(n, _)| name.equiv(&n)) { + Some(&(name, Active)) => { cx.features.push(name); } + Some(&(_, Removed)) => { + span_handler.span_err(mi.span, "feature has been removed"); + } + Some(&(_, Accepted)) => { + span_handler.span_warn(mi.span, "feature has been added to Rust, \ + directive not necessary"); + } + None => { + unknown_features.push(mi.span); + } + } + } + } + } + } + + visit::walk_crate(&mut cx, krate); + + (Features { + default_type_params: cx.has_feature("default_type_params"), + overloaded_calls: cx.has_feature("overloaded_calls"), + rustc_diagnostic_macros: cx.has_feature("rustc_diagnostic_macros"), + import_shadowing: cx.has_feature("import_shadowing"), + }, + unknown_features) +} + diff --git a/src/libsyntax/lib.rs b/src/libsyntax/lib.rs index 146b5a5b348..457d77efb70 100644 --- a/src/libsyntax/lib.rs +++ b/src/libsyntax/lib.rs @@ -59,12 +59,17 @@ pub mod ast_map; pub mod ast_util; pub mod attr; pub mod codemap; +pub mod config; pub mod crateid; pub mod diagnostic; +pub mod feature_gate; pub mod fold; pub mod owned_slice; pub mod parse; pub mod ptr; +pub mod show_span; +pub mod std_inject; +pub mod test; pub mod visit; pub mod print { diff --git a/src/libsyntax/parse/mod.rs b/src/libsyntax/parse/mod.rs index e5b6359000b..d73cb211694 100644 --- a/src/libsyntax/parse/mod.rs +++ b/src/libsyntax/parse/mod.rs @@ -17,7 +17,7 @@ use parse::attr::ParserAttr; use parse::parser::Parser; use ptr::P; -use std::cell::RefCell; +use std::cell::{Cell, RefCell}; use std::io::File; use std::rc::Rc; use std::str; @@ -37,12 +37,14 @@ pub struct ParseSess { pub span_diagnostic: SpanHandler, // better be the same as the one in the reader! /// Used to determine and report recursive mod inclusions included_mod_stack: RefCell<Vec<Path>>, + pub node_id: Cell<ast::NodeId>, } pub fn new_parse_sess() -> ParseSess { ParseSess { span_diagnostic: mk_span_handler(default_handler(Auto, None), CodeMap::new()), included_mod_stack: RefCell::new(Vec::new()), + node_id: Cell::new(1), } } @@ -50,6 +52,23 @@ pub fn new_parse_sess_special_handler(sh: SpanHandler) -> ParseSess { ParseSess { span_diagnostic: sh, included_mod_stack: RefCell::new(Vec::new()), + node_id: Cell::new(1), + } +} + +impl ParseSess { + pub fn next_node_id(&self) -> ast::NodeId { + self.reserve_node_ids(1) + } + pub fn reserve_node_ids(&self, count: ast::NodeId) -> ast::NodeId { + let v = self.node_id.get(); + + match v.checked_add(&count) { + Some(next) => { self.node_id.set(next); } + None => fail!("Input too large, ran out of node ids!") + } + + v } } diff --git a/src/libsyntax/show_span.rs b/src/libsyntax/show_span.rs new file mode 100644 index 00000000000..354ba854b10 --- /dev/null +++ b/src/libsyntax/show_span.rs @@ -0,0 +1,39 @@ +// Copyright 2014 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 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Span debugger +//! +//! This module shows spans for all expressions in the crate +//! to help with compiler debugging. + +use ast; +use diagnostic; +use visit; +use visit::Visitor; + +struct ShowSpanVisitor<'a> { + span_diagnostic: &'a diagnostic::SpanHandler, +} + +impl<'a, 'v> Visitor<'v> for ShowSpanVisitor<'a> { + fn visit_expr(&mut self, e: &ast::Expr) { + self.span_diagnostic.span_note(e.span, "expression"); + visit::walk_expr(self, e); + } + + fn visit_mac(&mut self, macro: &ast::Mac) { + visit::walk_mac(self, macro); + } +} + +pub fn run(span_diagnostic: &diagnostic::SpanHandler, krate: &ast::Crate) { + let mut v = ShowSpanVisitor { span_diagnostic: span_diagnostic }; + visit::walk_crate(&mut v, krate); +} diff --git a/src/libsyntax/std_inject.rs b/src/libsyntax/std_inject.rs new file mode 100644 index 00000000000..8a7e14643c1 --- /dev/null +++ b/src/libsyntax/std_inject.rs @@ -0,0 +1,235 @@ +// 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 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use ast; +use attr; +use codemap::DUMMY_SP; +use codemap; +use fold::Folder; +use fold; +use owned_slice::OwnedSlice; +use parse::token::InternedString; +use parse::token::special_idents; +use parse::token; +use ptr::P; +use util::small_vector::SmallVector; + +use std::mem; + +pub fn maybe_inject_crates_ref(krate: ast::Crate, alt_std_name: Option<String>, any_exe: bool) + -> ast::Crate { + if use_std(&krate) { + inject_crates_ref(krate, alt_std_name, any_exe) + } else { + krate + } +} + +pub fn maybe_inject_prelude(krate: ast::Crate) -> ast::Crate { + if use_std(&krate) { + inject_prelude(krate) + } else { + krate + } +} + +fn use_std(krate: &ast::Crate) -> bool { + !attr::contains_name(krate.attrs.as_slice(), "no_std") +} + +fn use_start(krate: &ast::Crate) -> bool { + !attr::contains_name(krate.attrs.as_slice(), "no_start") +} + +fn no_prelude(attrs: &[ast::Attribute]) -> bool { + attr::contains_name(attrs, "no_implicit_prelude") +} + +struct StandardLibraryInjector<'a> { + alt_std_name: Option<String>, + any_exe: bool, +} + +impl<'a> fold::Folder for StandardLibraryInjector<'a> { + fn fold_crate(&mut self, mut krate: ast::Crate) -> ast::Crate { + + // The name to use in `extern crate "name" as std;` + let actual_crate_name = match self.alt_std_name { + Some(ref s) => token::intern_and_get_ident(s.as_slice()), + None => token::intern_and_get_ident("std"), + }; + + let mut vis = vec!(ast::ViewItem { + node: ast::ViewItemExternCrate(token::str_to_ident("std"), + Some((actual_crate_name, ast::CookedStr)), + ast::DUMMY_NODE_ID), + attrs: vec!( + attr::mk_attr_outer(attr::mk_attr_id(), attr::mk_list_item( + InternedString::new("phase"), + vec!( + attr::mk_word_item(InternedString::new("plugin")), + attr::mk_word_item(InternedString::new("link") + ))))), + vis: ast::Inherited, + span: DUMMY_SP + }); + + if use_start(&krate) && self.any_exe { + let visible_rt_name = "rt"; + let actual_rt_name = "native"; + // Gensym the ident so it can't be named + let visible_rt_name = token::gensym_ident(visible_rt_name); + let actual_rt_name = token::intern_and_get_ident(actual_rt_name); + + vis.push(ast::ViewItem { + node: ast::ViewItemExternCrate(visible_rt_name, + Some((actual_rt_name, ast::CookedStr)), + ast::DUMMY_NODE_ID), + attrs: Vec::new(), + vis: ast::Inherited, + span: DUMMY_SP + }); + } + + // `extern crate` must be precede `use` items + mem::swap(&mut vis, &mut krate.module.view_items); + krate.module.view_items.push_all_move(vis); + + // don't add #![no_std] here, that will block the prelude injection later. + // Add it during the prelude injection instead. + + // Add #![feature(phase)] here, because we use #[phase] on extern crate std. + let feat_phase_attr = attr::mk_attr_inner(attr::mk_attr_id(), + attr::mk_list_item( + InternedString::new("feature"), + vec![attr::mk_word_item(InternedString::new("phase"))], + )); + // std_inject runs after feature checking so manually mark this attr + attr::mark_used(&feat_phase_attr); + krate.attrs.push(feat_phase_attr); + + krate + } +} + +fn inject_crates_ref(krate: ast::Crate, + alt_std_name: Option<String>, + any_exe: bool) -> ast::Crate { + let mut fold = StandardLibraryInjector { + alt_std_name: alt_std_name, + any_exe: any_exe, + }; + fold.fold_crate(krate) +} + +struct PreludeInjector<'a>; + + +impl<'a> fold::Folder for PreludeInjector<'a> { + fn fold_crate(&mut self, mut krate: ast::Crate) -> ast::Crate { + // Add #![no_std] here, so we don't re-inject when compiling pretty-printed source. + // This must happen here and not in StandardLibraryInjector because this + // fold happens second. + + let no_std_attr = attr::mk_attr_inner(attr::mk_attr_id(), + attr::mk_word_item(InternedString::new("no_std"))); + // std_inject runs after feature checking so manually mark this attr + attr::mark_used(&no_std_attr); + krate.attrs.push(no_std_attr); + + if !no_prelude(krate.attrs.as_slice()) { + // only add `use std::prelude::*;` if there wasn't a + // `#![no_implicit_prelude]` at the crate level. + // fold_mod() will insert glob path. + let globs_attr = attr::mk_attr_inner(attr::mk_attr_id(), + attr::mk_list_item( + InternedString::new("feature"), + vec!( + attr::mk_word_item(InternedString::new("globs")), + ))); + // std_inject runs after feature checking so manually mark this attr + attr::mark_used(&globs_attr); + krate.attrs.push(globs_attr); + + krate.module = self.fold_mod(krate.module); + } + krate + } + + fn fold_item(&mut self, item: P<ast::Item>) -> SmallVector<P<ast::Item>> { + if !no_prelude(item.attrs.as_slice()) { + // only recur if there wasn't `#![no_implicit_prelude]` + // on this item, i.e. this means that the prelude is not + // implicitly imported though the whole subtree + fold::noop_fold_item(item, self) + } else { + SmallVector::one(item) + } + } + + fn fold_mod(&mut self, ast::Mod {inner, view_items, items}: ast::Mod) -> ast::Mod { + let prelude_path = ast::Path { + span: DUMMY_SP, + global: false, + segments: vec!( + ast::PathSegment { + identifier: token::str_to_ident("std"), + lifetimes: Vec::new(), + types: OwnedSlice::empty(), + }, + ast::PathSegment { + identifier: token::str_to_ident("prelude"), + lifetimes: Vec::new(), + types: OwnedSlice::empty(), + }), + }; + + let (crates, uses) = view_items.partitioned(|x| { + match x.node { + ast::ViewItemExternCrate(..) => true, + _ => false, + } + }); + + // add prelude after any `extern crate` but before any `use` + let mut view_items = crates; + let vp = P(codemap::dummy_spanned(ast::ViewPathGlob(prelude_path, ast::DUMMY_NODE_ID))); + view_items.push(ast::ViewItem { + node: ast::ViewItemUse(vp), + attrs: vec![ast::Attribute { + span: DUMMY_SP, + node: ast::Attribute_ { + id: attr::mk_attr_id(), + style: ast::AttrOuter, + value: P(ast::MetaItem { + span: DUMMY_SP, + node: ast::MetaWord(token::get_name( + special_idents::prelude_import.name)), + }), + is_sugared_doc: false, + }, + }], + vis: ast::Inherited, + span: DUMMY_SP, + }); + view_items.push_all_move(uses); + + fold::noop_fold_mod(ast::Mod { + inner: inner, + view_items: view_items, + items: items + }, self) + } +} + +fn inject_prelude(krate: ast::Crate) -> ast::Crate { + let mut fold = PreludeInjector; + fold.fold_crate(krate) +} diff --git a/src/libsyntax/test.rs b/src/libsyntax/test.rs new file mode 100644 index 00000000000..f0e69712714 --- /dev/null +++ b/src/libsyntax/test.rs @@ -0,0 +1,573 @@ +// Copyright 2012-2014 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 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Code that generates a test runner to run all the tests in a crate + +#![allow(dead_code)] +#![allow(unused_imports)] + +use std::gc::{Gc, GC}; +use std::slice; +use std::mem; +use std::vec; +use ast_util::*; +use attr::AttrMetaMethods; +use attr; +use codemap::{DUMMY_SP, Span, ExpnInfo, NameAndSpan, MacroAttribute}; +use codemap; +use diagnostic; +use config; +use ext::base::ExtCtxt; +use ext::build::AstBuilder; +use ext::expand::ExpansionConfig; +use fold::{Folder, MoveMap}; +use fold; +use owned_slice::OwnedSlice; +use parse::token::InternedString; +use parse::{token, ParseSess}; +use print::pprust; +use {ast, ast_util}; +use ptr::P; +use util::small_vector::SmallVector; + +struct Test { + span: Span, + path: Vec<ast::Ident> , + bench: bool, + ignore: bool, + should_fail: bool +} + +struct TestCtxt<'a> { + sess: &'a ParseSess, + span_diagnostic: &'a diagnostic::SpanHandler, + path: Vec<ast::Ident>, + ext_cx: ExtCtxt<'a>, + testfns: Vec<Test>, + reexport_test_harness_main: Option<InternedString>, + is_test_crate: bool, + config: ast::CrateConfig, + + // top-level re-export submodule, filled out after folding is finished + toplevel_reexport: Option<ast::Ident>, +} + +// Traverse the crate, collecting all the test functions, eliding any +// existing main functions, and synthesizing a main test harness +pub fn modify_for_testing(sess: &ParseSess, + cfg: &ast::CrateConfig, + krate: ast::Crate, + span_diagnostic: &diagnostic::SpanHandler) -> ast::Crate { + // We generate the test harness when building in the 'test' + // configuration, either with the '--test' or '--cfg test' + // command line options. + let should_test = attr::contains_name(krate.config.as_slice(), "test"); + + // Check for #[reexport_test_harness_main = "some_name"] which + // creates a `use some_name = __test::main;`. This needs to be + // unconditional, so that the attribute is still marked as used in + // non-test builds. + let reexport_test_harness_main = + attr::first_attr_value_str_by_name(krate.attrs.as_slice(), + "reexport_test_harness_main"); + + if should_test { + generate_test_harness(sess, reexport_test_harness_main, krate, cfg, span_diagnostic) + } else { + strip_test_functions(krate) + } +} + +struct TestHarnessGenerator<'a> { + cx: TestCtxt<'a>, + tests: Vec<ast::Ident>, + + // submodule name, gensym'd identifier for re-exports + tested_submods: Vec<(ast::Ident, ast::Ident)>, +} + +impl<'a> fold::Folder for TestHarnessGenerator<'a> { + fn fold_crate(&mut self, c: ast::Crate) -> ast::Crate { + let mut folded = fold::noop_fold_crate(c, self); + + // Add a special __test module to the crate that will contain code + // generated for the test harness + let (mod_, reexport) = mk_test_module(&mut self.cx); + folded.module.items.push(mod_); + match reexport { + Some(re) => folded.module.view_items.push(re), + None => {} + } + folded + } + + fn fold_item(&mut self, i: P<ast::Item>) -> SmallVector<P<ast::Item>> { + self.cx.path.push(i.ident); + debug!("current path: {}", + ast_util::path_name_i(self.cx.path.as_slice())); + + if is_test_fn(&self.cx, &*i) || is_bench_fn(&self.cx, &*i) { + match i.node { + ast::ItemFn(_, ast::UnsafeFn, _, _, _) => { + let diag = self.cx.span_diagnostic; + diag.span_fatal(i.span, + "unsafe functions cannot be used for \ + tests"); + } + _ => { + debug!("this is a test function"); + let test = Test { + span: i.span, + path: self.cx.path.clone(), + bench: is_bench_fn(&self.cx, &*i), + ignore: is_ignored(&self.cx, &*i), + should_fail: should_fail(&*i) + }; + self.cx.testfns.push(test); + self.tests.push(i.ident); + // debug!("have {} test/bench functions", + // cx.testfns.len()); + } + } + } + + // We don't want to recurse into anything other than mods, since + // mods or tests inside of functions will break things + let res = match i.node { + ast::ItemMod(..) => fold::noop_fold_item(i, self), + _ => SmallVector::one(i), + }; + self.cx.path.pop(); + res + } + + fn fold_mod(&mut self, m: ast::Mod) -> ast::Mod { + let tests = mem::replace(&mut self.tests, Vec::new()); + let tested_submods = mem::replace(&mut self.tested_submods, Vec::new()); + let mut mod_folded = fold::noop_fold_mod(m, self); + let tests = mem::replace(&mut self.tests, tests); + let tested_submods = mem::replace(&mut self.tested_submods, tested_submods); + + // Remove any #[main] from the AST so it doesn't clash with + // the one we're going to add. Only if compiling an executable. + + mod_folded.items = mem::replace(&mut mod_folded.items, vec![]).move_map(|item| { + item.map(|ast::Item {id, ident, attrs, node, vis, span}| { + ast::Item { + id: id, + ident: ident, + attrs: attrs.into_iter().filter_map(|attr| { + if !attr.check_name("main") { + Some(attr) + } else { + None + } + }).collect(), + node: node, + vis: vis, + span: span + } + }) + }); + + if !tests.is_empty() || !tested_submods.is_empty() { + let (it, sym) = mk_reexport_mod(&mut self.cx, tests, tested_submods); + mod_folded.items.push(it); + + if !self.cx.path.is_empty() { + self.tested_submods.push((self.cx.path[self.cx.path.len()-1], sym)); + } else { + debug!("pushing nothing, sym: {}", sym); + self.cx.toplevel_reexport = Some(sym); + } + } + + mod_folded + } +} + +fn mk_reexport_mod(cx: &mut TestCtxt, tests: Vec<ast::Ident>, + tested_submods: Vec<(ast::Ident, ast::Ident)>) -> (P<ast::Item>, ast::Ident) { + let mut view_items = Vec::new(); + let super_ = token::str_to_ident("super"); + + view_items.extend(tests.into_iter().map(|r| { + cx.ext_cx.view_use_simple(DUMMY_SP, ast::Public, + cx.ext_cx.path(DUMMY_SP, vec![super_, r])) + })); + view_items.extend(tested_submods.into_iter().map(|(r, sym)| { + let path = cx.ext_cx.path(DUMMY_SP, vec![super_, r, sym]); + cx.ext_cx.view_use_simple_(DUMMY_SP, ast::Public, r, path) + })); + + let reexport_mod = ast::Mod { + inner: DUMMY_SP, + view_items: view_items, + items: Vec::new(), + }; + + let sym = token::gensym_ident("__test_reexports"); + let it = P(ast::Item { + ident: sym.clone(), + attrs: Vec::new(), + id: ast::DUMMY_NODE_ID, + node: ast::ItemMod(reexport_mod), + vis: ast::Public, + span: DUMMY_SP, + }); + + (it, sym) +} + +fn generate_test_harness(sess: &ParseSess, + reexport_test_harness_main: Option<InternedString>, + krate: ast::Crate, + cfg: &ast::CrateConfig, + sd: &diagnostic::SpanHandler) -> ast::Crate { + let mut cx: TestCtxt = TestCtxt { + sess: sess, + span_diagnostic: sd, + ext_cx: ExtCtxt::new(sess, cfg.clone(), + ExpansionConfig { + deriving_hash_type_parameter: false, + crate_name: "test".to_string(), + }), + path: Vec::new(), + testfns: Vec::new(), + reexport_test_harness_main: reexport_test_harness_main, + is_test_crate: is_test_crate(&krate), + config: krate.config.clone(), + toplevel_reexport: None, + }; + + cx.ext_cx.bt_push(ExpnInfo { + call_site: DUMMY_SP, + callee: NameAndSpan { + name: "test".to_string(), + format: MacroAttribute, + span: None + } + }); + + let mut fold = TestHarnessGenerator { + cx: cx, + tests: Vec::new(), + tested_submods: Vec::new(), + }; + let res = fold.fold_crate(krate); + fold.cx.ext_cx.bt_pop(); + return res; +} + +fn strip_test_functions(krate: ast::Crate) -> ast::Crate { + // When not compiling with --test we should not compile the + // #[test] functions + config::strip_items(krate, |attrs| { + !attr::contains_name(attrs.as_slice(), "test") && + !attr::contains_name(attrs.as_slice(), "bench") + }) +} + +fn is_test_fn(cx: &TestCtxt, i: &ast::Item) -> bool { + let has_test_attr = attr::contains_name(i.attrs.as_slice(), "test"); + + fn has_test_signature(i: &ast::Item) -> bool { + match &i.node { + &ast::ItemFn(ref decl, _, _, ref generics, _) => { + let no_output = match decl.output.node { + ast::TyNil => true, + _ => false + }; + decl.inputs.is_empty() + && no_output + && !generics.is_parameterized() + } + _ => false + } + } + + if has_test_attr && !has_test_signature(i) { + let diag = cx.span_diagnostic; + diag.span_err( + i.span, + "functions used as tests must have signature fn() -> ()." + ); + } + + return has_test_attr && has_test_signature(i); +} + +fn is_bench_fn(cx: &TestCtxt, i: &ast::Item) -> bool { + let has_bench_attr = attr::contains_name(i.attrs.as_slice(), "bench"); + + fn has_test_signature(i: &ast::Item) -> bool { + match i.node { + ast::ItemFn(ref decl, _, _, ref generics, _) => { + let input_cnt = decl.inputs.len(); + let no_output = match decl.output.node { + ast::TyNil => true, + _ => false + }; + let tparm_cnt = generics.ty_params.len(); + // NB: inadequate check, but we're running + // well before resolve, can't get too deep. + input_cnt == 1u + && no_output && tparm_cnt == 0u + } + _ => false + } + } + + if has_bench_attr && !has_test_signature(i) { + let diag = cx.span_diagnostic; + diag.span_err(i.span, "functions used as benches must have signature \ + `fn(&mut Bencher) -> ()`"); + } + + return has_bench_attr && has_test_signature(i); +} + +fn is_ignored(cx: &TestCtxt, i: &ast::Item) -> bool { + i.attrs.iter().any(|attr| { + // check ignore(cfg(foo, bar)) + attr.check_name("ignore") && match attr.meta_item_list() { + Some(ref cfgs) => { + attr::test_cfg(cx.config.as_slice(), cfgs.iter()) + } + None => true + } + }) +} + +fn should_fail(i: &ast::Item) -> bool { + attr::contains_name(i.attrs.as_slice(), "should_fail") +} + +/* + +We're going to be building a module that looks more or less like: + +mod __test { + extern crate test (name = "test", vers = "..."); + fn main() { + test::test_main_static(::os::args().as_slice(), tests) + } + + static tests : &'static [test::TestDescAndFn] = &[ + ... the list of tests in the crate ... + ]; +} + +*/ + +fn mk_std(cx: &TestCtxt) -> ast::ViewItem { + let id_test = token::str_to_ident("test"); + let (vi, vis) = if cx.is_test_crate { + (ast::ViewItemUse( + P(nospan(ast::ViewPathSimple(id_test, + path_node(vec!(id_test)), + ast::DUMMY_NODE_ID)))), + ast::Public) + } else { + (ast::ViewItemExternCrate(id_test, None, ast::DUMMY_NODE_ID), + ast::Inherited) + }; + ast::ViewItem { + node: vi, + attrs: Vec::new(), + vis: vis, + span: DUMMY_SP + } +} + +fn mk_test_module(cx: &mut TestCtxt) -> (P<ast::Item>, Option<ast::ViewItem>) { + // Link to test crate + let view_items = vec!(mk_std(cx)); + + // A constant vector of test descriptors. + let tests = mk_tests(cx); + + // The synthesized main function which will call the console test runner + // with our list of tests + let mainfn = (quote_item!(&mut cx.ext_cx, + pub fn main() { + #![main] + use std::slice::Slice; + test::test_main_static(::std::os::args().as_slice(), TESTS); + } + )).unwrap(); + + let testmod = ast::Mod { + inner: DUMMY_SP, + view_items: view_items, + items: vec!(mainfn, tests), + }; + let item_ = ast::ItemMod(testmod); + + let mod_ident = token::gensym_ident("__test"); + let item = ast::Item { + ident: mod_ident, + attrs: Vec::new(), + id: ast::DUMMY_NODE_ID, + node: item_, + vis: ast::Public, + span: DUMMY_SP, + }; + let reexport = cx.reexport_test_harness_main.as_ref().map(|s| { + // building `use <ident> = __test::main` + let reexport_ident = token::str_to_ident(s.get()); + + let use_path = + nospan(ast::ViewPathSimple(reexport_ident, + path_node(vec![mod_ident, token::str_to_ident("main")]), + ast::DUMMY_NODE_ID)); + + ast::ViewItem { + node: ast::ViewItemUse(P(use_path)), + attrs: vec![], + vis: ast::Inherited, + span: DUMMY_SP + } + }); + + debug!("Synthetic test module:\n{}\n", pprust::item_to_string(&item)); + + (P(item), reexport) +} + +fn nospan<T>(t: T) -> codemap::Spanned<T> { + codemap::Spanned { node: t, span: DUMMY_SP } +} + +fn path_node(ids: Vec<ast::Ident> ) -> ast::Path { + ast::Path { + span: DUMMY_SP, + global: false, + segments: ids.into_iter().map(|identifier| ast::PathSegment { + identifier: identifier, + lifetimes: Vec::new(), + types: OwnedSlice::empty(), + }).collect() + } +} + +fn mk_tests(cx: &TestCtxt) -> P<ast::Item> { + // The vector of test_descs for this crate + let test_descs = mk_test_descs(cx); + + // FIXME #15962: should be using quote_item, but that stringifies + // __test_reexports, causing it to be reinterned, losing the + // gensym information. + let sp = DUMMY_SP; + let ecx = &cx.ext_cx; + let struct_type = ecx.ty_path(ecx.path(sp, vec![ecx.ident_of("self"), + ecx.ident_of("test"), + ecx.ident_of("TestDescAndFn")]), + None); + let static_lt = ecx.lifetime(sp, token::special_idents::static_lifetime.name); + // &'static [self::test::TestDescAndFn] + let static_type = ecx.ty_rptr(sp, + ecx.ty(sp, ast::TyVec(struct_type)), + Some(static_lt), + ast::MutImmutable); + // static TESTS: $static_type = &[...]; + ecx.item_static(sp, + ecx.ident_of("TESTS"), + static_type, + ast::MutImmutable, + test_descs) +} + +fn is_test_crate(krate: &ast::Crate) -> bool { + match attr::find_crate_name(krate.attrs.as_slice()) { + Some(ref s) if "test" == s.get().as_slice() => true, + _ => false + } +} + +fn mk_test_descs(cx: &TestCtxt) -> P<ast::Expr> { + debug!("building test vector from {} tests", cx.testfns.len()); + + P(ast::Expr { + id: ast::DUMMY_NODE_ID, + node: ast::ExprAddrOf(ast::MutImmutable, + P(ast::Expr { + id: ast::DUMMY_NODE_ID, + node: ast::ExprVec(cx.testfns.iter().map(|test| { + mk_test_desc_and_fn_rec(cx, test) + }).collect()), + span: DUMMY_SP, + })), + span: DUMMY_SP, + }) +} + +fn mk_test_desc_and_fn_rec(cx: &TestCtxt, test: &Test) -> P<ast::Expr> { + // FIXME #15962: should be using quote_expr, but that stringifies + // __test_reexports, causing it to be reinterned, losing the + // gensym information. + + let span = test.span; + let path = test.path.clone(); + let ecx = &cx.ext_cx; + let self_id = ecx.ident_of("self"); + let test_id = ecx.ident_of("test"); + + // creates self::test::$name + let test_path = |name| { + ecx.path(span, vec![self_id, test_id, ecx.ident_of(name)]) + }; + // creates $name: $expr + let field = |name, expr| ecx.field_imm(span, ecx.ident_of(name), expr); + + debug!("encoding {}", ast_util::path_name_i(path.as_slice())); + + // path to the #[test] function: "foo::bar::baz" + let path_string = ast_util::path_name_i(path.as_slice()); + let name_expr = ecx.expr_str(span, token::intern_and_get_ident(path_string.as_slice())); + + // self::test::StaticTestName($name_expr) + let name_expr = ecx.expr_call(span, + ecx.expr_path(test_path("StaticTestName")), + vec![name_expr]); + + let ignore_expr = ecx.expr_bool(span, test.ignore); + let fail_expr = ecx.expr_bool(span, test.should_fail); + + // self::test::TestDesc { ... } + let desc_expr = ecx.expr_struct( + span, + test_path("TestDesc"), + vec![field("name", name_expr), + field("ignore", ignore_expr), + field("should_fail", fail_expr)]); + + + let mut visible_path = match cx.toplevel_reexport { + Some(id) => vec![id], + None => { + let diag = cx.span_diagnostic; + diag.handler.bug("expected to find top-level re-export name, but found None"); + } + }; + visible_path.extend(path.into_iter()); + + let fn_expr = ecx.expr_path(ecx.path_global(span, visible_path)); + + let variant_name = if test.bench { "StaticBenchFn" } else { "StaticTestFn" }; + // self::test::$variant_name($fn_expr) + let testfn_expr = ecx.expr_call(span, ecx.expr_path(test_path(variant_name)), vec![fn_expr]); + + // self::test::TestDescAndFn { ... } + ecx.expr_struct(span, + test_path("TestDescAndFn"), + vec![field("desc", desc_expr), + field("testfn", testfn_expr)]) +} |
