How to use default attribute when generating struct in rust macros

I want to make the attribute #[repr] optional, if no #[repr(type)] is provided, the default #[repr(u8)] will be used

if no #[repr] attribute is provided, the default attribute #[repr(u8)] should be used

i tired #[repr(if $repr { $repr } else { u8 })] but it doesn't seem to work

#[macro_export]
macro_rules! int_enum {
    (
        #[derive($($path:path),+)]
        $(#[$attr:meta])*
        $(#[repr($repr:ty)])?
        $vis:vis enum $Name:ident {
            $(
                $(#[$field_attr:meta])*
                $variant:ident = $index:tt,
            )*
        }
    ) => {
        $crate::macros::paste::paste! {
            #[derive(Debug,Clone,Copy,$crate::Serialize_repr,$crate::Deserialize_repr,enum_sql::IntEnumFromSql,enum_sql::IntEnumToSql , $($path),*)]
            $(#[$attr])*
            #[serde(try_from="i16", into="i16")]
            #[repr(if $repr ? {$repr}: {u8})]
            $vis enum $Name {
                $(
                    $(#[$field_attr])*
                    $variant = $index,
                )*
            }

            impl $Name {
               pub fn as_i16(&self) -> i16 {
                    use $Name::*;
                    match self {
                        $($variant => $index,)*
                    }
                }
            }

            impl From<$Name> for i16 {
                fn from(status: $Name) -> i16 {
                    status.as_i16()
                }
            }

            impl TryFrom<i16> for $Name {
                type Error = $crate::Error;

                fn try_from(code: i16) -> $crate::Result<Self> {
                    use $Name::*;
                    match code {
                        $($index => Ok($variant),)*
                        _ => Err($crate::Error::invalid("invalid_status_code").set_message(format!("unexpected  status: {}", code)))
                    }
                }
            }
        }
    };
    (
        $(#[$attr:meta])*
        $vis:vis enum $Name:ident {
            $(
                $(#[$field_attr:meta])*
                $variant:ident = $index:tt,
            )*
        }
    ) => {
        $crate::macros::paste::paste! {
            #[derive(Debug,Clone,Copy,$crate::Serialize_repr,$crate::Deserialize_repr,enum_sql::IntEnumFromSql,enum_sql::IntEnumToSql)]
            $(#[$attr])*
            #[repr(u8)]
            $vis enum $Name {
                $(
                    $(#[$field_attr])*
                    $variant = $index,
                )*
            }

            impl $Name {
               pub fn as_i16(&self) -> i16 {
                    use $Name::*;
                    match self {
                        $($variant => $index,)*
                    }
                }
            }

            impl From<$Name> for i16 {
                fn from(status: $Name) -> i16 {
                    status.as_i16()
                }
            }

            impl TryFrom<i16> for $Name {
                type Error = $crate::Error;

                fn try_from(code: i16) -> $crate::Result<Self> {
                    use $Name::*;
                    match code {
                        $($index => Ok($variant),)*
                        _ => Err($crate::Error::invalid("invalid_status_code").set_message(format!("unexpected  status: {}", code)))
                    }
                }
            }
        }
    }
}

stackoverflow

if you need to support arbitrary attributes, detecting if certain attribute exists is only possible with a proc-macro.

using declarative macro, you can only parse very limited form of inputs. for example, the following code uses two rules, this can do what you described (checking an "optional" #[repr(xxx)] attribute), ONLY IF the repr attribute, when present, is guaranteed to be the first attribute:

macro_rules! int_enum {
    // first rule requires a `#[repr(xxx)]` attribute first
    {
        #[repr($repr:ty)]
        $(#[$attr:meta])*
        $vis:vis enum $Name:ident { ...  }
    } => { ... };
    // if first rule fails, fall through the second rule,
    // which forward the tokens to the first rule recursively,
    // but prepended with the default `repr` attribute
    {
        $(#[$attr:meta])*
        $vis:vis enum $Name:ident { ... }
    } => {
        int_enum! {
            #[repr(u8)]
            $(#[$attr])*
            $vis enum $Name {...}
        }
    };
}

hi @nerditation thankyou buddy

technically, it's possible to write a declarative macro to parse what's in your original example (more or less), but it's usually very complicated and not worth the effort to make it able to parse the complete syntax.

the idea is simple: since the attributes of a enum definition is just a lienar sequence, it's no difference from other token list parsing, you incrementally match them one by one and put the result into different groups so the final rule can differentiate the cases.

just for the fun of it, here's one way to do it (be warned: it's horrible! and I intentionally wrote it very verbosely too), you can click "tools" button, then the "expand macros" command, if you are interested in how the expansion looks

1 Like

salute You're a devil genius

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.