Ignore #[test_case] on anything other than fn/const/static.

`expand_test_case` looks for any item with a `#[test_case]` attribute
and adds a `test_path_symbol` attribute to it while also fiddling with
the item's ident's span.

This is pretty weird, because `#[test_case]` is only valid on
`fn`/`const`/`static` items, as far as I can tell. But you don't
currently get an error or warning if you use it on other kinds of items.

This commit changes things so that a `#[test_case]` item is modified
only if it is `fn`/`const`/`static`. This is relevant for moving idents
from `Item` to `ItemKind`, because some item kinds don't have an ident,
e.g. `impl` blocks.

The commit also does the following.
- Renames a local variable `test_id` as `test_ident`.
- Changes a `const` to `static` in
  `tests/ui/custom_test_frameworks/full.rs` to give the `static` case
  some test coverage.
- Adds a `struct` and `impl` to the same test to give some test coverage
  to the non-affected item kinds. These have a `FIXME` comment
  identifying the weirdness here. Hopefully this will be useful
  breadcrumbs for somebody else in the future.
This commit is contained in:
Nicholas Nethercote 2025-03-21 15:25:30 +11:00
parent deed0f2480
commit 43018eacb6
2 changed files with 39 additions and 21 deletions

View file

@ -51,21 +51,26 @@ pub(crate) fn expand_test_case(
return vec![]; return vec![];
} }
}; };
item = item.map(|mut item| {
let test_path_symbol = Symbol::intern(&item_path( // `#[test_case]` is valid on functions, consts, and statics. Only modify
// skip the name of the root module // the item in those cases.
&ecx.current_expansion.module.mod_path[1..], match &mut item.kind {
&item.ident, ast::ItemKind::Fn(_) | ast::ItemKind::Const(_) | ast::ItemKind::Static(_) => {
)); item.ident.span = item.ident.span.with_ctxt(sp.ctxt());
item.vis = ast::Visibility { let test_path_symbol = Symbol::intern(&item_path(
span: item.vis.span, // skip the name of the root module
kind: ast::VisibilityKind::Public, &ecx.current_expansion.module.mod_path[1..],
tokens: None, &item.ident,
}; ));
item.ident.span = item.ident.span.with_ctxt(sp.ctxt()); item.vis = ast::Visibility {
item.attrs.push(ecx.attr_name_value_str(sym::rustc_test_marker, test_path_symbol, sp)); span: item.vis.span,
item kind: ast::VisibilityKind::Public,
}); tokens: None,
};
item.attrs.push(ecx.attr_name_value_str(sym::rustc_test_marker, test_path_symbol, sp));
}
_ => {}
}
let ret = if is_stmt { let ret = if is_stmt {
Annotatable::Stmt(P(ecx.stmt_item(item.span, item))) Annotatable::Stmt(P(ecx.stmt_item(item.span, item)))
@ -162,17 +167,17 @@ pub(crate) fn expand_test_or_bench(
let ret_ty_sp = cx.with_def_site_ctxt(fn_.sig.decl.output.span()); let ret_ty_sp = cx.with_def_site_ctxt(fn_.sig.decl.output.span());
let attr_sp = cx.with_def_site_ctxt(attr_sp); let attr_sp = cx.with_def_site_ctxt(attr_sp);
let test_id = Ident::new(sym::test, attr_sp); let test_ident = Ident::new(sym::test, attr_sp);
// creates test::$name // creates test::$name
let test_path = |name| cx.path(ret_ty_sp, vec![test_id, Ident::from_str_and_span(name, sp)]); let test_path = |name| cx.path(ret_ty_sp, vec![test_ident, Ident::from_str_and_span(name, sp)]);
// creates test::ShouldPanic::$name // creates test::ShouldPanic::$name
let should_panic_path = |name| { let should_panic_path = |name| {
cx.path( cx.path(
sp, sp,
vec![ vec![
test_id, test_ident,
Ident::from_str_and_span("ShouldPanic", sp), Ident::from_str_and_span("ShouldPanic", sp),
Ident::from_str_and_span(name, sp), Ident::from_str_and_span(name, sp),
], ],
@ -184,7 +189,7 @@ pub(crate) fn expand_test_or_bench(
cx.path( cx.path(
sp, sp,
vec![ vec![
test_id, test_ident,
Ident::from_str_and_span("TestType", sp), Ident::from_str_and_span("TestType", sp),
Ident::from_str_and_span(name, sp), Ident::from_str_and_span(name, sp),
], ],
@ -380,7 +385,8 @@ pub(crate) fn expand_test_or_bench(
}); });
// extern crate test // extern crate test
let test_extern = cx.item(sp, test_id, ast::AttrVec::new(), ast::ItemKind::ExternCrate(None)); let test_extern =
cx.item(sp, test_ident, ast::AttrVec::new(), ast::ItemKind::ExternCrate(None));
debug!("synthetic test item:\n{}\n", pprust::item_to_string(&test_const)); debug!("synthetic test item:\n{}\n", pprust::item_to_string(&test_const));

View file

@ -25,4 +25,16 @@ impl example_runner::Testable for IsFoo {
const TEST_1: IsFoo = IsFoo("hello"); const TEST_1: IsFoo = IsFoo("hello");
#[test_case] #[test_case]
const TEST_2: IsFoo = IsFoo("foo"); static TEST_2: IsFoo = IsFoo("foo");
// FIXME: `test_case` is currently ignored on anything other than
// fn/const/static. Should this be a warning/error?
#[test_case]
struct _S;
// FIXME: `test_case` is currently ignored on anything other than
// fn/const/static. Should this be a warning/error?
#[test_case]
impl _S {
fn _f() {}
}