I have been work­ing on a set of token munch­ing macro_rules! for a pro­ject at work re­cently. Par for the course for this sort of pro­ject I ran into all sorts of gnarly macro match­ing fail­ures and in­scrut­able rustc dia­gnostics that tend to ac­com­mod­ate them. Some­where along the way I was able to come up with a way to use a com­bin­a­tion of compile_error!, stringify! to achieve what I think can be best de­scribed as a print-de­bug­ging equi­val­ent for macro_rules. Here is how to ap­ply this and what to look out for.

I will, nat­ur­ally, use some pretty ob­vi­ously faulty and per­haps non­sensic­al-in-isol­a­tion macro_rules! for the sake of ex­amples here, but I prom­ise – this ap­proach works just as well in mac­ros that span a thou­sand lines. Or, at least, it has worked for me! Here’s the first spe­ci­men:

#[macro_export]
macro_rules! fragment_typo {
    ($exprtts:tt) => {
        $crate::fragment_typo!(@expr $expr)
    };
    (@expr $expr:expr) => {
        $expr
    };
}

Passing this macro an ex­pres­sion 42 will pro­duce the fol­low­ing dia­gnostic:

error: no rules expected `$`
  --> src/main.rs:4:38
   |
 2 | macro_rules! fragment_typo {
   | -------------------------- when calling this macro
 3 |     ($myexpr:tt) => {
 4 |         $crate::fragment_typo!(@expr $expr)
   |                                      ^^^^^ no rules expected this token in macro call
...
12 |     let x = fragment_typo!(42);
   |             ------------------ in this macro invocation
   |
note: while trying to match meta-variable `$expr:expr`
  --> src/main.rs:6:12
   |
 6 |     (@expr $expr:expr) => {
   |            ^^^^^^^^^^
   = note: this error originates in the macro `fragment_typo` (in Nightly builds, run with -Z macro-backtrace for more info)

In case you haven’t spot­ted the prob­lem yet, the first arm of the macro is us­ing $expr token which was­n’t bound any­where. Easy to see in a 10 line mac­ro, trivial to miss in a 1000 line one.

Here’s what I star­ted do­ing. First, I de­term­ine from the dia­gnostics which line to “in­stru­ment”. In this case rustc barfs at main.rs:4, so I wrap it in­side compile_error!(stringify!()), as such:

 macro_rules! fragment_typo {
     ($exprtts:tt) => {
+        compile_error!(stringify!(
         $crate::fragment_typo!(@expr $expr)
+        ))
     };
     (@expr $expr:expr) => {
         $expr
     };
 }

Build­ing again, com­piler now spits out:

error: $crate :: fragment_typo! (@ expr $expr)

At this point there are two things that I do to help me de­bug. I can now copy this macro in­voc­a­tion into my code to be in­voked dir­ectly, rather than through many lay­ers of macro in­voc­a­tions above. That way it is pos­sible to modify the in­voc­a­tion as needed, to de­term­ine if the prob­lem is in the macro lay­ers above (e.g. them ac­cu­mu­lat­ing/­group­ing tokens in the wrong way), or the macro be­ing in­voked (e.g. for­got to add an arm that would match this par­tic­u­lar shape), or per­haps this spe­cific in­voc­a­tion it­self as is the case here.

The other ap­proach is in­formed by the very use­ful prop­erty of how compile_error!(stringify!()) works: it prints out the tokens that frag­ments have matched, if it is able to. With that in mind, $expr stay­ing an un­ex­pan­ded “frag­ment” looks quite sus­pect here. In­deed, what we have in scope is $exprtts in­stead! Let’s fix and re­build to ex­em­plify what I mean:

 macro_rules! fragment_typo {
     ($exprtts:tt) => {
         compile_error!(stringify!(
-         $crate::fragment_typo!(@expr $expr)
+         $crate::fragment_typo!(@expr $exprtts)
         ))
     };
     (@expr $expr:expr) => {
         $expr
     };
 }
error: $crate :: fragment_typo! (@ expr 42)
  --> src/main.rs:4:9

To close this off, one more very nice prop­erty of compile_error! in mod­ern Rust is that it does not stop on the first oc­cur­rence. So you can do some­thing like this:

#[macro_export]
macro_rules! fragment_typo {
    ($exprtts:tt) => { {
        compile_error!(stringify!($crate::fragment_typo!(@expr $exprtts)));
        $crate::fragment_typo!(@expr $exprtts)
    } };
    (@expr $expr:expr) => { {
        compile_error!(stringify!($expr));
        $expr
    } };
}

and get an ac­tu­ally use­ful ex­pan­sion trace:

error: $crate :: fragment_typo! (@ expr 42)
  --> src/main.rs:4:9
...
error: 42
  --> src/main.rs:8:9

That’s all for this one, now you too can en­joy macro crimes without any of the head­ache!