Is there a way to pass a macro metavar as a part of a macro invocation?

I tried something like this.

#![feature(macro_metavar_expr)]

use std::fmt;

macro_rules! e_match {
    (<> $self:expr, $inner:ident, $($expr:tt)*) => {
        e_match!{
            <>,
            variants=[
                (E1, $($expr)*),
                (E2, $($expr)*),
            ], $self, $inner
        }

    };
    (<>, variants=[$(($v_id:ident, $($v_expr:tt)*),)+], $self:expr, $inner:ident) => {
        {
            macro_rules! e_match_ {
                ($$self:expr, $$inner:ident) => {
                    match $self {
                        $(
                            E::$v_id($inner)  => $($v_expr)*,
                        )+
                    }
                };
            }
            e_match_!($self, $inner)
        }
    };
}

#[derive(Debug, PartialEq)]
enum E {
    E1(u8),
    E2(u8),
}

#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq)]
enum ETag {
    E1,
    E2,
}


impl ETag {
    fn from_e(e: &E) -> Self {
        e_match!(<> e, _inner, Self::$v_id)
    }
}

impl fmt::Display for E {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        e_match!{
            <> self, _inner,
            f.write_str(concat!(stringify!(E), "::", stringify!($v_id)))
        }
    }
}

The first invocation fails to compile. And the second stringifies $v_id instead of the actual variant name.

I think this is due to macro hygiene. The $v_id is just an opaque identifier to the macros. Think of it this way: you shouldn't have to know the name used inside the macro.

If you want to experiment with macros, somebody else can probably explain the problem better than me. If you just want working code, try this:

use std::fmt;
use strum::EnumDiscriminants;

#[derive(Debug, PartialEq, EnumDiscriminants)]
#[strum_discriminants(name(ETag))]
enum E {
    E1(u8),
    E2(u8),
}

impl fmt::Display for E {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{:?}", ETag::from(self))
    }
}

fn main() {
    println!("{}", E::E1(5));
}

which uses the strum crate (strum = { version = "0.28.0", features = ["derive"] })

How about this?

use std::fmt;

macro_rules! e_match {
    ($dollar:tt$v_id:ident <> $self:expr, $inner:ident, $($expr:tt)*) => {
        {
            macro_rules! e_match_ {
                ($dollar$v_id:ident) => {
                    $($expr)*
                }
            }

            match $self {
                E::E1($inner) => e_match_!(E1),
                E::E2($inner) => e_match_!(E2),
            }
        }

    };
}

#[derive(Debug, PartialEq)]
enum E {
    E1(u8),
    E2(u8),
}

#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq)]
enum ETag {
    E1,
    E2,
}


impl ETag {
    fn from_e(e: &E) -> Self {
        e_match!($v_id <> e, _inner, Self::$v_id)
    }
}

impl fmt::Display for E {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        e_match!{
            $v_id <> self, _inner,
            f.write_str(concat!(stringify!(E), "::", stringify!($v_id)))
        }
    }
}
1 Like

This looks awesome. Thanks.