Matching tuple-like enum variants in a macro, where enum type and variants are metavariables: how do I write the matching pattern?

Viewed 685

Concisely put into Rust code, I'm trying to generate a pattern match like this:

if let Foo::Variant(_) = value {}
//     ^^^^^^^^^^^^^^^

in a macro, with both Foo (a type) and Variant (an identifier) passed to the macro as metavariables. In the real use case, I'm generating a match instead of an if let and am using multiple variants of the same enum, but if let resulted in a shorter reproducible example.

This works with simple enums:

enum Foo {
    Variant,
}

macro_rules! match_enum {
    (
        $value:ident: <$enum:ty>::$variant:ident
    ) => {
        if let <$enum>::$variant = $value {}
    };
}

fn main() {
    let foo = Foo::Variant;
    match_enum!(foo: <Foo>::Variant);
}

This compiles.

However, when I make the enum variant tuple-like, it breaks (changes highlighted):

enum Foo {
    Variant(usize),
//         ^^^^^^^
}

macro_rules! match_enum {
    (
        $value:ident: <$enum:ty>::$variant:ident
    ) => {
        if let <$enum>::$variant(_) = $value {}
//                              ^^^
    };
}

fn main() {
    let foo = Foo::Variant(0);
//                        ^^^
    match_enum!(foo: <Foo>::Variant);
}
   |         if let <$enum>::$variant(_) = $value {}
   |                -----------------^^^ unexpected `(` after qualified path
   |                |
   |                the qualified path
...
   |     match_enum!(foo: <Foo>::Variant);
   |     --------------------------------- in this macro invocation

I have tried some variations, more or less blindly; $enum::$variant(_), <$enum::$variant>(_), <$enum::$variant>::(_) among them.

Is this possible? Am I perhaps using the wrong types of metavariables?

This question seems to be related, but it focuses on mixing unit and tuple variants, and has not been resolved.

1 Answers

The problem seems to be caused by the $enum metavariable, as the following slight modifications show:

macro_rules! match_enum {
    (
        $value:ident: <$enum:ty>::$variant:ident
    ) => {
        // does not fix the problem
        if let <$enum>::Variant(_) = $value {}
        // fixes the problem
        if let Bar::$variant(_) = $value {}
    };
}

As the problem happens on the syntax level, we can try change the syntactic makeup of the generated code, in particular by introducing a type alias. We then need to scope that type to not leak out of the macro:

macro_rules! match_enum {
    (
        $value:ident: <$enum:ty>::$variant:ident
    ) => {
        {
            type Enum = $enum;
            if let Enum::Variant(_) = $value {}
        }
    };
}

It's just a workaround, but it's clean enough.

Related