Skip to content

Commit 0593dc7

Browse files
committed
Move #[test_case] to a syntax extension
1 parent e5ed105 commit 0593dc7

15 files changed

+108
-82
lines changed

src/librustc_driver/driver.rs

-1
Original file line numberDiff line numberDiff line change
@@ -828,7 +828,6 @@ where
828828
let (mut krate, features) = syntax::config::features(
829829
krate,
830830
&sess.parse_sess,
831-
sess.opts.test,
832831
sess.edition(),
833832
);
834833
// these need to be set "early" so that expansion sees `quote` if enabled.

src/librustc_lint/builtin.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1872,7 +1872,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnnameableTestItems {
18721872
return;
18731873
}
18741874

1875-
if let Some(attr) = attr::find_by_name(&it.attrs, "test_case") {
1875+
if let Some(attr) = attr::find_by_name(&it.attrs, "rustc_test_marker") {
18761876
cx.struct_span_lint(
18771877
UNNAMEABLE_TEST_ITEMS,
18781878
attr.span,

src/librustc_resolve/macros.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,7 @@ impl<'a, 'cl> Resolver<'a, 'cl> {
479479
return def;
480480
}
481481

482-
if kind == MacroKind::Attr && path.len() == 1 {
482+
if kind == MacroKind::Attr {
483483
if let Some(ext) = self.unshadowable_attrs.get(&path[0].name) {
484484
return Ok(ext.def());
485485
}

src/libsyntax/config.rs

+2-13
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,16 @@ use ptr::P;
2121

2222
/// A folder that strips out items that do not belong in the current configuration.
2323
pub struct StripUnconfigured<'a> {
24-
pub should_test: bool,
2524
pub sess: &'a ParseSess,
2625
pub features: Option<&'a Features>,
2726
}
2827

2928
// `cfg_attr`-process the crate's attributes and compute the crate's features.
30-
pub fn features(mut krate: ast::Crate, sess: &ParseSess, should_test: bool, edition: Edition)
29+
pub fn features(mut krate: ast::Crate, sess: &ParseSess, edition: Edition)
3130
-> (ast::Crate, Features) {
3231
let features;
3332
{
3433
let mut strip_unconfigured = StripUnconfigured {
35-
should_test,
3634
sess,
3735
features: None,
3836
};
@@ -118,11 +116,6 @@ impl<'a> StripUnconfigured<'a> {
118116
// Determine if a node with the given attributes should be included in this configuration.
119117
pub fn in_cfg(&mut self, attrs: &[ast::Attribute]) -> bool {
120118
attrs.iter().all(|attr| {
121-
// When not compiling with --test we should not compile the #[test] functions
122-
if !self.should_test && is_test(attr) {
123-
return false;
124-
}
125-
126119
let mis = if !is_cfg(attr) {
127120
return true;
128121
} else if let Some(mis) = attr.meta_item_list() {
@@ -249,7 +242,7 @@ impl<'a> StripUnconfigured<'a> {
249242
//
250243
// NB: This is intentionally not part of the fold_expr() function
251244
// in order for fold_opt_expr() to be able to avoid this check
252-
if let Some(attr) = expr.attrs().iter().find(|a| is_cfg(a) || is_test(a)) {
245+
if let Some(attr) = expr.attrs().iter().find(|a| is_cfg(a)) {
253246
let msg = "removing an expression is not supported in this position";
254247
self.sess.span_diagnostic.span_err(attr.span, msg);
255248
}
@@ -352,7 +345,3 @@ impl<'a> fold::Folder for StripUnconfigured<'a> {
352345
fn is_cfg(attr: &ast::Attribute) -> bool {
353346
attr.check_name("cfg")
354347
}
355-
356-
pub fn is_test(att: &ast::Attribute) -> bool {
357-
att.check_name("test_case")
358-
}

src/libsyntax/ext/expand.rs

+5-45
Original file line numberDiff line numberDiff line change
@@ -450,14 +450,12 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
450450
let (fragment_with_placeholders, invocations) = {
451451
let mut collector = InvocationCollector {
452452
cfg: StripUnconfigured {
453-
should_test: self.cx.ecfg.should_test,
454453
sess: self.cx.parse_sess,
455454
features: self.cx.ecfg.features,
456455
},
457456
cx: self.cx,
458457
invocations: Vec::new(),
459458
monotonic: self.monotonic,
460-
tests_nameable: true,
461459
};
462460
(fragment.fold_with(&mut collector), collector.invocations)
463461
};
@@ -475,7 +473,6 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
475473

476474
fn fully_configure(&mut self, item: Annotatable) -> Annotatable {
477475
let mut cfg = StripUnconfigured {
478-
should_test: self.cx.ecfg.should_test,
479476
sess: self.cx.parse_sess,
480477
features: self.cx.ecfg.features,
481478
};
@@ -1047,11 +1044,6 @@ struct InvocationCollector<'a, 'b: 'a> {
10471044
cfg: StripUnconfigured<'a>,
10481045
invocations: Vec<Invocation>,
10491046
monotonic: bool,
1050-
1051-
/// Test functions need to be nameable. Tests inside functions or in other
1052-
/// unnameable locations need to be ignored. `tests_nameable` tracks whether
1053-
/// any test functions found in the current context would be nameable.
1054-
tests_nameable: bool,
10551047
}
10561048

10571049
impl<'a, 'b> InvocationCollector<'a, 'b> {
@@ -1069,20 +1061,6 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
10691061
placeholder(fragment_kind, NodeId::placeholder_from_mark(mark))
10701062
}
10711063

1072-
/// Folds the item allowing tests to be expanded because they are still nameable.
1073-
/// This should probably only be called with module items
1074-
fn fold_nameable(&mut self, item: P<ast::Item>) -> OneVector<P<ast::Item>> {
1075-
fold::noop_fold_item(item, self)
1076-
}
1077-
1078-
/// Folds the item but doesn't allow tests to occur within it
1079-
fn fold_unnameable(&mut self, item: P<ast::Item>) -> OneVector<P<ast::Item>> {
1080-
let was_nameable = mem::replace(&mut self.tests_nameable, false);
1081-
let items = fold::noop_fold_item(item, self);
1082-
self.tests_nameable = was_nameable;
1083-
items
1084-
}
1085-
10861064
fn collect_bang(&mut self, mac: ast::Mac, span: Span, kind: AstFragmentKind) -> AstFragment {
10871065
self.collect(kind, InvocationKind::Bang { mac: mac, ident: None, span: span })
10881066
}
@@ -1297,7 +1275,7 @@ impl<'a, 'b> Folder for InvocationCollector<'a, 'b> {
12971275
fn fold_item(&mut self, item: P<ast::Item>) -> OneVector<P<ast::Item>> {
12981276
let item = configure!(self, item);
12991277

1300-
let (attr, traits, mut item) = self.classify_item(item);
1278+
let (attr, traits, item) = self.classify_item(item);
13011279
if attr.is_some() || !traits.is_empty() {
13021280
let item = Annotatable::Item(item);
13031281
return self.collect_attr(attr, traits, item, AstFragmentKind::Items).make_items();
@@ -1319,7 +1297,7 @@ impl<'a, 'b> Folder for InvocationCollector<'a, 'b> {
13191297
}
13201298
ast::ItemKind::Mod(ast::Mod { inner, .. }) => {
13211299
if item.ident == keywords::Invalid.ident() {
1322-
return self.fold_nameable(item);
1300+
return noop_fold_item(item, self);
13231301
}
13241302

13251303
let orig_directory_ownership = self.cx.current_expansion.directory_ownership;
@@ -1359,32 +1337,13 @@ impl<'a, 'b> Folder for InvocationCollector<'a, 'b> {
13591337

13601338
let orig_module =
13611339
mem::replace(&mut self.cx.current_expansion.module, Rc::new(module));
1362-
let result = self.fold_nameable(item);
1340+
let result = noop_fold_item(item, self);
13631341
self.cx.current_expansion.module = orig_module;
13641342
self.cx.current_expansion.directory_ownership = orig_directory_ownership;
13651343
result
13661344
}
13671345

1368-
// Ensure that test items can be exported by the harness generator.
1369-
// #[test] fn foo() {}
1370-
// becomes:
1371-
// #[test] pub fn foo_gensym(){}
1372-
ast::ItemKind::Const(..)
1373-
| ast::ItemKind::Static(..)
1374-
| ast::ItemKind::Fn(..) if self.cx.ecfg.should_test => {
1375-
if self.tests_nameable && attr::contains_name(&item.attrs, "test_case") {
1376-
// Publicize the item under gensymed name to avoid pollution
1377-
// This means #[test_case] items can't be referenced by user code
1378-
item = item.map(|mut item| {
1379-
item.vis = respan(item.vis.span, ast::VisibilityKind::Public);
1380-
item.ident = item.ident.gensym();
1381-
item
1382-
});
1383-
}
1384-
1385-
self.fold_unnameable(item)
1386-
}
1387-
_ => self.fold_unnameable(item),
1346+
_ => noop_fold_item(item, self),
13881347
}
13891348
}
13901349

@@ -1609,6 +1568,7 @@ impl<'feat> ExpansionConfig<'feat> {
16091568
feature_tests! {
16101569
fn enable_quotes = quote,
16111570
fn enable_asm = asm,
1571+
fn enable_custom_test_frameworks = custom_test_frameworks,
16121572
fn enable_global_asm = global_asm,
16131573
fn enable_log_syntax = log_syntax,
16141574
fn enable_concat_idents = concat_idents,

src/libsyntax/feature_gate.rs

+9-5
Original file line numberDiff line numberDiff line change
@@ -777,10 +777,6 @@ pub const BUILTIN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeG
777777
("no_link", Normal, Ungated),
778778
("derive", Normal, Ungated),
779779
("should_panic", Normal, Ungated),
780-
("test_case", Normal, Gated(Stability::Unstable,
781-
"custom_test_frameworks",
782-
"Custom test frameworks are experimental",
783-
cfg_fn!(custom_test_frameworks))),
784780
("ignore", Normal, Ungated),
785781
("no_implicit_prelude", Normal, Ungated),
786782
("reexport_test_harness_main", Normal, Ungated),
@@ -965,6 +961,11 @@ pub const BUILTIN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeG
965961
attribute is just used for rustc unit \
966962
tests and will never be stable",
967963
cfg_fn!(rustc_attrs))),
964+
("rustc_test_marker", Normal, Gated(Stability::Unstable,
965+
"rustc_attrs",
966+
"the `#[rustc_test_marker]` attribute \
967+
is used internally to track tests",
968+
cfg_fn!(rustc_attrs))),
968969

969970
// RFC #2094
970971
("nll", Whitelisted, Gated(Stability::Unstable,
@@ -1164,7 +1165,7 @@ pub const BUILTIN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeG
11641165
("type_length_limit", CrateLevel, Ungated),
11651166
("test_runner", CrateLevel, Gated(Stability::Unstable,
11661167
"custom_test_frameworks",
1167-
"Custom Test Frameworks is an unstable feature",
1168+
EXPLAIN_CUSTOM_TEST_FRAMEWORKS,
11681169
cfg_fn!(custom_test_frameworks))),
11691170
];
11701171

@@ -1382,6 +1383,9 @@ pub const EXPLAIN_ASM: &'static str =
13821383
pub const EXPLAIN_GLOBAL_ASM: &'static str =
13831384
"`global_asm!` is not stable enough for use and is subject to change";
13841385

1386+
pub const EXPLAIN_CUSTOM_TEST_FRAMEWORKS: &'static str =
1387+
"custom test frameworks are an unstable feature";
1388+
13851389
pub const EXPLAIN_LOG_SYNTAX: &'static str =
13861390
"`log_syntax!` is not stable enough for use and is subject to change";
13871391

src/libsyntax/parse/parser.rs

-1
Original file line numberDiff line numberDiff line change
@@ -6272,7 +6272,6 @@ impl<'a> Parser<'a> {
62726272
let (in_cfg, outer_attrs) = {
62736273
let mut strip_unconfigured = ::config::StripUnconfigured {
62746274
sess: self.sess,
6275-
should_test: false, // irrelevant
62766275
features: None, // don't perform gated feature checking
62776276
};
62786277
let outer_attrs = strip_unconfigured.process_cfg_attrs(outer_attrs.to_owned());

src/libsyntax/test.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,7 @@ fn visible_path(cx: &TestCtxt, path: &[Ident]) -> Vec<Ident>{
433433
}
434434

435435
fn is_test_case(i: &ast::Item) -> bool {
436-
attr::contains_name(&i.attrs, "test_case")
436+
attr::contains_name(&i.attrs, "rustc_test_marker")
437437
}
438438

439439
fn get_test_runner(sd: &errors::Handler, krate: &ast::Crate) -> Option<ast::Path> {

src/libsyntax_ext/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ mod global_asm;
5454
mod log_syntax;
5555
mod trace_macros;
5656
mod test;
57+
mod test_case;
5758

5859
pub mod proc_macro_registrar;
5960

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

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

149151
// format_args uses `unstable` things internally.
150152
register(Symbol::intern("format_args"),

src/libsyntax_ext/test.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,14 @@ pub fn expand_test_or_bench(
135135
};
136136

137137
let mut test_const = cx.item(sp, item.ident.gensym(),
138-
// #[test_case]
139-
vec![cx.attribute(attr_sp, cx.meta_word(attr_sp, Symbol::intern("test_case")))],
138+
vec![
139+
// #[cfg(test)]
140+
cx.attribute(attr_sp, cx.meta_list(attr_sp, Symbol::intern("cfg"), vec![
141+
cx.meta_list_item_word(attr_sp, Symbol::intern("test"))
142+
])),
143+
// #[rustc_test_marker]
144+
cx.attribute(attr_sp, cx.meta_word(attr_sp, Symbol::intern("rustc_test_marker")))
145+
],
140146
// const $ident: test::TestDescAndFn =
141147
ast::ItemKind::Const(cx.ty(sp, ast::TyKind::Path(None, test_path("TestDescAndFn"))),
142148
// test::TestDescAndFn {

src/libsyntax_ext/test_case.rs

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
2+
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
3+
// file at the top-level directory of this distribution and at
4+
// http://rust-lang.org/COPYRIGHT.
5+
//
6+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
7+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
8+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
9+
// option. This file may not be copied, modified, or distributed
10+
// except according to those terms.
11+
12+
// #[test_case] is used by custom test authors to mark tests
13+
// When building for test, it needs to make the item public and gensym the name
14+
// Otherwise, we'll omit the item. This behavior means that any item annotated
15+
// with #[test_case] is never addressable.
16+
//
17+
// We mark item with an inert attribute "rustc_test_marker" which the test generation
18+
// logic will pick up on.
19+
20+
use syntax::ext::base::*;
21+
use syntax::ext::build::AstBuilder;
22+
use syntax::ext::hygiene::{self, Mark, SyntaxContext};
23+
use syntax::ast;
24+
use syntax::source_map::respan;
25+
use syntax::symbol::Symbol;
26+
use syntax_pos::{DUMMY_SP, Span};
27+
use syntax::source_map::{ExpnInfo, MacroAttribute};
28+
use syntax::feature_gate;
29+
30+
pub fn expand(
31+
ecx: &mut ExtCtxt,
32+
attr_sp: Span,
33+
_meta_item: &ast::MetaItem,
34+
anno_item: Annotatable
35+
) -> Vec<Annotatable> {
36+
if !ecx.ecfg.enable_custom_test_frameworks() {
37+
feature_gate::emit_feature_err(&ecx.parse_sess,
38+
"custom_test_frameworks",
39+
attr_sp,
40+
feature_gate::GateIssue::Language,
41+
feature_gate::EXPLAIN_CUSTOM_TEST_FRAMEWORKS);
42+
43+
return vec![anno_item];
44+
}
45+
46+
if !ecx.ecfg.should_test { return vec![]; }
47+
48+
let sp = {
49+
let mark = Mark::fresh(Mark::root());
50+
mark.set_expn_info(ExpnInfo {
51+
call_site: DUMMY_SP,
52+
def_site: None,
53+
format: MacroAttribute(Symbol::intern("test_case")),
54+
allow_internal_unstable: true,
55+
allow_internal_unsafe: false,
56+
local_inner_macros: false,
57+
edition: hygiene::default_edition(),
58+
});
59+
attr_sp.with_ctxt(SyntaxContext::empty().apply_mark(mark))
60+
};
61+
62+
let mut item = anno_item.expect_item();
63+
64+
item = item.map(|mut item| {
65+
item.vis = respan(item.vis.span, ast::VisibilityKind::Public);
66+
item.ident = item.ident.gensym();
67+
item.attrs.push(
68+
ecx.attribute(sp,
69+
ecx.meta_word(sp, Symbol::intern("rustc_test_marker")))
70+
);
71+
item
72+
});
73+
74+
return vec![Annotatable::Item(item)]
75+
}

src/test/ui/cfg-non-opt-expr.rs

-2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,4 @@ fn main() {
1818
//~^ ERROR removing an expression is not supported in this position
1919
let _ = [1, 2, 3][#[cfg(unset)] 1];
2020
//~^ ERROR removing an expression is not supported in this position
21-
let _ = #[test_case] ();
22-
//~^ ERROR removing an expression is not supported in this position
2321
}

src/test/ui/cfg-non-opt-expr.stderr

+1-7
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,5 @@ error: removing an expression is not supported in this position
1616
LL | let _ = [1, 2, 3][#[cfg(unset)] 1];
1717
| ^^^^^^^^^^^^^
1818

19-
error: removing an expression is not supported in this position
20-
--> $DIR/cfg-non-opt-expr.rs:21:13
21-
|
22-
LL | let _ = #[test_case] ();
23-
| ^^^^^^^^^^^^
24-
25-
error: aborting due to 4 previous errors
19+
error: aborting due to 3 previous errors
2620

src/test/ui/feature-gate-custom_test_frameworks.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11-
#![test_runner(main)] //~ ERROR Custom Test Frameworks is an unstable feature
11+
#![test_runner(main)] //~ ERROR custom test frameworks are an unstable feature
1212

1313
fn main() {}

src/test/ui/feature-gate-custom_test_frameworks.stderr

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

0 commit comments

Comments
 (0)