Skip to main content

discrimin_ant_proc/
lib.rs

1//! # Discrimin-Ant Proc
2//!
3//! Discrimin-Ant is a tiny library to make working with `enum` discriminants just a little easier.
4//!
5//! ---
6//!
7//! Discrimin-Ant was originally built to make it easier to marshal enums for use for
8//! software systems that do not or cannot use the Rust ecosystem.
9//! It is intended to be used with [Serde](https://crates.io/crates/serde) to
10//! marshal and send enums over a network or save them to a file.
11//! This can be done by implementing a custom `serde::Serialize` and
12//! `serde::Deserialize` that treats an enum as a struct or tuple instead of a
13//! variant, since the variant serialize methods only provide the index of the enum
14//! instead of its discriminant. This allows enums to be serialized in a way such
15//! that rearranging the order of enums does not change the discriminant serialized.
16//!
17//! The primary features of this crate are the `Discriminantable` trait and the
18//! `discriminant` attribute.
19//! The `discriminant` attribute is meant to take the place of the `repr`
20//! attribute on enums, and will generate a few blocks of code:
21//! - An `impl` for `Discriminantable` for the enum, using the enum's actual discriminants
22//! - A fieldless version of the enum (regardless of whether or not the enum is already fieldless)
23//! - An `impl` for `Discriminantable` on the fieldless version of the enum
24//! - Other utilities on the fieldless version to ease use
25//!
26//! Specifically, given the enum
27//! ```no_run
28//! use discrimin_ant_proc::discriminant;
29//! #[discriminant(u8)]
30//! pub enum ComplexUEnum {
31//!     One(i32) = 1,
32//!     Two(i32),
33//!     Five { x: u32 } = 5,
34//!     Six { x: u32 },
35//! }
36//! ```
37//! the result will be
38//!
39//! ```no_run
40//! #[repr(u8)]
41//! pub enum ComplexUEnum {
42//!     One(i32) = 1,
43//!     Two(i32),
44//!     Five { x: u32 } = 5,
45//!     Six { x: u32 },
46//! }
47//! impl ComplexUEnum {
48//!     #[doc = r" Returns the discriminant of [Self]."]
49//!     pub const fn discriminant(&self) -> u8 {
50//!         unsafe { *core::ptr::from_ref::<Self>(self).cast::<u8>() }
51//!     }
52//! }
53//! impl discrimin_ant::Discriminantable for ComplexUEnum {
54//!     type Discriminant = u8;
55//!
56//!     fn discriminant(&self) -> Self::Discriminant {
57//!         self.discriminant()
58//!     }
59//! }
60//! #[doc = "Fieldless representations of [ComplexUEnum]. Used to extract discriminants without fully constructing the enum."]
61//! #[repr(u8)]
62//! pub enum ComplexUEnum_ {
63//!     #[doc = "A fieldless version of [ComplexUEnum::One], used to extract the variant's discriminant without needing to fully construct it."]
64//!     One = 1,
65//!     #[doc = "A fieldless version of [ComplexUEnum::Two], used to extract the variant's discriminant without needing to fully construct it."]
66//!     Two = 1 + 1u8,
67//!     #[doc = "A fieldless version of [ComplexUEnum::Five], used to extract the variant's discriminant without needing to fully construct it."]
68//!     Five = 5,
69//!     #[doc = "A fieldless version of [ComplexUEnum::Six], used to extract the variant's discriminant without needing to fully construct it."]
70//!     Six = 5 + 1u8,
71//! }
72//! impl ComplexUEnum_ {
73//!     #[doc = r" Returns the discriminant of [Self]."]
74//!     pub const fn discriminant(&self) -> u8 {
75//!         unsafe { *core::ptr::from_ref::<Self>(self).cast::<u8>() }
76//!     }
77//! }
78//! impl discrimin_ant::Discriminantable for ComplexUEnum_ {
79//!     type Discriminant = u8;
80//!
81//!     fn discriminant(&self) -> Self::Discriminant {
82//!         self.discriminant()
83//!     }
84//! }
85//! impl TryFrom<u8> for ComplexUEnum_ {
86//!     type Error = ();
87//!     fn try_from(value: u8) -> Result<Self, Self::Error> {
88//!         if value == (1) {
89//!             return Ok(Self::One);
90//!         }
91//!         if value == (1 + 1u8) {
92//!             return Ok(Self::Two);
93//!         }
94//!         if value == (5) {
95//!             return Ok(Self::Five);
96//!         }
97//!         if value == (5 + 1u8) {
98//!             return Ok(Self::Six);
99//!         }
100//!         Err(())
101//!     }
102//! }
103//! impl From<&ComplexUEnum> for ComplexUEnum_ {
104//!     fn from(value: &ComplexUEnum) -> Self {
105//!         match value {
106//!             ComplexUEnum::One(..) => Self::One,
107//!             ComplexUEnum::Two(..) => Self::Two,
108//!             ComplexUEnum::Five { .. } => Self::Five,
109//!             ComplexUEnum::Six { .. } => Self::Six,
110//!         }
111//!     }
112//! }
113//! ```
114//!
115//! Note that the `discriminant` attribute only supports [primitive representations](https://doc.rust-lang.org/reference/type-layout.html#primitive-representations)
116//! since those are the only types that have a [reliably accessible discriminant](https://doc.rust-lang.org/reference/items/enumerations.html#pointer-casting).
117//! Nevertheless, manual implementations of `Discriminant` can be made for any enum (and technically and other object).
118use proc_macro::TokenStream;
119
120mod discriminant;
121
122/// Assign a `repr` and produce a `discriminant` method. Also produce a fieldless version of the
123/// enum to allow for easier access to the discriminants without needing to construct one.
124///
125/// * `repr`: The representation for the enum. Should be one of the standard `u*` or `i*` data types.
126///   See <https://doc.rust-lang.org/nomicon/other-reprs.html#repru-repri>
127///   and <https://doc.rust-lang.org/reference/type-layout.html#primitive-representations>
128///
129/// # Example
130/// Fieldless
131/// ```rust
132/// use discrimin_ant_proc::discriminant;
133/// #[discriminant(i8)]
134/// enum FieldlessEnum {
135///     NOne = -1,
136///     One = 1,
137///     Two = 2,
138///     Three,
139///     Five = 5,
140///     Seven = 7,
141///     Nine = 9,
142/// }
143///
144/// assert_eq!(FieldlessEnum::NOne.discriminant(), -1);
145/// assert_eq!(FieldlessEnum::One.discriminant(), 1);
146/// assert_eq!(FieldlessEnum::Two.discriminant(), 2);
147/// assert_eq!(FieldlessEnum::Three.discriminant(), 3);
148/// assert_eq!(FieldlessEnum::Five.discriminant(), 5);
149/// assert_eq!(FieldlessEnum::Seven.discriminant(), 7);
150/// assert_eq!(FieldlessEnum::Nine.discriminant(), 9);
151/// // Still generates the Fieldless version for consistency
152/// assert_eq!(FieldlessEnum_::NOne.discriminant(), -1);
153/// assert_eq!(FieldlessEnum_::One.discriminant(), 1);
154/// assert_eq!(FieldlessEnum_::Two.discriminant(), 2);
155/// assert_eq!(FieldlessEnum_::Three.discriminant(), 3);
156/// assert_eq!(FieldlessEnum_::Five.discriminant(), 5);
157/// assert_eq!(FieldlessEnum_::Seven.discriminant(), 7);
158/// assert_eq!(FieldlessEnum_::Nine.discriminant(), 9);
159/// ```
160///
161/// With fields
162/// ```rust
163/// use discrimin_ant_proc::discriminant;
164/// #[discriminant(i8)]
165/// enum FieldEnum {
166///     NOne(u8) = -1,
167///     One { item: u8 } = 1,
168///     Two = 2,
169///     Three(u8, i16),
170///     Five = 5,
171///     Seven { item_a: i128, item_b: u32 } = 7,
172///     Nine = 9,
173/// }
174///
175/// assert_eq!(FieldEnum::NOne(0).discriminant(), -1);
176/// assert_eq!(FieldEnum::One { item: 0 }.discriminant(), 1);
177/// assert_eq!(FieldEnum::Two.discriminant(), 2);
178/// assert_eq!(FieldEnum::Three(0, 0).discriminant(), 3);
179/// assert_eq!(FieldEnum::Five.discriminant(), 5);
180/// assert_eq!(FieldEnum::Seven { item_a: 0, item_b: 0 }.discriminant(), 7);
181/// assert_eq!(FieldEnum::Nine.discriminant(), 9);
182/// // Fieldless version has the same discriminants
183/// assert_eq!(FieldEnum_::NOne.discriminant(), -1);
184/// assert_eq!(FieldEnum_::One.discriminant(), 1);
185/// assert_eq!(FieldEnum_::Two.discriminant(), 2);
186/// assert_eq!(FieldEnum_::Three.discriminant(), 3);
187/// assert_eq!(FieldEnum_::Five.discriminant(), 5);
188/// assert_eq!(FieldEnum_::Seven.discriminant(), 7);
189/// assert_eq!(FieldEnum_::Nine.discriminant(), 9);
190/// ```
191#[proc_macro_attribute]
192pub fn discriminant(attr: TokenStream, item: TokenStream) -> TokenStream {
193    let item = proc_macro2::TokenStream::from(item);
194    let attr = proc_macro2::TokenStream::from(attr);
195
196    discriminant::discriminant_impl(attr, item).into()
197}