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