Cardano
Cardano

Reputation: 951

Why can I not pass a captured token to a nested macro?

Multiple examples I've seen suggest that this should be possible, but it is apparently not:

lib.rs:

#![feature(trace_macros)]

#[macro_export]
macro_rules! inner_macro (
    (f32) => {"float"};
);

#[macro_export]
macro_rules! outer_macro {
    ($T:ty) => {
        inner_macro!($T)
    }
}

#[cfg(test)]
mod tests {

    #[test]
    fn test_nested() {
        trace_macros!(true);
        s1: String = String::from(outer_macro!(f32));
        s2: String = String::from(inner_macro!(f32));
        trace_macros!(false);
    }
}

Running cargo test gives the following output:

error: no rules expected the token `f32`
  --> src/lib.rs:11:22
   |
4  | / macro_rules! inner_macro (
5  | |     (f32) => {"float"};
6  | | );
   | |__- when calling this macro
...
11 |           inner_macro!($T)
   |                        ^^ no rules expected the token `f32`
...
21 |           s1: String = String::from(outer_macro!(f32));
   |                                     ----------------- in this macro invocation

This is confusing, because there certainly appears to be a rule expecting the token f32.

There are also notes from the expansion trace of the two macros. The first one does not work:

= note: expanding `outer_macro! { f32 }`
= note: to `inner_macro ! ( f32 )`
= note: expanding `inner_macro! { f32 }`

while the second one does:

= note: expanding `inner_macro! { f32 }`
= note: to `"float"`

Why does the first expansion of inner_macro! fail, while the exact same expansion succeeds when it is not nested inside another macro?

Edit: if we perform the substitution manually, it works and gives the expected output:

macro_rules! unknown {
    ($T:ty) => {
        inner_macro!(f32)
    }
}

Upvotes: 3

Views: 163

Answers (1)

Cardano
Cardano

Reputation: 951

After some more reading, it turns out this is an instance of a typical stumbling block. After being captured the first time, $T takes the value of an AST Node. Substituting $T will not emplace a token, it will emplace that AST Node. So what I expected to be something like this:

inner_macro!(`f32` [token])

was actually

inner_macro!(<Type>f32</Type>)

Unfortunately for users, the two invocations both get stringified the same way, to inner_macro! ( f32 ).

The correct way to do this is to capture the token-to-be-substituted as a "token tree":

macro_rules! unknown {
    ($T:tt) => {
        inner_macro!($T)
    }
}

Upvotes: 3

Related Questions