glib_macros/
properties.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use crate::utils::crate_ident_new;
4use proc_macro::TokenStream;
5use proc_macro2::TokenStream as TokenStream2;
6use quote::format_ident;
7use quote::{quote, quote_spanned};
8use std::collections::HashMap;
9use syn::ext::IdentExt;
10use syn::parenthesized;
11use syn::parse::Parse;
12use syn::punctuated::Punctuated;
13use syn::spanned::Spanned;
14use syn::Token;
15use syn::{parse_quote_spanned, Attribute, LitStr};
16
17pub struct PropsMacroInput {
18    wrapper_ty: syn::Path,
19    ext_trait: Option<Option<syn::Ident>>,
20    ident: syn::Ident,
21    props: Vec<PropDesc>,
22}
23
24pub struct PropertiesAttrs {
25    wrapper_ty: syn::Path,
26    // None => no ext trait,
27    // Some(None) => derive the ext trait from the wrapper type,
28    // Some(Some(ident)) => use the given ext trait Ident
29    ext_trait: Option<Option<syn::Ident>>,
30}
31
32impl Parse for PropertiesAttrs {
33    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
34        let mut wrapper_ty = None;
35        let mut ext_trait = None;
36
37        while !input.is_empty() {
38            let ident = input.parse::<syn::Ident>()?;
39            if ident == "wrapper_type" {
40                let _eq = input.parse::<Token![=]>()?;
41                wrapper_ty = Some(input.parse::<syn::Path>()?);
42            } else if ident == "ext_trait" {
43                if input.peek(Token![=]) {
44                    let _eq = input.parse::<Token![=]>()?;
45                    let ident = input.parse::<syn::Ident>()?;
46                    ext_trait = Some(Some(ident));
47                } else {
48                    ext_trait = Some(None);
49                }
50            }
51            if input.peek(Token![,]) {
52                input.parse::<Token![,]>()?;
53            }
54        }
55
56        Ok(Self {
57            wrapper_ty: wrapper_ty.ok_or_else(|| {
58                syn::Error::new(input.span(), "missing #[properties(wrapper_type = ...)]")
59            })?,
60            ext_trait,
61        })
62    }
63}
64
65impl Parse for PropsMacroInput {
66    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
67        let derive_input: syn::DeriveInput = input.parse()?;
68        let attrs = derive_input
69            .attrs
70            .iter()
71            .find(|x| x.path().is_ident("properties"))
72            .ok_or_else(|| {
73                syn::Error::new(
74                    derive_input.span(),
75                    "missing #[properties(wrapper_type = ...)]",
76                )
77            })?;
78        let attrs: PropertiesAttrs = attrs.parse_args()?;
79        let props: Vec<_> = match derive_input.data {
80            syn::Data::Struct(struct_data) => parse_fields(struct_data.fields)?,
81            _ => {
82                return Err(syn::Error::new(
83                    derive_input.span(),
84                    "Properties can only be derived on structs",
85                ))
86            }
87        };
88        Ok(Self {
89            wrapper_ty: attrs.wrapper_ty,
90            ext_trait: attrs.ext_trait,
91            ident: derive_input.ident,
92            props,
93        })
94    }
95}
96
97enum MaybeCustomFn {
98    Custom(Box<syn::Expr>),
99    Default,
100}
101
102impl std::convert::From<Option<syn::Expr>> for MaybeCustomFn {
103    fn from(item: Option<syn::Expr>) -> Self {
104        match item {
105            Some(expr) => Self::Custom(Box::new(expr)),
106            None => Self::Default,
107        }
108    }
109}
110
111enum PropAttr {
112    // builder(required_params).parameter(value)
113    // becomes
114    // Builder(Punctuated(required_params), Optionals(TokenStream))
115    Builder(Punctuated<syn::Expr, Token![,]>, TokenStream2),
116
117    // ident
118    Nullable,
119
120    // ident [= expr]
121    Get(Option<syn::Expr>),
122    Set(Option<syn::Expr>),
123
124    // ident = expr
125    OverrideClass(syn::Type),
126    OverrideInterface(syn::Type),
127
128    // ident = expr
129    Type(syn::Type),
130
131    // This will get translated from `ident = value` to `.ident(value)`
132    // and will get appended after the `builder(...)` call.
133    // ident [= expr]
134    BuilderField((syn::Ident, Option<syn::Expr>)),
135
136    // ident = ident
137    Member(syn::Ident),
138
139    // ident = "literal"
140    Name(syn::LitStr),
141}
142
143impl Parse for PropAttr {
144    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
145        let name = input.call(syn::Ident::parse_any)?;
146        let name_str = name.to_string();
147
148        let res = if input.peek(Token![=]) {
149            let _assign_token: Token![=] = input.parse()?;
150            // name = expr | type | ident
151            match &*name_str {
152                "name" => PropAttr::Name(input.parse()?),
153                "get" => PropAttr::Get(Some(input.parse()?)),
154                "set" => PropAttr::Set(Some(input.parse()?)),
155                "override_class" => PropAttr::OverrideClass(input.parse()?),
156                "override_interface" => PropAttr::OverrideInterface(input.parse()?),
157                "type" => PropAttr::Type(input.parse()?),
158                "member" => PropAttr::Member(input.parse()?),
159                // Special case "default = ..." and map it to .default_value(...)
160                "default" => PropAttr::BuilderField((
161                    syn::Ident::new("default_value", name.span()),
162                    Some(input.parse()?),
163                )),
164                _ => PropAttr::BuilderField((name, Some(input.parse()?))),
165            }
166        } else if input.peek(syn::token::Paren) {
167            match &*name_str {
168                "builder" => {
169                    let content;
170                    parenthesized!(content in input);
171                    let required = content.parse_terminated(syn::Expr::parse, Token![,])?;
172                    let rest: TokenStream2 = input.parse()?;
173                    PropAttr::Builder(required, rest)
174                }
175                _ => {
176                    return Err(syn::Error::new(
177                        name.span(),
178                        format!("Unsupported attribute list {name_str}(...)"),
179                    ))
180                }
181            }
182        } else {
183            // attributes with only the identifier name
184            match &*name_str {
185                "nullable" => PropAttr::Nullable,
186                "get" => PropAttr::Get(None),
187                "set" => PropAttr::Set(None),
188                "readwrite" | "read_only" | "write_only" => {
189                    return Err(syn::Error::new(
190                        name.span(),
191                        format!(
192                            "{name} is a flag managed by the Properties macro. \
193                            Use `get` and `set` to manage read and write access to a property",
194                        ),
195                    ))
196                }
197                _ => PropAttr::BuilderField((name, None)),
198            }
199        };
200        Ok(res)
201    }
202}
203
204#[derive(Default)]
205struct ReceivedAttrs {
206    nullable: bool,
207    get: Option<MaybeCustomFn>,
208    set: Option<MaybeCustomFn>,
209    override_class: Option<syn::Type>,
210    override_interface: Option<syn::Type>,
211    ty: Option<syn::Type>,
212    member: Option<syn::Ident>,
213    name: Option<syn::LitStr>,
214    builder: Option<(Punctuated<syn::Expr, Token![,]>, TokenStream2)>,
215    builder_fields: HashMap<syn::Ident, Option<syn::Expr>>,
216}
217
218impl Parse for ReceivedAttrs {
219    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
220        let attrs = syn::punctuated::Punctuated::<PropAttr, Token![,]>::parse_terminated(input)?;
221        let this = attrs.into_iter().fold(Self::default(), |mut this, attr| {
222            this.set_from_attr(attr);
223            this
224        });
225
226        Ok(this)
227    }
228}
229
230impl ReceivedAttrs {
231    fn set_from_attr(&mut self, attr: PropAttr) {
232        match attr {
233            PropAttr::Nullable => self.nullable = true,
234            PropAttr::Get(some_fn) => self.get = Some(some_fn.into()),
235            PropAttr::Set(some_fn) => self.set = Some(some_fn.into()),
236            PropAttr::Name(lit) => self.name = Some(lit),
237            PropAttr::OverrideClass(ty) => self.override_class = Some(ty),
238            PropAttr::OverrideInterface(ty) => self.override_interface = Some(ty),
239            PropAttr::Type(ty) => self.ty = Some(ty),
240            PropAttr::Member(member) => self.member = Some(member),
241            PropAttr::Builder(required_params, optionals) => {
242                self.builder = Some((required_params, optionals))
243            }
244            PropAttr::BuilderField((ident, expr)) => {
245                self.builder_fields.insert(ident, expr);
246            }
247        }
248    }
249}
250
251// It's a cleaned up version of `ReceivedAttrs` where some missing attributes get a default,
252// generated value.
253struct PropDesc {
254    attrs_span: proc_macro2::Span,
255    field_ident: syn::Ident,
256    ty: syn::Type,
257    name: syn::LitStr,
258    comments: Vec<Attribute>,
259    override_class: Option<syn::Type>,
260    override_interface: Option<syn::Type>,
261    nullable: bool,
262    get: Option<MaybeCustomFn>,
263    set: Option<MaybeCustomFn>,
264    member: Option<syn::Ident>,
265    builder: Option<(Punctuated<syn::Expr, Token![,]>, TokenStream2)>,
266    builder_fields: HashMap<syn::Ident, Option<syn::Expr>>,
267    is_construct_only: bool,
268}
269
270impl PropDesc {
271    fn new(
272        attrs_span: proc_macro2::Span,
273        field_ident: syn::Ident,
274        field_ty: syn::Type,
275        comments: Vec<Attribute>,
276        attrs: ReceivedAttrs,
277    ) -> syn::Result<Self> {
278        let ReceivedAttrs {
279            nullable,
280            get,
281            mut set,
282            override_class,
283            override_interface,
284            ty,
285            member,
286            name,
287            builder,
288            builder_fields,
289        } = attrs;
290
291        let is_construct_only = builder_fields.iter().any(|(k, _)| *k == "construct_only");
292        if is_construct_only && set.is_none() {
293            // Insert a default internal setter automatically
294            set = Some(MaybeCustomFn::Default);
295        }
296
297        if get.is_none() && set.is_none() {
298            return Err(syn::Error::new(
299                attrs_span,
300                "No `get` or `set` specified: at least one is required.".to_string(),
301            ));
302        }
303
304        if override_class.is_some() && override_interface.is_some() {
305            return Err(syn::Error::new(
306                attrs_span,
307                "Both `override_class` and `override_interface` specified.".to_string(),
308            ));
309        }
310
311        // Fill needed, but missing, attributes with calculated default values
312        let name = name.unwrap_or_else(|| {
313            syn::LitStr::new(
314                &field_ident.to_string().trim_matches('_').replace('_', "-"),
315                field_ident.span(),
316            )
317        });
318        let ty = ty.unwrap_or_else(|| field_ty.clone());
319
320        // Now that everything is set and safe, return the final property description
321        Ok(Self {
322            attrs_span,
323            field_ident,
324            ty,
325            name,
326            comments,
327            override_class,
328            override_interface,
329            nullable,
330            get,
331            set,
332            member,
333            builder,
334            builder_fields,
335            is_construct_only,
336        })
337    }
338    fn is_overriding(&self) -> bool {
339        self.override_class.is_some() || self.override_interface.is_some()
340    }
341}
342
343fn expand_param_spec(prop: &PropDesc) -> TokenStream2 {
344    let crate_ident = crate_ident_new();
345    let PropDesc {
346        ty, name, builder, ..
347    } = prop;
348    let stripped_name = strip_raw_prefix_from_name(name);
349
350    match (&prop.override_class, &prop.override_interface) {
351        (Some(c), None) => {
352            return quote!(#crate_ident::ParamSpecOverride::for_class::<#c>(#stripped_name))
353        }
354        (None, Some(i)) => {
355            return quote!(#crate_ident::ParamSpecOverride::for_interface::<#i>(#stripped_name))
356        }
357        (Some(_), Some(_)) => {
358            unreachable!("Both `override_class` and `override_interface` specified")
359        }
360        (None, None) => (),
361    };
362
363    let rw_flags = match (&prop.get, &prop.set) {
364        (Some(_), Some(_)) => quote!(.readwrite()),
365        (Some(_), None) => quote!(.read_only()),
366        (None, Some(_)) => quote!(.write_only()),
367        (None, None) => unreachable!("No `get` or `set` specified"),
368    };
369
370    let builder_call = builder
371        .as_ref()
372        .cloned()
373        .map(|(mut required_params, chained_methods)| {
374            let name_expr = syn::ExprLit {
375                attrs: vec![],
376                lit: syn::Lit::Str(stripped_name.to_owned()),
377            };
378            required_params.insert(0, name_expr.into());
379            let required_params = required_params.iter();
380
381            quote!((#(#required_params,)*)#chained_methods)
382        })
383        .unwrap_or(quote!((#stripped_name)));
384
385    let builder_fields = prop.builder_fields.iter().map(|(k, v)| quote!(.#k(#v)));
386
387    let span = prop.attrs_span;
388    quote_spanned! {span=>
389        <<#ty as #crate_ident::property::Property>::Value as #crate_ident::prelude::HasParamSpec>
390            ::param_spec_builder() #builder_call
391            #rw_flags
392            #(#builder_fields)*
393            .build()
394    }
395}
396
397fn expand_properties_fn(props: &[PropDesc]) -> TokenStream2 {
398    let n_props = props.len();
399    let crate_ident = crate_ident_new();
400    let param_specs = props.iter().map(expand_param_spec);
401    quote!(
402        fn derived_properties() -> &'static [#crate_ident::ParamSpec] {
403            use #crate_ident::prelude::ParamSpecBuilderExt;
404            static PROPERTIES: ::std::sync::OnceLock<[#crate_ident::ParamSpec; #n_props]> = ::std::sync::OnceLock::new();
405            PROPERTIES.get_or_init(|| [
406                #(#param_specs,)*
407            ])
408        }
409    )
410}
411
412fn expand_property_fn(props: &[PropDesc]) -> TokenStream2 {
413    let crate_ident = crate_ident_new();
414    let match_branch_get = props.iter().flat_map(|p| {
415        let PropDesc {
416            name,
417            field_ident,
418            member,
419            get,
420            ty,
421            ..
422        } = p;
423
424        let enum_ident = name_to_enum_ident(name.value());
425        let span = p.attrs_span;
426        get.as_ref().map(|get| {
427            let body = match (member, get) {
428                (_, MaybeCustomFn::Custom(expr)) => quote!(
429                    DerivedPropertiesEnum::#enum_ident => {
430                        let value: <#ty as #crate_ident::property::Property>::Value = (#expr)(&self);
431                        ::std::convert::From::from(value)
432                    }
433                ),
434                (None, MaybeCustomFn::Default) => quote!(
435                    DerivedPropertiesEnum::#enum_ident =>
436                        #crate_ident::property::PropertyGet::get(&self.#field_ident, |v| ::std::convert::From::from(v))
437
438                ),
439                (Some(member), MaybeCustomFn::Default) => quote!(
440                    DerivedPropertiesEnum::#enum_ident =>
441                        #crate_ident::property::PropertyGet::get(&self.#field_ident, |v| ::std::convert::From::from(&v.#member))
442
443                ),
444            };
445            quote_spanned!(span=> #body)
446        })
447    });
448    quote!(
449        fn derived_property(
450            &self,
451            id: usize,
452            pspec: &#crate_ident::ParamSpec
453        ) -> #crate_ident::Value {
454            let prop: DerivedPropertiesEnum = std::convert::TryFrom::try_from(id-1)
455                .unwrap_or_else(|_| panic!("property not defined {}", pspec.name()));
456            match prop {
457                #(#match_branch_get,)*
458                _ => panic!("missing getter for property {}", pspec.name()),
459            }
460        }
461    )
462}
463
464fn expand_set_property_fn(props: &[PropDesc]) -> TokenStream2 {
465    let crate_ident = crate_ident_new();
466    let match_branch_set = props.iter().flat_map(|p| {
467        let PropDesc {
468            name,
469            field_ident,
470            member,
471            set,
472            ty,
473            ..
474        } = p;
475        let stripped_name = strip_raw_prefix_from_name(name);
476        let crate_ident = crate_ident_new();
477        let enum_ident = name_to_enum_ident(name.value());
478        let span = p.attrs_span;
479        let expect = quote!(.unwrap_or_else(
480            |err| panic!(
481                "Invalid conversion from `glib::value::Value` to `{}` inside setter for property `{}`: {:?}",
482                ::std::any::type_name::<<#ty as #crate_ident::property::Property>::Value>(), #stripped_name, err
483            )
484        ));
485        set.as_ref().map(|set| {
486            let body = match (member, set) {
487                (_, MaybeCustomFn::Custom(expr)) => quote!(
488                    DerivedPropertiesEnum::#enum_ident => {
489                        (#expr)(&self, #crate_ident::Value::get(value)#expect);
490                    }
491                ),
492                (None, MaybeCustomFn::Default) => quote!(
493                    DerivedPropertiesEnum::#enum_ident => {
494                        #crate_ident::property::PropertySet::set(
495                            &self.#field_ident,
496                            #crate_ident::Value::get(value)#expect
497                        );
498                    }
499                ),
500                (Some(member), MaybeCustomFn::Default) => quote!(
501                    DerivedPropertiesEnum::#enum_ident => {
502                        #crate_ident::property::PropertySetNested::set_nested(
503                            &self.#field_ident,
504                            move |v| v.#member = #crate_ident::Value::get(value)#expect
505                        );
506                    }
507                ),
508            };
509            quote_spanned!(span=> #body)
510        })
511    });
512    quote!(
513        #[allow(unreachable_code)]
514        fn derived_set_property(&self,
515            id: usize,
516            value: &#crate_ident::Value,
517            pspec: &#crate_ident::ParamSpec
518        ){
519            let prop: DerivedPropertiesEnum = std::convert::TryFrom::try_from(id-1)
520                .unwrap_or_else(|_| panic!("property not defined {}", pspec.name()));
521            match prop {
522                #(#match_branch_set,)*
523                _ => panic!("missing setter for property {}", pspec.name()),
524            }
525        }
526    )
527}
528
529fn parse_fields(fields: syn::Fields) -> syn::Result<Vec<PropDesc>> {
530    let mut properties = vec![];
531
532    for field in fields.into_iter() {
533        let syn::Field {
534            ident, attrs, ty, ..
535        } = field;
536        // Store the comments until the next `#[property]` we see and then attach them to it.
537        let mut comments: Vec<Attribute> = vec![];
538        for prop_attr in attrs.iter() {
539            if prop_attr.path().is_ident("doc") {
540                comments.push(prop_attr.clone());
541            } else if prop_attr.path().is_ident("property") {
542                let span = prop_attr.span();
543                let existing_comments = comments;
544                comments = vec![];
545                properties.push(PropDesc::new(
546                    span,
547                    ident.as_ref().unwrap().clone(),
548                    ty.clone(),
549                    existing_comments,
550                    prop_attr.parse_args()?,
551                )?);
552            }
553        }
554    }
555
556    Ok(properties)
557}
558
559/// Converts a glib property name to a correct rust ident
560fn name_to_ident(name: &syn::LitStr) -> syn::Ident {
561    format_ident!("{}", name.value().replace('-', "_"))
562}
563
564/// Strips out raw identifier prefix (`r#`) from literal string items
565fn strip_raw_prefix_from_name(name: &LitStr) -> LitStr {
566    LitStr::new(
567        name.value().strip_prefix("r#").unwrap_or(&name.value()),
568        name.span(),
569    )
570}
571
572/// Splits the comments for a property between the getter and setter
573///
574/// The return tuple is the attributes to copy over into the getter and setter
575/// respectively.
576fn arrange_property_comments(comments: &[Attribute]) -> (Vec<&Attribute>, Vec<&Attribute>) {
577    let mut untagged = vec![];
578    let mut getter = vec![];
579    let mut setter = vec![];
580    let mut saw_section = false;
581
582    // We start with no tags so if the programmer doesn't split the comments we can still arrange them.
583    let mut current_section = &mut untagged;
584    for attr in comments {
585        if let syn::Meta::NameValue(meta) = &attr.meta {
586            if let syn::Expr::Lit(expr) = &meta.value {
587                if let syn::Lit::Str(lit_str) = &expr.lit {
588                    // Now that we have the one line of comment, see if we need
589                    // to switch a particular section to be the active one (via
590                    // the header syntax) or add the current line to the active
591                    // section.
592                    match lit_str.value().trim() {
593                        "# Getter" => {
594                            current_section = &mut getter;
595                            saw_section = true;
596                        }
597                        "# Setter" => {
598                            current_section = &mut setter;
599                            saw_section = true;
600                        }
601                        _ => current_section.push(attr),
602                    }
603                }
604            }
605        }
606    }
607
608    // If no sections were defined then we put the same in both
609    if !saw_section {
610        return (untagged.clone(), untagged);
611    }
612
613    (getter, setter)
614}
615
616fn expand_impl_getset_properties(props: &[PropDesc]) -> Vec<syn::ImplItemFn> {
617    let crate_ident = crate_ident_new();
618    let defs = props.iter().filter(|p| !p.is_overriding()).map(|p| {
619        let name = &p.name;
620        let stripped_name = strip_raw_prefix_from_name(name);
621        let ident = name_to_ident(name);
622        let ty = &p.ty;
623
624        let (getter_docs, setter_docs) = arrange_property_comments(&p.comments);
625
626        let getter = p.get.is_some().then(|| {
627            let span = p.attrs_span;
628            parse_quote_spanned!(span=>
629                #(#getter_docs)*
630                #[must_use]
631                #[allow(dead_code)]
632                pub fn #ident(&self) -> <#ty as #crate_ident::property::Property>::Value {
633                    self.property::<<#ty as #crate_ident::property::Property>::Value>(#stripped_name)
634                }
635            )
636        });
637
638        let setter = (p.set.is_some() && !p.is_construct_only).then(|| {
639            let ident = format_ident!("set_{}", ident);
640            let target_ty = quote!(<<#ty as #crate_ident::property::Property>::Value as #crate_ident::prelude::HasParamSpec>::SetValue);
641            let set_ty = if p.nullable {
642               quote!(::core::option::Option<impl std::borrow::Borrow<#target_ty>>)
643            } else {
644               quote!(impl std::borrow::Borrow<#target_ty>)
645            };
646            let upcasted_borrowed_value = if p.nullable {
647                quote!(
648                    value.as_ref().map(|v| std::borrow::Borrow::borrow(v))
649                )
650            } else {
651                quote!(
652                    std::borrow::Borrow::borrow(&value)
653                )
654            };
655            let span = p.attrs_span;
656            parse_quote_spanned!(span=>
657                #(#setter_docs)*
658                #[allow(dead_code)]
659                pub fn #ident<'a>(&self, value: #set_ty) {
660                    self.set_property_from_value(#stripped_name, &::std::convert::From::from(#upcasted_borrowed_value))
661                }
662            )
663        });
664
665        [getter, setter]
666    });
667    defs.flatten() // flattens []
668        .flatten() // removes None
669        .collect::<Vec<_>>()
670}
671
672fn expand_impl_connect_prop_notify(props: &[PropDesc]) -> Vec<syn::ImplItemFn> {
673    let crate_ident = crate_ident_new();
674    let connection_fns = props.iter().filter(|p| !p.is_overriding()).map(|p| -> syn::ImplItemFn {
675        let name = &p.name;
676        let stripped_name = strip_raw_prefix_from_name(name);
677        let fn_ident = format_ident!("connect_{}_notify", name_to_ident(name));
678        let span = p.attrs_span;
679        let doc = format!("Listen for notifications of a change in the `{}` property", name.value());
680        parse_quote_spanned!(span=>
681            #[doc = #doc]
682            #[allow(dead_code)]
683            pub fn #fn_ident<F: Fn(&Self) + 'static>(&self, f: F) -> #crate_ident::SignalHandlerId {
684                self.connect_notify_local(::core::option::Option::Some(#stripped_name), move |this, _| {
685                    f(this)
686                })
687            }
688        )
689    });
690    connection_fns.collect::<Vec<_>>()
691}
692
693fn expand_impl_notify_prop(wrapper_type: &syn::Path, props: &[PropDesc]) -> Vec<syn::ImplItemFn> {
694    let crate_ident = crate_ident_new();
695    let emit_fns = props.iter().filter(|p| !p.is_overriding()).map(|p| -> syn::ImplItemFn {
696        let name = strip_raw_prefix_from_name(&p.name);
697        let fn_ident = format_ident!("notify_{}", name_to_ident(&name));
698        let span = p.attrs_span;
699        let enum_ident = name_to_enum_ident(name.value());
700        let doc = format!("Notify listeners of a change in the `{}` property", name.value());
701        parse_quote_spanned!(span=>
702            #[doc = #doc]
703            #[allow(dead_code)]
704            pub fn #fn_ident(&self) {
705                self.notify_by_pspec(
706                    &<<#wrapper_type as #crate_ident::object::ObjectSubclassIs>::Subclass
707                        as #crate_ident::subclass::object::DerivedObjectProperties>::derived_properties()
708                    [DerivedPropertiesEnum::#enum_ident as usize]
709                );
710            }
711        )
712    });
713    emit_fns.collect::<Vec<_>>()
714}
715
716fn name_to_enum_ident(name: String) -> syn::Ident {
717    let mut name = name.strip_prefix("r#").unwrap_or(&name).to_owned();
718    let mut slice = name.as_mut_str();
719    while let Some(i) = slice.find('-') {
720        let (head, tail) = slice.split_at_mut(i);
721        if let Some(c) = head.get_mut(0..1) {
722            c.make_ascii_uppercase();
723        }
724        slice = &mut tail[1..];
725    }
726    if let Some(c) = slice.get_mut(0..1) {
727        c.make_ascii_uppercase();
728    }
729    let enum_member: String = name.split('-').collect();
730    format_ident!("{}", enum_member)
731}
732
733fn expand_properties_enum(props: &[PropDesc]) -> TokenStream2 {
734    if props.is_empty() {
735        quote! {
736            #[derive(Debug, Copy, Clone)]
737            enum DerivedPropertiesEnum {}
738            impl std::convert::TryFrom<usize> for DerivedPropertiesEnum {
739                type Error = usize;
740
741                fn try_from(item: usize) -> ::core::result::Result<Self, <Self as std::convert::TryFrom<usize>>::Error> {
742                    ::core::result::Result::Err(item)
743                }
744            }
745        }
746    } else {
747        let properties: Vec<syn::Ident> = props
748            .iter()
749            .map(|p| {
750                let name: String = p.name.value();
751
752                name_to_enum_ident(name)
753            })
754            .collect();
755        let props = properties.iter();
756        let indices = 0..properties.len();
757        quote! {
758            #[repr(usize)]
759            #[derive(Debug, Copy, Clone)]
760            enum DerivedPropertiesEnum {
761                #(#props,)*
762            }
763            impl std::convert::TryFrom<usize> for DerivedPropertiesEnum {
764                type Error = usize;
765
766                fn try_from(item: usize) -> ::core::result::Result<Self, <Self as std::convert::TryFrom<usize>>::Error> {
767                    match item {
768                        #(#indices => ::core::result::Result::Ok(Self::#properties),)*
769                        _ => ::core::result::Result::Err(item)
770                    }
771                }
772            }
773        }
774    }
775}
776
777pub fn impl_derive_props(input: PropsMacroInput) -> TokenStream {
778    let struct_ident = &input.ident;
779    let crate_ident = crate_ident_new();
780    let wrapper_type = input.wrapper_ty;
781    let fn_properties = expand_properties_fn(&input.props);
782    let fn_property = expand_property_fn(&input.props);
783    let fn_set_property = expand_set_property_fn(&input.props);
784    let getset_properties = expand_impl_getset_properties(&input.props);
785    let connect_prop_notify = expand_impl_connect_prop_notify(&input.props);
786    let notify_prop = expand_impl_notify_prop(&wrapper_type, &input.props);
787    let properties_enum = expand_properties_enum(&input.props);
788
789    let rust_interface = if let Some(ext_trait) = input.ext_trait {
790        let trait_ident = if let Some(ext_trait) = ext_trait {
791            ext_trait
792        } else {
793            format_ident!(
794                "{}PropertiesExt",
795                wrapper_type.segments.last().unwrap().ident
796            )
797        };
798        let fns_without_visibility_modifier = getset_properties
799            .into_iter()
800            .chain(connect_prop_notify)
801            .chain(notify_prop)
802            .map(|mut item| {
803                item.vis = syn::Visibility::Inherited;
804                item
805            });
806        quote! {
807            pub trait #trait_ident: #crate_ident::prelude::IsA<#wrapper_type> {
808                #(#fns_without_visibility_modifier)*
809            }
810            impl<T: #crate_ident::prelude::IsA<#wrapper_type>> #trait_ident for T {}
811        }
812    } else {
813        quote! {
814            #[allow(dead_code)]
815            impl #wrapper_type {
816                #(#getset_properties)*
817                #(#connect_prop_notify)*
818                #(#notify_prop)*
819            }
820        }
821    };
822
823    let expanded = quote! {
824        #properties_enum
825
826        impl #crate_ident::subclass::object::DerivedObjectProperties for #struct_ident {
827            #fn_properties
828            #fn_property
829            #fn_set_property
830        }
831
832        #rust_interface
833    };
834    proc_macro::TokenStream::from(expanded)
835}