#[macro_export]
macro_rules! injective_enum_map {
{ $enum_ty:ty, $into:ty, $try_from:ty, $($body:tt)* } => {
$crate::__impl_from_enum! { $enum_ty, $into, $($body)* }
$crate::__impl_enum_try_from! { $enum_ty, $try_from, $($body)* }
};
{ $enum_ty:ty, $into:ty, $try_from:ty } => {
$crate::__impl_from_enum! { $enum_ty, $into }
$crate::__impl_enum_try_from! { $enum_ty, $try_from }
};
{ $enum_ty:ty, $both:ty, $($body:tt)* } => {
$crate::__impl_from_enum! { $enum_ty, $both, $($body)* }
$crate::__impl_enum_try_from! { $enum_ty, $both, $($body)* }
};
{ $enum_ty:ty, $both:ty } => {
$crate::__impl_from_enum! { $enum_ty, $both }
$crate::__impl_enum_try_from! { $enum_ty, $both }
};
}
#[cfg(test)]
mod tests {
use crate::injective_enum_map;
#[test]
fn empty_both_specified() {
#[derive(Debug, PartialEq, Eq)]
enum Empty {}
injective_enum_map! {Empty, u8, u32}
assert_eq!(Empty::try_from(2_u32), Err(()));
}
#[test]
fn empty_one_specified() {
#[derive(Debug, PartialEq, Eq)]
enum Empty {}
injective_enum_map! {Empty, u8}
assert_eq!(Empty::try_from(2_u8), Err(()));
}
#[test]
fn nonempty_both_specified() {
#[derive(Debug, PartialEq, Eq)]
enum AtMostTwo {
Zero,
One,
Two,
}
injective_enum_map! {
AtMostTwo, u8, u32,
Zero <=> 0,
One <=> 1,
Two <=> 2,
}
assert_eq!(u8::from(AtMostTwo::One), 1_u8);
assert_eq!(AtMostTwo::try_from(2_u32), Ok(AtMostTwo::Two));
assert_eq!(AtMostTwo::try_from(4_u32), Err(()));
}
#[test]
fn nonempty_one_specified() {
#[derive(Debug, PartialEq, Eq)]
enum AtMostTwo {
Zero,
One,
Two,
}
injective_enum_map! {
AtMostTwo, u32,
Zero <=> 0,
One <=> 1,
Two <=> 2,
}
assert_eq!(u32::from(AtMostTwo::One), 1_u32);
assert_eq!(AtMostTwo::try_from(2_u32), Ok(AtMostTwo::Two));
assert_eq!(AtMostTwo::try_from(4_u32), Err(()));
}
#[test]
fn nonempty_to_enum_bijective() {
#[derive(Debug, PartialEq, Eq)]
enum Enum {
One,
Two,
Three,
}
#[derive(Debug, PartialEq, Eq)]
enum Other {
Uno,
Dos,
Tres,
}
injective_enum_map! {
Enum, Other, Other,
One <=> Other::Uno,
Two <=> Other::Dos,
Three <=> Other::Tres,
}
assert_eq!(Other::from(Enum::Three), Other::Tres);
assert_eq!(Enum::try_from(Other::Uno), Ok(Enum::One));
}
#[test]
fn nonempty_to_enum_injective() {
#[derive(Debug, PartialEq, Eq)]
enum Enum {
One,
Two,
Three,
}
#[derive(Debug, PartialEq, Eq)]
enum Other {
Uno,
Dos,
Tres,
Cuatro,
}
injective_enum_map! {
Enum, Other, Other,
One <=> Other::Uno,
Two <=> Other::Dos,
Three <=> Other::Tres,
}
assert_eq!(Other::from(Enum::Three), Other::Tres);
assert_eq!(Enum::try_from(Other::Uno), Ok(Enum::One));
assert_eq!(Enum::try_from(Other::Cuatro), Err(()));
}
#[test]
fn enum_to_string() {
#[derive(Debug, PartialEq, Eq)]
enum Empty {}
#[derive(Debug, PartialEq, Eq)]
enum Nonempty {
Something,
}
injective_enum_map! {Empty, &'static str, &str}
injective_enum_map! {
Nonempty, &'static str, &str,
Something <=> "Something",
}
assert_eq!(Empty::try_from("Anything"), Err(()));
assert_eq!(Nonempty::try_from("Something"), Ok(Nonempty::Something));
assert_eq!(Nonempty::try_from("Nothing"), Err(()));
}
#[test]
fn trailing_commas() {
enum Empty {}
enum Nonempty {
Something,
}
injective_enum_map!(Empty, u8, u8);
injective_enum_map! { Empty, u16 };
injective_enum_map! {
Empty, i8, i8,
};
injective_enum_map! { Empty, i16, };
injective_enum_map!(Nonempty, u8, u8, Something <=> 0);
injective_enum_map! { Nonempty, u16, Something <=> 0};
injective_enum_map! {
Nonempty, i8, i8, Something <=> 0,
};
injective_enum_map! { Nonempty, i16, Something <=> 0,};
}
#[test]
fn non_unit_variant() {
#[derive(Debug, PartialEq)]
enum Thing {
Player {
name: &'static str,
hp: u32,
strength: f32,
},
PhysicalObject {
name: &'static str,
hp: u32,
},
Spell {
name: &'static str,
strength: f32,
},
Marker(&'static str),
HardcodedMarker(&'static str, u32),
}
type Stuff = (u8, Option<&'static str>, Option<u32>, Option<f32>);
injective_enum_map! {
Thing, Stuff,
Player { name, hp, strength } <=> (0, Some(name), Some(hp), Some(strength)),
PhysicalObject { name, hp } <=> (1, Some(name), Some(hp), None),
Spell { name, strength } <=> (2, Some(name), None, Some(strength)),
Marker(name) <=> (3, Some(name), None, None),
HardcodedMarker(name, id) <=> (4, Some(name), Some(id), None),
}
assert_eq!(
Stuff::from(Thing::Player { name: "person", hp: 2, strength: 1.5 }),
(0, Some("person"), Some(2), Some(1.5)),
);
assert_eq!(
Stuff::from(Thing::Marker("place")),
(3, Some("place"), None, None),
);
assert_eq!(
Thing::try_from((1_u8, Some("object"), Some(100_u32), None)),
Ok(Thing::PhysicalObject { name: "object", hp: 100 }),
);
assert_eq!(
Thing::try_from((1_u8, Some("object"), Some(100_u32), Some(1e30))),
Err(()),
);
}
#[test]
fn intentionally_non_injective() {
#[derive(Debug, PartialEq, Eq)]
enum Enum {
One,
Two,
Three,
}
#[derive(Debug, PartialEq, Eq)]
enum Other {
Uno,
Dos,
Tres,
Cuatro,
}
#[allow(warnings)]
{
injective_enum_map! {
Other, Enum,
Uno <=> Enum::One,
Dos <=> Enum::Two,
Tres <=> Enum::Three,
Cuatro <=> Enum::Three,
}
}
assert_eq!(Other::try_from(Enum::Three), Ok(Other::Tres));
assert_eq!(Enum::from(Other::Uno), Enum::One);
assert_eq!(Enum::from(Other::Cuatro), Enum::Three);
}
}
#[cfg(doctest)]
pub mod compile_fail_tests {
pub fn _nonempty_but_nothing_provided() {}
pub fn _nonempty_but_not_enough_provided() {}
pub fn _nonempty_not_injective_warning() {}
pub fn _nonempty_not_surjective_warning() {}
pub fn _nonempty_to_enum_not_injective_warning() {}
pub fn _missing_comma() {}
}