feather_macro/
lib.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2025 Fundament Software SPC <https://fundament.software>
3
4use proc_macro::TokenStream;
5use proc_macro2::Span;
6use quote::{format_ident, quote};
7use syn::{parse_macro_input, Data, DataEnum, DeriveInput, Meta};
8
9/*#[proc_macro_derive(Properties)]
10pub fn properties(input: TokenStream) -> TokenStream {
11    let ast = parse_macro_input!(input as DeriveInput);
12    let struct_name = &ast.ident;
13
14    let fields = if let syn::Data::Struct(syn::DataStruct {
15        fields: syn::Fields::Named(ref fields),
16        ..
17    }) = ast.data
18    {
19        fields
20    } else {
21        panic!("This can only be applied to a Struct")
22    };
23
24    let mut keys = Vec::new();
25    let mut idents = Vec::new();
26    let mut types = Vec::new();
27
28    for field in fields.named.iter() {
29        let field_name: &syn::Ident = field.ident.as_ref().unwrap();
30        let name: String = field_name.to_string();
31        let literal_key_str = syn::LitStr::new(&name, field.span());
32        let type_name = &field.ty;
33        keys.push(quote! { #literal_key_str });
34        idents.push(&field.ident);
35        types.push(type_name.to_token_stream());
36    }
37}*/
38
39fn data_enum(ast: &DeriveInput) -> &DataEnum {
40    if let Data::Enum(data_enum) = &ast.data {
41        data_enum
42    } else {
43        panic!("`Dispath` derive can only be used on an enum.");
44    }
45}
46
47fn find_enum_module(attrs: &[syn::Attribute]) -> syn::Result<String> {
48    // Extract EnumVariantType's module, since this has to be used in conjuction with our derive
49    for attr in attrs.iter() {
50        if attr.path().is_ident("evt") {
51            let nested = attr
52                .parse_args_with(
53                    syn::punctuated::Punctuated::<Meta, syn::Token![,]>::parse_terminated,
54                )
55                .unwrap();
56
57            for meta in nested {
58                if let Meta::NameValue(name_value) = meta {
59                    if let (true, syn::Expr::Lit(lit_str)) =
60                        (name_value.path.is_ident("module"), name_value.value)
61                    {
62                        if let syn::Lit::Str(s) = lit_str.lit {
63                            return Ok(s.value());
64                        } else {
65                            return Err(syn::Error::new(Span::call_site(), ""));
66                        }
67                    } else {
68                        return Err(syn::Error::new(Span::call_site(), ""));
69                    }
70                }
71            }
72
73            // This would be a lot easier but it doesn't seem to work for #[evt(derive(Clone), module = "mouse_area_event")]
74            /*let _ = attr.parse_nested_meta(|meta| {
75                if meta.path.is_ident("module") {
76                    let value = meta.value()?;
77                    let s: LitStr = value.parse()?;
78                    enum_module = Some(s.value());
79                }
80
81                Ok(())
82            });*/
83        }
84    }
85
86    // Error here doesn't matter, we transform it into another error message upon return
87    Err(syn::Error::new(Span::call_site(), ""))
88}
89
90#[proc_macro_derive(Dispatch)]
91pub fn dispatchable(input: TokenStream) -> TokenStream {
92    let crate_name = std::env::var("CARGO_PKG_NAME").unwrap();
93
94    let crate_name = format_ident!(
95        "{}",
96        if crate_name == "feather-ui" {
97            "crate"
98        } else {
99            "feather_ui"
100        }
101    );
102
103    let ast = parse_macro_input!(input as DeriveInput);
104    let enum_module = format_ident!(
105        "{}",
106        find_enum_module(&ast.attrs).expect(
107        "Expected `evt` attribute argument in the form: `#[evt(module = \"some_module_name\")]`",
108    ));
109
110    let enum_name = &ast.ident;
111    let data_enum = data_enum(&ast);
112    let variants = &data_enum.variants;
113
114    let mut extract_declarations = proc_macro2::TokenStream::new();
115    let mut restore_declarations = proc_macro2::TokenStream::new();
116
117    for (counter, variant) in variants.iter().enumerate() {
118        let variant_name = &variant.ident;
119
120        let idx = (1_u64)
121            .checked_shl(counter as u32)
122            .expect("Too many variants! Can't handle more than 64!");
123
124        if variant.fields.is_empty() {
125            extract_declarations.extend(quote! {
126                #enum_name::#variant_name => (
127                    #idx,
128                    Box::new(#enum_module::#variant_name::try_from(self).unwrap()),
129                ),
130            });
131        } else {
132            let underscores = variant.fields.iter().map(|_| format_ident!("_"));
133            extract_declarations.extend(quote! {
134                #enum_name::#variant_name(#(#underscores),*) => (
135                    #idx,
136                    Box::new(#enum_module::#variant_name::try_from(self).unwrap()),
137                ),
138            });
139        }
140
141        restore_declarations.extend(quote! {
142            #idx => Ok(#enum_name::from(
143                *pair
144                    .1
145                    .downcast::<#enum_module::#variant_name>()
146                    .map_err(|_| {
147                        #crate_name::Error::MismatchedEnumTag(
148                            pair.0,
149                            std::any::TypeId::of::<#enum_module::#variant_name>(),
150                            typeid,
151                        )
152                    })?,
153            )),
154        });
155    }
156
157    let counter = variants.len();
158    quote! {
159        impl #crate_name::Dispatchable for #enum_name {
160            const SIZE: usize = #counter;
161
162            fn extract(self) -> #crate_name::DispatchPair {
163                match self {
164                    #extract_declarations
165                }
166            }
167
168            fn restore(pair: #crate_name::DispatchPair) -> Result<Self, #crate_name::Error> {
169                let typeid = (*pair.1).type_id();
170                match pair.0 {
171                    #restore_declarations
172                    _ => Err(#crate_name::Error::InvalidEnumTag(pair.0)),
173                }
174            }
175        }
176    }
177    .into()
178}