Macro is not expanded in `where` clause

I'm trying to write a macro that will output trait implementations for tuples of different sizes with generic constraints. I've simplified my case to the essentials (This is made to bind functions of unknown signature to a context, from which the arguments would be taken for the function invocation).
Given:

trait TestTrait<T, U>
{
    fn func(&self, _:T) -> U;
}

trait OtherTrait<T> {}

I want to get:

impl<Func, T, P1, P2, ...> TestTrait<T, (P1, P2, ...)> for Func
    where 
        Func : Fn((P1, P2, ...)) -> T,
        T : OtherTrait<P1> + OtherTrait<P2> + ...
{
    fn func(&self, _:T) -> (P1, P2, ...)
    {
        unimplemented!()
    }
}

I wrote a macro for that, but a nested macro is not expanded:

macro_rules! constraint {
    ( $head:ident, $( $tail:ident, )* ) => {
        + OtherTrait<$head> constraint!($tail)
    };

    () => {};
}

macro_rules! test {
    ( $head:ident, $( $tail:ident, )* ) => {
        impl<Func, T, $head, $( $tail ),* > TestTrait<T, $head, ( $( $tail ),* )> for Func
            where
                Func : Fn(( $head, $( $tail ),* )) -> T,
                T : constraint!($head, $( $tail ),* )
                           // ^ expected one of `(`, `+`, `,`, `::`, `<`, or `{`, found `!`
        {
            fn func(&self, _:T) -> ( $head, $( $tail )*, )
            {
                unimplemented!()
            }
        }
    };

    () => {};
}

test!(P1, P2, P3, );

I also tried including T : part in the macro and writing a list of T : OtherTrait<T?>, but got other errors. Is it impossible to do that or am I missing something?

1 Like

I removed head and tail since I didn't see any unique behavior (if you need it in the actual implementation it's 100% possible to put them back). Also if you are going to make this macro for N types, I'd go with a second macro.
Here's the playground.

The basic problem is that macros can only expand into specific types of things. This is so that they can be incorporated directly into the grammar and parsed as part of the AST without needing to knowing exactly what tokens they expand into. The downside is that, thanks to this, macros don't work in many places. The upshot is that this makes it possible for e.g. 2 * m!() to expand into 2 * (3 + 5) without needing to explicitly put parentheses.

The legal positions are:

  • Items. (e.g. fn, trait, struct, ...)
  • Expressions.
  • A statement list.
  • Types.
  • Patterns.

You're trying to use it where trait bounds are expected; that's not on the list.

You can read more about this limitation (and techniques for working around it) here: https://danielkeep.github.io/tlborm/book/README.html

3 Likes

Helped a lot, thanx.
Actual implementation

This is my version with two macros, I prefer it this way because it doesn't complicate the implementation as much. And commas are also a bit simpler to get right.
But that's just my way of doing things, using a single macro works absolutely fine.

1 Like

I see. It does look better that way.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.