gtk4_macros/
template_callbacks_attribute.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use proc_macro2::{Span, TokenStream};
4use quote::{quote, ToTokens, TokenStreamExt};
5use syn::{parse::Parse, Error, Meta, Result, Token};
6
7use crate::util::*;
8
9pub const WRONG_PLACE_MSG: &str =
10    "This macro should be used on the `impl` block for a CompositeTemplate widget";
11
12mod keywords {
13    syn::custom_keyword!(functions);
14    syn::custom_keyword!(function);
15    syn::custom_keyword!(name);
16}
17
18pub struct Args {
19    functions: bool,
20}
21
22impl Parse for Args {
23    fn parse(input: syn::parse::ParseStream) -> Result<Self> {
24        let mut args = Self { functions: false };
25        while !input.is_empty() {
26            let lookahead = input.lookahead1();
27            if lookahead.peek(keywords::functions) {
28                input.parse::<keywords::functions>()?;
29                args.functions = true;
30            } else {
31                return Err(lookahead.error());
32            }
33            if !input.is_empty() {
34                input.parse::<Token![,]>()?;
35            }
36        }
37        Ok(args)
38    }
39}
40
41#[derive(Default)]
42pub struct CallbackArgs {
43    name: Option<String>,
44    function: Option<bool>,
45}
46
47impl CallbackArgs {
48    fn is_function(&self, args: &Args) -> bool {
49        self.function.unwrap_or(args.functions)
50    }
51    fn start(&self, args: &Args) -> usize {
52        match self.is_function(args) {
53            true => 1,
54            false => 0,
55        }
56    }
57}
58
59impl Parse for CallbackArgs {
60    fn parse(input: syn::parse::ParseStream) -> Result<Self> {
61        let mut args = Self {
62            name: None,
63            function: None,
64        };
65
66        while !input.is_empty() {
67            let lookahead = input.lookahead1();
68            if lookahead.peek(keywords::name) {
69                let kw = input.parse::<keywords::name>()?;
70                if args.name.is_some() {
71                    return Err(syn::Error::new_spanned(kw, "Duplicate `name` attribute"));
72                }
73                input.parse::<Token![=]>()?;
74                let name = input.parse::<syn::LitStr>()?;
75                args.name.replace(name.value());
76            } else if lookahead.peek(keywords::function) {
77                let kw = input.parse::<keywords::function>()?;
78                if args.function.is_some() {
79                    return Err(syn::Error::new_spanned(
80                        kw,
81                        "Only one of `function` is allowed",
82                    ));
83                }
84                let function = if input.peek(Token![=]) {
85                    input.parse::<Token![=]>()?;
86                    input.parse::<syn::LitBool>()?.value
87                } else {
88                    true
89                };
90                args.function.replace(function);
91            } else {
92                return Err(lookahead.error());
93            }
94            if !input.is_empty() {
95                input.parse::<Token![,]>()?;
96            }
97        }
98        Ok(args)
99    }
100}
101
102pub fn impl_template_callbacks(mut input: syn::ItemImpl, args: Args) -> Result<TokenStream> {
103    let syn::ItemImpl {
104        attrs,
105        generics,
106        trait_,
107        self_ty,
108        items,
109        ..
110    } = &mut input;
111    if trait_.is_some() {
112        return Err(Error::new(Span::call_site(), WRONG_PLACE_MSG));
113    }
114    let crate_ident = crate_ident_new();
115
116    let mut callbacks = vec![];
117    for item in items.iter_mut() {
118        if let syn::ImplItem::Fn(method) = item {
119            let mut i = 0;
120            let mut attr = None;
121            while i < method.attrs.len() {
122                if method.attrs[i].path().is_ident("template_callback") {
123                    let callback = method.attrs.remove(i);
124                    if attr.is_some() {
125                        return Err(Error::new_spanned(
126                            callback,
127                            "Duplicate `template_callback` attribute",
128                        ));
129                    } else {
130                        attr.replace(callback);
131                    }
132                } else {
133                    i += 1;
134                }
135            }
136
137            let attr = match attr {
138                Some(attr) => attr,
139                None => continue,
140            };
141
142            let ident = &method.sig.ident;
143
144            let callback_args = if matches!(attr.meta, Meta::Path(_)) {
145                CallbackArgs::default()
146            } else {
147                attr.parse_args::<CallbackArgs>()?
148            };
149
150            let name = callback_args
151                .name
152                .as_ref()
153                .cloned()
154                .unwrap_or_else(|| ident.to_string());
155            let start = callback_args.start(&args);
156
157            let mut arg_names = vec![];
158            let mut has_rest = false;
159            let value_unpacks = method.sig.inputs.iter_mut().enumerate().map(|(index, arg)| {
160                if has_rest {
161                    return Err(Error::new_spanned(arg, "Arguments past argument with `rest` attribute"));
162                }
163                let index = index + start;
164                let name = quote::format_ident!("value{}", index);
165                arg_names.push(name.clone());
166                let unwrap_value = |ty, err_msg| {
167                    let index_err_msg = format!(
168                        "Failed to get argument `{ident}` at index {index}: Closure invoked with only {{}} arguments",
169                    );
170                    quote! {
171                        let #name = <[#crate_ident::glib::Value]>::get(&values, #index)
172                            .unwrap_or_else(|| panic!(#index_err_msg, values.len()));
173                        let #name = #crate_ident::glib::Value::get::<#ty>(#name)
174                            .unwrap_or_else(|e| panic!(#err_msg, e));
175                    }
176                };
177                match arg {
178                    syn::FnArg::Receiver(receiver) => {
179                        let err_msg = format!(
180                            "Wrong type for `self` in template callback `{ident}`: {{:?}}",
181                        );
182                        if receiver.reference.is_none() {
183                            Ok(Some(unwrap_value(quote! { #self_ty }, err_msg)))
184                        } else {
185                            if receiver.mutability.is_some() {
186                                return Err(Error::new_spanned(receiver, "Receiver cannot be a mutable reference"));
187                            }
188                            let self_value_ty = quote! {
189                                &<#self_ty as #crate_ident::glib::subclass::types::FromObject>::FromObjectType
190                            };
191                            let mut unwrap = unwrap_value(self_value_ty, err_msg);
192                            unwrap.append_all(quote! {
193                                let #name = <#self_ty as #crate_ident::glib::subclass::types::FromObject>::from_object(#name);
194                            });
195                            Ok(Some(unwrap))
196                        }
197                    },
198                    syn::FnArg::Typed(typed) => {
199                        let mut i = 0;
200                        let mut cur_is_rest = false;
201                        while i < typed.attrs.len() {
202                            if typed.attrs[i].path().is_ident("rest") {
203                                let rest = typed.attrs.remove(i);
204                                if cur_is_rest {
205                                    return Err(Error::new_spanned(rest, "Duplicate `rest` attribute"));
206                                } else if rest.meta.require_path_only().is_err() {
207                                    return Err(Error::new_spanned(rest, "Tokens after `rest` attribute"));
208                                }
209                                cur_is_rest = true;
210                            } else {
211                                i += 1;
212                            }
213                        }
214                        if cur_is_rest {
215                            has_rest = true;
216                            let end = if callback_args.is_function(&args) {
217                                quote! { (values.len() - #start) }
218                            } else {
219                                quote! { values.len() }
220                            };
221                            Ok(Some(quote! {
222                                let #name = &values[#index..#end];
223                            }))
224                        } else {
225                            let ty = typed.ty.as_ref();
226                            let err_msg = format!(
227                                "Wrong type for argument {index} in template callback `{ident}`: {{:?}}",
228                            );
229                            Ok(Some(unwrap_value(ty.to_token_stream(), err_msg)))
230                        }
231                    }
232                }
233            }).collect::<Result<Option<Vec<_>>>>()?;
234
235            let body = value_unpacks
236                .map(|value_unpacks| {
237                    let call = quote! { #self_ty::#ident(#(#arg_names),*) };
238                    let stream = match (&method.sig.asyncness, &method.sig.output) {
239                        (None, syn::ReturnType::Default) => quote! {
240                            #(#value_unpacks)*
241                            #call;
242                            ::std::option::Option::None
243                        },
244                        (None, syn::ReturnType::Type(_, _)) => quote! {
245                            #(#value_unpacks)*
246                            let ret = #call;
247                            let ret: #crate_ident::glib::Value = ::std::convert::From::from(ret);
248                            ::std::option::Option::Some(ret)
249                        },
250                        (Some(_), syn::ReturnType::Default) => quote! {
251                            let values = values.to_vec();
252                            #crate_ident::glib::MainContext::default().spawn_local(async move {
253                                #(#value_unpacks)*
254                                #call.await
255                            });
256                            ::std::option::Option::None
257                        },
258                        (Some(async_), syn::ReturnType::Type(_, _)) => {
259                            return Err(Error::new_spanned(
260                                async_,
261                                "`async` only allowed on template callbacks without a return value",
262                            ));
263                        }
264                    };
265                    Ok(stream)
266                })
267                .transpose()?
268                .unwrap_or_else(|| quote! { ::std::option::Option::None });
269
270            callbacks.push(quote! {
271                (#name, |values| {
272                    #body
273                })
274            });
275        }
276    }
277
278    Ok(quote! {
279        #(#attrs)*
280        impl #generics #self_ty {
281            #(#items)*
282        }
283
284        impl #crate_ident::subclass::widget::CompositeTemplateCallbacks for #self_ty {
285            const CALLBACKS: &'static [#crate_ident::subclass::widget::TemplateCallback] = &[
286                #(#callbacks),*
287            ];
288        }
289    })
290}