1
Fork 0

Move #[test_case] to a syntax extension

This commit is contained in:
John Renner 2018-09-02 09:03:24 -07:00
parent e5ed105716
commit 0593dc7e3c
15 changed files with 108 additions and 82 deletions

View file

@ -828,7 +828,6 @@ where
let (mut krate, features) = syntax::config::features( let (mut krate, features) = syntax::config::features(
krate, krate,
&sess.parse_sess, &sess.parse_sess,
sess.opts.test,
sess.edition(), sess.edition(),
); );
// these need to be set "early" so that expansion sees `quote` if enabled. // these need to be set "early" so that expansion sees `quote` if enabled.

View file

@ -1872,7 +1872,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnnameableTestItems {
return; return;
} }
if let Some(attr) = attr::find_by_name(&it.attrs, "test_case") { if let Some(attr) = attr::find_by_name(&it.attrs, "rustc_test_marker") {
cx.struct_span_lint( cx.struct_span_lint(
UNNAMEABLE_TEST_ITEMS, UNNAMEABLE_TEST_ITEMS,
attr.span, attr.span,

View file

@ -479,7 +479,7 @@ impl<'a, 'cl> Resolver<'a, 'cl> {
return def; return def;
} }
if kind == MacroKind::Attr && path.len() == 1 { if kind == MacroKind::Attr {
if let Some(ext) = self.unshadowable_attrs.get(&path[0].name) { if let Some(ext) = self.unshadowable_attrs.get(&path[0].name) {
return Ok(ext.def()); return Ok(ext.def());
} }

View file

@ -21,18 +21,16 @@ use ptr::P;
/// A folder that strips out items that do not belong in the current configuration. /// A folder that strips out items that do not belong in the current configuration.
pub struct StripUnconfigured<'a> { pub struct StripUnconfigured<'a> {
pub should_test: bool,
pub sess: &'a ParseSess, pub sess: &'a ParseSess,
pub features: Option<&'a Features>, pub features: Option<&'a Features>,
} }
// `cfg_attr`-process the crate's attributes and compute the crate's features. // `cfg_attr`-process the crate's attributes and compute the crate's features.
pub fn features(mut krate: ast::Crate, sess: &ParseSess, should_test: bool, edition: Edition) pub fn features(mut krate: ast::Crate, sess: &ParseSess, edition: Edition)
-> (ast::Crate, Features) { -> (ast::Crate, Features) {
let features; let features;
{ {
let mut strip_unconfigured = StripUnconfigured { let mut strip_unconfigured = StripUnconfigured {
should_test,
sess, sess,
features: None, features: None,
}; };
@ -118,11 +116,6 @@ impl<'a> StripUnconfigured<'a> {
// Determine if a node with the given attributes should be included in this configuration. // Determine if a node with the given attributes should be included in this configuration.
pub fn in_cfg(&mut self, attrs: &[ast::Attribute]) -> bool { pub fn in_cfg(&mut self, attrs: &[ast::Attribute]) -> bool {
attrs.iter().all(|attr| { attrs.iter().all(|attr| {
// When not compiling with --test we should not compile the #[test] functions
if !self.should_test && is_test(attr) {
return false;
}
let mis = if !is_cfg(attr) { let mis = if !is_cfg(attr) {
return true; return true;
} else if let Some(mis) = attr.meta_item_list() { } else if let Some(mis) = attr.meta_item_list() {
@ -249,7 +242,7 @@ impl<'a> StripUnconfigured<'a> {
// //
// NB: This is intentionally not part of the fold_expr() function // NB: This is intentionally not part of the fold_expr() function
// in order for fold_opt_expr() to be able to avoid this check // in order for fold_opt_expr() to be able to avoid this check
if let Some(attr) = expr.attrs().iter().find(|a| is_cfg(a) || is_test(a)) { if let Some(attr) = expr.attrs().iter().find(|a| is_cfg(a)) {
let msg = "removing an expression is not supported in this position"; let msg = "removing an expression is not supported in this position";
self.sess.span_diagnostic.span_err(attr.span, msg); self.sess.span_diagnostic.span_err(attr.span, msg);
} }
@ -352,7 +345,3 @@ impl<'a> fold::Folder for StripUnconfigured<'a> {
fn is_cfg(attr: &ast::Attribute) -> bool { fn is_cfg(attr: &ast::Attribute) -> bool {
attr.check_name("cfg") attr.check_name("cfg")
} }
pub fn is_test(att: &ast::Attribute) -> bool {
att.check_name("test_case")
}

View file

@ -450,14 +450,12 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
let (fragment_with_placeholders, invocations) = { let (fragment_with_placeholders, invocations) = {
let mut collector = InvocationCollector { let mut collector = InvocationCollector {
cfg: StripUnconfigured { cfg: StripUnconfigured {
should_test: self.cx.ecfg.should_test,
sess: self.cx.parse_sess, sess: self.cx.parse_sess,
features: self.cx.ecfg.features, features: self.cx.ecfg.features,
}, },
cx: self.cx, cx: self.cx,
invocations: Vec::new(), invocations: Vec::new(),
monotonic: self.monotonic, monotonic: self.monotonic,
tests_nameable: true,
}; };
(fragment.fold_with(&mut collector), collector.invocations) (fragment.fold_with(&mut collector), collector.invocations)
}; };
@ -475,7 +473,6 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
fn fully_configure(&mut self, item: Annotatable) -> Annotatable { fn fully_configure(&mut self, item: Annotatable) -> Annotatable {
let mut cfg = StripUnconfigured { let mut cfg = StripUnconfigured {
should_test: self.cx.ecfg.should_test,
sess: self.cx.parse_sess, sess: self.cx.parse_sess,
features: self.cx.ecfg.features, features: self.cx.ecfg.features,
}; };
@ -1047,11 +1044,6 @@ struct InvocationCollector<'a, 'b: 'a> {
cfg: StripUnconfigured<'a>, cfg: StripUnconfigured<'a>,
invocations: Vec<Invocation>, invocations: Vec<Invocation>,
monotonic: bool, monotonic: bool,
/// Test functions need to be nameable. Tests inside functions or in other
/// unnameable locations need to be ignored. `tests_nameable` tracks whether
/// any test functions found in the current context would be nameable.
tests_nameable: bool,
} }
impl<'a, 'b> InvocationCollector<'a, 'b> { impl<'a, 'b> InvocationCollector<'a, 'b> {
@ -1069,20 +1061,6 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
placeholder(fragment_kind, NodeId::placeholder_from_mark(mark)) placeholder(fragment_kind, NodeId::placeholder_from_mark(mark))
} }
/// Folds the item allowing tests to be expanded because they are still nameable.
/// This should probably only be called with module items
fn fold_nameable(&mut self, item: P<ast::Item>) -> OneVector<P<ast::Item>> {
fold::noop_fold_item(item, self)
}
/// Folds the item but doesn't allow tests to occur within it
fn fold_unnameable(&mut self, item: P<ast::Item>) -> OneVector<P<ast::Item>> {
let was_nameable = mem::replace(&mut self.tests_nameable, false);
let items = fold::noop_fold_item(item, self);
self.tests_nameable = was_nameable;
items
}
fn collect_bang(&mut self, mac: ast::Mac, span: Span, kind: AstFragmentKind) -> AstFragment { fn collect_bang(&mut self, mac: ast::Mac, span: Span, kind: AstFragmentKind) -> AstFragment {
self.collect(kind, InvocationKind::Bang { mac: mac, ident: None, span: span }) self.collect(kind, InvocationKind::Bang { mac: mac, ident: None, span: span })
} }
@ -1297,7 +1275,7 @@ impl<'a, 'b> Folder for InvocationCollector<'a, 'b> {
fn fold_item(&mut self, item: P<ast::Item>) -> OneVector<P<ast::Item>> { fn fold_item(&mut self, item: P<ast::Item>) -> OneVector<P<ast::Item>> {
let item = configure!(self, item); let item = configure!(self, item);
let (attr, traits, mut item) = self.classify_item(item); let (attr, traits, item) = self.classify_item(item);
if attr.is_some() || !traits.is_empty() { if attr.is_some() || !traits.is_empty() {
let item = Annotatable::Item(item); let item = Annotatable::Item(item);
return self.collect_attr(attr, traits, item, AstFragmentKind::Items).make_items(); return self.collect_attr(attr, traits, item, AstFragmentKind::Items).make_items();
@ -1319,7 +1297,7 @@ impl<'a, 'b> Folder for InvocationCollector<'a, 'b> {
} }
ast::ItemKind::Mod(ast::Mod { inner, .. }) => { ast::ItemKind::Mod(ast::Mod { inner, .. }) => {
if item.ident == keywords::Invalid.ident() { if item.ident == keywords::Invalid.ident() {
return self.fold_nameable(item); return noop_fold_item(item, self);
} }
let orig_directory_ownership = self.cx.current_expansion.directory_ownership; let orig_directory_ownership = self.cx.current_expansion.directory_ownership;
@ -1359,32 +1337,13 @@ impl<'a, 'b> Folder for InvocationCollector<'a, 'b> {
let orig_module = let orig_module =
mem::replace(&mut self.cx.current_expansion.module, Rc::new(module)); mem::replace(&mut self.cx.current_expansion.module, Rc::new(module));
let result = self.fold_nameable(item); let result = noop_fold_item(item, self);
self.cx.current_expansion.module = orig_module; self.cx.current_expansion.module = orig_module;
self.cx.current_expansion.directory_ownership = orig_directory_ownership; self.cx.current_expansion.directory_ownership = orig_directory_ownership;
result result
} }
// Ensure that test items can be exported by the harness generator. _ => noop_fold_item(item, self),
// #[test] fn foo() {}
// becomes:
// #[test] pub fn foo_gensym(){}
ast::ItemKind::Const(..)
| ast::ItemKind::Static(..)
| ast::ItemKind::Fn(..) if self.cx.ecfg.should_test => {
if self.tests_nameable && attr::contains_name(&item.attrs, "test_case") {
// Publicize the item under gensymed name to avoid pollution
// This means #[test_case] items can't be referenced by user code
item = item.map(|mut item| {
item.vis = respan(item.vis.span, ast::VisibilityKind::Public);
item.ident = item.ident.gensym();
item
});
}
self.fold_unnameable(item)
}
_ => self.fold_unnameable(item),
} }
} }
@ -1609,6 +1568,7 @@ impl<'feat> ExpansionConfig<'feat> {
feature_tests! { feature_tests! {
fn enable_quotes = quote, fn enable_quotes = quote,
fn enable_asm = asm, fn enable_asm = asm,
fn enable_custom_test_frameworks = custom_test_frameworks,
fn enable_global_asm = global_asm, fn enable_global_asm = global_asm,
fn enable_log_syntax = log_syntax, fn enable_log_syntax = log_syntax,
fn enable_concat_idents = concat_idents, fn enable_concat_idents = concat_idents,

View file

@ -777,10 +777,6 @@ pub const BUILTIN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeG
("no_link", Normal, Ungated), ("no_link", Normal, Ungated),
("derive", Normal, Ungated), ("derive", Normal, Ungated),
("should_panic", Normal, Ungated), ("should_panic", Normal, Ungated),
("test_case", Normal, Gated(Stability::Unstable,
"custom_test_frameworks",
"Custom test frameworks are experimental",
cfg_fn!(custom_test_frameworks))),
("ignore", Normal, Ungated), ("ignore", Normal, Ungated),
("no_implicit_prelude", Normal, Ungated), ("no_implicit_prelude", Normal, Ungated),
("reexport_test_harness_main", Normal, Ungated), ("reexport_test_harness_main", Normal, Ungated),
@ -965,6 +961,11 @@ pub const BUILTIN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeG
attribute is just used for rustc unit \ attribute is just used for rustc unit \
tests and will never be stable", tests and will never be stable",
cfg_fn!(rustc_attrs))), cfg_fn!(rustc_attrs))),
("rustc_test_marker", Normal, Gated(Stability::Unstable,
"rustc_attrs",
"the `#[rustc_test_marker]` attribute \
is used internally to track tests",
cfg_fn!(rustc_attrs))),
// RFC #2094 // RFC #2094
("nll", Whitelisted, Gated(Stability::Unstable, ("nll", Whitelisted, Gated(Stability::Unstable,
@ -1164,7 +1165,7 @@ pub const BUILTIN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeG
("type_length_limit", CrateLevel, Ungated), ("type_length_limit", CrateLevel, Ungated),
("test_runner", CrateLevel, Gated(Stability::Unstable, ("test_runner", CrateLevel, Gated(Stability::Unstable,
"custom_test_frameworks", "custom_test_frameworks",
"Custom Test Frameworks is an unstable feature", EXPLAIN_CUSTOM_TEST_FRAMEWORKS,
cfg_fn!(custom_test_frameworks))), cfg_fn!(custom_test_frameworks))),
]; ];
@ -1382,6 +1383,9 @@ pub const EXPLAIN_ASM: &'static str =
pub const EXPLAIN_GLOBAL_ASM: &'static str = pub const EXPLAIN_GLOBAL_ASM: &'static str =
"`global_asm!` is not stable enough for use and is subject to change"; "`global_asm!` is not stable enough for use and is subject to change";
pub const EXPLAIN_CUSTOM_TEST_FRAMEWORKS: &'static str =
"custom test frameworks are an unstable feature";
pub const EXPLAIN_LOG_SYNTAX: &'static str = pub const EXPLAIN_LOG_SYNTAX: &'static str =
"`log_syntax!` is not stable enough for use and is subject to change"; "`log_syntax!` is not stable enough for use and is subject to change";

View file

@ -6272,7 +6272,6 @@ impl<'a> Parser<'a> {
let (in_cfg, outer_attrs) = { let (in_cfg, outer_attrs) = {
let mut strip_unconfigured = ::config::StripUnconfigured { let mut strip_unconfigured = ::config::StripUnconfigured {
sess: self.sess, sess: self.sess,
should_test: false, // irrelevant
features: None, // don't perform gated feature checking features: None, // don't perform gated feature checking
}; };
let outer_attrs = strip_unconfigured.process_cfg_attrs(outer_attrs.to_owned()); let outer_attrs = strip_unconfigured.process_cfg_attrs(outer_attrs.to_owned());

View file

@ -433,7 +433,7 @@ fn visible_path(cx: &TestCtxt, path: &[Ident]) -> Vec<Ident>{
} }
fn is_test_case(i: &ast::Item) -> bool { fn is_test_case(i: &ast::Item) -> bool {
attr::contains_name(&i.attrs, "test_case") attr::contains_name(&i.attrs, "rustc_test_marker")
} }
fn get_test_runner(sd: &errors::Handler, krate: &ast::Crate) -> Option<ast::Path> { fn get_test_runner(sd: &errors::Handler, krate: &ast::Crate) -> Option<ast::Path> {

View file

@ -54,6 +54,7 @@ mod global_asm;
mod log_syntax; mod log_syntax;
mod trace_macros; mod trace_macros;
mod test; mod test;
mod test_case;
pub mod proc_macro_registrar; pub mod proc_macro_registrar;
@ -145,6 +146,7 @@ pub fn register_builtins(resolver: &mut dyn syntax::ext::base::Resolver,
assert: assert::expand_assert, assert: assert::expand_assert,
} }
register(Symbol::intern("test_case"), MultiModifier(Box::new(test_case::expand)));
// format_args uses `unstable` things internally. // format_args uses `unstable` things internally.
register(Symbol::intern("format_args"), register(Symbol::intern("format_args"),

View file

@ -135,8 +135,14 @@ pub fn expand_test_or_bench(
}; };
let mut test_const = cx.item(sp, item.ident.gensym(), let mut test_const = cx.item(sp, item.ident.gensym(),
// #[test_case] vec![
vec![cx.attribute(attr_sp, cx.meta_word(attr_sp, Symbol::intern("test_case")))], // #[cfg(test)]
cx.attribute(attr_sp, cx.meta_list(attr_sp, Symbol::intern("cfg"), vec![
cx.meta_list_item_word(attr_sp, Symbol::intern("test"))
])),
// #[rustc_test_marker]
cx.attribute(attr_sp, cx.meta_word(attr_sp, Symbol::intern("rustc_test_marker")))
],
// const $ident: test::TestDescAndFn = // const $ident: test::TestDescAndFn =
ast::ItemKind::Const(cx.ty(sp, ast::TyKind::Path(None, test_path("TestDescAndFn"))), ast::ItemKind::Const(cx.ty(sp, ast::TyKind::Path(None, test_path("TestDescAndFn"))),
// test::TestDescAndFn { // test::TestDescAndFn {

View file

@ -0,0 +1,75 @@
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// #[test_case] is used by custom test authors to mark tests
// When building for test, it needs to make the item public and gensym the name
// Otherwise, we'll omit the item. This behavior means that any item annotated
// with #[test_case] is never addressable.
//
// We mark item with an inert attribute "rustc_test_marker" which the test generation
// logic will pick up on.
use syntax::ext::base::*;
use syntax::ext::build::AstBuilder;
use syntax::ext::hygiene::{self, Mark, SyntaxContext};
use syntax::ast;
use syntax::source_map::respan;
use syntax::symbol::Symbol;
use syntax_pos::{DUMMY_SP, Span};
use syntax::source_map::{ExpnInfo, MacroAttribute};
use syntax::feature_gate;
pub fn expand(
ecx: &mut ExtCtxt,
attr_sp: Span,
_meta_item: &ast::MetaItem,
anno_item: Annotatable
) -> Vec<Annotatable> {
if !ecx.ecfg.enable_custom_test_frameworks() {
feature_gate::emit_feature_err(&ecx.parse_sess,
"custom_test_frameworks",
attr_sp,
feature_gate::GateIssue::Language,
feature_gate::EXPLAIN_CUSTOM_TEST_FRAMEWORKS);
return vec![anno_item];
}
if !ecx.ecfg.should_test { return vec![]; }
let sp = {
let mark = Mark::fresh(Mark::root());
mark.set_expn_info(ExpnInfo {
call_site: DUMMY_SP,
def_site: None,
format: MacroAttribute(Symbol::intern("test_case")),
allow_internal_unstable: true,
allow_internal_unsafe: false,
local_inner_macros: false,
edition: hygiene::default_edition(),
});
attr_sp.with_ctxt(SyntaxContext::empty().apply_mark(mark))
};
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.attrs.push(
ecx.attribute(sp,
ecx.meta_word(sp, Symbol::intern("rustc_test_marker")))
);
item
});
return vec![Annotatable::Item(item)]
}

View file

@ -18,6 +18,4 @@ fn main() {
//~^ ERROR removing an expression is not supported in this position //~^ ERROR removing an expression is not supported in this position
let _ = [1, 2, 3][#[cfg(unset)] 1]; let _ = [1, 2, 3][#[cfg(unset)] 1];
//~^ ERROR removing an expression is not supported in this position //~^ ERROR removing an expression is not supported in this position
let _ = #[test_case] ();
//~^ ERROR removing an expression is not supported in this position
} }

View file

@ -16,11 +16,5 @@ error: removing an expression is not supported in this position
LL | let _ = [1, 2, 3][#[cfg(unset)] 1]; LL | let _ = [1, 2, 3][#[cfg(unset)] 1];
| ^^^^^^^^^^^^^ | ^^^^^^^^^^^^^
error: removing an expression is not supported in this position error: aborting due to 3 previous errors
--> $DIR/cfg-non-opt-expr.rs:21:13
|
LL | let _ = #[test_case] ();
| ^^^^^^^^^^^^
error: aborting due to 4 previous errors

View file

@ -8,6 +8,6 @@
// option. This file may not be copied, modified, or distributed // option. This file may not be copied, modified, or distributed
// except according to those terms. // except according to those terms.
#![test_runner(main)] //~ ERROR Custom Test Frameworks is an unstable feature #![test_runner(main)] //~ ERROR custom test frameworks are an unstable feature
fn main() {} fn main() {}

View file

@ -1,7 +1,7 @@
error[E0658]: Custom Test Frameworks is an unstable feature (see issue #50297) error[E0658]: custom test frameworks are an unstable feature (see issue #50297)
--> $DIR/feature-gate-custom_test_frameworks.rs:11:1 --> $DIR/feature-gate-custom_test_frameworks.rs:11:1
| |
LL | #![test_runner(main)] //~ ERROR Custom Test Frameworks is an unstable feature LL | #![test_runner(main)] //~ ERROR custom test frameworks are an unstable feature
| ^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^
| |
= help: add #![feature(custom_test_frameworks)] to the crate attributes to enable = help: add #![feature(custom_test_frameworks)] to the crate attributes to enable