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