Use a macro in path position?

I'm trying to write a macro which emits an enum definition. As part of this, I'd like to do something like:

enum Foo {
    $(
        __inner_normalize!($variant),
    )*
}

Unfortunately, it looks like you can't use a macro invocation where a path AST node is expected. As a demonstration, the following code gives the following error:

macro_rules! foo {
    ($p:path) => {}
}

macro_rules! bar {
    () => {}
}

foo!(bar!());

Error:

error: no rules expected the token `!`
 --> src/lib.rs:9:9
  |
1 | macro_rules! foo {
  | ---------------- when calling this macro
...
9 | foo!(bar!());
  |         ^ no rules expected this token in macro call

Is there any way around this limitation - to use a macro invocation in path position?

So the error is because bar!() is not a path but instead an item. This works

playground

macro_rules! foo {
    ($p:item) => {}
}

macro_rules! bar {
    () => {}
}

foo!(bar!{});

Also keep in mind the order of macro expansion - foo!(bar!()) will expand foo first, giving it the literal tokens bar!() as input. If and only if foo! retains the invocation of bar! in its output will bar! be run at all.

2 Likes

Yeah, this is not possible. You need to recurse with some accumulator to handle each variant (or use a proc-macro, of course):

macro_rules! __inner_normalize {
    (
        $enum:tt
        (variants: $($variants:tt)*)
        $Variant:ident $(,
        $($rest:tt)*)?
    ) => (__inner_normalize! {
        $enum_name
        (variants:
            $($variants)*
            /* your expansion using $Variant here */,
        )
        $($($rest)*)?
    });

    (
        (enum: $(#[$attr:meta])* $pub:vis enum $EnumName:ident )
        (variants: $($variants:tt)* )
        /* Nothing left to parse / handle */
    ) => (
        $(#[$attr])*
        $pub
        enum $EnumName {
            $($variants)*
        }
    );
}

And then as a caller you can then write:

__inner_normalize! {
    (enum: #[derive(Debug)] pub enum Foo)
    (variants: )
    $($variant)*
}