1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
use proc_macro::TokenStream;
use quote::{format_ident, quote, ToTokens};
use syn::{parse_macro_input, parse_quote, ItemImpl, TraitItemFn};

#[proc_macro_attribute]
pub fn object(_attrs: TokenStream, input: TokenStream) -> TokenStream {
    let item = parse_macro_input!(input as ItemImpl);

    let mut items = Vec::new();
    let mut handle_items = Vec::new();
    for item in item.items {
        match item {
            syn::ImplItem::Fn(fn_item) => {
                let mut fn_item = fn_item.clone();
                if fn_item
                    .attrs
                    .iter()
                    .find(|attr| {
                        attr.meta
                            .path()
                            .get_ident()
                            .map(|ident| ident.to_string())
                            .as_deref()
                            == Some("slot")
                    })
                    .is_some()
                {
                    fn_item.attrs.clear();
                    items.push(fn_item.clone());

                    fn_item.sig.inputs[0] = parse_quote!(&self);
                    let fn_ident = &fn_item.sig.ident;
                    let inputs: Vec<_> = fn_item
                        .sig
                        .inputs
                        .iter()
                        .filter_map(|arg| match arg {
                            syn::FnArg::Receiver(_) => None,
                            syn::FnArg::Typed(ty) => Some(ty.pat.to_token_stream()),
                        })
                        .collect();
                    fn_item.block = parse_quote!({
                        self.handle.update(move |me| {
                            me.#fn_ident(#(#inputs)*)
                        });
                    });

                    handle_items.push(fn_item.clone());
                } else {
                    fn_item.attrs.clear();
                    items.push(fn_item.clone());
                }
            }
            syn::ImplItem::Verbatim(tokens) => {
                let item: TraitItemFn = syn::parse2(tokens).unwrap();
                let sig = item.sig;

                let input_pats = sig.inputs.iter().filter_map(|arg| match arg {
                    syn::FnArg::Receiver(_) => None,
                    syn::FnArg::Typed(pat_ty) => Some(&pat_ty.pat),
                });

                items.push(parse_quote! {
                    #[allow(unused_variables)]
                    pub #sig {
                        viewbuilder::Runtime::current().emit(Box::new((#(#input_pats),*,)))
                    }
                });

                let input_tys = sig.inputs.iter().filter_map(|arg| match arg {
                    syn::FnArg::Receiver(_) => None,
                    syn::FnArg::Typed(pat_ty) => Some(&pat_ty.ty),
                });
                let ident = sig.ident;
                handle_items.push(parse_quote! {
                    pub fn #ident(&self) -> viewbuilder::Signal<(#(#input_tys),*,)> {
                        viewbuilder::Signal::new(self.handle.key())
                    }
                });
            }
            _ => {}
        }
    }

    let ident = format_ident!("{}", item.self_ty.to_token_stream().to_string());
    let handle_ident = format_ident!("{}Handle", &ident);
    let output = quote! {
        impl viewbuilder::Object for #ident {
            type Handle = #handle_ident;
        }

        impl #ident {
            #(#items)*
        }

        #[derive(Clone)]
        pub struct #handle_ident {
            handle: viewbuilder::HandleState<#ident>,
        }

        impl From<viewbuilder::HandleState<#ident>> for #handle_ident {
            fn from(value: viewbuilder::HandleState<#ident>) -> Self {
                Self { handle: value }
            }
        }


        impl #handle_ident {
            #(#handle_items)*
        }
    };
    output.into_token_stream().into()
}