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?
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>);
}
That works perfectly, thanks so much!
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>);
}
2 Likes