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                    // ok
76                }
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        // to get a better error message
118        use crate::zng_hot_entry as _;
119
120        // don't use `linkme::distributed_slice` because it requires direct dependency to `linkme`.
121        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}