From 0b0e66141f6f1389832bf3949519d0b9b08f0090 Mon Sep 17 00:00:00 2001 From: ranfdev Date: Sun, 30 Jul 2023 23:42:43 +0200 Subject: [PATCH] Add support for ext_trait in properties macro --- glib-macros/src/lib.rs | 11 ++- glib-macros/src/properties.rs | 128 ++++++++++++++++++++++---------- glib-macros/tests/properties.rs | 50 +++++++++++++ 3 files changed, 146 insertions(+), 43 deletions(-) diff --git a/glib-macros/src/lib.rs b/glib-macros/src/lib.rs index 353127263596..14b6944b5fa6 100644 --- a/glib-macros/src/lib.rs +++ b/glib-macros/src/lib.rs @@ -873,7 +873,7 @@ pub fn cstr_bytes(item: TokenStream) -> TokenStream { /// | `set [= expr]` | Specify that the property is writable and use `PropertySet::set` [or optionally set a custom internal setter] | | `#[property(set)]`, `#[property(set = set_prop)]`, or `[property(set = \|_, val\| {})]` | /// | `override_class = expr` | The type of class of which to override the property from | | `#[property(override_class = SomeClass)]` | /// | `override_interface = expr` | The type of interface of which to override the property from | | `#[property(override_interface = SomeInterface)]` | -/// | `nullable` | Whether to use `Option` in the wrapper's generated setter | | `#[property(nullable)]` | +/// | `nullable` | Whether to use `Option` in the generated setter method | | `#[property(nullable)]` | /// | `member = ident` | Field of the nested type where property is retrieved and set | | `#[property(member = author)]` | /// | `construct_only` | Specify that the property is construct only. This will not generate a public setter and only allow the property to be set during object construction. The use of a custom internal setter is supported. | | `#[property(get, construct_only)]` or `#[property(get, set = set_prop, construct_only)]` | /// | `builder()[.ident]*` | Used to input required params or add optional Param Spec builder fields | | `#[property(builder(SomeEnum::default()))]`, `#[builder().default_value(1).minimum(0).maximum(5)]`, etc. | @@ -885,15 +885,18 @@ pub fn cstr_bytes(item: TokenStream) -> TokenStream { /// You might hit a roadblock when declaring properties with this macro because you want to use a name that happens to be a Rust keyword. This may happen with names like `loop`, which is a pretty common name when creating things like animation handlers. /// To use those names, you can make use of the raw identifier feature of Rust. Simply prefix the identifier name with `r#` in the struct declaration. Internally, those `r#`s are stripped so you can use its expected name in [`ObjectExt::property`] or within GtkBuilder template files. /// -/// # Generated wrapper methods +/// # Generated methods /// The following methods are generated on the wrapper type specified on `#[properties(wrapper_type = ...)]`: /// * `$property()`, when the property is readable /// * `set_$property()`, when the property is writable and not construct-only /// * `connect_$property_notify()` /// * `notify_$property()` +/// +/// You can choose to move the method definitions to a trait by using `#[properties(wrapper_type = MyType, ext_trait = MyTypeExt)]`. +/// /// -/// Notice: You can't reimplement the generated methods on the wrapper type, -/// but you can change their behavior using a custom internal getter/setter. +/// Notice: You can't reimplement the generated methods on the wrapper type, unless you move them to a trait. +/// You can change the behavior of the generated getter/setter methods by using a custom internal getter/setter. /// /// # Internal getters and setters /// By default, they are generated for you. However, you can use a custom getter/setter diff --git a/glib-macros/src/properties.rs b/glib-macros/src/properties.rs index 3d63464db4a7..2f52b008e11f 100644 --- a/glib-macros/src/properties.rs +++ b/glib-macros/src/properties.rs @@ -7,31 +7,46 @@ use quote::format_ident; use quote::{quote, quote_spanned}; use std::collections::HashMap; use syn::ext::IdentExt; -use syn::parenthesized; use syn::parse::Parse; use syn::punctuated::Punctuated; use syn::spanned::Spanned; -use syn::LitStr; use syn::Token; +use syn::parenthesized; +use syn::{parse_quote_spanned, LitStr}; pub struct PropsMacroInput { wrapper_ty: syn::Path, + ext_trait: Option, ident: syn::Ident, props: Vec, } -pub struct PropertiesAttr { - _wrapper_ty_token: syn::Ident, - _eq: Token![=], +pub struct PropertiesAttrs { wrapper_ty: syn::Path, + ext_trait: Option, } -impl Parse for PropertiesAttr { +impl Parse for PropertiesAttrs { fn parse(input: syn::parse::ParseStream) -> syn::Result { + let mut wrapper_ty = None; + let mut ext_trait = None; + + while !input.is_empty() { + let ident = input.parse::()?; + let _eq = input.parse::()?; + if ident == "wrapper_type" { + wrapper_ty = Some(input.parse::()?); + } else if ident == "ext_trait" { + ext_trait = Some(input.parse::()?); + } + if input.peek(Token![,]) { + input.parse::()?; + } + } + Ok(Self { - _wrapper_ty_token: input.parse()?, - _eq: input.parse()?, - wrapper_ty: input.parse()?, + wrapper_ty: wrapper_ty.ok_or_else(|| syn::Error::new(input.span(), "missing #[properties(wrapper_type = ...)]"))?, + ext_trait, }) } } @@ -39,7 +54,7 @@ impl Parse for PropertiesAttr { impl Parse for PropsMacroInput { fn parse(input: syn::parse::ParseStream) -> syn::Result { let derive_input: syn::DeriveInput = input.parse()?; - let wrapper_ty = derive_input + let attrs = derive_input .attrs .iter() .find(|x| x.path().is_ident("properties")) @@ -49,7 +64,7 @@ impl Parse for PropsMacroInput { "missing #[properties(wrapper_type = ...)]", ) })?; - let wrapper_ty: PropertiesAttr = wrapper_ty.parse_args()?; + let attrs: PropertiesAttrs = attrs.parse_args()?; let props: Vec<_> = match derive_input.data { syn::Data::Struct(struct_data) => parse_fields(struct_data.fields)?, _ => { @@ -60,7 +75,8 @@ impl Parse for PropsMacroInput { } }; Ok(Self { - wrapper_ty: wrapper_ty.wrapper_ty, + wrapper_ty: attrs.wrapper_ty, + ext_trait: attrs.ext_trait, ident: derive_input.ident, props, }) @@ -529,7 +545,8 @@ fn strip_raw_prefix_from_name(name: &LitStr) -> LitStr { ) } -fn expand_wrapper_getset_properties(props: &[PropDesc]) -> TokenStream2 { + +fn expand_impl_getset_properties(props: &[PropDesc]) -> Vec { let crate_ident = crate_ident_new(); let defs = props.iter().map(|p| { let name = &p.name; @@ -538,7 +555,8 @@ fn expand_wrapper_getset_properties(props: &[PropDesc]) -> TokenStream2 { let ty = &p.ty; let getter = p.get.is_some().then(|| { - quote!(pub fn #ident(&self) -> <#ty as #crate_ident::Property>::Value { + let span = p.attrs_span; + parse_quote_spanned!(span=> pub fn #ident(&self) -> <#ty as #crate_ident::Property>::Value { self.property::<<#ty as #crate_ident::Property>::Value>(#stripped_name) }) }); @@ -560,43 +578,44 @@ fn expand_wrapper_getset_properties(props: &[PropDesc]) -> TokenStream2 { std::borrow::Borrow::borrow(&value) ) }; - quote!(pub fn #ident<'a>(&self, value: #set_ty) { + let span = p.attrs_span; + parse_quote_spanned!(span=> pub fn #ident<'a>(&self, value: #set_ty) { self.set_property_from_value(#stripped_name, &::std::convert::From::from(#upcasted_borrowed_value)) }) }); - let span = p.attrs_span; - quote_spanned!(span=> - #getter - #setter - ) + [getter, setter] }); - quote!(#(#defs)*) + defs.flatten() // flattens [] + .flatten() // removes None + .collect::>() } -fn expand_wrapper_connect_prop_notify(props: &[PropDesc]) -> TokenStream2 { + +fn expand_impl_connect_prop_notify(props: &[PropDesc]) -> Vec { let crate_ident = crate_ident_new(); - let connection_fns = props.iter().map(|p| { + let connection_fns = props.iter().map(|p| -> syn::ImplItemFn { let name = &p.name; let stripped_name = strip_raw_prefix_from_name(name); let fn_ident = format_ident!("connect_{}_notify", name_to_ident(name)); let span = p.attrs_span; - quote_spanned!(span=> pub fn #fn_ident(&self, f: F) -> #crate_ident::SignalHandlerId { + parse_quote_spanned!(span=> pub fn #fn_ident(&self, f: F) -> #crate_ident::SignalHandlerId { self.connect_notify_local(::core::option::Option::Some(#stripped_name), move |this, _| { f(this) }) }) }); - quote!(#(#connection_fns)*) + connection_fns.collect::>() } -fn expand_wrapper_notify_prop(props: &[PropDesc]) -> TokenStream2 { + +fn expand_impl_notify_prop(props: &[PropDesc]) -> Vec { let crate_ident = crate_ident_new(); - let emit_fns = props.iter().map(|p| { + let emit_fns = props.iter().map(|p| -> syn::ImplItemFn { let name = strip_raw_prefix_from_name(&p.name); let fn_ident = format_ident!("notify_{}", name_to_ident(&name)); let span = p.attrs_span; let enum_ident = name_to_enum_ident(name.value()); - quote_spanned!(span=> pub fn #fn_ident(&self) { + parse_quote_spanned!(span=> pub fn #fn_ident(&self) { self.notify_by_pspec( &<::Subclass as #crate_ident::subclass::object::DerivedObjectProperties>::derived_properties() @@ -604,7 +623,7 @@ fn expand_wrapper_notify_prop(props: &[PropDesc]) -> TokenStream2 { ); }) }); - quote!(#(#emit_fns)*) + emit_fns.collect::>() } fn name_to_enum_ident(name: String) -> syn::Ident { @@ -658,14 +677,51 @@ pub fn impl_derive_props(input: PropsMacroInput) -> TokenStream { let struct_ident = &input.ident; let crate_ident = crate_ident_new(); let wrapper_type = input.wrapper_ty; + let ext_trait = input.ext_trait; let fn_properties = expand_properties_fn(&input.props); let fn_property = expand_property_fn(&input.props); let fn_set_property = expand_set_property_fn(&input.props); - let getset_properties = expand_wrapper_getset_properties(&input.props); - let connect_prop_notify = expand_wrapper_connect_prop_notify(&input.props); - let notify_prop = expand_wrapper_notify_prop(&input.props); + let getset_properties = expand_impl_getset_properties(&input.props); + let connect_prop_notify = expand_impl_connect_prop_notify(&input.props); + let notify_prop = expand_impl_notify_prop(&input.props); let properties_enum = expand_properties_enum(&input.props); + let rust_interface = if let Some(ext_trait) = ext_trait { + let signatures = getset_properties.iter() + .chain(connect_prop_notify.iter()) + .chain(notify_prop.iter()) + .map(|item| { + &item.sig + }); + let trait_def = quote! { + pub trait #ext_trait { + #(#signatures;)* + } + }; + let impls = getset_properties.into_iter() + .chain(connect_prop_notify) + .chain(notify_prop) + .map(|mut item| { + item.vis = syn::Visibility::Inherited; + item + }); + quote! { + #trait_def + impl #ext_trait for #wrapper_type { + #(#impls)* + } + } + } else { + quote! { + #[allow(dead_code)] + impl #wrapper_type { + #(#getset_properties)* + #(#connect_prop_notify)* + #(#notify_prop)* + } + } + }; + let expanded = quote! { use #crate_ident::{PropertyGet, PropertySet, ToValue}; @@ -677,13 +733,7 @@ pub fn impl_derive_props(input: PropsMacroInput) -> TokenStream { #fn_set_property } - #[allow(dead_code)] - impl #wrapper_type { - #getset_properties - #connect_prop_notify - #notify_prop - } - + #rust_interface }; proc_macro::TokenStream::from(expanded) } diff --git a/glib-macros/tests/properties.rs b/glib-macros/tests/properties.rs index 8cecda011f66..f8145a21427d 100644 --- a/glib-macros/tests/properties.rs +++ b/glib-macros/tests/properties.rs @@ -388,6 +388,56 @@ fn props() { ); } +mod ext_trait { + use glib::subclass::object::DerivedObjectProperties; + use glib::ObjectExt; + + use glib::subclass::{prelude::ObjectImpl, types::ObjectSubclass}; + use glib_macros::Properties; + use std::cell::RefCell; + + pub mod imp { + use super::*; + + #[derive(Properties, Default)] + #[properties(wrapper_type = super::Author, ext_trait = AuthorExt)] + pub struct Author { + #[property(get, set)] + firstname: RefCell, + #[property(get, set)] + lastname: RefCell, + } + + #[glib::derived_properties] + impl ObjectImpl for Author {} + + #[glib::object_subclass] + impl ObjectSubclass for Author { + const NAME: &'static str = "Author"; + type Type = super::Author; + } + } + + glib::wrapper! { + pub struct Author(ObjectSubclass); + } + impl Author { + pub fn new() -> Self { + glib::Object::builder().build() + } + } +} + +#[test] +fn ext_trait() { + use ext_trait::imp::AuthorExt; + let author = ext_trait::Author::new(); + AuthorExt::set_firstname(&author, "John"); + AuthorExt::set_lastname(&author, "Doe"); + assert_eq!(AuthorExt::firstname(&author), "John"); + assert_eq!(AuthorExt::lastname(&author), "Doe"); +} + #[cfg(test)] mod kw_names { mod imp {