use serde::de::{self, Visitor};
use std::any::type_name;
use std::fmt::{self, Debug, Display};
use std::marker::PhantomData;
use std::str::FromStr;
#[derive(Debug, PartialEq, Eq)]
pub struct UnknownEnumVariant<E> {
pub raw_value: String,
pub type_container: PhantomData<E>,
pub variants: &'static [&'static str],
}
impl<E> Display for UnknownEnumVariant<E> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let type_name = type_name::<E>();
let type_name = type_name.rsplit("::").next().unwrap_or(type_name);
write!(
f,
"unknown variant {:?} of enum `{}`, expected one of: {:?}",
self.raw_value, type_name, self.variants
)
}
}
impl<E: Debug> std::error::Error for UnknownEnumVariant<E> {}
#[macro_export]
macro_rules! define_str_enum {
(
$(#![$macro_attr:ident])?
$(#[$emeta:meta])*
$vis:vis enum $enum:ident {
$(
$(#[$varmeta:meta])*
$variant:ident = $display:literal $(= $num:literal)?,
)+
}
) => {
$crate::define_enum_with_introspection! {
$(#[$emeta])*
$vis enum $enum {
$(
$(#[$varmeta])*
$variant $(= $num)?,
)+
}
}
#[allow(dead_code)]
impl $enum {
$vis const fn as_str(&self) -> &'static str {
match self {
$(
Self::$variant => $display,
)+
}
}
$vis const fn as_cstr(&self) -> &'static ::std::ffi::CStr {
match self {
$(
Self::$variant => unsafe {
::std::ffi::CStr::from_bytes_with_nul_unchecked(
::std::concat!($display, "\0").as_bytes()
)
}
)+
}
}
#[inline(always)]
$vis const fn values() -> &'static [&'static str] {
&[ $( $display, )+ ]
}
}
impl ::std::convert::AsRef<str> for $enum {
#[inline(always)]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl ::std::ops::Deref for $enum {
type Target = str;
#[inline(always)]
fn deref(&self) -> &str {
self.as_str()
}
}
impl ::std::convert::From<$enum> for ::std::string::String {
#[inline(always)]
fn from(e: $enum) -> Self {
e.as_str().into()
}
}
impl ::std::convert::From<$enum> for &'static str {
#[inline(always)]
fn from(e: $enum) -> Self {
e.as_str()
}
}
impl ::std::str::FromStr for $enum {
type Err = $crate::define_str_enum::UnknownEnumVariant<$enum>;
fn from_str(s: &str) -> ::std::result::Result<Self, Self::Err> {
use ::std::marker::PhantomData;
use $crate::define_str_enum::UnknownEnumVariant;
use ::std::result::Result::{Ok, Err};
$($crate::define_str_enum! { @attr $macro_attr
let s = s.trim();
let s = s.to_lowercase();
let s = s.as_str();
})?
match s {
$(
$display => Ok(Self::$variant),
)+
_ => Err(UnknownEnumVariant {
raw_value: s.into(),
type_container: PhantomData,
variants: Self::values()
}),
}
}
}
impl ::std::fmt::Display for $enum {
#[inline(always)]
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
f.write_str(self.as_str())
}
}
impl serde::Serialize for $enum {
#[inline(always)]
fn serialize<S>(&self, serializer: S) -> ::std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.as_str())
}
}
impl<'de> serde::Deserialize<'de> for $enum {
#[inline]
fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use ::std::result::Result::Ok;
use serde::de::Error;
let tmp = deserializer.deserialize_str($crate::define_str_enum::FromStrVisitor::<$enum>::default())?;
let res = tmp.parse().map_err(|_| {
Error::unknown_variant(&tmp, Self::values())
})?;
Ok(res)
}
}
impl<L: $crate::tlua::AsLua> $crate::tlua::Push<L> for $enum {
type Err = $crate::tlua::Void;
#[inline(always)]
fn push_to_lua(&self, lua: L) -> $crate::tlua::PushResult<L, Self> {
$crate::tlua::PushInto::push_into_lua(self.as_str(), lua)
}
}
impl<L: $crate::tlua::AsLua> $crate::tlua::PushOne<L> for $enum {}
impl<L: $crate::tlua::AsLua> $crate::tlua::PushInto<L> for $enum {
type Err = $crate::tlua::Void;
#[inline(always)]
fn push_into_lua(self, lua: L) -> $crate::tlua::PushIntoResult<L, Self> {
$crate::tlua::PushInto::push_into_lua(self.as_str(), lua)
}
}
impl<L: $crate::tlua::AsLua> $crate::tlua::PushOneInto<L> for $enum {}
impl<L: $crate::tlua::AsLua> $crate::tlua::LuaRead<L> for $enum {
#[inline]
fn lua_read_at_position(
lua: L,
index: ::std::num::NonZeroI32
) -> $crate::tlua::ReadResult<Self, L> {
let s = $crate::tlua::StringInLua::lua_read_at_position(lua, index)?;
match s.parse() {
Ok(v) => Ok(v),
Err(_) => {
let e = $crate::tlua::WrongType::info("reading string enum")
.expected(format!("one of {:?}", Self::values()))
.actual(format!("string '{}'", &*s));
Err((s.into_inner(), e))
}
}
}
}
impl $crate::msgpack::Encode for $enum {
fn encode(
&self,
w: &mut impl std::io::Write,
context: &$crate::msgpack::Context,
) -> std::result::Result<(), $crate::msgpack::EncodeError> {
<&str as $crate::msgpack::Encode>::encode(&self.as_str(), w, context)
}
}
impl<'de> $crate::msgpack::Decode<'de> for $enum {
fn decode(r: &mut &'de [u8], _context: &$crate::msgpack::Context) -> std::result::Result<Self, $crate::msgpack::DecodeError> {
use $crate::msgpack::rmp;
let len = rmp::decode::read_str_len(r)
.map_err(|err| $crate::msgpack::DecodeError::new::<Self>(err))?;
let decoded_variant = r.get(0..(len as usize))
.ok_or_else(|| $crate::msgpack::DecodeError::new::<Self>("not enough data"))?;
let decoded_variant_str = std::str::from_utf8(decoded_variant)
.map_err(|err| $crate::msgpack::DecodeError::new::<Self>(err))?;
let res = match decoded_variant_str {
$(
$display => Ok(Self::$variant),
)+
v => Err({
$crate::msgpack::DecodeError::new::<$enum>(
format!("unknown enum variant `{}`, expected on of {:?}", v, Self::values())
)
}),
};
*r = &r[len as usize..];
res
}
}
$crate::define_str_enum_extra!{ $vis $enum }
};
(@attr coerce_from_str $($then:tt)*) => {
$($then)*
};
(@attr $other:ident $($then:tt)*) => {
compile_error!(
concat!("unknown attribute: ", stringify!($other))
)
};
}
#[cfg(feature = "extra_impls")]
#[macro_export]
macro_rules! define_str_enum_extra {
($vis:vis $enum:ident) => {
#[allow(dead_code)]
impl $enum {
#[inline]
$vis const fn to_smolstr(self) -> ::smol_str::SmolStr {
::smol_str::SmolStr::new_static(self.as_str())
}
}
}
}
#[cfg(not(feature = "extra_impls"))]
#[macro_export]
macro_rules! define_str_enum_extra {
($($any:tt)*) => {};
}
#[macro_export]
macro_rules! define_enum_with_introspection {
(
$(#![$macro_attr:ident])?
$(#[$emeta:meta])*
$vis:vis enum $enum:ident {
$(
$(#[$varmeta:meta])*
$variant:ident $(= $discriminant:expr)?
),+
$(,)?
}
) => {
$(#[$emeta])*
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
$vis enum $enum {
$(
$(#[$varmeta])*
$variant $(= $discriminant)?,
)+
}
#[allow(dead_code)]
impl $enum {
pub const VARIANTS: &'static [Self] = &[ $( Self::$variant, )+ ];
pub const MIN: Self = {
let mut i = 1;
let mut min = $enum::VARIANTS[0];
while i < $enum::VARIANTS.len() {
if ($enum::VARIANTS[i] as i64) < (min as i64) {
min = $enum::VARIANTS[i];
}
i += 1;
}
min
};
pub const MAX: Self = {
let mut i = 1;
let mut max = $enum::VARIANTS[0];
while i < $enum::VARIANTS.len() {
if ($enum::VARIANTS[i] as i64) > (max as i64) {
max = $enum::VARIANTS[i];
}
i += 1;
}
max
};
pub const DISCRIMINANTS_ARE_SUBSEQUENT: bool = {
let len = $enum::VARIANTS.len() as u64;
assert!(len <= i64::MAX as u64, "that's too many variants, my brother in Christ");
let actual_span = i64::checked_sub($enum::MAX as _, $enum::MIN as _);
if let Some(actual_span) = actual_span {
actual_span == (len - 1) as i64
} else {
false
}
};
pub const fn variant_name(&self) -> &'static str {
match self {
$( Self::$variant => ::std::stringify!($variant), )+
}
}
pub const fn from_i64(n: i64) -> Option<Self> {
if !$enum::DISCRIMINANTS_ARE_SUBSEQUENT {
return match n {
$( n if n == Self::$variant as i64 => Some(Self::$variant), )+
_ => None,
};
}
if n < $enum::MIN as i64 || n > $enum::MAX as i64 {
return None;
}
unsafe {
const SIZE: usize = std::mem::size_of::<$enum>();
match SIZE {
8 => Some(*(&(n as i64) as *const _ as *const $enum)),
4 => Some(*(&(n as i32) as *const _ as *const $enum)),
2 => Some(*(&(n as i16) as *const _ as *const $enum)),
1 => Some(*(&(n as i8) as *const _ as *const $enum)),
_ => { panic!("unreachable"); }
}
}
}
}
macro_rules! impl_try_from_int {
($t:ty) => {
impl std::convert::TryFrom<$t> for $enum {
type Error = $t;
#[inline(always)]
fn try_from(n: $t) -> std::result::Result<Self, $t> {
Self::from_i64(n as _).ok_or(n)
}
}
}
}
impl_try_from_int! { i8 }
impl_try_from_int! { u8 }
impl_try_from_int! { i16 }
impl_try_from_int! { u16 }
impl_try_from_int! { i32 }
impl_try_from_int! { u32 }
impl_try_from_int! { i64 }
impl_try_from_int! { u64 }
impl_try_from_int! { isize }
impl_try_from_int! { usize }
}
}
#[derive(Clone, Copy)]
pub struct FromStrVisitor<T>(PhantomData<T>);
impl<T> Default for FromStrVisitor<T> {
fn default() -> Self {
Self(Default::default())
}
}
impl<'de, Err: Display, T: FromStr<Err = Err>> Visitor<'de> for FromStrVisitor<T> {
type Value = T;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a string")
}
fn visit_borrowed_str<E>(self, value: &'de str) -> Result<Self::Value, E>
where
E: de::Error,
{
value.parse().map_err(de::Error::custom)
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
value.parse().map_err(de::Error::custom)
}
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
where
E: de::Error,
{
value.parse().map_err(de::Error::custom)
}
}
#[allow(clippy::assertions_on_constants)]
#[allow(clippy::redundant_pattern_matching)]
#[cfg(feature = "internal_test")]
mod tests {
use super::*;
use crate::util::str_eq;
define_enum_with_introspection! {
#[allow(clippy::enum_clike_unportable_variant)]
enum MyEnum {
First,
EvenFirster = -1,
Foo = 1,
Bar = 2,
Xxx = 1027,
Yyy,
Baz = 3,
ISizeMax = isize::MAX,
}
}
#[rustfmt::skip]
const _: () = {
assert!(matches!(MyEnum::MIN, MyEnum::EvenFirster));
assert!(matches!(MyEnum::MAX, MyEnum::ISizeMax));
assert!(MyEnum::VARIANTS.len() == 8);
assert!(!MyEnum::DISCRIMINANTS_ARE_SUBSEQUENT);
assert!(matches!(MyEnum::from_i64(-1), Some(MyEnum::EvenFirster)));
assert!(matches!(MyEnum::from_i64(0), Some(MyEnum::First)));
assert!(matches!(MyEnum::from_i64(1), Some(MyEnum::Foo)));
assert!(matches!(MyEnum::from_i64(2), Some(MyEnum::Bar)));
assert!(matches!(MyEnum::from_i64(3), Some(MyEnum::Baz)));
assert!(matches!(MyEnum::from_i64(1027), Some(MyEnum::Xxx)));
assert!(matches!(MyEnum::from_i64(1028), Some(MyEnum::Yyy)));
assert!(matches!(MyEnum::from_i64(isize::MAX as _), Some(MyEnum::ISizeMax)));
assert!(matches!(MyEnum::from_i64(-2), None));
assert!(str_eq(MyEnum::EvenFirster.variant_name(), "EvenFirster"));
};
define_enum_with_introspection! {
#[repr(i64)]
enum BottomMost3 {
Smallest = i64::MIN,
NextSmallest,
NextNextSmallest,
}
}
#[rustfmt::skip]
const _: () = {
assert!(matches!(BottomMost3::MIN, BottomMost3::Smallest));
assert!(matches!(BottomMost3::MAX, BottomMost3::NextNextSmallest));
assert!(BottomMost3::VARIANTS.len() == 3);
assert!(BottomMost3::DISCRIMINANTS_ARE_SUBSEQUENT);
assert!(matches!(BottomMost3::from_i64(i64::MIN), Some(BottomMost3::Smallest)));
assert!(matches!(BottomMost3::from_i64(i64::MIN + 1), Some(BottomMost3::NextSmallest)));
assert!(matches!(BottomMost3::from_i64(i64::MIN + 2), Some(BottomMost3::NextNextSmallest)));
};
define_enum_with_introspection! {
#[repr(i64)]
enum TopMost3 {
PrevPrevLargest = i64::MAX - 2,
PrevLargest,
Largest,
}
}
#[rustfmt::skip]
const _: () = {
assert!(matches!(TopMost3::MIN, TopMost3::PrevPrevLargest));
assert!(matches!(TopMost3::MAX, TopMost3::Largest));
assert!(TopMost3::VARIANTS.len() == 3);
assert!(TopMost3::DISCRIMINANTS_ARE_SUBSEQUENT);
assert!(matches!(TopMost3::from_i64(i64::MAX - 2), Some(TopMost3::PrevPrevLargest)));
assert!(matches!(TopMost3::from_i64(i64::MAX - 1), Some(TopMost3::PrevLargest)));
assert!(matches!(TopMost3::from_i64(i64::MAX), Some(TopMost3::Largest)));
};
define_enum_with_introspection! {
#[repr(u8)] enum SingleVariant { U8Max = u8::MAX }
}
#[rustfmt::skip]
const _: () = {
assert!(matches!(SingleVariant::from_i64(u8::MAX as _), Some(SingleVariant::U8Max)));
assert!(matches!(SingleVariant::MIN, SingleVariant::U8Max));
assert!(matches!(SingleVariant::MAX, SingleVariant::U8Max));
assert!(SingleVariant::VARIANTS.len() == 1);
assert!(SingleVariant::DISCRIMINANTS_ARE_SUBSEQUENT);
};
define_enum_with_introspection! {
#[repr(u8)] enum AutoDiscriminantsU8 { A = u8::MIN, B, C = u8::MAX, }
}
#[rustfmt::skip]
const _: () = {
assert!(matches!(AutoDiscriminantsU8::MIN, AutoDiscriminantsU8::A));
assert!(matches!(AutoDiscriminantsU8::MAX, AutoDiscriminantsU8::C));
assert!(matches!(AutoDiscriminantsU8::from_i64(u8::MIN as _), Some(AutoDiscriminantsU8::A)));
assert!(matches!(AutoDiscriminantsU8::from_i64(u8::MAX as _), Some(AutoDiscriminantsU8::C)));
assert!(AutoDiscriminantsU8::VARIANTS.len() == 3);
assert!(!AutoDiscriminantsU8::DISCRIMINANTS_ARE_SUBSEQUENT);
};
define_enum_with_introspection! {
#[repr(i8)] enum AutoDiscriminantsI8 { A = i8::MIN, B, C = i8::MAX, }
}
#[rustfmt::skip]
const _: () = {
assert!(matches!(AutoDiscriminantsI8::MIN, AutoDiscriminantsI8::A));
assert!(matches!(AutoDiscriminantsI8::MAX, AutoDiscriminantsI8::C));
assert!(matches!(AutoDiscriminantsI8::from_i64(i8::MIN as _), Some(AutoDiscriminantsI8::A)));
assert!(matches!(AutoDiscriminantsI8::from_i64(i8::MAX as _), Some(AutoDiscriminantsI8::C)));
assert!(AutoDiscriminantsI8::VARIANTS.len() == 3);
assert!(!AutoDiscriminantsI8::DISCRIMINANTS_ARE_SUBSEQUENT);
};
define_enum_with_introspection! {
#[repr(u16)] enum AutoDiscriminantsU16 { A = u16::MIN, B, C = u16::MAX, }
}
#[rustfmt::skip]
const _: () = {
assert!(matches!(AutoDiscriminantsU16::MIN, AutoDiscriminantsU16::A));
assert!(matches!(AutoDiscriminantsU16::MAX, AutoDiscriminantsU16::C));
assert!(matches!(AutoDiscriminantsU16::from_i64(u16::MIN as _), Some(AutoDiscriminantsU16::A)));
assert!(matches!(AutoDiscriminantsU16::from_i64(u16::MAX as _), Some(AutoDiscriminantsU16::C)));
assert!(AutoDiscriminantsU16::VARIANTS.len() == 3);
assert!(!AutoDiscriminantsU16::DISCRIMINANTS_ARE_SUBSEQUENT);
};
define_enum_with_introspection! {
#[repr(i16)] enum AutoDiscriminantsI16 { A = i16::MIN, B, C = i16::MAX, }
}
#[rustfmt::skip]
const _: () = {
assert!(matches!(AutoDiscriminantsI16::MIN, AutoDiscriminantsI16::A));
assert!(matches!(AutoDiscriminantsI16::MAX, AutoDiscriminantsI16::C));
assert!(matches!(AutoDiscriminantsI16::from_i64(i16::MIN as _), Some(AutoDiscriminantsI16::A)));
assert!(matches!(AutoDiscriminantsI16::from_i64(i16::MAX as _), Some(AutoDiscriminantsI16::C)));
assert!(AutoDiscriminantsI16::VARIANTS.len() == 3);
assert!(!AutoDiscriminantsI16::DISCRIMINANTS_ARE_SUBSEQUENT);
};
define_enum_with_introspection! {
#[repr(u32)] enum AutoDiscriminantsU32 { A = u32::MIN, B, C = u32::MAX, }
}
#[rustfmt::skip]
const _: () = {
assert!(matches!(AutoDiscriminantsU32::MIN, AutoDiscriminantsU32::A));
assert!(matches!(AutoDiscriminantsU32::MAX, AutoDiscriminantsU32::C));
assert!(matches!(AutoDiscriminantsU32::from_i64(u32::MIN as _), Some(AutoDiscriminantsU32::A)));
assert!(matches!(AutoDiscriminantsU32::from_i64(u32::MAX as _), Some(AutoDiscriminantsU32::C)));
assert!(AutoDiscriminantsU32::VARIANTS.len() == 3);
assert!(!AutoDiscriminantsU32::DISCRIMINANTS_ARE_SUBSEQUENT);
};
define_enum_with_introspection! {
#[repr(i32)] enum AutoDiscriminantsI32 { A = i32::MIN, B, C = i32::MAX, }
}
#[rustfmt::skip]
const _: () = {
assert!(matches!(AutoDiscriminantsI32::MIN, AutoDiscriminantsI32::A));
assert!(matches!(AutoDiscriminantsI32::MAX, AutoDiscriminantsI32::C));
assert!(matches!(AutoDiscriminantsI32::from_i64(i32::MIN as _), Some(AutoDiscriminantsI32::A)));
assert!(matches!(AutoDiscriminantsI32::from_i64(i32::MAX as _), Some(AutoDiscriminantsI32::C)));
assert!(AutoDiscriminantsI32::VARIANTS.len() == 3);
assert!(!AutoDiscriminantsI32::DISCRIMINANTS_ARE_SUBSEQUENT);
};
define_enum_with_introspection! {
#[repr(u64)] enum AutoDiscriminantsU64 { A = u64::MIN, B, C = u64::MAX, }
}
#[rustfmt::skip]
const _: () = {
assert!(matches!(AutoDiscriminantsU64::MIN, AutoDiscriminantsU64::C));
assert!(matches!(AutoDiscriminantsU64::MAX, AutoDiscriminantsU64::B));
assert!(matches!(AutoDiscriminantsU64::from_i64(u64::MIN as _), Some(AutoDiscriminantsU64::A)));
assert!(matches!(AutoDiscriminantsU64::from_i64(u64::MAX as _), Some(AutoDiscriminantsU64::C)));
assert!(AutoDiscriminantsU64::VARIANTS.len() == 3);
assert!(AutoDiscriminantsU64::DISCRIMINANTS_ARE_SUBSEQUENT);
};
define_enum_with_introspection! {
#[repr(i64)] enum AutoDiscriminantsI64 { A = i64::MIN, B, C = i64::MAX, }
}
#[rustfmt::skip]
const _: () = {
assert!(matches!(AutoDiscriminantsI64::MIN, AutoDiscriminantsI64::A));
assert!(matches!(AutoDiscriminantsI64::MAX, AutoDiscriminantsI64::C));
assert!(matches!(AutoDiscriminantsI64::from_i64(i64::MIN as _), Some(AutoDiscriminantsI64::A)));
assert!(matches!(AutoDiscriminantsI64::from_i64(i64::MAX as _), Some(AutoDiscriminantsI64::C)));
assert!(AutoDiscriminantsI64::VARIANTS.len() == 3);
assert!(!AutoDiscriminantsI64::DISCRIMINANTS_ARE_SUBSEQUENT);
};
define_str_enum! {
enum StrEnumWithIntrospection {
One = "Two" = 3,
Next = "autodiscriminant",
Food = "food" = 0xf00d,
}
}
#[rustfmt::skip]
const _: () = {
assert!(matches!(StrEnumWithIntrospection::MIN, StrEnumWithIntrospection::One));
assert!(matches!(StrEnumWithIntrospection::MAX, StrEnumWithIntrospection::Food));
assert!(StrEnumWithIntrospection::VARIANTS.len() == 3);
assert!(!StrEnumWithIntrospection::DISCRIMINANTS_ARE_SUBSEQUENT);
assert!(str_eq(StrEnumWithIntrospection::One.variant_name(), "One"));
assert!(str_eq(StrEnumWithIntrospection::One.as_str(), "Two"));
assert!(StrEnumWithIntrospection::One as i64 == 3);
assert!(str_eq(StrEnumWithIntrospection::Next.variant_name(), "Next"));
assert!(str_eq(StrEnumWithIntrospection::Next.as_str(), "autodiscriminant"));
assert!(StrEnumWithIntrospection::Next as i64 == 4);
assert!(str_eq(StrEnumWithIntrospection::Food.variant_name(), "Food"));
assert!(str_eq(StrEnumWithIntrospection::Food.as_str(), "food"));
assert!(StrEnumWithIntrospection::Food as i64 == 0xf00d);
};
}