1use proc_macro::TokenStream;
5use proc_macro2::Span;
6use quote::{format_ident, quote};
7use syn::{parse_macro_input, Data, DataEnum, DeriveInput, Meta};
8
9fn 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 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 }
84 }
85
86 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}