zng_app_proc_macros/
property.rs

1use std::mem;
2
3use proc_macro2::{Ident, Span, TokenStream};
4use quote::{ToTokens, quote};
5use syn::{parse::Parse, punctuated::Punctuated, spanned::Spanned, *};
6
7use crate::util::{Attributes, Errors, crate_core, set_stream_span};
8
9pub fn expand(args: proc_macro::TokenStream, input: proc_macro::TokenStream) -> proc_macro::TokenStream {
10    let mut errors = Errors::default();
11
12    let args = match parse::<Args>(args) {
13        Ok(a) => a,
14        Err(e) => {
15            errors.push_syn(e);
16            Args {
17                nest_group: parse_quote!(CONTEXT),
18                capture: false,
19                default: None,
20                impl_for: None,
21            }
22        }
23    };
24
25    let nest_group = args.nest_group;
26    let capture = args.capture;
27    let impl_for = args.impl_for;
28
29    let mut item = match parse::<ItemFn>(input.clone()) {
30        Ok(i) => i,
31        Err(e) => {
32            errors.push_syn(e);
33            let input = TokenStream::from(input);
34            let r = quote! {
35                #input
36                #errors
37            };
38            return r.into();
39        }
40    };
41    let mut attrs = Attributes::new(mem::take(&mut item.attrs));
42    let mut mtd_attrs = Attributes::new(vec![]);
43    mtd_attrs.docs.clone_from(&attrs.docs);
44    let mut extra_docs = quote!();
45
46    // note that the tags "c" and "P" are used by the `widget.js` to find properties.
47    if capture {
48        attrs.tag_doc("c", "Capture-only property function");
49        mtd_attrs.tag_doc("c", "Capture-only property method");
50        if !attrs.docs.is_empty() {
51            extra_docs = quote! {
52                ///
53                /// # Capture-Only
54                ///
55                /// This property is capture-only, it only defines a property signature, it does not implement any behavior by itself.
56                /// Widgets can capture and implement this property as part of their intrinsics, otherwise it will have no
57                /// effect if set on a widget that does not implement it.
58            };
59        }
60
61        if item.sig.inputs.is_empty() {
62            errors.push(
63                "capture property functions must have at least 1 input: arg0, ..",
64                item.sig.inputs.span(),
65            );
66        }
67    } else {
68        attrs.tag_doc("P", "Property function");
69        mtd_attrs.tag_doc("P", "Property method");
70
71        if item.sig.inputs.len() < 2 {
72            errors.push(
73                "property functions must have at least 2 inputs: child: impl UiNode, arg0, ..",
74                item.sig.inputs.span(),
75            );
76        }
77    }
78
79    if item.sig.inputs.is_empty() {
80        // patch to continue validation.
81        let core = crate_core();
82        item.sig.inputs.push(parse_quote!(__child__: impl #core::widget::node::UiNode));
83    }
84
85    if let Some(async_) = &item.sig.asyncness {
86        errors.push("property functions cannot be `async`", async_.span());
87    }
88    if let Some(unsafe_) = &item.sig.unsafety {
89        errors.push("property functions cannot be `unsafe`", unsafe_.span());
90    }
91    if let Some(abi) = &item.sig.abi {
92        errors.push("property functions cannot be `extern`", abi.span());
93    }
94    if let Some(lifetime) = item.sig.generics.lifetimes().next() {
95        errors.push("property functions cannot declare lifetimes", lifetime.span());
96    }
97    if let Some(const_) = item.sig.generics.const_params().next() {
98        errors.push("property functions do not support `const` generics", const_.span());
99    }
100
101    let inputs: Vec<_> = item.sig.inputs.iter().map(|arg| Input::from_arg(arg, &mut errors)).collect();
102    if !inputs[0].ty.is_empty() && !capture {
103        // first param passed Input::from_arg validation, check if is node.
104        if !matches!(inputs[0].kind, InputKind::UiNode) {
105            errors.push("first input must be `impl UiNode`", inputs[0].ty.span());
106        }
107    }
108
109    if capture {
110        let inputs = inputs.iter().map(|i| &i.ident);
111        if !item.block.stmts.is_empty() {
112            errors.push("capture property must have an empty body", item.block.span());
113        }
114        item.block.stmts.clear();
115        item.block.stmts.push(parse_quote! {
116            let _ = (#(#inputs,)*);
117        });
118    }
119    let item = item;
120    let first_input = if capture { 0 } else { 1 };
121
122    let output_span = match &item.sig.output {
123        ReturnType::Default => {
124            if !capture {
125                errors.push("output type must be `impl UiNode`", item.sig.ident.span());
126            }
127            proc_macro2::Span::call_site()
128        }
129        ReturnType::Type(_, ty) => {
130            if capture {
131                errors.push("capture must not have output", ty.span());
132            }
133            ty.span()
134        }
135    };
136
137    // validate generics
138    // validate input types, generate args
139
140    let extra = if errors.is_empty() {
141        // generate items if all is valid.
142
143        let core = crate_core();
144        let cfg = &attrs.cfg;
145        let deprecated = &attrs.deprecated;
146        let vis = &item.vis;
147        let ident = &item.sig.ident;
148        let ident_str = ident.to_string();
149        let generics = &item.sig.generics;
150        let has_generics = !generics.params.empty_or_trailing();
151        let (impl_gens, ty_gens, where_gens) = generics.split_for_impl();
152        let path_gens = if has_generics { quote!(::#ty_gens) } else { quote!() };
153
154        let ident_unset = ident!("unset_{}", ident);
155        let ident_args = ident!("{}_args__", ident);
156        let ident_inputs = ident!("{}_inputs__", ident);
157        let ident_meta = ident!("{}_", ident);
158        let ident_sorted = ident!("{}__", ident);
159
160        let default;
161        let default_fn;
162        let args_default = match args.default {
163            Some(d) => Some((d.default.span(), d.args.to_token_stream())),
164            None => {
165                let mut default = quote!();
166                let first_input = if capture { 0 } else { 1 };
167                for input in &inputs[first_input..] {
168                    match input.kind {
169                        InputKind::Var => {
170                            if ident_str.starts_with("is_") || ident_str.starts_with("has_") {
171                                let core = set_stream_span(core.clone(), input.ty.span());
172                                default.extend(quote_spanned! {input.ty.span()=>
173                                    #core::widget::builder::state_var(),
174                                })
175                            } else if ident_str.starts_with("get_") || ident_str.starts_with("actual_") {
176                                let core = set_stream_span(core.clone(), input.ty.span());
177                                default.extend(quote_spanned! {input.ty.span()=>
178                                    #core::widget::builder::getter_var(),
179                                })
180                            }
181                        }
182                        InputKind::UiNode => default.extend(quote! {
183                            #core::widget::node::NilUiNode,
184                        }),
185                        InputKind::UiNodeList => default.extend(quote! {
186                            #core::widget::node::UiVec::new(),
187                        }),
188                        InputKind::WidgetHandler if !has_generics => default.extend(quote! {
189                            #core::handler::hn!(|_| {}),
190                        }),
191                        _ => {
192                            default = quote!();
193                            break;
194                        }
195                    }
196                }
197
198                if !default.is_empty() {
199                    Some((Span::call_site(), default))
200                } else {
201                    None
202                }
203            }
204        };
205
206        if let Some((span, args)) = args_default {
207            default = quote_spanned! {span=>
208                fn __default__ #impl_gens() -> std::boxed::Box<dyn #core::widget::builder::PropertyArgs> #where_gens {
209                    #ident_meta {}.args(#args)
210                }
211            };
212            default_fn = quote! {
213                Some(__default__ #path_gens)
214            };
215        } else {
216            default = quote!();
217            default_fn = quote! {
218                None
219            };
220        }
221        let mut input_info = quote!();
222        let mut get_var = quote!();
223        let mut get_value = quote!();
224        let mut get_ui_node = quote!();
225        let mut get_ui_node_list = quote!();
226        let mut get_widget_handler = quote!();
227
228        let mut instantiate = quote!();
229        let mut input_idents = vec![];
230        let mut input_tys = vec![];
231        let mut storage_tys = vec![];
232        let mut input_to_storage = vec![];
233        let mut named_into = quote!();
234        let mut get_when_input = quote!();
235        let mut input_new_dyn = vec![];
236
237        let mut allowed_in_when_expr = true;
238        let mut allowed_in_when_assign = true;
239
240        for (i, input) in inputs[first_input..].iter().enumerate() {
241            let ident = &input.ident;
242            let input_ty = &input.ty;
243            input_idents.push(ident);
244            input_tys.push(input_ty);
245            storage_tys.push(&input.storage_ty);
246
247            let kind = input.kind;
248            let info_ty = &input.info_ty;
249            let storage_ty = &input.storage_ty;
250            input_info.extend(quote! {
251                #core::widget::builder::PropertyInput {
252                    name: stringify!(#ident),
253                    kind: #kind,
254                    ty: std::any::TypeId::of::<#info_ty>(),
255                    ty_name: std::any::type_name::<#info_ty>(),
256                },
257            });
258
259            match kind {
260                InputKind::Var => {
261                    input_to_storage.push(quote! {
262                        self.inputs().#ident(#ident)
263                    });
264                    get_var.extend(quote! {
265                        #i => &self.#ident,
266                    });
267                    instantiate.extend(quote! {
268                        std::clone::Clone::clone(&self.#ident),
269                    });
270                    named_into.extend(quote! {
271                        pub fn #ident #impl_gens(&self, #ident: #input_ty) -> #storage_ty #where_gens {
272                            #core::widget::builder::var_to_args(#ident)
273                        }
274                    });
275                    input_new_dyn.push(quote! {
276                        let __actions__ = #core::widget::builder::iter_input_build_actions(&__args__.build_actions, &__args__.build_actions_when_data, #i);
277                        #core::widget::builder::new_dyn_var(&mut __inputs__, __actions__)
278                    });
279                    let get_ident = ident!("__w_{ident}__");
280                    let get_ident_i = ident!("__w_{i}__");
281                    get_when_input.extend(quote! {
282                        pub fn #get_ident #impl_gens(&self)
283                        -> (#core::widget::builder::WhenInputVar, impl #core::var::Var<#info_ty>) #where_gens {
284                            #core::widget::builder::WhenInputVar::new::<#info_ty>()
285                        }
286                        pub fn #get_ident_i #impl_gens(&self)
287                        -> (#core::widget::builder::WhenInputVar, impl #core::var::Var<#info_ty>) #where_gens {
288                            #core::widget::builder::WhenInputVar::new::<#info_ty>()
289                        }
290                    });
291                }
292                InputKind::Value => {
293                    allowed_in_when_assign = false;
294                    input_to_storage.push(quote! {
295                        self.inputs().#ident(#ident)
296                    });
297                    named_into.extend(quote! {
298                        pub fn #ident #impl_gens(&self, #ident: #input_ty) -> #storage_ty #where_gens {
299                            #core::widget::builder::value_to_args(#ident)
300                        }
301                    });
302                    get_value.extend(quote! {
303                        #i => &self.#ident,
304                    });
305                    instantiate.extend(quote! {
306                        std::clone::Clone::clone(&self.#ident),
307                    });
308                    input_new_dyn.push(quote! {
309                        let __actions__ = #core::widget::builder::iter_input_build_actions(&__args__.build_actions, &__args__.build_actions_when_data, #i);
310                        #core::widget::builder::new_dyn_other(&mut __inputs__, __actions__)
311                    });
312                    let get_ident = ident!("__w_{ident}__");
313                    let get_ident_i = ident!("__w_{i}__");
314                    get_when_input.extend(quote! {
315                        pub fn #get_ident #impl_gens(&self)
316                        -> (#core::widget::builder::WhenInputVar, impl #core::var::Var<#info_ty>) #where_gens {
317                            #core::widget::builder::WhenInputVar::new::<#info_ty>()
318                        }
319                        pub fn #get_ident_i #impl_gens(&self)
320                        -> (#core::widget::builder::WhenInputVar, impl #core::var::Var<#info_ty>) #where_gens {
321                            #core::widget::builder::WhenInputVar::new::<#info_ty>()
322                        }
323                    });
324                }
325                InputKind::UiNode => {
326                    allowed_in_when_expr = false;
327                    input_to_storage.push(quote! {
328                        #core::widget::builder::ui_node_to_args(#ident)
329                    });
330                    named_into.extend(quote! {
331                        pub fn #ident(&self, #ident: #input_ty) -> #core::widget::node::BoxedUiNode {
332                            #core::widget::node::UiNode::boxed(#ident)
333                        }
334                    });
335                    get_ui_node.extend(quote! {
336                        #i => &self.#ident,
337                    });
338                    instantiate.extend(quote! {
339                        self.#ident.take_on_init(),
340                    });
341                    input_new_dyn.push(quote! {
342                        let __actions__ = #core::widget::builder::iter_input_build_actions(&__args__.build_actions, &__args__.build_actions_when_data, #i);
343                        #core::widget::builder::new_dyn_ui_node(&mut __inputs__, __actions__)
344                    });
345                    let get_ident = ident!("__w_{ident}__");
346                    let get_ident_i = ident!("__w_{i}__");
347                    get_when_input.extend(quote! {
348                        pub fn #get_ident(&self)
349                        -> (#core::widget::builder::WhenInputVar, impl #core::var::Var<#core::widget::builder::UiNodeInWhenExprError>) {
350                            #core::widget::builder::WhenInputVar::new::<#core::widget::builder::UiNodeInWhenExprError>()
351                        }
352                        pub fn #get_ident_i(&self)
353                        -> (#core::widget::builder::WhenInputVar, impl #core::var::Var<#core::widget::builder::UiNodeInWhenExprError>) {
354                            #core::widget::builder::WhenInputVar::new::<#core::widget::builder::UiNodeInWhenExprError>()
355                        }
356                    });
357                }
358                InputKind::UiNodeList => {
359                    allowed_in_when_expr = false;
360                    input_to_storage.push(quote! {
361                        #core::widget::builder::ui_node_list_to_args(#ident)
362                    });
363                    named_into.extend(quote! {
364                        pub fn #ident(&self, #ident: #input_ty) -> #core::widget::node::BoxedUiNodeList {
365                            #core::widget::node::UiNodeList::boxed(#ident)
366                        }
367                    });
368                    get_ui_node_list.extend(quote! {
369                        #i => &self.#ident,
370                    });
371                    instantiate.extend(quote! {
372                        self.#ident.take_on_init(),
373                    });
374                    input_new_dyn.push(quote! {
375                        let __actions__ = #core::widget::builder::iter_input_build_actions(&__args__.build_actions, &__args__.build_actions_when_data, #i);
376                        #core::widget::builder::new_dyn_ui_node_list(&mut __inputs__, __actions__)
377                    });
378                    let get_ident = ident!("__w_{ident}__");
379                    let get_ident_i = ident!("__w_{i}__");
380                    get_when_input.extend(quote! {
381                        pub fn #get_ident(&self)
382                        -> (#core::widget::builder::WhenInputVar, impl #core::var::Var<#core::widget::builder::UiNodeListInWhenExprError>) {
383                            #core::widget::builder::WhenInputVar::new::<#core::widget::builder::UiNodeListInWhenExprError>()
384                        }
385                        pub fn #get_ident_i(&self)
386                        -> (#core::widget::builder::WhenInputVar, impl #core::var::Var<#core::widget::builder::UiNodeListInWhenExprError>) {
387                            #core::widget::builder::WhenInputVar::new::<#core::widget::builder::UiNodeListInWhenExprError>()
388                        }
389                    });
390                }
391                InputKind::WidgetHandler => {
392                    allowed_in_when_expr = false;
393                    input_to_storage.push(quote! {
394                        #core::widget::builder::widget_handler_to_args(#ident)
395                    });
396                    named_into.extend(quote! {
397                        pub fn #ident #impl_gens(&self, #ident: #input_ty) -> #input_ty #where_gens {
398                            #ident
399                        }
400                    });
401                    get_widget_handler.extend(quote! {
402                        #i => &self.#ident,
403                    });
404                    instantiate.extend(quote! {
405                        std::clone::Clone::clone(&self.#ident),
406                    });
407                    input_new_dyn.push(quote! {
408                        let __actions__ = #core::widget::builder::iter_input_build_actions(&__args__.build_actions, &__args__.build_actions_when_data, #i);
409                        #core::widget::builder::new_dyn_widget_handler(&mut __inputs__, __actions__)
410                    });
411                    let get_ident = ident!("__w_{ident}__");
412                    let get_ident_i = ident!("__w_{i}__");
413                    get_when_input.extend(quote! {
414                        pub fn #get_ident #impl_gens(&self)
415                        -> (#core::widget::builder::WhenInputVar, impl #core::var::Var<#core::widget::builder::WidgetHandlerInWhenExprError>) #where_gens {
416                            #core::widget::builder::WhenInputVar::new::<#core::widget::builder::WidgetHandlerInWhenExprError>()
417                        }
418                        pub fn #get_ident_i #impl_gens(&self)
419                        -> (#core::widget::builder::WhenInputVar, impl #core::var::Var<#core::widget::builder::WidgetHandlerInWhenExprError>) #where_gens {
420                            #core::widget::builder::WhenInputVar::new::<#core::widget::builder::WidgetHandlerInWhenExprError>()
421                        }
422                    });
423                }
424            }
425        }
426
427        if !get_var.is_empty() {
428            get_var = quote! {
429                fn var(&self, __index__: usize) -> &dyn #core::var::AnyVar {
430                    match __index__ {
431                        #get_var
432                        n => #core::widget::builder::panic_input(&self.property(), n, #core::widget::builder::InputKind::Var),
433                    }
434                }
435            }
436        }
437        if !get_value.is_empty() {
438            get_value = quote! {
439                fn value(&self, __index__: usize) -> &dyn #core::var::AnyVarValue {
440                    match __index__ {
441                        #get_value
442                        n => #core::widget::builder::panic_input(&self.property(), n, #core::widget::builder::InputKind::Value),
443                    }
444                }
445            }
446        }
447        if !get_ui_node.is_empty() {
448            get_ui_node = quote! {
449                fn ui_node(&self, __index__: usize) -> &#core::widget::node::ArcNode<#core::widget::node::BoxedUiNode> {
450                    match __index__ {
451                        #get_ui_node
452                        n => #core::widget::builder::panic_input(&self.property(), n, #core::widget::builder::InputKind::UiNode),
453                    }
454                }
455            }
456        }
457        if !get_ui_node_list.is_empty() {
458            get_ui_node_list = quote! {
459                fn ui_node_list(&self, __index__: usize) -> &#core::widget::node::ArcNodeList<#core::widget::node::BoxedUiNodeList> {
460                    match __index__ {
461                        #get_ui_node_list
462                        n => #core::widget::builder::panic_input(&self.property(), n, #core::widget::builder::InputKind::UiNodeList),
463                    }
464                }
465            }
466        }
467        if !get_widget_handler.is_empty() {
468            get_widget_handler = quote! {
469                fn widget_handler(&self, __index__: usize) -> &dyn #core::widget::builder::AnyArcWidgetHandler {
470                    match __index__ {
471                        #get_widget_handler
472                        n => #core::widget::builder::panic_input(&self.property(), n, #core::widget::builder::InputKind::WidgetHandler),
473                    }
474                }
475            }
476        }
477
478        let mut sorted_inputs: Vec<_> = inputs[first_input..].iter().collect();
479        sorted_inputs.sort_by_key(|i| &i.ident);
480        let sorted_idents: Vec<_> = sorted_inputs.iter().map(|i| &i.ident).collect();
481        let sorted_tys: Vec<_> = sorted_inputs.iter().map(|i| &i.ty).collect();
482
483        let node_instance = ident_spanned!(output_span=> "__node__");
484
485        let docs = &attrs.docs;
486
487        let allowed_in_when_expr = if allowed_in_when_expr {
488            quote! {
489                pub const fn allowed_in_when_expr(&self) {}
490            }
491        } else {
492            quote!()
493        };
494        let allowed_in_when_assign = if allowed_in_when_assign {
495            quote! {
496                pub const fn allowed_in_when_assign(&self) {}
497            }
498        } else {
499            quote!()
500        };
501
502        let source_location = crate::widget_util::source_location(&core, ident.span());
503
504        let meta = quote! {
505            #cfg
506            #[doc(hidden)]
507            #[allow(non_camel_case_types)]
508            #vis struct #ident_meta { }
509            #cfg
510            #[doc(hidden)]
511            #[allow(dead_code)]
512            impl #ident_meta {
513                pub fn id(&self) -> #core::widget::builder::PropertyId {
514                    #core::static_id! {
515                        static ref ID: #core::widget::builder::PropertyId;
516                    }
517                    *ID
518                }
519
520                #allowed_in_when_expr
521                #allowed_in_when_assign
522
523                pub fn info #impl_gens(&self) -> #core::widget::builder::PropertyInfo #where_gens {
524                    #core::widget::builder::PropertyInfo {
525                        group: {
526                            use #core::widget::builder::nest_group_items::*;
527                            #nest_group
528                        },
529                        capture: #capture,
530                        id: self.id(),
531                        name: std::stringify!(#ident),
532                        location: #source_location,
533                        default: self.default_fn #path_gens(),
534                        new: Self::args_dyn #path_gens,
535                        inputs: std::boxed::Box::new([
536                            #input_info
537                        ]),
538                    }
539                }
540
541                #vis const fn input_types #impl_gens(&self) -> #core::widget::builder::PropertyInputTypes<(#(#storage_tys,)*)> #where_gens {
542                    #core::widget::builder::PropertyInputTypes::unit()
543                }
544
545                pub fn default_fn #impl_gens(&self) -> std::option::Option<fn () -> std::boxed::Box<dyn #core::widget::builder::PropertyArgs>> #where_gens {
546                    #default
547                    #default_fn
548                }
549
550                #vis fn args #impl_gens(
551                    &self,
552                    #(#input_idents: #input_tys),*
553                ) -> std::boxed::Box<dyn #core::widget::builder::PropertyArgs> #where_gens {
554                    std::boxed::Box::new(#ident_args {
555                        #(#input_idents: #input_to_storage),*
556                    })
557                }
558
559                #vis fn args_sorted #impl_gens(
560                    &self,
561                    #(#sorted_idents: #sorted_tys),*
562                ) -> std::boxed::Box<dyn #core::widget::builder::PropertyArgs> #where_gens {
563                    self.args(#(#input_idents),*)
564                }
565
566                fn args_dyn #impl_gens(
567                    __args__: #core::widget::builder::PropertyNewArgs,
568                ) -> std::boxed::Box<dyn #core::widget::builder::PropertyArgs> #where_gens {
569                    let mut __inputs__ = __args__.args.into_iter();
570                    Box::new(#ident_args #path_gens {
571                        #(#input_idents: { #input_new_dyn },)*
572                    })
573                }
574
575                pub fn inputs(&self) -> #ident_inputs {
576                    #ident_inputs { }
577                }
578            }
579        };
580        let instantiate = if capture {
581            quote! {
582                #[allow(unused)]
583                use self::#ident;
584                __child__
585            }
586        } else {
587            quote! {
588                let #node_instance = #ident(__child__, #instantiate);
589                #core::widget::node::UiNode::boxed(#node_instance)
590            }
591        };
592
593        let allow_deprecated = deprecated.as_ref().map(|_| {
594            quote! {
595                #[allow(deprecated)]
596            }
597        });
598
599        let args = quote! {
600            #cfg
601            #[doc(hidden)]
602            #[derive(std::clone::Clone)]
603            #[allow(non_camel_case_types)]
604            #vis struct #ident_args #impl_gens #where_gens {
605                #(#input_idents: #storage_tys),*
606            }
607            #cfg
608            #[doc(hidden)]
609            impl #impl_gens #core::widget::builder::PropertyArgs for #ident_args #ty_gens #where_gens {
610                fn clone_boxed(&self) -> std::boxed::Box<dyn #core::widget::builder::PropertyArgs> {
611                    Box::new(std::clone::Clone::clone(self))
612                }
613
614                fn property(&self) -> #core::widget::builder::PropertyInfo {
615                    #ident_meta { }.info #path_gens()
616                }
617
618                #allow_deprecated
619                fn instantiate(&self, __child__: #core::widget::node::BoxedUiNode) -> #core::widget::node::BoxedUiNode {
620                    #instantiate
621                }
622
623                #get_var
624                #get_value
625                #get_ui_node
626                #get_ui_node_list
627                #get_widget_handler
628            }
629        };
630        let inputs = quote! {
631            #cfg
632            #[doc(hidden)]
633            #[allow(non_camel_case_types)]
634            #vis struct #ident_inputs { }
635            #cfg
636            #[doc(hidden)]
637            impl #ident_inputs {
638                #named_into
639                #get_when_input
640            }
641        };
642
643        let direct_impl = if let Some(impl_for) = impl_for {
644            let mut target = impl_for.target;
645            let (generics_impl, generics) = match &target.segments.last().unwrap().arguments {
646                PathArguments::None => (quote!(), quote!()),
647                PathArguments::AngleBracketed(b) => {
648                    if b.args.is_empty() {
649                        (quote!(), quote!())
650                    } else if b.args.len() > 1 {
651                        errors.push("only `<P>` generics is allowed", b.span());
652                        (quote!(), quote!())
653                    } else {
654                        target.segments.last_mut().unwrap().arguments = PathArguments::None;
655                        (quote!(<P: #core::widget::base::WidgetImpl>), quote!(<P>))
656                    }
657                }
658                PathArguments::Parenthesized(p) => {
659                    errors.push("only `<P>` generics is allowed", p.span());
660                    (quote!(), quote!())
661                }
662            };
663            let docs = &mtd_attrs.docs;
664            quote! {
665                #cfg
666                impl #generics_impl #target #generics {
667                    #(#docs)*
668                    #deprecated
669                    #vis fn #ident #impl_gens(&self, #(#input_idents: #input_tys),*) #where_gens {
670                        let args = #ident_meta { }.args(#(#input_idents),*);
671                        #core::widget::base::WidgetImpl::base_ref(self).mtd_property__(args)
672                    }
673
674                    #[doc(hidden)]
675                    #[allow(dead_code)]
676                    #vis fn #ident_unset(&self) {
677                        #core::widget::base::WidgetImpl::base_ref(self).mtd_property_unset__(#ident_meta { }.id())
678                    }
679
680                    #[doc(hidden)]
681                    #[allow(dead_code)]
682                    #vis fn #ident_sorted #impl_gens(&mut self, #(#sorted_idents: #sorted_tys),*) #where_gens {
683                        let args = #ident_meta { }.args_sorted(#(#sorted_idents),*);
684                        #core::widget::base::WidgetImpl::base_ref(self).mtd_property__(args)
685                    }
686
687                    #[doc(hidden)]
688                    #[allow(dead_code)]
689                    #vis fn #ident_meta(&self) -> #ident_meta {
690                        #ident_meta { }
691                    }
692                }
693            }
694        } else {
695            quote!()
696        };
697
698        quote! {
699            #direct_impl
700
701            #cfg
702            #[doc(hidden)]
703            #[allow(non_camel_case_types)]
704            #vis trait #ident: #core::widget::base::WidgetExt {
705                type MetaType;
706
707                #(#docs)*
708                #deprecated
709                #[allow(clippy::too_many_arguments)]
710                fn #ident #impl_gens(&mut self, #(#input_idents: #input_tys),*) #where_gens {
711                    let args = #ident_meta { }.args(#(#input_idents),*);
712                    self.ext_property__(args)
713                }
714
715                /// Unset the property.
716                fn #ident_unset(&mut self) {
717                    self.ext_property_unset__(#ident_meta {}.id())
718                }
719
720                #[doc(hidden)]
721                fn #ident_sorted #impl_gens(&mut self, #(#sorted_idents: #sorted_tys),*) #where_gens {
722                    let args = #ident_meta { }.args_sorted(#(#sorted_idents),*);
723                    self.ext_property__(args)
724                }
725
726                #[doc(hidden)]
727                fn #ident_meta(&self) -> #ident_meta {
728                    #ident_meta { }
729                }
730            }
731            #cfg
732            #[doc(hidden)]
733            impl self::#ident for #core::widget::base::WidgetBase {
734                type MetaType = ();
735            }
736            #cfg
737            #[doc(hidden)]
738            impl self::#ident for #core::widget::base::NonWidgetBase {
739                type MetaType = ();
740            }
741            #cfg
742            #[doc(hidden)]
743            impl self::#ident for #core::widget::builder::WgtInfo {
744                type MetaType = #ident_meta;
745            }
746
747            #meta
748            #args
749            #inputs
750        }
751    } else {
752        quote!()
753    };
754
755    let r = quote! {
756        #attrs
757        #extra_docs
758        #item
759        #extra
760        #errors
761    };
762    r.into()
763}
764
765struct Args {
766    nest_group: Expr,
767    capture: bool,
768    default: Option<Default>,
769    impl_for: Option<ImplFor>,
770}
771impl Parse for Args {
772    fn parse(input: parse::ParseStream) -> Result<Self> {
773        Ok(Args {
774            nest_group: input.parse()?,
775            capture: if input.peek(Token![,]) && input.peek2(keyword::capture) {
776                let _: Token![,] = input.parse()?;
777                let _: keyword::capture = input.parse()?;
778                true
779            } else {
780                false
781            },
782            default: if input.peek(Token![,]) && input.peek2(Token![default]) {
783                Some(input.parse()?)
784            } else {
785                None
786            },
787            impl_for: if input.peek(Token![,]) && (input.peek2(keyword::widget_impl) || input.peek2(Token![for])) {
788                Some(input.parse()?)
789            } else {
790                None
791            },
792        })
793    }
794}
795
796struct Default {
797    default: Token![default],
798    args: Punctuated<Expr, Token![,]>,
799}
800impl Parse for Default {
801    fn parse(input: parse::ParseStream) -> Result<Self> {
802        let _: Token![,] = input.parse()?;
803        let default = input.parse()?;
804        let inner;
805        parenthesized!(inner in input);
806        Ok(Default {
807            default,
808            args: Punctuated::parse_terminated(&inner)?,
809        })
810    }
811}
812
813struct ImplFor {
814    target: Path,
815}
816impl Parse for ImplFor {
817    fn parse(input: parse::ParseStream) -> Result<Self> {
818        let _: Token![,] = input.parse()?;
819        let _: keyword::widget_impl = input.parse()?;
820        let inner;
821        parenthesized!(inner in input);
822
823        Ok(ImplFor { target: inner.parse()? })
824    }
825}
826
827#[derive(Clone, Copy)]
828enum InputKind {
829    Var,
830    Value,
831    UiNode,
832    WidgetHandler,
833    UiNodeList,
834}
835impl ToTokens for InputKind {
836    fn to_tokens(&self, tokens: &mut TokenStream) {
837        let kin = match self {
838            InputKind::Var => ident!("Var"),
839            InputKind::Value => ident!("Value"),
840            InputKind::UiNode => ident!("UiNode"),
841            InputKind::WidgetHandler => ident!("WidgetHandler"),
842            InputKind::UiNodeList => ident!("UiNodeList"),
843        };
844        let core = crate_core();
845        tokens.extend(quote! {
846            #core::widget::builder::InputKind::#kin
847        });
848    }
849}
850
851struct Input {
852    ident: Ident,
853    kind: InputKind,
854    ty: TokenStream,
855    info_ty: TokenStream,
856    storage_ty: TokenStream,
857}
858impl Input {
859    fn from_arg(arg: &FnArg, errors: &mut Errors) -> Input {
860        let mut input = Input {
861            ident: ident!("__invalid__"),
862            kind: InputKind::Value,
863            ty: quote!(),
864            storage_ty: quote!(),
865            info_ty: quote!(),
866        };
867        match arg {
868            FnArg::Receiver(rcv) => {
869                errors.push("methods cannot be properties", rcv.span());
870            }
871            FnArg::Typed(t) => {
872                if !t.attrs.is_empty() {
873                    errors.push("property input cannot have attributes", t.attrs[0].span());
874                }
875
876                match *t.pat.clone() {
877                    Pat::Ident(id) => {
878                        if id.ident == "self" {
879                            errors.push("methods cannot be properties", id.ident.span());
880                        }
881                        input.ident = id.ident;
882                    }
883                    p => {
884                        errors.push("property input can only have a simple ident", p.span());
885                    }
886                }
887                let core = crate_core();
888
889                match *t.ty.clone() {
890                    Type::ImplTrait(mut it) if it.bounds.len() == 1 => {
891                        let bounds = it.bounds.pop().unwrap().into_value();
892                        match bounds {
893                            TypeParamBound::Trait(tra) if tra.lifetimes.is_none() && tra.paren_token.is_none() => {
894                                let path = tra.path;
895                                let seg = path.segments.last().unwrap();
896
897                                fn ty_from_generic(
898                                    input: &mut Input,
899                                    errors: &mut Errors,
900                                    t: &Type,
901                                    kind: InputKind,
902                                    args: &PathArguments,
903                                ) -> bool {
904                                    if let PathArguments::AngleBracketed(it) = args {
905                                        if it.args.len() == 1 {
906                                            input.kind = kind;
907                                            input.ty = t.to_token_stream();
908                                            input.info_ty = it.args.last().unwrap().to_token_stream();
909                                            return true;
910                                        }
911                                    }
912                                    errors.push("expected single generic param", args.span());
913                                    false
914                                }
915
916                                match seg.ident.to_string().as_str() {
917                                    "IntoVar" if !seg.arguments.is_empty() => {
918                                        if ty_from_generic(&mut input, errors, &t.ty, InputKind::Var, &seg.arguments) {
919                                            let t = &input.info_ty;
920                                            input.storage_ty = quote!(#core::var::BoxedVar<#t>);
921                                        }
922                                    }
923                                    "IntoValue" if !seg.arguments.is_empty() => {
924                                        if ty_from_generic(&mut input, errors, &t.ty, InputKind::Value, &seg.arguments) {
925                                            input.storage_ty = input.info_ty.clone();
926                                        }
927                                    }
928                                    "WidgetHandler" if !seg.arguments.is_empty() => {
929                                        if ty_from_generic(&mut input, errors, &t.ty, InputKind::WidgetHandler, &seg.arguments) {
930                                            let t = &input.info_ty;
931                                            input.storage_ty = quote!(#core::widget::builder::ArcWidgetHandler<#t>);
932                                        }
933                                    }
934                                    "UiNode" => {
935                                        input.kind = InputKind::UiNode;
936                                        input.ty = t.ty.to_token_stream();
937                                        input.info_ty = quote_spanned!(t.ty.span()=> #core::widget::node::BoxedUiNode);
938                                        input.storage_ty = quote!(#core::widget::node::ArcNode<#core::widget::node::BoxedUiNode>);
939                                    }
940                                    "UiNodeList" => {
941                                        input.kind = InputKind::UiNodeList;
942                                        input.ty = t.ty.to_token_stream();
943                                        input.info_ty = quote_spanned!(t.ty.span()=> #core::widget::node::BoxedUiNodeList);
944                                        input.storage_ty = quote!(#core::widget::node::ArcNodeList<#core::widget::node::BoxedUiNodeList>)
945                                    }
946                                    _ => {
947                                        errors.push("property input can only have impl types for: IntoVar<T>, IntoValue<T>, UiNode, WidgetHandler<A>, UiNodeList", seg.span());
948                                    }
949                                }
950                            }
951                            t => {
952                                errors.push("property input can only have `impl OneTrait`", t.span());
953                            }
954                        }
955                    }
956                    t => {
957                        errors.push("property input can only have `impl OneTrait` types", t.span());
958                    }
959                }
960            }
961        }
962        input
963    }
964}
965
966pub mod keyword {
967    syn::custom_keyword!(capture);
968    syn::custom_keyword!(widget_impl);
969}
970
971pub fn expand_meta(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
972    let args = parse_macro_input!(input as MetaArgs);
973    let core = crate_core();
974
975    let r = match args {
976        MetaArgs::Method { self_ty, sep, property } => {
977            let meta_ident = ident!("{}_", property);
978            quote! {
979                <#self_ty as #core::widget::base::WidgetImpl> #sep info_instance__() . #meta_ident()
980            }
981        }
982        MetaArgs::Function { path } => {
983            let ident = &path.segments.last().unwrap().ident;
984            let meta_ident = ident!("{}_", ident);
985
986            quote! {
987                <#core::widget::builder::WgtInfo as #path>::#meta_ident(&#core::widget::builder::WgtInfo)
988            }
989        }
990    };
991    r.into()
992}
993enum MetaArgs {
994    Method {
995        self_ty: Token![Self],
996        sep: Token![::],
997        property: Ident,
998    },
999    Function {
1000        path: Path,
1001    },
1002}
1003impl Parse for MetaArgs {
1004    fn parse(input: parse::ParseStream) -> Result<Self> {
1005        if input.peek(Token![Self]) {
1006            Ok(Self::Method {
1007                self_ty: input.parse()?,
1008                sep: input.parse()?,
1009                property: input.parse()?,
1010            })
1011        } else {
1012            Ok(Self::Function { path: input.parse()? })
1013        }
1014    }
1015}
1016
1017pub fn expand_impl(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
1018    let p = parse_macro_input!(input as PropertyImplArgs);
1019    if p.args.empty_or_trailing() {
1020        return quote_spanned! {p.path.span()=>
1021            compile_error!("missing args")
1022        }
1023        .into();
1024    }
1025
1026    let mut attrs = Attributes::new(p.attrs);
1027    attrs.tag_doc("P", "This method is a widget property");
1028
1029    let cfg = &attrs.cfg;
1030    let vis = p.vis;
1031    let path = p.path;
1032    let ident = &path.segments.last().unwrap().ident;
1033    let ident_unset = ident!("unset_{ident}");
1034    let ident_meta = ident!("{}_", ident);
1035    let ident_sorted = ident!("{}__", ident);
1036    let args = p.args;
1037    let mut sorted_args: Vec<_> = args.iter().collect();
1038    sorted_args.sort_by_key(|a| &a.ident);
1039
1040    let arg_idents = args.iter().map(|a| &a.ident);
1041    let sorted_idents: Vec<_> = sorted_args.iter().map(|a| &a.ident).collect();
1042    let sorted_tys = sorted_args.iter().map(|a| &a.ty);
1043
1044    let core = crate_core();
1045
1046    let r = quote! {
1047        #attrs
1048        #vis fn #ident(&self, #args) {
1049            #core::widget::base::WidgetImpl::base_ref(self).reexport__(|base__| {
1050                #path::#ident(base__, #(#arg_idents),*);
1051            });
1052        }
1053
1054        /// Unset the property.
1055        #cfg
1056        #[doc(hidden)]
1057        #[allow(dead_code)]
1058        #vis fn #ident_unset(&self) {
1059            #core::widget::base::WidgetImpl::base_ref(self).reexport__(|base__| {
1060                #path::#ident_unset(base__);
1061            });
1062        }
1063
1064        #cfg
1065        #[doc(hidden)]
1066        #[allow(dead_code)]
1067        #vis fn #ident_sorted(&self, #(#sorted_idents: #sorted_tys),*) {
1068            #core::widget::base::WidgetImpl::base_ref(self).reexport__(|base__| {
1069                #path::#ident_sorted(base__, #(#sorted_idents),*);
1070            });
1071        }
1072
1073        #cfg
1074        #[doc(hidden)]
1075        #[allow(dead_code)]
1076        #vis fn #ident_meta(&self) -> <#core::widget::builder::WgtInfo as #path>::MetaType {
1077            <#core::widget::builder::WgtInfo as #path>::#ident_meta(&#core::widget::builder::WgtInfo)
1078        }
1079    };
1080    r.into()
1081}
1082
1083struct PropertyImplArgs {
1084    attrs: Vec<Attribute>,
1085    vis: syn::Visibility,
1086    path: Path,
1087    args: Punctuated<SimpleFnArg, Token![,]>,
1088}
1089impl Parse for PropertyImplArgs {
1090    fn parse(input: parse::ParseStream) -> Result<Self> {
1091        Ok(Self {
1092            attrs: Attribute::parse_outer(&non_user_braced!(input, "attrs"))?,
1093            vis: non_user_braced!(input, "vis").parse().unwrap(),
1094            path: non_user_braced!(input, "path").parse()?,
1095            args: Punctuated::parse_terminated(&non_user_braced!(input, "args"))?,
1096        })
1097    }
1098}
1099
1100#[derive(Clone)]
1101struct SimpleFnArg {
1102    ident: Ident,
1103    _s: Token![:],
1104    ty: Type,
1105}
1106impl ToTokens for SimpleFnArg {
1107    fn to_tokens(&self, tokens: &mut TokenStream) {
1108        self.ident.to_tokens(tokens);
1109        self._s.to_tokens(tokens);
1110        self.ty.to_tokens(tokens);
1111    }
1112}
1113impl Parse for SimpleFnArg {
1114    fn parse(input: parse::ParseStream) -> Result<Self> {
1115        Ok(Self {
1116            ident: input.parse()?,
1117            _s: input.parse()?,
1118            ty: input.parse()?,
1119        })
1120    }
1121}