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}