Macro that expands to invalid syntax


#1

Hi, total Rust noob here. I am playing around with macros and noticed this.

This is a valid definition of my_println!, which simply calls println! with the same arguments:

macro_rules! my_println {
    (
        $($arg:expr),*
    ) => {
        println!($($arg),*)
    }
}

If one accidentally forgets the comma in the expansion, the definition is wrong. The resulting rustc error is:

error: no rules expected the token `&args[1..]`
  --> main.rs:23:20
   |
23 |         println!($($arg)*)
   |                    ^^^^
...
31 |     my_println!("{:?}", &args[1..]);
   |     ------------------------- in this macro invocation

Why is the error so misleading? AFAIK Rust does not allow space-separated lists of expressions, so this could be caught at macro-definition time. But even if caught later, the problem is NOT that “no rule expected the token blah”. A better error would be “no rule expanded to valid syntax”, because clearly all available rules did expect that token, so this wasn’t a failure to match the arguments.

Should I post this on the implementation forum?


#2

The error message is indicating a failure to match the arguments of println. The expansion of my_println produces:

println!("{:?}" &args[1..])
         ^^^^^^ ^^^^^^^^^^
          expr     expr

The definition of println accepts the following patterns:

() => { ... };
($fmt:expr) => { ... };
($fmt:expr, $($arg:tt)*) => { ... };

So after matching the first $:expr, it is going to expect either the end of the macro invocation (the second rule) or a comma token (the third rule). Instead it sees another $:expr which is neither of those. In other words, no rules expected the token &args[1..].

AFAIK Rust does not allow space-separated lists of expressions

None of the standard library macros take a space-separated list of expressions, but Rust certainly allows it for your own macros.

macro_rules! semenzato {
    ($($e:expr)*) => {
        $(
            println!("{}", $e);
        )*
    }
}

fn main() {
    semenzato!(1 2+2 3+3+3);
}

#3

I see, thanks!

I still think the error message is misleading:

error: no rules expected the token `&args[1..]`
  --> main.rs:23:20
   |
23 |         println!($($arg)*)
   |                    ^^^^
...
31 |     my_println!("{:?}", &args[1..]);
   |     ------------------------- in this macro invocation

The message says “in this macro invocation” so one would assume that the match fails in the expansion of my_println. In fact, had the message simply said “at this line”, I might have dug deeper :wink:

Also, I am surprised that space-separated variable-length lists are allowed, since fixed-length are not:

error: $a:expr is followed by $b:expr, which is not allowed for expr fragments
–> main.rs:19:18
|
19 | $a: expr $b: expr
| ^^^^^^^^

Finally, the manual says that they should not be allowed. So maybe a bug?

From https://doc.rust-lang.org/book/first-edition/macros.html#syntactic-requirements

There are additional rules regarding the next token after a metavariable:

expr and stmt variables may only be followed by one of: => , ;
ty and path variables may only be followed by one of: => , = | ; : > [ { as where
pat variables may only be followed by one of: => , = | if in
Other variables may be followed by any token.


#4

Oh right… additionally, Zach in my team reminded me that space-separated expressions are ambiguous:

a b - c

could be two expressions (“a” and “b - c”) or three (“a”, “b”, and “-c”).


#5

Filed https://github.com/rust-lang/rust/issues/48220.