zng_ext_hot_reload_proc_macros/
hot_node.rs
1use proc_macro2::*;
2use quote::*;
3use syn::{
4 parse::{Parse, Result},
5 spanned::Spanned as _,
6 *,
7};
8
9use crate::util::Errors;
10
11pub fn expand(args: proc_macro::TokenStream, input: proc_macro::TokenStream) -> proc_macro::TokenStream {
12 let mut errors = Errors::default();
13
14 let args = match parse::<Args>(args) {
15 Ok(a) => a,
16 Err(e) => {
17 errors.push_syn(e);
18 Args {
19 name: LitStr::new("", Span::call_site()),
20 }
21 }
22 };
23
24 let item = match parse::<ItemFn>(input.clone()) {
25 Ok(i) => i,
26 Err(e) => {
27 errors.push_syn(e);
28 let input = TokenStream::from(input);
29 let r = quote! {
30 #input
31 #errors
32 };
33 return r.into();
34 }
35 };
36
37 if let Some(async_) = &item.sig.asyncness {
38 errors.push("hot node functions cannot be `async`", async_.span());
39 }
40 if let Some(unsafe_) = &item.sig.unsafety {
41 errors.push("hot node functions cannot be `unsafe`", unsafe_.span());
42 }
43 if let Some(abi) = &item.sig.abi {
44 errors.push("hot node functions cannot be `extern`", abi.span());
45 }
46 if let Some(lifetime) = item.sig.generics.lifetimes().next() {
47 errors.push("hot node functions cannot declare lifetimes", lifetime.span());
48 }
49 if let Some(const_) = item.sig.generics.const_params().next() {
50 errors.push("hot node functions do not support `const` generics", const_.span());
51 }
52 if let Some(ty_) = item.sig.generics.type_params().next() {
53 errors.push("hot node functions do not support named generics", ty_.span());
54 }
55
56 let ident = &item.sig.ident;
57 let slice_ident = ident!("__ZNG_HOT_{}", ident);
58 let builder_ident = ident!("__zng_hot_builder_{}", ident);
59 let actual_ident = ident!("__zng_hot_actual_{}", ident);
60
61 let mut name = args.name;
62 if name.value().is_empty() {
63 name = LitStr::new(&ident.to_string(), ident.span());
64 }
65
66 let inputs: Vec<_> = item.sig.inputs.iter().map(|arg| Input::from_arg(arg, &mut errors)).collect();
67
68 match &item.sig.output {
69 ReturnType::Default => errors.push("hot node functions must output `impl UiNode`", item.sig.fn_token.span()),
70 ReturnType::Type(_, t) => match &**t {
71 Type::ImplTrait(t) => match t.bounds.last().unwrap() {
72 TypeParamBound::Trait(t)
73 if t.lifetimes.is_none() && t.paren_token.is_none() && t.path.segments.last().unwrap().ident == "UiNode" =>
74 {
75 }
77 _ => errors.push("hot node functions must output `impl UiNode`", t.span()),
78 },
79 _ => errors.push("hot node functions must output `impl UiNode`", t.span()),
80 },
81 }
82
83 if !errors.is_empty() {
84 return quote! {
85 #item
86 #errors
87 }
88 .into();
89 }
90
91 let mut unpack_args = quote!();
92 for input in &inputs {
93 let t = &input.gen_ty;
94 match input.kind {
95 InputKind::Var => unpack_args.extend(quote! {
96 __args__.pop_var::<#t>(),
97 }),
98 InputKind::Value => unpack_args.extend(quote! {
99 __args__.pop_value::<#t>(),
100 }),
101 InputKind::UiNode => unpack_args.extend(quote! {
102 __args__.pop_ui_node(),
103 }),
104 InputKind::WidgetHandler => unpack_args.extend(quote! {
105 __args__.pop_widget_handler::<#t>(),
106 }),
107 InputKind::UiNodeList => unpack_args.extend(quote! {
108 __args__.pop_ui_node_list(),
109 }),
110 InputKind::TryClone => unpack_args.extend(quote! {
111 __args__.pop_clone::<#t>(),
112 }),
113 }
114 }
115
116 let hot_side = quote! {
117 use crate::zng_hot_entry as _;
119
120 crate::zng_hot_entry::HOT_NODES! {
122 #[doc(hidden)]
123 static #slice_ident: crate::zng_hot_entry::HotNodeEntry = {
124 crate::zng_hot_entry::HotNodeEntry {
125 manifest_dir: env!("CARGO_MANIFEST_DIR"),
126 hot_node_name: #name,
127 hot_node_fn: #builder_ident,
128 }
129 };
130 }
131
132 #[doc(hidden)]
133 fn #builder_ident(mut __args__: crate::zng_hot_entry::HotNodeArgs) -> crate::zng_hot_entry::HotNode {
134 crate::zng_hot_entry::HotNode::new(#actual_ident(
135 #unpack_args
136 ))
137 }
138 };
139
140 let mut item = item;
141 let mut proxy_item = item.clone();
142
143 item.vis = Visibility::Inherited;
144 item.sig.ident = actual_ident;
145 let input_len = inputs.len();
146
147 let mut pack_args = quote!();
148 for input in inputs.iter().rev() {
149 let ident = &input.ident;
150 let t = &input.gen_ty;
151 match input.kind {
152 InputKind::Var => {
153 pack_args.extend(quote_spanned! {ident.span()=>
154 __args__.push_var::<#t>(#ident);
155 });
156 }
157 InputKind::Value => {
158 pack_args.extend(quote_spanned! {ident.span()=>
159 __args__.push_value::<#t>(#ident);
160 });
161 }
162 InputKind::UiNode => {
163 pack_args.extend(quote_spanned! {ident.span()=>
164 __args__.push_ui_node(#ident);
165 });
166 }
167 InputKind::WidgetHandler => {
168 pack_args.extend(quote_spanned! {ident.span()=>
169 __args__.push_widget_handler::<#t>(#ident);
170 });
171 }
172 InputKind::UiNodeList => {
173 pack_args.extend(quote_spanned! {ident.span()=>
174 __args__.push_ui_node_list(#ident);
175 });
176 }
177 InputKind::TryClone => {
178 pack_args.extend(quote_spanned! {ident.span()=>
179 __args__.push_clone::<#t>(#ident);
180 });
181 }
182 }
183 }
184
185 proxy_item.block = parse_quote! {
186 {
187 let mut __args__ = crate::zng_hot_entry::HotNodeArgs::with_capacity(#input_len);
188 #pack_args
189
190 crate::zng_hot_entry::HotNodeHost::new(env!("CARGO_MANIFEST_DIR"), #name, __args__, #builder_ident)
191 }
192 };
193
194 let host_side = quote! {
195 #item
196 #proxy_item
197 };
198
199 let r = quote! {
200 #hot_side
201 #host_side
202 #errors
203 };
204
205 r.into()
206}
207
208struct Args {
209 name: LitStr,
210}
211impl Parse for Args {
212 fn parse(input: parse::ParseStream) -> Result<Self> {
213 if input.is_empty() {
214 return Ok(Args {
215 name: LitStr::new("", input.span()),
216 });
217 }
218
219 Ok(Args { name: input.parse()? })
220 }
221}
222
223#[derive(Clone, Copy)]
224enum InputKind {
225 Var,
226 Value,
227 UiNode,
228 WidgetHandler,
229 UiNodeList,
230 TryClone,
231}
232struct Input {
233 ident: Ident,
234 kind: InputKind,
235 gen_ty: TokenStream,
236}
237impl Input {
238 fn from_arg(arg: &FnArg, errors: &mut Errors) -> Input {
239 let mut input = Input {
240 ident: ident!("__invalid__"),
241 kind: InputKind::Value,
242 gen_ty: quote!(),
243 };
244 match arg {
245 FnArg::Receiver(rcv) => {
246 errors.push("methods cannot be hot nodes", rcv.span());
247 }
248 FnArg::Typed(t) => {
249 if !t.attrs.is_empty() {
250 errors.push("hot node input cannot have attributes", t.attrs[0].span());
251 }
252
253 match *t.pat.clone() {
254 Pat::Ident(id) => {
255 if id.ident == "self" {
256 errors.push("methods cannot be hot nodes", id.ident.span());
257 }
258 input.ident = id.ident;
259 }
260 p => {
261 errors.push("hot node input can only have a simple ident", p.span());
262 }
263 }
264
265 match *t.ty.clone() {
266 Type::ImplTrait(mut it) if it.bounds.len() == 1 => {
267 let bounds = it.bounds.pop().unwrap().into_value();
268 match bounds {
269 TypeParamBound::Trait(tra) if tra.lifetimes.is_none() && tra.paren_token.is_none() => {
270 let path = tra.path;
271 let seg = path.segments.last().unwrap();
272
273 fn ty_from_generic(input: &mut Input, errors: &mut Errors, kind: InputKind, args: &PathArguments) -> bool {
274 if let PathArguments::AngleBracketed(it) = args {
275 if it.args.len() == 1 {
276 input.kind = kind;
277 input.gen_ty = it.args.last().unwrap().to_token_stream();
278 return true;
279 }
280 }
281 errors.push("expected single generic param", args.span());
282 false
283 }
284
285 match seg.ident.to_string().as_str() {
286 "IntoVar" if !seg.arguments.is_empty() => {
287 ty_from_generic(&mut input, errors, InputKind::Var, &seg.arguments);
288 }
289 "IntoValue" if !seg.arguments.is_empty() => {
290 ty_from_generic(&mut input, errors, InputKind::Value, &seg.arguments);
291 }
292 "WidgetHandler" if !seg.arguments.is_empty() => {
293 ty_from_generic(&mut input, errors, InputKind::WidgetHandler, &seg.arguments);
294 }
295 "UiNode" => {
296 input.kind = InputKind::UiNode;
297 }
298 "UiNodeList" => {
299 input.kind = InputKind::UiNodeList;
300 }
301 _ => {
302 errors.push("hot node input can only have impl types for: IntoVar<T>, IntoValue<T>, UiNode, WidgetHandler<A>, UiNodeList", seg.span());
303 }
304 }
305 }
306 t => {
307 errors.push("hot node input can only have `impl OneTrait`", t.span());
308 }
309 }
310 }
311 Type::Array(a) => {
312 input.kind = InputKind::TryClone;
313 input.gen_ty = a.to_token_stream();
314 }
315 Type::BareFn(f) => {
316 input.kind = InputKind::TryClone;
317 input.gen_ty = f.to_token_stream();
318 }
319 Type::Path(p) => {
320 input.kind = InputKind::TryClone;
321 input.gen_ty = p.to_token_stream();
322 }
323 Type::Tuple(t) => {
324 input.kind = InputKind::TryClone;
325 input.gen_ty = t.to_token_stream();
326 }
327 t => {
328 errors.push(
329 "hot node input can only have `Clone+Send+Any` types or `impl OneTrait` property types",
330 t.span(),
331 );
332 }
333 }
334 }
335 }
336 input
337 }
338}