Rollup merge of #63919 - matthewjasper:remove-gensymmed, r=petrochenkov
Use hygiene for AST passes
AST passes are now able to have resolve consider their expansions as if they were opaque macros defined either in some module in the current crate, or a fake empty module with `#[no_implicit_prelude]`.
* Add an ExpnKind for AST passes.
* Remove gensyms in AST passes.
* Remove gensyms in`#[test]`, `#[bench]` and `#[test_case]`.
* Allow opaque macros to define tests.
* Move tests for unit tests to their own directory.
* Remove `Ident::{gensym, is_gensymed}` - `Ident::gensym_if_underscore` still exists.
cc #60869, #61019
r? @petrochenkov
This commit is contained in:
@@ -11,7 +11,7 @@ use syntax::source_map::respan;
|
||||
use syntax::symbol::sym;
|
||||
use syntax::tokenstream::*;
|
||||
use syntax_pos::{Span, DUMMY_SP};
|
||||
use syntax_pos::hygiene::{ExpnData, ExpnKind, MacroKind};
|
||||
use syntax_pos::hygiene::{ExpnData, ExpnKind, AstPass};
|
||||
|
||||
use std::mem;
|
||||
|
||||
@@ -44,7 +44,7 @@ pub fn inject(
|
||||
if !named_exts.is_empty() {
|
||||
let mut extra_items = Vec::new();
|
||||
let span = DUMMY_SP.fresh_expansion(ExpnData::allow_unstable(
|
||||
ExpnKind::Macro(MacroKind::Attr, sym::plugin), DUMMY_SP, edition,
|
||||
ExpnKind::AstPass(AstPass::PluginMacroDefs), DUMMY_SP, edition,
|
||||
[sym::rustc_attrs][..].into(),
|
||||
));
|
||||
for (name, ext) in named_exts {
|
||||
|
||||
@@ -3,8 +3,7 @@ use std::mem;
|
||||
use smallvec::smallvec;
|
||||
use syntax::ast::{self, Ident};
|
||||
use syntax::attr;
|
||||
use syntax::source_map::{ExpnData, ExpnKind, respan};
|
||||
use syntax::ext::base::{ExtCtxt, MacroKind};
|
||||
use syntax::ext::base::ExtCtxt;
|
||||
use syntax::ext::expand::{AstFragment, ExpansionConfig};
|
||||
use syntax::ext::proc_macro::is_proc_macro_attr;
|
||||
use syntax::parse::ParseSess;
|
||||
@@ -12,6 +11,7 @@ use syntax::ptr::P;
|
||||
use syntax::symbol::{kw, sym};
|
||||
use syntax::visit::{self, Visitor};
|
||||
use syntax_pos::{Span, DUMMY_SP};
|
||||
use syntax_pos::hygiene::AstPass;
|
||||
|
||||
struct ProcMacroDerive {
|
||||
trait_name: ast::Name,
|
||||
@@ -308,8 +308,7 @@ impl<'a> Visitor<'a> for CollectProcMacros<'a> {
|
||||
|
||||
// Creates a new module which looks like:
|
||||
//
|
||||
// #[doc(hidden)]
|
||||
// mod $gensym {
|
||||
// const _: () = {
|
||||
// extern crate proc_macro;
|
||||
//
|
||||
// use proc_macro::bridge::client::ProcMacro;
|
||||
@@ -327,32 +326,30 @@ fn mk_decls(
|
||||
custom_attrs: &[ProcMacroDef],
|
||||
custom_macros: &[ProcMacroDef],
|
||||
) -> P<ast::Item> {
|
||||
let span = DUMMY_SP.fresh_expansion(ExpnData::allow_unstable(
|
||||
ExpnKind::Macro(MacroKind::Attr, sym::proc_macro), DUMMY_SP, cx.parse_sess.edition,
|
||||
[sym::rustc_attrs, sym::proc_macro_internals][..].into(),
|
||||
));
|
||||
let expn_id = cx.resolver.expansion_for_ast_pass(
|
||||
DUMMY_SP,
|
||||
AstPass::ProcMacroHarness,
|
||||
&[sym::rustc_attrs, sym::proc_macro_internals],
|
||||
None,
|
||||
);
|
||||
let span = DUMMY_SP.with_def_site_ctxt(expn_id);
|
||||
|
||||
let hidden = cx.meta_list_item_word(span, sym::hidden);
|
||||
let doc = cx.meta_list(span, sym::doc, vec![hidden]);
|
||||
let doc_hidden = cx.attribute(doc);
|
||||
|
||||
let proc_macro = Ident::with_dummy_span(sym::proc_macro);
|
||||
let proc_macro = Ident::new(sym::proc_macro, span);
|
||||
let krate = cx.item(span,
|
||||
proc_macro,
|
||||
Vec::new(),
|
||||
ast::ItemKind::ExternCrate(None));
|
||||
|
||||
let bridge = Ident::from_str("bridge");
|
||||
let client = Ident::from_str("client");
|
||||
let proc_macro_ty = Ident::from_str("ProcMacro");
|
||||
let custom_derive = Ident::from_str("custom_derive");
|
||||
let attr = Ident::from_str("attr");
|
||||
let bang = Ident::from_str("bang");
|
||||
let crate_kw = Ident::with_dummy_span(kw::Crate);
|
||||
let bridge = Ident::from_str_and_span("bridge", span);
|
||||
let client = Ident::from_str_and_span("client", span);
|
||||
let proc_macro_ty = Ident::from_str_and_span("ProcMacro", span);
|
||||
let custom_derive = Ident::from_str_and_span("custom_derive", span);
|
||||
let attr = Ident::from_str_and_span("attr", span);
|
||||
let bang = Ident::from_str_and_span("bang", span);
|
||||
|
||||
let decls = {
|
||||
let local_path = |sp: Span, name| {
|
||||
cx.expr_path(cx.path(sp.with_ctxt(span.ctxt()), vec![crate_kw, name]))
|
||||
cx.expr_path(cx.path(sp.with_ctxt(span.ctxt()), vec![name]))
|
||||
};
|
||||
let proc_macro_ty_method_path = |method| cx.expr_path(cx.path(span, vec![
|
||||
proc_macro, bridge, client, proc_macro_ty, method,
|
||||
@@ -381,7 +378,7 @@ fn mk_decls(
|
||||
|
||||
let decls_static = cx.item_static(
|
||||
span,
|
||||
Ident::from_str("_DECLS"),
|
||||
Ident::from_str_and_span("_DECLS", span),
|
||||
cx.ty_rptr(span,
|
||||
cx.ty(span, ast::TyKind::Slice(
|
||||
cx.ty_path(cx.path(span,
|
||||
@@ -392,22 +389,22 @@ fn mk_decls(
|
||||
).map(|mut i| {
|
||||
let attr = cx.meta_word(span, sym::rustc_proc_macro_decls);
|
||||
i.attrs.push(cx.attribute(attr));
|
||||
i.vis = respan(span, ast::VisibilityKind::Public);
|
||||
i
|
||||
});
|
||||
|
||||
let module = cx.item_mod(
|
||||
let block = cx.expr_block(cx.block(
|
||||
span,
|
||||
span,
|
||||
ast::Ident::from_str("decls").gensym(),
|
||||
vec![doc_hidden],
|
||||
vec![krate, decls_static],
|
||||
).map(|mut i| {
|
||||
i.vis = respan(span, ast::VisibilityKind::Public);
|
||||
i
|
||||
});
|
||||
vec![cx.stmt_item(span, krate), cx.stmt_item(span, decls_static)],
|
||||
));
|
||||
|
||||
// Integrate the new module into existing module structures.
|
||||
let module = AstFragment::Items(smallvec![module]);
|
||||
cx.monotonic_expander().fully_expand_fragment(module).make_items().pop().unwrap()
|
||||
let anon_constant = cx.item_const(
|
||||
span,
|
||||
ast::Ident::new(kw::Underscore, span),
|
||||
cx.ty(span, ast::TyKind::Tup(Vec::new())),
|
||||
block,
|
||||
);
|
||||
|
||||
// Integrate the new item into existing module structures.
|
||||
let items = AstFragment::Items(smallvec![anon_constant]);
|
||||
cx.monotonic_expander().fully_expand_fragment(items).make_items().pop().unwrap()
|
||||
}
|
||||
|
||||
@@ -1,86 +1,86 @@
|
||||
use syntax::{ast, attr};
|
||||
use syntax::edition::Edition;
|
||||
use syntax::ext::hygiene::MacroKind;
|
||||
use syntax::ext::expand::ExpansionConfig;
|
||||
use syntax::ext::hygiene::AstPass;
|
||||
use syntax::ext::base::{ExtCtxt, Resolver};
|
||||
use syntax::parse::ParseSess;
|
||||
use syntax::ptr::P;
|
||||
use syntax::source_map::{ExpnData, ExpnKind, dummy_spanned, respan};
|
||||
use syntax::symbol::{Ident, Symbol, kw, sym};
|
||||
use syntax_pos::DUMMY_SP;
|
||||
|
||||
use std::iter;
|
||||
|
||||
pub fn inject(
|
||||
mut krate: ast::Crate, alt_std_name: Option<&str>, edition: Edition
|
||||
mut krate: ast::Crate,
|
||||
resolver: &mut dyn Resolver,
|
||||
sess: &ParseSess,
|
||||
alt_std_name: Option<Symbol>,
|
||||
) -> (ast::Crate, Option<Symbol>) {
|
||||
let rust_2018 = edition >= Edition::Edition2018;
|
||||
let rust_2018 = sess.edition >= Edition::Edition2018;
|
||||
|
||||
// the first name in this list is the crate name of the crate with the prelude
|
||||
let names: &[&str] = if attr::contains_name(&krate.attrs, sym::no_core) {
|
||||
let names: &[Symbol] = if attr::contains_name(&krate.attrs, sym::no_core) {
|
||||
return (krate, None);
|
||||
} else if attr::contains_name(&krate.attrs, sym::no_std) {
|
||||
if attr::contains_name(&krate.attrs, sym::compiler_builtins) {
|
||||
&["core"]
|
||||
&[sym::core]
|
||||
} else {
|
||||
&["core", "compiler_builtins"]
|
||||
&[sym::core, sym::compiler_builtins]
|
||||
}
|
||||
} else {
|
||||
&["std"]
|
||||
&[sym::std]
|
||||
};
|
||||
|
||||
let expn_id = resolver.expansion_for_ast_pass(
|
||||
DUMMY_SP,
|
||||
AstPass::StdImports,
|
||||
&[sym::prelude_import],
|
||||
None,
|
||||
);
|
||||
let span = DUMMY_SP.with_def_site_ctxt(expn_id);
|
||||
let call_site = DUMMY_SP.with_call_site_ctxt(expn_id);
|
||||
|
||||
let ecfg = ExpansionConfig::default("std_lib_injection".to_string());
|
||||
let cx = ExtCtxt::new(sess, ecfg, resolver);
|
||||
|
||||
|
||||
// .rev() to preserve ordering above in combination with insert(0, ...)
|
||||
let alt_std_name = alt_std_name.map(Symbol::intern);
|
||||
for orig_name_str in names.iter().rev() {
|
||||
// HACK(eddyb) gensym the injected crates on the Rust 2018 edition,
|
||||
// so they don't accidentally interfere with the new import paths.
|
||||
let orig_name_sym = Symbol::intern(orig_name_str);
|
||||
let orig_name_ident = Ident::with_dummy_span(orig_name_sym);
|
||||
let (rename, orig_name) = if rust_2018 {
|
||||
(orig_name_ident.gensym(), Some(orig_name_sym))
|
||||
for &name in names.iter().rev() {
|
||||
let ident = if rust_2018 {
|
||||
Ident::new(name, span)
|
||||
} else {
|
||||
(orig_name_ident, None)
|
||||
Ident::new(name, call_site)
|
||||
};
|
||||
krate.module.items.insert(0, P(ast::Item {
|
||||
attrs: vec![attr::mk_attr_outer(
|
||||
attr::mk_word_item(ast::Ident::with_dummy_span(sym::macro_use))
|
||||
)],
|
||||
vis: dummy_spanned(ast::VisibilityKind::Inherited),
|
||||
node: ast::ItemKind::ExternCrate(alt_std_name.or(orig_name)),
|
||||
ident: rename,
|
||||
id: ast::DUMMY_NODE_ID,
|
||||
span: DUMMY_SP,
|
||||
tokens: None,
|
||||
}));
|
||||
krate.module.items.insert(0, cx.item(
|
||||
span,
|
||||
ident,
|
||||
vec![cx.attribute(cx.meta_word(span, sym::macro_use))],
|
||||
ast::ItemKind::ExternCrate(alt_std_name),
|
||||
));
|
||||
}
|
||||
|
||||
// the crates have been injected, the assumption is that the first one is the one with
|
||||
// the prelude.
|
||||
// The crates have been injected, the assumption is that the first one is
|
||||
// the one with the prelude.
|
||||
let name = names[0];
|
||||
|
||||
let span = DUMMY_SP.fresh_expansion(ExpnData::allow_unstable(
|
||||
ExpnKind::Macro(MacroKind::Attr, sym::std_inject), DUMMY_SP, edition,
|
||||
[sym::prelude_import][..].into(),
|
||||
));
|
||||
let import_path = if rust_2018 {
|
||||
[name, sym::prelude, sym::v1].iter()
|
||||
.map(|symbol| ast::Ident::new(*symbol, span)).collect()
|
||||
} else {
|
||||
[kw::PathRoot, name, sym::prelude, sym::v1].iter()
|
||||
.map(|symbol| ast::Ident::new(*symbol, span)).collect()
|
||||
};
|
||||
|
||||
krate.module.items.insert(0, P(ast::Item {
|
||||
attrs: vec![attr::mk_attr_outer(
|
||||
attr::mk_word_item(ast::Ident::new(sym::prelude_import, span)))],
|
||||
vis: respan(span.shrink_to_lo(), ast::VisibilityKind::Inherited),
|
||||
node: ast::ItemKind::Use(P(ast::UseTree {
|
||||
prefix: ast::Path {
|
||||
segments: iter::once(ast::Ident::with_dummy_span(kw::PathRoot))
|
||||
.chain(
|
||||
[name, "prelude", "v1"].iter().cloned()
|
||||
.map(ast::Ident::from_str)
|
||||
).map(ast::PathSegment::from_ident).collect(),
|
||||
span,
|
||||
},
|
||||
let use_item = cx.item(
|
||||
span,
|
||||
ast::Ident::invalid(),
|
||||
vec![cx.attribute(cx.meta_word(span, sym::prelude_import))],
|
||||
ast::ItemKind::Use(P(ast::UseTree {
|
||||
prefix: cx.path(span, import_path),
|
||||
kind: ast::UseTreeKind::Glob,
|
||||
span,
|
||||
})),
|
||||
id: ast::DUMMY_NODE_ID,
|
||||
ident: ast::Ident::invalid(),
|
||||
span,
|
||||
tokens: None,
|
||||
}));
|
||||
);
|
||||
|
||||
(krate, Some(Symbol::intern(name)))
|
||||
krate.module.items.insert(0, use_item);
|
||||
|
||||
(krate, Some(name))
|
||||
}
|
||||
|
||||
@@ -28,11 +28,11 @@ pub fn expand_test_case(
|
||||
|
||||
if !ecx.ecfg.should_test { return vec![]; }
|
||||
|
||||
let sp = ecx.with_legacy_ctxt(attr_sp);
|
||||
let sp = ecx.with_def_site_ctxt(attr_sp);
|
||||
let mut item = anno_item.expect_item();
|
||||
item = item.map(|mut item| {
|
||||
item.vis = respan(item.vis.span, ast::VisibilityKind::Public);
|
||||
item.ident = item.ident.gensym();
|
||||
item.ident.span = item.ident.span.with_ctxt(sp.ctxt());
|
||||
item.attrs.push(
|
||||
ecx.attribute(ecx.meta_word(sp, sym::rustc_test_marker))
|
||||
);
|
||||
@@ -92,10 +92,9 @@ pub fn expand_test_or_bench(
|
||||
return vec![Annotatable::Item(item)];
|
||||
}
|
||||
|
||||
let (sp, attr_sp) = (cx.with_legacy_ctxt(item.span), cx.with_legacy_ctxt(attr_sp));
|
||||
let (sp, attr_sp) = (cx.with_def_site_ctxt(item.span), cx.with_def_site_ctxt(attr_sp));
|
||||
|
||||
// Gensym "test" so we can extern crate without conflicting with any local names
|
||||
let test_id = cx.ident_of("test").gensym();
|
||||
let test_id = ast::Ident::new(sym::test, attr_sp);
|
||||
|
||||
// creates test::$name
|
||||
let test_path = |name| {
|
||||
@@ -112,7 +111,7 @@ pub fn expand_test_or_bench(
|
||||
|
||||
let test_fn = if is_bench {
|
||||
// A simple ident for a lambda
|
||||
let b = cx.ident_of("b");
|
||||
let b = ast::Ident::from_str_and_span("b", attr_sp);
|
||||
|
||||
cx.expr_call(sp, cx.expr_path(test_path("StaticBenchFn")), vec![
|
||||
// |b| self::test::assert_test_result(
|
||||
@@ -143,7 +142,7 @@ pub fn expand_test_or_bench(
|
||||
])
|
||||
};
|
||||
|
||||
let mut test_const = cx.item(sp, ast::Ident::new(item.ident.name, sp).gensym(),
|
||||
let mut test_const = cx.item(sp, ast::Ident::new(item.ident.name, sp),
|
||||
vec![
|
||||
// #[cfg(test)]
|
||||
cx.attribute(cx.meta_list(attr_sp, sym::cfg, vec![
|
||||
@@ -192,17 +191,17 @@ pub fn expand_test_or_bench(
|
||||
));
|
||||
test_const = test_const.map(|mut tc| { tc.vis.node = ast::VisibilityKind::Public; tc});
|
||||
|
||||
// extern crate test as test_gensym
|
||||
// extern crate test
|
||||
let test_extern = cx.item(sp,
|
||||
test_id,
|
||||
vec![],
|
||||
ast::ItemKind::ExternCrate(Some(sym::test))
|
||||
ast::ItemKind::ExternCrate(None)
|
||||
);
|
||||
|
||||
log::debug!("synthetic test item:\n{}\n", pprust::item_to_string(&test_const));
|
||||
|
||||
vec![
|
||||
// Access to libtest under a gensymed name
|
||||
// Access to libtest under a hygienic name
|
||||
Annotatable::Item(test_extern),
|
||||
// The generated test case
|
||||
Annotatable::Item(test_const),
|
||||
|
||||
@@ -5,32 +5,30 @@ use smallvec::{smallvec, SmallVec};
|
||||
use syntax::ast::{self, Ident};
|
||||
use syntax::attr;
|
||||
use syntax::entry::{self, EntryPointType};
|
||||
use syntax::ext::base::{ExtCtxt, MacroKind, Resolver};
|
||||
use syntax::ext::base::{ExtCtxt, Resolver};
|
||||
use syntax::ext::expand::{AstFragment, ExpansionConfig};
|
||||
use syntax::feature_gate::Features;
|
||||
use syntax::mut_visit::{*, ExpectOne};
|
||||
use syntax::parse::ParseSess;
|
||||
use syntax::ptr::P;
|
||||
use syntax::source_map::{ExpnData, ExpnKind, dummy_spanned};
|
||||
use syntax::symbol::{kw, sym, Symbol};
|
||||
use syntax::source_map::respan;
|
||||
use syntax::symbol::{sym, Symbol};
|
||||
use syntax_pos::{Span, DUMMY_SP};
|
||||
use syntax_pos::hygiene::{AstPass, SyntaxContext, Transparency};
|
||||
|
||||
use std::{iter, mem};
|
||||
|
||||
struct Test {
|
||||
span: Span,
|
||||
path: Vec<Ident>,
|
||||
ident: Ident,
|
||||
}
|
||||
|
||||
struct TestCtxt<'a> {
|
||||
span_diagnostic: &'a errors::Handler,
|
||||
path: Vec<Ident>,
|
||||
ext_cx: ExtCtxt<'a>,
|
||||
def_site: Span,
|
||||
test_cases: Vec<Test>,
|
||||
reexport_test_harness_main: Option<Symbol>,
|
||||
test_runner: Option<ast::Path>,
|
||||
// top-level re-export submodule, filled out after folding is finished
|
||||
toplevel_reexport: Option<Ident>,
|
||||
}
|
||||
|
||||
// Traverse the crate, collecting all the test functions, eliding any
|
||||
@@ -43,8 +41,8 @@ pub fn inject(
|
||||
span_diagnostic: &errors::Handler,
|
||||
features: &Features,
|
||||
) {
|
||||
// Check for #[reexport_test_harness_main = "some_name"] which
|
||||
// creates a `use __test::main as some_name;`. This needs to be
|
||||
// Check for #![reexport_test_harness_main = "some_name"] which gives the
|
||||
// main test function the name `some_name` without hygiene. This needs to be
|
||||
// unconditional, so that the attribute is still marked as used in
|
||||
// non-test builds.
|
||||
let reexport_test_harness_main =
|
||||
@@ -56,16 +54,13 @@ pub fn inject(
|
||||
|
||||
if should_test {
|
||||
generate_test_harness(sess, resolver, reexport_test_harness_main,
|
||||
krate, span_diagnostic, features, test_runner)
|
||||
krate, features, test_runner)
|
||||
}
|
||||
}
|
||||
|
||||
struct TestHarnessGenerator<'a> {
|
||||
cx: TestCtxt<'a>,
|
||||
tests: Vec<Ident>,
|
||||
|
||||
// submodule name, gensym'd identifier for re-exports
|
||||
tested_submods: Vec<(Ident, Ident)>,
|
||||
tests: Vec<Test>,
|
||||
}
|
||||
|
||||
impl<'a> MutVisitor for TestHarnessGenerator<'a> {
|
||||
@@ -77,49 +72,47 @@ impl<'a> MutVisitor for TestHarnessGenerator<'a> {
|
||||
}
|
||||
|
||||
fn flat_map_item(&mut self, i: P<ast::Item>) -> SmallVec<[P<ast::Item>; 1]> {
|
||||
let ident = i.ident;
|
||||
if ident.name != kw::Invalid {
|
||||
self.cx.path.push(ident);
|
||||
}
|
||||
debug!("current path: {}", path_name_i(&self.cx.path));
|
||||
|
||||
let mut item = i.into_inner();
|
||||
if is_test_case(&item) {
|
||||
debug!("this is a test item");
|
||||
|
||||
let test = Test {
|
||||
span: item.span,
|
||||
path: self.cx.path.clone(),
|
||||
ident: item.ident,
|
||||
};
|
||||
self.cx.test_cases.push(test);
|
||||
self.tests.push(item.ident);
|
||||
self.tests.push(test);
|
||||
}
|
||||
|
||||
// We don't want to recurse into anything other than mods, since
|
||||
// mods or tests inside of functions will break things
|
||||
if let ast::ItemKind::Mod(mut module) = item.node {
|
||||
let tests = mem::take(&mut self.tests);
|
||||
let tested_submods = mem::take(&mut self.tested_submods);
|
||||
noop_visit_mod(&mut module, self);
|
||||
let tests = mem::replace(&mut self.tests, tests);
|
||||
let tested_submods = mem::replace(&mut self.tested_submods, tested_submods);
|
||||
let mut tests = mem::replace(&mut self.tests, tests);
|
||||
|
||||
if !tests.is_empty() || !tested_submods.is_empty() {
|
||||
let (it, sym) = mk_reexport_mod(&mut self.cx, item.id, tests, tested_submods);
|
||||
module.items.push(it);
|
||||
|
||||
if !self.cx.path.is_empty() {
|
||||
self.tested_submods.push((self.cx.path[self.cx.path.len()-1], sym));
|
||||
if !tests.is_empty() {
|
||||
let parent = if item.id == ast::DUMMY_NODE_ID {
|
||||
ast::CRATE_NODE_ID
|
||||
} else {
|
||||
debug!("pushing nothing, sym: {:?}", sym);
|
||||
self.cx.toplevel_reexport = Some(sym);
|
||||
item.id
|
||||
};
|
||||
// Create an identifier that will hygienically resolve the test
|
||||
// case name, even in another module.
|
||||
let expn_id = self.cx.ext_cx.resolver.expansion_for_ast_pass(
|
||||
module.inner,
|
||||
AstPass::TestHarness,
|
||||
&[],
|
||||
Some(parent),
|
||||
);
|
||||
for test in &mut tests {
|
||||
// See the comment on `mk_main` for why we're using
|
||||
// `apply_mark` directly.
|
||||
test.ident.span = test.ident.span.apply_mark(expn_id, Transparency::Opaque);
|
||||
}
|
||||
self.cx.test_cases.extend(tests);
|
||||
}
|
||||
item.node = ast::ItemKind::Mod(module);
|
||||
}
|
||||
if ident.name != kw::Invalid {
|
||||
self.cx.path.pop();
|
||||
}
|
||||
smallvec![P(item)]
|
||||
}
|
||||
|
||||
@@ -133,6 +126,7 @@ impl<'a> MutVisitor for TestHarnessGenerator<'a> {
|
||||
struct EntryPointCleaner {
|
||||
// Current depth in the ast
|
||||
depth: usize,
|
||||
def_site: Span,
|
||||
}
|
||||
|
||||
impl MutVisitor for EntryPointCleaner {
|
||||
@@ -149,8 +143,10 @@ impl MutVisitor for EntryPointCleaner {
|
||||
EntryPointType::MainAttr |
|
||||
EntryPointType::Start =>
|
||||
item.map(|ast::Item {id, ident, attrs, node, vis, span, tokens}| {
|
||||
let allow_ident = Ident::with_dummy_span(sym::allow);
|
||||
let dc_nested = attr::mk_nested_word_item(Ident::from_str("dead_code"));
|
||||
let allow_ident = Ident::new(sym::allow, self.def_site);
|
||||
let dc_nested = attr::mk_nested_word_item(
|
||||
Ident::from_str_and_span("dead_code", self.def_site),
|
||||
);
|
||||
let allow_dead_code_item = attr::mk_list_item(allow_ident, vec![dc_nested]);
|
||||
let allow_dead_code = attr::mk_attr_outer(allow_dead_code_item);
|
||||
|
||||
@@ -181,124 +177,99 @@ impl MutVisitor for EntryPointCleaner {
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates an item (specifically a module) that "pub use"s the tests passed in.
|
||||
/// Each tested submodule will contain a similar reexport module that we will export
|
||||
/// under the name of the original module. That is, `submod::__test_reexports` is
|
||||
/// reexported like so `pub use submod::__test_reexports as submod`.
|
||||
fn mk_reexport_mod(cx: &mut TestCtxt<'_>,
|
||||
parent: ast::NodeId,
|
||||
tests: Vec<Ident>,
|
||||
tested_submods: Vec<(Ident, Ident)>)
|
||||
-> (P<ast::Item>, Ident) {
|
||||
let super_ = Ident::with_dummy_span(kw::Super);
|
||||
|
||||
let items = tests.into_iter().map(|r| {
|
||||
cx.ext_cx.item_use_simple(DUMMY_SP, dummy_spanned(ast::VisibilityKind::Public),
|
||||
cx.ext_cx.path(DUMMY_SP, vec![super_, r]))
|
||||
}).chain(tested_submods.into_iter().map(|(r, sym)| {
|
||||
let path = cx.ext_cx.path(DUMMY_SP, vec![super_, r, sym]);
|
||||
cx.ext_cx.item_use_simple_(DUMMY_SP, dummy_spanned(ast::VisibilityKind::Public),
|
||||
Some(r), path)
|
||||
})).collect();
|
||||
|
||||
let reexport_mod = ast::Mod {
|
||||
inline: true,
|
||||
inner: DUMMY_SP,
|
||||
items,
|
||||
};
|
||||
|
||||
let name = Ident::from_str("__test_reexports").gensym();
|
||||
let parent = if parent == ast::DUMMY_NODE_ID { ast::CRATE_NODE_ID } else { parent };
|
||||
cx.ext_cx.current_expansion.id = cx.ext_cx.resolver.get_module_scope(parent);
|
||||
let module = P(ast::Item {
|
||||
ident: name,
|
||||
attrs: Vec::new(),
|
||||
id: ast::DUMMY_NODE_ID,
|
||||
node: ast::ItemKind::Mod(reexport_mod),
|
||||
vis: dummy_spanned(ast::VisibilityKind::Public),
|
||||
span: DUMMY_SP,
|
||||
tokens: None,
|
||||
});
|
||||
|
||||
// Integrate the new module into existing module structures.
|
||||
let module = AstFragment::Items(smallvec![module]);
|
||||
let module =
|
||||
cx.ext_cx.monotonic_expander().fully_expand_fragment(module).make_items().pop().unwrap();
|
||||
|
||||
(module, name)
|
||||
}
|
||||
|
||||
/// Crawl over the crate, inserting test reexports and the test main function
|
||||
fn generate_test_harness(sess: &ParseSess,
|
||||
resolver: &mut dyn Resolver,
|
||||
reexport_test_harness_main: Option<Symbol>,
|
||||
krate: &mut ast::Crate,
|
||||
sd: &errors::Handler,
|
||||
features: &Features,
|
||||
test_runner: Option<ast::Path>) {
|
||||
// Remove the entry points
|
||||
let mut cleaner = EntryPointCleaner { depth: 0 };
|
||||
cleaner.visit_crate(krate);
|
||||
|
||||
let mut econfig = ExpansionConfig::default("test".to_string());
|
||||
econfig.features = Some(features);
|
||||
|
||||
let ext_cx = ExtCtxt::new(sess, econfig, resolver);
|
||||
|
||||
let expn_id = ext_cx.resolver.expansion_for_ast_pass(
|
||||
DUMMY_SP,
|
||||
AstPass::TestHarness,
|
||||
&[sym::main, sym::test, sym::rustc_attrs],
|
||||
None,
|
||||
);
|
||||
let def_site = DUMMY_SP.with_def_site_ctxt(expn_id);
|
||||
|
||||
// Remove the entry points
|
||||
let mut cleaner = EntryPointCleaner { depth: 0, def_site };
|
||||
cleaner.visit_crate(krate);
|
||||
|
||||
let cx = TestCtxt {
|
||||
span_diagnostic: sd,
|
||||
ext_cx: ExtCtxt::new(sess, econfig, resolver),
|
||||
path: Vec::new(),
|
||||
ext_cx,
|
||||
def_site,
|
||||
test_cases: Vec::new(),
|
||||
reexport_test_harness_main,
|
||||
toplevel_reexport: None,
|
||||
test_runner
|
||||
};
|
||||
|
||||
TestHarnessGenerator {
|
||||
cx,
|
||||
tests: Vec::new(),
|
||||
tested_submods: Vec::new(),
|
||||
}.visit_crate(krate);
|
||||
}
|
||||
|
||||
/// Creates a function item for use as the main function of a test build.
|
||||
/// This function will call the `test_runner` as specified by the crate attribute
|
||||
///
|
||||
/// By default this expands to
|
||||
///
|
||||
/// #[main]
|
||||
/// pub fn main() {
|
||||
/// extern crate test;
|
||||
/// test::test_main_static(&[
|
||||
/// &test_const1,
|
||||
/// &test_const2,
|
||||
/// &test_const3,
|
||||
/// ]);
|
||||
/// }
|
||||
///
|
||||
/// Most of the Ident have the usual def-site hygiene for the AST pass. The
|
||||
/// exception is the `test_const`s. These have a syntax context that has two
|
||||
/// opaque marks: one from the expansion of `test` or `test_case`, and one
|
||||
/// generated in `TestHarnessGenerator::flat_map_item`. When resolving this
|
||||
/// identifier after failing to find a matching identifier in the root module
|
||||
/// we remove the outer mark, and try resolving at its def-site, which will
|
||||
/// then resolve to `test_const`.
|
||||
///
|
||||
/// The expansion here can be controlled by two attributes:
|
||||
///
|
||||
/// `reexport_test_harness_main` provides a different name for the `main`
|
||||
/// function and `test_runner` provides a path that replaces
|
||||
/// `test::test_main_static`.
|
||||
fn mk_main(cx: &mut TestCtxt<'_>) -> P<ast::Item> {
|
||||
// Writing this out by hand:
|
||||
// pub fn main() {
|
||||
// #![main]
|
||||
// test::test_main_static(&[..tests]);
|
||||
// }
|
||||
let sp = DUMMY_SP.fresh_expansion(ExpnData::allow_unstable(
|
||||
ExpnKind::Macro(MacroKind::Attr, sym::test_case), DUMMY_SP, cx.ext_cx.parse_sess.edition,
|
||||
[sym::main, sym::test, sym::rustc_attrs][..].into(),
|
||||
));
|
||||
let sp = cx.def_site;
|
||||
let ecx = &cx.ext_cx;
|
||||
let test_id = Ident::with_dummy_span(sym::test);
|
||||
let test_id = Ident::new(sym::test, sp);
|
||||
|
||||
// test::test_main_static(...)
|
||||
let mut test_runner = cx.test_runner.clone().unwrap_or(
|
||||
ecx.path(sp, vec![
|
||||
test_id, ecx.ident_of("test_main_static")
|
||||
]));
|
||||
ecx.path(sp, vec![test_id, Ident::from_str_and_span("test_main_static", sp)]));
|
||||
|
||||
test_runner.span = sp;
|
||||
|
||||
let test_main_path_expr = ecx.expr_path(test_runner);
|
||||
let call_test_main = ecx.expr_call(sp, test_main_path_expr,
|
||||
vec![mk_tests_slice(cx)]);
|
||||
vec![mk_tests_slice(cx, sp)]);
|
||||
let call_test_main = ecx.stmt_expr(call_test_main);
|
||||
|
||||
// #![main]
|
||||
let main_meta = ecx.meta_word(sp, sym::main);
|
||||
let main_attr = ecx.attribute(main_meta);
|
||||
|
||||
// extern crate test as test_gensym
|
||||
// extern crate test
|
||||
let test_extern_stmt = ecx.stmt_item(sp, ecx.item(sp,
|
||||
test_id,
|
||||
vec![],
|
||||
ast::ItemKind::ExternCrate(None)
|
||||
));
|
||||
|
||||
// #[main]
|
||||
let main_meta = ecx.meta_word(sp, sym::main);
|
||||
let main_attr = ecx.attribute(main_meta);
|
||||
|
||||
// pub fn main() { ... }
|
||||
let main_ret_ty = ecx.ty(sp, ast::TyKind::Tup(vec![]));
|
||||
|
||||
@@ -316,8 +287,8 @@ fn mk_main(cx: &mut TestCtxt<'_>) -> P<ast::Item> {
|
||||
|
||||
// Honor the reexport_test_harness_main attribute
|
||||
let main_id = match cx.reexport_test_harness_main {
|
||||
Some(sym) => Ident::new(sym, sp),
|
||||
None => Ident::from_str_and_span("main", sp).gensym(),
|
||||
Some(sym) => Ident::new(sym, sp.with_ctxt(SyntaxContext::root())),
|
||||
None => Ident::from_str_and_span("main", sp),
|
||||
};
|
||||
|
||||
let main = P(ast::Item {
|
||||
@@ -325,7 +296,7 @@ fn mk_main(cx: &mut TestCtxt<'_>) -> P<ast::Item> {
|
||||
attrs: vec![main_attr],
|
||||
id: ast::DUMMY_NODE_ID,
|
||||
node: main,
|
||||
vis: dummy_spanned(ast::VisibilityKind::Public),
|
||||
vis: respan(sp, ast::VisibilityKind::Public),
|
||||
span: sp,
|
||||
tokens: None,
|
||||
});
|
||||
@@ -335,44 +306,20 @@ fn mk_main(cx: &mut TestCtxt<'_>) -> P<ast::Item> {
|
||||
cx.ext_cx.monotonic_expander().fully_expand_fragment(main).make_items().pop().unwrap()
|
||||
}
|
||||
|
||||
fn path_name_i(idents: &[Ident]) -> String {
|
||||
let mut path_name = "".to_string();
|
||||
let mut idents_iter = idents.iter().peekable();
|
||||
while let Some(ident) = idents_iter.next() {
|
||||
path_name.push_str(&ident.as_str());
|
||||
if idents_iter.peek().is_some() {
|
||||
path_name.push_str("::")
|
||||
}
|
||||
}
|
||||
path_name
|
||||
}
|
||||
|
||||
/// Creates a slice containing every test like so:
|
||||
/// &[path::to::test1, path::to::test2]
|
||||
fn mk_tests_slice(cx: &TestCtxt<'_>) -> P<ast::Expr> {
|
||||
/// &[&test1, &test2]
|
||||
fn mk_tests_slice(cx: &TestCtxt<'_>, sp: Span) -> P<ast::Expr> {
|
||||
debug!("building test vector from {} tests", cx.test_cases.len());
|
||||
let ref ecx = cx.ext_cx;
|
||||
|
||||
ecx.expr_vec_slice(DUMMY_SP,
|
||||
|
||||
ecx.expr_vec_slice(sp,
|
||||
cx.test_cases.iter().map(|test| {
|
||||
ecx.expr_addr_of(test.span,
|
||||
ecx.expr_path(ecx.path(test.span, visible_path(cx, &test.path))))
|
||||
ecx.expr_path(ecx.path(test.span, vec![test.ident])))
|
||||
}).collect())
|
||||
}
|
||||
|
||||
/// Creates a path from the top-level __test module to the test via __test_reexports
|
||||
fn visible_path(cx: &TestCtxt<'_>, path: &[Ident]) -> Vec<Ident>{
|
||||
let mut visible_path = vec![];
|
||||
match cx.toplevel_reexport {
|
||||
Some(id) => visible_path.push(id),
|
||||
None => {
|
||||
cx.span_diagnostic.bug("expected to find top-level re-export name, but found None");
|
||||
}
|
||||
}
|
||||
visible_path.extend_from_slice(path);
|
||||
visible_path
|
||||
}
|
||||
|
||||
fn is_test_case(i: &ast::Item) -> bool {
|
||||
attr::contains_name(&i.attrs, sym::rustc_test_marker)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user