From aa4d0e30189fda6badf7077220092477b6c7a27f Mon Sep 17 00:00:00 2001 From: Jason Francis Date: Tue, 7 Feb 2023 18:43:14 -0500 Subject: [PATCH 1/5] glib-macros: port attribute parsing to use deluxe crate --- glib-macros/Cargo.toml | 1 + glib-macros/src/boxed_derive.rs | 32 ++++--- glib-macros/src/enum_derive.rs | 83 ++++++++-------- glib-macros/src/error_domain_derive.rs | 30 +++--- glib-macros/src/flags_attribute.rs | 109 ++++++++++----------- glib-macros/src/lib.rs | 18 ++-- glib-macros/src/shared_boxed_derive.rs | 35 +++---- glib-macros/src/utils.rs | 106 +-------------------- glib-macros/src/variant_derive.rs | 127 ++++++++++++------------- 9 files changed, 214 insertions(+), 327 deletions(-) diff --git a/glib-macros/Cargo.toml b/glib-macros/Cargo.toml index 8c0738aaa5cc..590b1f03b862 100644 --- a/glib-macros/Cargo.toml +++ b/glib-macros/Cargo.toml @@ -19,6 +19,7 @@ proc-macro2 = "1.0" quote = "1.0" syn = { version = "1.0", features = ["full"], default-features = false } proc-macro-crate = "1.0" +deluxe = "0.4.1" [lib] proc-macro = true diff --git a/glib-macros/src/boxed_derive.rs b/glib-macros/src/boxed_derive.rs index 5af23cadcfdf..b17bd2f5ee7e 100644 --- a/glib-macros/src/boxed_derive.rs +++ b/glib-macros/src/boxed_derive.rs @@ -1,10 +1,9 @@ // Take a look at the license at the top of the repository in the LICENSE file. use proc_macro2::{Ident, TokenStream}; -use proc_macro_error::abort_call_site; use quote::quote; -use crate::utils::{crate_ident_new, find_attribute_meta, find_nested_meta, parse_name}; +use crate::utils::crate_ident_new; fn gen_option_to_ptr() -> TokenStream { quote! { @@ -91,21 +90,22 @@ fn gen_impl_to_value_optional(name: &Ident, crate_ident: &TokenStream) -> TokenS } } -pub fn impl_boxed(input: &syn::DeriveInput) -> TokenStream { - let name = &input.ident; +#[derive(deluxe::ExtractAttributes, Default)] +#[deluxe(attributes(boxed_type))] +struct BoxedType { + name: String, + #[deluxe(default)] + nullable: bool, +} - let gtype_name = match parse_name(input, "boxed_type") { - Ok(name) => name, - Err(e) => abort_call_site!( - "{}: #[derive(glib::Boxed)] requires #[boxed_type(name = \"BoxedTypeName\")]", - e - ), - }; +pub fn impl_boxed(mut input: syn::DeriveInput) -> TokenStream { + let name = &input.ident; - let meta = find_attribute_meta(&input.attrs, "boxed_type") - .unwrap() - .unwrap(); - let nullable = find_nested_meta(&meta, "nullable").is_some(); + let errors = deluxe::Errors::new(); + let BoxedType { + name: gtype_name, + nullable, + } = deluxe::extract_attributes_optional(&mut input.attrs, &errors); let crate_ident = crate_ident_new(); @@ -121,6 +121,8 @@ pub fn impl_boxed(input: &syn::DeriveInput) -> TokenStream { }; quote! { + #errors + impl #crate_ident::subclass::boxed::BoxedType for #name { const NAME: &'static str = #gtype_name; } diff --git a/glib-macros/src/enum_derive.rs b/glib-macros/src/enum_derive.rs index b59b80cbc260..9e36e3299faf 100644 --- a/glib-macros/src/enum_derive.rs +++ b/glib-macros/src/enum_derive.rs @@ -6,9 +6,20 @@ use proc_macro_error::abort_call_site; use quote::{quote, quote_spanned}; use syn::{punctuated::Punctuated, spanned::Spanned, token::Comma, Data, Ident, Variant}; -use crate::utils::{ - crate_ident_new, gen_enum_from_glib, parse_item_attributes, parse_name, ItemAttribute, -}; +use crate::utils::{crate_ident_new, gen_enum_from_glib}; + +#[derive(deluxe::ExtractAttributes, Default)] +#[deluxe(attributes(enum_type))] +struct EnumType { + name: String, +} + +#[derive(deluxe::ExtractAttributes, Default)] +#[deluxe(attributes(enum_value), default)] +struct EnumValue { + name: Option, + nick: Option, +} // Generate glib::gobject_ffi::GEnumValue structs mapping the enum such as: // glib::gobject_ffi::GEnumValue { @@ -18,35 +29,24 @@ use crate::utils::{ // }, fn gen_enum_values( enum_name: &Ident, - enum_variants: &Punctuated, + enum_variants: &mut Punctuated, + errors: &deluxe::Errors, ) -> (TokenStream, usize) { let crate_ident = crate_ident_new(); // start at one as GEnumValue array is null-terminated let mut n = 1; - let recurse = enum_variants.iter().map(|v| { - let name = &v.ident; - let mut value_name = name.to_string().to_upper_camel_case(); - let mut value_nick = name.to_string().to_kebab_case(); - - let attrs = parse_item_attributes("enum_value", &v.attrs); - let attrs = match attrs { - Ok(attrs) => attrs, - Err(e) => abort_call_site!( - "{}: derive(glib::Enum) enum supports only the following optional attributes: #[enum_value(name = \"The Cat\", nick = \"chat\")]", - e - ), - }; - - attrs.into_iter().for_each(|attr| - match attr { - ItemAttribute::Name(n) => value_name = n, - ItemAttribute::Nick(n) => value_nick = n, - } - ); + let recurse = enum_variants.iter_mut().map(|v| { + let EnumValue { + name: value_name, + nick: value_nick, + } = deluxe::extract_attributes_optional(v, errors); - let value_name = format!("{value_name}\0"); - let value_nick = format!("{value_nick}\0"); + let name = &v.ident; + let mut value_name = value_name.unwrap_or_else(|| name.to_string().to_upper_camel_case()); + let mut value_nick = value_nick.unwrap_or_else(|| name.to_string().to_kebab_case()); + value_name.push('\0'); + value_nick.push('\0'); n += 1; quote_spanned! {v.span()=> @@ -65,28 +65,27 @@ fn gen_enum_values( ) } -pub fn impl_enum(input: &syn::DeriveInput) -> TokenStream { - let name = &input.ident; - - let enum_variants = match input.data { - Data::Enum(ref e) => &e.variants, +pub fn impl_enum(mut input: syn::DeriveInput) -> TokenStream { + let enum_variants = match &mut input.data { + Data::Enum(e) => &mut e.variants, _ => abort_call_site!("#[derive(glib::Enum)] only supports enums"), }; - let gtype_name = match parse_name(input, "enum_type") { - Ok(name) => name, - Err(e) => abort_call_site!( - "{}: #[derive(glib::Enum)] requires #[enum_type(name = \"EnumTypeName\")]", - e - ), - }; + let errors = deluxe::Errors::new(); + let EnumType { + name: mut gtype_name, + } = deluxe::extract_attributes_optional(&mut input.attrs, &errors); + gtype_name.push('\0'); + let name = &input.ident; let from_glib = gen_enum_from_glib(name, enum_variants); - let (enum_values, nb_enum_values) = gen_enum_values(name, enum_variants); + let (enum_values, nb_enum_values) = gen_enum_values(name, enum_variants, &errors); let crate_ident = crate_ident_new(); quote! { + #errors + impl #crate_ident::translate::IntoGlib for #name { type GlibType = i32; @@ -175,9 +174,11 @@ pub fn impl_enum(input: &syn::DeriveInput) -> TokenStream { }, ]; - let name = ::std::ffi::CString::new(#gtype_name).expect("CString::new failed"); unsafe { - let type_ = #crate_ident::gobject_ffi::g_enum_register_static(name.as_ptr(), VALUES.as_ptr()); + let type_ = #crate_ident::gobject_ffi::g_enum_register_static( + #gtype_name.as_ptr() as *const _, + VALUES.as_ptr(), + ); let type_: #crate_ident::Type = #crate_ident::translate::from_glib(type_); assert!(type_.is_valid()); TYPE = type_; diff --git a/glib-macros/src/error_domain_derive.rs b/glib-macros/src/error_domain_derive.rs index 3fbcdabb2fa4..e728410b3e30 100644 --- a/glib-macros/src/error_domain_derive.rs +++ b/glib-macros/src/error_domain_derive.rs @@ -5,29 +5,35 @@ use proc_macro_error::abort_call_site; use quote::quote; use syn::Data; -use crate::utils::{crate_ident_new, gen_enum_from_glib, parse_name}; +use crate::utils::{crate_ident_new, gen_enum_from_glib}; -pub fn impl_error_domain(input: &syn::DeriveInput) -> TokenStream { +#[derive(deluxe::ExtractAttributes, Default)] +#[deluxe(attributes(error_domain))] +struct ErrorDomainType { + name: String, +} + +pub fn impl_error_domain(mut input: syn::DeriveInput) -> TokenStream { let name = &input.ident; - let enum_variants = match input.data { - Data::Enum(ref e) => &e.variants, + let enum_variants = match &mut input.data { + Data::Enum(e) => &mut e.variants, _ => abort_call_site!("#[derive(glib::ErrorDomain)] only supports enums"), }; - let domain_name = match parse_name(input, "error_domain") { - Ok(name) => name, - Err(e) => abort_call_site!( - "{}: #[derive(glib::ErrorDomain)] requires #[error_domain(name = \"domain-name\")]", - e - ), - }; + let errors = deluxe::Errors::new(); + let ErrorDomainType { + name: mut domain_name, + } = deluxe::extract_attributes_optional(&mut input.attrs, &errors); + domain_name.push('\0'); let crate_ident = crate_ident_new(); let from_glib = gen_enum_from_glib(name, enum_variants); quote! { + #errors + impl #crate_ident::error::ErrorDomain for #name { #[inline] fn domain() -> #crate_ident::Quark { @@ -35,7 +41,7 @@ pub fn impl_error_domain(input: &syn::DeriveInput) -> TokenStream { static QUARK: #crate_ident::once_cell::sync::Lazy<#crate_ident::Quark> = #crate_ident::once_cell::sync::Lazy::new(|| unsafe { - from_glib(#crate_ident::ffi::g_quark_from_static_string(concat!(#domain_name, "\0") as *const str as *const _)) + from_glib(#crate_ident::ffi::g_quark_from_static_string(#domain_name.as_ptr() as *const _)) }); *QUARK } diff --git a/glib-macros/src/flags_attribute.rs b/glib-macros/src/flags_attribute.rs index 20bc2f40328e..a1b6abaa1c39 100644 --- a/glib-macros/src/flags_attribute.rs +++ b/glib-macros/src/flags_attribute.rs @@ -2,26 +2,22 @@ use heck::{ToKebabCase, ToUpperCamelCase}; use proc_macro2::TokenStream; -use proc_macro_error::abort_call_site; use quote::{quote, quote_spanned}; -use syn::{ - punctuated::Punctuated, spanned::Spanned, token::Comma, Attribute, Data, DeriveInput, Ident, - NestedMeta, Variant, Visibility, -}; - -use crate::utils::{ - crate_ident_new, find_attribute_meta, find_nested_meta, parse_item_attributes, - parse_name_attribute, ItemAttribute, -}; - -// Flag is not registered if it has the #[flags_value(skip)] meta -fn attribute_has_skip(attrs: &[Attribute]) -> bool { - let meta = find_attribute_meta(attrs, "flags_value").unwrap(); - - match meta { - None => false, - Some(meta) => find_nested_meta(&meta, "skip").is_some(), - } +use syn::{punctuated::Punctuated, spanned::Spanned, token::Comma, Ident, Variant, Visibility}; + +use crate::utils::crate_ident_new; + +#[derive(deluxe::ParseMetaItem, Default)] +struct FlagsType { + name: String, +} + +#[derive(deluxe::ExtractAttributes, Default)] +#[deluxe(attributes(flags_value), default)] +struct FlagsValue { + skip: bool, + name: Option, + nick: Option, } // Generate glib::gobject_ffi::GFlagsValue structs mapping the enum such as: @@ -32,44 +28,38 @@ fn attribute_has_skip(attrs: &[Attribute]) -> bool { // }, fn gen_flags_values( enum_name: &Ident, - enum_variants: &Punctuated, + enum_variants: &mut Punctuated, + errors: &deluxe::Errors, ) -> (TokenStream, usize) { let crate_ident = crate_ident_new(); // start at one as GFlagsValue array is null-terminated let mut n = 1; - let recurse = enum_variants.iter().filter(|v| { !attribute_has_skip(&v.attrs) } ).map(|v| { - let name = &v.ident; - let mut value_name = name.to_string().to_upper_camel_case(); - let mut value_nick = name.to_string().to_kebab_case(); - - let attrs = parse_item_attributes("flags_value", &v.attrs); - let attrs = match attrs { - Ok(attrs) => attrs, - Err(e) => abort_call_site!( - "{}: #[glib::flags] supports only the following optional attributes: #[flags_value(name = \"The Name\", nick = \"the-nick\")] or #[flags_value(skip)]", - e - ), - }; - - attrs.into_iter().for_each(|attr| - match attr { - ItemAttribute::Name(n) => value_name = n, - ItemAttribute::Nick(n) => value_nick = n, - } - ); + let recurse = enum_variants.iter_mut().filter_map(|v| { + let FlagsValue { + skip, + name: value_name, + nick: value_nick, + } = deluxe::extract_attributes_optional(v, errors); + + if skip { + return None; + } - let value_name = format!("{value_name}\0"); - let value_nick = format!("{value_nick}\0"); + let name = &v.ident; + let mut value_name = value_name.unwrap_or_else(|| name.to_string().to_upper_camel_case()); + let mut value_nick = value_nick.unwrap_or_else(|| name.to_string().to_kebab_case()); + value_name.push('\0'); + value_nick.push('\0'); n += 1; - quote_spanned! {v.span()=> + Some(quote_spanned! {v.span()=> #crate_ident::gobject_ffi::GFlagsValue { value: #enum_name::#name.bits(), value_name: #value_name as *const _ as *const _, value_nick: #value_nick as *const _ as *const _, }, - } + }) }); ( quote! { @@ -104,29 +94,24 @@ fn gen_bitflags( } } -pub fn impl_flags(attrs: &NestedMeta, input: &DeriveInput) -> TokenStream { - let gtype_name = match parse_name_attribute(attrs) { - Ok(name) => name, - Err(e) => abort_call_site!( - "{}: [glib::flags] requires #[glib::flags(name = \"FlagsTypeName\")]", - e - ), - }; +pub fn impl_flags(attrs: TokenStream, mut input: syn::ItemEnum) -> TokenStream { + let errors = deluxe::Errors::new(); + let FlagsType { + name: mut gtype_name, + } = deluxe::parse2_optional(attrs, &errors); + gtype_name.push('\0'); let name = &input.ident; let visibility = &input.vis; - let enum_variants = match input.data { - Data::Enum(ref e) => &e.variants, - _ => abort_call_site!("#[glib::flags] only supports enums"), - }; - let crate_ident = crate_ident_new(); - let bitflags = gen_bitflags(name, visibility, enum_variants, &crate_ident); - let (flags_values, nb_flags_values) = gen_flags_values(name, enum_variants); + let bitflags = gen_bitflags(name, visibility, &input.variants, &crate_ident); + let (flags_values, nb_flags_values) = gen_flags_values(name, &mut input.variants, &errors); quote! { + #errors + #bitflags impl #crate_ident::translate::IntoGlib for #name { @@ -212,9 +197,11 @@ pub fn impl_flags(attrs: &NestedMeta, input: &DeriveInput) -> TokenStream { }, ]; - let name = ::std::ffi::CString::new(#gtype_name).expect("CString::new failed"); unsafe { - let type_ = #crate_ident::gobject_ffi::g_flags_register_static(name.as_ptr(), VALUES.as_ptr()); + let type_ = #crate_ident::gobject_ffi::g_flags_register_static( + #gtype_name.as_ptr() as *const _, + VALUES.as_ptr(), + ); let type_: #crate_ident::Type = #crate_ident::translate::from_glib(type_); assert!(type_.is_valid()); TYPE = type_; diff --git a/glib-macros/src/lib.rs b/glib-macros/src/lib.rs index ccd32b41a26a..efcb47802fd6 100644 --- a/glib-macros/src/lib.rs +++ b/glib-macros/src/lib.rs @@ -17,7 +17,7 @@ mod utils; use proc_macro::TokenStream; use proc_macro_error::proc_macro_error; -use syn::{parse_macro_input, DeriveInput, NestedMeta}; +use syn::{parse_macro_input, DeriveInput}; /// Macro for passing variables as strong or weak references into a closure. /// @@ -460,7 +460,7 @@ pub fn closure_local(item: TokenStream) -> TokenStream { #[proc_macro_error] pub fn enum_derive(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); - let gen = enum_derive::impl_enum(&input); + let gen = enum_derive::impl_enum(input); gen.into() } @@ -497,9 +497,8 @@ pub fn enum_derive(input: TokenStream) -> TokenStream { #[proc_macro_attribute] #[proc_macro_error] pub fn flags(attr: TokenStream, item: TokenStream) -> TokenStream { - let attr_meta = parse_macro_input!(attr as NestedMeta); - let input = parse_macro_input!(item as DeriveInput); - let gen = flags_attribute::impl_flags(&attr_meta, &input); + let input = parse_macro_input!(item as syn::ItemEnum); + let gen = flags_attribute::impl_flags(attr.into(), input); gen.into() } @@ -525,7 +524,7 @@ pub fn flags(attr: TokenStream, item: TokenStream) -> TokenStream { #[proc_macro_error] pub fn error_domain_derive(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); - let gen = error_domain_derive::impl_error_domain(&input); + let gen = error_domain_derive::impl_error_domain(input); gen.into() } @@ -554,7 +553,7 @@ pub fn error_domain_derive(input: TokenStream) -> TokenStream { #[proc_macro_error] pub fn boxed_derive(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); - let gen = boxed_derive::impl_boxed(&input); + let gen = boxed_derive::impl_boxed(input); gen.into() } @@ -588,7 +587,7 @@ pub fn boxed_derive(input: TokenStream) -> TokenStream { #[proc_macro_error] pub fn shared_boxed_derive(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); - let gen = shared_boxed_derive::impl_shared_boxed(&input); + let gen = shared_boxed_derive::impl_shared_boxed(input); gen.into() } @@ -829,7 +828,8 @@ pub fn downgrade(input: TokenStream) -> TokenStream { #[proc_macro_error] pub fn variant_derive(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); - variant_derive::impl_variant(input) + let gen = variant_derive::impl_variant(input); + gen.into() } #[proc_macro] pub fn cstr_bytes(item: TokenStream) -> TokenStream { diff --git a/glib-macros/src/shared_boxed_derive.rs b/glib-macros/src/shared_boxed_derive.rs index 0e3db53b90da..3c6e60d0b099 100644 --- a/glib-macros/src/shared_boxed_derive.rs +++ b/glib-macros/src/shared_boxed_derive.rs @@ -4,7 +4,7 @@ use proc_macro2::{Ident, TokenStream}; use proc_macro_error::abort_call_site; use quote::quote; -use crate::utils::{crate_ident_new, find_attribute_meta, find_nested_meta, parse_name}; +use crate::utils::crate_ident_new; fn gen_impl_to_value_optional(name: &Ident, crate_ident: &TokenStream) -> TokenStream { let refcounted_type_prefix = refcounted_type_prefix(name, crate_ident); @@ -93,29 +93,30 @@ fn refcounted_type_prefix(name: &Ident, crate_ident: &TokenStream) -> proc_macro } } -pub fn impl_shared_boxed(input: &syn::DeriveInput) -> proc_macro2::TokenStream { +#[derive(deluxe::ExtractAttributes, Default)] +#[deluxe(attributes(shared_boxed_type))] +struct SharedBoxedType { + name: String, + #[deluxe(default)] + nullable: bool, +} + +pub fn impl_shared_boxed(mut input: syn::DeriveInput) -> proc_macro2::TokenStream { let name = &input.ident; - let refcounted_type = match refcounted_type(input) { + let errors = deluxe::Errors::new(); + let SharedBoxedType { + name: gtype_name, + nullable, + } = deluxe::extract_attributes_optional(&mut input.attrs, &errors); + + let refcounted_type = match refcounted_type(&input) { Some(p) => p, _ => { abort_call_site!("#[derive(glib::SharedBoxed)] requires struct MyStruct(T: RefCounted)") } }; - let gtype_name = match parse_name(input, "shared_boxed_type") { - Ok(name) => name, - Err(e) => abort_call_site!( - "{}: #[derive(glib::SharedBoxed)] requires #[shared_boxed_type(name = \"SharedBoxedTypeName\")]", - e - ), - }; - - let meta = find_attribute_meta(&input.attrs, "shared_boxed_type") - .unwrap() - .unwrap(); - let nullable = find_nested_meta(&meta, "nullable").is_some(); - let crate_ident = crate_ident_new(); let refcounted_type_prefix = refcounted_type_prefix(name, &crate_ident); @@ -132,6 +133,8 @@ pub fn impl_shared_boxed(input: &syn::DeriveInput) -> proc_macro2::TokenStream { }; quote! { + #errors + impl #crate_ident::subclass::shared::SharedType for #name { const NAME: &'static str = #gtype_name; diff --git a/glib-macros/src/utils.rs b/glib-macros/src/utils.rs index fbcba3a92557..b17d362fe0f4 100644 --- a/glib-macros/src/utils.rs +++ b/glib-macros/src/utils.rs @@ -1,113 +1,9 @@ // Take a look at the license at the top of the repository in the LICENSE file. -use anyhow::{bail, Result}; use proc_macro2::{Ident, Span, TokenStream}; use proc_macro_crate::crate_name; use quote::{quote, quote_spanned}; -use syn::{ - punctuated::Punctuated, spanned::Spanned, token::Comma, Attribute, DeriveInput, Lit, Meta, - MetaList, NestedMeta, Variant, -}; - -// find the #[@attr_name] attribute in @attrs -pub fn find_attribute_meta(attrs: &[Attribute], attr_name: &str) -> Result> { - let meta = match attrs.iter().find(|a| a.path.is_ident(attr_name)) { - Some(a) => a.parse_meta(), - _ => return Ok(None), - }; - match meta? { - Meta::List(n) => Ok(Some(n)), - _ => bail!("wrong meta type"), - } -} - -// parse a single meta like: ident = "value" -fn parse_attribute(meta: &NestedMeta) -> Result<(String, String)> { - let meta = match &meta { - NestedMeta::Meta(m) => m, - _ => bail!("wrong meta type"), - }; - let meta = match meta { - Meta::NameValue(n) => n, - _ => bail!("wrong meta type"), - }; - let value = match &meta.lit { - Lit::Str(s) => s.value(), - _ => bail!("wrong meta type"), - }; - - let ident = match meta.path.get_ident() { - None => bail!("missing ident"), - Some(ident) => ident, - }; - - Ok((ident.to_string(), value)) -} - -pub fn find_nested_meta<'a>(meta: &'a MetaList, name: &str) -> Option<&'a NestedMeta> { - meta.nested.iter().find(|n| match n { - NestedMeta::Meta(m) => m.path().is_ident(name), - _ => false, - }) -} - -pub fn parse_name_attribute(meta: &NestedMeta) -> Result { - let (ident, v) = parse_attribute(meta)?; - - match ident.as_ref() { - "name" => Ok(v), - s => bail!("Unknown meta {}", s), - } -} - -// Parse attribute such as: -// #[enum_type(name = "TestAnimalType")] -pub fn parse_name(input: &DeriveInput, attr_name: &str) -> Result { - let meta = match find_attribute_meta(&input.attrs, attr_name)? { - Some(meta) => meta, - _ => bail!("Missing '{}' attribute", attr_name), - }; - - let meta = match find_nested_meta(&meta, "name") { - Some(meta) => meta, - _ => bail!("Missing meta 'name'"), - }; - - parse_name_attribute(meta) -} - -#[derive(Debug)] -pub enum ItemAttribute { - Name(String), - Nick(String), -} - -fn parse_item_attribute(meta: &NestedMeta) -> Result { - let (ident, v) = parse_attribute(meta)?; - - match ident.as_ref() { - "name" => Ok(ItemAttribute::Name(v)), - "nick" => Ok(ItemAttribute::Nick(v)), - s => bail!("Unknown item meta {}", s), - } -} - -// Parse optional enum item attributes such as: -// #[enum_value(name = "My Name", nick = "my-nick")] -pub fn parse_item_attributes(attr_name: &str, attrs: &[Attribute]) -> Result> { - let meta = find_attribute_meta(attrs, attr_name)?; - - let v = match meta { - Some(meta) => meta - .nested - .iter() - .map(parse_item_attribute) - .collect::, _>>()?, - None => Vec::new(), - }; - - Ok(v) -} +use syn::{punctuated::Punctuated, spanned::Spanned, token::Comma, Variant}; pub fn crate_ident_new() -> TokenStream { use proc_macro_crate::FoundCrate; diff --git a/glib-macros/src/variant_derive.rs b/glib-macros/src/variant_derive.rs index 7f2662e535ac..b1414e5b8004 100644 --- a/glib-macros/src/variant_derive.rs +++ b/glib-macros/src/variant_derive.rs @@ -1,32 +1,37 @@ // Take a look at the license at the top of the repository in the LICENSE file. use heck::ToKebabCase; -use proc_macro::TokenStream; +use proc_macro2::TokenStream; use proc_macro_error::abort; use quote::{format_ident, quote}; use syn::{Data, DeriveInput, Fields, FieldsNamed, FieldsUnnamed, Generics, Ident, Type}; -use crate::utils::{crate_ident_new, find_attribute_meta}; +use crate::utils::crate_ident_new; -pub fn impl_variant(input: DeriveInput) -> TokenStream { +pub fn impl_variant(mut input: DeriveInput) -> TokenStream { match input.data { Data::Struct(data_struct) => { derive_variant_for_struct(input.ident, input.generics, data_struct) } Data::Enum(data_enum) => { - let mode = get_enum_mode(&input.attrs); + let errors = deluxe::Errors::new(); + let mode = get_enum_mode(&mut input.attrs, &errors); let has_data = data_enum .variants .iter() .any(|v| !matches!(v.fields, syn::Fields::Unit)); - if has_data { + let tokens = if has_data { derive_variant_for_enum(input.ident, input.generics, data_enum, mode) } else { derive_variant_for_c_enum(input.ident, input.generics, data_enum, mode) - } + }; + quote! { #errors #tokens } } - Data::Union(..) => { - panic!("#[derive(glib::Variant)] is not available for unions."); + Data::Union(_) => { + abort!( + input, + "#[derive(glib::Variant)] is not available for unions." + ); } } } @@ -236,22 +241,31 @@ fn derive_variant_for_struct( } }; - let derived = quote! { + quote! { #static_variant_type #to_variant #from_variant - }; - - derived.into() + } } +#[derive(deluxe::ParseMetaItem, Default)] +#[deluxe(default)] enum EnumMode { + #[default] + #[deluxe(skip)] String, + #[deluxe(skip)] Repr(Ident), - Enum { repr: bool }, - Flags { repr: bool }, + Enum { + #[deluxe(skip)] + repr: bool, + }, + Flags { + #[deluxe(skip)] + repr: bool, + }, } impl EnumMode { @@ -448,7 +462,7 @@ fn derive_variant_for_enum( _ => unimplemented!(), }; - let derived = quote! { + quote! { impl #impl_generics #glib::StaticVariantType for #ident #type_generics #where_clause { #[inline] fn static_variant_type() -> ::std::borrow::Cow<'static, #glib::VariantTy> { @@ -488,8 +502,7 @@ fn derive_variant_for_enum( } } } - }; - derived.into() + } } fn derive_variant_for_c_enum( @@ -598,7 +611,7 @@ fn derive_variant_for_c_enum( ), }; - let derived = quote! { + quote! { impl #impl_generics #glib::StaticVariantType for #ident #type_generics #where_clause { #[inline] fn static_variant_type() -> ::std::borrow::Cow<'static, #glib::VariantTy> { @@ -628,70 +641,48 @@ fn derive_variant_for_c_enum( #from_variant } } - }; - derived.into() + } } -fn get_enum_mode(attrs: &[syn::Attribute]) -> EnumMode { - let meta = find_attribute_meta(attrs, "variant_enum").unwrap(); - let mut repr_attr = None; - let mut mode = EnumMode::String; - if let Some(meta) = meta.as_ref() { - for nested in &meta.nested { - let meta = match nested { - syn::NestedMeta::Meta(m) => m, - syn::NestedMeta::Lit(s) => abort!(s, "wrong meta type"), - }; - let meta = match meta { - syn::Meta::Path(p) => p, - _ => abort!(meta, "wrong meta type"), - }; - let path = match meta.get_ident() { - Some(p) => p, - None => abort!(meta, "wrong meta type"), - }; - match path.to_string().as_ref() { - "repr" => repr_attr = Some(path), - "enum" => mode = EnumMode::Enum { repr: false }, - "flags" => mode = EnumMode::Flags { repr: false }, - s => abort!(path, "Unknown variant_enum meta {}", s), - } - } - } +#[derive(deluxe::ExtractAttributes, Default)] +#[deluxe(attributes(variant_enum))] +struct VariantEnum { + #[deluxe(flatten)] + mode: EnumMode, + repr: deluxe::Flag, +} + +fn get_enum_mode(attrs: &mut Vec, errors: &deluxe::Errors) -> EnumMode { + let VariantEnum { mode, repr } = deluxe::extract_attributes_optional(attrs, errors); match mode { - EnumMode::String if repr_attr.is_some() => { - let repr_attr = repr_attr.unwrap(); + EnumMode::String if repr.is_set() => { let repr = get_repr(attrs).unwrap_or_else(|| { - abort!( - repr_attr, - "Must have #[repr] attribute with one of i8, i16, i32, i64, u8, u16, u32, u64" - ) + errors.push( + syn::spanned::Spanned::span(&repr), + "#[derive(glib::Variant)] can only have #[repr(...)] with one of i8, i16, i32, i64, u8, u16, u32, u64" + ); + format_ident!("i32") }); EnumMode::Repr(repr) } EnumMode::Enum { .. } => EnumMode::Enum { - repr: repr_attr.is_some(), + repr: repr.is_set(), }, EnumMode::Flags { .. } => EnumMode::Flags { - repr: repr_attr.is_some(), + repr: repr.is_set(), }, e => e, } } -fn get_repr(attrs: &[syn::Attribute]) -> Option { - let list = find_attribute_meta(attrs, "repr").ok()??; - for nested in list.nested { - if let syn::NestedMeta::Meta(meta @ syn::Meta::Path(_)) = nested { - if let Some(ty) = meta.path().get_ident() { - match ty.to_string().as_str() { - "i8" | "i16" | "i32" | "i64" | "u8" | "u16" | "u32" | "u64" => { - return Some(ty.clone()) - } - _ => (), - } - } - } +fn get_repr(attrs: &mut Vec) -> Option { + #[derive(deluxe::ExtractAttributes)] + #[deluxe(attributes(repr))] + struct Repr(Ident); + + let Repr(repr) = deluxe::extract_attributes(attrs).ok()?; + match repr.to_string().as_str() { + "i8" | "i16" | "i32" | "i64" | "u8" | "u16" | "u32" | "u64" => Some(repr), + _ => None, } - None } From f529a90020a8c1867f04089bc4c654852bd316ad Mon Sep 17 00:00:00 2001 From: Jason Francis Date: Tue, 14 Feb 2023 11:23:31 -0500 Subject: [PATCH 2/5] glib-macros: add clone_block attribute --- glib-macros/Cargo.toml | 5 +- glib-macros/src/clone_block_attribute.rs | 987 ++++++++++++++++++++++ glib-macros/src/lib.rs | 11 + glib-macros/tests/clone_block.rs | 717 ++++++++++++++++ glib-macros/tests/clone_block_closures.rs | 236 ++++++ glib/src/lib.rs | 4 +- 6 files changed, 1957 insertions(+), 3 deletions(-) create mode 100644 glib-macros/src/clone_block_attribute.rs create mode 100644 glib-macros/tests/clone_block.rs create mode 100644 glib-macros/tests/clone_block_closures.rs diff --git a/glib-macros/Cargo.toml b/glib-macros/Cargo.toml index 590b1f03b862..ecdc619c256c 100644 --- a/glib-macros/Cargo.toml +++ b/glib-macros/Cargo.toml @@ -17,7 +17,7 @@ heck = "0.4" proc-macro-error = "1.0" proc-macro2 = "1.0" quote = "1.0" -syn = { version = "1.0", features = ["full"], default-features = false } +syn = { version = "1.0", features = ["full", "visit-mut"], default-features = false } proc-macro-crate = "1.0" deluxe = "0.4.1" @@ -25,6 +25,9 @@ deluxe = "0.4.1" proc-macro = true [dev-dependencies] +futures-channel = "0.3" +futures-executor = "0.3" +futures-util = "0.3" glib = { path = "../glib" } trybuild2 = "1.0" once_cell = "1.9.0" diff --git a/glib-macros/src/clone_block_attribute.rs b/glib-macros/src/clone_block_attribute.rs new file mode 100644 index 000000000000..c005234ff598 --- /dev/null +++ b/glib-macros/src/clone_block_attribute.rs @@ -0,0 +1,987 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use deluxe::{Errors, HasAttributes}; +use proc_macro2::{Span, TokenStream}; +use quote::{format_ident, quote, quote_spanned, ToTokens}; +use std::collections::HashSet; +use std::rc::Rc; +use syn::{ + parse::{ParseStream, Parser}, + parse_quote, parse_quote_spanned, + spanned::Spanned, + visit_mut::VisitMut, +}; + +enum Capture { + Strong { + span: Span, + ident: Option, + from: Option, + }, + Weak { + span: Span, + ident: Option, + from: Option, + or: Option>, + }, + Watch { + span: Span, + ident: Option, + from: Option, + }, +} + +#[derive(Clone, deluxe::ParseMetaItem, Default)] +#[deluxe(default)] +enum UpgradeFailAction { + #[default] + #[deluxe(skip)] + Unspecified, + #[deluxe(skip)] + Default(syn::Expr), + #[deluxe(rename = default_allow_none)] + AllowNone, + #[deluxe(rename = default_panic)] + Panic, + #[deluxe(rename = default_return, transparent)] + Return(Option), +} + +impl UpgradeFailAction { + #[inline] + fn is_unspecified(&self) -> bool { + matches!(self, UpgradeFailAction::Unspecified) + } +} + +#[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone)] +enum Mode { + Clone, + Closure, + ClosureAsync, +} + +fn is_simple_expr(mut expr: &syn::Expr) -> bool { + // try to do some looping to avoid blowing up the stack + loop { + match expr { + syn::Expr::Cast(e) => expr = &e.expr, + syn::Expr::Field(e) => expr = &e.base, + syn::Expr::Index(e) => return is_simple_expr(&e.expr) && is_simple_expr(&e.index), + syn::Expr::Lit(_) => return true, + syn::Expr::Paren(e) => expr = &e.expr, + syn::Expr::Path(_) => return true, + syn::Expr::Reference(e) => expr = &e.expr, + syn::Expr::Type(e) => expr = &e.expr, + _ => return false, + } + } +} + +impl Capture { + fn ident(&self) -> Option<&syn::Ident> { + match self { + Self::Strong { ident, .. } => ident.as_ref(), + Self::Weak { ident, .. } => ident.as_ref(), + Self::Watch { ident, .. } => ident.as_ref(), + } + } + fn set_default_fail(&mut self, action: &Rc) { + if let Self::Weak { or, .. } = self { + if or.is_none() { + *or = Some(action.clone()); + } + } + } + fn outer_tokens(&self, index: usize, glib: &syn::Path) -> Option { + Some(match self { + Self::Strong { ident, from, .. } => { + let target = format_ident!("____strong{}", index, span = Span::mixed_site()); + let input = from + .as_ref() + .map(|f| f.to_token_stream()) + .or_else(|| Some(ident.as_ref()?.to_token_stream()))?; + quote! { let #target = ::std::clone::Clone::clone(&#input); } + } + Self::Weak { ident, from, .. } => { + let target = format_ident!("____weak{}", index, span = Span::mixed_site()); + let input = from + .as_ref() + .map(|f| f.to_token_stream()) + .or_else(|| Some(ident.as_ref()?.to_token_stream()))?; + quote! { let #target = #glib::clone::Downgrade::downgrade(&#input); } + } + Self::Watch { ident, from, .. } => { + let target = format_ident!("____watch{}", index, span = Span::mixed_site()); + let input = from + .as_ref() + .map(|f| f.to_token_stream()) + .or_else(|| Some(ident.as_ref()?.to_token_stream()))?; + if from.as_ref().map(is_simple_expr).unwrap_or(true) { + quote! { + let #target = #glib::object::Watchable::watched_object(&#input); + } + } else { + let watch_ident = syn::Ident::new("____watch", Span::mixed_site()); + quote! { + let #watch_ident = ::std::clone::Clone::clone(&#input); + let #target = #glib::object::Watchable::watched_object(&#watch_ident); + } + } + } + }) + } + fn rename_tokens(&self, index: usize) -> Option { + Some(match self { + Self::Strong { ident, .. } => { + let ident = ident.as_ref()?; + let input = format_ident!("____strong{}", index, span = Span::mixed_site()); + quote! { let #ident = #input; } + } + _ => return None, + }) + } + fn inner_tokens(&self, index: usize, mode: Mode, glib: &syn::Path) -> Option { + Some(match self { + Self::Strong { .. } => return None, + Self::Weak { ident, or, .. } => { + let ident = ident.as_ref()?; + let input = format_ident!("____weak{}", index, span = Span::mixed_site()); + let upgrade = quote! { #glib::clone::Upgrade::upgrade(&#input) }; + let upgrade = match or.as_ref().map(|or| or.as_ref()) { + None | Some(UpgradeFailAction::AllowNone) => upgrade, + Some(or) => { + let action = match or { + UpgradeFailAction::Panic => { + let name = ident.to_string(); + quote! { ::std::panic!("Failed to upgrade `{}`", #name) } + } + UpgradeFailAction::Default(expr) => expr.to_token_stream(), + UpgradeFailAction::Return(expr) => { + if mode != Mode::Clone { + quote! { + return #glib::closure::ToClosureReturnValue::to_closure_return_value( + &#expr + ) + } + } else { + quote! { return #expr } + } + } + _ => unreachable!(), + }; + quote_spanned! { Span::mixed_site() => + match #upgrade { + ::std::option::Option::Some(v) => v, + ::std::option::Option::None => #action + } + } + } + }; + quote! { let #ident = #upgrade; } + } + Self::Watch { ident, .. } => { + if mode == Mode::ClosureAsync { + return None; + } + let ident = ident.as_ref()?; + let input = format_ident!("____watch{}", index, span = Span::mixed_site()); + quote! { + let #ident = unsafe { #input.borrow() }; + let #ident = ::std::convert::AsRef::as_ref(&#ident); + } + } + }) + } + fn async_inner_tokens(&self, index: usize) -> Option { + Some(match self { + Self::Strong { ident, .. } => { + ident.as_ref()?; + quote! { let #ident = ::std::clone::Clone::clone(&#ident); } + } + Self::Weak { ident, .. } => { + ident.as_ref()?; + let input = format_ident!("____weak{}", index, span = Span::mixed_site()); + quote! { let #input = ::std::clone::Clone::clone(&#input); } + } + Self::Watch { ident, .. } => { + let ident = ident.as_ref()?; + let input = format_ident!("____watch{}", index, span = Span::mixed_site()); + quote! { + let #ident = ::std::clone::Clone::clone(unsafe { &*#input.borrow() }); + } + } + }) + } + fn after_tokens(&self, glib: &syn::Path) -> Option { + Some(match self { + Self::Watch { ident, from, .. } if ident.is_some() || from.is_some() => { + let closure_ident = syn::Ident::new("____closure", Span::mixed_site()); + if from.as_ref().map(is_simple_expr).unwrap_or(true) { + let input = from + .as_ref() + .map(|f| f.to_token_stream()) + .or_else(|| Some(ident.as_ref()?.to_token_stream()))?; + quote! { + #glib::object::Watchable::watch_closure(&#input, &#closure_ident); + } + } else { + let watch_ident = syn::Ident::new("____watch", Span::mixed_site()); + quote! { + #glib::object::ObjectExt::watch_closure(&#watch_ident, &#closure_ident); + } + } + } + _ => return None, + }) + } +} + +impl Spanned for Capture { + fn span(&self) -> Span { + match self { + Self::Strong { span, .. } => *span, + Self::Weak { span, .. } => *span, + Self::Watch { span, .. } => *span, + } + } +} + +impl deluxe::ParseMetaItem for Capture { + fn parse_meta_item(_input: ParseStream, _mode: deluxe::ParseMode) -> deluxe::Result { + unreachable!() + } + fn parse_meta_item_named(input: ParseStream, name: &str, span: Span) -> deluxe::Result { + #[inline] + fn parse_capture( + input: ParseStream, + func: impl FnOnce(ParseStream) -> syn::Result, + ) -> syn::Result<(T, Option)> { + let t = input + .peek(syn::token::Paren) + .then(|| func(input)) + .transpose()? + .unwrap_or_default(); + let ident = if input.peek(syn::Token![_]) { + input.parse::()?; + None + } else { + Some(input.parse()?) + }; + Ok((t, ident)) + } + match name { + "strong" => { + let (from, ident) = parse_capture(input, parse_strong)?; + Ok(Capture::Strong { span, ident, from }) + } + "weak" => { + let ((from, or), ident) = parse_capture(input, parse_weak)?; + Ok(Capture::Weak { + span, + ident, + from, + or: or.map(Rc::new), + }) + } + "watch" => { + let (from, ident) = parse_capture(input, parse_strong)?; + Ok(Capture::Watch { span, ident, from }) + } + _ => unreachable!(), + } + } +} + +fn extract_idents<'p>(pat: &'p syn::Pat, idents: &mut HashSet<&'p syn::Ident>) { + use syn::Pat::*; + match pat { + Box(p) => extract_idents(&p.pat, idents), + Ident(p) => { + idents.insert(&p.ident); + } + Or(p) => p.cases.iter().for_each(|p| extract_idents(p, idents)), + Reference(p) => extract_idents(&p.pat, idents), + Slice(p) => p.elems.iter().for_each(|p| extract_idents(p, idents)), + Struct(p) => p.fields.iter().for_each(|p| extract_idents(&p.pat, idents)), + Tuple(p) => p.elems.iter().for_each(|p| extract_idents(p, idents)), + TupleStruct(p) => p.pat.elems.iter().for_each(|p| extract_idents(p, idents)), + Type(p) => extract_idents(&p.pat, idents), + _ => {} + } +} + +mod keywords { + syn::custom_keyword!(or); + syn::custom_keyword!(or_panic); + syn::custom_keyword!(or_return); + syn::custom_keyword!(allow_none); +} + +#[derive(Default, deluxe::ExtractAttributes)] +#[deluxe(attributes(clone))] +struct CloneAttrs { + #[deluxe(append, rename = strong, alias = weak)] + captures: Vec, + #[deluxe(flatten)] + or: UpgradeFailAction, +} + +#[derive(Default, deluxe::ExtractAttributes)] +#[deluxe(attributes(closure))] +struct ClosureAttrs { + local: deluxe::Flag, + #[deluxe(append, rename = strong, alias = weak, alias = watch)] + captures: Vec, + #[deluxe(flatten)] + or: UpgradeFailAction, +} + +fn parse_strong(input: syn::parse::ParseStream<'_>) -> syn::Result> { + if input.is_empty() { + return Ok(None); + } + let content; + syn::parenthesized!(content in input); + if content.is_empty() { + return Ok(None); + } + let expr = content.parse()?; + content.parse::()?; + Ok(Some(expr)) +} + +#[inline] +fn has_expr(input: syn::parse::ParseBuffer) -> bool { + // check if only one token + if input.peek(keywords::or_panic) || input.peek(keywords::allow_none) { + let _ = input.step(|c| Ok(((), c.token_tree().unwrap().1))); + if input.is_empty() { + return false; + } + } + // check if only one token and one expr + if input.peek(keywords::or) || input.peek(keywords::or_return) { + let _ = input.step(|c| Ok(((), c.token_tree().unwrap().1))); + if input.is_empty() { + return false; + } + if input.parse::().is_err() { + return false; + } + if input.is_empty() { + return false; + } + } + true +} + +fn parse_weak( + input: syn::parse::ParseStream<'_>, +) -> syn::Result<(Option, Option)> { + if input.is_empty() { + return Ok((None, None)); + } + let content; + syn::parenthesized!(content in input); + if content.is_empty() { + return Ok((None, None)); + } + let expr = if has_expr(content.fork()) { + Some(content.parse()?) + } else { + None + }; + let lookahead = content.lookahead1(); + let fail_action = if lookahead.peek(keywords::or) { + content.parse::()?; + let ret = content.parse()?; + Some(UpgradeFailAction::Default(ret)) + } else if lookahead.peek(keywords::or_panic) { + content.parse::()?; + Some(UpgradeFailAction::Panic) + } else if lookahead.peek(keywords::or_return) { + content.parse::()?; + let ret = if content.is_empty() { + None + } else { + Some(content.parse()?) + }; + Some(UpgradeFailAction::Return(ret)) + } else if lookahead.peek(keywords::allow_none) { + content.parse::()?; + Some(UpgradeFailAction::AllowNone) + } else if content.is_empty() { + None + } else { + return Err(lookahead.error()); + }; + content.parse::()?; + Ok((expr, fail_action)) +} + +fn has_captures<'p>(mut inputs: impl Iterator) -> bool { + inputs.any(|pat| { + pat.attrs() + .iter() + .any(|a| a.path.is_ident("strong") || a.path.is_ident("weak")) + }) +} + +fn extract_attr(attrs: &mut T, name: &str) -> Option { + let attrs = attrs.attrs_mut().ok()?; + let attr_index = attrs.iter().position(|a| a.path.is_ident(name)); + attr_index.map(|attr_index| attrs.remove(attr_index)) +} + +struct Visitor<'v> { + crate_path: &'v syn::Path, + errors: &'v Errors, +} + +impl<'v> Visitor<'v> { + fn create_gclosure(&mut self, closure: &syn::ExprClosure) -> Option { + let has_closure = closure.attrs.iter().any(|a| a.path.is_ident("closure")); + let has_watch = closure + .inputs + .iter() + .any(|pat| pat.attrs().iter().any(|a| a.path.is_ident("watch"))); + if !has_closure && !has_watch { + return None; + } + + let mut attrs = closure.attrs.clone(); + let ClosureAttrs { + local, + mut captures, + or: mut action, + } = deluxe::extract_attributes_optional(&mut attrs, self.errors); + let local = !has_closure || local.is_set(); + + let mode = match closure.body.as_ref() { + syn::Expr::Async(_) => Mode::ClosureAsync, + _ => Mode::Closure, + }; + let mut inputs = closure.inputs.iter().cloned().collect::>(); + if let Some(caps) = self.get_captures(&mut inputs, mode) { + captures.extend(caps); + } + self.extract_default_fail_action(&mut attrs, &mut action); + if !action.is_unspecified() { + let action = Rc::new(action); + for capture in &mut captures { + capture.set_default_fail(&action); + } + } + if !captures.is_empty() && closure.capture.is_none() { + self.errors.push_spanned( + closure, + "Closure must be `move` to use #[watch] or #[strong] or #[weak]", + ); + } + self.validate_captures(&captures, &inputs); + + let mut rest_index = None; + for (index, pat) in inputs.iter_mut().enumerate() { + if let Ok(attrs) = pat.attrs_mut() { + if let Some(attr) = extract_attr(attrs, "rest") { + if !attr.tokens.is_empty() { + self.errors.push_spanned( + &attr.tokens, + format!( + "Unknown tokens on #[{}] attribute", + attr.path.to_token_stream(), + ), + ); + } + rest_index = Some(index); + break; + } + } + } + if let Some(rest_index) = rest_index { + while inputs.len() > rest_index + 1 { + let pat = inputs.remove(rest_index + 1); + self.errors + .push_spanned(pat, "Arguments not allowed past #[rest] parameter"); + } + } + + let glib = self.crate_path; + let closure_ident = syn::Ident::new("____closure", Span::mixed_site()); + let values_ident = syn::Ident::new("____values", Span::mixed_site()); + let constructor = if local { + format_ident!("new_local") + } else { + format_ident!("new") + }; + let outer = captures + .iter() + .enumerate() + .map(|(i, c)| c.outer_tokens(i, glib)); + let rename = captures.iter().enumerate().map(|(i, c)| c.rename_tokens(i)); + let inner = captures + .iter() + .enumerate() + .map(|(i, c)| c.inner_tokens(i, mode, glib)); + let after = captures.iter().map(|c| c.after_tokens(glib)); + let required_arg_count = inputs + .iter() + .enumerate() + .rev() + .find_map(|(i, p)| { + (Some(i) != rest_index && !matches!(p, syn::Pat::Wild(_))).then_some(i + 1) + }) + .unwrap_or(0); + let assert_arg_count = (required_arg_count > 0).then(|| { + quote! { + if #values_ident.len() < #required_arg_count { + ::std::panic!( + "Closure called with wrong number of arguments: Expected {}, got {}", + #required_arg_count, + #values_ident.len(), + ); + } + } + }); + let arg_unwraps = inputs.iter().enumerate().map(|(index, pat)| match pat { + syn::Pat::Wild(_) => None, + _ => { + let attrs = pat.attrs(); + Some(if Some(index) == rest_index { + quote! { + #(#attrs)* + let #pat = &#values_ident[#index..#values_ident.len()]; + } + } else { + quote! { + #(#attrs)* + let #pat = #glib::Value::get(&#values_ident[#index]) + .unwrap_or_else(|e| { + ::std::panic!("Wrong type for closure argument {}: {:?}", #index, e) + }); + } + }) + } + }); + let expr = &closure.body; + let inner_body = quote! { { + #assert_arg_count + #(#inner)* + #(#arg_unwraps)* + #expr + } }; + let body = if mode == Mode::ClosureAsync { + let async_inner = captures + .iter() + .enumerate() + .map(|(i, c)| c.async_inner_tokens(i)); + quote! { + let #values_ident = #values_ident.to_vec(); + #(#async_inner)* + #glib::MainContext::default().spawn_local( + async move { let _: () = #inner_body.await; } + ); + ::std::option::Option::None + } + } else { + let inner_body = match &closure.output { + syn::ReturnType::Type(_, ty) => { + let ret = syn::Ident::new("____ret", Span::mixed_site()); + quote! { + { + let #ret: #ty = #inner_body; + #ret + } + } + } + _ => quote! { #inner_body }, + }; + quote! { + #glib::closure::IntoClosureReturnValue::into_closure_return_value( + #inner_body + ) + } + }; + Some(parse_quote_spanned! { Span::mixed_site() => + { + #(#outer)* + #(#rename)* + let #closure_ident = #glib::closure::RustClosure::#constructor(move |#values_ident| { + #body + }); + #(#after)* + #closure_ident + } + }) + } + + fn create_closure(&mut self, closure: &syn::ExprClosure) -> Option { + let has_clone = closure.attrs.iter().any(|a| a.path.is_ident("clone")); + if !has_clone && !has_captures(closure.inputs.iter()) { + return None; + } + let mut attrs = closure.attrs.clone(); + let CloneAttrs { + mut captures, + or: mut action, + } = deluxe::extract_attributes_optional(&mut attrs, self.errors); + + let mut inputs = closure.inputs.iter().cloned().collect::>(); + if let Some(caps) = self.get_captures(&mut inputs, Mode::Clone) { + captures.extend(caps); + } + self.validate_captures(&captures, &inputs); + if closure.capture.is_none() { + self.errors.push_spanned( + closure, + "Closure must be `move` to use #[strong] or #[weak]", + ); + } + self.extract_default_fail_action(&mut attrs, &mut action); + if !action.is_unspecified() { + let action = Rc::new(action); + for capture in &mut captures { + capture.set_default_fail(&action); + } + } + let glib = self.crate_path; + let outer = captures + .iter() + .enumerate() + .map(|(i, c)| c.outer_tokens(i, glib)); + let rename = captures.iter().enumerate().map(|(i, c)| c.rename_tokens(i)); + let inner = captures + .iter() + .enumerate() + .map(|(i, c)| c.inner_tokens(i, Mode::Clone, glib)); + let output; + let body = if let syn::Expr::Async(syn::ExprAsync { + attrs, + capture, + block, + .. + }) = &*closure.body + { + output = syn::ReturnType::Default; + let block = match &closure.output { + syn::ReturnType::Type(_, ty) => { + let ret = syn::Ident::new("____ret", Span::mixed_site()); + quote! { + let #ret: #ty = #block; + #ret + } + } + _ => quote! { #block }, + }; + parse_quote! { + #(#attrs)* + async #capture { + #(#inner)* + #block + } + } + } else { + output = closure.output.clone(); + let old_body = &closure.body; + parse_quote! { + { + #(#inner)* + #old_body + } + } + }; + let body = syn::ExprClosure { + attrs, + movability: closure.movability, + asyncness: closure.asyncness, + capture: closure.capture, + or1_token: closure.or1_token, + inputs: FromIterator::from_iter(inputs.into_iter()), + or2_token: closure.or2_token, + output, + body: Box::new(body), + }; + Some(parse_quote_spanned! { Span::mixed_site() => + { + #(#outer)* + #(#rename)* + #body + } + }) + } + + fn create_async(&mut self, async_: &syn::ExprAsync) -> Option { + let has_clone = async_.attrs.iter().any(|a| a.path.is_ident("clone")); + if !has_clone { + return None; + } + let mut attrs = async_.attrs.clone(); + let CloneAttrs { + mut captures, + or: mut action, + } = deluxe::extract_attributes_optional(&mut attrs, self.errors); + + self.validate_captures(&captures, &[]); + if async_.capture.is_none() { + self.errors + .push_spanned(async_, "Async block must be `move` to use #[clone]"); + } + self.extract_default_fail_action(&mut attrs, &mut action); + if !action.is_unspecified() { + let action = Rc::new(action); + for capture in &mut captures { + capture.set_default_fail(&action); + } + } + let glib = self.crate_path; + let outer = captures + .iter() + .enumerate() + .map(|(i, c)| c.outer_tokens(i, glib)); + let rename = captures.iter().enumerate().map(|(i, c)| c.rename_tokens(i)); + let inner = captures + .iter() + .enumerate() + .map(|(i, c)| c.inner_tokens(i, Mode::Clone, glib)); + let block = &async_.block; + let block = parse_quote! { + { + #(#inner)* + #block + } + }; + let body = syn::ExprAsync { + attrs, + async_token: async_.async_token, + capture: async_.capture, + block, + }; + Some(parse_quote_spanned! { Span::mixed_site() => + { + #(#outer)* + #(#rename)* + #body + } + }) + } + + fn validate_pat_ident(&mut self, pat: syn::Pat) -> Option { + match pat { + syn::Pat::Ident(syn::PatIdent { ident, .. }) => Some(ident), + _ => { + self.errors + .push_spanned(pat, "Pattern for captured variable must be an identifier"); + None + } + } + } + + fn validate_captures(&mut self, captures: &[Capture], inputs: &[syn::Pat]) { + let mut has_watch = false; + let mut names = HashSet::new(); + for pat in inputs { + extract_idents(pat, &mut names); + } + for capture in captures { + if let Capture::Watch { span, .. } = capture { + if has_watch { + self.errors + .push(*span, "Only one #[watch] attribute is allowed on closure"); + } else { + has_watch = true; + } + } + if let Some(ident) = capture.ident() { + if names.contains(ident) { + self.errors.push_spanned( + ident, + format!( + "Identifier `{ident}` is used more than once in this parameter list", + ), + ); + } else { + names.insert(ident); + } + } + } + } + + fn get_captures(&mut self, inputs: &mut Vec, mode: Mode) -> Option> { + let mut captures = Vec::new(); + let mut index = 0; + while index < inputs.len() { + let mut strong = None; + let mut weak = None; + let mut watch = None; + if let Ok(attrs) = inputs[index].attrs_mut() { + if let Some(attr) = extract_attr(attrs, "strong") { + strong = Some(attr); + } else if let Some(attr) = extract_attr(attrs, "weak") { + weak = Some(attr); + } else if mode != Mode::Clone { + if let Some(attr) = extract_attr(attrs, "watch") { + watch = Some(attr); + } + } + if strong.is_some() || weak.is_some() || watch.is_some() { + for attr in attrs { + self.errors.push_spanned( + attr, + "Extra attributes not allowed on #[strong] or #[weak] or #[watch] capture", + ); + } + } + } + if let Some(strong) = strong { + let span = strong.span(); + let from = parse_strong.parse2(strong.tokens).unwrap_or_else(|e| { + self.errors.push_syn(e); + None + }); + let pat = inputs.remove(index); + let ident = if matches!(pat, syn::Pat::Wild(_)) { + None + } else { + self.validate_pat_ident(pat) + }; + if ident.is_some() || from.is_some() { + captures.push(Capture::Strong { span, ident, from }); + } else { + self.errors.push( + span, + "capture must be named or provide a source expression using #[strong(...)]", + ); + } + } else if let Some(weak) = weak { + let span = weak.span(); + let (from, or) = parse_weak.parse2(weak.tokens).unwrap_or_else(|e| { + self.errors.push_syn(e); + (None, None) + }); + let pat = inputs.remove(index); + let ident = if matches!(pat, syn::Pat::Wild(_)) { + None + } else { + self.validate_pat_ident(pat) + }; + if ident.is_some() || from.is_some() { + captures.push(Capture::Weak { + span, + ident, + from, + or: or.map(Rc::new), + }); + } else { + self.errors.push( + span, + "capture must be named or provide a source expression using #[weak(...)]", + ); + } + } else if let Some(watch) = watch { + let span = watch.span(); + let from = parse_strong.parse2(watch.tokens).unwrap_or_else(|e| { + self.errors.push_syn(e); + None + }); + let pat = inputs.remove(index); + let ident = if matches!(pat, syn::Pat::Wild(_)) { + None + } else { + self.validate_pat_ident(pat) + }; + if ident.is_some() || from.is_some() { + captures.push(Capture::Watch { span, ident, from }); + } else { + self.errors.push( + span, + "capture must be named or provide a source expression using #[watch(...)]", + ); + } + } else { + index += 1; + } + } + if captures.is_empty() { + None + } else { + Some(captures) + } + } + + fn extract_default_fail_action( + &mut self, + attrs: &mut Vec, + action_out: &mut UpgradeFailAction, + ) { + loop { + let action = if let Some(attr) = extract_attr(attrs, "default_panic") { + let span = attr.span(); + if let Err(e) = syn::parse2::(attr.tokens) { + self.errors.push_syn(e); + } + Some((span, UpgradeFailAction::Panic)) + } else if let Some(attr) = extract_attr(attrs, "default_allow_none") { + let span = attr.span(); + if let Err(e) = syn::parse2::(attr.tokens) { + self.errors.push_syn(e); + } + Some((span, UpgradeFailAction::AllowNone)) + } else if let Some(attr) = extract_attr(attrs, "default_return") { + let span = attr.span(); + let ret = (|input: syn::parse::ParseStream<'_>| { + if input.is_empty() { + return Ok(None); + } + let content; + syn::parenthesized!(content in input); + let expr = content.parse::()?; + content.parse::()?; + input.parse::()?; + Ok(Some(expr)) + }) + .parse2(attr.tokens); + match ret { + Ok(expr) => Some((span, UpgradeFailAction::Return(expr))), + Err(e) => { + self.errors.push_syn(e); + None + } + } + } else { + None + }; + if let Some((span, action)) = action { + if !action_out.is_unspecified() { + self.errors.push(span, "Duplicate default action specified"); + } + *action_out = action; + } else { + break; + } + } + } +} + +impl<'v> VisitMut for Visitor<'v> { + fn visit_expr_mut(&mut self, expr: &mut syn::Expr) { + let new_expr = if let syn::Expr::Closure(closure) = expr { + syn::visit_mut::visit_expr_mut(self, closure.body.as_mut()); + self.create_gclosure(closure) + .or_else(|| self.create_closure(closure)) + } else if let syn::Expr::Async(async_) = expr { + self.create_async(async_) + } else { + syn::visit_mut::visit_expr_mut(self, expr); + None + }; + if let Some(new_expr) = new_expr { + *expr = new_expr; + } + } +} + +pub fn impl_clone_block(item: &mut syn::Item, crate_path: &syn::Path, errors: &Errors) { + let mut visitor = Visitor { crate_path, errors }; + visitor.visit_item_mut(item); +} diff --git a/glib-macros/src/lib.rs b/glib-macros/src/lib.rs index efcb47802fd6..8f3bc2f04269 100644 --- a/glib-macros/src/lib.rs +++ b/glib-macros/src/lib.rs @@ -2,6 +2,7 @@ mod boxed_derive; mod clone; +mod clone_block_attribute; mod closure; mod downgrade_derive; mod enum_derive; @@ -961,3 +962,13 @@ pub fn derive_props(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as properties::PropsMacroInput); properties::impl_derive_props(input) } + +#[proc_macro_attribute] +#[proc_macro_error::proc_macro_error] +pub fn clone_block(_attr: TokenStream, item: TokenStream) -> TokenStream { + let mut item = syn::parse_macro_input!(item as syn::Item); + let errors = deluxe::Errors::new(); + let glib = utils::crate_ident_new(); + clone_block_attribute::impl_clone_block(&mut item, &syn::parse_quote! { #glib }, &errors); + errors.output_with(item).into() +} diff --git a/glib-macros/tests/clone_block.rs b/glib-macros/tests/clone_block.rs new file mode 100644 index 000000000000..eab25528741b --- /dev/null +++ b/glib-macros/tests/clone_block.rs @@ -0,0 +1,717 @@ +use std::cell::{Cell, RefCell}; +use std::panic; +use std::rc::Rc; +use std::sync::{Arc, Mutex}; +use std::thread; + +use futures_executor::block_on; +use glib::clone_block; + +struct State { + count: i32, + started: bool, +} + +impl State { + fn new() -> Self { + Self { + count: 0, + started: false, + } + } +} + +#[test] +#[clone_block] +fn clone_and_references() { + let state = Rc::new(RefCell::new(State::new())); + let ref_state = &state; + assert!(!ref_state.borrow().started); + + let closure = { + move |#[weak] ref_state| { + ref_state.unwrap().borrow_mut().started = true; + } + }; + + closure(); + assert!(ref_state.borrow().started); +} + +#[test] +#[clone_block] +fn subfields_renaming() { + struct Foo { + v: Rc, + } + + impl Foo { + fn foo(&self) { + let state = Rc::new(RefCell::new(State::new())); + + let closure = move |#[strong(self.v)] v, #[weak(state)] hello, _| { + println!("v: {v}"); + hello.unwrap().borrow_mut().started = true; + }; + closure(2); + } + } + + Foo { v: Rc::new(0) }.foo(); +} + +#[test] +#[clone_block] +fn renaming() { + let state = Rc::new(RefCell::new(State::new())); + assert!(!state.borrow().started); + + let closure = { + move |#[weak(state)] hello| { + hello.unwrap().borrow_mut().started = true; + } + }; + + closure(); + assert!(state.borrow().started); +} + +#[test] +#[clone_block] +fn clone_closure() { + let state = Rc::new(RefCell::new(State::new())); + assert!(!state.borrow().started); + + let closure = { + move |#[weak(or_return)] state| { + state.borrow_mut().started = true; + } + }; + + closure(); + + assert!(state.borrow().started); + assert_eq!(state.borrow().count, 0); + + let closure = { + let state2 = Rc::new(RefCell::new(State::new())); + assert!(state.borrow().started); + + move |#[weak(or_return)] state, #[strong] state2| { + state.borrow_mut().count += 1; + state.borrow_mut().started = true; + state2.borrow_mut().started = true; + } + }; + + closure(); + + assert_eq!(state.borrow().count, 1); + assert!(state.borrow().started); +} + +#[test] +#[clone_block] +fn clone_default_value() { + let closure = { + let state = Rc::new(RefCell::new(State::new())); + move |_, #[weak(or_return 42)] state| { + state.borrow_mut().started = true; + 10 + } + }; + + assert_eq!(42, closure(50)); +} + +#[test] +#[clone_block] +fn clone_panic() { + let state = Arc::new(Mutex::new(State::new())); + state.lock().expect("Failed to lock state mutex").count = 20; + + let closure = { + let state2 = Arc::new(Mutex::new(State::new())); + move |#[weak(or_panic)] state2, #[strong] state, _| { + state.lock().expect("Failed to lock state mutex").count = 21; + state2.lock().expect("Failed to lock state2 mutex").started = true; + 10 + } + }; + + let result = panic::catch_unwind(|| { + closure(50); + }); + + assert!(result.is_err()); + + assert_eq!(state.lock().expect("Failed to lock state mutex").count, 20); +} + +#[test] +fn clone_import_rename() { + import_rename::test(); +} + +#[clone_block] +mod import_rename { + use glib::clone_block as clone_block_g; + + #[allow(unused_macros)] + macro_rules! clone_block { + ($($anything:tt)*) => { + |_, _| panic!("The clone_block macro doesn't support renaming") + }; + } + + #[allow(unused_variables)] + #[clone_block_g] + pub fn test() { + let n = 2; + + let closure: Box = Box::new(move |#[strong] n, _, _| { + println!("The clone_block macro does support renaming") + }); + + closure(0, 0); + } +} + +#[test] +#[clone_block] +fn test_clone_macro_self_rename() { + #[derive(Debug)] + struct Foo { + v: u8, + } + + impl Foo { + #[allow(dead_code)] + fn foo(&self) { + let closure = move |_x, #[strong(self)] this| { + println!("v: {this:?}"); + }; + closure(0i8); // to prevent compiler error for unknown `x` type. + let _ = move |#[strong(self)] this| { + println!("v: {this:?}"); + }; + let closure = move |_x, #[strong(self)] this| println!("v: {this:?}"); + closure(0i8); // to prevent compiler error for unknown `x` type. + let _ = move |#[strong(self)] this| println!("v: {this:?}"); + + // Fields now! + let closure = move |_x, #[strong(self.v)] v| { + println!("v: {v:?}"); + }; + closure(0i8); // to prevent compiler error for unknown `x` type. + let _ = move |#[strong(self.v)] v| println!("v: {v:?}"); + + // With default_panic + let closure = #[default_panic] + move |#[strong(self.v)] v, _x| { + println!("v: {v:?}"); + }; + closure(0i8); // to prevent compiler error for unknown `x` type. + let _ = #[default_panic] + move |#[strong(self.v)] v| println!("v: {v:?}"); + + // With default_return + let closure = #[default_return(true)] + move |#[strong(self.v)] _v, _x| false; + closure(0i8); // to prevent compiler error for unknown `x` type. + let _ = #[default_return(true)] + move |#[strong(self.v)] _v| false; + } + } +} + +#[test] +#[clone_block] +fn test_clone_macro_rename() { + let v = Rc::new(1); + + let closure = move |_x, #[weak(v or_panic)] y| { + println!("v: {y}"); + }; + closure(0i8); // to prevent compiler error for unknown `x` type. + let _ = move |#[weak(v or_panic)] y| println!("v: {y}"); + + let closure = move |_x, #[strong(v)] y| { + println!("v: {y}"); + }; + closure(0i8); // to prevent compiler error for unknown `x` type. + let _ = move |#[strong(v)] y| println!("v: {y}"); + + let closure = move |_x, #[weak(v or_return)] y| { + println!("v: {y}"); + }; + closure(0i8); // to prevent compiler error for unknown `x` type. + let _ = move |#[weak(v or_return)] y| println!("v: {y}"); + + let closure = move |_x, #[strong(v)] y| { + println!("v: {y}"); + }; + closure(0i8); // to prevent compiler error for unknown `x` type. + let _ = move |#[strong(v)] y| println!("v: {y}"); + + let closure = move |_x, #[weak(v or_return true)] _y| false; + closure(0i8); // to prevent compiler error for unknown `x` type. + let _ = move |#[weak(v or_return true)] _y| false; + + let closure = move |_x, #[strong(v)] _y| false; + closure(0i8); // to prevent compiler error for unknown `x` type. + let _ = move |#[strong(v)] _y| false; +} + +#[test] +#[clone_block] +#[allow(unused_variables)] +fn test_clone_macro_simple() { + let v = Rc::new(1); + + let closure = move |_x, #[weak(or_panic)] v| { + println!("v: {v}"); + }; + closure(0i8); // to prevent compiler error for unknown `x` type. + let _ = move |#[weak(or_panic)] v| println!("v: {v}"); + + let closure = move |_x, #[strong] v| { + println!("v: {v}"); + }; + closure(0i8); // to prevent compiler error for unknown `x` type. + let _ = move |#[strong] v| println!("v: {v}"); + + let closure = move |#[weak] v, _x| { + println!("v: {}", v.unwrap()); + }; + closure(0i8); // to prevent compiler error for unknown `x` type. + let _ = move |#[weak] v| println!("v: {}", v.unwrap()); + + let closure = move |_x, #[strong] v| { + println!("v: {v}"); + }; + closure(0i8); // to prevent compiler error for unknown `x` type. + let _ = move |#[strong] v| println!("v: {v}"); + + let closure = move |_x, #[weak(or_return true)] v| false; + closure(0i8); // to prevent compiler error for unknown `x` type. + let _ = move |#[weak(or_return true)] v| false; + + let closure = move |_x, #[strong] v| false; + closure(0i8); // to prevent compiler error for unknown `x` type. + let _ = move |#[strong] v| false; +} + +#[test] +#[clone_block] +#[allow(unused_variables)] +fn test_clone_macro_double_simple() { + let v = Rc::new(1); + let w = Rc::new(2); + + let closure = move |#[weak(or_panic)] v, #[weak(or_panic)] w, _x| { + println!("v: {v}, w: {w}"); + }; + closure(0i8); // to prevent compiler error for unknown `x` type. + let _ = #[default_panic] + move |#[weak] v, #[weak] w| println!("v: {v}, w: {w}"); + + let closure = #[default_panic] + move |#[strong] v, #[strong] w, _x| { + println!("v: {v}, w: {w}"); + }; + closure(0i8); // to prevent compiler error for unknown `x` type. + let _ = #[default_panic] + move |#[strong] v, #[strong] w| println!("v: {v}, w: {w}"); + + let closure = #[default_return] + move |_x, #[weak] v, #[weak] w| { + println!("v: {v}, w: {w}"); + }; + closure(0i8); // to prevent compiler error for unknown `x` type. + let _ = #[default_return] + move |#[weak] v, #[weak] w| println!("v: {v}, w: {w}"); + + let closure = move |_x, #[strong] v, #[strong] w| { + println!("v: {v}, w: {w}"); + }; + closure(0i8); // to prevent compiler error for unknown `x` type. + let _ = move |#[strong] v, #[strong] w| println!("v: {v}, w: {w}"); + + let closure = #[default_return(true)] + move |_x, #[weak] v, #[weak] w| false; + closure(0i8); // to prevent compiler error for unknown `x` type. + let _ = #[default_return(true)] + move |#[weak] v, #[weak] w| false; + + let closure = #[default_return(true)] + move |#[strong] v, #[strong] w, _x| false; + closure(0i8); // to prevent compiler error for unknown `x` type. + let _ = #[default_return(true)] + move |#[strong] v, #[strong] w| false; +} + +#[test] +#[clone_block] +#[allow(unused_variables)] +fn test_clone_macro_double_rename() { + let v = Rc::new(1); + let w = Rc::new(2); + let done = Rc::new(RefCell::new(0)); + + let closure = #[default_panic] + move |z, #[weak(v)] x, #[weak] w| z + *x + *w; + assert_eq!(closure(1i8), 4i8); + let closure = #[default_panic] + move |#[weak(v)] x, #[weak] w| 1; + assert_eq!(closure(), 1); + + let closure = #[default_panic] + move |z, #[weak] v, #[weak(w)] x| z + *v + *x; + assert_eq!(closure(10i8), 13i8); + let closure = #[default_panic] + move |#[weak] v, #[weak(w)] x| 2 + *x; + assert_eq!(closure(), 4); + + let closure = #[default_panic] + move |#[strong(v)] x, #[strong] w, z| z + *x + *w; + assert_eq!(closure(3i8), 6i8); + let closure = #[default_panic] + move |#[strong(v)] x, #[strong] w| 4 + *w; + assert_eq!(closure(), 6); + + let closure = #[default_panic] + move |#[strong] v, #[strong(w)] x, z| z + *v + *x; + assert_eq!(closure(0i8), 3i8); + let closure = #[default_panic] + move |#[strong] v, #[strong(w)] x| 5; + assert_eq!(closure(), 5); + + let t_done = done.clone(); + let closure = #[default_return] + move |z, #[weak(v)] x, #[weak] w| { + *t_done.borrow_mut() = z + *x + *w; + }; + closure(4i8); + assert_eq!(*done.borrow(), 7); + let t_done = done.clone(); + let closure = #[default_return] + move |#[weak(v)] x, #[weak] w| *t_done.borrow_mut() = *x + *w; + closure(); + assert_eq!(*done.borrow(), 3); + + let t_done = done.clone(); + let closure = #[default_return] + move |z, #[weak] v, #[weak(w)] x| { + *t_done.borrow_mut() = z + *v + *x; + }; + closure(8i8); + assert_eq!(*done.borrow(), 11i8); + let t_done = done.clone(); + let closure = #[default_return] + move |#[weak] v, #[weak(w)] x| *t_done.borrow_mut() = *v * *x; + closure(); + assert_eq!(*done.borrow(), 2); + + let t_done = done.clone(); + let closure = move |z, #[strong(v)] x, #[strong] w| { + *t_done.borrow_mut() = z + *x + *w; + }; + closure(9i8); + assert_eq!(*done.borrow(), 12i8); + let t_done = done.clone(); + let closure = move |#[strong(v)] x, #[strong] w| *t_done.borrow_mut() = *x - *w; + closure(); + assert_eq!(*done.borrow(), -1); + + let t_done = done.clone(); + let closure = move |z, #[strong] v, #[strong(w)] x| { + *t_done.borrow_mut() = *v + *x * z; + }; + closure(2i8); + assert_eq!(*done.borrow(), 5); + let t_done = done.clone(); + let closure = move |#[strong] v, #[strong(w)] x| *t_done.borrow_mut() = *x - *v; + closure(); + assert_eq!(*done.borrow(), 1); + + let closure = #[default_return(true)] + move |_, #[weak(v)] _x, #[weak] w| false; + assert!(!closure(0u8)); + let closure = #[default_return(true)] + move |#[weak(v)] _x, #[weak] w| false; + assert!(!closure()); + + let closure = #[default_return(true)] + move |_, #[weak] v, #[weak(w)] _x| false; + assert!(!closure("a")); + let closure = #[default_return(true)] + move |#[weak] v, #[weak(w)] _x| false; + assert!(!closure()); + + let closure = #[default_return(true)] + move |#[strong(v)] _x, #[strong] w, _| false; + assert!(!closure('a')); + let closure = #[default_return(true)] + move |#[strong(v)] _x, #[strong] w| false; + assert!(!closure()); + + let closure = #[default_return(true)] + move |#[strong] v, #[strong(w)] _x, _| false; + assert!(!closure(12.)); + let closure = #[default_return(true)] + move |#[strong] v, #[strong(w)] _x| false; + assert!(!closure()); +} + +macro_rules! test_arc_closure { + ($name:ident, $kind:tt, $($fail:tt)+) => { + #[test] + #[clone_block] + fn $name() { + // We need Arc and Mutex to use them below in the thread. + let check = Arc::new(Mutex::new(0)); + let v = Arc::new(Mutex::new(1)); + let w = Arc::new(Mutex::new(1)); + + let closure = #[$($fail)+] move |#[$kind(v)] x, #[$kind] w, #[weak] check, arg: i8| { + *x.lock().unwrap() += arg; + *w.lock().unwrap() += arg; + *check.lock().unwrap() += 1; + }; + closure(1); + assert_eq!(2, *v.lock().unwrap()); + assert_eq!(2, *w.lock().unwrap()); + assert_eq!(1, *check.lock().unwrap()); + + let closure2 = #[$($fail)+] move |#[$kind] v, #[$kind(w)] x, #[weak] check, arg: i8| { + *v.lock().unwrap() += arg; + *x.lock().unwrap() += arg; + *check.lock().unwrap() += 1; + }; + closure2(1); + assert_eq!(3, *v.lock().unwrap()); + assert_eq!(3, *w.lock().unwrap()); + assert_eq!(2, *check.lock().unwrap()); + + macro_rules! inner { + (strong) => {{}}; + (weak) => {{ + std::mem::drop(v); + std::mem::drop(w); + + // We use the threads to ensure that the closure panics as expected. + assert!(thread::spawn(move || { + closure(1); + }).join().is_err()); + assert_eq!(2, *check.lock().unwrap()); + assert!(thread::spawn(move || { + closure2(1); + }).join().is_err()); + assert_eq!(2, *check.lock().unwrap()); + }} + } + + inner!($kind); + } + }; +} + +macro_rules! test_rc_closure { + ($name:ident, $kind:tt, $($fail:tt)+) => { + #[test] + #[clone_block] + fn $name() { + let check = Rc::new(RefCell::new(0)); + let v = Rc::new(RefCell::new(1)); + let w = Rc::new(RefCell::new(1)); + + let closure = #[$($fail)+] move |#[$kind(v)] x, #[$kind] w, #[weak] check, arg: i8| { + *x.borrow_mut() += arg; + *w.borrow_mut() += arg; + *check.borrow_mut() += 1; + }; + closure(1); + assert_eq!(2, *v.borrow()); + assert_eq!(2, *w.borrow()); + assert_eq!(1, *check.borrow()); + + let closure2 = #[$($fail)+] move |#[$kind] v, #[$kind(w)] x, #[weak] check, arg: i8| { + *v.borrow_mut() += arg; + *x.borrow_mut() += arg; + *check.borrow_mut() += 1; + }; + closure2(1); + assert_eq!(3, *v.borrow()); + assert_eq!(3, *w.borrow()); + assert_eq!(2, *check.borrow()); + + macro_rules! inner { + (strong) => {{}}; + (weak) => {{ + std::mem::drop(v); + std::mem::drop(w); + + closure(1); + assert_eq!(2, *check.borrow()); + closure2(1); + assert_eq!(2, *check.borrow()); + }} + } + + inner!($kind); + } + }; +} + +test_arc_closure!(test_clone_macro_typed_arc_weak, weak, default_panic); +test_arc_closure!(test_clone_macro_typed_arc_strong, strong, default_panic); +test_rc_closure!(test_clone_macro_typed_rc_weak, weak, default_return); +test_rc_closure!(test_clone_macro_typed_rc_strong, strong, default_return); + +#[test] +#[clone_block] +fn test_clone_macro_typed_args() { + let check = Rc::new(RefCell::new(0)); + let v = Rc::new(RefCell::new(1)); + let w = Rc::new(RefCell::new(1)); + let closure = move |#[weak(or_return)] v, + #[weak(w or_return )] x, + #[weak(or_return)] check, + arg: i8, + arg2| { + *v.borrow_mut() = arg; + *x.borrow_mut() = arg2; + *check.borrow_mut() += 1; + }; + closure(0, 9); + assert_eq!(0, *v.borrow()); + assert_eq!(9, *w.borrow()); + assert_eq!(1, *check.borrow()); + + std::mem::drop(v); + std::mem::drop(w); + assert_eq!(1, *check.borrow()); +} + +macro_rules! test_default { + ($name:ident, $ret:expr, $($closure_body:tt)*) => { + #[test] + #[clone_block] + #[allow(clippy::bool_assert_comparison)] + #[allow(clippy::nonminimal_bool)] + fn $name() { + let v = Rc::new(1); + let tmp = move |#[weak(v or_return $ret)] _v| $($closure_body)*; + assert_eq!(tmp(), $($closure_body)*, "shouldn't use or_return value!"); + ::std::mem::drop(v); + assert_eq!(tmp(), $ret, "should use or_return value!"); + } + } +} + +#[derive(PartialEq, Debug)] +struct Foo(i32); + +test_default!(test_clone_macro_default_return_newtype, Foo(0), Foo(1)); + +#[derive(PartialEq, Debug)] +struct Bar { + x: i32, +} + +test_default!( + test_clone_macro_default_return_struct, + Bar { x: 0 }, + Bar { x: 1 } +); + +#[derive(PartialEq, Debug)] +enum Enum { + A, + B(i32), + C { x: i32 }, +} +test_default!( + test_clone_macro_default_return_enum_unit, + Enum::A, + Enum::B(0) +); +test_default!( + test_clone_macro_default_return_enum_tuple, + Enum::B(0), + Enum::A +); +test_default!( + test_clone_macro_default_return_enum_struct, + Enum::C { x: 0 }, + Enum::A +); +test_default!( + test_clone_macro_default_return_expr, + { + let x = 12; + x + 2 + }, + 19 +); +// This one is simply to check that we wait for the comma for the default-return value. +test_default!( + test_clone_macro_default_return_bool, + Enum::A == Enum::B(0) || false, + true +); + +#[test] +#[clone_block] +fn test_clone_macro_body() { + let v = Arc::new(Mutex::new(0)); + + let closure = #[default_return] + move |#[weak] v| { + std::thread::spawn(move || { + let mut lock = v.lock().expect("failed to lock"); + for _ in 1..=10 { + *lock += 1; + } + }) + .join() + .expect("thread::spawn failed"); + }; + closure(); + assert_eq!(10, *v.lock().expect("failed to lock")); +} + +#[test] +#[clone_block] +fn test_clone_macro_async_kinds() { + let v = Rc::new(RefCell::new(1)); + + let closure = move |#[weak(or_return)] v| async move { + *v.borrow_mut() += 1; + }; + block_on(closure()); + assert_eq!(*v.borrow(), 2); + block_on( + #[clone(weak(or_return) v)] + async move { + *v.borrow_mut() += 1; + }, + ); + assert_eq!(*v.borrow(), 3); +} + +#[test] +#[clone_block] +fn test_clone_attr() { + let func = { + let a = Rc::new(Cell::new(1)); + let b = Rc::new(Cell::new(2)); + let c = Rc::new(Cell::new(3)); + let func = #[clone(strong a, weak b, weak(c or_return 2) d, default_return(1))] + move |x| a.get() + b.get() + d.get() + x; + assert_eq!(func(4), 10); + func + }; + assert_eq!(func(500), 1); +} diff --git a/glib-macros/tests/clone_block_closures.rs b/glib-macros/tests/clone_block_closures.rs new file mode 100644 index 000000000000..ca891e89b88c --- /dev/null +++ b/glib-macros/tests/clone_block_closures.rs @@ -0,0 +1,236 @@ +use glib::object::ObjectExt; + +#[test] +#[glib::clone_block] +fn closure() { + let empty = #[closure] + || {}; + empty.invoke::<()>(&[]); + + let no_arg = #[closure] + || 2i32; + assert_eq!(no_arg.invoke::(&[]), 2); + + let add_1 = #[closure] + |x: i32| x + 1; + assert_eq!(add_1.invoke::(&[&3i32]), 4); + + let concat_str = #[closure] + |s: &str| s.to_owned() + " World"; + assert_eq!(concat_str.invoke::(&[&"Hello"]), "Hello World"); + + let ignored_arg = #[closure] + |x: i32, _, z: i32| x + z; + assert_eq!(ignored_arg.invoke::(&[&1i32, &2i32, &3i32]), 4); + + let weak_test = { + let obj = glib::Object::new::(); + + assert_eq!(obj.ref_count(), 1); + let weak_test = move |#[watch] obj| obj.ref_count(); + assert_eq!(obj.ref_count(), 1); + assert_eq!(weak_test.invoke::(&[]), 2); + assert_eq!(obj.ref_count(), 1); + + weak_test + }; + weak_test.invoke::<()>(&[]); + + { + trait TestExt { + fn ref_count_in_closure(&self) -> u32; + } + + impl TestExt for glib::Object { + fn ref_count_in_closure(&self) -> u32 { + let closure = move |#[watch(self)] obj| obj.ref_count(); + closure.invoke::(&[]) + } + } + + let obj = glib::Object::new::(); + assert_eq!(obj.ref_count_in_closure(), 2); + } + + { + struct A { + obj: glib::Object, + } + + impl A { + fn ref_count_in_closure(&self) -> u32 { + let closure = move |#[watch(self.obj)] obj| obj.ref_count(); + closure.invoke::(&[]) + } + } + + let a = A { + obj: glib::Object::new(), + }; + assert_eq!(a.ref_count_in_closure(), 2); + } + + let strong_test = { + let obj = glib::Object::new::(); + + let strong_test = #[closure(local)] + move |#[strong] obj| obj.ref_count(); + assert_eq!(strong_test.invoke::(&[]), 2); + + strong_test + }; + assert_eq!(strong_test.invoke::(&[]), 1); + + let weak_none_test = { + let obj = glib::Object::new::(); + + let weak_none_test = #[closure(local)] + move |#[weak] obj| obj.map(|o| o.ref_count()).unwrap_or_default(); + assert_eq!(weak_none_test.invoke::(&[]), 2); + + weak_none_test + }; + assert_eq!(weak_none_test.invoke::(&[]), 0); + + { + let obj1 = glib::Object::new::(); + let obj2 = glib::Object::new::(); + + let obj_arg_test = #[closure] + |a: glib::Object, b: glib::Object| a.ref_count() + b.ref_count(); + let rc = obj_arg_test.invoke::(&[&obj1, &obj2]); + assert_eq!(rc, 6); + + let alias_test = #[closure(local)] + move |#[strong(obj1)] a, #[strong] obj2| a.ref_count() + obj2.ref_count(); + assert_eq!(alias_test.invoke::(&[]), 4); + } + + { + struct A { + a: glib::Object, + } + + let a = glib::Object::new(); + let a_struct = A { a }; + let struct_test = #[closure(local)] + move |#[strong(a_struct.a)] a| a.ref_count(); + assert_eq!(struct_test.invoke::(&[]), 2); + } + + { + use glib::prelude::*; + use glib::subclass::prelude::*; + + #[derive(Default)] + pub struct FooPrivate {} + + #[glib::object_subclass] + impl ObjectSubclass for FooPrivate { + const NAME: &'static str = "MyFoo2"; + type Type = Foo; + } + + impl ObjectImpl for FooPrivate {} + + glib::wrapper! { + pub struct Foo(ObjectSubclass); + } + + impl Foo { + fn my_ref_count(&self) -> u32 { + self.ref_count() + } + } + + let cast_test = { + let f = glib::Object::new::(); + + assert_eq!(f.my_ref_count(), 1); + let cast_test = move |#[watch] f| f.my_ref_count(); + assert_eq!(f.my_ref_count(), 1); + assert_eq!(cast_test.invoke::(&[]), 2); + assert_eq!(f.my_ref_count(), 1); + + let f_ref = &f; + let _ = move |#[watch] f_ref| f_ref.my_ref_count(); + + cast_test + }; + cast_test.invoke::<()>(&[]); + } + + let sum = #[closure] + |x: i32, #[rest] rest: &[glib::Value]| -> i32 { + x + rest.iter().map(|v| v.get::().unwrap()).sum::() + }; + assert_eq!(sum.invoke::(&[&2i32]), 2i32); + assert_eq!(sum.invoke::(&[&2i32, &3i32]), 5i32); + assert_eq!(sum.invoke::(&[&10i32, &100i32, &1000i32]), 1110i32); +} + +glib::wrapper! { + struct SendObject(ObjectSubclass); +} + +mod send { + use glib::prelude::*; + use glib::subclass::prelude::*; + use glib::{ParamSpec, Value}; + + #[derive(glib::Properties, Default)] + #[properties(wrapper_type = super::SendObject)] + pub(super) struct SendObject { + #[property(get, set)] + value: std::sync::atomic::AtomicU64, + } + + #[glib::object_subclass] + impl ObjectSubclass for SendObject { + const NAME: &'static str = "SendObject"; + type Type = super::SendObject; + } + + impl ObjectImpl for SendObject { + fn properties() -> &'static [ParamSpec] { + Self::derived_properties() + } + fn set_property(&self, _id: usize, _value: &Value, _pspec: &ParamSpec) { + Self::derived_set_property(self, _id, _value, _pspec) + } + fn property(&self, id: usize, _pspec: &ParamSpec) -> Value { + Self::derived_property(self, id, _pspec) + } + } +} + +#[test] +#[glib::clone_block] +fn async_closure() { + use futures_util::StreamExt; + + let ctx = glib::MainContext::default(); + let (tx, mut rx) = futures_channel::mpsc::unbounded::(); + let obj = glib::Object::new::(); + let get_obj = || &obj; + let closure = #[closure] + move |s: &str, + #[strong] tx, + #[weak(obj or_panic)] _obj, + #[watch(*get_obj())] obj2| async move { + glib::timeout_future_seconds(0).await; + let v = s.parse().unwrap(); + tx.unbounded_send(v).unwrap(); + obj2.set_value(v); + }; + + tx.unbounded_send(60).unwrap(); + assert_eq!(obj.value(), 0); + closure.invoke::<()>(&[&"70"]); + assert_eq!(obj.value(), 0); + assert_eq!(ctx.block_on(rx.next()), Some(60)); + assert_eq!(obj.value(), 0); + assert_eq!(ctx.block_on(rx.next()), Some(70)); + assert_eq!(obj.value(), 70); + assert!(rx.try_next().is_err()); +} diff --git a/glib/src/lib.rs b/glib/src/lib.rs index 5009fadd3f4c..e74282633dd9 100644 --- a/glib/src/lib.rs +++ b/glib/src/lib.rs @@ -10,8 +10,8 @@ pub use ffi; #[doc(hidden)] pub use glib_macros::cstr_bytes; pub use glib_macros::{ - clone, closure, closure_local, flags, object_interface, object_subclass, Boxed, Downgrade, - Enum, ErrorDomain, Properties, SharedBoxed, Variant, + clone, clone_block, closure, closure_local, flags, object_interface, object_subclass, Boxed, + Downgrade, Enum, ErrorDomain, Properties, SharedBoxed, Variant, }; pub use gobject_ffi; #[doc(hidden)] From cc620d4f9e3fabf78394ed184e850c144b3bcb6f Mon Sep 17 00:00:00 2001 From: Jason Francis Date: Tue, 14 Feb 2023 11:24:42 -0500 Subject: [PATCH 3/5] glib-macros: prototype porting wrapper to a proc macro --- glib-macros/src/lib.rs | 11 + glib-macros/src/wrapper_attribute.rs | 250 ++++++++++ glib-macros/src/wrapper_attribute/boxed.rs | 502 +++++++++++++++++++++ glib/src/lib.rs | 4 +- 4 files changed, 765 insertions(+), 2 deletions(-) create mode 100644 glib-macros/src/wrapper_attribute.rs create mode 100644 glib-macros/src/wrapper_attribute/boxed.rs diff --git a/glib-macros/src/lib.rs b/glib-macros/src/lib.rs index 8f3bc2f04269..6879796fa737 100644 --- a/glib-macros/src/lib.rs +++ b/glib-macros/src/lib.rs @@ -13,6 +13,7 @@ mod object_subclass_attribute; mod properties; mod shared_boxed_derive; mod variant_derive; +mod wrapper_attribute; mod utils; @@ -972,3 +973,13 @@ pub fn clone_block(_attr: TokenStream, item: TokenStream) -> TokenStream { clone_block_attribute::impl_clone_block(&mut item, &syn::parse_quote! { #glib }, &errors); errors.output_with(item).into() } + +#[proc_macro_attribute] +#[proc_macro_error] +pub fn wrapper(attr: TokenStream, item: TokenStream) -> TokenStream { + let input = parse_macro_input!(item as syn::ItemStruct); + match wrapper_attribute::impl_wrapper(attr.into(), input) { + Ok(gen) => gen.into(), + Err(e) => e.into_compile_error().into(), + } +} diff --git a/glib-macros/src/wrapper_attribute.rs b/glib-macros/src/wrapper_attribute.rs new file mode 100644 index 000000000000..6ad578bf1afd --- /dev/null +++ b/glib-macros/src/wrapper_attribute.rs @@ -0,0 +1,250 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use std::collections::HashSet; + +use proc_macro2::TokenStream; +use quote::ToTokens; +use syn::spanned::Spanned; + +mod boxed; +#[allow(dead_code)] +mod boxed_inline { + #[derive(deluxe::ParseMetaItem)] + pub struct BoxedInline { + copy: Option, + free: Option, + init: Option, + copy_into: Option, + clear: Option, + r#type: Option, + #[deluxe(flatten)] + args: super::WrapperArgs, + } +} +#[allow(dead_code)] +mod shared { + #[derive(deluxe::ParseMetaItem)] + pub struct Shared { + r#ref: syn::Expr, + unref: syn::Expr, + r#type: Option, + #[deluxe(flatten)] + args: super::WrapperArgs, + } +} +#[allow(dead_code)] +mod object { + #[derive(deluxe::ParseMetaItem)] + pub struct Object { + #[deluxe(append)] + extends: Vec, + #[deluxe(append)] + implements: Vec, + r#type: syn::Expr, + #[deluxe(flatten)] + args: super::WrapperArgs, + } +} +#[allow(dead_code)] +mod object_subclass { + #[derive(deluxe::ParseMetaItem)] + pub struct ObjectSubclass { + #[deluxe(append)] + extends: Vec, + #[deluxe(append)] + implements: Vec, + #[deluxe(flatten)] + args: super::WrapperArgs, + } +} +#[allow(dead_code)] +mod interface { + #[derive(deluxe::ParseMetaItem)] + pub struct Interface { + #[deluxe(append)] + requires: Vec, + r#type: syn::Expr, + #[deluxe(flatten)] + args: super::WrapperArgs, + } +} +#[allow(dead_code)] +mod object_interface { + #[derive(deluxe::ParseMetaItem)] + pub struct ObjectInterface { + #[deluxe(append)] + requires: Vec, + #[deluxe(flatten)] + args: super::WrapperArgs, + } +} + +#[derive(deluxe::ParseMetaItem)] +struct WrapperArgs { + #[deluxe( + append, + rename = skip_trait, + map = |s: HashSet| s.into_iter().map(|s| s.to_string()).collect(), + )] + skipped_traits: HashSet, +} + +enum Member<'a> { + Named(&'a syn::Ident), + Unnamed(syn::Index), +} + +impl<'a> Member<'a> { + fn from_fields(fields: &'a syn::Fields) -> impl Iterator> { + fields + .iter() + .enumerate() + .map(|(index, field)| match &field.ident { + Some(ident) => Member::Named(ident), + None => Member::Unnamed(syn::Index { + index: index as u32, + span: field.ty.span(), + }), + }) + } +} + +impl<'a> ToTokens for Member<'a> { + #[inline] + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + Self::Named(ident) => ident.to_tokens(tokens), + Self::Unnamed(index) => index.to_tokens(tokens), + } + } +} + +fn construct_stmt( + fields: &syn::Fields, + first_func: impl FnOnce(Member<'_>) -> TokenStream, + mut rest_func: impl FnMut(Member<'_>) -> TokenStream, +) -> TokenStream { + let mut first_func = Some(first_func); + let members = fields.iter().enumerate().map(move |(index, f)| { + let member = match &f.ident { + Some(ident) => Member::Named(ident), + None => Member::Unnamed(syn::Index { + index: index as u32, + span: f.ty.span(), + }), + }; + let expr: TokenStream = if index == 0 { + (first_func.take().unwrap())(member) + } else { + rest_func(member) + }; + match &f.ident { + Some(ident) => quote::quote_spanned! { f.ty.span() => #ident: #expr }, + None => expr, + } + }); + match fields { + syn::Fields::Named(_) => quote::quote! { Self { #(#members),* } }, + syn::Fields::Unnamed(_) => quote::quote! { Self(#(#members),*) }, + _ => unreachable!(), + } +} + +#[inline] +fn unique_lifetime(name: &str, generics: &syn::Generics) -> syn::Lifetime { + let mut ident = String::from(name); + while generics.lifetimes().any(|l| l.lifetime.ident == ident) { + ident.push('_'); + } + ident.insert(0, '\''); + syn::Lifetime::new(&ident, proc_macro2::Span::mixed_site()) +} + +#[inline] +fn insert_lifetime(name: &str, generics: &mut syn::Generics) -> syn::Lifetime { + let lt = unique_lifetime(name, generics); + generics.params.insert(0, syn::parse_quote! { #lt }); + lt +} + +fn get_first_field_type_param(fields: &syn::Fields, type_index: usize) -> syn::Result<&syn::Type> { + fields + .iter() + .next() + .ok_or_else(|| fields.span()) + .and_then(|f| match &f.ty { + syn::Type::Path(tp) => tp + .path + .segments + .last() + .ok_or_else(|| tp.span()) + .and_then(|s| match &s.arguments { + syn::PathArguments::AngleBracketed(ga) => ga + .args + .iter() + .nth(type_index) + .ok_or_else(|| ga.span()) + .and_then(|a| match a { + syn::GenericArgument::Type(t) => Ok(t), + t => Err(t.span()), + }), + a => Err(a.span()), + }), + ty => Err(ty.span()), + }) + .map_err(|span| { + syn::Error::new( + span, + format_args!("First field missing type argument {type_index}"), + ) + }) +} + +#[inline] +fn get_type_name(ty: &syn::Type) -> Option<&syn::Ident> { + match ty { + syn::Type::Path(tp) => tp.path.segments.last().map(|s| &s.ident), + _ => None, + } +} + +fn add_repr_transparent(item: &mut syn::ItemStruct) { + if item.fields.iter().skip(1).all(|f| { + get_type_name(&f.ty) + .map(|i| i == "PhantomData") + .unwrap_or(false) + }) { + item.attrs.extend( + syn::parse::Parser::parse_str(syn::Attribute::parse_outer, "#[repr(transparent)]") + .unwrap(), + ); + } +} + +pub fn impl_wrapper(attr: TokenStream, mut item: syn::ItemStruct) -> syn::Result { + let first_field = match item.fields.iter().next() { + Some(f) => f, + None => { + return Err(syn::Error::new_spanned( + item, + "Wrapper struct must have at least one field", + )); + } + }; + let type_name = match get_type_name(&first_field.ty) { + Some(n) => n, + None => { + return Err(syn::Error::new_spanned( + item, + "First field must be a type path", + )) + } + }; + + let mut tokens = match type_name.to_string().as_str() { + "Boxed" => deluxe::parse2::(attr)?.into_token_stream(&mut item)?, + _ => return Err(syn::Error::new_spanned(type_name, "Unknown wrapper type")), + }; + item.to_tokens(&mut tokens); + Ok(tokens) +} diff --git a/glib-macros/src/wrapper_attribute/boxed.rs b/glib-macros/src/wrapper_attribute/boxed.rs new file mode 100644 index 000000000000..91ea55617134 --- /dev/null +++ b/glib-macros/src/wrapper_attribute/boxed.rs @@ -0,0 +1,502 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use proc_macro2::TokenStream; +use quote::{quote, quote_spanned}; +use syn::spanned::Spanned; + +#[derive(deluxe::ParseMetaItem)] +pub struct Boxed { + copy: syn::Expr, + free: syn::Expr, + r#type: Option, + #[deluxe(flatten)] + args: super::WrapperArgs, +} + +impl Boxed { + pub fn into_token_stream(self, item: &mut syn::ItemStruct) -> syn::Result { + let crate_ = crate::utils::crate_ident_new(); + let ffi_name = super::get_first_field_type_param(&item.fields, 0)?.clone(); + if let Ok(second) = super::get_first_field_type_param(&item.fields, 1) { + return Err(syn::Error::new_spanned(second, "unexpected tokens")); + } + super::add_repr_transparent(item); + item.fields.iter_mut().next().unwrap().ty = syn::parse_quote! { + #crate_::boxed::Boxed<#ffi_name, Self> + }; + + let Boxed { + copy, + free, + r#type, + args: super::WrapperArgs { skipped_traits }, + } = self; + + let struct_ident = &item.ident; + let (impl_generics, type_generics, where_clause) = item.generics.split_for_impl(); + + let mut lt_generics = item.generics.clone(); + let lt = super::insert_lifetime("a", &mut lt_generics); + let (lt_impl_generics, _, _) = lt_generics.split_for_impl(); + + let mut tokens = quote! { + impl #impl_generics #struct_ident #type_generics #where_clause { + #[doc = "Return the inner pointer to the underlying C value."] + #[inline] + pub fn as_ptr(&self) -> *mut #ffi_name { + unsafe { *(self as *const Self as *const *const #ffi_name) as *mut #ffi_name } + } + + #[doc = "Borrows the underlying C value."] + #[inline] + pub unsafe fn from_glib_ptr_borrow<#lt>(ptr: *const *const #ffi_name) -> &#lt Self { + &*(ptr as *const Self) + } + + #[doc = "Borrows the underlying C value mutably."] + #[inline] + pub unsafe fn from_glib_ptr_borrow_mut<#lt>(ptr: *mut *mut #ffi_name) -> &#lt mut Self { + &mut *(ptr as *mut Self) + } + } + }; + + let inner = super::Member::from_fields(&item.fields).next().unwrap(); + + if !skipped_traits.contains("Clone") { + let stmt = super::construct_stmt( + &item.fields, + |m| quote_spanned! { m.span() => ::std::clone::Clone::clone(&self.#m) }, + |m| quote_spanned! { m.span() => ::std::clone::Clone::clone(&self.#m) }, + ); + tokens.extend(quote! { + impl #impl_generics ::std::clone::Clone for #struct_ident #type_generics #where_clause { + #[inline] + fn clone(&self) -> Self { + #stmt + } + } + }); + } + if !skipped_traits.contains("GlibPtrDefault") { + tokens.extend(quote! { + #[doc(hidden)] + impl #impl_generics #crate_::translate::GlibPtrDefault for #struct_ident #type_generics #where_clause { + type GlibType = *mut #ffi_name; + } + }); + } + if !skipped_traits.contains("TransparentPtrType") { + tokens.extend(quote! { + #[doc(hidden)] + unsafe impl #impl_generics #crate_::translate::TransparentPtrType for #struct_ident #type_generics #where_clause {} + }); + } + if !skipped_traits.contains("ToGlibPtr") { + tokens.extend(quote! { + #[doc(hidden)] + impl #lt_impl_generics #crate_::translate::ToGlibPtr<#lt, *const #ffi_name> for #struct_ident #type_generics #where_clause { + type Storage = ::std::marker::PhantomData<&#lt #crate_::boxed::Boxed<#ffi_name, Self>>; + + #[inline] + fn to_glib_none(&#lt self) -> #crate_::translate::Stash<#lt, *const #ffi_name, Self> { + let stash = #crate_::translate::ToGlibPtr::to_glib_none(&self.#inner); + #crate_::translate::Stash(stash.0, stash.1) + } + #[inline] + fn to_glib_full(&self) -> *const #ffi_name { + #crate_::translate::ToGlibPtr::to_glib_full(&self.#inner) + } + } + #[doc(hidden)] + impl #lt_impl_generics #crate_::translate::ToGlibPtr<#lt, *mut #ffi_name> for #struct_ident #type_generics #where_clause { + type Storage = ::std::marker::PhantomData<&#lt #crate_::boxed::Boxed<#ffi_name, Self>>; + + #[inline] + fn to_glib_none(&#lt self) -> #crate_::translate::Stash<#lt, *mut #ffi_name, Self> { + let stash = #crate_::translate::ToGlibPtr::to_glib_none(&self.#inner); + #crate_::translate::Stash(stash.0 as *mut _, stash.1) + } + #[inline] + fn to_glib_full(&self) -> *mut #ffi_name { + #crate_::translate::ToGlibPtr::to_glib_full(&self.#inner) as *mut _ + } + } + }); + } + if !skipped_traits.contains("ToGlibPtrMut") { + tokens.extend(quote! { + #[doc(hidden)] + impl #lt_impl_generics #crate_::translate::ToGlibPtrMut<#lt, *mut #ffi_name> for #struct_ident #type_generics #where_clause { + type Storage = ::std::marker::PhantomData<&#lt mut #crate_::boxed::Boxed<#ffi_name, Self>>; + + #[inline] + fn to_glib_none_mut(&#lt mut self) -> #crate_::translate::StashMut<#lt, *mut #ffi_name, Self> { + let stash = #crate_::translate::ToGlibPtrMut::to_glib_none_mut(&mut self.#inner); + #crate_::translate::StashMut(stash.0, stash.1) + } + } + }); + } + if !skipped_traits.contains("ToGlibContainerFromSlice") { + tokens.extend(quote! { + #[doc(hidden)] + impl #lt_impl_generics #crate_::translate::ToGlibContainerFromSlice<#lt, *mut *const #ffi_name> for #struct_ident #type_generics #where_clause { + type Storage = (::std::marker::PhantomData<&#lt [Self]>, ::std::option::Option<::std::vec::Vec<*const #ffi_name>>); + + fn to_glib_none_from_slice(t: &#lt [Self]) -> (*mut *const #ffi_name, Self::Storage) { + let mut v_ptr = ::std::vec::Vec::with_capacity(t.len() + 1); + unsafe { + let ptr = v_ptr.as_mut_ptr(); + ::std::ptr::copy_nonoverlapping(t.as_ptr() as *mut *const #ffi_name, ptr, t.len()); + ::std::ptr::write(ptr.add(t.len()), ::std::ptr::null_mut()); + v_ptr.set_len(t.len() + 1); + } + + (v_ptr.as_ptr() as *mut *const #ffi_name, (::std::marker::PhantomData, Some(v_ptr))) + } + fn to_glib_container_from_slice(t: &#lt [Self]) -> (*mut *const #ffi_name, Self::Storage) { + let v_ptr = unsafe { + let v_ptr = #crate_::ffi::g_malloc(::std::mem::size_of::<*const #ffi_name>() * (t.len() + 1)) as *mut *const #ffi_name; + + ::std::ptr::copy_nonoverlapping(t.as_ptr() as *mut *const #ffi_name, v_ptr, t.len()); + ::std::ptr::write(v_ptr.add(t.len()), ::std::ptr::null_mut()); + + v_ptr + }; + + (v_ptr, (::std::marker::PhantomData, None)) + } + fn to_glib_full_from_slice(t: &[Self]) -> *mut *const #ffi_name { + unsafe { + let v_ptr = #crate_::ffi::g_malloc(::std::mem::size_of::<*const #ffi_name>() * (t.len() + 1)) as *mut *const #ffi_name; + + for (i, s) in t.iter().enumerate() { + ::std::ptr::write(v_ptr.add(i), #crate_::translate::ToGlibPtr::to_glib_full(s)); + } + ::std::ptr::write(v_ptr.add(t.len()), ::std::ptr::null_mut()); + + v_ptr + } + } + } + #[doc(hidden)] + impl #lt_impl_generics #crate_::translate::ToGlibContainerFromSlice<#lt, *const *const #ffi_name> for #struct_ident #type_generics #where_clause { + type Storage = (::std::marker::PhantomData<&#lt [Self]>, ::std::option::Option<::std::vec::Vec<*const #ffi_name>>); + + #[inline] + fn to_glib_none_from_slice(t: &#lt [Self]) -> (*const *const #ffi_name, Self::Storage) { + let (ptr, stash) = #crate_::translate::ToGlibContainerFromSlice::<#lt, *mut *const #ffi_name>::to_glib_none_from_slice(t); + (ptr as *const *const #ffi_name, stash) + } + #[inline] + fn to_glib_container_from_slice(_: &#lt [Self]) -> (*const *const #ffi_name, Self::Storage) { + // Can't have consumer free a *const pointer + ::std::unimplemented!() + } + #[inline] + fn to_glib_full_from_slice(_: &[Self]) -> *const *const #ffi_name { + // Can't have consumer free a *const pointer + ::std::unimplemented!() + } + } + }); + } + if !skipped_traits.contains("FromGlibPtrNone") { + let stmt = super::construct_stmt( + &item.fields, + |m| quote_spanned! { m.span() => #crate_::translate::from_glib_none(ptr) }, + |m| quote_spanned! { m.span() => ::std::default::Default::default() }, + ); + tokens.extend(quote! { + #[doc(hidden)] + impl #impl_generics #crate_::translate::FromGlibPtrNone<*mut #ffi_name> for #struct_ident #type_generics #where_clause { + #[inline] + unsafe fn from_glib_none(ptr: *mut #ffi_name) -> Self { + #stmt + } + } + #[doc(hidden)] + impl #impl_generics #crate_::translate::FromGlibPtrNone<*const #ffi_name> for #struct_ident #type_generics #where_clause { + #[inline] + unsafe fn from_glib_none(ptr: *const #ffi_name) -> Self { + #stmt + } + } + }); + } + if !skipped_traits.contains("FromGlibPtrFull") { + let stmt = super::construct_stmt( + &item.fields, + |m| quote_spanned! { m.span() => #crate_::translate::from_glib_full(ptr) }, + |m| quote_spanned! { m.span() => ::std::default::Default::default() }, + ); + tokens.extend(quote! { + #[doc(hidden)] + impl #impl_generics #crate_::translate::FromGlibPtrFull<*mut #ffi_name> for #struct_ident #type_generics #where_clause { + #[inline] + unsafe fn from_glib_full(ptr: *mut #ffi_name) -> Self { + #stmt + } + } + #[doc(hidden)] + impl #impl_generics #crate_::translate::FromGlibPtrFull<*const #ffi_name> for #struct_ident #type_generics #where_clause { + #[inline] + unsafe fn from_glib_full(ptr: *const #ffi_name) -> Self { + #stmt + } + } + }); + } + if !skipped_traits.contains("FromGlibPtrBorrow") { + let stmt = super::construct_stmt( + &item.fields, + |m| quote_spanned! { m.span() => #crate_::translate::from_glib_borrow::<_, #crate_::boxed::Boxed<_, _>>(ptr).into_inner() }, + |m| quote_spanned! { m.span() => ::std::default::Default::default() }, + ); + tokens.extend(quote! { + #[doc(hidden)] + impl #impl_generics #crate_::translate::FromGlibPtrBorrow<*mut #ffi_name> for #struct_ident #type_generics #where_clause { + #[inline] + unsafe fn from_glib_borrow(ptr: *mut #ffi_name) -> #crate_::translate::Borrowed { + #crate_::translate::Borrowed::new( + #stmt + ) + } + } + #[doc(hidden)] + impl #impl_generics #crate_::translate::FromGlibPtrBorrow<*const #ffi_name> for #struct_ident #type_generics #where_clause { + #[inline] + unsafe fn from_glib_borrow(ptr: *const #ffi_name) -> #crate_::translate::Borrowed { + #crate_::translate::from_glib_borrow::<_, Self>(ptr as *mut #ffi_name) + } + } + }); + } + if !skipped_traits.contains("FromGlibContainerAsVec") { + tokens.extend(quote! { + #[doc(hidden)] + impl #impl_generics #crate_::translate::FromGlibContainerAsVec<*mut #ffi_name, *mut *mut #ffi_name> for #struct_ident #type_generics #where_clause { + unsafe fn from_glib_none_num_as_vec(ptr: *mut *mut #ffi_name, num: usize) -> ::std::vec::Vec { + if num == 0 || ptr.is_null() { + return ::std::vec::Vec::new(); + } + + let mut res = ::std::vec::Vec::::with_capacity(num); + let res_ptr = res.as_mut_ptr(); + for i in 0..num { + ::std::ptr::write(res_ptr.add(i), #crate_::translate::from_glib_none(::std::ptr::read(ptr.add(i)))); + } + res.set_len(num); + res + } + unsafe fn from_glib_container_num_as_vec(ptr: *mut *mut #ffi_name, num: usize) -> ::std::vec::Vec { + let res = #crate_::translate::FromGlibContainerAsVec::from_glib_none_num_as_vec(ptr, num); + #crate_::ffi::g_free(ptr as *mut _); + res + } + unsafe fn from_glib_full_num_as_vec(ptr: *mut *mut #ffi_name, num: usize) -> ::std::vec::Vec { + if num == 0 || ptr.is_null() { + #crate_::ffi::g_free(ptr as *mut _); + return ::std::vec::Vec::new(); + } + + let mut res = ::std::vec::Vec::with_capacity(num); + let res_ptr = res.as_mut_ptr(); + ::std::ptr::copy_nonoverlapping(ptr as *mut Self, res_ptr, num); + res.set_len(num); + #crate_::ffi::g_free(ptr as *mut _); + res + } + } + }); + } + if !skipped_traits.contains("FromGlibPtrArrayContainerAsVec") { + tokens.extend(quote! { + #[doc(hidden)] + impl #impl_generics #crate_::translate::FromGlibPtrArrayContainerAsVec<*mut #ffi_name, *mut *mut #ffi_name> for #struct_ident #type_generics #where_clause { + #[inline] + unsafe fn from_glib_none_as_vec(ptr: *mut *mut #ffi_name) -> ::std::vec::Vec { + #crate_::translate::FromGlibContainerAsVec::from_glib_none_num_as_vec(ptr, #crate_::translate::c_ptr_array_len(ptr)) + } + #[inline] + unsafe fn from_glib_container_as_vec(ptr: *mut *mut #ffi_name) -> ::std::vec::Vec { + #crate_::translate::FromGlibContainerAsVec::from_glib_container_num_as_vec(ptr, #crate_::translate::c_ptr_array_len(ptr)) + } + #[inline] + unsafe fn from_glib_full_as_vec(ptr: *mut *mut #ffi_name) -> ::std::vec::Vec { + #crate_::translate::FromGlibContainerAsVec::from_glib_full_num_as_vec(ptr, #crate_::translate::c_ptr_array_len(ptr)) + } + } + }); + } + if !skipped_traits.contains("IntoGlibPtr") { + tokens.extend(quote! { + #[doc(hidden)] + impl #impl_generics #crate_::translate::IntoGlibPtr<*mut #ffi_name> for #struct_ident #type_generics #where_clause { + #[inline] + unsafe fn into_glib_ptr(self) -> *mut #ffi_name { + let s = ::std::mem::ManuallyDrop::new(self); + #crate_::translate::ToGlibPtr::<*const #ffi_name>::to_glib_none(&*s).0 as *mut _ + } + } + #[doc(hidden)] + impl #impl_generics #crate_::translate::IntoGlibPtr<*const #ffi_name> for #struct_ident #type_generics #where_clause { + #[inline] + unsafe fn into_glib_ptr(self) -> *const #ffi_name { + let s = ::std::mem::ManuallyDrop::new(self); + #crate_::translate::ToGlibPtr::<*const #ffi_name>::to_glib_none(&*s).0 as *const _ + } + } + }); + } + if let Some(get_type_expr) = r#type { + if !skipped_traits.contains("StaticType") { + tokens.extend(quote! { + impl #impl_generics #crate_::types::StaticType for #struct_ident #type_generics #where_clause { + #[inline] + fn static_type() -> #crate_::types::Type { + #[allow(unused_unsafe)] + unsafe { #crate_::translate::from_glib((#get_type_expr)()) } + } + } + }); + } + if !skipped_traits.contains("ValueType") { + tokens.extend(quote! { + #[doc(hidden)] + impl #impl_generics #crate_::value::ValueType for #struct_ident #type_generics #where_clause { + type Type = Self; + } + }); + } + if !skipped_traits.contains("ValueTypeOptional") { + tokens.extend(quote! { + #[doc(hidden)] + impl #impl_generics #crate_::value::ValueTypeOptional for #struct_ident #type_generics #where_clause {} + }); + } + if !skipped_traits.contains("FromValue") { + tokens.extend(quote! { + #[doc(hidden)] + unsafe impl #lt_impl_generics #crate_::value::FromValue<#lt> for #struct_ident #type_generics #where_clause { + type Checker = #crate_::value::GenericValueTypeOrNoneChecker; + + #[inline] + unsafe fn from_value(value: &#lt #crate_::Value) -> Self { + let ptr = #crate_::gobject_ffi::g_value_dup_boxed(#crate_::translate::ToGlibPtr::to_glib_none(value).0); + debug_assert!(!ptr.is_null()); + >::from_glib_full(ptr as *mut #ffi_name) + } + } + + #[doc(hidden)] + unsafe impl #lt_impl_generics #crate_::value::FromValue<#lt> for &#lt #struct_ident #type_generics #where_clause { + type Checker = #crate_::value::GenericValueTypeOrNoneChecker; + + #[inline] + unsafe fn from_value(value: &#lt #crate_::Value) -> Self { + debug_assert_eq!(::std::mem::size_of::(), ::std::mem::size_of::<#crate_::ffi::gpointer>()); + let value = &*(value as *const #crate_::Value as *const #crate_::gobject_ffi::GValue); + debug_assert!(!value.data[0].v_pointer.is_null()); + <#struct_ident #type_generics>::from_glib_ptr_borrow(&value.data[0].v_pointer as *const #crate_::ffi::gpointer as *const *const #ffi_name) + } + } + }); + } + if !skipped_traits.contains("ToValue") { + tokens.extend(quote! { + #[doc(hidden)] + impl #impl_generics #crate_::value::ToValue for #struct_ident #type_generics #where_clause { + #[inline] + fn to_value(&self) -> #crate_::Value { + unsafe { + let mut value = #crate_::Value::from_type_unchecked(::static_type()); + #crate_::gobject_ffi::g_value_take_boxed( + #crate_::translate::ToGlibPtrMut::to_glib_none_mut(&mut value).0, + #crate_::translate::ToGlibPtr::<*const #ffi_name>::to_glib_full(self) as *mut _, + ); + value + } + } + + #[inline] + fn value_type(&self) -> #crate_::Type { + ::static_type() + } + } + }); + } + if !skipped_traits.contains("From") { + tokens.extend(quote! { + #[doc(hidden)] + impl #impl_generics ::std::convert::From<#struct_ident #type_generics> for #crate_::Value #where_clause { + #[inline] + fn from(o: #struct_ident #type_generics) -> Self { + unsafe { + let mut value = #crate_::Value::from_type_unchecked( + <#struct_ident #type_generics as #crate_::StaticType>::static_type(), + ); + #crate_::gobject_ffi::g_value_take_boxed( + #crate_::translate::ToGlibPtrMut::to_glib_none_mut(&mut value).0, + #crate_::translate::IntoGlibPtr::<*mut #ffi_name>::into_glib_ptr(o) as *mut _, + ); + value + } + } + } + }); + } + if !skipped_traits.contains("ToValueOptional") { + tokens.extend(quote! { + #[doc(hidden)] + impl #impl_generics #crate_::value::ToValueOptional for #struct_ident #type_generics #where_clause { + #[inline] + fn to_value_optional(s: Option<&Self>) -> #crate_::Value { + let mut value = #crate_::Value::for_value_type::(); + unsafe { + #crate_::gobject_ffi::g_value_take_boxed( + #crate_::translate::ToGlibPtrMut::to_glib_none_mut(&mut value).0, + #crate_::translate::ToGlibPtr::<*const #ffi_name>::to_glib_full(&s) as *mut _, + ); + } + + value + } + } + }); + } + if !skipped_traits.contains("HasParamSpec") { + tokens.extend(quote! { + impl #impl_generics #crate_::HasParamSpec for #struct_ident #type_generics #where_clause { + type ParamSpec = #crate_::ParamSpecBoxed; + type SetValue = Self; + type BuilderFn = fn(&str) -> #crate_::ParamSpecBoxedBuilder; + + fn param_spec_builder() -> Self::BuilderFn { + |name| Self::ParamSpec::builder(name) + } + } + }); + } + } + if !skipped_traits.contains("BoxedMemoryManager") { + tokens.extend(quote! { + #[doc(hidden)] + impl #impl_generics #crate_::boxed::BoxedMemoryManager<#ffi_name> for #struct_ident #type_generics #where_clause { + #[inline] + unsafe fn copy(ptr: *const #ffi_name) -> *mut #ffi_name { + (#copy)(ptr) + } + + #[inline] + #[allow(clippy::no_effect)] + unsafe fn free(ptr: *mut #ffi_name) { + (#free)(ptr) + } + } + }); + } + Ok(tokens) + } +} diff --git a/glib/src/lib.rs b/glib/src/lib.rs index e74282633dd9..85b80a1eb28c 100644 --- a/glib/src/lib.rs +++ b/glib/src/lib.rs @@ -10,8 +10,8 @@ pub use ffi; #[doc(hidden)] pub use glib_macros::cstr_bytes; pub use glib_macros::{ - clone, clone_block, closure, closure_local, flags, object_interface, object_subclass, Boxed, - Downgrade, Enum, ErrorDomain, Properties, SharedBoxed, Variant, + clone, clone_block, closure, closure_local, flags, object_interface, object_subclass, + wrapper as wrapper_attr, Boxed, Downgrade, Enum, ErrorDomain, Properties, SharedBoxed, Variant, }; pub use gobject_ffi; #[doc(hidden)] From 402a1278393282eadb31669365f3110fee3f31bc Mon Sep 17 00:00:00 2001 From: Jason Francis Date: Tue, 14 Feb 2023 11:25:15 -0500 Subject: [PATCH 4/5] changed glib/pango Boxed types to use wrapper attribute --- glib/src/error.rs | 41 +++++++++++++++------------------ glib/src/value_array.rs | 20 ++++++++-------- pango/src/attr_color.rs | 2 +- pango/src/attr_float.rs | 2 +- pango/src/attr_font_desc.rs | 2 +- pango/src/attr_font_features.rs | 2 +- pango/src/attr_int.rs | 2 +- pango/src/attr_language.rs | 2 +- pango/src/attr_shape.rs | 4 ++-- pango/src/attr_size.rs | 4 ++-- pango/src/attr_string.rs | 2 +- pango/src/attribute.rs | 32 +++++++++++-------------- 12 files changed, 51 insertions(+), 64 deletions(-) diff --git a/glib/src/error.rs b/glib/src/error.rs index 255aa2e7b242..2e9ad5424245 100644 --- a/glib/src/error.rs +++ b/glib/src/error.rs @@ -5,21 +5,18 @@ use std::{borrow::Cow, convert::Infallible, error, ffi::CStr, fmt, str}; -use crate::{translate::*, Quark}; +use crate::{self as glib, translate::*, Quark}; -wrapper! { - // rustdoc-stripper-ignore-next - /// A generic error capable of representing various error domains (types). - #[derive(PartialEq, Eq, PartialOrd, Ord, Hash)] - #[doc(alias = "GError")] - pub struct Error(Boxed); - - match fn { - copy => |ptr| ffi::g_error_copy(ptr), - free => |ptr| ffi::g_error_free(ptr), - type_ => || ffi::g_error_get_type(), - } -} +// rustdoc-stripper-ignore-next +/// A generic error capable of representing various error domains (types). +#[glib_macros::wrapper( + copy = ffi::g_error_copy, + free = ffi::g_error_free, + type = ffi::g_error_get_type, +)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)] +#[doc(alias = "GError")] +pub struct Error(Boxed); unsafe impl Send for Error {} unsafe impl Sync for Error {} @@ -42,20 +39,20 @@ impl Error { // rustdoc-stripper-ignore-next /// Checks if the error domain matches `T`. pub fn is(&self) -> bool { - self.inner.domain == T::domain().into_glib() + self.0.domain == T::domain().into_glib() } // rustdoc-stripper-ignore-next /// Returns the error domain quark pub fn domain(&self) -> Quark { - unsafe { from_glib(self.inner.domain) } + unsafe { from_glib(self.0.domain) } } // rustdoc-stripper-ignore-next /// Checks if the error matches the specified domain and error code. #[doc(alias = "g_error_matches")] pub fn matches(&self, err: T) -> bool { - self.is::() && self.inner.code == err.code() + self.is::() && self.0.code == err.code() } // rustdoc-stripper-ignore-next @@ -77,7 +74,7 @@ impl Error { /// ``` pub fn kind(&self) -> Option { if self.is::() { - T::from(self.inner.code) + T::from(self.0.code) } else { None } @@ -90,7 +87,7 @@ impl Error { /// trait, but you can use this method if you need to have the message as a `&str`. pub fn message(&self) -> &str { unsafe { - let bytes = CStr::from_ptr(self.inner.message).to_bytes(); + let bytes = CStr::from_ptr(self.0.message).to_bytes(); str::from_utf8(bytes) .unwrap_or_else(|err| str::from_utf8(&bytes[..err.valid_up_to()]).unwrap()) } @@ -106,10 +103,8 @@ impl fmt::Display for Error { impl fmt::Debug for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Error") - .field("domain", unsafe { - &crate::Quark::from_glib(self.inner.domain) - }) - .field("code", &self.inner.code) + .field("domain", unsafe { &crate::Quark::from_glib(self.0.domain) }) + .field("code", &self.0.code) .field("message", &self.message()) .finish() } diff --git a/glib/src/value_array.rs b/glib/src/value_array.rs index 081985aeee75..d1cb8401726a 100644 --- a/glib/src/value_array.rs +++ b/glib/src/value_array.rs @@ -3,6 +3,7 @@ use std::{cmp::Ordering, ops, slice}; use crate::{ + self as glib, prelude::*, translate::*, value::{ @@ -11,16 +12,13 @@ use crate::{ HasParamSpec, ParamSpecValueArray, ParamSpecValueArrayBuilder, Type, Value, }; -wrapper! { - #[derive(Debug)] - #[doc(alias = "GValueArray")] - pub struct ValueArray(Boxed); - - match fn { - copy => |ptr| gobject_ffi::g_value_array_copy(mut_override(ptr)), - free => |ptr| gobject_ffi::g_value_array_free(ptr), - } -} +#[glib_macros::wrapper( + copy = |ptr| gobject_ffi::g_value_array_copy(mut_override(ptr)), + free = gobject_ffi::g_value_array_free, +)] +#[derive(Debug)] +#[doc(alias = "GValueArray")] +pub struct ValueArray(Boxed); impl ValueArray { #[doc(alias = "g_value_array_new")] @@ -43,7 +41,7 @@ impl ValueArray { #[inline] pub fn len(&self) -> usize { - self.inner.n_values as usize + self.0.n_values as usize } #[doc(alias = "get_nth")] diff --git a/pango/src/attr_color.rs b/pango/src/attr_color.rs index 1a64aa306463..1cbfcdf7b2ca 100644 --- a/pango/src/attr_color.rs +++ b/pango/src/attr_color.rs @@ -45,6 +45,6 @@ impl AttrColor { } pub fn color(&self) -> Color { - unsafe { from_glib_none((&self.inner.color) as *const ffi::PangoColor) } + unsafe { from_glib_none((&self.0.color) as *const ffi::PangoColor) } } } diff --git a/pango/src/attr_float.rs b/pango/src/attr_float.rs index 23ac459cc40f..00db5481390c 100644 --- a/pango/src/attr_float.rs +++ b/pango/src/attr_float.rs @@ -29,6 +29,6 @@ impl AttrFloat { } pub fn value(&self) -> f64 { - self.inner.value + self.0.value } } diff --git a/pango/src/attr_font_desc.rs b/pango/src/attr_font_desc.rs index 8d1703762d9c..7a9161c21c1d 100644 --- a/pango/src/attr_font_desc.rs +++ b/pango/src/attr_font_desc.rs @@ -13,6 +13,6 @@ impl AttrFontDesc { } pub fn desc(&self) -> FontDescription { - unsafe { from_glib_none(self.inner.desc) } + unsafe { from_glib_none(self.0.desc) } } } diff --git a/pango/src/attr_font_features.rs b/pango/src/attr_font_features.rs index 7a910919f7bf..115912581eaf 100644 --- a/pango/src/attr_font_features.rs +++ b/pango/src/attr_font_features.rs @@ -17,6 +17,6 @@ impl AttrFontFeatures { } pub fn features(&self) -> glib::GString { - unsafe { from_glib_none(self.inner.features) } + unsafe { from_glib_none(self.0.features) } } } diff --git a/pango/src/attr_int.rs b/pango/src/attr_int.rs index 4970ad21afdc..632b2f6b43c3 100644 --- a/pango/src/attr_int.rs +++ b/pango/src/attr_int.rs @@ -156,6 +156,6 @@ impl AttrInt { } pub fn value(&self) -> i32 { - self.inner.value + self.0.value } } diff --git a/pango/src/attr_language.rs b/pango/src/attr_language.rs index f5c8d31ac0f3..ccaca7cc842c 100644 --- a/pango/src/attr_language.rs +++ b/pango/src/attr_language.rs @@ -17,6 +17,6 @@ impl AttrLanguage { } pub fn value(&self) -> Language { - unsafe { from_glib_none(self.inner.value) } + unsafe { from_glib_none(self.0.value) } } } diff --git a/pango/src/attr_shape.rs b/pango/src/attr_shape.rs index 52194b92f98f..f9e06032e1db 100644 --- a/pango/src/attr_shape.rs +++ b/pango/src/attr_shape.rs @@ -18,10 +18,10 @@ impl AttrShape { } pub fn ink_rect(&self) -> Rectangle { - unsafe { from_glib_none(&self.inner.ink_rect as *const _) } + unsafe { from_glib_none(&self.0.ink_rect as *const _) } } pub fn logical_rect(&self) -> Rectangle { - unsafe { from_glib_none(&self.inner.logical_rect as *const _) } + unsafe { from_glib_none(&self.0.logical_rect as *const _) } } } diff --git a/pango/src/attr_size.rs b/pango/src/attr_size.rs index 421d046ceb05..b0debcf75c8b 100644 --- a/pango/src/attr_size.rs +++ b/pango/src/attr_size.rs @@ -22,10 +22,10 @@ impl AttrSize { } pub fn size(&self) -> i32 { - self.inner.size + self.0.size } pub fn absolute(&self) -> bool { - unsafe { from_glib(self.inner.absolute as i32) } + unsafe { from_glib(self.0.absolute as i32) } } } diff --git a/pango/src/attr_string.rs b/pango/src/attr_string.rs index 01361da51947..c8486ef090af 100644 --- a/pango/src/attr_string.rs +++ b/pango/src/attr_string.rs @@ -13,6 +13,6 @@ impl AttrString { } pub fn value(&self) -> glib::GString { - unsafe { from_glib_none(self.inner.value) } + unsafe { from_glib_none(self.0.value) } } } diff --git a/pango/src/attribute.rs b/pango/src/attribute.rs index a63967a9bc24..86a2adc3d33f 100644 --- a/pango/src/attribute.rs +++ b/pango/src/attribute.rs @@ -84,30 +84,24 @@ macro_rules! define_attribute_struct { #[cfg(any(feature = "v1_44", feature = "dox"))] #[cfg_attr(feature = "dox", doc(cfg(feature = "v1_44")))] - glib::wrapper! { - #[derive(Debug)] - pub struct $rust_type(Boxed<$ffi_type>); - - match fn { - copy => |ptr| ffi::pango_attribute_copy(ptr as *const ffi::PangoAttribute) as *mut $ffi_type, - free => |ptr| ffi::pango_attribute_destroy(ptr as *mut ffi::PangoAttribute), - type_ => || ffi::pango_attribute_get_type(), - } - } + #[glib::wrapper_attr( + copy = |ptr| ffi::pango_attribute_copy(ptr as *const ffi::PangoAttribute) as *mut $ffi_type, + free = |ptr| ffi::pango_attribute_destroy(ptr as *mut ffi::PangoAttribute), + type = ffi::pango_attribute_get_type, + )] + #[derive(Debug)] + pub struct $rust_type(Boxed<$ffi_type>); unsafe impl Send for $rust_type {} unsafe impl Sync for $rust_type {} #[cfg(not(any(feature = "v1_44", feature = "dox")))] - glib::wrapper! { - #[derive(Debug)] - pub struct $rust_type(Boxed<$ffi_type>); - - match fn { - copy => |ptr| ffi::pango_attribute_copy(ptr as *const ffi::PangoAttribute) as *mut $ffi_type, - free => |ptr| ffi::pango_attribute_destroy(ptr as *mut ffi::PangoAttribute), - } - } + #[glib::wrapper_attr( + copy = |ptr| ffi::pango_attribute_copy(ptr as *const ffi::PangoAttribute) as *mut $ffi_type, + free = |ptr| ffi::pango_attribute_destroy(ptr as *mut ffi::PangoAttribute), + )] + #[derive(Debug)] + pub struct $rust_type(Boxed<$ffi_type>); impl $rust_type { #[doc(alias = "pango_attribute_equal")] From 401e76cc50b349ed914f78017f77e51ffb5f4a1c Mon Sep 17 00:00:00 2001 From: Jason Francis Date: Wed, 15 Feb 2023 10:24:37 -0500 Subject: [PATCH 5/5] glib-macros: add ObjectImpl derive --- glib-macros/src/lib.rs | 10 ++- glib-macros/src/object_impl_derive.rs | 108 ++++++++++++++++++++++++++ glib-macros/src/utils.rs | 91 ++++++++++++++++++++++ glib-macros/tests/properties.rs | 46 ++++------- glib/src/lib.rs | 3 +- 5 files changed, 223 insertions(+), 35 deletions(-) create mode 100644 glib-macros/src/object_impl_derive.rs diff --git a/glib-macros/src/lib.rs b/glib-macros/src/lib.rs index 6879796fa737..8ed3389f8ef7 100644 --- a/glib-macros/src/lib.rs +++ b/glib-macros/src/lib.rs @@ -8,6 +8,7 @@ mod downgrade_derive; mod enum_derive; mod error_domain_derive; mod flags_attribute; +mod object_impl_derive; mod object_interface_attribute; mod object_subclass_attribute; mod properties; @@ -965,7 +966,6 @@ pub fn derive_props(input: TokenStream) -> TokenStream { } #[proc_macro_attribute] -#[proc_macro_error::proc_macro_error] pub fn clone_block(_attr: TokenStream, item: TokenStream) -> TokenStream { let mut item = syn::parse_macro_input!(item as syn::Item); let errors = deluxe::Errors::new(); @@ -975,7 +975,6 @@ pub fn clone_block(_attr: TokenStream, item: TokenStream) -> TokenStream { } #[proc_macro_attribute] -#[proc_macro_error] pub fn wrapper(attr: TokenStream, item: TokenStream) -> TokenStream { let input = parse_macro_input!(item as syn::ItemStruct); match wrapper_attribute::impl_wrapper(attr.into(), input) { @@ -983,3 +982,10 @@ pub fn wrapper(attr: TokenStream, item: TokenStream) -> TokenStream { Err(e) => e.into_compile_error().into(), } } + +#[proc_macro_derive(ObjectImpl, attributes(object_impl))] +pub fn object_impl_derive(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as utils::DeriveHeader); + let gen = object_impl_derive::impl_object_impl(input); + gen.into() +} diff --git a/glib-macros/src/object_impl_derive.rs b/glib-macros/src/object_impl_derive.rs new file mode 100644 index 000000000000..989f147adc28 --- /dev/null +++ b/glib-macros/src/object_impl_derive.rs @@ -0,0 +1,108 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use proc_macro2::TokenStream; +use quote::quote; + +use crate::utils::DeriveHeader; + +#[derive(Default, deluxe::ExtractAttributes)] +#[deluxe(attributes(object_impl))] +struct ObjectImpl { + derived_properties: deluxe::Flag, + #[deluxe(map(|e| FlagOrExpr::into_expr(e, "signals")))] + signals: Option, + #[deluxe(map(|e| FlagOrExpr::into_expr(e, "constructed")))] + constructed: Option, + #[deluxe(map(|e| FlagOrExpr::into_expr(e, "dispose")))] + dispose: Option, +} + +enum FlagOrExpr { + Flag, + Expr(syn::Expr), +} + +impl deluxe::ParseMetaItem for FlagOrExpr { + #[inline] + fn parse_meta_item( + input: syn::parse::ParseStream, + _mode: deluxe::ParseMode, + ) -> deluxe::Result { + Ok(Self::Expr(input.parse()?)) + } + #[inline] + fn parse_meta_item_flag(_span: proc_macro2::Span) -> deluxe::Result { + Ok(Self::Flag) + } +} + +impl FlagOrExpr { + #[inline] + fn into_expr(e: Option, default_name: &str) -> Option { + e.map(|e| match e { + Self::Flag => { + let func = syn::Ident::new(default_name, proc_macro2::Span::call_site()); + syn::parse_quote! { Self::#func } + } + Self::Expr(expr) => expr, + }) + } +} + +pub fn impl_object_impl(mut input: DeriveHeader) -> TokenStream { + let errors = deluxe::Errors::new(); + let ObjectImpl { + derived_properties, + signals, + constructed, + dispose, + } = deluxe::extract_attributes_optional(&mut input, &errors); + + let glib = crate::utils::crate_ident_new(); + let ident = &input.ident; + let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl(); + + let properties = derived_properties.is_set().then(|| { + quote! { + fn properties() -> &'static [#glib::ParamSpec] { + Self::derived_properties() + } + fn property(&self, id: ::std::primitive::usize, pspec: &#glib::ParamSpec) -> #glib::Value { + Self::derived_property(self, id, pspec) + } + fn set_property(&self, id: ::std::primitive::usize, value: &#glib::Value, pspec: &#glib::ParamSpec) { + Self::derived_set_property(self, id, value, pspec) + } + } + }); + let signals = signals.map(|signals| { + quote! { + fn signals() -> &'static [#glib::subclass::Signal] { + (#signals)() + } + } + }); + let constructed = constructed.map(|constructed| { + quote! { + fn constructed(&self) { + (#constructed)(self) + } + } + }); + let dispose = dispose.map(|dispose| { + quote! { + fn dispose(&self) { + (#dispose)(self) + } + } + }); + quote! { + #errors + impl #impl_generics #glib::subclass::object::ObjectImpl for #ident #type_generics #where_clause { + #properties + #signals + #constructed + #dispose + } + } +} diff --git a/glib-macros/src/utils.rs b/glib-macros/src/utils.rs index b17d362fe0f4..b77e875ddd38 100644 --- a/glib-macros/src/utils.rs +++ b/glib-macros/src/utils.rs @@ -58,3 +58,94 @@ pub fn gen_enum_from_glib( None } } + +// Simplified DeriveInput without fields, for faster parsing +pub struct DeriveHeader { + pub attrs: Vec, + pub vis: syn::Visibility, + pub ident: syn::Ident, + pub generics: syn::Generics, + pub data: DeriveData, +} + +pub enum DeriveData { + Struct, + Enum, + Union, +} + +impl syn::parse::Parse for DeriveHeader { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let attrs = input.call(syn::Attribute::parse_outer)?; + let vis = input.parse()?; + let data = input.parse()?; + let ident = input.parse()?; + let mut generics = input.parse::()?; + let lookahead = input.lookahead1(); + if lookahead.peek(syn::Token![where]) { + generics.where_clause = Some(input.parse()?); + } else if input.peek(syn::token::Paren) { + let content; + syn::parenthesized!(content in input); + skip_all(&content); + if input.peek(syn::Token![where]) { + generics.where_clause = Some(input.parse()?); + } + } else if lookahead.peek(syn::token::Brace) { + let content; + syn::braced!(content in input); + skip_all(&content); + } else if lookahead.peek(syn::Token![;]) { + input.parse::()?; + } else { + return Err(lookahead.error()); + } + Ok(Self { + attrs, + vis, + ident, + generics, + data, + }) + } +} + +impl syn::parse::Parse for DeriveData { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let lookahead = input.lookahead1(); + if lookahead.peek(syn::Token![struct]) { + input.parse::()?; + Ok(Self::Struct) + } else if lookahead.peek(syn::Token![enum]) { + input.parse::()?; + Ok(Self::Enum) + } else if lookahead.peek(syn::Token![union]) { + input.parse::()?; + Ok(Self::Union) + } else { + Err(lookahead.error()) + } + } +} + +impl deluxe::HasAttributes for DeriveHeader { + #[inline] + fn attrs(&self) -> &[syn::Attribute] { + &self.attrs + } + #[inline] + fn attrs_mut(&mut self) -> deluxe::Result<&mut Vec> { + Ok(&mut self.attrs) + } +} + +#[inline] +pub fn skip_all(input: syn::parse::ParseStream) { + let _ = input.step(|cursor| { + let mut cur = *cursor; + while let Some((_, next)) = cur.token_tree() { + cur = next; + } + Ok(((), cur)) + }); +} diff --git a/glib-macros/tests/properties.rs b/glib-macros/tests/properties.rs index a5b3ee7bef69..0a124764068d 100644 --- a/glib-macros/tests/properties.rs +++ b/glib-macros/tests/properties.rs @@ -7,15 +7,14 @@ use glib::ParamFlags; mod base { use glib::prelude::*; use glib::subclass::prelude::*; - use glib_macros::Properties; + use glib_macros::{ObjectImpl, Properties}; use std::marker::PhantomData; pub mod imp { - use glib::{ParamSpec, Value}; - use super::*; - #[derive(Properties, Default)] + #[derive(Default, ObjectImpl, Properties)] + #[object_impl(derived_properties)] #[properties(wrapper_type = super::Base)] pub struct Base { #[property(get = Self::not_overridden)] @@ -24,18 +23,6 @@ mod base { not_overridden: PhantomData, } - impl ObjectImpl for Base { - fn properties() -> &'static [ParamSpec] { - Self::derived_properties() - } - fn set_property(&self, _id: usize, _value: &Value, _pspec: &ParamSpec) { - Self::derived_set_property(self, _id, _value, _pspec) - } - fn property(&self, id: usize, _pspec: &ParamSpec) -> Value { - Self::derived_property(self, id, _pspec) - } - } - #[glib::object_subclass] impl ObjectSubclass for Base { const NAME: &'static str = "MyBase"; @@ -60,7 +47,7 @@ mod base { mod foo { use glib::prelude::*; use glib::subclass::prelude::*; - use glib_macros::Properties; + use glib_macros::{ObjectImpl, Properties}; use once_cell::sync::OnceCell; use std::cell::Cell; use std::cell::RefCell; @@ -87,12 +74,14 @@ mod foo { } pub mod imp { - use glib::{ParamSpec, Value}; use std::rc::Rc; + use glib::subclass::Signal; + use super::*; - #[derive(Properties, Default)] + #[derive(Default, ObjectImpl, Properties)] + #[object_impl(derived_properties, signals, constructed = Self::my_constructed, dispose)] #[properties(wrapper_type = super::Foo)] pub struct Foo { #[property(get, set)] @@ -155,18 +144,6 @@ mod foo { send_weak_ref_prop: glib::SendWeakRef, } - impl ObjectImpl for Foo { - fn properties() -> &'static [ParamSpec] { - Self::derived_properties() - } - fn set_property(&self, _id: usize, _value: &Value, _pspec: &ParamSpec) { - Self::derived_set_property(self, _id, _value, _pspec) - } - fn property(&self, id: usize, _pspec: &ParamSpec) -> Value { - Self::derived_property(self, id, _pspec) - } - } - #[glib::object_subclass] impl ObjectSubclass for Foo { const NAME: &'static str = "MyFoo"; @@ -182,11 +159,16 @@ mod foo { String::from("Hello world!") } fn set_fizz(&self, value: String) { - *self.fizz.borrow_mut() = format!("custom set: {}", value); + *self.fizz.borrow_mut() = format!("custom set: {value}"); } fn overridden(&self) -> u32 { 43 } + fn signals() -> &'static [Signal] { + &[] + } + fn my_constructed(&self) {} + fn dispose(&self) {} } } diff --git a/glib/src/lib.rs b/glib/src/lib.rs index 85b80a1eb28c..0556b1b6458e 100644 --- a/glib/src/lib.rs +++ b/glib/src/lib.rs @@ -11,7 +11,8 @@ pub use ffi; pub use glib_macros::cstr_bytes; pub use glib_macros::{ clone, clone_block, closure, closure_local, flags, object_interface, object_subclass, - wrapper as wrapper_attr, Boxed, Downgrade, Enum, ErrorDomain, Properties, SharedBoxed, Variant, + wrapper as wrapper_attr, Boxed, Downgrade, Enum, ErrorDomain, ObjectImpl, Properties, + SharedBoxed, Variant, }; pub use gobject_ffi; #[doc(hidden)]