Macro not expanding when nested

enum Test {
    Any(&'static [Test]),
    All(&'static [Test]),
    Base
}

macro_rules! test {
    (Any($( $x:expr ),*)) => {
        Any(&[ $(test!( $x )),* ])
    };
    (All($( $x:expr ),*)) => {
        All(&[ $(test!( $x )),* ])
    };
    ($x:expr) => {
        $x
    };
}

fn main() {
    use self::Test::*;
    let _ = test!(Any(Base, Base, Base));
    let _ = test!(Any(All(Any(Base, Base, Base), Base, Base), Base));
}

Only the first assignment works, with the second one spitting out errors because the inside isn't expanded.

Let's simplify the testcase a little:

test!(Any(All(Base, Base), Base))

Let's say we're now transforming the top-level Any, and we're in this branch:

    (Any($( $x:expr ),*)) => {
        Any(&[ $(test!( $x )),* ])
    };

So now our $x is a "list" of two expressions: All(Base, Base) and Base. What's important is that you've explicitely marked that the $x:expr should be parsed as a regular Rust expression, so All(Base, Base) is a regular function call! When you pass it to the test!( $x ), it's not passed as a stream of tokens, but rather as a one single "thing" – an expression.

Because of that, the nested test! doesn't see All ..., but rather a single expression, and executes the ($x:expr) => { $x }; arm.

To avoid this eager parsing as expression, you need to change the $e:expr into something like $($x:tt)*. Unfortunately, a comma that you split on is also considered a tt, so it won't work. You can further wrap everything in [] to avoid ambiguity, but it gets ugly to use (playground):

    (Any($( [ $($x:tt)* ] ),*)) => {
        Any(&[ $(test!( $($x)* )),* ])
    };

If you assume that all your "sub-expressions" take the form of Ident(...) (so the Base is now written as Base()), you can get rid of [] using something like that (playground):

    (Any($( $x:ident $y:tt ),*)) => {
        Any(&[ $(test!( $x $y )),* ])
    };

If you want a general solution for "parsing the stream of tokens as $($x:tt)* until a given delimiter" (in your case comma), there's a technique called "tt munching", explained somewhere in The Little Book of Rust Macros.

edit: Here's a playground with the final version (it's not the simplest one though).

2 Likes

(O_O) wow, glad I asked for help on that one. Thanks! Mind if I use that macro, with credit and without any major modification?

Sure, use it as you like! Also, don't have to credit me, but linking to that macros book would be worthwile, I guess.