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