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