1extern crate proc_macro;
2
3use proc_macro::TokenStream;
4use quote::quote;
5use syn::{
6 DeriveInput, FnArg, Ident, ItemFn, Pat, PatIdent, Path, Result, Token, Type, TypePath,
7 TypeReference,
8 parse::{Parse, ParseStream},
9 parse_macro_input, parse_str,
10 punctuated::Punctuated,
11};
12
13#[derive(Debug, Clone)]
14struct IdentList {
15 values: Punctuated<Ident, Token![,]>,
16}
17
18impl Parse for IdentList {
19 fn parse(input: ParseStream) -> Result<Self> {
20 Ok(Self {
21 values: input.parse_terminated(Ident::parse)?,
22 })
23 }
24}
25
26fn unpack_context(ty: &Type, pat: &Pat) -> Option<Ident> {
27 match ty {
28 Type::Path(TypePath { path, .. }) => {
29 if let Some(segment) = path.segments.iter().next_back()
30 && segment.ident == "WidgetContext"
31 && let Pat::Ident(PatIdent { ident, .. }) = pat
32 {
33 return Some(ident.to_owned());
34 }
35 }
36 Type::Reference(TypeReference { elem, .. }) => {
37 return unpack_context(elem, pat);
38 }
39 _ => {}
40 }
41 None
42}
43
44fn is_arg_context(arg: &FnArg) -> Option<Ident> {
45 if let FnArg::Typed(pat) = arg {
46 unpack_context(&pat.ty, &pat.pat)
47 } else {
48 None
49 }
50}
51
52#[proc_macro_attribute]
59pub fn pre_hooks(attr: TokenStream, input: TokenStream) -> TokenStream {
60 let ItemFn {
61 attrs,
62 vis,
63 sig,
64 block,
65 } = parse_macro_input!(input as ItemFn);
66 let context = sig
67 .inputs
68 .iter()
69 .find_map(is_arg_context)
70 .unwrap_or_else(|| panic!("Could not find function context argument!"));
71 let list = parse_macro_input!(attr as IdentList);
72 let hooks = list
73 .values
74 .into_iter()
75 .map(|v| quote! { #context.use_hook(#v); });
76
77 let tokens = quote! {
78 #(#attrs)*
79 #vis #sig {
80 #({#hooks})*
81 #block
82 }
83 };
84 tokens.into()
85}
86
87#[proc_macro_attribute]
91pub fn post_hooks(attr: TokenStream, input: TokenStream) -> TokenStream {
92 let ItemFn {
93 attrs,
94 vis,
95 sig,
96 block,
97 } = parse_macro_input!(input as ItemFn);
98 let context = sig
99 .inputs
100 .iter()
101 .find_map(is_arg_context)
102 .unwrap_or_else(|| panic!("Could not find function context argument!"));
103 let list = parse_macro_input!(attr as IdentList);
104 let hooks = list
105 .values
106 .into_iter()
107 .map(|v| quote! { #context.use_hook(#v); });
108
109 let tokens = quote! {
110 #(#attrs)*
111 #vis #sig {
112 let result = {
113 #block
114 };
115 #({#hooks})*
116 result
117 }
118 };
119 tokens.into()
120}
121
122#[proc_macro_derive(PropsData, attributes(remote, props_data, prefab))]
143pub fn derive_props(input: TokenStream) -> TokenStream {
144 let DeriveInput { ident, attrs, .. } = parse_macro_input!(input as DeriveInput);
145
146 let mut path = Path::from(ident);
147 let mut props_data = parse_str::<Path>("PropsData").unwrap();
148 let mut prefab = parse_str::<Path>("Prefab").unwrap();
149 for attr in attrs {
150 if let Some(ident) = attr.path.get_ident() {
151 if ident == "remote" {
152 path = attr.parse_args::<Path>().unwrap();
153 } else if ident == "props_data" {
154 props_data = attr.parse_args::<Path>().unwrap();
155 } else if ident == "prefab" {
156 prefab = attr.parse_args::<Path>().unwrap();
157 }
158 }
159 }
160
161 let tokens = quote! {
162 impl #props_data for #path
163 where
164 Self: Clone,
165 {
166 fn clone_props(&self) -> Box<dyn #props_data> {
167 Box::new(self.clone())
168 }
169
170 fn as_any(&self) -> &dyn std::any::Any {
171 self
172 }
173 }
174
175 impl #prefab for #path {}
176 };
177 tokens.into()
178}
179
180#[proc_macro_derive(MessageData, attributes(remote, message_data))]
193pub fn derive_message(input: TokenStream) -> TokenStream {
194 let DeriveInput { ident, attrs, .. } = parse_macro_input!(input as DeriveInput);
195
196 let mut path = Path::from(ident);
197 let mut message_data = parse_str::<Path>("MessageData").unwrap();
198 for attr in attrs {
199 if let Some(ident) = attr.path.get_ident() {
200 if ident == "remote" {
201 path = attr.parse_args::<Path>().unwrap();
202 } else if ident == "message_data" {
203 message_data = attr.parse_args::<Path>().unwrap();
204 }
205 }
206 }
207
208 let tokens = quote! {
209 impl #message_data for #path
210 where
211 Self: Clone,
212 {
213 fn clone_message(&self) -> Box<dyn #message_data> {
214 Box::new(self.clone())
215 }
216
217 fn as_any(&self) -> &dyn std::any::Any {
218 self
219 }
220 }
221 };
222 tokens.into()
223}