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}