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:
Mazdak Farrokhzad 2019-09-07 08:06:04 +02:00 committed by GitHub
commit db493ef613
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 554 additions and 438 deletions

View File

@ -390,9 +390,17 @@ impl_stable_hash_for!(struct ::syntax_pos::hygiene::ExpnData {
impl_stable_hash_for!(enum ::syntax_pos::hygiene::ExpnKind {
Root,
Macro(kind, descr),
AstPass(kind),
Desugaring(kind)
});
impl_stable_hash_for!(enum ::syntax_pos::hygiene::AstPass {
StdImports,
TestHarness,
ProcMacroHarness,
PluginMacroDefs,
});
impl_stable_hash_for!(enum ::syntax_pos::hygiene::DesugaringKind {
CondTemporary,
Async,

View File

@ -646,6 +646,30 @@ pub fn struct_lint_level<'a>(sess: &'a Session,
(Level::Forbid, None) => sess.struct_err(msg),
};
// Check for future incompatibility lints and issue a stronger warning.
let lints = sess.lint_store.borrow();
let lint_id = LintId::of(lint);
let future_incompatible = lints.future_incompatible(lint_id);
// If this code originates in a foreign macro, aka something that this crate
// did not itself author, then it's likely that there's nothing this crate
// can do about it. We probably want to skip the lint entirely.
if err.span.primary_spans().iter().any(|s| in_external_macro(sess, *s)) {
// Any suggestions made here are likely to be incorrect, so anything we
// emit shouldn't be automatically fixed by rustfix.
err.allow_suggestions(false);
// If this is a future incompatible lint it'll become a hard error, so
// we have to emit *something*. Also allow lints to whitelist themselves
// on a case-by-case basis for emission in a foreign macro.
if future_incompatible.is_none() && !lint.report_in_external_macro {
err.cancel();
// Don't continue further, since we don't want to have
// `diag_span_note_once` called for a diagnostic that isn't emitted.
return err;
}
}
let name = lint.name_lower();
match src {
LintSource::Default => {
@ -695,10 +719,6 @@ pub fn struct_lint_level<'a>(sess: &'a Session,
err.code(DiagnosticId::Lint(name));
// Check for future incompatibility lints and issue a stronger warning.
let lints = sess.lint_store.borrow();
let lint_id = LintId::of(lint);
let future_incompatible = lints.future_incompatible(lint_id);
if let Some(future_incompatible) = future_incompatible {
const STANDARD_MESSAGE: &str =
"this was previously accepted by the compiler but is being phased out; \
@ -723,22 +743,6 @@ pub fn struct_lint_level<'a>(sess: &'a Session,
err.note(&citation);
}
// If this code originates in a foreign macro, aka something that this crate
// did not itself author, then it's likely that there's nothing this crate
// can do about it. We probably want to skip the lint entirely.
if err.span.primary_spans().iter().any(|s| in_external_macro(sess, *s)) {
// Any suggestions made here are likely to be incorrect, so anything we
// emit shouldn't be automatically fixed by rustfix.
err.allow_suggestions(false);
// If this is a future incompatible lint it'll become a hard error, so
// we have to emit *something*. Also allow lints to whitelist themselves
// on a case-by-case basis for emission in a foreign macro.
if future_incompatible.is_none() && !lint.report_in_external_macro {
err.cancel()
}
}
return err
}
@ -868,7 +872,7 @@ pub fn in_external_macro(sess: &Session, span: Span) -> bool {
let expn_data = span.ctxt().outer_expn_data();
match expn_data.kind {
ExpnKind::Root | ExpnKind::Desugaring(DesugaringKind::ForLoop) => false,
ExpnKind::Desugaring(_) => true, // well, it's "external"
ExpnKind::AstPass(_) | ExpnKind::Desugaring(_) => true, // well, it's "external"
ExpnKind::Macro(MacroKind::Bang, _) => {
if expn_data.def_site.is_dummy() {
// dummy span for the def_site means it's an external macro

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,
&sess.parse_sess,
alt_std_name,
);
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

@ -126,7 +126,8 @@ impl<'a> Resolver<'a> {
crate fn macro_def_scope(&mut self, expn_id: ExpnId) -> Module<'a> {
let def_id = match self.macro_defs.get(&expn_id) {
Some(def_id) => *def_id,
None => return self.graph_root,
None => return self.ast_transform_scopes.get(&expn_id)
.unwrap_or(&self.graph_root),
};
if let Some(id) = self.definitions.as_local_node_id(def_id) {
self.local_macro_def_scopes[&id]

View File

@ -604,6 +604,14 @@ impl<'a> Resolver<'a> {
if lookup_ident.span.rust_2018() {
let extern_prelude_names = self.extern_prelude.clone();
for (ident, _) in extern_prelude_names.into_iter() {
if ident.span.from_expansion() {
// Idents are adjusted to the root context before being
// resolved in the extern prelude, so reporting this to the
// user is no help. This skips the injected
// `extern crate std` in the 2018 edition, which would
// otherwise cause duplicate suggestions.
continue;
}
if let Some(crate_id) = self.crate_loader.maybe_process_path_extern(ident.name,
ident.span) {
let crate_root = self.get_module(DefId {

View File

@ -880,6 +880,10 @@ pub struct Resolver<'a> {
/// There will be an anonymous module created around `g` with the ID of the
/// entry block for `f`.
block_map: NodeMap<Module<'a>>,
/// A fake module that contains no definition and no prelude. Used so that
/// some AST passes can generate identifiers that only resolve to local or
/// language items.
empty_module: Module<'a>,
module_map: FxHashMap<DefId, Module<'a>>,
extern_module_map: FxHashMap<(DefId, bool /* MacrosOnly? */), Module<'a>>,
binding_parent_modules: FxHashMap<PtrKey<'a, NameBinding<'a>>, Module<'a>>,
@ -914,6 +918,7 @@ pub struct Resolver<'a> {
non_macro_attrs: [Lrc<SyntaxExtension>; 2],
macro_defs: FxHashMap<ExpnId, DefId>,
local_macro_def_scopes: FxHashMap<NodeId, Module<'a>>,
ast_transform_scopes: FxHashMap<ExpnId, Module<'a>>,
unused_macros: NodeMap<Span>,
proc_macro_stubs: NodeSet,
/// Traces collected during macro resolution and validated when it's complete.
@ -1081,6 +1086,21 @@ impl<'a> Resolver<'a> {
no_implicit_prelude: attr::contains_name(&krate.attrs, sym::no_implicit_prelude),
..ModuleData::new(None, root_module_kind, root_def_id, ExpnId::root(), krate.span)
});
let empty_module_kind = ModuleKind::Def(
DefKind::Mod,
root_def_id,
kw::Invalid,
);
let empty_module = arenas.alloc_module(ModuleData {
no_implicit_prelude: true,
..ModuleData::new(
Some(graph_root),
empty_module_kind,
root_def_id,
ExpnId::root(),
DUMMY_SP,
)
});
let mut module_map = FxHashMap::default();
module_map.insert(DefId::local(CRATE_DEF_INDEX), graph_root);
@ -1140,10 +1160,12 @@ impl<'a> Resolver<'a> {
label_res_map: Default::default(),
export_map: FxHashMap::default(),
trait_map: Default::default(),
empty_module,
module_map,
block_map: Default::default(),
extern_module_map: FxHashMap::default(),
binding_parent_modules: FxHashMap::default(),
ast_transform_scopes: FxHashMap::default(),
glob_map: Default::default(),

View File

@ -8,6 +8,7 @@ use crate::{ModuleOrUniformRoot, KNOWN_TOOLS};
use crate::Namespace::*;
use crate::resolve_imports::ImportResolver;
use rustc::hir::def::{self, DefKind, NonMacroAttrKind};
use rustc::hir::def_id;
use rustc::middle::stability;
use rustc::{ty, lint, span_bug};
use syntax::ast::{self, NodeId, Ident};
@ -25,6 +26,7 @@ use syntax_pos::{Span, DUMMY_SP};
use std::{mem, ptr};
use rustc_data_structures::sync::Lrc;
use syntax_pos::hygiene::AstPass;
type Res = def::Res<NodeId>;
@ -95,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));
@ -136,6 +128,37 @@ impl<'a> base::Resolver for Resolver<'a> {
}
}
// Create a new Expansion with a definition site of the provided module, or
// a fake empty `#[no_implicit_prelude]` module if no module is provided.
fn expansion_for_ast_pass(
&mut self,
call_site: Span,
pass: AstPass,
features: &[Symbol],
parent_module_id: Option<NodeId>,
) -> ExpnId {
let expn_id = ExpnId::fresh(Some(ExpnData::allow_unstable(
ExpnKind::AstPass(pass),
call_site,
self.session.edition(),
features.into(),
)));
let parent_scope = if let Some(module_id) = parent_module_id {
let parent_def_id = self.definitions.local_def_id(module_id);
self.definitions.add_parent_module_of_macro_def(expn_id, parent_def_id);
self.module_map[&parent_def_id]
} else {
self.definitions.add_parent_module_of_macro_def(
expn_id,
def_id::DefId::local(def_id::CRATE_DEF_INDEX),
);
self.empty_module
};
self.ast_transform_scopes.insert(expn_id, parent_scope);
expn_id
}
fn resolve_imports(&mut self) {
ImportResolver { r: self }.resolve_imports()
}

View File

@ -1307,12 +1307,11 @@ impl<'a, 'b> ImportResolver<'a, 'b> {
None => continue,
};
// Filter away ambiguous and gensymed imports. Gensymed imports
// (e.g. implicitly injected `std`) cannot be properly encoded in metadata,
// so they can cause name conflict errors downstream.
let is_good_import = binding.is_import() && !binding.is_ambiguity() &&
// Note that as_str() de-gensyms the Symbol
!(ident.is_gensymed() && ident.name.as_str() != "_");
// Filter away ambiguous imports and anything that has def-site
// hygiene.
// FIXME: Implement actual cross-crate hygiene.
let is_good_import = binding.is_import() && !binding.is_ambiguity()
&& !ident.span.modern().from_expansion();
if is_good_import || binding.is_macro_def() {
let res = binding.res();
if res != Res::Err {

View File

@ -3,7 +3,7 @@ use crate::attr::{self, HasAttrs, Stability, Deprecation};
use crate::source_map::SourceMap;
use crate::edition::Edition;
use crate::ext::expand::{self, AstFragment, Invocation};
use crate::ext::hygiene::{ExpnId, Transparency};
use crate::ext::hygiene::ExpnId;
use crate::mut_visit::{self, MutVisitor};
use crate::parse::{self, parser, ParseSess, DirectoryOwnership};
use crate::parse::token;
@ -16,7 +16,7 @@ use crate::visit::Visitor;
use errors::{DiagnosticBuilder, DiagnosticId};
use smallvec::{smallvec, SmallVec};
use syntax_pos::{FileName, Span, MultiSpan, DUMMY_SP};
use syntax_pos::hygiene::{ExpnData, ExpnKind};
use syntax_pos::hygiene::{AstPass, ExpnData, ExpnKind};
use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::sync::{self, Lrc};
@ -732,13 +732,19 @@ 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]);
fn register_builtin_macro(&mut self, ident: ast::Ident, ext: SyntaxExtension);
fn expansion_for_ast_pass(
&mut self,
call_site: Span,
pass: AstPass,
features: &[Symbol],
parent_module_id: Option<NodeId>,
) -> ExpnId;
fn resolve_imports(&mut self);
fn resolve_macro_invocation(
@ -822,20 +828,20 @@ impl<'a> ExtCtxt<'a> {
/// Equivalent of `Span::def_site` from the proc macro API,
/// except that the location is taken from the span passed as an argument.
pub fn with_def_site_ctxt(&self, span: Span) -> Span {
span.with_ctxt_from_mark(self.current_expansion.id, Transparency::Opaque)
span.with_def_site_ctxt(self.current_expansion.id)
}
/// Equivalent of `Span::call_site` from the proc macro API,
/// except that the location is taken from the span passed as an argument.
pub fn with_call_site_ctxt(&self, span: Span) -> Span {
span.with_ctxt_from_mark(self.current_expansion.id, Transparency::Transparent)
span.with_call_site_ctxt(self.current_expansion.id)
}
/// Span with a context reproducing `macro_rules` hygiene (hygienic locals, unhygienic items).
/// FIXME: This should be eventually replaced either with `with_def_site_ctxt` (preferably),
/// or with `with_call_site_ctxt` (where necessary).
pub fn with_legacy_ctxt(&self, span: Span) -> Span {
span.with_ctxt_from_mark(self.current_expansion.id, Transparency::SemiTransparent)
span.with_legacy_ctxt(self.current_expansion.id)
}
/// Returns span for the macro which originally caused the current expansion to happen.

View File

@ -128,10 +128,14 @@ pub fn print_crate<'a>(cm: &'a SourceMap,
let fake_attr = attr::mk_attr_inner(list);
s.print_attribute(&fake_attr);
// #![no_std]
let no_std_meta = attr::mk_word_item(ast::Ident::with_dummy_span(sym::no_std));
let fake_attr = attr::mk_attr_inner(no_std_meta);
s.print_attribute(&fake_attr);
// Currently on Rust 2018 we don't have `extern crate std;` at the crate
// root, so this is not needed, and actually breaks things.
if sess.edition == syntax_pos::edition::Edition::Edition2015 {
// #![no_std]
let no_std_meta = attr::mk_word_item(ast::Ident::with_dummy_span(sym::no_std));
let fake_attr = attr::mk_attr_inner(no_std_meta);
s.print_attribute(&fake_attr);
}
}
s.print_mod(&krate.module, &krate.attrs);

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,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()
}

View File

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

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

View File

@ -360,7 +360,7 @@ impl SyntaxContext {
}
/// Extend a syntax context with a given expansion and transparency.
pub fn apply_mark(self, expn_id: ExpnId, transparency: Transparency) -> SyntaxContext {
crate fn apply_mark(self, expn_id: ExpnId, transparency: Transparency) -> SyntaxContext {
HygieneData::with(|data| data.apply_mark(self, expn_id, transparency))
}
@ -550,7 +550,7 @@ impl Span {
/// The returned span belongs to the created expansion and has the new properties,
/// but its location is inherited from the current span.
pub fn fresh_expansion(self, expn_data: ExpnData) -> Span {
self.fresh_expansion_with_transparency(expn_data, Transparency::SemiTransparent)
self.fresh_expansion_with_transparency(expn_data, Transparency::Transparent)
}
pub fn fresh_expansion_with_transparency(
@ -639,8 +639,9 @@ pub enum ExpnKind {
/// No expansion, aka root expansion. Only `ExpnId::root()` has this kind.
Root,
/// Expansion produced by a macro.
/// FIXME: Some code injected by the compiler before HIR lowering also gets this kind.
Macro(MacroKind, Symbol),
/// Transform done by the compiler on the AST.
AstPass(AstPass),
/// Desugaring done by the compiler during HIR lowering.
Desugaring(DesugaringKind)
}
@ -650,6 +651,7 @@ impl ExpnKind {
match *self {
ExpnKind::Root => kw::PathRoot,
ExpnKind::Macro(_, descr) => descr,
ExpnKind::AstPass(kind) => Symbol::intern(kind.descr()),
ExpnKind::Desugaring(kind) => Symbol::intern(kind.descr()),
}
}
@ -683,6 +685,26 @@ impl MacroKind {
}
}
/// The kind of AST transform.
#[derive(Clone, Copy, PartialEq, Debug, RustcEncodable, RustcDecodable)]
pub enum AstPass {
StdImports,
TestHarness,
ProcMacroHarness,
PluginMacroDefs,
}
impl AstPass {
fn descr(self) -> &'static str {
match self {
AstPass::StdImports => "standard library imports",
AstPass::TestHarness => "test harness",
AstPass::ProcMacroHarness => "proc macro harness",
AstPass::PluginMacroDefs => "plugin macro definitions",
}
}
}
/// The kind of compiler desugaring.
#[derive(Clone, Copy, PartialEq, Debug, RustcEncodable, RustcDecodable)]
pub enum DesugaringKind {

View File

@ -442,6 +442,7 @@ impl Span {
let (pre, post) = match expn_data.kind {
ExpnKind::Root => break,
ExpnKind::Desugaring(..) => ("desugaring of ", ""),
ExpnKind::AstPass(..) => ("", ""),
ExpnKind::Macro(macro_kind, _) => match macro_kind {
MacroKind::Bang => ("", "!"),
MacroKind::Attr => ("#[", "]"),
@ -513,6 +514,25 @@ impl Span {
span.ctxt)
}
/// Equivalent of `Span::def_site` from the proc macro API,
/// except that the location is taken from the `self` span.
pub fn with_def_site_ctxt(self, expn_id: ExpnId) -> Span {
self.with_ctxt_from_mark(expn_id, Transparency::Opaque)
}
/// Equivalent of `Span::call_site` from the proc macro API,
/// except that the location is taken from the `self` span.
pub fn with_call_site_ctxt(&self, expn_id: ExpnId) -> Span {
self.with_ctxt_from_mark(expn_id, Transparency::Transparent)
}
/// Span with a context reproducing `macro_rules` hygiene (hygienic locals, unhygienic items).
/// FIXME: This should be eventually replaced either with `with_def_site_ctxt` (preferably),
/// or with `with_call_site_ctxt` (where necessary).
pub fn with_legacy_ctxt(&self, expn_id: ExpnId) -> Span {
self.with_ctxt_from_mark(expn_id, Transparency::SemiTransparent)
}
/// Produces a span with the same location as `self` and context produced by a macro with the
/// given ID and transparency, assuming that macro was defined directly and not produced by
/// some other macro (which is the case for built-in and procedural macros).

View File

@ -801,21 +801,15 @@ impl Ident {
Ident::new(self.name, self.span.modern_and_legacy())
}
/// Transforms an identifier into one with the same name, but gensymed.
pub fn gensym(self) -> Ident {
let name = with_interner(|interner| interner.gensymed(self.name));
Ident::new(name, self.span)
}
/// Transforms an underscore identifier into one with the same name, but
/// gensymed. Leaves non-underscore identifiers unchanged.
pub fn gensym_if_underscore(self) -> Ident {
if self.name == kw::Underscore { self.gensym() } else { self }
}
// WARNING: this function is deprecated and will be removed in the future.
pub fn is_gensymed(self) -> bool {
with_interner(|interner| interner.is_gensymed(self.name))
if self.name == kw::Underscore {
let name = with_interner(|interner| interner.gensymed(self.name));
Ident::new(name, self.span)
} else {
self
}
}
/// Convert the name to a `LocalInternedString`. This is a slowish
@ -892,9 +886,12 @@ impl UseSpecializedDecodable for Ident {
///
/// Examples:
/// ```
/// assert_eq!(Ident::from_str("x"), Ident::from_str("x"))
/// assert_ne!(Ident::from_str("x").gensym(), Ident::from_str("x"))
/// assert_ne!(Ident::from_str("x").gensym(), Ident::from_str("x").gensym())
/// assert_eq!(Ident::from_str("_"), Ident::from_str("_"))
/// assert_ne!(Ident::from_str("_").gensym_if_underscore(), Ident::from_str("_"))
/// assert_ne!(
/// Ident::from_str("_").gensym_if_underscore(),
/// Ident::from_str("_").gensym_if_underscore(),
/// )
/// ```
/// Internally, a symbol is implemented as an index, and all operations
/// (including hashing, equality, and ordering) operate on that index. The use

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

@ -2,40 +2,40 @@ PRINT-BANG INPUT (DISPLAY): struct M ($crate :: S) ;
PRINT-BANG INPUT (DEBUG): TokenStream [
Ident {
ident: "struct",
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
Ident {
ident: "M",
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
ident: "$crate",
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
Punct {
ch: ':',
spacing: Joint,
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
Punct {
ch: ':',
spacing: Alone,
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
Ident {
ident: "S",
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
],
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
Punct {
ch: ';',
spacing: Alone,
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
]
PRINT-ATTR INPUT (DISPLAY): struct A(crate::S);
@ -43,39 +43,39 @@ PRINT-ATTR RE-COLLECTED (DISPLAY): struct A ($crate :: S) ;
PRINT-ATTR INPUT (DEBUG): TokenStream [
Ident {
ident: "struct",
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
Ident {
ident: "A",
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
ident: "$crate",
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
Punct {
ch: ':',
spacing: Joint,
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
Punct {
ch: ':',
spacing: Alone,
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
Ident {
ident: "S",
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
],
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
Punct {
ch: ';',
spacing: Alone,
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
]

View File

@ -3,55 +3,55 @@ PRINT-ATTR RE-COLLECTED (DISPLAY): struct A (identity ! ($crate :: S)) ;
PRINT-ATTR INPUT (DEBUG): TokenStream [
Ident {
ident: "struct",
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
Ident {
ident: "A",
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
ident: "identity",
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
Punct {
ch: '!',
spacing: Alone,
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
ident: "$crate",
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
Punct {
ch: ':',
spacing: Joint,
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
Punct {
ch: ':',
spacing: Alone,
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
Ident {
ident: "S",
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
],
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
],
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
Punct {
ch: ';',
spacing: Alone,
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
]
PRINT-ATTR INPUT (DISPLAY): struct B(identity!(::dollar_crate_external :: S));
@ -59,54 +59,54 @@ PRINT-ATTR RE-COLLECTED (DISPLAY): struct B (identity ! ($crate :: S)) ;
PRINT-ATTR INPUT (DEBUG): TokenStream [
Ident {
ident: "struct",
span: #7 bytes(LO..HI),
span: #8 bytes(LO..HI),
},
Ident {
ident: "B",
span: #7 bytes(LO..HI),
span: #8 bytes(LO..HI),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
ident: "identity",
span: #7 bytes(LO..HI),
span: #8 bytes(LO..HI),
},
Punct {
ch: '!',
spacing: Alone,
span: #7 bytes(LO..HI),
span: #8 bytes(LO..HI),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
ident: "$crate",
span: #7 bytes(LO..HI),
span: #8 bytes(LO..HI),
},
Punct {
ch: ':',
spacing: Joint,
span: #7 bytes(LO..HI),
span: #8 bytes(LO..HI),
},
Punct {
ch: ':',
spacing: Alone,
span: #7 bytes(LO..HI),
span: #8 bytes(LO..HI),
},
Ident {
ident: "S",
span: #7 bytes(LO..HI),
span: #8 bytes(LO..HI),
},
],
span: #7 bytes(LO..HI),
span: #8 bytes(LO..HI),
},
],
span: #7 bytes(LO..HI),
span: #8 bytes(LO..HI),
},
Punct {
ch: ';',
spacing: Alone,
span: #7 bytes(LO..HI),
span: #8 bytes(LO..HI),
},
]

View File

@ -2,40 +2,40 @@ PRINT-BANG INPUT (DISPLAY): struct M ($crate :: S) ;
PRINT-BANG INPUT (DEBUG): TokenStream [
Ident {
ident: "struct",
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
Ident {
ident: "M",
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
ident: "$crate",
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
Punct {
ch: ':',
spacing: Joint,
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
Punct {
ch: ':',
spacing: Alone,
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
Ident {
ident: "S",
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
],
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
Punct {
ch: ';',
spacing: Alone,
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
]
PRINT-ATTR INPUT (DISPLAY): struct A(crate::S);
@ -43,40 +43,40 @@ PRINT-ATTR RE-COLLECTED (DISPLAY): struct A ($crate :: S) ;
PRINT-ATTR INPUT (DEBUG): TokenStream [
Ident {
ident: "struct",
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
Ident {
ident: "A",
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
ident: "$crate",
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
Punct {
ch: ':',
spacing: Joint,
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
Punct {
ch: ':',
spacing: Alone,
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
Ident {
ident: "S",
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
],
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
Punct {
ch: ';',
spacing: Alone,
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
]
PRINT-DERIVE INPUT (DISPLAY): struct D(crate::S);
@ -84,80 +84,80 @@ PRINT-DERIVE RE-COLLECTED (DISPLAY): struct D ($crate :: S) ;
PRINT-DERIVE INPUT (DEBUG): TokenStream [
Ident {
ident: "struct",
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
Ident {
ident: "D",
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
ident: "$crate",
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
Punct {
ch: ':',
spacing: Joint,
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
Punct {
ch: ':',
spacing: Alone,
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
Ident {
ident: "S",
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
],
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
Punct {
ch: ';',
spacing: Alone,
span: #2 bytes(LO..HI),
span: #3 bytes(LO..HI),
},
]
PRINT-BANG INPUT (DISPLAY): struct M ($crate :: S) ;
PRINT-BANG INPUT (DEBUG): TokenStream [
Ident {
ident: "struct",
span: #9 bytes(LO..HI),
span: #10 bytes(LO..HI),
},
Ident {
ident: "M",
span: #9 bytes(LO..HI),
span: #10 bytes(LO..HI),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
ident: "$crate",
span: #9 bytes(LO..HI),
span: #10 bytes(LO..HI),
},
Punct {
ch: ':',
spacing: Joint,
span: #9 bytes(LO..HI),
span: #10 bytes(LO..HI),
},
Punct {
ch: ':',
spacing: Alone,
span: #9 bytes(LO..HI),
span: #10 bytes(LO..HI),
},
Ident {
ident: "S",
span: #9 bytes(LO..HI),
span: #10 bytes(LO..HI),
},
],
span: #9 bytes(LO..HI),
span: #10 bytes(LO..HI),
},
Punct {
ch: ';',
spacing: Alone,
span: #9 bytes(LO..HI),
span: #10 bytes(LO..HI),
},
]
PRINT-ATTR INPUT (DISPLAY): struct A(::dollar_crate_external::S);
@ -165,40 +165,40 @@ PRINT-ATTR RE-COLLECTED (DISPLAY): struct A ($crate :: S) ;
PRINT-ATTR INPUT (DEBUG): TokenStream [
Ident {
ident: "struct",
span: #9 bytes(LO..HI),
span: #10 bytes(LO..HI),
},
Ident {
ident: "A",
span: #9 bytes(LO..HI),
span: #10 bytes(LO..HI),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
ident: "$crate",
span: #9 bytes(LO..HI),
span: #10 bytes(LO..HI),
},
Punct {
ch: ':',
spacing: Joint,
span: #9 bytes(LO..HI),
span: #10 bytes(LO..HI),
},
Punct {
ch: ':',
spacing: Alone,
span: #9 bytes(LO..HI),
span: #10 bytes(LO..HI),
},
Ident {
ident: "S",
span: #9 bytes(LO..HI),
span: #10 bytes(LO..HI),
},
],
span: #9 bytes(LO..HI),
span: #10 bytes(LO..HI),
},
Punct {
ch: ';',
spacing: Alone,
span: #9 bytes(LO..HI),
span: #10 bytes(LO..HI),
},
]
PRINT-DERIVE INPUT (DISPLAY): struct D(::dollar_crate_external::S);
@ -206,39 +206,39 @@ PRINT-DERIVE RE-COLLECTED (DISPLAY): struct D ($crate :: S) ;
PRINT-DERIVE INPUT (DEBUG): TokenStream [
Ident {
ident: "struct",
span: #9 bytes(LO..HI),
span: #10 bytes(LO..HI),
},
Ident {
ident: "D",
span: #9 bytes(LO..HI),
span: #10 bytes(LO..HI),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
ident: "$crate",
span: #9 bytes(LO..HI),
span: #10 bytes(LO..HI),
},
Punct {
ch: ':',
spacing: Joint,
span: #9 bytes(LO..HI),
span: #10 bytes(LO..HI),
},
Punct {
ch: ':',
spacing: Alone,
span: #9 bytes(LO..HI),
span: #10 bytes(LO..HI),
},
Ident {
ident: "S",
span: #9 bytes(LO..HI),
span: #10 bytes(LO..HI),
},
],
span: #9 bytes(LO..HI),
span: #10 bytes(LO..HI),
},
Punct {
ch: ';',
spacing: Alone,
span: #9 bytes(LO..HI),
span: #10 bytes(LO..HI),
},
]

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`.