Using meta in macro_rules

This works

macro_rules! m {
    ( a $(@$meta:ident)* ) => {
        m! ( b $(@$meta)* );
    };
    ( b @ meta ) => { "ok" };
    ( b ) => { "ok" };
}

fn main() {
    println!("{}", m!(a @meta));
    println!("{}", m!(a));
}

This doesn't

macro_rules! m {
    ( a $(@$meta:meta)* ) => { // NOTE change in here matcher type
        m! ( b $(@$meta)* );
    };
    ( b @ meta ) => { "ok" };
    ( b ) => { "ok" };
}

fn main() {
    println!("{}", m!(a @meta));
    println!("{}", m!(a));
}
error: no rules expected the token `meta`
  --> src/main.rs:3:19
   |
3  |         m! ( b $(@$meta)* );
   |                   ^^^^^
...
10 |     println!("{}", m!(a @meta));
   |                    ----------- in this macro invocation

Am I doing something wrong? Is the meta matcher useless?

Possibly https://github.com/rust-lang/rust/issues/41472

I believe this is by design to avoid parsing and re-parsing the same syntax tree nodes many times during recursive macro expansion. Most macro_rules matchers (everything other than $:ident, $:lifetime, $:tt) become atomic opaque bundles when matched, and subsequent rules cannot inspect inside of them.

This is the same behavior as Rust Quiz.

The bundling into an opaque atomic token is observable if you match $meta against $:tt.

macro_rules! m {
    ($tt:tt) => {
        println!("it is a single token");
    };
}

macro_rules! n {
    ($meta:meta) => {
        m!($meta);
    };
}

fn main() {
    n!(recursion_limit = "128");
}

I believe changing this behavior would require an RFC that addresses performance implications and backward compatibility implications.