How to pass the output of a macro to another macro as an argument?

Hello.

I want to create a declarative macro that internally invokes a procedural macro.

For the purpose of this example, the simplified version of the procedural macro:

#[proc_macro]
pub fn inner_macro(input: TokenStream) -> TokenStream {
    let arg = parse_macro_input!(input as LitStr).value();
    quote!(["123", "456", #arg]).into()
}

The declarative macro I'm trying to define:

macro_rules! mymacro {
    ($arg:literal) => {
        mymacro!(@parse inner_macro!($arg))
    };

    (@parse [$($x:expr),*]) => {
        [
            $(
                $x.to_string()
            ),*
        ]
    };
}

The usage of the macros:

fn main() {
    let proc = inner_macro!("789");
    let decl_manual = mymacro!(@parse ["123", "456", "789"]);
    let decl = mymacro!("789");
}

The expansion:

fn main() {
    let proc = ["123", "456", "789"];
    let decl_manual = ["123".to_string(), "456".to_string(), "789".to_string()];
    let decl = ();
}

I expected decl value expansion to be the same as the decl_manual value expansion.

The code fails to compile due to the following error:

error: no rules expected the token `inner_macro`
  --> 
   |
59 | macro_rules! mymacro {
   | -------------------- when calling this macro
60 |     ($arg:literal) => {
61 |         mymacro!(@parse inner_macro!($arg))
   |                         ^^^^^^^^^^^ no rules expected this token in macro call
...
76 |     let decl = mymacro!("789");
   |                --------------- in this macro invocation
   |
note: while trying to match `[`
  --> 
   |
64 |     (@parse [$($x:expr),*]) => {
   |             ^
   = note: this error originates in the macro `mymacro` (in Nightly builds, run with -Z macro-backtrace for more info)

However, it looks like the problem is not with the passing an argument itself, but rather than with the parsing of the tokens of the argument, since the following code compiles without errors:

macro_rules! mymacro {
    ($arg:literal) => {
        mymacro!(@parse inner_macro!($arg))
    };

    (@parse $($x:tt)*) => {
        $($x)*
    };
}

But changing the argument of the @parse macro to [$($x:tt)*], with the brackets, even though they are present in the output of the inner_macro, the compilation results in the same error as in the first time.

Is there a way to use output of a (procedural) macro as an argument for another macro?

Hope for your help!
Thanks!

I don't believe so, and that you're running into the situation recently discussed here (including the next couple comments).

If you can work with the expected output of the inner macro as a blob as opposed to an expanded set of tokens, there may be a way forward. For your example that could be...

macro_rules! mymacro {
    // ...
    (@parse $arr:expr) => {
        $arr.map(|s| s.to_string())
    };
}

...but I recognize your actual use case may not be so simple.

1 Like

You can't do this directly.

What you can do is change inner_macro so that instead of expanding to the output, it instead expands to an invocation of mymacro, passing its output as input to mymacro. You can generalise this by taking the name of the "continuation" macro as an input. That is, instead of:

mymacro!(@parse inner_macro!("789"))

You change it into the following sequence of expansions:

inner_macro!("789", then mymacro!(@parse))
// =>
mymacro!(@parse ["123", "456", "789"])

Of course, this only works if you're in a position to modify inner_macro.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.