1use 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 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(Punctuated<syn::Expr, Token![,]>, TokenStream2),
116
117 Nullable,
119
120 Get(Option<syn::Expr>),
122 Set(Option<syn::Expr>),
123
124 OverrideClass(syn::Type),
126 OverrideInterface(syn::Type),
127
128 Type(syn::Type),
130
131 BuilderField((syn::Ident, Option<syn::Expr>)),
135
136 Member(syn::Ident),
138
139 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 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 "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 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
251struct 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 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 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 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 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
559fn name_to_ident(name: &syn::LitStr) -> syn::Ident {
561 format_ident!("{}", name.value().replace('-', "_"))
562}
563
564fn 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
572fn 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 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 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 !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() .flatten() .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}