rustmacrosargumentsparameter-passingrust-decl-macros

Why do I get "unexpected token" when trying to call an inner macro with the argument of an outer macro?


I don't understand this failure while trying to pass the expression received by the higher! macro to the lower! macro:

// A low-level macro using only Rust primitives.
macro_rules! lower {
    (x, $a:expr) => {
        println!("x is {}", $a);
    };
    (x($b:expr), $a:expr) => {
        println!("x({}) is rather {}", $b, $a);
    };
}

// A higher-level macro using my own previous macro.
macro_rules! higher {
    ($xstuff:expr, $a:expr) => {
        // Here, I expect transferring the expression $xstuff to lower!.. but it fails.
        lower!($xstuff, $a)
    };
}

fn main() {
    lower!(x, '5'); // x is 5
    lower!(x(8), '6'); // x(8) is rather 6

    higher!(x(7), '9'); 
}
error: no rules expected the token `x(7)`
  --> src/main.rs:15:16
   |
2  | macro_rules! lower {
   | ------------------ when calling this macro
...
15 |         lower!($xstuff, $a)
   |                ^^^^^^^ no rules expected this token in macro call
...
23 |     higher!(x(7), '9'); 
   |     ------------------- in this macro invocation

I would expect this last token to be expected by the rule in lower!, but the compiler tells me it is unexpected. What am I missing here? How can I transfer the expression received by higher! as $xstuff to lower!?


Solution

  • After the call to higher!, x(7) has been parsed as a complete expression held by the macro variable $xstuff:

        ($xstuff:expr, $a:expr) => { /* ... */ }
    //           ^~~~
    

    However, neither of the macro rules for lower! accept an arbitrary expression as the first argument, they only accept the token x:

        (x, $a:expr) => { /* ... */ }
    //   ^
        (x($b:expr), $a:expr) => { /* ... */ }
    //   ^
    

    The easiest fix is to place the same restrictions about the x in the higher macro:

    macro_rules! higher {
        (x($xstuff:expr), $a:expr) => {
            lower!(x($xstuff), $a)
        };
    }
    

    An alternate solution (that changes the calling syntax) is to not immediately parse x(7) as an expression, but instead a collection of token trees. You need to add additional grouping at the call site so that the parser knows when to stop though:

    macro_rules! higher {
        (($($xstuff:tt)*), $a:expr) => {
            lower!($($xstuff)*, $a)
        };
    }
    
    fn main() {
        higher!((x(7)), '9');
    }
    

    See also: