glib_macros/
closure.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use proc_macro2::{Ident, Span, TokenStream};
4use quote::{quote, ToTokens};
5use syn::{
6    parse::{Parse, ParseStream},
7    spanned::Spanned,
8    Attribute, ExprClosure, Token,
9};
10
11use crate::{
12    clone::{Capture, CaptureKind, UpgradeBehaviour},
13    utils::crate_ident_new,
14};
15
16struct Closure {
17    captures: Vec<Capture>,
18    args: Vec<Ident>,
19    upgrade_behaviour: UpgradeBehaviour,
20    closure: ExprClosure,
21    constructor: &'static str,
22}
23
24impl Parse for Closure {
25    fn parse(input: ParseStream) -> syn::Result<Self> {
26        if input.is_empty() {
27            return Err(syn::Error::new(Span::call_site(), "expected a closure"));
28        }
29
30        let mut captures: Vec<Capture> = vec![];
31        let mut upgrade_behaviour: Option<(UpgradeBehaviour, Span)> = None;
32
33        loop {
34            // There must either be one or no attributes here. Multiple attributes are not
35            // supported.
36            //
37            // If this is a capture attribute, it must be followed by an identifier.
38            // If this is an upgrade failure attribute, it might be followed by a closure. After the
39            // upgrade failure attribute there must not be any further attributes.
40            //
41            // If this is not an attribute then it is a closure, async closure or async block which
42            // is handled outside the loop
43            let attrs = input.call(Attribute::parse_outer)?;
44            if attrs.is_empty() {
45                break;
46            };
47
48            if let Some(capture) = Capture::maybe_parse(&attrs, input)? {
49                if capture.kind == CaptureKind::Watch
50                    && captures.iter().any(|c| c.kind == CaptureKind::Watch)
51                {
52                    return Err(syn::Error::new_spanned(
53                        &attrs[0],
54                        "only one `watch` capture is allowed per closure",
55                    ));
56                }
57
58                captures.push(capture);
59            } else if let Some(behaviour) = UpgradeBehaviour::maybe_parse(&attrs, input)? {
60                if upgrade_behaviour.is_some() {
61                    return Err(syn::Error::new_spanned(
62                        &attrs[0],
63                        "multiple upgrade failure attributes are not supported",
64                    ));
65                }
66
67                upgrade_behaviour = Some((behaviour, attrs[0].span()));
68                break;
69            } else if let Some(ident) = attrs[0].path().get_ident() {
70                return Err(syn::Error::new_spanned(
71                        &attrs[0],
72                        format!(
73                            "unsupported attribute `{ident}`: only `watch`, `strong`, `weak`, `weak_allow_none`, `to_owned`, `upgrade_or`, `upgrade_or_else`, `upgrade_or_default` and `upgrade_or_panic` are supported",
74                        ),
75                ));
76            } else {
77                return Err(syn::Error::new_spanned(
78                        &attrs[0],
79                        "unsupported attribute: only `strong`, `weak`, `weak_allow_none`, `to_owned`, `upgrade_or_else`, `upgrade_or_default` and `upgrade_or_panic` are supported",
80                ));
81            }
82        }
83
84        if let Some((_, ref span)) = upgrade_behaviour {
85            if captures.iter().all(|c| c.kind != CaptureKind::Weak) {
86                return Err(syn::Error::new(
87                    *span,
88                    "upgrade failure attribute can only be used together with weak variable captures",
89                ));
90            }
91        }
92
93        let upgrade_behaviour = upgrade_behaviour.map(|x| x.0).unwrap_or_default();
94
95        let mut closure = input.parse::<ExprClosure>()?;
96        if closure.asyncness.is_some() {
97            return Err(syn::Error::new_spanned(
98                closure,
99                "async closures not supported",
100            ));
101        }
102        if !captures.is_empty() && closure.capture.is_none() {
103            return Err(syn::Error::new_spanned(
104                closure,
105                "closures need to capture variables by move. Please add the `move` keyword",
106            ));
107        }
108        closure.capture = None;
109
110        let args = closure
111            .inputs
112            .iter()
113            .enumerate()
114            .map(|(i, _)| Ident::new(&format!("____value{i}"), Span::call_site()))
115            .collect();
116
117        // Trailing comma, if any
118        if input.peek(Token![,]) {
119            input.parse::<Token![,]>()?;
120        }
121
122        Ok(Closure {
123            captures,
124            args,
125            upgrade_behaviour,
126            closure,
127            constructor: "new",
128        })
129    }
130}
131
132impl ToTokens for Closure {
133    fn to_tokens(&self, tokens: &mut TokenStream) {
134        let crate_ident = crate_ident_new();
135
136        let closure_ident = Ident::new("____closure", Span::call_site());
137        let values_ident = Ident::new("____values", Span::call_site());
138        let upgrade_failure_closure_ident =
139            Ident::new("____upgrade_failure_closure", Span::call_site());
140        let upgrade_failure_closure_wrapped_ident =
141            Ident::new("____upgrade_failure_closure_wrapped", Span::call_site());
142
143        let outer_before = self
144            .captures
145            .iter()
146            .map(|c| c.outer_before_tokens(&crate_ident));
147        let inner_before = self.captures.iter().map(|c| {
148            c.inner_before_tokens(
149                &crate_ident,
150                &self.upgrade_behaviour,
151                &upgrade_failure_closure_wrapped_ident,
152                Some(quote! {
153                    return #crate_ident::closure::IntoClosureReturnValue::into_closure_return_value(());
154                }),
155            )
156        });
157        let outer_after = self
158            .captures
159            .iter()
160            .map(|c| c.outer_after_tokens(&crate_ident, &closure_ident));
161
162        let arg_values = self.args.iter().enumerate().map(|(index, arg)| {
163            let err_msg = format!("Wrong type for argument {index}: {{:?}}");
164            quote! {
165                let #arg = ::core::result::Result::unwrap_or_else(
166                    #crate_ident::Value::get(&#values_ident[#index]),
167                    |e| panic!(#err_msg, e),
168                );
169            }
170        });
171        let arg_names = &self.args;
172        let args_len = self.args.len();
173        let closure = &self.closure;
174        let constructor = Ident::new(self.constructor, Span::call_site());
175
176        let upgrade_failure_closure = match self.upgrade_behaviour {
177            UpgradeBehaviour::Default => Some(quote! {
178                let #upgrade_failure_closure_ident = ::std::default::Default::default;
179                let #upgrade_failure_closure_wrapped_ident = ||
180                    #crate_ident::closure::IntoClosureReturnValue::into_closure_return_value(
181                        (#upgrade_failure_closure_ident)()
182                    );
183            }),
184            UpgradeBehaviour::Expression(ref expr) => Some(quote! {
185                let #upgrade_failure_closure_ident = move || {
186                    #expr
187                };
188                let #upgrade_failure_closure_wrapped_ident = ||
189                    #crate_ident::closure::IntoClosureReturnValue::into_closure_return_value(
190                        (#upgrade_failure_closure_ident)()
191                    );
192            }),
193            UpgradeBehaviour::Closure(ref closure_2) => Some(quote! {
194                    let #upgrade_failure_closure_ident = #closure_2;
195                    let #upgrade_failure_closure_wrapped_ident = ||
196                        #crate_ident::closure::IntoClosureReturnValue::into_closure_return_value(
197                            (#upgrade_failure_closure_ident)()
198                        );
199            }),
200            _ => None,
201        };
202
203        let assert_return_type = upgrade_failure_closure.is_some().then(|| {
204            quote! {
205                fn ____same<T>(_a: &T, _b: impl Fn() -> T) {}
206                ____same(&____res, #upgrade_failure_closure_ident);
207            }
208        });
209
210        tokens.extend(quote! {
211            {
212                let #closure_ident = {
213                    #(#outer_before)*
214                    #crate_ident::closure::RustClosure::#constructor(move |#values_ident| {
215                        assert_eq!(
216                            #values_ident.len(),
217                            #args_len,
218                            "Expected {} arguments but got {}",
219                            #args_len,
220                            #values_ident.len(),
221                        );
222                        #upgrade_failure_closure
223                        #(#inner_before)*
224                        #(#arg_values)*
225                        #crate_ident::closure::IntoClosureReturnValue::into_closure_return_value({
226                            let ____res = (#closure)(#(#arg_names),*);
227                            #assert_return_type
228                            ____res
229                        })
230                    }
231                    )
232                };
233                #(#outer_after)*
234                #closure_ident
235            }
236        });
237    }
238}
239
240pub(crate) fn closure_inner(
241    input: proc_macro::TokenStream,
242    constructor: &'static str,
243) -> proc_macro::TokenStream {
244    let mut closure = syn::parse_macro_input!(input as Closure);
245    closure.constructor = constructor;
246    closure.into_token_stream().into()
247}