I'm trying to implement a macro that seems to be slightly beyond my macro-fu.
The macro generates an impl<...> Trait (...)
for generic tuples of different sizes, but I need the each type of the tuple to implement another trait in a recursive way, such that the Output
type of one implements the other trait for the next type.
The code I have so far works conceptually, in that when the macros are manually expanded, the code does what I want it to and everything is great. However, the macros don't seem to be expanding how I expect them to when compiled.
This is the minimum example of what I'm doing right now:
use paste::paste;
trait TupleAppend<T> {
type Output;
}
trait SelectorReduce {
type Output;
}
macro_rules! reduce_bound {
($largest:literal $($rest:literal)+) => {
paste!{
<reduce_bound!($($rest)+)>::Output as TupleAppend<[<S$largest>]>
}
};
($smallest:literal) => {
paste!{
() as TupleAppend<[<S$smallest>]>
}
};
() => {};
}
macro_rules! reduce_bounds {
($to_impl:literal $($rest:literal)+) => {
paste!
{
reduce_bounds!($($rest)*)
<reduce_bound!($($rest)*)>::Output: TupleAppend<[<S$to_impl>]>,
}
};
($smallest:literal) => {
paste!{
(): TupleAppend<[<S$smallest>]>,
}
};
() => {};
}
macro_rules! impl_reduce {
($($vals:literal)*) => {
paste! {
impl<$([<S$vals>]),*> SelectorReduce for ($([<S$vals>]),*)
where
reduce_bounds!($($vals)*)
{
type Output = <reduce_bound!{$($vals)*}>::Output;
}
}
};
}
impl_reduce!(0 1 2 3);
When compiling, rustc gives me the following error:
error: expected one of `:`, `==`, or `=`, found `{`
--> src\lib.rs:46:13
|
45 | reduce_bounds!($($vals)*)
| - expected one of `:`, `==`, or `=`
46 | / {
47 | | type Output = <reduce_bound!{$($vals)*}>::Output;
48 | | }
| |_____________^ unexpected token
...
53 | impl_reduce!(0 1 2 3);
| --------------------- in this macro invocation
|
= note: this error originates in the macro `impl_reduce` (in Nightly builds, run with -Z macro-backtrace for more info)
So it looks like rustc is unable to expand the reduce_bounds!()
macro call when it's in the trait bounds position. Is that expected behavior? Should I be able to call a macro there? If not, is there any way to create these bounds within the macro?
Using rust-analyzer to recursively expand the macro, it partially works, and expands to the following:
impl<S0, S1, S2, S3> SelectorReduce for (S0, S1, S2, S3)
where
(): TupleAppend<S3>,
<reduce_bound!(3)>::Output: TupleAppend<S2>,
<reduce_bound!(2 3)>::Output: TupleAppend<S1>,
<reduce_bound!(1 2 3)>::Output: TupleAppend<S0>,
{
type Output = <<<<() as TupleAppend<S3>>::Output as TupleAppend<S2>>::Output as TupleAppend<
S1,
>>::Output as TupleAppend<S0>>::Output;
}
So the top level reduce_bounds!()
expands out, and the base level reduce_bound!()
expands, but the other levels of it get stuck for some reason.
This is what I expect it to expand to, and what it expands out to when the above reduce_bounds!()
are manually expanded and replaced:
impl<S0, S1, S2, S3> SelectorReduce for (S0, S1, S2, S3)
where
(): TupleAppend<S3>,
<() as TupleAppend<S3>>::Output: TupleAppend<S2>,
<<() as TupleAppend<S3>>::Output as TupleAppend<S2>>::Output: TupleAppend<S1>,
<<<() as TupleAppend<S3>>::Output as TupleAppend<S2>>::Output as TupleAppend<S1>>::Output:
TupleAppend<S0>,
{
type Output = <<<<() as TupleAppend<S3>>::Output as TupleAppend<S2>>::Output as TupleAppend<
S1,
>>::Output as TupleAppend<S0>>::Output;
}
When manually implemented, this functions correctly in the context of the larger project, I just can't get the macros to expand how I need them to.