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