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, 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: 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: Punctuated = syn::punctuated::Punctuated::<PropAttr, Token![,]>::parse_terminated(input)?;
221 let this: ReceivedAttrs = attrs.into_iter().fold(Self::default(), |mut this: ReceivedAttrs, attr: PropAttr| {
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: Option) => self.get = Some(some_fn.into()),
235 PropAttr::Set(some_fn: Option) => self.set = Some(some_fn.into()),
236 PropAttr::Name(lit: LitStr) => self.name = Some(lit),
237 PropAttr::OverrideClass(ty: Type) => self.override_class = Some(ty),
238 PropAttr::OverrideInterface(ty: Type) => self.override_interface = Some(ty),
239 PropAttr::Type(ty: Type) => self.ty = Some(ty),
240 PropAttr::Member(member: Ident) => self.member = Some(member),
241 PropAttr::Builder(required_params: Punctuated, optionals: TokenStream) => {
242 self.builder = Some((required_params, optionals))
243 }
244 PropAttr::BuilderField((ident: Ident, expr: Option)) => {
245 self.builder_fields.insert(k:ident, v: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 override_class: Option<syn::Type>,
259 override_interface: Option<syn::Type>,
260 nullable: bool,
261 get: Option<MaybeCustomFn>,
262 set: Option<MaybeCustomFn>,
263 member: Option<syn::Ident>,
264 builder: Option<(Punctuated<syn::Expr, Token![,]>, TokenStream2)>,
265 builder_fields: HashMap<syn::Ident, Option<syn::Expr>>,
266 is_construct_only: bool,
267}
268
269impl PropDesc {
270 fn new(
271 attrs_span: proc_macro2::Span,
272 field_ident: syn::Ident,
273 field_ty: syn::Type,
274 attrs: ReceivedAttrs,
275 ) -> syn::Result<Self> {
276 let ReceivedAttrs {
277 nullable,
278 get,
279 mut set,
280 override_class,
281 override_interface,
282 ty,
283 member,
284 name,
285 builder,
286 builder_fields,
287 } = attrs;
288
289 let is_construct_only = builder_fields.iter().any(|(k, _)| *k == "construct_only");
290 if is_construct_only && set.is_none() {
291 // Insert a default internal setter automatically
292 set = Some(MaybeCustomFn::Default);
293 }
294
295 if get.is_none() && set.is_none() {
296 return Err(syn::Error::new(
297 attrs_span,
298 "No `get` or `set` specified: at least one is required.".to_string(),
299 ));
300 }
301
302 if override_class.is_some() && override_interface.is_some() {
303 return Err(syn::Error::new(
304 attrs_span,
305 "Both `override_class` and `override_interface` specified.".to_string(),
306 ));
307 }
308
309 // Fill needed, but missing, attributes with calculated default values
310 let name = name.unwrap_or_else(|| {
311 syn::LitStr::new(
312 &field_ident.to_string().trim_matches('_').replace('_', "-"),
313 field_ident.span(),
314 )
315 });
316 let ty = ty.unwrap_or_else(|| field_ty.clone());
317
318 // Now that everything is set and safe, return the final proprety description
319 Ok(Self {
320 attrs_span,
321 field_ident,
322 ty,
323 name,
324 override_class,
325 override_interface,
326 nullable,
327 get,
328 set,
329 member,
330 builder,
331 builder_fields,
332 is_construct_only,
333 })
334 }
335}
336
337fn expand_param_spec(prop: &PropDesc) -> TokenStream2 {
338 let crate_ident = crate_ident_new();
339 let PropDesc {
340 ty, name, builder, ..
341 } = prop;
342 let stripped_name = strip_raw_prefix_from_name(name);
343
344 match (&prop.override_class, &prop.override_interface) {
345 (Some(c), None) => {
346 return quote!(#crate_ident::ParamSpecOverride::for_class::<#c>(#stripped_name))
347 }
348 (None, Some(i)) => {
349 return quote!(#crate_ident::ParamSpecOverride::for_interface::<#i>(#stripped_name))
350 }
351 (Some(_), Some(_)) => {
352 unreachable!("Both `override_class` and `override_interface` specified")
353 }
354 (None, None) => (),
355 };
356
357 let rw_flags = match (&prop.get, &prop.set) {
358 (Some(_), Some(_)) => quote!(.readwrite()),
359 (Some(_), None) => quote!(.read_only()),
360 (None, Some(_)) => quote!(.write_only()),
361 (None, None) => unreachable!("No `get` or `set` specified"),
362 };
363
364 let builder_call = builder
365 .as_ref()
366 .cloned()
367 .map(|(mut required_params, chained_methods)| {
368 let name_expr = syn::ExprLit {
369 attrs: vec![],
370 lit: syn::Lit::Str(stripped_name.to_owned()),
371 };
372 required_params.insert(0, name_expr.into());
373 let required_params = required_params.iter();
374
375 quote!((#(#required_params,)*)#chained_methods)
376 })
377 .unwrap_or(quote!((#stripped_name)));
378
379 let builder_fields = prop.builder_fields.iter().map(|(k, v)| quote!(.#k(#v)));
380
381 let span = prop.attrs_span;
382 quote_spanned! {span=>
383 <<#ty as #crate_ident::Property>::Value as #crate_ident::HasParamSpec>
384 ::param_spec_builder() #builder_call
385 #rw_flags
386 #(#builder_fields)*
387 .build()
388 }
389}
390
391fn expand_properties_fn(props: &[PropDesc]) -> TokenStream2 {
392 let n_props: usize = props.len();
393 let crate_ident: TokenStream = crate_ident_new();
394 let param_specs: impl Iterator = props.iter().map(expand_param_spec);
395 quote!(
396 fn derived_properties() -> &'static [#crate_ident::ParamSpec] {
397 use #crate_ident::ParamSpecBuilderExt;
398 use #crate_ident::once_cell::sync::Lazy;
399 static PROPERTIES: Lazy<[#crate_ident::ParamSpec; #n_props]> = Lazy::new(|| [
400 #(#param_specs,)*
401 ]);
402 PROPERTIES.as_ref()
403 }
404 )
405}
406
407fn expand_property_fn(props: &[PropDesc]) -> TokenStream2 {
408 let crate_ident = crate_ident_new();
409 let match_branch_get = props.iter().flat_map(|p| {
410 let PropDesc {
411 name,
412 field_ident,
413 member,
414 get,
415 ty,
416 ..
417 } = p;
418
419 let enum_ident = name_to_enum_ident(name.value());
420 let span = p.attrs_span;
421 get.as_ref().map(|get| {
422 let body = match (member, get) {
423 (_, MaybeCustomFn::Custom(expr)) => quote!(
424 DerivedPropertiesEnum::#enum_ident => {
425 let value: <#ty as #crate_ident::Property>::Value = (#expr)(&self);
426 ::std::convert::From::from(value)
427 }
428 ),
429 (None, MaybeCustomFn::Default) => quote!(
430 DerivedPropertiesEnum::#enum_ident =>
431 #crate_ident::PropertyGet::get(&self.#field_ident, |v| ::std::convert::From::from(v))
432
433 ),
434 (Some(member), MaybeCustomFn::Default) => quote!(
435 DerivedPropertiesEnum::#enum_ident =>
436 #crate_ident::PropertyGet::get(&self.#field_ident, |v| ::std::convert::From::from(&v.#member))
437
438 ),
439 };
440 quote_spanned!(span=> #body)
441 })
442 });
443 quote!(
444 fn derived_property(
445 &self,
446 id: usize,
447 pspec: &#crate_ident::ParamSpec
448 ) -> #crate_ident::Value {
449 let prop: DerivedPropertiesEnum = std::convert::TryFrom::try_from(id-1)
450 .unwrap_or_else(|_| panic!("property not defined {}", pspec.name()));
451 match prop {
452 #(#match_branch_get,)*
453 _ => panic!("missing getter for property {}", pspec.name()),
454 }
455 }
456 )
457}
458
459fn expand_set_property_fn(props: &[PropDesc]) -> TokenStream2 {
460 let crate_ident = crate_ident_new();
461 let match_branch_set = props.iter().flat_map(|p| {
462 let PropDesc {
463 name,
464 field_ident,
465 member,
466 set,
467 ty,
468 ..
469 } = p;
470 let stripped_name = strip_raw_prefix_from_name(name);
471 let crate_ident = crate_ident_new();
472 let enum_ident = name_to_enum_ident(name.value());
473 let span = p.attrs_span;
474 let expect = quote!(.unwrap_or_else(
475 |err| panic!(
476 "Invalid conversion from `glib::value::Value` to `{}` inside setter for property `{}`: {:?}",
477 ::std::any::type_name::<<#ty as #crate_ident::Property>::Value>(), #stripped_name, err
478 )
479 ));
480 set.as_ref().map(|set| {
481 let body = match (member, set) {
482 (_, MaybeCustomFn::Custom(expr)) => quote!(
483 DerivedPropertiesEnum::#enum_ident => {
484 (#expr)(&self, #crate_ident::Value::get(value)#expect);
485 }
486 ),
487 (None, MaybeCustomFn::Default) => quote!(
488 DerivedPropertiesEnum::#enum_ident => {
489 #crate_ident::PropertySet::set(
490 &self.#field_ident,
491 #crate_ident::Value::get(value)#expect
492 );
493 }
494 ),
495 (Some(member), MaybeCustomFn::Default) => quote!(
496 DerivedPropertiesEnum::#enum_ident => {
497 #crate_ident::PropertySetNested::set_nested(
498 &self.#field_ident,
499 move |v| v.#member = #crate_ident::Value::get(value)#expect
500 );
501 }
502 ),
503 };
504 quote_spanned!(span=> #body)
505 })
506 });
507 quote!(
508 fn derived_set_property(&self,
509 id: usize,
510 value: &#crate_ident::Value,
511 pspec: &#crate_ident::ParamSpec
512 ){
513 let prop: DerivedPropertiesEnum = std::convert::TryFrom::try_from(id-1)
514 .unwrap_or_else(|_| panic!("property not defined {}", pspec.name()));
515 match prop {
516 #(#match_branch_set,)*
517 _ => panic!("missing setter for property {}", pspec.name()),
518 }
519 }
520 )
521}
522
523fn parse_fields(fields: syn::Fields) -> syn::Result<Vec<PropDesc>> {
524 fieldsimpl Iterator>
525 .into_iter()
526 .flat_map(|field: Field| {
527 let syn::Field {
528 ident: Option, attrs: Vec, ty: Type, ..
529 } = field;
530 attrsimpl Iterator
531 .into_iter()
532 .filter(|a: &Attribute| a.path().is_ident("property"))
533 .map(move |prop_attrs: Attribute| {
534 let span: Span = prop_attrs.span();
535 PropDesc::new(
536 attrs_span:span,
537 field_ident:ident.as_ref().unwrap().clone(),
538 field_ty:ty.clone(),
539 attrs:prop_attrs.parse_args()?,
540 )
541 })
542 })
543 .collect::<syn::Result<_>>()
544}
545
546/// Converts a glib property name to a correct rust ident
547fn name_to_ident(name: &syn::LitStr) -> syn::Ident {
548 format_ident!("{}", name.value().replace('-', "_"))
549}
550
551/// Strips out raw identifier prefix (`r#`) from literal string items
552fn strip_raw_prefix_from_name(name: &LitStr) -> LitStr {
553 LitStr::new(
554 value:name.value().strip_prefix("r#").unwrap_or(&name.value()),
555 name.span(),
556 )
557}
558
559fn expand_impl_getset_properties(props: &[PropDesc]) -> Vec<syn::ImplItemFn> {
560 let crate_ident = crate_ident_new();
561 let defs = props.iter().map(|p| {
562 let name = &p.name;
563 let stripped_name = strip_raw_prefix_from_name(name);
564 let ident = name_to_ident(name);
565 let ty = &p.ty;
566
567 let getter = p.get.is_some().then(|| {
568 let span = p.attrs_span;
569 parse_quote_spanned!(span=>
570 #[must_use]
571 pub fn #ident(&self) -> <#ty as #crate_ident::Property>::Value {
572 self.property::<<#ty as #crate_ident::Property>::Value>(#stripped_name)
573 })
574 });
575
576 let setter = (p.set.is_some() && !p.is_construct_only).then(|| {
577 let ident = format_ident!("set_{}", ident);
578 let target_ty = quote!(<<#ty as #crate_ident::Property>::Value as #crate_ident::HasParamSpec>::SetValue);
579 let set_ty = if p.nullable {
580 quote!(::core::option::Option<impl std::borrow::Borrow<#target_ty>>)
581 } else {
582 quote!(impl std::borrow::Borrow<#target_ty>)
583 };
584 let upcasted_borrowed_value = if p.nullable {
585 quote!(
586 value.as_ref().map(|v| std::borrow::Borrow::borrow(v))
587 )
588 } else {
589 quote!(
590 std::borrow::Borrow::borrow(&value)
591 )
592 };
593 let span = p.attrs_span;
594 parse_quote_spanned!(span=> pub fn #ident<'a>(&self, value: #set_ty) {
595 self.set_property_from_value(#stripped_name, &::std::convert::From::from(#upcasted_borrowed_value))
596 })
597 });
598 [getter, setter]
599 });
600 defs.flatten() // flattens []
601 .flatten() // removes None
602 .collect::<Vec<_>>()
603}
604
605fn expand_impl_connect_prop_notify(props: &[PropDesc]) -> Vec<syn::ImplItemFn> {
606 let crate_ident: TokenStream = crate_ident_new();
607 let connection_fns: impl Iterator = props.iter().map(|p: &PropDesc| -> syn::ImplItemFn {
608 let name: &LitStr = &p.name;
609 let stripped_name: LitStr = strip_raw_prefix_from_name(name);
610 let fn_ident: Ident = format_ident!("connect_{}_notify", name_to_ident(name));
611 let span: Span = p.attrs_span;
612 parse_quote_spanned!(span=> pub fn #fn_ident<F: Fn(&Self) + 'static>(&self, f: F) -> #crate_ident::SignalHandlerId {
613 self.connect_notify_local(::core::option::Option::Some(#stripped_name), move |this, _| {
614 f(this)
615 })
616 })
617 });
618 connection_fns.collect::<Vec<_>>()
619}
620
621fn expand_impl_notify_prop(props: &[PropDesc]) -> Vec<syn::ImplItemFn> {
622 let crate_ident: TokenStream = crate_ident_new();
623 let emit_fns: impl Iterator = props.iter().map(|p: &PropDesc| -> syn::ImplItemFn {
624 let name: LitStr = strip_raw_prefix_from_name(&p.name);
625 let fn_ident: Ident = format_ident!("notify_{}", name_to_ident(&name));
626 let span: Span = p.attrs_span;
627 let enum_ident: Ident = name_to_enum_ident(name:name.value());
628 parse_quote_spanned!(span=> pub fn #fn_ident(&self) {
629 self.notify_by_pspec(
630 &<<Self as #crate_ident::object::ObjectSubclassIs>::Subclass
631 as #crate_ident::subclass::object::DerivedObjectProperties>::derived_properties()
632 [DerivedPropertiesEnum::#enum_ident as usize]
633 );
634 })
635 });
636 emit_fns.collect::<Vec<_>>()
637}
638
639fn name_to_enum_ident(name: String) -> syn::Ident {
640 let mut name: String = name.strip_prefix("r#").unwrap_or(&name).to_owned();
641 let mut slice: &mut str = name.as_mut_str();
642 while let Some(i: usize) = slice.find('-') {
643 let (head: &mut str, tail: &mut str) = slice.split_at_mut(mid:i);
644 if let Some(c: &mut str) = head.get_mut(0..1) {
645 c.make_ascii_uppercase();
646 }
647 slice = &mut tail[1..];
648 }
649 if let Some(c: &mut str) = slice.get_mut(0..1) {
650 c.make_ascii_uppercase();
651 }
652 let enum_member: String = name.split('-').collect();
653 format_ident!("{}", enum_member)
654}
655
656fn expand_properties_enum(props: &[PropDesc]) -> TokenStream2 {
657 let properties: Vec<syn::Ident> = props
658 .iter()
659 .map(|p| {
660 let name: String = p.name.value();
661
662 name_to_enum_ident(name)
663 })
664 .collect();
665 let props = properties.iter();
666 let indices = 0..properties.len();
667 quote! {
668 #[repr(usize)]
669 #[derive(Debug, Copy, Clone)]
670 enum DerivedPropertiesEnum {
671 #(#props,)*
672 }
673 impl std::convert::TryFrom<usize> for DerivedPropertiesEnum {
674 type Error = usize;
675
676 fn try_from(item: usize) -> ::core::result::Result<Self, <Self as std::convert::TryFrom<usize>>::Error> {
677 match item {
678 #(#indices => ::core::result::Result::Ok(Self::#properties),)*
679 _ => ::core::result::Result::Err(item)
680 }
681 }
682 }
683 }
684}
685
686pub fn impl_derive_props(input: PropsMacroInput) -> TokenStream {
687 let struct_ident = &input.ident;
688 let crate_ident = crate_ident_new();
689 let wrapper_type = input.wrapper_ty;
690 let fn_properties = expand_properties_fn(&input.props);
691 let fn_property = expand_property_fn(&input.props);
692 let fn_set_property = expand_set_property_fn(&input.props);
693 let getset_properties = expand_impl_getset_properties(&input.props);
694 let connect_prop_notify = expand_impl_connect_prop_notify(&input.props);
695 let notify_prop = expand_impl_notify_prop(&input.props);
696 let properties_enum = expand_properties_enum(&input.props);
697
698 let rust_interface = if let Some(ext_trait) = input.ext_trait {
699 let trait_ident = if let Some(ext_trait) = ext_trait {
700 ext_trait
701 } else {
702 format_ident!(
703 "{}PropertiesExt",
704 wrapper_type.segments.last().unwrap().ident
705 )
706 };
707 let signatures = getset_properties
708 .iter()
709 .chain(connect_prop_notify.iter())
710 .chain(notify_prop.iter())
711 .map(|item| &item.sig);
712 let trait_def = quote! {
713 pub trait #trait_ident {
714 #(#signatures;)*
715 }
716 };
717 let impls = getset_properties
718 .into_iter()
719 .chain(connect_prop_notify)
720 .chain(notify_prop)
721 .map(|mut item| {
722 item.vis = syn::Visibility::Inherited;
723 item
724 });
725 quote! {
726 #trait_def
727 impl #trait_ident for #wrapper_type {
728 #(#impls)*
729 }
730 }
731 } else {
732 quote! {
733 #[allow(dead_code)]
734 impl #wrapper_type {
735 #(#getset_properties)*
736 #(#connect_prop_notify)*
737 #(#notify_prop)*
738 }
739 }
740 };
741
742 let expanded = quote! {
743 use #crate_ident::{PropertyGet, PropertySet, ToValue};
744
745 #properties_enum
746
747 impl #crate_ident::subclass::object::DerivedObjectProperties for #struct_ident {
748 #fn_properties
749 #fn_property
750 #fn_set_property
751 }
752
753 #rust_interface
754 };
755 proc_macro::TokenStream::from(expanded)
756}
757