mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-23 15:23:46 +00:00
Make use of hygiene in AST passes
This commit is contained in:
parent
4082cd95a8
commit
6fcdb36ccb
@ -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()
|
||||
);
|
||||
|
@ -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));
|
||||
|
@ -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]);
|
||||
|
@ -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,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()
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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,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)
|
||||
}
|
||||
|
1
src/test/ui/hygiene/auxiliary/not-libstd.rs
Normal file
1
src/test/ui/hygiene/auxiliary/not-libstd.rs
Normal file
@ -0,0 +1 @@
|
||||
pub fn not_in_lib_std() {}
|
29
src/test/ui/hygiene/prelude-import-hygiene.rs
Normal file
29
src/test/ui/hygiene/prelude-import-hygiene.rs
Normal 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() {}
|
@ -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() {}
|
||||
|
@ -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`.
|
22
src/test/ui/test-attrs/decl-macro-test.rs
Normal file
22
src/test/ui/test-attrs/decl-macro-test.rs
Normal 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!();
|
@ -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() {}
|
21
src/test/ui/test-attrs/inaccessible-test-modules.stderr
Normal file
21
src/test/ui/test-attrs/inaccessible-test-modules.stderr
Normal 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`.
|
Loading…
Reference in New Issue
Block a user