From 2fd321847507ba2efc6cc6ae47151dd129adbfd6 Mon Sep 17 00:00:00 2001
From: Philipp Jungkamp
Date: Fri, 22 Nov 2024 18:00:36 +0100
Subject: [PATCH] Add initial glib::signals! macro
---
examples/object_subclass/author.rs | 16 +-
glib-macros/src/lib.rs | 19 +-
glib-macros/src/signals.rs | 482 +++++++++++++++++++++++++++++
glib/src/lib.rs | 4 +-
glib/src/subclass/object.rs | 12 +
5 files changed, 525 insertions(+), 8 deletions(-)
create mode 100644 glib-macros/src/signals.rs
diff --git a/examples/object_subclass/author.rs b/examples/object_subclass/author.rs
index 86b6001149b5..e311acb8658b 100644
--- a/examples/object_subclass/author.rs
+++ b/examples/object_subclass/author.rs
@@ -4,14 +4,13 @@
use glib::prelude::*;
use glib::subclass::prelude::*;
use glib::subclass::Signal;
-use glib::Properties;
+use glib::subclass::object::DerivedObjectSignals;
use std::cell::RefCell;
-use std::sync::OnceLock;
mod imp {
use super::*;
- #[derive(Properties, Default)]
+ #[derive(glib::Properties, Default)]
#[properties(wrapper_type = super::Author)]
pub struct Author {
#[property(get, set)]
@@ -20,11 +19,17 @@ mod imp {
surname: RefCell,
}
+ #[glib::signals(wrapper_type = super::Author)]
+ impl Author {
+
+ #[signal]
+ fn awarded(&self) {}
+ }
+
#[glib::derived_properties]
impl ObjectImpl for Author {
fn signals() -> &'static [Signal] {
- static SIGNALS: OnceLock> = OnceLock::new();
- SIGNALS.get_or_init(|| vec![Signal::builder("awarded").build()])
+ Self::derived_signals()
}
}
@@ -38,6 +43,7 @@ mod imp {
glib::wrapper! {
pub struct Author(ObjectSubclass);
}
+
impl Author {
pub fn new(name: &str, surname: &str) -> Self {
glib::Object::builder()
diff --git a/glib-macros/src/lib.rs b/glib-macros/src/lib.rs
index 8f66dc855fd5..b77543915135 100644
--- a/glib-macros/src/lib.rs
+++ b/glib-macros/src/lib.rs
@@ -14,13 +14,14 @@ mod properties;
mod shared_boxed_derive;
mod value_delegate_derive;
mod variant_derive;
+mod signals;
mod utils;
use flags_attribute::AttrInput;
use proc_macro::TokenStream;
use proc_macro2::Span;
-use syn::{parse_macro_input, DeriveInput};
+use syn::{parse_macro_input, DeriveInput, Error};
use utils::{parse_nested_meta_items_from_stream, NestedMetaItem};
/// Macro for passing variables as strong or weak references into a closure.
@@ -1587,3 +1588,19 @@ pub fn derive_value_delegate(input: TokenStream) -> TokenStream {
pub fn async_test(args: TokenStream, item: TokenStream) -> TokenStream {
async_test::async_test(args, item)
}
+
+#[proc_macro_attribute]
+pub fn signals(attr: TokenStream, item: TokenStream) -> TokenStream {
+ let args = parse_macro_input!(attr as signals::SignalsArgs);
+ match syn::parse::(item) {
+ Ok(input) => signals::impl_signals(input, args)
+ .unwrap_or_else(syn::Error::into_compile_error)
+ .into(),
+ Err(_) => Error::new(
+ Span::call_site(),
+ signals::WRONG_PLACE_MSG,
+ )
+ .into_compile_error()
+ .into(),
+ }
+}
diff --git a/glib-macros/src/signals.rs b/glib-macros/src/signals.rs
new file mode 100644
index 000000000000..a2707318e163
--- /dev/null
+++ b/glib-macros/src/signals.rs
@@ -0,0 +1,482 @@
+// Take a look at the license at the top of the repository in the LICENSE file.
+#![allow(unused)]
+
+use std::sync;
+
+use proc_macro2::{Span, TokenStream};
+use quote::{quote, ToTokens, TokenStreamExt};
+use syn::{parse::Parse, spanned::Spanned, ExprAsync, Result};
+
+use crate::utils::*;
+
+pub const WRONG_PLACE_MSG: &str =
+ "This macro should be used on the `impl` block for the `ObjectSubclassImpl` type of a `glib::wrapper!` subclass";
+
+pub struct SignalsArgs {
+ wrapper_type: syn::Type,
+}
+
+impl Parse for SignalsArgs {
+ fn parse(input: syn::parse::ParseStream) -> Result {
+ let mut wrapper_type = None;
+
+ while !input.is_empty() {
+ let ident = input.parse::()?;
+
+ if ident == "wrapper_type" {
+ let _eq = input.parse::()?;
+ wrapper_type = Some(input.parse::()?);
+ }
+
+ if input.peek(syn::Token![,]) {
+ input.parse::()?;
+ }
+ }
+
+ Ok(Self {
+ wrapper_type: wrapper_type.ok_or_else(|| {
+ syn::Error::new(
+ input.span(),
+ "missing wrapper_type in #[signal(wrapper_type = ...)]",
+ )
+ })?,
+ })
+ }
+}
+
+enum SignalArg {
+ Name(syn::Ident, syn::Token![=], syn::LitStr),
+ RunFirst(syn::Ident),
+ RunLast(syn::Ident),
+ RunCleanup(syn::Ident),
+ NoRecurse(syn::Ident),
+ Detailed(syn::Ident),
+ Action(syn::Ident),
+ NoHooks(syn::Ident),
+ MustCollect(syn::Ident),
+ Deprecated(syn::Ident),
+ Accumulator(syn::Ident, syn::Token![=], syn::Expr),
+}
+
+impl SignalArg {
+ fn span(&self) -> Span {
+ match self {
+ Self::Name(ident, _, _)
+ | Self::RunFirst(ident)
+ | Self::RunLast(ident)
+ | Self::RunCleanup(ident)
+ | Self::NoRecurse(ident)
+ | Self::Detailed(ident)
+ | Self::Action(ident)
+ | Self::NoHooks(ident)
+ | Self::MustCollect(ident)
+ | Self::Deprecated(ident)
+ | Self::Accumulator(ident, _, _) => ident.span(),
+ }
+ }
+}
+
+impl Parse for SignalArg {
+ fn parse(input: syn::parse::ParseStream) -> Result {
+ let ident = input.parse::()?;
+
+ if ident == "name" {
+ let eq_token = input.parse::()?;
+ let lit_str = input.parse::()?;
+ Ok(SignalArg::Name(ident, eq_token, lit_str))
+ } else if ident == "run_first" {
+ Ok(SignalArg::RunFirst(ident))
+ } else if ident == "run_last" {
+ Ok(SignalArg::RunLast(ident))
+ } else if ident == "run_cleanup" {
+ Ok(SignalArg::RunCleanup(ident))
+ } else if ident == "no_recurse" {
+ Ok(SignalArg::NoRecurse(ident))
+ } else if ident == "detailed" {
+ Ok(SignalArg::Detailed(ident))
+ } else if ident == "action" {
+ Ok(SignalArg::Action(ident))
+ } else if ident == "no_hooks" {
+ Ok(SignalArg::NoHooks(ident))
+ } else if ident == "must_collect" {
+ Ok(SignalArg::MustCollect(ident))
+ } else if ident == "deprecated" {
+ Ok(SignalArg::Deprecated(ident))
+ } else if ident == "accumulator" {
+ let eq_token = input.parse::()?;
+ let expr = input.parse::()?;
+ Ok(SignalArg::Accumulator(ident, eq_token, expr))
+ } else {
+ Err(syn::Error::new_spanned(ident, "invalid argument"))
+ }
+ }
+}
+
+#[derive(Default)]
+struct SignalArgs(syn::punctuated::Punctuated);
+
+impl Parse for SignalArgs {
+ fn parse(input: syn::parse::ParseStream) -> Result {
+ input
+ .parse_terminated(SignalArg::parse, syn::Token![,])
+ .map(Self)
+ }
+}
+
+struct SignalBuilder {
+ wrapper_type: syn::Type,
+ impl_type: syn::Type,
+ fn_ident: syn::Ident,
+ param_types: Vec,
+ return_type: Option,
+ class_handler: bool,
+
+ name: Option,
+ run_first: Option,
+ run_last: Option,
+ run_cleanup: Option,
+ no_recurse: Option,
+ detailed: Option,
+ action: Option,
+ no_hooks: Option,
+ must_collect: Option,
+ deprecated: Option,
+ accumulator: Option<(syn::Ident, syn::Expr)>,
+}
+
+impl SignalBuilder {
+ fn new(
+ fn_: &syn::ImplItemFn,
+ impl_type: syn::Type,
+ wrapper_type: syn::Type,
+ args: Vec,
+ ) -> Result {
+ if !fn_.sig.inputs.get(0).is_some_and(|fn_arg| {
+ matches!(
+ fn_arg,
+ syn::FnArg::Receiver(syn::Receiver {
+ reference: Some(_),
+ mutability: None,
+ ..
+ })
+ )
+ }) {
+ return Err(syn::Error::new_spanned(
+ fn_,
+ "A #[signal] function needs a &self parameter",
+ ));
+ }
+
+ let fn_ident = fn_.sig.ident.clone();
+
+ let param_types = fn_
+ .sig
+ .inputs
+ .iter()
+ .filter_map(|fn_arg| match fn_arg {
+ syn::FnArg::Receiver(_) => None,
+ syn::FnArg::Typed(pat) => Some(pat.ty.as_ref().clone()),
+ })
+ .collect();
+
+ let return_type = match &fn_.sig.output {
+ syn::ReturnType::Default => None,
+ syn::ReturnType::Type(_, type_) => Some(type_.as_ref().clone()),
+ };
+
+ let class_handler = !fn_.block.stmts.is_empty();
+
+ let mut builder = Self {
+ wrapper_type,
+ impl_type,
+ fn_ident,
+ param_types,
+ return_type,
+ class_handler,
+ name: None,
+ run_first: None,
+ run_last: None,
+ run_cleanup: None,
+ no_recurse: None,
+ detailed: None,
+ action: None,
+ no_hooks: None,
+ must_collect: None,
+ deprecated: None,
+ accumulator: None,
+ };
+
+ for arg in args {
+ builder.add_arg(arg)?;
+ }
+
+ Ok(builder)
+ }
+
+ fn add_arg(&mut self, arg: SignalArg) -> Result<()> {
+ match arg {
+ SignalArg::Name(_, _, lit_str) => {
+ self.name = Some(lit_str);
+ }
+
+ SignalArg::RunFirst(ident) if self.run_first.is_none() => {
+ self.run_first = Some(ident);
+ }
+
+ SignalArg::RunLast(ident) if self.run_last.is_none() => {
+ self.run_last = Some(ident);
+ }
+
+ SignalArg::RunCleanup(ident) if self.run_cleanup.is_none() => {
+ self.run_cleanup = Some(ident);
+ }
+
+ SignalArg::NoRecurse(ident) if self.no_recurse.is_none() => {
+ self.no_recurse = Some(ident);
+ }
+
+ SignalArg::Detailed(ident) if self.detailed.is_none() => {
+ self.detailed = Some(ident);
+ }
+
+ SignalArg::Action(ident) if self.action.is_none() => {
+ self.action = Some(ident);
+ }
+
+ SignalArg::NoHooks(ident) if self.no_hooks.is_none() => {
+ self.no_hooks = Some(ident);
+ }
+
+ SignalArg::MustCollect(ident) if self.must_collect.is_none() => {
+ self.must_collect = Some(ident);
+ }
+
+ SignalArg::Deprecated(ident) if self.deprecated.is_none() => {
+ self.deprecated = Some(ident);
+ }
+
+ SignalArg::Accumulator(ident, _, expr) if self.accumulator.is_none() => {
+ self.accumulator = Some((ident, expr));
+ }
+
+ duplicate => {
+ return Err(syn::Error::new(
+ duplicate.span(),
+ "found duplicate argument for #[signal]",
+ ))
+ }
+ }
+
+ Ok(())
+ }
+
+ fn expr(&self) -> TokenStream {
+ let crate_ident = crate_ident_new();
+
+ let Self {
+ wrapper_type,
+ impl_type,
+ fn_ident,
+ param_types,
+ return_type,
+ class_handler,
+ name,
+ run_first,
+ run_last,
+ run_cleanup,
+ no_recurse,
+ detailed,
+ action,
+ no_hooks,
+ must_collect,
+ deprecated,
+ accumulator,
+ } = self;
+
+ let name = name.clone().unwrap_or_else(|| {
+ syn::LitStr::new(
+ &fn_ident.to_string().trim_matches('_').replace('_', "-"),
+ fn_ident.span(),
+ )
+ });
+
+ let param_static_types = (!param_types.is_empty()).then(|| {
+ param_types.iter().map(|type_| {
+ quote! { <#type_ as #crate_ident::types::StaticType>::static_type() }
+ })
+ });
+
+ let class_handler_wrapper = class_handler.then(|| {
+ let wrapper_ident = syn::Ident::new("wrapper", Span::mixed_site());
+ let params_ident = syn::Ident::new("params", Span::mixed_site());
+ let param_idents = (0..param_types.len())
+ .map(|idx| quote::format_ident!("arg{}", idx, span = Span::mixed_site()))
+ .collect::>();
+ let return_ident = syn::Ident::new("return_", Span::mixed_site());
+
+ let param_bindings = param_types.iter().zip(¶m_idents).enumerate().map(|(idx, (type_, ident))| {
+ let real_idx = idx + 1;
+ quote! { let #ident: #type_ = #params_ident[#real_idx].get().unwrap(); }
+ });
+
+ let return_wrapper = if *class_handler {
+ quote! {
+ |#return_ident| {
+ Some(<#return_type as #crate_ident::value::ToValue>::to_value(return_ident))
+ }
+ }
+ } else {
+ quote! { (|()| None) }
+ };
+
+ let unwrapped = quote! {
+ #impl_type::#fn_ident(
+ <#impl_type as #crate_ident::subclass::types::ObjectSubclassExt>::from_obj(wrapper_ident),
+ #(#param_idents,)*
+ )
+ };
+
+ quote! {
+ |#params_ident: &[#crate_ident::value::Value]| -> ::std::option::Option<#crate_ident::value::Value> {
+ let #wrapper_ident: #wrapper_type = #params_ident[0].get().unwrap();
+ #(#param_bindings)*
+ (#return_wrapper)(#unwrapped)
+ }
+ }
+ });
+
+ let return_type = return_type.iter();
+ let class_handler = class_handler_wrapper.into_iter();
+ let param_types = param_static_types
+ .map(|static_types| quote! { param_types([#(#static_types),*]) })
+ .into_iter();
+ let run_first = run_first.iter();
+ let run_last = run_last.iter();
+ let run_cleanup = run_cleanup.iter();
+ let no_recurse = no_recurse.iter();
+ let detailed = detailed.iter();
+ let action = action.iter();
+ let no_hooks = no_hooks.iter();
+ let must_collect = must_collect.iter();
+ let deprecated = deprecated.iter();
+
+ quote! {
+ #crate_ident::subclass::signal::Signal::builder(#name)
+ #(.#param_types)*
+ #(.return_type::<#return_type>())*
+ #(.class_handler(#class_handler))*
+ #(.#run_first())*
+ #(.#run_last())*
+ #(.#run_cleanup())*
+ #(.#no_recurse())*
+ #(.#detailed())*
+ #(.#action())*
+ #(.#no_hooks())*
+ #(.#must_collect())*
+ #(.#deprecated())*
+ .build()
+ }
+ }
+}
+
+fn parse_signal_attr(attr: syn::Attribute) -> Result> {
+ let args = match attr.meta {
+ syn::Meta::Path(path) => {
+ assert!(path.get_ident().is_some_and(|ident| ident == "signal"));
+ SignalArgs::default()
+ }
+
+ syn::Meta::List(list) => {
+ assert!(list.path.get_ident().is_some_and(|ident| ident == "signal"));
+ syn::parse2(list.tokens)?
+ }
+
+ other => {
+ return Err(syn::Error::new_spanned(
+ other,
+ "Invalid #[signal] attribute",
+ ))
+ }
+ };
+
+ Ok(args.0.into_iter())
+}
+
+fn parse_signal_attrs(attrs: &mut Vec) -> Result