Can I Have a Macro Rules Macro Expand in the Body of an Attribute Proc Macro?

I have a situation where I want to genrate code with a macro rules macro that generates code that has an attribute macro applied to it, but it seems that the macro rules macro isn't expanding totally before the attribute macro gets it's tokens. For instance:

macro_rules! testing {
    (@test) => {
        async fn hello(&self) -> String {
            "hello".into()
        }
    };
    () => {
        #[Object]
        impl ProjectTask {
            testing!(@test);

            async fn test(&self) -> bool {
                false
            }
        }
    };
}

The #[Object] attribute macro is supposed to read async function signatures and conver them to GraphQL resolver functions, but what I need to do is generate those async function signatures with my macro_rules macro, but it seems that my call to testing!(@test) doesn't get expanded before the #[Object] proc macro gets run. It actually does get expanded, but it get's expanded after not before.

Honestly that kind of makes sense, but is there any way to influence the ordering? How can I expand the tokens before I pass them to the #[Object] macro?

Not really. It’s always the outermost macro first. In

#[Object]
impl ProjectTask {
    testing!(@test);

    async fn test(&self) -> bool {
        false
    }
}

first #[Object], then testing! expands (provided that the testing! call is still present in the result of #[Object]). Or in something like foo!(bar!(baz)) the macro foo! is is expanded first and if its result still contains the bar!, or any other macro call, the outermost of those is called next, etc.


If you really need to, you could try to implement a very specific helper macro that “knows” the definition of testing and can do something like

expand_testing_in!{
    #[Object]
    impl ProjectTask {
        testing!(@test);

        async fn test(&self) -> bool {
            false
        }
    }
}

where expand_testing_in could replace the testing!(@test); “call” with its result

async fn hello(&self) -> String {
    "hello".into()
}

Of course that isn’t a particularly clean or general solution, in particular, testing itself wouldn’t even have to be a macro on its own anymore.

1 Like

Awesome, thanks.

Thankfully, I finally realized that I don't actually need to resolve macros inside of #[Object] macro because I can generate all the code in one run without using macros on the inside. It just seemed convenient initially to have the helper macros, but it wasn't really necessary. I was failing to see the forest through all the trees. :smile: