Search code examples
rustrust-macros

Why is "-x" trying to parse as a literal and failing in declarative macros?


I have a little macro I wrote to help me explore what things are parsed as. With a few arms lopped off, it looks like:

macro_rules! show_me{
    ($l : literal $($tail : tt)*) => {
        println!("{} is a literal and then I see {}", stringify!($l), stringify!($($tail)*));
        show_me!($($tail)*)
    };
    (- $($tail : tt)*) => {
        println!("{} is a - and then I see {}", stringify!(-), stringify!($($tail)*));
        show_me!($($tail)*)
    };
    ($i : ident $($tail : tt)*) => {
        println!("{} is an ident and then I see {}", stringify!($i), stringify!($($tail)*));
        show_me!($($tail)*)
    };
    ($token : tt $($tail : tt)*) => {
        println!("{} is a mystery and then I see {}", stringify!($token), stringify!($($tail)*));
        show_me!($($tail)*)
    };
    () => {}
}

This behaves as expected on some inputs using rustc 1.66.0 (69f9c33d7 2022-12-12):

show_me!(x + 1);
# x is an ident and then I see + 1
# + is a mystery and then I see 1
# 1 is a literal and then I see

But if I try a negative number, I get (a little) surprised:

show_me!(-4);
# -4 is a literal and then I see 

This would make perfect sense, except that https://doc.rust-lang.org/reference/procedural-macros.html says (under the description of declarative macros):

  • Literals ("string", 1, etc)

    Note that negation (e.g. -1) is never a part of such literal tokens, but a separate operator token.

Where it gets weird to me is if I try to run show_me!(-x):

error: unexpected token: `x`
  --> src/main.rs:54:15
   |
54 |     show_me!(-x);
   |               ^
   |
  ::: src/utils/macros.rs:27:6
   |
27 |     ($l : literal $($tail : tt)*) => {
   |      ------------ while parsing argument for this `literal` macro fragment

Note that if I comment out the literal arm of show_me, or just move it to the bottom of the matcher, the other arms are perfectly happy with this:

show_me!(-x);
# - is a - and then I see x
# x is an ident and then I see 

My expectation would be that the matching arm would either match successfully, or fall through to the next pattern. Instead, it's throwing compile errors. What is happening in this case?

Also, am I misreading the documentation or is it incorrect? To me it seems to clearly say that - should never be part of literal expressions in declarative macros (though it may be part of procedural ones.)


Solution

  • This was asked in issue #82968, and the response was:

    This is working as intended, non-terminal metavariables always fully commit to parsing a part of the syntax, if this fails, the whole macro call is rejected.

    You can see the same behavior with e.g. expr.