glib_macros/
clone.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, Expr, ExprAsync, ExprClosure, Token,
9};
10
11use crate::utils::crate_ident_new;
12
13#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14pub(crate) enum CaptureKind {
15    Watch,
16    Weak,
17    WeakAllowNone,
18    Strong,
19    ToOwned,
20}
21
22impl TryFrom<&'_ Ident> for CaptureKind {
23    type Error = syn::Error;
24
25    fn try_from(s: &Ident) -> Result<Self, Self::Error> {
26        Ok(match s.to_string().as_str() {
27            "watch" => CaptureKind::Watch,
28            "strong" => CaptureKind::Strong,
29            "weak" => CaptureKind::Weak,
30            "weak_allow_none" => CaptureKind::WeakAllowNone,
31            "to_owned" => CaptureKind::ToOwned,
32            _ => {
33                // This is actually never shown to the user but we need some kind of error type for
34                // TryFrom, () would be enough but then clippy complains.
35                //
36                // We'll keep it here in case it is useful somewhere at a later time.
37                return Err(syn::Error::new(
38                    s.span(),
39                    format!("unknown capture type `{s}`"),
40                ));
41            }
42        })
43    }
44}
45
46#[derive(Default)]
47pub(crate) enum UpgradeBehaviour {
48    #[default]
49    Unit,
50    Panic,
51    Default,
52    Expression(Expr),
53    Closure(ExprClosure),
54}
55
56impl UpgradeBehaviour {
57    pub(crate) fn maybe_parse(
58        attrs: &[Attribute],
59        input: ParseStream,
60    ) -> syn::Result<Option<Self>> {
61        // Caller checked for empty
62        let attr = &attrs[0];
63        attr.meta.require_path_only()?;
64
65        let Some(attr_name) = attr.path().get_ident() else {
66            return Ok(None);
67        };
68
69        let upgrade_behaviour = match attr_name.to_string().as_str() {
70            "upgrade_or" => {
71                let expr = input.parse::<Expr>()?;
72                input.parse::<Token![,]>()?;
73                UpgradeBehaviour::Expression(expr)
74            }
75            "upgrade_or_else" => {
76                let closure = input.parse::<ExprClosure>()?;
77                if closure.asyncness.is_some() {
78                    return Err(syn::Error::new_spanned(
79                        &closure,
80                        "`upgrade_or_else` closure needs to be a non-async closure",
81                    ));
82                }
83                if !closure.inputs.is_empty() {
84                    return Err(syn::Error::new_spanned(
85                        &closure,
86                        "`upgrade_or_else` closure must not have any parameters",
87                    ));
88                }
89
90                input.parse::<Token![,]>()?;
91                UpgradeBehaviour::Closure(closure)
92            }
93            "upgrade_or_default" => UpgradeBehaviour::Default,
94            "upgrade_or_panic" => UpgradeBehaviour::Panic,
95            _ => {
96                return Ok(None);
97            }
98        };
99
100        if attrs.len() > 1 {
101            return Err(syn::Error::new_spanned(
102                &attrs[1],
103                format!(
104                    "upgrade failure attribute must not be followed by any other attributes. Found {} more attribute{}",
105                    attrs.len() - 1,
106                    if attrs.len() > 2 { "s" } else { "" },
107            )));
108        }
109
110        let next_attrs = &input.call(Attribute::parse_outer)?;
111        if !next_attrs.is_empty() {
112            return Err(syn::Error::new_spanned(
113                &next_attrs[0],
114                format!(
115                    "upgrade failure attribute must not be followed by any other attributes. Found {} more attribute{}",
116                    next_attrs.len(),
117                    if next_attrs.len() > 1 { "s" } else { "" },
118                )
119            ));
120        }
121
122        Ok(Some(upgrade_behaviour))
123    }
124}
125
126pub(crate) struct Capture {
127    pub(crate) name: Expr,
128    pub(crate) alias: Option<Ident>,
129    pub(crate) kind: CaptureKind,
130}
131
132impl Capture {
133    pub(crate) fn maybe_parse(
134        attrs: &[Attribute],
135        input: ParseStream,
136    ) -> syn::Result<Option<Self>> {
137        // Caller checked for empty
138        let attr = &attrs[0];
139
140        let Some(attr_name) = attr.path().get_ident() else {
141            return Ok(None);
142        };
143        let Ok(kind) = CaptureKind::try_from(attr_name) else {
144            return Ok(None);
145        };
146
147        if attrs.len() > 1 {
148            return Err(syn::Error::new_spanned(
149                &attrs[1],
150                "variable capture attributes must be followed by an identifier",
151            ));
152        }
153
154        let mut alias = None;
155        if let syn::Meta::List(ref list) = attr.meta {
156            list.parse_nested_meta(|meta| {
157                if meta.path.is_ident("rename_to") {
158                    let value = meta.value()?;
159                    let id = value.parse::<Ident>()?;
160                    if alias.is_some() {
161                        return Err(meta.error("multiple `rename_to` properties are not allowed"));
162                    }
163                    alias = Some(id);
164                } else if let Some(ident) = meta.path.get_ident() {
165                    return Err(
166                        meta.error(
167                            format!(
168                                "unsupported capture attribute property `{ident}`: only `rename_to` is supported"
169                            ),
170                        ),
171                    );
172                } else {
173                    return Err(meta.error("unsupported capture attribute property"));
174                }
175                Ok(())
176            })?;
177        }
178
179        let name = input.parse::<Expr>()?;
180        match name {
181            Expr::Path(ref p) if p.path.get_ident().is_some() => {
182                if p.path.get_ident().unwrap() == "self" && alias.is_none() {
183                    return Err(
184                        syn::Error::new_spanned(
185                            attr,
186                            "capture attribute for `self` requires usage of the `rename_to` attribute property",
187                        ),
188                    );
189                }
190                // Nothing to do, it's just an identifier
191            }
192            _ if alias.is_some() => {
193                // Nothing to do, it's an alias
194            }
195            _ => {
196                return Err(
197                    syn::Error::new_spanned(
198                        attr,
199                        "capture attribute for an expression requires usage of the `rename_to` attribute property",
200                    ),
201                );
202            }
203        }
204
205        input.parse::<Token![,]>()?;
206
207        Ok(Some(Capture { name, alias, kind }))
208    }
209
210    pub(crate) fn alias(&self) -> TokenStream {
211        if let Some(ref alias) = self.alias {
212            alias.to_token_stream()
213        } else {
214            self.name.to_token_stream()
215        }
216    }
217
218    pub(crate) fn outer_before_tokens(&self, crate_ident: &TokenStream) -> TokenStream {
219        let alias = self.alias();
220        let name = &self.name;
221        match self.kind {
222            CaptureKind::Watch => quote! {
223                let #alias = #crate_ident::object::Watchable::watched_object(&#name);
224            },
225            CaptureKind::Weak | CaptureKind::WeakAllowNone => quote! {
226                let #alias = #crate_ident::clone::Downgrade::downgrade(&#name);
227            },
228            CaptureKind::Strong => quote! {
229                let #alias = #name.clone();
230            },
231            CaptureKind::ToOwned => quote! {
232                let #alias = ::std::borrow::ToOwned::to_owned(&*#name);
233            },
234        }
235    }
236
237    pub(crate) fn outer_after_tokens(
238        &self,
239        crate_ident: &TokenStream,
240        closure_ident: &Ident,
241    ) -> TokenStream {
242        let name = &self.name;
243        match self.kind {
244            CaptureKind::Watch => quote! {
245                #crate_ident::object::Watchable::watch_closure(&#name, &#closure_ident);
246            },
247            _ => Default::default(),
248        }
249    }
250
251    pub(crate) fn inner_before_tokens(
252        &self,
253        crate_ident: &TokenStream,
254        weak_upgrade_failure_kind: &UpgradeBehaviour,
255        upgrade_failure_closure_ident: &Ident,
256        unit_return: Option<TokenStream>,
257    ) -> TokenStream {
258        let alias = self.alias();
259        match self.kind {
260            CaptureKind::Watch => {
261                quote! {
262                    let #alias = unsafe { #alias.borrow() };
263                    let #alias = ::core::convert::AsRef::as_ref(&#alias);
264                }
265            }
266            CaptureKind::Weak => match weak_upgrade_failure_kind {
267                UpgradeBehaviour::Panic => {
268                    let err_msg = format!(
269                        "Failed to upgrade `{}`. If you don't want to panic, use `#[upgrade_or]`, `#[upgrade_or_else]` or `#[upgrade_or_default]`",
270                        alias,
271                    );
272                    quote! {
273                        let Some(#alias) = #crate_ident::clone::Upgrade::upgrade(&#alias) else {
274                            panic!(#err_msg);
275                        };
276                    }
277                }
278                UpgradeBehaviour::Default
279                | UpgradeBehaviour::Expression(_)
280                | UpgradeBehaviour::Closure(_) => {
281                    let err_msg = format!("Failed to upgrade `{}`", alias);
282                    quote! {
283                        let Some(#alias) = #crate_ident::clone::Upgrade::upgrade(&#alias) else {
284                            #crate_ident::g_debug!(
285                                #crate_ident::CLONE_MACRO_LOG_DOMAIN,
286                                #err_msg,
287                            );
288                            return (#upgrade_failure_closure_ident)();
289                        };
290                    }
291                }
292                UpgradeBehaviour::Unit => {
293                    let err_msg = format!("Failed to upgrade `{}`", alias);
294                    let unit_return = unit_return.unwrap_or_else(|| {
295                        quote! { return; }
296                    });
297                    quote! {
298                        let Some(#alias) = #crate_ident::clone::Upgrade::upgrade(&#alias) else {
299                            #crate_ident::g_debug!(
300                                #crate_ident::CLONE_MACRO_LOG_DOMAIN,
301                                #err_msg,
302                            );
303                            #unit_return
304                        };
305                    }
306                }
307            },
308            CaptureKind::WeakAllowNone => quote! {
309                let #alias = #crate_ident::clone::Upgrade::upgrade(&#alias);
310            },
311            _ => Default::default(),
312        }
313    }
314}
315
316#[derive(Clone)]
317enum ClosureOrAsync {
318    Closure(ExprClosure),
319    Async(ExprAsync),
320}
321
322impl Parse for ClosureOrAsync {
323    fn parse(input: ParseStream) -> syn::Result<Self> {
324        let expr = input.parse::<Expr>()?;
325        match expr {
326            Expr::Async(async_) => {
327                if async_.capture.is_none() {
328                    return Err(syn::Error::new_spanned(
329                        async_,
330                        "async blocks need to capture variables by move. Please add the `move` keyword",
331                    ));
332                }
333
334                Ok(ClosureOrAsync::Async(async_))
335            }
336            Expr::Closure(closure) => {
337                if closure.capture.is_none() {
338                    return Err(syn::Error::new_spanned(
339                        closure,
340                        "closures need to capture variables by move. Please add the `move` keyword",
341                    ));
342                }
343
344                Ok(ClosureOrAsync::Closure(closure))
345            }
346            _ => Err(syn::Error::new_spanned(
347                expr,
348                "only closures and async blocks are supported",
349            )),
350        }
351    }
352}
353
354impl ToTokens for ClosureOrAsync {
355    fn to_tokens(&self, tokens: &mut TokenStream) {
356        match self {
357            ClosureOrAsync::Closure(ref c) => c.to_tokens(tokens),
358            ClosureOrAsync::Async(ref a) => a.to_tokens(tokens),
359        }
360    }
361}
362
363struct Clone {
364    captures: Vec<Capture>,
365    upgrade_behaviour: UpgradeBehaviour,
366    body: ClosureOrAsync,
367}
368
369impl Parse for Clone {
370    fn parse(input: ParseStream) -> syn::Result<Self> {
371        if input.is_empty() {
372            return Err(syn::Error::new(
373                Span::call_site(),
374                "expected a closure or async block",
375            ));
376        }
377
378        let mut captures: Vec<Capture> = vec![];
379        let mut upgrade_behaviour: Option<(UpgradeBehaviour, Span)> = None;
380
381        loop {
382            // There must either be one or no attributes here. Multiple attributes are not
383            // supported.
384            //
385            // If this is a capture attribute, it must be followed by an identifier.
386            // If this is an upgrade failure attribute, it might be followed by a closure. After the
387            // upgrade failure attribute there must not be any further attributes.
388            //
389            // If this is not an attribute then it is a closure, async closure or async block which
390            // is handled outside the loop
391            let attrs = input.call(Attribute::parse_outer)?;
392            if attrs.is_empty() {
393                break;
394            };
395
396            if let Some(capture) = Capture::maybe_parse(&attrs, input)? {
397                if capture.kind == CaptureKind::Watch {
398                    return Err(syn::Error::new_spanned(
399                        &attrs[0],
400                        "watch variable captures are not supported",
401                    ));
402                }
403
404                captures.push(capture);
405            } else if let Some(behaviour) = UpgradeBehaviour::maybe_parse(&attrs, input)? {
406                if upgrade_behaviour.is_some() {
407                    return Err(syn::Error::new_spanned(
408                        &attrs[0],
409                        "multiple upgrade failure attributes are not supported",
410                    ));
411                }
412
413                upgrade_behaviour = Some((behaviour, attrs[0].span()));
414                break;
415            } else if let Some(ident) = attrs[0].path().get_ident() {
416                return Err(syn::Error::new_spanned(
417                        &attrs[0],
418                        format!(
419                            "unsupported attribute `{ident}`: only `strong`, `weak`, `weak_allow_none`, `to_owned`, `upgrade_or`, `upgrade_or_else`, `upgrade_or_default` and `upgrade_or_panic` are supported",
420                        ),
421                ));
422            } else {
423                return Err(syn::Error::new_spanned(
424                        &attrs[0],
425                        "unsupported attribute: only `strong`, `weak`, `weak_allow_none`, `to_owned`, `upgrade_or_else`, `upgrade_or_default` and `upgrade_or_panic` are supported",
426                ));
427            }
428        }
429
430        if let Some((_, ref span)) = upgrade_behaviour {
431            if captures.iter().all(|c| c.kind != CaptureKind::Weak) {
432                return Err(syn::Error::new(
433                    *span,
434                    "upgrade failure attribute can only be used together with weak variable captures",
435                ));
436            }
437        }
438
439        let upgrade_behaviour = upgrade_behaviour.map(|x| x.0).unwrap_or_default();
440
441        // Following is a closure or async block
442        let body = input.parse::<ClosureOrAsync>()?;
443
444        // Trailing comma, if any
445        if input.peek(Token![,]) {
446            input.parse::<Token![,]>()?;
447        }
448
449        Ok(Clone {
450            captures,
451            upgrade_behaviour,
452            body,
453        })
454    }
455}
456
457impl ToTokens for Clone {
458    fn to_tokens(&self, tokens: &mut TokenStream) {
459        let crate_ident = crate_ident_new();
460
461        let upgrade_failure_closure_ident =
462            Ident::new("____upgrade_failure_closure", Span::call_site());
463
464        let outer_before = self
465            .captures
466            .iter()
467            .map(|c| c.outer_before_tokens(&crate_ident));
468        let inner_before = self.captures.iter().map(|c| {
469            c.inner_before_tokens(
470                &crate_ident,
471                &self.upgrade_behaviour,
472                &upgrade_failure_closure_ident,
473                None,
474            )
475        });
476
477        let upgrade_failure_closure = match self.upgrade_behaviour {
478            UpgradeBehaviour::Default => Some(quote! {
479                let #upgrade_failure_closure_ident = ::std::default::Default::default;
480            }),
481            UpgradeBehaviour::Expression(ref expr) => Some(quote! {
482                let #upgrade_failure_closure_ident = move || {
483                    #expr
484                };
485            }),
486            UpgradeBehaviour::Closure(ref closure) => Some(quote! {
487                let #upgrade_failure_closure_ident = #closure;
488            }),
489            _ => None,
490        };
491
492        let body = match self.body {
493            ClosureOrAsync::Closure(ref c) => {
494                let ExprClosure {
495                    attrs,
496                    lifetimes,
497                    constness,
498                    movability,
499                    asyncness,
500                    capture,
501                    or1_token,
502                    inputs,
503                    or2_token,
504                    output,
505                    body,
506                } = c;
507
508                quote! {
509                    #(#attrs)*
510                    #lifetimes
511                    #constness
512                    #movability
513                    #asyncness
514                    #capture
515                    #or1_token
516                    #inputs
517                    #or2_token
518                    #output
519                    {
520                        #upgrade_failure_closure
521                        #(#inner_before)*
522                        #body
523                    }
524                }
525            }
526            ClosureOrAsync::Async(ref a) => {
527                let ExprAsync {
528                    attrs,
529                    async_token,
530                    capture,
531                    block,
532                } = a;
533
534                // Directly output the statements instead of the whole block including braces as we
535                // already produce a block with braces below and otherwise a compiler warning about
536                // unnecessary braces is wrongly emitted.
537                let stmts = &block.stmts;
538
539                quote! {
540                    #(#attrs)*
541                    #async_token
542                    #capture
543                    {
544                        #upgrade_failure_closure
545                        #(#inner_before)*
546                        #(#stmts)*
547                    }
548                }
549            }
550        };
551
552        tokens.extend(quote! {
553            {
554                #(#outer_before)*
555                #body
556            }
557        });
558    }
559}
560
561pub(crate) fn clone_inner(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
562    let clone = syn::parse_macro_input!(input as Clone);
563    clone.into_token_stream().into()
564}