Macro matcher branching on particular path

I am learning macros and I came up with the following broken code:

mod x {
    pub const V: usize = 1;

    pub mod a {
        pub const V: usize = 2;
    }

    pub mod b {
        pub const V: usize = 3;
    }

    pub mod c {
        pub mod cc {
            pub const V: usize = 4;
        }
    }

    macro_rules! show_token {
        ($($t:path),*) => {
            $(
                show_token!(@print_single $t);
            )*
        };

        (@print_single self) => {
            println!("'{}'+++", self::V * 333);
        };

        (@print_single $t:path) => {
            println!("'{}'", $t::V);
        };
    }

    pub fn run() {
        show_token!(self, a, b, c::cc);
    }
}

fn main() {
    x::run();
}

The aim is simple, to have distinct behavior if self is provided as path.

Playground module with path - compilation error

Playground module with token tree - executes as I would expect

Playground submodule with token tree - compilation error

Playground submodule with path - compilation error

only possible if the match arm for self is directly called, not when it is forwarded with a captured meta variable of path kind. once the token tree is captured as a meta variable (except for the lifetime, ident, and tt kinds), it is converted to an opaque token tree and cannot be re-examined against literal tokens such as self.

see forwarding a matched fragment for the precise description.

2 Likes

This is definitely part of the issue!

Your response answers why the matcher for a particular path (i.e. self) is never matched by a either a recursive or forward call of a macro.

Very much Thanks!

However… there is more than just a logical issue here.

The syntax of the code above is not accepted by the compiler. $t::V is not accepted yielding:

Errors

Exited with status 101

Standard Error

   Compiling playground v0.0.1 (/playground)
error: expected one of `,`, `.`, `?`, or an operator, found `::`
  --> src/main.rs:30:32
   |
30 |             println!("'{}'", $t::V);
   |                                ^^ expected one of `,`, `.`, `?`, or an operator
...
35 |         show_token!(self, a, b, c::cc);
   |         ------------------------------ in this macro invocation
   |
   = note: this error originates in the macro `show_token` (in Nightly builds, run with -Z macro-backtrace for more info)

error: could not compile `playground` (bin "playground") due to 4 previous errors

I am not an expert, but it seems you cannot simply compose paths this way.

Looking at the reference, the path is defined (yes I am simplifying) as a sequence of identifiers separated by ::s, not recursively[1] as some composition of shorter paths. Your $t is an opaque path and thus cannot be a part of (longer) path.

Contrary to e.g. expressions, which are defined recursively, so you can compose them of smaller (and opaque) expressions.


  1. there is no grammar rule of the shape Path -> Path :: Ident ↩︎

that's to be expected. as I said, a meta variable is partially parsed into an opaque ast node.

in this case, let's denote $t as Path("self"), although the actual value "self" is not important to the parser, only the kind matters, so the "expression" $t::V is seen by the compiler as the token sequence [Path("<opaque>"), Punct("::"), Ident("V")], which does NOT match the grammar rule for any path expression, e.g. SimplePath. what would work should be something like [Keyword("self"), Punct("::"), Ident("V")].

the key take away is, although you work with token trees, rust macros operate on typed tokens (think AST nodes), NOT textual tokens.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.