Macro to replace type parameters?


#1

I have the following macro (playground link) that is intended to walk a type definition and replace any type parameter A with Self:

// Replace all instances of the type A with the type Self in types.
macro_rules! replace_a_self_ty {
    // type parameters (X<Y, Z>)
    ($name:ident<$($ty:ty),*>) => (replace_a_self_ty!($name)<$(replace_a_self_ty!($ty),)*>);
    // tuple ((X, Y, Z))
    (($($ty:ty),*)) => (($(replace_a_self_ty!($ty)),*));
    // slice ([X])
    ([$ty:ty]) => ([replace_a_self_ty!($ty)]);
    // array ([X; N])
    ([$ty:ty; $n:expr]) => ([replace_a_self_ty!($ty); $n]);
    // reference (&X)
    (&$ty:ty) => (&replace_a_self_ty!($ty));
    (A) => (Self);
    ($ty:tt) => ($ty);
}

trait Foo {
    fn get_self() -> replace_a_self_ty!(Option<A>);
}

Unfortunately, I get this error:

error: macro expansion ignores token `<` and any following
 --> src/main.rs:4:61
  |
4 |     ($name:ident<$($ty:ty),*>) => (replace_a_self_ty!($name)<$(replace_a_self_ty!($ty),)*>);
  |                                                             ^
  |
note: caused by the macro expansion here; the usage of `replace_a_self_ty!` is likely invalid in type context
 --> src/main.rs:18:22
  |
18|     fn get_self() -> replace_a_self_ty!(Option<A>);
  |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Is there any way to accomplish this?


#2

What you are seeing is that once an input has been matched as $:ty it can no longer be matched as tokens by subsequent rules. Minimal example:

macro_rules! demo {
    (1 $ty:ty) => {
        demo!(2 $ty);
    };
    (2 A) => {};
}

fn main() {
    demo!(1 A);
}
error: no rules expected the token `A`
 --> src/main.rs:3:17
  |
3 |         demo!(2 $ty);
  |                 ^^^
...
9 |     demo!(1 A);
  |     ----------- in this macro invocation

I would implement this with a TT muncher that replaces token A with Self.

// Replace all instances of the type A with the type Self in types.
macro_rules! replace_a_self_ty {
    // Open parenthesis.
    (@($($stack:tt)*) ($($first:tt)*) $($rest:tt)*) => {
        replace_a_self_ty!(@(() $($stack)*) $($first)* __paren $($rest)*)
    };

    // Open square bracket.
    (@($($stack:tt)*) [$($first:tt)*] $($rest:tt)*) => {
        replace_a_self_ty!(@(() $($stack)*) $($first)* __bracket $($rest)*)
    };

    // Close parenthesis.
    (@(($($close:tt)*) ($($top:tt)*) $($stack:tt)*) __paren $($rest:tt)*) => {
        replace_a_self_ty!(@(($($top)* ($($close)*)) $($stack)*) $($rest)*)
    };

    // Close square bracket.
    (@(($($close:tt)*) ($($top:tt)*) $($stack:tt)*) __bracket $($rest:tt)*) => {
        replace_a_self_ty!(@(($($top)* [$($close)*]) $($stack)*) $($rest)*)
    };

    // Replace `A` token with `Self`.
    (@(($($top:tt)*) $($stack:tt)*) A $($rest:tt)*) => {
        replace_a_self_ty!(@(($($top)* Self) $($stack)*) $($rest)*)
    };

    // Munch a token that is not `A`.
    (@(($($top:tt)*) $($stack:tt)*) $first:tt $($rest:tt)*) => {
        replace_a_self_ty!(@(($($top)* $first) $($stack)*) $($rest)*)
    };

    // Done.
    (@(($($top:tt)+))) => {
        $($top)+
    };

    // Begin with an empty stack.
    ($($input:tt)+) => {
        replace_a_self_ty!(@(()) $($input)*)
    };
}

trait Foo: Sized {
    fn get_self() -> replace_a_self_ty!(Option<A>);
}

#3

That works perfectly, thanks so much!


#4

I published a macro library called tt-call which should make this sort of macro much easier to implement. There is a building block tt_replace! which could be used as follows:


#[macro_use]
extern crate tt_call;

macro_rules! is_capital_a {
    {
        $caller:tt
        input = [{ A }]
    } => {
        tt_return! {
            $caller
            is = [{ true }]
        }
    };

    {
        $caller:tt
        input = [{ $other:tt }]
    } => {
        tt_return! {
            $caller
            is = [{ false }]
        }
    };
}

// Replace all instances of the type A with the type Self in types.
macro_rules! replace_a_self_ty {
    ($($tokens:tt)*) => {
        tt_call! {
            macro = [{ tt_replace }]
            condition = [{ is_capital_a }]
            replace_with = [{ Self }]
            input = [{ $($tokens)* }]
        }
    };
}

trait Foo: Sized {
    fn get_self() -> replace_a_self_ty!(Option<A>);
}