1

My goal is to write a macro expand! such that:

struct A;
struct B;
struct Mut<T>;

expand!()         => ()
expand!(A)        => (A,)
expand!(mut A)    => (Mut<A>,)
expand!(A, mut B) => (A, Mut<B>,) 
// etc

[Edit] added trailing comma for consistent tuple syntax.

I wrote this macro so far:

macro_rules! to_type {
    ( $ty:ty ) => { $ty };
    ( mut $ty:ty ) => { Mut<$ty> };
}

macro_rules! expand {
    ( $( $(mut)? $ty:ty ),* ) => {
        (
        $( to_type!($ty) ),*
        ,)
    };
}

What I'm struggling with, is capturing the mut token. How can I assign it to a variable and reuse it in the macro body? Is it possible to work on more than 1 token at a time?

TheOperator
  • 5,936
  • 29
  • 42
  • 1
    If there is only one type in the macro invocation, do you want to expand to a tuple with a single element, or to a bare type? – Sven Marnach Nov 12 '20 at 12:46
  • Good question, I guess you're asking because of the `(value, )` syntax for 1-element tuples. Let's say the result always has to be a tuple. Since trailing commas are allowed, that should not complicate the rules. – TheOperator Nov 12 '20 at 14:01

1 Answers1

1

Something like this?

macro_rules! expand {
    (@phase2($($ty_final:ty),*),) => {
        ($($ty_final,)*)
    };
    (@phase2($($ty_final:ty),*), mut $ty:ty, $($rest:tt)*) => {
        expand!(@phase2($($ty_final,)* Mut::<$ty>), $($rest)*)
    };
    (@phase2($($ty_final:ty),*), $ty:ty, $($rest:tt)*) => {
        expand!(@phase2($($ty_final,)* $ty), $($rest)*)
    };
    ($($t:tt)*) => {
        expand!(@phase2(), $($t)*)
    };
}

struct A;
struct B;
struct Mut<T>(std::marker::PhantomData<T>);

fn main() {
    #[allow(unused_parens)]
    let _: expand!() = ();
    #[allow(unused_parens)]
    let _: expand!(A,) = (A,);
    #[allow(unused_parens)]
    let _: expand!(mut B,)    = (Mut::<B>(Default::default()),);
    #[allow(unused_parens)]
    let _: expand!(A, mut B,) = (A, Mut::<B>(Default::default()));
}
phimuemue
  • 34,669
  • 9
  • 84
  • 115
  • Thank you! Could you elaborate a bit why pattern-matching a new introduced symbol `@phase2(types)` is necessary? In other words, why two simple match-arms `$ty:ty, $($rest:tt)*` and `mut $ty:ty, $($rest:tt)*` do not work? – TheOperator Nov 12 '20 at 17:11
  • Also, it seems like the trailing commas in the `expand!(A,)` or `expand!(A, mut B,)` calls are still required, and I haven't seen an easy way to remove it? – TheOperator Nov 12 '20 at 17:42