Can I use recursive macros in trait bounds like this?

51 views Asked by At

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.

0

There are 0 answers