Macro not expanding when nested


#1
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.


#2

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).


#3

(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?


#4

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