complex_enum_macros/
lib.rs

1//! A derive macro for automatically implementing code conversion for enums
2//!
3//! This crate provides two derive macros:
4//! - `ToCode` generates a `to_code()` method for converting enum variants to `Option<u8>` values
5//! - `TryFromCode` generates a `try_from_code()` method for creating enum variants from `u8` values
6//!
7//! Both macros work with unit variants, named fields, and tuple variants. For using TryFromCode fields in variants must implement
8//! the `Default` trait as they will be initialized with default values during conversion from codes.
9//!
10//! # Example
11//! ```
12//! use complex_enum_macros::{ToCode, TryFromCode};
13//!
14//! #[derive(ToCode, TryFromCode, Debug, PartialEq)]
15//! #[repr(u8)]
16//! enum Command {
17//!     Unknown,
18//!     Start = 0x01,
19//!     SetConfig { value: Option<u32> } = 0x02,
20//!     SendData(String) = 0x03,
21//!     Stop = 0x04,
22//! }
23//!
24//! // Convert enum to code
25//! let cmd = Command::SetConfig { value: Some(42) };
26//! assert_eq!(cmd.to_code(), Some(0x02));
27//!
28//! // Create enum from code and modify
29//! let mut cmd = Command::try_from_code(0x02).unwrap();
30//! match cmd {
31//!     Command::SetConfig { ref mut value } => *value = Some(42),
32//!     _ => unreachable!(),
33//! }
34//! assert_eq!(cmd.to_code(), Some(0x02));
35//! match cmd {
36//!     Command::SetConfig { value } => assert_eq!(value, Some(42)),
37//!     _ => unreachable!(),
38//! }
39//!
40//! // Unknown codes return None
41//! assert_eq!(Command::try_from_code(0xFF), None);
42//!
43//! // Variants without codes return None
44//! assert_eq!(Command::Unknown.to_code(), None);
45//! ```
46//!
47//! # Features
48//! - Automatic code conversion in both directions
49//! - Support for all enum variant types
50//! - Default initialization of fields
51//!
52//! # Requirements
53//! - Enum must have `#[repr(u8)]`
54//! - For TryFromCode field types must implement `Default`
55//! - Variants with codes must have explicit discriminants
56
57use proc_macro::TokenStream;
58use quote::quote;
59use syn::{parse_macro_input, Data, DeriveInput, Fields};
60
61#[proc_macro_derive(ToCode)]
62pub fn derive_to_code(input: TokenStream) -> TokenStream {
63    let input = parse_macro_input!(input as DeriveInput);
64
65    let name = &input.ident;
66
67    let variants = match input.data {
68        Data::Enum(data) => data.variants,
69        _ => panic!("ToCode can only be derived for enums"),
70    };
71
72    let match_arms = variants.iter().map(|variant| {
73        let variant_name = &variant.ident;
74
75        if let Some((_, expr)) = &variant.discriminant {
76            match &variant.fields {
77                Fields::Unit => quote! {
78                    #name::#variant_name => Some(#expr),
79                },
80                Fields::Named(_) => quote! {
81                    #name::#variant_name { .. } => Some(#expr),
82                },
83                Fields::Unnamed(_) => quote! {
84                    #name::#variant_name(..) => Some(#expr),
85                },
86            }
87        } else {
88            match &variant.fields {
89                Fields::Unit => quote! {
90                    #name::#variant_name => None,
91                },
92                Fields::Named(_) => quote! {
93                    #name::#variant_name { .. } => None,
94                },
95                Fields::Unnamed(_) => quote! {
96                    #name::#variant_name(..) => None,
97                },
98            }
99        }
100    });
101
102    let expanded = quote! {
103        impl #name {
104            pub fn to_code(&self) -> Option<u8> {
105                match self {
106                    #(#match_arms)*
107                }
108            }
109        }
110    };
111
112    TokenStream::from(expanded)
113}
114
115#[proc_macro_derive(TryFromCode)]
116pub fn derive_try_from_code(input: TokenStream) -> TokenStream {
117    let input = parse_macro_input!(input as DeriveInput);
118    let name = &input.ident;
119
120    let variants = match input.data {
121        Data::Enum(data) => data.variants,
122        _ => panic!("TryFromCode can only be derived for enums"),
123    };
124
125    let match_arms = variants.iter().map(|variant| {
126        let variant_name = &variant.ident;
127
128        if let Some((_, expr)) = &variant.discriminant {
129            match &variant.fields {
130                Fields::Unit => quote! {
131                    #expr => Some(Self::#variant_name),
132                },
133                Fields::Named(fields) => {
134                    let field_names = fields.named.iter().map(|f| &f.ident);
135                    quote! {
136                        #expr => Some(Self::#variant_name {
137                            #(#field_names: Default::default(),)*
138                        }),
139                    }
140                }
141                Fields::Unnamed(fields) => {
142                    let field_count = fields.unnamed.len();
143                    let defaults = std::iter::repeat(quote!(Default::default())).take(field_count);
144                    quote! {
145                        #expr => Some(Self::#variant_name(#(#defaults),*)),
146                    }
147                }
148            }
149        } else {
150            quote!()
151        }
152    });
153
154    let expanded = quote! {
155        impl #name {
156            pub fn try_from_code(code: u8) -> Option<Self> {
157                match code {
158                    #(#match_arms)*
159                    _ => None,
160                }
161            }
162        }
163    };
164
165    TokenStream::from(expanded)
166}