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