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 #[doc = "Converts the enum to its `u8` representation."]
107 pub fn to_code(&self) -> Option<u8> {
108 match self {
109 #(#match_arms)*
110 }
111 }
112 }
113 };
114
115 TokenStream::from(expanded)
116}
117
118/// Derives the `TryFromCode` trait for an enum.
119#[proc_macro_derive(TryFromCode)]
120pub fn derive_try_from_code(input: TokenStream) -> TokenStream {
121 let input = parse_macro_input!(input as DeriveInput);
122 let name = &input.ident;
123
124 let variants = match input.data {
125 Data::Enum(data) => data.variants,
126 _ => panic!("TryFromCode can only be derived for enums"),
127 };
128
129 let match_arms = variants.iter().map(|variant| {
130 let variant_name = &variant.ident;
131
132 if let Some((_, expr)) = &variant.discriminant {
133 match &variant.fields {
134 Fields::Unit => quote! {
135 #expr => Some(Self::#variant_name),
136 },
137 Fields::Named(fields) => {
138 let field_names = fields.named.iter().map(|f| &f.ident);
139 quote! {
140 #expr => Some(Self::#variant_name {
141 #(#field_names: Default::default(),)*
142 }),
143 }
144 }
145 Fields::Unnamed(fields) => {
146 let field_count = fields.unnamed.len();
147 let defaults = std::iter::repeat(quote!(Default::default())).take(field_count);
148 quote! {
149 #expr => Some(Self::#variant_name(#(#defaults),*)),
150 }
151 }
152 }
153 } else {
154 quote!()
155 }
156 });
157
158 let expanded = quote! {
159 impl #name {
160 #[doc = "Tries to convert a `u8` code to an enum variant."]
161 pub fn try_from_code(code: u8) -> Option<Self> {
162 match code {
163 #(#match_arms)*
164 _ => None,
165 }
166 }
167 }
168 };
169
170 TokenStream::from(expanded)
171}