Skip to content

Commit 02a369a

Browse files
committed
Auto merge of #52890 - djrenren:test-visibility, r=petrochenkov
Reexport tests without polluting namespaces This should fix issue #52557. Basically now we gensym a new name for the test function and reexport that. That way the test function's reexport name can't conflict because it was impossible for the test author to write it down. We then use a `use` statement to expose the original name using the original visibility.
2 parents db54765 + 77f9aca commit 02a369a

File tree

2 files changed

+100
-5
lines changed

2 files changed

+100
-5
lines changed

src/libsyntax/ext/expand.rs

+62-5
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use codemap::{ExpnInfo, MacroBang, MacroAttribute, dummy_spanned, respan};
1515
use config::{is_test_or_bench, StripUnconfigured};
1616
use errors::{Applicability, FatalError};
1717
use ext::base::*;
18+
use ext::build::AstBuilder;
1819
use ext::derive::{add_derived_markers, collect_derives};
1920
use ext::hygiene::{self, Mark, SyntaxContext};
2021
use ext::placeholders::{placeholder, PlaceholderExpander};
@@ -474,6 +475,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
474475
cx: self.cx,
475476
invocations: Vec::new(),
476477
monotonic: self.monotonic,
478+
tests_nameable: true,
477479
};
478480
(fragment.fold_with(&mut collector), collector.invocations)
479481
};
@@ -1049,6 +1051,11 @@ struct InvocationCollector<'a, 'b: 'a> {
10491051
cfg: StripUnconfigured<'a>,
10501052
invocations: Vec<Invocation>,
10511053
monotonic: bool,
1054+
1055+
/// Test functions need to be nameable. Tests inside functions or in other
1056+
/// unnameable locations need to be ignored. `tests_nameable` tracks whether
1057+
/// any test functions found in the current context would be nameable.
1058+
tests_nameable: bool,
10521059
}
10531060

10541061
impl<'a, 'b> InvocationCollector<'a, 'b> {
@@ -1066,6 +1073,20 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
10661073
placeholder(fragment_kind, NodeId::placeholder_from_mark(mark))
10671074
}
10681075

1076+
/// Folds the item allowing tests to be expanded because they are still nameable.
1077+
/// This should probably only be called with module items
1078+
fn fold_nameable(&mut self, item: P<ast::Item>) -> SmallVector<P<ast::Item>> {
1079+
fold::noop_fold_item(item, self)
1080+
}
1081+
1082+
/// Folds the item but doesn't allow tests to occur within it
1083+
fn fold_unnameable(&mut self, item: P<ast::Item>) -> SmallVector<P<ast::Item>> {
1084+
let was_nameable = mem::replace(&mut self.tests_nameable, false);
1085+
let items = fold::noop_fold_item(item, self);
1086+
self.tests_nameable = was_nameable;
1087+
items
1088+
}
1089+
10691090
fn collect_bang(&mut self, mac: ast::Mac, span: Span, kind: AstFragmentKind) -> AstFragment {
10701091
self.collect(kind, InvocationKind::Bang { mac: mac, ident: None, span: span })
10711092
}
@@ -1306,7 +1327,7 @@ impl<'a, 'b> Folder for InvocationCollector<'a, 'b> {
13061327
}
13071328
ast::ItemKind::Mod(ast::Mod { inner, .. }) => {
13081329
if item.ident == keywords::Invalid.ident() {
1309-
return noop_fold_item(item, self);
1330+
return self.fold_nameable(item);
13101331
}
13111332

13121333
let orig_directory_ownership = self.cx.current_expansion.directory_ownership;
@@ -1346,22 +1367,58 @@ impl<'a, 'b> Folder for InvocationCollector<'a, 'b> {
13461367

13471368
let orig_module =
13481369
mem::replace(&mut self.cx.current_expansion.module, Rc::new(module));
1349-
let result = noop_fold_item(item, self);
1370+
let result = self.fold_nameable(item);
13501371
self.cx.current_expansion.module = orig_module;
13511372
self.cx.current_expansion.directory_ownership = orig_directory_ownership;
13521373
result
13531374
}
13541375
// Ensure that test functions are accessible from the test harness.
1376+
// #[test] fn foo() {}
1377+
// becomes:
1378+
// #[test] pub fn foo_gensym(){}
1379+
// #[allow(unused)]
1380+
// use foo_gensym as foo;
13551381
ast::ItemKind::Fn(..) if self.cx.ecfg.should_test => {
1356-
if item.attrs.iter().any(|attr| is_test_or_bench(attr)) {
1382+
if self.tests_nameable && item.attrs.iter().any(|attr| is_test_or_bench(attr)) {
1383+
let orig_ident = item.ident;
1384+
let orig_vis = item.vis.clone();
1385+
1386+
// Publicize the item under gensymed name to avoid pollution
13571387
item = item.map(|mut item| {
13581388
item.vis = respan(item.vis.span, ast::VisibilityKind::Public);
1389+
item.ident = item.ident.gensym();
13591390
item
13601391
});
1392+
1393+
// Use the gensymed name under the item's original visibility
1394+
let mut use_item = self.cx.item_use_simple_(
1395+
item.ident.span,
1396+
orig_vis,
1397+
Some(orig_ident),
1398+
self.cx.path(item.ident.span,
1399+
vec![keywords::SelfValue.ident(), item.ident]));
1400+
1401+
// #[allow(unused)] because the test function probably isn't being referenced
1402+
use_item = use_item.map(|mut ui| {
1403+
ui.attrs.push(
1404+
self.cx.attribute(DUMMY_SP, attr::mk_list_item(DUMMY_SP,
1405+
Ident::from_str("allow"), vec![
1406+
attr::mk_nested_word_item(Ident::from_str("unused"))
1407+
]
1408+
))
1409+
);
1410+
1411+
ui
1412+
});
1413+
1414+
SmallVector::many(
1415+
self.fold_unnameable(item).into_iter()
1416+
.chain(self.fold_unnameable(use_item)))
1417+
} else {
1418+
self.fold_unnameable(item)
13611419
}
1362-
noop_fold_item(item, self)
13631420
}
1364-
_ => noop_fold_item(item, self),
1421+
_ => self.fold_unnameable(item),
13651422
}
13661423
}
13671424

src/test/run-pass/issue-52557.rs

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
// This test checks for namespace pollution by private tests.
12+
// Tests used to marked as public causing name conflicts with normal
13+
// functions only in test builds.
14+
15+
// compile-flags: --test
16+
17+
mod a {
18+
pub fn foo() -> bool {
19+
true
20+
}
21+
}
22+
23+
mod b {
24+
#[test]
25+
fn foo() {
26+
local_name(); // ensure the local name still works
27+
}
28+
29+
#[test]
30+
fn local_name() {}
31+
}
32+
33+
use a::*;
34+
use b::*;
35+
36+
pub fn conflict() {
37+
let _: bool = foo();
38+
}

0 commit comments

Comments
 (0)