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}