From f0257361d88277af5a6383db410facc3a740f887 Mon Sep 17 00:00:00 2001 From: jgcodes2020 Date: Wed, 22 Jan 2025 23:07:57 -0500 Subject: [PATCH 1/8] start work --- glib-macros/src/lib.rs | 15 ++++++++++ glib-macros/src/signals.rs | 57 ++++++++++++++++++++++++++++++++++++ glib-macros/tests/signals.rs | 53 +++++++++++++++++++++++++++++++++ 3 files changed, 125 insertions(+) create mode 100644 glib-macros/src/signals.rs create mode 100644 glib-macros/tests/signals.rs diff --git a/glib-macros/src/lib.rs b/glib-macros/src/lib.rs index 8f66dc855fd5..cb34ae6ef398 100644 --- a/glib-macros/src/lib.rs +++ b/glib-macros/src/lib.rs @@ -12,6 +12,7 @@ mod flags_attribute; mod object_impl_attributes; mod properties; mod shared_boxed_derive; +mod signals; mod value_delegate_derive; mod variant_derive; @@ -1501,6 +1502,20 @@ pub fn derived_properties(_attr: TokenStream, item: TokenStream) -> TokenStream .into() } +/// This macro enables you to implement object signals in a quick way. +#[proc_macro_attribute] +pub fn signals(attr: TokenStream, item: TokenStream) -> TokenStream { + let attr_input = syn::parse_macro_input!(attr as signals::SignalAttrInput); + + syn::parse::(item) + .map_err(|_| { + syn::Error::new(Span::call_site(), signals::WRONG_PLACE_MSG) + }) + .and_then(|item_input| signals::impl_signals(attr_input, item_input)) + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} + /// # Example /// ``` /// use glib::prelude::*; diff --git a/glib-macros/src/signals.rs b/glib-macros/src/signals.rs new file mode 100644 index 000000000000..949c799af9ed --- /dev/null +++ b/glib-macros/src/signals.rs @@ -0,0 +1,57 @@ +use proc_macro2::TokenStream; +use syn::{parse::Parse, spanned::Spanned, Token}; + +pub const WRONG_PLACE_MSG: &str = + "This macro should be used on a plain `impl` block of the inner object type"; + +pub struct SignalAttrInput { + wrapper_ty: syn::Path, + // None => no ext trait, + // Some(None) => derive the ext trait from the wrapper type, + // Some(Some(ident)) => use the given ext trait Ident + ext_trait: Option>, +} + +impl Parse for SignalAttrInput { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let mut wrapper_ty = None; + let mut ext_trait = None; + + while !input.is_empty() { + let ident = input.parse::()?; + if ident == "wrapper_type" { + let _eq = input.parse::()?; + wrapper_ty = Some(input.parse::()?); + } else if ident == "ext_trait" { + if input.peek(Token![=]) { + let _eq = input.parse::()?; + let ident = input.parse::()?; + ext_trait = Some(Some(ident)); + } else { + ext_trait = Some(None); + } + } + if input.peek(Token![,]) { + input.parse::()?; + } + } + + Ok(Self { + wrapper_ty: wrapper_ty.ok_or_else(|| { + syn::Error::new(input.span(), "missing #[signals(wrapper_type = ...)]") + })?, + ext_trait, + }) + } +} + +pub fn impl_signals(attr: SignalAttrInput, item: syn::ItemImpl) -> syn::Result { + if let Some((_, trait_path, _)) = &item.trait_ { + return Err(syn::Error::new_spanned(trait_path, WRONG_PLACE_MSG)); + } + + + + + Ok(TokenStream::new()) +} \ No newline at end of file diff --git a/glib-macros/tests/signals.rs b/glib-macros/tests/signals.rs new file mode 100644 index 000000000000..2b4e300e265f --- /dev/null +++ b/glib-macros/tests/signals.rs @@ -0,0 +1,53 @@ +mod base { + use glib::prelude::*; + use glib::subclass::prelude::*; + use glib::subclass::Signal; + use glib::Value; + use glib_macros::signals; + use std::marker::PhantomData; + + pub mod imp { + use std::sync::OnceLock; + + use super::*; + + #[derive(Default)] + pub struct Base {} + + // #[signals(wrapper_type = super::Base)] + impl Base { + fn void_signal(&self) {} + } + + #[glib::object_subclass] + impl ObjectSubclass for Base { + const NAME: &'static str = "MyBase"; + type Type = super::Base; + } + + impl ObjectImpl for Base { + fn constructed(&self) { + } + + fn signals() -> &'static [Signal] { + static SIGNALS: OnceLock<[Signal; 1]> = OnceLock::new(); + SIGNALS.get_or_init(|| { + [Signal::builder("void-signal") + .class_handler(|values| { + let this = values[0].get::<::Type>().unwrap(); + this.imp().void_signal(); + None + }) + .action() + .build()] + }) + } + } + } + + glib::wrapper! { + pub struct Base(ObjectSubclass); + } +} + +fn main() {} From aaa447b946b127129130b4e230448c4d8bede499 Mon Sep 17 00:00:00 2001 From: jgcodes2020 Date: Mon, 27 Jan 2025 08:11:07 -0500 Subject: [PATCH 2/8] basic implementation of signals macro --- Cargo.lock | 1 + glib-macros/Cargo.toml | 1 + glib-macros/src/lib.rs | 2 +- glib-macros/src/signals.rs | 352 ++++++++++++++++++++++++++++++++++- glib-macros/tests/signals.rs | 25 +-- glib/src/lib.rs | 2 +- glib/src/subclass/object.rs | 10 + 7 files changed, 361 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2cda1cda8270..c1c8b8edd103 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -495,6 +495,7 @@ dependencies = [ name = "glib-macros" version = "0.21.0" dependencies = [ + "bitflags", "glib", "heck", "proc-macro-crate", diff --git a/glib-macros/Cargo.toml b/glib-macros/Cargo.toml index 04461f77ebd1..f732d5d16967 100644 --- a/glib-macros/Cargo.toml +++ b/glib-macros/Cargo.toml @@ -18,6 +18,7 @@ proc-macro2 = "1.0" quote = "1.0" syn = { version = "2.0.96", features = ["full"] } proc-macro-crate = "3.1" +bitflags.workspace = true [lib] proc-macro = true diff --git a/glib-macros/src/lib.rs b/glib-macros/src/lib.rs index cb34ae6ef398..a22943c4920e 100644 --- a/glib-macros/src/lib.rs +++ b/glib-macros/src/lib.rs @@ -1505,7 +1505,7 @@ pub fn derived_properties(_attr: TokenStream, item: TokenStream) -> TokenStream /// This macro enables you to implement object signals in a quick way. #[proc_macro_attribute] pub fn signals(attr: TokenStream, item: TokenStream) -> TokenStream { - let attr_input = syn::parse_macro_input!(attr as signals::SignalAttrInput); + let attr_input = syn::parse_macro_input!(attr as signals::Args); syn::parse::(item) .map_err(|_| { diff --git a/glib-macros/src/signals.rs b/glib-macros/src/signals.rs index 949c799af9ed..b95abb8ef4ce 100644 --- a/glib-macros/src/signals.rs +++ b/glib-macros/src/signals.rs @@ -1,10 +1,33 @@ -use proc_macro2::TokenStream; -use syn::{parse::Parse, spanned::Spanned, Token}; +use bitflags::bitflags; +use proc_macro2::{Punct, Span, TokenStream, TokenTree}; +use syn::{ext::IdentExt, parse::Parse, Token}; + +use crate::utils::crate_ident_new; pub const WRONG_PLACE_MSG: &str = "This macro should be used on a plain `impl` block of the inner object type"; -pub struct SignalAttrInput { +struct ImplItemIncompleteFn { + pub attrs: Vec, + pub vis: syn::Visibility, + pub defaultness: Option, + pub sig: syn::Signature, + pub semi_token: Token![;], +} +impl Parse for ImplItemIncompleteFn { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + Ok(Self { + attrs: input.call(syn::Attribute::parse_outer)?, + vis: input.parse()?, + defaultness: input.parse()?, + sig: input.parse()?, + semi_token: input.parse()?, + }) + } +} + +/// Arguments to the `#[signals]` attribute. +pub struct Args { wrapper_ty: syn::Path, // None => no ext trait, // Some(None) => derive the ext trait from the wrapper type, @@ -12,7 +35,7 @@ pub struct SignalAttrInput { ext_trait: Option>, } -impl Parse for SignalAttrInput { +impl Parse for Args { fn parse(input: syn::parse::ParseStream) -> syn::Result { let mut wrapper_ty = None; let mut ext_trait = None; @@ -45,13 +68,326 @@ impl Parse for SignalAttrInput { } } -pub fn impl_signals(attr: SignalAttrInput, item: syn::ItemImpl) -> syn::Result { - if let Some((_, trait_path, _)) = &item.trait_ { +/// A single parameter in `#[signal(...)]`. +pub enum SignalAttr { + RunFirst, + RunLast, + RunCleanup, + NoRecurse, + Detailed, + Action, + NoHooks, + Accum(syn::Expr), +} +impl Parse for SignalAttr { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let name = input.call(syn::Ident::parse_any)?; + let name_str = name.to_string(); + + let result = if input.peek(Token![=]) { + let _assign_token: Token![=] = input.parse()?; + + match &*name_str { + "accum" => Self::Accum(input.parse()?), + _ => { + return Err(syn::Error::new_spanned( + name, + format!("Unsupported option {name_str}"), + )) + } + } + } else { + match &*name_str { + "run_first" => Self::RunFirst, + "run_last" => Self::RunLast, + "run_cleanup" => Self::RunCleanup, + "no_recurse" => Self::NoRecurse, + "detailed" => Self::Detailed, + "action" => Self::Action, + "no_hooks" => Self::NoHooks, + _ => { + return Err(syn::Error::new_spanned( + name, + format!("Unsupported option {name_str}"), + )) + } + } + }; + + Ok(result) + } +} + +bitflags! { + #[derive(Default, Clone, Copy, PartialEq, Eq)] + struct SignalFlags: u16 { + const RUN_FIRST = 1 << 0; + const RUN_LAST = 1 << 1; + const RUN_CLEANUP = 1 << 2; + const NO_RECURSE = 1 << 3; + const DETAILED = 1 << 4; + const ACTION = 1 << 5; + const NO_HOOKS = 1 << 6; + } +} + +/// Collected info from the #[signal(...)] attribute. +#[derive(Default)] +struct SignalTagInfo { + flags: SignalFlags, + accum: Option, +} +impl SignalTagInfo { + fn set_from_attr(&mut self, attr: SignalAttr) { + match attr { + SignalAttr::RunFirst => self.flags |= SignalFlags::RUN_FIRST, + SignalAttr::RunLast => self.flags |= SignalFlags::RUN_LAST, + SignalAttr::RunCleanup => self.flags |= SignalFlags::RUN_CLEANUP, + SignalAttr::NoRecurse => self.flags |= SignalFlags::NO_RECURSE, + SignalAttr::Detailed => self.flags |= SignalFlags::DETAILED, + SignalAttr::Action => self.flags |= SignalFlags::ACTION, + SignalAttr::NoHooks => self.flags |= SignalFlags::NO_HOOKS, + SignalAttr::Accum(expr) => self.accum = Some(expr), + } + } +} +impl SignalTagInfo { + fn extract_from<'a, I: IntoIterator>( + attrs: I, + ) -> syn::Result> { + let attribute = match attrs + .into_iter() + .find(|attr| attr.path().is_ident("signal")) + { + Some(attr) => attr, + None => return Ok(None), + }; + match &attribute.meta { + syn::Meta::Path(_) => Ok(Some(Self::default())), + syn::Meta::List(meta_list) => Ok(Some(meta_list.parse_args::()?)), + syn::Meta::NameValue(meta_name_value) => { + return Err(syn::Error::new_spanned( + meta_name_value, + "Invalid #[signal] syntax", + )) + } + } + } +} +impl Parse for SignalTagInfo { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let attrs = + syn::punctuated::Punctuated::::parse_separated_nonempty(input)?; + + let mut value = Self::default(); + for attr in attrs { + value.set_from_attr(attr); + } + Ok(value) + } +} + +/// Full description of an eventual signal, based on the provided +/// method signature and signal tag info. +struct SignalDesc { + name: String, + param_types: Vec, + return_type: Option, + flags: SignalFlags, + class_handler: Option, + accum: Option, +} +impl SignalDesc { + fn new( + tag_info: SignalTagInfo, + signature: &syn::Signature, + complete: bool, + ) -> syn::Result { + const EXPECT_SELF_REF: &str = "signal method must take &self as its first parameter"; + + // ensure function takes &self first + match signature.inputs.get(0) { + Some(syn::FnArg::Receiver(syn::Receiver { + reference: Some(_), + mutability: None, + .. + })) => (), + _ => return Err(syn::Error::new_spanned(signature, EXPECT_SELF_REF)), + } + + // for now, get name from signature + let name = signature.ident.to_string().replace('_', "-"); + + // parameters are remaining signature types + let param_types = if signature.inputs.len() >= 2 { + signature + .inputs + .iter() + .skip(1) + .map(|arg| match arg { + syn::FnArg::Receiver(receiver) => panic!("unexpected receiver"), + syn::FnArg::Typed(pat_type) => (&*pat_type.ty).clone(), + }) + .collect() + } else { + Vec::new() + }; + + let return_type = match &signature.output { + syn::ReturnType::Default => None, + syn::ReturnType::Type(_, rt) => match &**rt { + syn::Type::Tuple(syn::TypeTuple { elems, .. }) if elems.is_empty() => None, + rt => Some(rt.clone()), + }, + }; + + let flags = tag_info.flags; + + let class_handler = complete.then(|| signature.ident.clone()); + + let accum = tag_info.accum; + + Ok(Self { + name, + param_types, + return_type, + flags, + class_handler, + accum, + }) + } +} + +pub fn impl_signals(attr: Args, input: syn::ItemImpl) -> syn::Result { + if let Some((_, trait_path, _)) = &input.trait_ { return Err(syn::Error::new_spanned(trait_path, WRONG_PLACE_MSG)); } + let crate_name = crate_ident_new(); + + let mut out_impl = input.clone(); + out_impl.items.clear(); + let mut out_signals = Vec::::new(); + + // Extract signal methods + for item in &input.items { + match item { + item @ syn::ImplItem::Fn(method) => { + let attr_info = match SignalTagInfo::extract_from(&method.attrs)? { + Some(attr_info) => attr_info, + None => { + out_impl.items.push(item.clone()); + continue; + } + }; + let desc = SignalDesc::new(attr_info, &method.sig, true)?; + out_signals.push(desc); + } + item @ syn::ImplItem::Verbatim(tokens) => { + // try to parse as an incomplete function + let method = match syn::parse2::(tokens.clone()) { + Ok(parsed) => parsed, + Err(_) => { + out_impl.items.push(item.clone()); + continue; + } + }; + // if it has the signal attribute, it's a signal + // let the Rust compiler generate an error if not + let attr_info = match SignalTagInfo::extract_from(&method.attrs)? { + Some(attr_info) => attr_info, + None => { + out_impl.items.push(item.clone()); + continue; + } + }; + let desc = SignalDesc::new(attr_info, &method.sig, false)?; + out_signals.push(desc); + } + item => out_impl.items.push(item.clone()), + } + } + // Implement DerivedObjectSignals + let derive_signals = impl_object_signals(&crate_name, &*input.self_ty, &out_signals); + // Implement wrapper type + Ok(quote::quote! { + #out_impl + #derive_signals + }) +} - Ok(TokenStream::new()) -} \ No newline at end of file +fn impl_object_signals<'a>( + glib: &TokenStream, + ty: &syn::Type, + signals: impl IntoIterator, +) -> TokenStream { + let builders: Vec<_> = signals + .into_iter() + .map(|signal| { + let name = syn::LitStr::new(&signal.name, Span::call_site()); + let run_first = signal.flags.contains(SignalFlags::RUN_FIRST).then(|| { + quote::quote! { + .run_first() + } + }); + let run_last = signal.flags.contains(SignalFlags::RUN_LAST).then(|| { + quote::quote! { + .run_last() + } + }); + let run_cleanup = signal.flags.contains(SignalFlags::RUN_CLEANUP).then(|| { + quote::quote! { + .run_cleanup() + } + }); + let no_recurse = signal.flags.contains(SignalFlags::NO_RECURSE).then(|| { + quote::quote! { + .no_recurse() + } + }); + let detailed = signal.flags.contains(SignalFlags::DETAILED).then(|| { + quote::quote! { + .detailed() + } + }); + let action = signal.flags.contains(SignalFlags::ACTION).then(|| { + quote::quote! { + .action() + } + }); + let no_hooks = signal.flags.contains(SignalFlags::NO_HOOKS).then(|| { + quote::quote! { + .no_hooks() + } + }); + quote::quote! { + #glib::subclass::signal::Signal::builder(#name) + #run_first + #run_last + #run_cleanup + #no_recurse + #detailed + #action + #no_hooks + .build() + } + }) + .collect(); + let count = builders.len(); + quote::quote! { + #[automatically_derived] + impl #glib::subclass::object::DerivedObjectSignals for #ty { + fn derived_signals() -> &'static [glib::subclass::signal::Signal] { + static SIGNALS: ::std::sync::OnceLock<[#glib::subclass::signal::Signal; #count]> = + ::std::sync::OnceLock::new(); + SIGNALS.get_or_init(|| { + [ + #(#builders),* + ] + }) + } + } + } +} diff --git a/glib-macros/tests/signals.rs b/glib-macros/tests/signals.rs index 2b4e300e265f..2911f4c13809 100644 --- a/glib-macros/tests/signals.rs +++ b/glib-macros/tests/signals.rs @@ -1,22 +1,17 @@ mod base { use glib::prelude::*; use glib::subclass::prelude::*; - use glib::subclass::Signal; - use glib::Value; - use glib_macros::signals; - use std::marker::PhantomData; pub mod imp { - use std::sync::OnceLock; - use super::*; #[derive(Default)] pub struct Base {} - // #[signals(wrapper_type = super::Base)] + #[glib::signals(wrapper_type = super::Base)] impl Base { - fn void_signal(&self) {} + #[signal(run_first, no_recurse, no_hooks)] + fn void_signal(&self); } #[glib::object_subclass] @@ -28,20 +23,6 @@ mod base { impl ObjectImpl for Base { fn constructed(&self) { } - - fn signals() -> &'static [Signal] { - static SIGNALS: OnceLock<[Signal; 1]> = OnceLock::new(); - SIGNALS.get_or_init(|| { - [Signal::builder("void-signal") - .class_handler(|values| { - let this = values[0].get::<::Type>().unwrap(); - this.imp().void_signal(); - None - }) - .action() - .build()] - }) - } } } diff --git a/glib/src/lib.rs b/glib/src/lib.rs index a4ed600ef0f1..a4b18598e74b 100644 --- a/glib/src/lib.rs +++ b/glib/src/lib.rs @@ -30,7 +30,7 @@ pub use bitflags; #[doc(hidden)] pub use glib_macros::cstr_bytes; pub use glib_macros::{ - async_test, clone, closure, closure_local, derived_properties, flags, object_interface, + async_test, clone, closure, closure_local, derived_properties, flags, signals, object_interface, object_subclass, Boxed, Downgrade, Enum, ErrorDomain, Properties, SharedBoxed, ValueDelegate, Variant, }; diff --git a/glib/src/subclass/object.rs b/glib/src/subclass/object.rs index b6e548c6cd7e..53873943380a 100644 --- a/glib/src/subclass/object.rs +++ b/glib/src/subclass/object.rs @@ -192,6 +192,16 @@ pub trait DerivedObjectProperties: ObjectSubclass { } } +// rustdoc-stripper-ignore-next +/// Trait containing only the signal related functions of [`ObjectImpl`]. +/// Implemented by the [`signals`](crate::signals) macro. +/// When implementing `ObjectImpl` you may want to delegate the function calls to this trait. +pub trait DerivedObjectSignals: ObjectSubclass { + fn derived_signals() -> &'static [Signal] { + &[] + } +} + // rustdoc-stripper-ignore-next /// Extension trait for `glib::Object`'s class struct. /// From 90f045f0367b5ede41ea04caea455dd4ccc1fdb3 Mon Sep 17 00:00:00 2001 From: jgcodes2020 Date: Mon, 27 Jan 2025 14:29:07 -0500 Subject: [PATCH 3/8] class handlers --- glib-macros/src/signals.rs | 280 +++++++++++++++++++---------------- glib-macros/tests/signals.rs | 8 +- 2 files changed, 160 insertions(+), 128 deletions(-) diff --git a/glib-macros/src/signals.rs b/glib-macros/src/signals.rs index b95abb8ef4ce..8d91e644a0a9 100644 --- a/glib-macros/src/signals.rs +++ b/glib-macros/src/signals.rs @@ -1,6 +1,7 @@ use bitflags::bitflags; -use proc_macro2::{Punct, Span, TokenStream, TokenTree}; -use syn::{ext::IdentExt, parse::Parse, Token}; +use proc_macro2::{Literal, Punct, Span, TokenStream, TokenTree}; +use quote::ToTokens; +use syn::{ext::IdentExt, parse::Parse, punctuated::Punctuated, Token}; use crate::utils::crate_ident_new; @@ -70,13 +71,13 @@ impl Parse for Args { /// A single parameter in `#[signal(...)]`. pub enum SignalAttr { - RunFirst, - RunLast, - RunCleanup, - NoRecurse, - Detailed, - Action, - NoHooks, + RunFirst(syn::Ident), + RunLast(syn::Ident), + RunCleanup(syn::Ident), + NoRecurse(syn::Ident), + Detailed(syn::Ident), + Action(syn::Ident), + NoHooks(syn::Ident), Accum(syn::Expr), } impl Parse for SignalAttr { @@ -98,13 +99,13 @@ impl Parse for SignalAttr { } } else { match &*name_str { - "run_first" => Self::RunFirst, - "run_last" => Self::RunLast, - "run_cleanup" => Self::RunCleanup, - "no_recurse" => Self::NoRecurse, - "detailed" => Self::Detailed, - "action" => Self::Action, - "no_hooks" => Self::NoHooks, + "run_first" => Self::RunFirst(name), + "run_last" => Self::RunLast(name), + "run_cleanup" => Self::RunCleanup(name), + "no_recurse" => Self::NoRecurse(name), + "detailed" => Self::Detailed(name), + "action" => Self::Action(name), + "no_hooks" => Self::NoHooks(name), _ => { return Err(syn::Error::new_spanned( name, @@ -117,73 +118,44 @@ impl Parse for SignalAttr { Ok(result) } } - -bitflags! { - #[derive(Default, Clone, Copy, PartialEq, Eq)] - struct SignalFlags: u16 { - const RUN_FIRST = 1 << 0; - const RUN_LAST = 1 << 1; - const RUN_CLEANUP = 1 << 2; - const NO_RECURSE = 1 << 3; - const DETAILED = 1 << 4; - const ACTION = 1 << 5; - const NO_HOOKS = 1 << 6; - } -} - -/// Collected info from the #[signal(...)] attribute. -#[derive(Default)] -struct SignalTagInfo { - flags: SignalFlags, - accum: Option, -} -impl SignalTagInfo { - fn set_from_attr(&mut self, attr: SignalAttr) { - match attr { - SignalAttr::RunFirst => self.flags |= SignalFlags::RUN_FIRST, - SignalAttr::RunLast => self.flags |= SignalFlags::RUN_LAST, - SignalAttr::RunCleanup => self.flags |= SignalFlags::RUN_CLEANUP, - SignalAttr::NoRecurse => self.flags |= SignalFlags::NO_RECURSE, - SignalAttr::Detailed => self.flags |= SignalFlags::DETAILED, - SignalAttr::Action => self.flags |= SignalFlags::ACTION, - SignalAttr::NoHooks => self.flags |= SignalFlags::NO_HOOKS, - SignalAttr::Accum(expr) => self.accum = Some(expr), - } - } -} -impl SignalTagInfo { - fn extract_from<'a, I: IntoIterator>( - attrs: I, - ) -> syn::Result> { - let attribute = match attrs +impl SignalAttr { + fn extract_items<'a>( + attrs: impl IntoIterator, + ) -> syn::Result>> { + let attr = match attrs .into_iter() .find(|attr| attr.path().is_ident("signal")) { Some(attr) => attr, None => return Ok(None), }; - match &attribute.meta { - syn::Meta::Path(_) => Ok(Some(Self::default())), - syn::Meta::List(meta_list) => Ok(Some(meta_list.parse_args::()?)), - syn::Meta::NameValue(meta_name_value) => { + match &attr.meta { + syn::Meta::Path(_) => Ok(Some(Vec::new())), + syn::Meta::List(meta_list) => { + let attrs: Punctuated = + meta_list.parse_args_with(Punctuated::parse_separated_nonempty)?; + Ok(Some(attrs.into_iter().collect())) + } + syn::Meta::NameValue(_) => { return Err(syn::Error::new_spanned( - meta_name_value, - "Invalid #[signal] syntax", + attr, + "expected #[signal] or #[signal(, ...)]", )) } } } } -impl Parse for SignalTagInfo { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - let attrs = - syn::punctuated::Punctuated::::parse_separated_nonempty(input)?; - let mut value = Self::default(); - for attr in attrs { - value.set_from_attr(attr); - } - Ok(value) +bitflags! { + #[derive(Default, Clone, Copy, PartialEq, Eq)] + struct SignalFlags: u16 { + const RUN_FIRST = 1 << 0; + const RUN_LAST = 1 << 1; + const RUN_CLEANUP = 1 << 2; + const NO_RECURSE = 1 << 3; + const DETAILED = 1 << 4; + const ACTION = 1 << 5; + const NO_HOOKS = 1 << 6; } } @@ -193,13 +165,12 @@ struct SignalDesc { name: String, param_types: Vec, return_type: Option, - flags: SignalFlags, + flags: Vec, class_handler: Option, - accum: Option, } impl SignalDesc { fn new( - tag_info: SignalTagInfo, + flags: Vec, signature: &syn::Signature, complete: bool, ) -> syn::Result { @@ -225,7 +196,7 @@ impl SignalDesc { .iter() .skip(1) .map(|arg| match arg { - syn::FnArg::Receiver(receiver) => panic!("unexpected receiver"), + syn::FnArg::Receiver(_) => panic!("unexpected receiver"), syn::FnArg::Typed(pat_type) => (&*pat_type.ty).clone(), }) .collect() @@ -241,19 +212,14 @@ impl SignalDesc { }, }; - let flags = tag_info.flags; - let class_handler = complete.then(|| signature.ident.clone()); - let accum = tag_info.accum; - Ok(Self { name, param_types, return_type, flags, class_handler, - accum, }) } } @@ -272,14 +238,20 @@ pub fn impl_signals(attr: Args, input: syn::ItemImpl) -> syn::Result { - let attr_info = match SignalTagInfo::extract_from(&method.attrs)? { - Some(attr_info) => attr_info, + let flags = match SignalAttr::extract_items(&method.attrs)? { + Some(flags) => flags, None => { out_impl.items.push(item.clone()); continue; } }; - let desc = SignalDesc::new(attr_info, &method.sig, true)?; + let mut out_method = method.clone(); + out_method + .attrs + .retain(|item| !item.path().is_ident("signal")); + out_impl.items.push(syn::ImplItem::Fn(out_method)); + + let desc = SignalDesc::new(flags, &method.sig, true)?; out_signals.push(desc); } item @ syn::ImplItem::Verbatim(tokens) => { @@ -293,14 +265,14 @@ pub fn impl_signals(attr: Args, input: syn::ItemImpl) -> syn::Result attr_info, + let flags = match SignalAttr::extract_items(&method.attrs)? { + Some(flags) => flags, None => { out_impl.items.push(item.clone()); continue; } }; - let desc = SignalDesc::new(attr_info, &method.sig, false)?; + let desc = SignalDesc::new(flags, &method.sig, false)?; out_signals.push(desc); } item => out_impl.items.push(item.clone()), @@ -327,50 +299,104 @@ fn impl_object_signals<'a>( .into_iter() .map(|signal| { let name = syn::LitStr::new(&signal.name, Span::call_site()); - let run_first = signal.flags.contains(SignalFlags::RUN_FIRST).then(|| { - quote::quote! { - .run_first() - } - }); - let run_last = signal.flags.contains(SignalFlags::RUN_LAST).then(|| { - quote::quote! { - .run_last() - } - }); - let run_cleanup = signal.flags.contains(SignalFlags::RUN_CLEANUP).then(|| { - quote::quote! { - .run_cleanup() - } - }); - let no_recurse = signal.flags.contains(SignalFlags::NO_RECURSE).then(|| { - quote::quote! { - .no_recurse() - } - }); - let detailed = signal.flags.contains(SignalFlags::DETAILED).then(|| { - quote::quote! { - .detailed() - } - }); - let action = signal.flags.contains(SignalFlags::ACTION).then(|| { - quote::quote! { - .action() - } - }); - let no_hooks = signal.flags.contains(SignalFlags::NO_HOOKS).then(|| { - quote::quote! { - .no_hooks() - } - }); + let param_types = match signal.param_types.is_empty() { + true => None, + false => { + let param_types = &signal.param_types; + Some(quote::quote! { + .param_types([#(<#param_types as #glib::types::StaticType>::static_type()),*]) + }) + }, + }; + let return_type = match signal.return_type.as_ref() { + Some(rt) => { + Some(quote::quote! { + .return_type::<#rt>() + }) + }, + None => None, + }; + let flags = signal + .flags + .iter() + .map(|item| match item { + SignalAttr::RunFirst(ident) => quote::quote! { + .#ident() + }, + SignalAttr::RunLast(ident) => quote::quote! { + .#ident() + }, + SignalAttr::RunCleanup(ident) => quote::quote! { + .#ident() + }, + SignalAttr::NoRecurse(ident) => quote::quote! { + .#ident() + }, + SignalAttr::Detailed(ident) => quote::quote! { + .#ident() + }, + SignalAttr::Action(ident) => quote::quote! { + .#ident() + }, + SignalAttr::NoHooks(ident) => quote::quote! { + .#ident() + }, + SignalAttr::Accum(expr) => quote::quote! { + .accumulator(#expr) + }, + }) + .fold(TokenStream::new(), |mut curr, next| { + curr.extend(next); + curr + }); + let class_handler = match &signal.class_handler { + Some(handler_fn) => { + let mut param_idents: Vec = Vec::with_capacity(signal.param_types.len()); + let mut param_stmts: Vec = Vec::with_capacity(signal.param_types.len()); + + for i in 0..signal.param_types.len() { + let i_h = i + 1; + let ty = &signal.param_types[i]; + let ident = quote::format_ident!("param{}", i); + let err_msg = Literal::string(&format!( + "Parameter {} for signal did not match ({})", + i_h, + ty.to_token_stream().to_string() + )); + + let stmt = quote::quote! { + let #ident = values[#i_h].get::<#ty>() + .expect(#err_msg); + }; + + param_idents.push(ident); + param_stmts.push(stmt); + } + + Some(quote::quote! { + .class_handler(|values| { + let this = values[0].get::< + ::Type + >() + .expect("`Self` parameter for signal did not match"); + #(#param_stmts)* + let result = < + ::Type + as #glib::subclass::types::ObjectSubclassIsExt + >::imp(&this) + .#handler_fn(#(#param_idents),*); + None + }) + }) + }, + None => None, + }; quote::quote! { #glib::subclass::signal::Signal::builder(#name) - #run_first - #run_last - #run_cleanup - #no_recurse - #detailed - #action - #no_hooks + #param_types + #return_type + #flags + #class_handler .build() } }) diff --git a/glib-macros/tests/signals.rs b/glib-macros/tests/signals.rs index 2911f4c13809..5c0a00e4836c 100644 --- a/glib-macros/tests/signals.rs +++ b/glib-macros/tests/signals.rs @@ -11,7 +11,13 @@ mod base { #[glib::signals(wrapper_type = super::Base)] impl Base { #[signal(run_first, no_recurse, no_hooks)] - fn void_signal(&self); + fn one(&self) -> (); + #[signal(run_last, action)] + fn two(&self, pi: i32, pf: f32, ps: &str) -> i32; + #[signal] + fn three(&self, pf: f32) { + println!("pf = {}", pf); + } } #[glib::object_subclass] From b04277be1695dcf68e261c110d5f9dc363e83848 Mon Sep 17 00:00:00 2001 From: jgcodes2020 Date: Mon, 27 Jan 2025 15:05:28 -0500 Subject: [PATCH 4/8] refactor and prep for wrapper impl --- glib-macros/src/derived_signals_attribute.rs | 55 +++++++++++++++++++ glib-macros/src/lib.rs | 23 ++++++-- .../src/{signals.rs => signals_attribute.rs} | 14 ++++- glib-macros/tests/signals.rs | 2 + glib/src/lib.rs | 6 +- glib/src/subclass/mod.rs | 5 +- 6 files changed, 96 insertions(+), 9 deletions(-) create mode 100644 glib-macros/src/derived_signals_attribute.rs rename glib-macros/src/{signals.rs => signals_attribute.rs} (97%) diff --git a/glib-macros/src/derived_signals_attribute.rs b/glib-macros/src/derived_signals_attribute.rs new file mode 100644 index 000000000000..85a44796cb4b --- /dev/null +++ b/glib-macros/src/derived_signals_attribute.rs @@ -0,0 +1,55 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use proc_macro2::{Span, TokenStream}; +use quote::quote; + +pub const WRONG_PLACE_MSG: &str = + "This macro should be used on `impl` block for `glib::ObjectImpl` trait"; + +pub fn impl_derived_signals(input: &syn::ItemImpl) -> syn::Result { + let syn::ItemImpl { + attrs, + generics, + trait_, + self_ty, + items, + .. + } = input; + + let trait_path = &trait_ + .as_ref() + .ok_or_else(|| syn::Error::new(Span::call_site(), WRONG_PLACE_MSG))? + .1; + + let mut has_signals = false; + + for item in items { + if let syn::ImplItem::Fn(method) = item { + let ident = &method.sig.ident; + + if ident == "signals" { + has_signals = true; + } + } + } + + let glib = crate::utils::crate_ident_new(); + + let signals = quote!( + fn signals() -> &'static [#glib::subclass::signal::Signal] { + Self::derived_signals() + } + ); + + let generated = [ + (!has_signals).then_some(signals), + ]; + + Ok(quote!( + #(#attrs)* + impl #generics #trait_path for #self_ty { + #(#items)* + #(#generated)* + } + )) +} diff --git a/glib-macros/src/lib.rs b/glib-macros/src/lib.rs index a22943c4920e..51f8d763a5f2 100644 --- a/glib-macros/src/lib.rs +++ b/glib-macros/src/lib.rs @@ -5,6 +5,7 @@ mod boxed_derive; mod clone; mod closure; mod derived_properties_attribute; +mod derived_signals_attribute; mod downgrade_derive; mod enum_derive; mod error_domain_derive; @@ -12,7 +13,7 @@ mod flags_attribute; mod object_impl_attributes; mod properties; mod shared_boxed_derive; -mod signals; +mod signals_attribute; mod value_delegate_derive; mod variant_derive; @@ -1505,13 +1506,27 @@ pub fn derived_properties(_attr: TokenStream, item: TokenStream) -> TokenStream /// This macro enables you to implement object signals in a quick way. #[proc_macro_attribute] pub fn signals(attr: TokenStream, item: TokenStream) -> TokenStream { - let attr_input = syn::parse_macro_input!(attr as signals::Args); + let attr_input = syn::parse_macro_input!(attr as signals_attribute::Args); syn::parse::(item) .map_err(|_| { - syn::Error::new(Span::call_site(), signals::WRONG_PLACE_MSG) + syn::Error::new(Span::call_site(), signals_attribute::WRONG_PLACE_MSG) }) - .and_then(|item_input| signals::impl_signals(attr_input, item_input)) + .and_then(|item_input| signals_attribute::impl_signals(attr_input, item_input)) + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} + +#[proc_macro_attribute] +pub fn derived_signals(_attr: TokenStream, item: TokenStream) -> TokenStream { + syn::parse::(item) + .map_err(|_| { + syn::Error::new( + Span::call_site(), + derived_signals_attribute::WRONG_PLACE_MSG, + ) + }) + .and_then(|input| derived_signals_attribute::impl_derived_signals(&input)) .unwrap_or_else(syn::Error::into_compile_error) .into() } diff --git a/glib-macros/src/signals.rs b/glib-macros/src/signals_attribute.rs similarity index 97% rename from glib-macros/src/signals.rs rename to glib-macros/src/signals_attribute.rs index 8d91e644a0a9..7e32feb692da 100644 --- a/glib-macros/src/signals.rs +++ b/glib-macros/src/signals_attribute.rs @@ -1,5 +1,5 @@ use bitflags::bitflags; -use proc_macro2::{Literal, Punct, Span, TokenStream, TokenTree}; +use proc_macro2::{Literal, Span, TokenStream}; use quote::ToTokens; use syn::{ext::IdentExt, parse::Parse, punctuated::Punctuated, Token}; @@ -8,6 +8,9 @@ use crate::utils::crate_ident_new; pub const WRONG_PLACE_MSG: &str = "This macro should be used on a plain `impl` block of the inner object type"; +/// An incomplete function in an impl block. +/// This is used to declare a signal with no class handler. +#[allow(unused)] struct ImplItemIncompleteFn { pub attrs: Vec, pub vis: syn::Visibility, @@ -417,3 +420,12 @@ fn impl_object_signals<'a>( } } } + +fn impl_signal_wrapper<'a>( + args: Args, + glib: &TokenStream, + ty: &syn::Type, + signals: impl IntoIterator +) -> TokenStream { + todo!() +} diff --git a/glib-macros/tests/signals.rs b/glib-macros/tests/signals.rs index 5c0a00e4836c..c10368ee0da7 100644 --- a/glib-macros/tests/signals.rs +++ b/glib-macros/tests/signals.rs @@ -4,6 +4,7 @@ mod base { pub mod imp { use super::*; + use glib::subclass::prelude::*; #[derive(Default)] pub struct Base {} @@ -26,6 +27,7 @@ mod base { type Type = super::Base; } + #[glib::derived_signals] impl ObjectImpl for Base { fn constructed(&self) { } diff --git a/glib/src/lib.rs b/glib/src/lib.rs index a4b18598e74b..d057de17cef7 100644 --- a/glib/src/lib.rs +++ b/glib/src/lib.rs @@ -30,9 +30,9 @@ pub use bitflags; #[doc(hidden)] pub use glib_macros::cstr_bytes; pub use glib_macros::{ - async_test, clone, closure, closure_local, derived_properties, flags, signals, object_interface, - object_subclass, Boxed, Downgrade, Enum, ErrorDomain, Properties, SharedBoxed, ValueDelegate, - Variant, + async_test, clone, closure, closure_local, derived_properties, derived_signals, flags, + object_interface, object_subclass, signals, Boxed, Downgrade, Enum, ErrorDomain, Properties, + SharedBoxed, ValueDelegate, Variant, }; pub use glib_sys as ffi; pub use gobject_sys as gobject_ffi; diff --git a/glib/src/subclass/mod.rs b/glib/src/subclass/mod.rs index 2ac82b03ee0f..977fd80bbcaa 100644 --- a/glib/src/subclass/mod.rs +++ b/glib/src/subclass/mod.rs @@ -445,7 +445,10 @@ pub mod prelude { pub use super::{ boxed::BoxedType, interface::{ObjectInterface, ObjectInterfaceExt, ObjectInterfaceType}, - object::{DerivedObjectProperties, ObjectClassSubclassExt, ObjectImpl, ObjectImplExt}, + object::{ + DerivedObjectProperties, DerivedObjectSignals, ObjectClassSubclassExt, ObjectImpl, + ObjectImplExt, + }, shared::{RefCounted, SharedType}, type_module::{TypeModuleImpl, TypeModuleImplExt}, type_plugin::{TypePluginImpl, TypePluginImplExt, TypePluginRegisterImpl}, From b7bc8782db805071d628f4d5ee34e6238b08d907 Mon Sep 17 00:00:00 2001 From: jgcodes2020 Date: Mon, 27 Jan 2025 21:34:20 -0500 Subject: [PATCH 5/8] implement connect/emit on the wrapper type --- glib-macros/src/signals_attribute.rs | 106 +++++++++++++++++++++++---- glib-macros/tests/signals.rs | 26 ++++--- 2 files changed, 106 insertions(+), 26 deletions(-) diff --git a/glib-macros/src/signals_attribute.rs b/glib-macros/src/signals_attribute.rs index 7e32feb692da..4e28648f420a 100644 --- a/glib-macros/src/signals_attribute.rs +++ b/glib-macros/src/signals_attribute.rs @@ -166,6 +166,7 @@ bitflags! { /// method signature and signal tag info. struct SignalDesc { name: String, + rs_name: String, param_types: Vec, return_type: Option, flags: Vec, @@ -190,7 +191,8 @@ impl SignalDesc { } // for now, get name from signature - let name = signature.ident.to_string().replace('_', "-"); + let rs_name = signature.ident.to_string(); + let name = rs_name.replace('_', "-"); // parameters are remaining signature types let param_types = if signature.inputs.len() >= 2 { @@ -219,6 +221,7 @@ impl SignalDesc { Ok(Self { name, + rs_name, param_types, return_type, flags, @@ -286,20 +289,26 @@ pub fn impl_signals(attr: Args, input: syn::ItemImpl) -> syn::Result( glib: &TokenStream, ty: &syn::Type, - signals: impl IntoIterator, + signals: impl IntoIterator< + Item = &'a SignalDesc, + IntoIter = impl Iterator + ExactSizeIterator + >, ) -> TokenStream { - let builders: Vec<_> = signals - .into_iter() + let signal_iter = signals.into_iter(); + let count = signal_iter.len(); + let builders = signal_iter .map(|signal| { let name = syn::LitStr::new(&signal.name, Span::call_site()); let param_types = match signal.param_types.is_empty() { @@ -347,10 +356,6 @@ fn impl_object_signals<'a>( SignalAttr::Accum(expr) => quote::quote! { .accumulator(#expr) }, - }) - .fold(TokenStream::new(), |mut curr, next| { - curr.extend(next); - curr }); let class_handler = match &signal.class_handler { Some(handler_fn) => { @@ -398,13 +403,11 @@ fn impl_object_signals<'a>( #glib::subclass::signal::Signal::builder(#name) #param_types #return_type - #flags + #(#flags)* #class_handler .build() } - }) - .collect(); - let count = builders.len(); + }); quote::quote! { #[automatically_derived] impl #glib::subclass::object::DerivedObjectSignals for #ty { @@ -424,8 +427,83 @@ fn impl_object_signals<'a>( fn impl_signal_wrapper<'a>( args: Args, glib: &TokenStream, - ty: &syn::Type, signals: impl IntoIterator ) -> TokenStream { - todo!() + let signal_iter = signals.into_iter(); + let methods = signal_iter + .map(|signal| { + let connect_id = quote::format_ident!("connect_{}", &signal.rs_name); + let emit_id = quote::format_ident!("emit_{}", &signal.rs_name); + + let signal_name = Literal::string(&signal.name); + + let closure_bound = { + let param_types = &signal.param_types; + let return_type = signal + .return_type + .as_ref() + .map_or_else( + || quote::quote! { () }, + |value| value.to_token_stream() + ); + quote::quote! { + Fn(&Self, #(#param_types),*) -> #return_type + 'static + } + }; + + let param_types = &signal.param_types; + + let return_type = signal + .return_type + .as_ref() + .map_or_else( + || quote::quote! { () }, + |value| value.to_token_stream() + ); + + let closure_params: Vec<_> = (0..signal.param_types.len()) + .map(|i| quote::format_ident!("param{}", i)) + .collect(); + + let emit_coda = signal + .return_type + .as_ref() + .map_or_else( + || quote::quote! { ; }, + |ty| quote::quote! { + .unwrap().get::<#ty>().unwrap() + } + ); + + + quote::quote! { + pub fn #connect_id(&self, f: F) -> #glib::SignalHandlerId { + ::connect_closure( + self, + #signal_name, + false, + #glib::closure_local!(|this: &Self, #(#closure_params: #param_types),*| -> #return_type { + f(this, #(#closure_params),*) + }) + ) + } + pub fn #emit_id(&self, #(#closure_params: #param_types),*) -> #return_type { + ::emit_by_name_with_values( + self, + #signal_name, + &[ + #(#glib::value::ToValue::to_value(&#closure_params)),* + ] + ) + #emit_coda + } + } + }); + let ty = &args.wrapper_ty; + + quote::quote! { + impl #ty { + #(#methods)* + } + } } diff --git a/glib-macros/tests/signals.rs b/glib-macros/tests/signals.rs index c10368ee0da7..35fe6546c45a 100644 --- a/glib-macros/tests/signals.rs +++ b/glib-macros/tests/signals.rs @@ -1,24 +1,23 @@ mod base { + use std::sync::LazyLock; + + use glib::object::ObjectSubclassIs; use glib::prelude::*; - use glib::subclass::prelude::*; + use glib::subclass::{prelude::*, SignalId}; pub mod imp { use super::*; - use glib::subclass::prelude::*; #[derive(Default)] pub struct Base {} #[glib::signals(wrapper_type = super::Base)] impl Base { - #[signal(run_first, no_recurse, no_hooks)] - fn one(&self) -> (); - #[signal(run_last, action)] - fn two(&self, pi: i32, pf: f32, ps: &str) -> i32; + #[signal(run_first, action)] + fn run_first(&self) -> (); + #[signal] - fn three(&self, pf: f32) { - println!("pf = {}", pf); - } + fn has_params(&self, int: i32, float: f32) -> i32; } #[glib::object_subclass] @@ -29,8 +28,7 @@ mod base { #[glib::derived_signals] impl ObjectImpl for Base { - fn constructed(&self) { - } + fn constructed(&self) {} } } @@ -39,4 +37,8 @@ mod base { } } -fn main() {} +fn main() { + let foo = glib::Object::new::(); + + foo.emit_run_first(); +} From 716acfadb2f7903245c6efec56e40b24273ed66c Mon Sep 17 00:00:00 2001 From: jgcodes2020 Date: Tue, 28 Jan 2025 21:55:18 -0500 Subject: [PATCH 6/8] add an actual test --- glib-macros/tests/signals.rs | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/glib-macros/tests/signals.rs b/glib-macros/tests/signals.rs index 35fe6546c45a..3db32e77c066 100644 --- a/glib-macros/tests/signals.rs +++ b/glib-macros/tests/signals.rs @@ -1,3 +1,7 @@ +use std::{cell::Cell, rc::Rc}; + +use glib::object::ObjectExt; + mod base { use std::sync::LazyLock; @@ -37,8 +41,27 @@ mod base { } } -fn main() { +#[test] +fn basic_test() { let foo = glib::Object::new::(); + let check: Rc> = Rc::new(Cell::new(false)); + + let h_id = foo.connect_run_first({ + let check = Rc::clone(&check); + move |_| { + check.set(true); + } + }); + foo.emit_run_first(); + assert_eq!(check.get(), true, "Signal handler should have run"); + + foo.disconnect(h_id); + check.set(false); + + foo.emit_run_first(); + assert_eq!(check.get(), false, "Signal handler should not have run"); + + } From 0f657685404b4e97a3e195519aef61404177744b Mon Sep 17 00:00:00 2001 From: jgcodes2020 Date: Tue, 28 Jan 2025 22:10:41 -0500 Subject: [PATCH 7/8] fix cargo fmt errors --- glib-macros/src/derived_signals_attribute.rs | 4 +- glib-macros/src/lib.rs | 4 +- glib-macros/src/signals_attribute.rs | 204 +++++++++---------- glib-macros/tests/signals.rs | 2 - 4 files changed, 102 insertions(+), 112 deletions(-) diff --git a/glib-macros/src/derived_signals_attribute.rs b/glib-macros/src/derived_signals_attribute.rs index 85a44796cb4b..e2a9d3bf9164 100644 --- a/glib-macros/src/derived_signals_attribute.rs +++ b/glib-macros/src/derived_signals_attribute.rs @@ -41,9 +41,7 @@ pub fn impl_derived_signals(input: &syn::ItemImpl) -> syn::Result { } ); - let generated = [ - (!has_signals).then_some(signals), - ]; + let generated = [(!has_signals).then_some(signals)]; Ok(quote!( #(#attrs)* diff --git a/glib-macros/src/lib.rs b/glib-macros/src/lib.rs index 51f8d763a5f2..3ca4d9f94226 100644 --- a/glib-macros/src/lib.rs +++ b/glib-macros/src/lib.rs @@ -1509,9 +1509,7 @@ pub fn signals(attr: TokenStream, item: TokenStream) -> TokenStream { let attr_input = syn::parse_macro_input!(attr as signals_attribute::Args); syn::parse::(item) - .map_err(|_| { - syn::Error::new(Span::call_site(), signals_attribute::WRONG_PLACE_MSG) - }) + .map_err(|_| syn::Error::new(Span::call_site(), signals_attribute::WRONG_PLACE_MSG)) .and_then(|item_input| signals_attribute::impl_signals(attr_input, item_input)) .unwrap_or_else(syn::Error::into_compile_error) .into() diff --git a/glib-macros/src/signals_attribute.rs b/glib-macros/src/signals_attribute.rs index 4e28648f420a..641ed054d78d 100644 --- a/glib-macros/src/signals_attribute.rs +++ b/glib-macros/src/signals_attribute.rs @@ -302,112 +302,108 @@ fn impl_object_signals<'a>( glib: &TokenStream, ty: &syn::Type, signals: impl IntoIterator< - Item = &'a SignalDesc, - IntoIter = impl Iterator + ExactSizeIterator + Item = &'a SignalDesc, + IntoIter = impl Iterator + ExactSizeIterator, >, ) -> TokenStream { let signal_iter = signals.into_iter(); let count = signal_iter.len(); - let builders = signal_iter - .map(|signal| { - let name = syn::LitStr::new(&signal.name, Span::call_site()); - let param_types = match signal.param_types.is_empty() { - true => None, - false => { - let param_types = &signal.param_types; - Some(quote::quote! { - .param_types([#(<#param_types as #glib::types::StaticType>::static_type()),*]) - }) - }, - }; - let return_type = match signal.return_type.as_ref() { - Some(rt) => { - Some(quote::quote! { - .return_type::<#rt>() - }) - }, - None => None, - }; - let flags = signal - .flags - .iter() - .map(|item| match item { - SignalAttr::RunFirst(ident) => quote::quote! { - .#ident() - }, - SignalAttr::RunLast(ident) => quote::quote! { - .#ident() - }, - SignalAttr::RunCleanup(ident) => quote::quote! { - .#ident() - }, - SignalAttr::NoRecurse(ident) => quote::quote! { - .#ident() - }, - SignalAttr::Detailed(ident) => quote::quote! { - .#ident() - }, - SignalAttr::Action(ident) => quote::quote! { - .#ident() - }, - SignalAttr::NoHooks(ident) => quote::quote! { - .#ident() - }, - SignalAttr::Accum(expr) => quote::quote! { - .accumulator(#expr) - }, - }); - let class_handler = match &signal.class_handler { - Some(handler_fn) => { - let mut param_idents: Vec = Vec::with_capacity(signal.param_types.len()); - let mut param_stmts: Vec = Vec::with_capacity(signal.param_types.len()); - - for i in 0..signal.param_types.len() { - let i_h = i + 1; - let ty = &signal.param_types[i]; - let ident = quote::format_ident!("param{}", i); - let err_msg = Literal::string(&format!( - "Parameter {} for signal did not match ({})", - i_h, - ty.to_token_stream().to_string() - )); - - let stmt = quote::quote! { - let #ident = values[#i_h].get::<#ty>() - .expect(#err_msg); - }; - - param_idents.push(ident); - param_stmts.push(stmt); - } + let builders = signal_iter.map(|signal| { + let name = syn::LitStr::new(&signal.name, Span::call_site()); + let param_types = match signal.param_types.is_empty() { + true => None, + false => { + let param_types = &signal.param_types; + Some(quote::quote! { + .param_types([#(<#param_types as #glib::types::StaticType>::static_type()),*]) + }) + } + }; + let return_type = match signal.return_type.as_ref() { + Some(rt) => Some(quote::quote! { + .return_type::<#rt>() + }), + None => None, + }; + let flags = signal.flags.iter().map(|item| match item { + SignalAttr::RunFirst(ident) => quote::quote! { + .#ident() + }, + SignalAttr::RunLast(ident) => quote::quote! { + .#ident() + }, + SignalAttr::RunCleanup(ident) => quote::quote! { + .#ident() + }, + SignalAttr::NoRecurse(ident) => quote::quote! { + .#ident() + }, + SignalAttr::Detailed(ident) => quote::quote! { + .#ident() + }, + SignalAttr::Action(ident) => quote::quote! { + .#ident() + }, + SignalAttr::NoHooks(ident) => quote::quote! { + .#ident() + }, + SignalAttr::Accum(expr) => quote::quote! { + .accumulator(#expr) + }, + }); + let class_handler = match &signal.class_handler { + Some(handler_fn) => { + let mut param_idents: Vec = + Vec::with_capacity(signal.param_types.len()); + let mut param_stmts: Vec = + Vec::with_capacity(signal.param_types.len()); + + for i in 0..signal.param_types.len() { + let i_h = i + 1; + let ty = &signal.param_types[i]; + let ident = quote::format_ident!("param{}", i); + let err_msg = Literal::string(&format!( + "Parameter {} for signal did not match ({})", + i_h, + ty.to_token_stream().to_string() + )); + + let stmt = quote::quote! { + let #ident = values[#i_h].get::<#ty>() + .expect(#err_msg); + }; + + param_idents.push(ident); + param_stmts.push(stmt); + } - Some(quote::quote! { - .class_handler(|values| { - let this = values[0].get::< - ::Type - >() - .expect("`Self` parameter for signal did not match"); - #(#param_stmts)* - let result = < - ::Type - as #glib::subclass::types::ObjectSubclassIsExt - >::imp(&this) - .#handler_fn(#(#param_idents),*); - None - }) + Some(quote::quote! { + .class_handler(|values| { + let this = values[0].get::< + ::Type + >() + .expect("`Self` parameter for signal did not match"); + #(#param_stmts)* + let result = < + ::Type + as #glib::subclass::types::ObjectSubclassIsExt + >::imp(&this) + .#handler_fn(#(#param_idents),*); + None }) - }, - None => None, - }; - quote::quote! { - #glib::subclass::signal::Signal::builder(#name) - #param_types - #return_type - #(#flags)* - #class_handler - .build() + }) } - }); + None => None, + }; + quote::quote! { + #glib::subclass::signal::Signal::builder(#name) + #param_types + #return_type + #(#flags)* + #class_handler + .build() + } + }); quote::quote! { #[automatically_derived] impl #glib::subclass::object::DerivedObjectSignals for #ty { @@ -425,9 +421,9 @@ fn impl_object_signals<'a>( } fn impl_signal_wrapper<'a>( - args: Args, + args: Args, glib: &TokenStream, - signals: impl IntoIterator + signals: impl IntoIterator, ) -> TokenStream { let signal_iter = signals.into_iter(); let methods = signal_iter @@ -443,7 +439,7 @@ fn impl_signal_wrapper<'a>( .return_type .as_ref() .map_or_else( - || quote::quote! { () }, + || quote::quote! { () }, |value| value.to_token_stream() ); quote::quote! { @@ -457,7 +453,7 @@ fn impl_signal_wrapper<'a>( .return_type .as_ref() .map_or_else( - || quote::quote! { () }, + || quote::quote! { () }, |value| value.to_token_stream() ); @@ -483,7 +479,7 @@ fn impl_signal_wrapper<'a>( #signal_name, false, #glib::closure_local!(|this: &Self, #(#closure_params: #param_types),*| -> #return_type { - f(this, #(#closure_params),*) + f(this, #(#closure_params),*) }) ) } diff --git a/glib-macros/tests/signals.rs b/glib-macros/tests/signals.rs index 3db32e77c066..938dd30cba28 100644 --- a/glib-macros/tests/signals.rs +++ b/glib-macros/tests/signals.rs @@ -62,6 +62,4 @@ fn basic_test() { foo.emit_run_first(); assert_eq!(check.get(), false, "Signal handler should not have run"); - - } From e2731927bca314383ca2e68451f6713264c4064a Mon Sep 17 00:00:00 2001 From: jgcodes2020 Date: Tue, 28 Jan 2025 22:44:53 -0500 Subject: [PATCH 8/8] add license headers to files that were missing one --- glib-macros/src/signals_attribute.rs | 2 ++ glib-macros/tests/signals.rs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/glib-macros/src/signals_attribute.rs b/glib-macros/src/signals_attribute.rs index 641ed054d78d..029265703f4a 100644 --- a/glib-macros/src/signals_attribute.rs +++ b/glib-macros/src/signals_attribute.rs @@ -1,3 +1,5 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + use bitflags::bitflags; use proc_macro2::{Literal, Span, TokenStream}; use quote::ToTokens; diff --git a/glib-macros/tests/signals.rs b/glib-macros/tests/signals.rs index 938dd30cba28..bd087af161b2 100644 --- a/glib-macros/tests/signals.rs +++ b/glib-macros/tests/signals.rs @@ -1,3 +1,5 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + use std::{cell::Cell, rc::Rc}; use glib::object::ObjectExt;