rustmacros

Is it possible to have repetitions within an inner macro_rules defined by another macro_rules in Rust?


I'd like to write a macro that defines macros that define enums with attrs, something like this:

macro_rules! define_enum_definer {
  {
    $($variant: ident => $t: ty,)+
  }
  => {
    macro_rules! define_enum {
      ($(#[$attrs:meta])*$name: ident($container: ident)) => {
        $(#[$attrs])*
        enum $name {
          $($variant($container<$t>),)+
        }
      };
    }
  };
}

define_enum_definer! { I32 => i32, I64 => i64, }
define_enum!(
  #[derive(Clone, Debug)]
  #[non_exhaustive]
  DynTypedVec(Vec)
);

fn main() {
  let x = DynTypedVec::I32(vec![1, 2, 3]);
  println!("{:?}", x);
}

But this fails with

error: attempted to repeat an expression containing no syntax variables matched as repeating at this depth
  --> src/main.rs:12:9
   |
12 |       ($(#[$attrs:meta])*$name: ident($container: ident)) => {
   |         ^^^^^^^^^^^^^^^^

It's trying to repeat the syntax variable in the outer macro, but I need it to repeat for the inner macro. I tried to escape the inner repeats with $dollar, but that too fails with

error: missing fragment specifier
  --> src/main.rs:12:8
   |
12 |       ($dollar(#[$attrs:meta])*$name: ident($container: ident)) => {
   |        ^^^^^^^
...
22 | define_inner_macro! { Foo => i32, Bar => i64, }
   | ----------------------------------------------- in this macro invocation

Is there a way around this, or is my only recourse to use procedural macros?


Solution

  • You can use the trick here: https://github.com/rust-lang/rust/issues/35853#issuecomment-415993963. I'll just copy their code below; adapting it is simple enough.

    macro_rules! with_dollar_sign {
        ($($body:tt)*) => {
            macro_rules! __with_dollar_sign { $($body)* }
            __with_dollar_sign!($);
        }
    }
    
    macro_rules! make_println {
        ($name:ident, $fmt:expr) => {
            with_dollar_sign! {
                ($d:tt) => {
                    macro_rules! $name {
                        ($d($d args:expr),*) => {           // (1)
                            println!($fmt, $d($d args),*);  // (2)
                        }
                    }
                }
            }
        };
    }
    
    make_println!(my_dbg, "{:?}");
    
    fn main() {
        my_dbg!(42);
    }