Skip to content

Commit

Permalink
Move #[test_case] to a syntax extension
Browse files Browse the repository at this point in the history
  • Loading branch information
djrenren committed Sep 5, 2018
1 parent e5ed105 commit 0593dc7
Show file tree
Hide file tree
Showing 15 changed files with 108 additions and 82 deletions.
1 change: 0 additions & 1 deletion src/librustc_driver/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -828,7 +828,6 @@ where
let (mut krate, features) = syntax::config::features(
krate,
&sess.parse_sess,
sess.opts.test,
sess.edition(),
);
// these need to be set "early" so that expansion sees `quote` if enabled.
Expand Down
2 changes: 1 addition & 1 deletion src/librustc_lint/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1872,7 +1872,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnnameableTestItems {
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(
UNNAMEABLE_TEST_ITEMS,
attr.span,
Expand Down
2 changes: 1 addition & 1 deletion src/librustc_resolve/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,7 @@ impl<'a, 'cl> Resolver<'a, 'cl> {
return def;
}

if kind == MacroKind::Attr && path.len() == 1 {
if kind == MacroKind::Attr {
if let Some(ext) = self.unshadowable_attrs.get(&path[0].name) {
return Ok(ext.def());
}
Expand Down
15 changes: 2 additions & 13 deletions src/libsyntax/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,16 @@ use ptr::P;

/// A folder that strips out items that do not belong in the current configuration.
pub struct StripUnconfigured<'a> {
pub should_test: bool,
pub sess: &'a ParseSess,
pub features: Option<&'a 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) {
let features;
{
let mut strip_unconfigured = StripUnconfigured {
should_test,
sess,
features: None,
};
Expand Down Expand Up @@ -118,11 +116,6 @@ impl<'a> StripUnconfigured<'a> {
// Determine if a node with the given attributes should be included in this configuration.
pub fn in_cfg(&mut self, attrs: &[ast::Attribute]) -> bool {
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) {
return true;
} else if let Some(mis) = attr.meta_item_list() {
Expand Down Expand Up @@ -249,7 +242,7 @@ impl<'a> StripUnconfigured<'a> {
//
// NB: This is intentionally not part of the fold_expr() function
// 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";
self.sess.span_diagnostic.span_err(attr.span, msg);
}
Expand Down Expand Up @@ -352,7 +345,3 @@ impl<'a> fold::Folder for StripUnconfigured<'a> {
fn is_cfg(attr: &ast::Attribute) -> bool {
attr.check_name("cfg")
}

pub fn is_test(att: &ast::Attribute) -> bool {
att.check_name("test_case")
}
50 changes: 5 additions & 45 deletions src/libsyntax/ext/expand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -450,14 +450,12 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
let (fragment_with_placeholders, invocations) = {
let mut collector = InvocationCollector {
cfg: StripUnconfigured {
should_test: self.cx.ecfg.should_test,
sess: self.cx.parse_sess,
features: self.cx.ecfg.features,
},
cx: self.cx,
invocations: Vec::new(),
monotonic: self.monotonic,
tests_nameable: true,
};
(fragment.fold_with(&mut collector), collector.invocations)
};
Expand All @@ -475,7 +473,6 @@ impl<'a, 'b> MacroExpander<'a, 'b> {

fn fully_configure(&mut self, item: Annotatable) -> Annotatable {
let mut cfg = StripUnconfigured {
should_test: self.cx.ecfg.should_test,
sess: self.cx.parse_sess,
features: self.cx.ecfg.features,
};
Expand Down Expand Up @@ -1047,11 +1044,6 @@ struct InvocationCollector<'a, 'b: 'a> {
cfg: StripUnconfigured<'a>,
invocations: Vec<Invocation>,
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> {
Expand All @@ -1069,20 +1061,6 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
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 {
self.collect(kind, InvocationKind::Bang { mac: mac, ident: None, span: span })
}
Expand Down Expand Up @@ -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>> {
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() {
let item = Annotatable::Item(item);
return self.collect_attr(attr, traits, item, AstFragmentKind::Items).make_items();
Expand All @@ -1319,7 +1297,7 @@ impl<'a, 'b> Folder for InvocationCollector<'a, 'b> {
}
ast::ItemKind::Mod(ast::Mod { inner, .. }) => {
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;
Expand Down Expand Up @@ -1359,32 +1337,13 @@ impl<'a, 'b> Folder for InvocationCollector<'a, 'b> {

let orig_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.directory_ownership = orig_directory_ownership;
result
}

// Ensure that test items can be exported by the harness generator.
// #[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),
_ => noop_fold_item(item, self),
}
}

Expand Down Expand Up @@ -1609,6 +1568,7 @@ impl<'feat> ExpansionConfig<'feat> {
feature_tests! {
fn enable_quotes = quote,
fn enable_asm = asm,
fn enable_custom_test_frameworks = custom_test_frameworks,
fn enable_global_asm = global_asm,
fn enable_log_syntax = log_syntax,
fn enable_concat_idents = concat_idents,
Expand Down
14 changes: 9 additions & 5 deletions src/libsyntax/feature_gate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -777,10 +777,6 @@ pub const BUILTIN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeG
("no_link", Normal, Ungated),
("derive", 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),
("no_implicit_prelude", Normal, Ungated),
("reexport_test_harness_main", Normal, Ungated),
Expand Down Expand Up @@ -965,6 +961,11 @@ pub const BUILTIN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeG
attribute is just used for rustc unit \
tests and will never be stable",
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
("nll", Whitelisted, Gated(Stability::Unstable,
Expand Down Expand Up @@ -1164,7 +1165,7 @@ pub const BUILTIN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeG
("type_length_limit", CrateLevel, Ungated),
("test_runner", CrateLevel, Gated(Stability::Unstable,
"custom_test_frameworks",
"Custom Test Frameworks is an unstable feature",
EXPLAIN_CUSTOM_TEST_FRAMEWORKS,
cfg_fn!(custom_test_frameworks))),
];

Expand Down Expand Up @@ -1382,6 +1383,9 @@ pub const EXPLAIN_ASM: &'static str =
pub const EXPLAIN_GLOBAL_ASM: &'static str =
"`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 =
"`log_syntax!` is not stable enough for use and is subject to change";

Expand Down
1 change: 0 additions & 1 deletion src/libsyntax/parse/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6272,7 +6272,6 @@ impl<'a> Parser<'a> {
let (in_cfg, outer_attrs) = {
let mut strip_unconfigured = ::config::StripUnconfigured {
sess: self.sess,
should_test: false, // irrelevant
features: None, // don't perform gated feature checking
};
let outer_attrs = strip_unconfigured.process_cfg_attrs(outer_attrs.to_owned());
Expand Down
2 changes: 1 addition & 1 deletion src/libsyntax/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ fn visible_path(cx: &TestCtxt, path: &[Ident]) -> Vec<Ident>{
}

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> {
Expand Down
2 changes: 2 additions & 0 deletions src/libsyntax_ext/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ mod global_asm;
mod log_syntax;
mod trace_macros;
mod test;
mod test_case;

pub mod proc_macro_registrar;

Expand Down Expand Up @@ -145,6 +146,7 @@ pub fn register_builtins(resolver: &mut dyn syntax::ext::base::Resolver,
assert: assert::expand_assert,
}

register(Symbol::intern("test_case"), MultiModifier(Box::new(test_case::expand)));

// format_args uses `unstable` things internally.
register(Symbol::intern("format_args"),
Expand Down
10 changes: 8 additions & 2 deletions src/libsyntax_ext/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,14 @@ pub fn expand_test_or_bench(
};

let mut test_const = cx.item(sp, item.ident.gensym(),
// #[test_case]
vec![cx.attribute(attr_sp, cx.meta_word(attr_sp, Symbol::intern("test_case")))],
vec![
// #[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 =
ast::ItemKind::Const(cx.ty(sp, ast::TyKind::Path(None, test_path("TestDescAndFn"))),
// test::TestDescAndFn {
Expand Down
75 changes: 75 additions & 0 deletions src/libsyntax_ext/test_case.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@

// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// https://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://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)]
}
2 changes: 0 additions & 2 deletions src/test/ui/cfg-non-opt-expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,4 @@ fn main() {
//~^ ERROR removing an expression is not supported in this position
let _ = [1, 2, 3][#[cfg(unset)] 1];
//~^ ERROR removing an expression is not supported in this position
let _ = #[test_case] ();
//~^ ERROR removing an expression is not supported in this position
}
8 changes: 1 addition & 7 deletions src/test/ui/cfg-non-opt-expr.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,5 @@ error: removing an expression is not supported in this position
LL | let _ = [1, 2, 3][#[cfg(unset)] 1];
| ^^^^^^^^^^^^^

error: removing an expression is not supported in this position
--> $DIR/cfg-non-opt-expr.rs:21:13
|
LL | let _ = #[test_case] ();
| ^^^^^^^^^^^^

error: aborting due to 4 previous errors
error: aborting due to 3 previous errors

2 changes: 1 addition & 1 deletion src/test/ui/feature-gate-custom_test_frameworks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@
// option. This file may not be copied, modified, or distributed
// 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() {}
4 changes: 2 additions & 2 deletions src/test/ui/feature-gate-custom_test_frameworks.stderr
Original file line number Diff line number Diff line change
@@ -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
|
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
Expand Down

0 comments on commit 0593dc7

Please sign in to comment.