Rollup merge of #152425 - Ozzy1423:test-runner, r=JonathanBrouwer

Port #![test_runner] to the attribute parser

Tracking issue: https://github.com/rust-lang/rust/issues/131229

r? @JonathanBrouwer
This commit is contained in:
Jacob Pratt 2026-02-12 00:41:10 -05:00 committed by GitHub
commit 9cbcd11ae4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 63 additions and 35 deletions

View file

@ -228,3 +228,32 @@ impl<S: Stage> NoArgsAttributeParser<S> for RustcOutlivesParser {
]);
const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcOutlives;
}
pub(crate) struct TestRunnerParser;
impl<S: Stage> SingleAttributeParser<S> for TestRunnerParser {
const PATH: &[Symbol] = &[sym::test_runner];
const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost;
const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]);
const TEMPLATE: AttributeTemplate = template!(List: &["path"]);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let Some(list) = args.list() else {
cx.expected_list(cx.attr_span, args);
return None;
};
let Some(single) = list.single() else {
cx.expected_single_argument(list.span);
return None;
};
let Some(meta) = single.meta_item() else {
cx.unexpected_literal(single.span());
return None;
};
Some(AttributeKind::TestRunner(meta.path().0.clone()))
}
}

View file

@ -218,6 +218,7 @@ attribute_parsers!(
Single<SanitizeParser>,
Single<ShouldPanicParser>,
Single<SkipDuringMethodDispatchParser>,
Single<TestRunnerParser>,
Single<TransparencyParser>,
Single<TypeLengthLimitParser>,
Single<WindowsSubsystemParser>,

View file

@ -1002,20 +1002,6 @@ pub(crate) struct AsmUnsupportedClobberAbi {
pub(crate) macro_name: &'static str,
}
#[derive(Diagnostic)]
#[diag("`test_runner` argument must be a path")]
pub(crate) struct TestRunnerInvalid {
#[primary_span]
pub(crate) span: Span,
}
#[derive(Diagnostic)]
#[diag("`#![test_runner(..)]` accepts exactly 1 argument")]
pub(crate) struct TestRunnerNargs {
#[primary_span]
pub(crate) span: Span,
}
#[derive(Diagnostic)]
#[diag("expected token: `,`")]
pub(crate) struct ExpectedCommaInList {

View file

@ -8,10 +8,11 @@ use rustc_ast::entry::EntryPointType;
use rustc_ast::mut_visit::*;
use rustc_ast::visit::Visitor;
use rustc_ast::{ModKind, attr};
use rustc_errors::DiagCtxtHandle;
use rustc_attr_parsing::AttributeParser;
use rustc_expand::base::{ExtCtxt, ResolverExpand};
use rustc_expand::expand::{AstFragment, ExpansionConfig};
use rustc_feature::Features;
use rustc_hir::attrs::AttributeKind;
use rustc_session::Session;
use rustc_session::lint::builtin::UNNAMEABLE_TEST_ITEMS;
use rustc_span::hygiene::{AstPass, SyntaxContext, Transparency};
@ -60,7 +61,7 @@ pub fn inject(
// Do this here so that the test_runner crate attribute gets marked as used
// even in non-test builds
let test_runner = get_test_runner(dcx, krate);
let test_runner = get_test_runner(sess, features, krate);
if sess.is_test_crate() {
let panic_strategy = match (panic_strategy, sess.opts.unstable_opts.panic_abort_tests) {
@ -386,20 +387,16 @@ fn get_test_name(i: &ast::Item) -> Option<Symbol> {
attr::first_attr_value_str_by_name(&i.attrs, sym::rustc_test_marker)
}
fn get_test_runner(dcx: DiagCtxtHandle<'_>, krate: &ast::Crate) -> Option<ast::Path> {
let test_attr = attr::find_by_name(&krate.attrs, sym::test_runner)?;
let meta_list = test_attr.meta_item_list()?;
let span = test_attr.span;
match &*meta_list {
[single] => match single.meta_item() {
Some(meta_item) if meta_item.is_word() => return Some(meta_item.path.clone()),
_ => {
dcx.emit_err(errors::TestRunnerInvalid { span });
}
},
_ => {
dcx.emit_err(errors::TestRunnerNargs { span });
}
fn get_test_runner(sess: &Session, features: &Features, krate: &ast::Crate) -> Option<ast::Path> {
match AttributeParser::parse_limited(
sess,
&krate.attrs,
sym::test_runner,
krate.spans.inner_span,
krate.id,
Some(features),
) {
Some(rustc_hir::Attribute::Parsed(AttributeKind::TestRunner(path))) => Some(path),
_ => None,
}
None
}

View file

@ -5,7 +5,7 @@ pub use ReprAttr::*;
use rustc_abi::Align;
pub use rustc_ast::attr::data_structures::*;
use rustc_ast::token::DocFragmentKind;
use rustc_ast::{AttrStyle, ast};
use rustc_ast::{AttrStyle, Path, ast};
use rustc_data_structures::fx::FxIndexMap;
use rustc_error_messages::{DiagArgValue, IntoDiagArg};
use rustc_macros::{Decodable, Encodable, HashStable_Generic, PrintAttribute};
@ -1367,6 +1367,9 @@ pub enum AttributeKind {
/// `#[unsafe(force_target_feature(enable = "...")]`.
TargetFeature { features: ThinVec<(Symbol, Span)>, attr_span: Span, was_forced: bool },
/// Represents `#![test_runner(path)]`
TestRunner(Path),
/// Represents `#[thread_local]`
ThreadLocal,

View file

@ -176,6 +176,7 @@ impl AttributeKind {
ShouldPanic { .. } => No,
Stability { .. } => Yes,
TargetFeature { .. } => No,
TestRunner(..) => Yes,
ThreadLocal => No,
TrackCaller(..) => Yes,
TypeLengthLimit { .. } => No,

View file

@ -3,6 +3,7 @@ use std::ops::Deref;
use std::path::PathBuf;
use rustc_abi::Align;
use rustc_ast::ast::{Path, join_path_idents};
use rustc_ast::attr::data_structures::CfgEntry;
use rustc_ast::attr::version::RustcVersion;
use rustc_ast::token::{CommentKind, DocFragmentKind};
@ -106,6 +107,16 @@ impl PrintAttribute for PathBuf {
p.word(self.display().to_string());
}
}
impl PrintAttribute for Path {
fn should_render(&self) -> bool {
true
}
fn print_attribute(&self, p: &mut Printer) {
p.word(join_path_idents(self.segments.iter().map(|seg| seg.ident)));
}
}
macro_rules! print_skip {
($($t: ty),* $(,)?) => {$(
impl PrintAttribute for $t {

View file

@ -364,6 +364,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
| AttributeKind::RustcVariance
| AttributeKind::RustcVarianceOfOpaques
| AttributeKind::ShouldPanic { .. }
| AttributeKind::TestRunner(..)
| AttributeKind::ThreadLocal
| AttributeKind::TypeLengthLimit { .. }
| AttributeKind::UnstableFeatureBound(..)
@ -411,9 +412,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
| sym::rustc_mir
// crate-level attrs, are checked below
| sym::feature
| sym::register_tool
| sym::test_runner,
..
| sym::register_tool, ..
] => {}
[name, rest@..] => {
match BUILTIN_ATTRIBUTE_MAP.get(name) {

View file

@ -17,6 +17,7 @@
#![feature(rustc_private)]
#![feature(test)]
#![feature(trim_prefix_suffix)]
#![recursion_limit = "256"]
#![warn(rustc::internal)]
// tidy-alphabetical-end