Make use of hygiene in AST passes

This commit is contained in:
Matthew Jasper 2019-08-25 21:03:24 +01:00
parent 4082cd95a8
commit 6fcdb36ccb
15 changed files with 297 additions and 256 deletions

View File

@ -233,7 +233,7 @@ pub fn register_plugins<'a>(
syntax::attr::inject(krate, &sess.parse_sess, &sess.opts.debugging_opts.crate_attr)
});
let (mut krate, features) = syntax::config::features(
let (krate, features) = syntax::config::features(
krate,
&sess.parse_sess,
sess.edition(),
@ -268,16 +268,6 @@ pub fn register_plugins<'a>(
middle::recursion_limit::update_limits(sess, &krate);
});
krate = time(sess, "crate injection", || {
let alt_std_name = sess.opts.alt_std_name.as_ref().map(|s| &**s);
let (krate, name) =
syntax_ext::standard_library_imports::inject(krate, alt_std_name, sess.edition());
if let Some(name) = name {
sess.parse_sess.injected_crate_name.set(name);
}
krate
});
let registrars = time(sess, "plugin loading", || {
plugin::load::load_plugins(
sess,
@ -370,6 +360,21 @@ fn configure_and_expand_inner<'a>(
&resolver_arenas,
);
syntax_ext::register_builtin_macros(&mut resolver, sess.edition());
krate = time(sess, "crate injection", || {
let alt_std_name = sess.opts.alt_std_name.as_ref().map(|s| Symbol::intern(s));
let (krate, name) = syntax_ext::standard_library_imports::inject(
krate,
&mut resolver,
alt_std_name,
sess.edition(),
);
if let Some(name) = name {
sess.parse_sess.injected_crate_name.set(name);
}
krate
});
syntax_ext::plugin_macro_defs::inject(
&mut krate, &mut resolver, plugin_info.syntax_exts, sess.edition()
);

View File

@ -97,16 +97,6 @@ impl<'a> base::Resolver for Resolver<'a> {
self.session.next_node_id()
}
fn get_module_scope(&mut self, id: NodeId) -> ExpnId {
let expn_id = ExpnId::fresh(Some(ExpnData::default(
ExpnKind::Macro(MacroKind::Attr, sym::test_case), DUMMY_SP, self.session.edition()
)));
let module = self.module_map[&self.definitions.local_def_id(id)];
self.invocation_parent_scopes.insert(expn_id, ParentScope::module(module));
self.definitions.set_invocation_parent(expn_id, module.def_id().unwrap().index);
expn_id
}
fn resolve_dollar_crates(&mut self) {
hygiene::update_dollar_crate_names(|ctxt| {
let ident = Ident::new(kw::DollarCrate, DUMMY_SP.with_ctxt(ctxt));

View File

@ -653,8 +653,6 @@ bitflags::bitflags! {
pub trait Resolver {
fn next_node_id(&mut self) -> NodeId;
fn get_module_scope(&mut self, id: NodeId) -> ExpnId;
fn resolve_dollar_crates(&mut self);
fn visit_ast_fragment_with_placeholders(&mut self, expn_id: ExpnId, fragment: &AstFragment,
extra_placeholders: &[NodeId]);

View File

@ -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 {

View File

@ -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,29 @@ 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 span = cx.resolver.span_for_ast_pass(
DUMMY_SP,
AstPass::ProcMacroHarness,
&[sym::rustc_attrs, sym::proc_macro_internals],
None,
);
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 +377,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 +388,44 @@ 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 = P(ast::Expr {
id: ast::DUMMY_NODE_ID,
attrs: syntax::ThinVec::new(),
node: ast::ExprKind::Block(P(ast::Block {
id: ast::DUMMY_NODE_ID,
rules: ast::BlockCheckMode::Default,
stmts: vec![
ast::Stmt {
id: ast::DUMMY_NODE_ID,
node: ast::StmtKind::Item(krate),
span,
},
ast::Stmt {
id: ast::DUMMY_NODE_ID,
node: ast::StmtKind::Item(decls_static),
span,
}
],
span,
}), None),
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
});
let anon_constant = cx.item_const(
span,
ast::Ident::new(kw::Underscore, span),
P(ast::Ty {
id: ast::DUMMY_NODE_ID,
node: ast::TyKind::Tup(Vec::new()),
span,
}),
block,
);
// 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 items = AstFragment::Items(smallvec![anon_constant]);
cx.monotonic_expander().fully_expand_fragment(items).make_items().pop().unwrap()
}

View File

@ -1,52 +1,56 @@
use syntax::{ast, attr};
use syntax::edition::Edition;
use syntax::ext::hygiene::MacroKind;
use syntax::ext::hygiene::AstPass;
use syntax::ext::base::Resolver;
use syntax::ptr::P;
use syntax::source_map::{ExpnData, ExpnKind, dummy_spanned, respan};
use syntax::source_map::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,
alt_std_name: Option<Symbol>,
edition: Edition,
) -> (ast::Crate, Option<Symbol>) {
let rust_2018 = 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 span = resolver.span_for_ast_pass(
DUMMY_SP,
AstPass::StdImports,
&[sym::prelude_import],
None,
);
// .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);
for &orig_name_sym in names.iter().rev() {
let (rename, orig_name) = if rust_2018 {
(orig_name_ident.gensym(), Some(orig_name_sym))
(Ident::new(kw::Underscore, span), Some(orig_name_sym))
} else {
(orig_name_ident, None)
(Ident::with_dummy_span(orig_name_sym), None)
};
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))
attr::mk_word_item(ast::Ident::new(sym::macro_use, span))
)],
vis: dummy_spanned(ast::VisibilityKind::Inherited),
vis: respan(span, ast::VisibilityKind::Inherited),
node: ast::ItemKind::ExternCrate(alt_std_name.or(orig_name)),
ident: rename,
id: ast::DUMMY_NODE_ID,
span: DUMMY_SP,
span,
tokens: None,
}));
}
@ -55,24 +59,22 @@ pub fn inject(
// 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 segments = if rust_2018 {
[name, sym::prelude, sym::v1].iter()
.map(|symbol| ast::PathSegment::from_ident(ast::Ident::new(*symbol, span)))
.collect()
} else {
[kw::PathRoot, name, sym::prelude, sym::v1].iter()
.map(|symbol| ast::PathSegment::from_ident(ast::Ident::with_dummy_span(*symbol)))
.collect()
};
krate.module.items.insert(0, P(ast::Item {
let use_item = 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,
},
prefix: ast::Path { segments, span },
kind: ast::UseTreeKind::Glob,
span,
})),
@ -80,7 +82,65 @@ pub fn inject(
ident: ast::Ident::invalid(),
span,
tokens: None,
}));
});
(krate, Some(Symbol::intern(name)))
let prelude_import_item = if rust_2018 {
let hygienic_extern_crate = P(ast::Item {
attrs: vec![],
vis: respan(span, ast::VisibilityKind::Inherited),
node: ast::ItemKind::ExternCrate(alt_std_name),
ident: ast::Ident::new(name, span),
id: ast::DUMMY_NODE_ID,
span,
tokens: None,
});
// Use an anonymous const to hide `extern crate std as hygienic_std`
// FIXME: Once inter-crate hygiene exists, this can just be `use_item`.
P(ast::Item {
attrs: Vec::new(),
vis: respan(span.shrink_to_lo(), ast::VisibilityKind::Inherited),
node: ast::ItemKind::Const(
P(ast::Ty {
id: ast::DUMMY_NODE_ID,
node: ast::TyKind::Tup(Vec::new()),
span,
}),
P(ast::Expr {
id: ast::DUMMY_NODE_ID,
attrs: syntax::ThinVec::new(),
node: ast::ExprKind::Block(P(ast::Block {
id: ast::DUMMY_NODE_ID,
rules: ast::BlockCheckMode::Default,
stmts: vec![
ast::Stmt {
id: ast::DUMMY_NODE_ID,
node: ast::StmtKind::Item(use_item),
span,
},
ast::Stmt {
id: ast::DUMMY_NODE_ID,
node: ast::StmtKind::Item(hygienic_extern_crate),
span,
}
],
span,
}), None),
span,
})
),
id: ast::DUMMY_NODE_ID,
ident: ast::Ident::new(kw::Underscore, span),
span,
tokens: None,
})
} else {
// Have `extern crate std` at the root, so don't need to create a named
// extern crate item.
use_item
};
krate.module.items.insert(0, prelude_import_item);
(krate, Some(name))
}

View File

@ -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),

View File

@ -5,32 +5,29 @@ 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>,
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 +40,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 +53,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 +71,46 @@ 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 sp = self.cx.ext_cx.resolver.span_for_ast_pass(
module.inner,
AstPass::TestHarness,
&[],
Some(parent),
);
let expn = sp.ctxt().outer_expn();
for test in &mut tests {
test.ident.span = test.ident.span.apply_mark(expn, 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)]
}
@ -181,59 +172,11 @@ 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
@ -244,19 +187,15 @@ fn generate_test_harness(sess: &ParseSess,
econfig.features = Some(features);
let cx = TestCtxt {
span_diagnostic: sd,
ext_cx: ExtCtxt::new(sess, econfig, resolver),
path: Vec::new(),
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);
}
@ -268,12 +207,14 @@ fn mk_main(cx: &mut TestCtxt<'_>) -> P<ast::Item> {
// #![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.ext_cx.resolver.span_for_ast_pass(
DUMMY_SP,
AstPass::TestHarness,
&[sym::main, sym::test, sym::rustc_attrs],
None,
);
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(
@ -285,14 +226,14 @@ fn mk_main(cx: &mut TestCtxt<'_>) -> P<ast::Item> {
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![],
@ -316,8 +257,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 +266,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 +276,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)
}

View File

@ -0,0 +1 @@
pub fn not_in_lib_std() {}

View File

@ -0,0 +1,29 @@
// Make sure that attribute used when injecting the prelude are resolved
// hygienically.
// check-pass
// aux-build:not-libstd.rs
//revisions: rust2015 rust2018
//[rust2018] edition:2018
// The prelude import shouldn't see these as candidates for when it's trying to
// use the built-in macros.
extern crate core;
use core::prelude::v1::test as prelude_import;
use core::prelude::v1::test as macro_use;
// Should not be used for the prelude import - not a concern in the 2015 edition
// because `std` is already declared in the crate root.
#[cfg(rust2018)]
extern crate not_libstd as std;
#[cfg(rust2018)]
mod x {
// The extern crate item should override `std` in the extern prelude.
fn f() {
std::not_in_lib_std();
}
}
fn main() {}

View File

@ -1,7 +1,9 @@
// build-pass (FIXME(62277): could be check-pass?)
// check-pass
// edition:2018
// aux-build:gensymed.rs
extern crate gensymed;
use gensymed::*;
fn main() {}

View File

@ -1,21 +0,0 @@
error[E0432]: unresolved import `__test`
--> $DIR/inaccessible-test-modules.rs:5:5
|
LL | use __test as x;
| ------^^^^^
| |
| no `__test` in the root
| help: a similar name exists in the module: `test`
error[E0432]: unresolved import `__test_reexports`
--> $DIR/inaccessible-test-modules.rs:6:5
|
LL | use __test_reexports as y;
| ----------------^^^^^
| |
| no `__test_reexports` in the root
| help: a similar name exists in the module: `__test_reexports`
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0432`.

View File

@ -0,0 +1,22 @@
// Check that declarative macros can declare tests
// check-pass
// compile-flags: --test
#![feature(decl_macro)]
macro create_test() {
#[test]
fn test() {}
}
macro create_module_test() {
mod x {
#[test]
fn test() {}
}
}
create_test!();
create_test!();
create_module_test!();

View File

@ -2,8 +2,8 @@
// the `--test` harness creates modules with these textual names, but
// they should be inaccessible from normal code.
use __test as x; //~ ERROR unresolved import `__test`
use __test_reexports as y; //~ ERROR unresolved import `__test_reexports`
use main as x; //~ ERROR unresolved import `main`
use test as y; //~ ERROR unresolved import `test`
#[test]
fn baz() {}

View File

@ -0,0 +1,21 @@
error[E0432]: unresolved import `main`
--> $DIR/inaccessible-test-modules.rs:5:5
|
LL | use main as x;
| ----^^^^^
| |
| no `main` in the root
| help: a similar name exists in the module: `main`
error[E0432]: unresolved import `test`
--> $DIR/inaccessible-test-modules.rs:6:5
|
LL | use test as y;
| ----^^^^^
| |
| no `test` in the root
| help: a similar name exists in the module: `test`
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0432`.