Using meta in macro_rules

#1

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?

0 Likes

#2

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

0 Likes

#3

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 https://dtolnay.github.io/rust-quiz/9.

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.

0 Likes