gtk4_macros/
template_callbacks_attribute.rs
1use 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}