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
//! A derive macro for automatically implementing code conversion for enums
//!
//! This crate provides a `ToCode` derive macro that generates a `to_code()` method
//! for converting enum variants to `Option<u8>` values.
//!
//! # Example
//! ```
//! use complex_enum_macros::ToCode;
//!
//! #[derive(ToCode)]
//! #[repr(u8)]
//! enum Command {
//!     Start = 0x01,
//!     SetConfig { value: Option<u32> } = 0x02,
//!     SendData(String) = 0x03,
//!     Stop = 0x04,
//!     Unknown,
//! }
//!
//! let cmd = Command::SetConfig { value: Some(42) };
//! assert_eq!(cmd.to_code(), Some(0x02));
//! let cmd = Command::SendData("Hello, world!".to_string());
//! assert_eq!(cmd.to_code(), Some(0x03));
//! let cmd = Command::Unknown;
//! assert_eq!(cmd.to_code(), None);
//! ```

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)
}