tinystate_derive/lib.rs
1//! Derive macros for `tinystate`.
2//!
3//! This crate provides procedural macros to automatically implement the [`States`] and [`Events`]
4//! traits for enum types, eliminating boilerplate code.
5//!
6//! # Usage
7//!
8//! Enable the `derive` feature in your `Cargo.toml`:
9//!
10//! ```toml
11//! [dependencies]
12//! tinystate = { version = "0.1", features = ["derive"] }
13//! ```
14//!
15//! Then derive the traits on your enums:
16//!
17//! ```rust,ignore
18//! use tinystate::{States, Events};
19//!
20//! #[derive(States)]
21//! enum TrafficLight {
22//! Red,
23//! Yellow,
24//! Green,
25//! }
26//!
27//! #[derive(Events)]
28//! enum TrafficEvent {
29//! Timer,
30//! Emergency,
31//! }
32//! ```
33//!
34//! # Requirements
35//!
36//! - Both macros only work on enums with unit variants (no fields)
37//! - The first variant of a `States` enum becomes the default state
38//!
39//! [`States`]: https://docs.rs/tinystate/latest/tinystate/trait.States.html
40//! [`Events`]: https://docs.rs/tinystate/latest/tinystate/trait.Events.html
41
42use proc_macro::TokenStream;
43use quote::quote;
44use syn::parse_macro_input;
45use syn::Data;
46use syn::DeriveInput;
47use syn::Fields;
48
49/// Derives the `States` trait for an enum.
50///
51/// This macro automatically implements the `States` trait along with `Default`, `Copy`, and `Clone`.
52/// The first variant of the enum becomes the default state.
53///
54/// # Requirements
55///
56/// - Can only be derived for enums
57/// - All variants must be unit variants (no fields)
58///
59/// # Generated Implementations
60///
61/// - `States` - Maps variants to/from indices starting at 0
62/// - `Default` - Uses the first variant as the default
63/// - `Copy` and `Clone` - Enables efficient copying
64///
65/// # Examples
66///
67/// ```rust,ignore
68/// use tinystate::States;
69///
70/// #[derive(States)]
71/// enum DoorState {
72/// Closed, // Index 0, also the default
73/// Open, // Index 1
74/// Locked, // Index 2
75/// }
76///
77/// let state = DoorState::default();
78/// assert_eq!(state.index(), 0);
79/// assert!(matches!(state, DoorState::Closed));
80/// ```
81///
82/// # Panics
83///
84/// The derive macro will panic at compile time if:
85/// - Applied to a struct or union (not an enum)
86/// - Any variant has fields (only unit variants are allowed)
87///
88/// The generated `from_index` method will panic at runtime if called with an invalid index.
89#[proc_macro_derive(States)]
90pub fn derive_states(input: TokenStream) -> TokenStream {
91 let input = parse_macro_input!(input as DeriveInput);
92 let name = &input.ident;
93
94 let variants = match &input.data {
95 Data::Enum(data_enum) => &data_enum.variants,
96 _ => panic!("States can only be derived for enums"),
97 };
98
99 // Check that all variants are unit variants (no fields)
100 for variant in variants {
101 if !matches!(variant.fields, Fields::Unit) {
102 panic!("States can only be derived for enums with unit variants (no fields)");
103 }
104 }
105
106 let variant_names: Vec<_> = variants.iter().map(|v| &v.ident).collect();
107
108 // Generate match arms for index()
109 let index_arms = variant_names.iter().enumerate().map(|(i, variant)| {
110 quote! {
111 #name::#variant => #i,
112 }
113 });
114
115 // Generate match arms for from_index()
116 let from_index_arms = variant_names.iter().enumerate().map(|(i, variant)| {
117 quote! {
118 #i => #name::#variant,
119 }
120 });
121
122 // Get the first variant for Default implementation
123 let first_variant = &variant_names[0];
124
125 let expanded = quote! {
126 impl States for #name {
127 fn index(&self) -> usize {
128 match self {
129 #(#index_arms)*
130 }
131 }
132
133 fn from_index(index: usize) -> Self {
134 match index {
135 #(#from_index_arms)*
136 _ => panic!("Invalid index {} for {}", index, stringify!(#name)),
137 }
138 }
139 }
140
141 impl Default for #name {
142 fn default() -> Self {
143 #name::#first_variant
144 }
145 }
146
147 impl Copy for #name {}
148
149 impl Clone for #name {
150 fn clone(&self) -> Self {
151 *self
152 }
153 }
154 };
155
156 TokenStream::from(expanded)
157}
158
159/// Derives the `Events` trait for an enum.
160///
161/// This macro automatically implements the `Events` trait along with `Copy` and `Clone`.
162/// Unlike `States`, this does not generate a `Default` implementation.
163///
164/// # Requirements
165///
166/// - Can only be derived for enums
167/// - All variants must be unit variants (no fields)
168///
169/// # Generated Implementations
170///
171/// - `Events` - Maps variants to/from indices starting at 0
172/// - `Copy` and `Clone` - Enables efficient copying
173///
174/// # Examples
175///
176/// ```rust,ignore
177/// use tinystate::Events;
178///
179/// #[derive(Events)]
180/// enum DoorEvent {
181/// Push, // Index 0
182/// Pull, // Index 1
183/// Lock, // Index 2
184/// Unlock, // Index 3
185/// }
186///
187/// let event = DoorEvent::Lock;
188/// assert_eq!(event.index(), 2);
189///
190/// let reconstructed = DoorEvent::from_index(2);
191/// assert_eq!(reconstructed.index(), event.index());
192/// ```
193///
194/// # Panics
195///
196/// The derive macro will panic at compile time if:
197/// - Applied to a struct or union (not an enum)
198/// - Any variant has fields (only unit variants are allowed)
199///
200/// The generated `from_index` method will panic at runtime if called with an invalid index.
201#[proc_macro_derive(Events)]
202pub fn derive_events(input: TokenStream) -> TokenStream {
203 let input = parse_macro_input!(input as DeriveInput);
204 let name = &input.ident;
205
206 let variants = match &input.data {
207 Data::Enum(data_enum) => &data_enum.variants,
208 _ => panic!("Events can only be derived for enums"),
209 };
210
211 // Check that all variants are unit variants (no fields)
212 for variant in variants {
213 if !matches!(variant.fields, Fields::Unit) {
214 panic!("Events can only be derived for enums with unit variants (no fields)");
215 }
216 }
217
218 let variant_names: Vec<_> = variants.iter().map(|v| &v.ident).collect();
219
220 // Generate match arms for index()
221 let index_arms = variant_names.iter().enumerate().map(|(i, variant)| {
222 quote! {
223 #name::#variant => #i,
224 }
225 });
226
227 // Generate match arms for from_index()
228 let from_index_arms = variant_names.iter().enumerate().map(|(i, variant)| {
229 quote! {
230 #i => #name::#variant,
231 }
232 });
233
234 let expanded = quote! {
235 impl Events for #name {
236 fn index(&self) -> usize {
237 match self {
238 #(#index_arms)*
239 }
240 }
241
242 fn from_index(index: usize) -> Self {
243 match index {
244 #(#from_index_arms)*
245 _ => panic!("Invalid index {} for {}", index, stringify!(#name)),
246 }
247 }
248 }
249
250 impl Copy for #name {}
251
252 impl Clone for #name {
253 fn clone(&self) -> Self {
254 *self
255 }
256 }
257 };
258
259 TokenStream::from(expanded)
260}