How to generate `#![..]` top level macro calls with a proc macro

Hi there,

I'm trying to generate required top level macro invocations like those:

#![cfg_attr(test, no_main)]
#![feature(custom_test_frameworks)]
#![reexport_test_harness_main = "test_main"]
#![test_runner(test_runner)]

But this simple proc macro does not seem to make the trick:

#[proc_macro_attribute]
pub fn ruspiro_test_framework(_attr: TokenStream, _item: TokenStream) -> TokenStream {
    quote!(
        #![cfg_attr(test, no_main)]
        #![feature(custom_test_frameworks)]
        #![reexport_test_harness_main = "test_main"]
        #![test_runner(test_runner)]
     )
    .into()
}

This leads to the issue:

error: an inner attribute is not permitted in this context
  --> src\lib.rs:13:19
   |
13 | #![cfg_attr(test, ruspiro_test_macros::ruspiro_test_framework)]
   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files. Outer attributes, like `#[test]`, annotate the item following them.
   = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)

Is this not possible, or do I need a special implementation within my proc_macro to make this possible?

Thanks in advance for any hints.

I don't believe this is currently possible.

A macro must expand to a full "thing", where "thing" is an item, an expression, a type (maybe a pattern too).

Attributes are not "things" themselves, they are modifier on these other things, so it is literally impossible to have a macro that expands to attributes on their own.

What you can have is: a macro that wraps some item definition and re-emits it, with the extra desired attributes applied to it.

Sadly, a macro cannot emit or be applied to the root of a crate, so it is indeed literally impossible to do what you want.

The two closest solutions are:

  • Bundling all the attributes into a single line:

    #![cfg_attr(all(), cfg_attr(test, no_main), feature(custom_test_frameworks), test_runner(test_runner))]
    
  • using a build.rs or other meta codegen tool to generate a root file with those attributes.

TL,DR

It is not possible yet, and there aren't even good workarounds for this :slightly_frowning_face:

Well, this is quite unfortunate.

The reason why I think it would be great to have this functionality is, that it would simplify the usage of custom test frameworks. I've defined one in a separate crate but there is a lot of boilerplate required in every crate that should make use of the custom test framework.

It would be highly appreciated if the boilerplate could be reduced to one or two proc-macro calls to improve usability of custom test frameworks.

So if anyone from the Rust team is also scanning the posts here this might be a possible future feature when it comes to testing support of Rust :wink:

1 Like

It would also be useful to alias clippy and other lint setups. Definitely a desirable feature!