From c25611d5d336ee28c1e003899668b7deacd8ddec Mon Sep 17 00:00:00 2001 From: Ben Ford Date: Fri, 25 Jul 2025 14:14:09 +0100 Subject: [PATCH] Allow immutable signals, and add one to qml_features --- crates/cxx-qt-gen/src/generator/cpp/signal.rs | 17 ++-- .../cxx-qt-gen/src/generator/rust/signals.rs | 7 +- crates/cxx-qt-gen/src/parser/signals.rs | 21 ++--- crates/cxx-qt-gen/test_inputs/signals.rs | 3 + crates/cxx-qt-gen/test_outputs/signals.cpp | 55 +++++++++++ crates/cxx-qt-gen/test_outputs/signals.h | 15 +++ crates/cxx-qt-gen/test_outputs/signals.rs | 91 +++++++++++++++++++ examples/qml_features/cpp/external_qobject.h | 1 + examples/qml_features/rust/src/externcxxqt.rs | 5 + 9 files changed, 194 insertions(+), 21 deletions(-) diff --git a/crates/cxx-qt-gen/src/generator/cpp/signal.rs b/crates/cxx-qt-gen/src/generator/cpp/signal.rs index 1dc4808da..e0f6121ef 100644 --- a/crates/cxx-qt-gen/src/generator/cpp/signal.rs +++ b/crates/cxx-qt-gen/src/generator/cpp/signal.rs @@ -51,6 +51,7 @@ fn parameter_types_and_values( parameters: &[ParsedFunctionParameter], type_names: &TypeNames, self_ty: &Name, + mutable: bool, ) -> Result { let mut parameter_named_types_with_self = vec![]; let mut parameter_types_with_self = vec![]; @@ -66,10 +67,11 @@ fn parameter_types_and_values( let parameter_named_types = parameter_named_types_with_self.join(", "); + let is_const = if !mutable { " const" } else { "" }; // Insert the extra argument into the closure let self_ty = self_ty.cxx_qualified(); - parameter_named_types_with_self.insert(0, format!("{self_ty}& self")); - parameter_types_with_self.insert(0, format!("{self_ty}&")); + parameter_named_types_with_self.insert(0, format!("{self_ty}{is_const}& self")); + parameter_types_with_self.insert(0, format!("{self_ty}{is_const}&")); parameter_values_with_self.insert(0, "self".to_owned()); Ok(Parameters { @@ -109,7 +111,8 @@ pub fn generate_cpp_signal( let free_connect_ident_cpp = idents_helper.connect_name.cxx_unqualified(); // Retrieve the parameters for the signal - let parameters = parameter_types_and_values(&signal.parameters, type_names, qobject_name)?; + let parameters = + parameter_types_and_values(&signal.parameters, type_names, qobject_name, signal.mutable)?; let parameters_named_types = parameters.named_types; let parameters_named_types_with_self = parameters.named_types_with_self; let parameter_types_with_self = parameters.types_with_self; @@ -121,6 +124,8 @@ pub fn generate_cpp_signal( let signal_handler_call = idents_helper.function_call; let signal_handler_drop = idents_helper.function_drop; let namespace = idents_helper.namespace; + let reference_type = if !signal.mutable { " const &" } else { "&" }; + let is_const = if !signal.mutable { " const" } else { "" }; let signal_handler_type = format!("SignalHandler<::{namespace}::{param_struct} *>"); @@ -135,7 +140,7 @@ pub fn generate_cpp_signal( // Generate the Q_SIGNAL if this is not an existing signal if !signal.inherit { generated.methods.push(CppFragment::Header(format!( - "Q_SIGNAL void {signal_ident}({parameters_named_types});" + "Q_SIGNAL void {signal_ident}({parameters_named_types}){is_const};" ))); } @@ -144,7 +149,7 @@ pub fn generate_cpp_signal( r#" namespace {namespace} {{ ::QMetaObject::Connection - {free_connect_ident_cpp}({qobject_ident_namespaced}& self, {signal_handler_alias_namespaced} closure, ::Qt::ConnectionType type); + {free_connect_ident_cpp}({qobject_ident_namespaced}{reference_type} self, {signal_handler_alias_namespaced} closure, ::Qt::ConnectionType type); }} // namespace {namespace} "# }, @@ -177,7 +182,7 @@ pub fn generate_cpp_signal( namespace {namespace} {{ ::QMetaObject::Connection - {free_connect_ident_cpp}({qobject_ident_namespaced}& self, {signal_handler_alias_namespaced} closure, ::Qt::ConnectionType type) + {free_connect_ident_cpp}({qobject_ident_namespaced}{reference_type} self, {signal_handler_alias_namespaced} closure, ::Qt::ConnectionType type) {{ return ::QObject::connect( &self, diff --git a/crates/cxx-qt-gen/src/generator/rust/signals.rs b/crates/cxx-qt-gen/src/generator/rust/signals.rs index 2b407522c..4adad7b04 100644 --- a/crates/cxx-qt-gen/src/generator/rust/signals.rs +++ b/crates/cxx-qt-gen/src/generator/rust/signals.rs @@ -85,9 +85,10 @@ pub fn generate_rust_signal( let self_type_cxx = if signal.mutable { parse_quote_spanned! {span => Pin<&mut #qobject_name_rust> } } else { - // CODECOV_EXCLUDE_START - unreachable!("Signals cannot be immutable right now so this cannot be reached") - // CODECOV_EXCLUDE_STOP + // // CODECOV_EXCLUDE_START + // unreachable!("Signals cannot be immutable right now so this cannot be reached") + // // CODECOV_EXCLUDE_STOP + parse_quote_spanned! {span => &#qobject_name_rust } }; let self_type_qualified = syn_type_cxx_bridge_to_qualified(&self_type_cxx, type_names)?; let qualified_impl = qobject_name.rust_qualified(); diff --git a/crates/cxx-qt-gen/src/parser/signals.rs b/crates/cxx-qt-gen/src/parser/signals.rs index 81e264ebb..7b145281d 100644 --- a/crates/cxx-qt-gen/src/parser/signals.rs +++ b/crates/cxx-qt-gen/src/parser/signals.rs @@ -9,7 +9,7 @@ use crate::{ }; use core::ops::Deref; use std::ops::DerefMut; -use syn::{spanned::Spanned, Attribute, Error, ForeignItemFn, Result, Visibility}; +use syn::{Attribute, ForeignItemFn, Result, Visibility}; #[derive(Clone)] /// Describes an individual Signal @@ -42,12 +42,13 @@ impl ParsedSignal { let fields = MethodFields::parse(method, auto_case)?; let attrs = require_attributes(&fields.method.attrs, &Self::ALLOWED_ATTRS)?; - if !fields.mutable { - return Err(Error::new( - fields.method.span(), - "signals must be mutable, use Pin<&mut T> instead of T for the self type", - )); - } + // TODO: add proper checks + // if !fields.mutable { + // return Err(Error::new( + // fields.method.span(), + // "signals must be mutable, use Pin<&mut T> instead of T for the self type", + // )); + // } let inherit = attrs.contains_key("inherit"); @@ -96,17 +97,13 @@ mod tests { assert_parse_errors! { |input| ParsedSignal::parse(input, CaseConversion::none()) => - // No immutable signals - { fn ready(self: &MyObject); } + // No namespaces { - // No namespaces #[namespace = "disallowed_namespace"] fn ready(self: Pin<&mut MyObject>); } // Missing self { fn ready(x: f64); } - // Self needs to be receiver like self: &T instead of &self - { fn ready(&self); } } } diff --git a/crates/cxx-qt-gen/test_inputs/signals.rs b/crates/cxx-qt-gen/test_inputs/signals.rs index ec298cca7..416867a47 100644 --- a/crates/cxx-qt-gen/test_inputs/signals.rs +++ b/crates/cxx-qt-gen/test_inputs/signals.rs @@ -26,6 +26,9 @@ mod ffi { #[qsignal] fn ready(self: Pin<&mut Self>); + #[qsignal] + fn const_ready(&self); + #[qsignal] fn data_changed( self: Pin<&mut Self>, diff --git a/crates/cxx-qt-gen/test_outputs/signals.cpp b/crates/cxx-qt-gen/test_outputs/signals.cpp index 3e0b51c48..92e5551f0 100644 --- a/crates/cxx-qt-gen/test_outputs/signals.cpp +++ b/crates/cxx-qt-gen/test_outputs/signals.cpp @@ -112,6 +112,61 @@ MyObject_readyConnect( } } // namespace cxx_qt::my_object::rust::cxxqtgen1 +// Define namespace otherwise we hit a GCC bug +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56480 +namespace rust::cxxqt1 { +template<> +SignalHandler< + ::cxx_qt::my_object::rust::cxxqtgen1::MyObjectCxxQtSignalParamsconst_ready*>:: + ~SignalHandler() noexcept +{ + if (data[0] == nullptr && data[1] == nullptr) { + return; + } + + drop_MyObject_signal_handler_const_ready(::std::move(*this)); +} + +template<> +template<> +void +SignalHandler< + ::cxx_qt::my_object::rust::cxxqtgen1::MyObjectCxxQtSignalParamsconst_ready*>:: +operator()( + cxx_qt::my_object::MyObject const& self) +{ + call_MyObject_signal_handler_const_ready(*this, self); +} + +static_assert(alignof(SignalHandler<::cxx_qt::my_object::rust::cxxqtgen1:: + MyObjectCxxQtSignalParamsconst_ready*>) <= + alignof(::std::size_t), + "unexpected aligment"); +static_assert(sizeof(SignalHandler<::cxx_qt::my_object::rust::cxxqtgen1:: + MyObjectCxxQtSignalParamsconst_ready*>) == + sizeof(::std::size_t[2]), + "unexpected size"); +} // namespace rust::cxxqt1 + +namespace cxx_qt::my_object::rust::cxxqtgen1 { +::QMetaObject::Connection +MyObject_const_readyConnect( + cxx_qt::my_object::MyObject const& self, + ::cxx_qt::my_object::rust::cxxqtgen1::MyObjectCxxQtSignalHandlerconst_ready + closure, + ::Qt::ConnectionType type) +{ + return ::QObject::connect( + &self, + &cxx_qt::my_object::MyObject::const_ready, + &self, + [&, closure = ::std::move(closure)]() mutable { + closure.template operator()(self); + }, + type); +} +} // namespace cxx_qt::my_object::rust::cxxqtgen1 + // Define namespace otherwise we hit a GCC bug // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56480 namespace rust::cxxqt1 { diff --git a/crates/cxx-qt-gen/test_outputs/signals.h b/crates/cxx-qt-gen/test_outputs/signals.h index bad91af1b..4202a2ede 100644 --- a/crates/cxx-qt-gen/test_outputs/signals.h +++ b/crates/cxx-qt-gen/test_outputs/signals.h @@ -14,6 +14,11 @@ using MyObjectCxxQtSignalHandlerready = ::rust::cxxqt1::SignalHandler; } // namespace cxx_qt::my_object::rust::cxxqtgen1 +namespace cxx_qt::my_object::rust::cxxqtgen1 { +using MyObjectCxxQtSignalHandlerconst_ready = + ::rust::cxxqt1::SignalHandler; +} // namespace cxx_qt::my_object::rust::cxxqtgen1 + namespace cxx_qt::my_object::rust::cxxqtgen1 { using MyObjectCxxQtSignalHandlerdata_changed = ::rust::cxxqt1::SignalHandler; @@ -47,6 +52,15 @@ MyObject_readyConnect( ::Qt::ConnectionType type); } // namespace cxx_qt::my_object::rust::cxxqtgen1 +namespace cxx_qt::my_object::rust::cxxqtgen1 { +::QMetaObject::Connection +MyObject_const_readyConnect( + cxx_qt::my_object::MyObject const& self, + ::cxx_qt::my_object::rust::cxxqtgen1::MyObjectCxxQtSignalHandlerconst_ready + closure, + ::Qt::ConnectionType type); +} // namespace cxx_qt::my_object::rust::cxxqtgen1 + namespace cxx_qt::my_object::rust::cxxqtgen1 { ::QMetaObject::Connection MyObject_data_changedConnect( @@ -77,6 +91,7 @@ class MyObject public: Q_INVOKABLE void invokable() noexcept; Q_SIGNAL void ready(); + Q_SIGNAL void const_ready() const; Q_SIGNAL void data_changed(::std::int32_t first, ::std::unique_ptr second, QPoint third, diff --git a/crates/cxx-qt-gen/test_outputs/signals.rs b/crates/cxx-qt-gen/test_outputs/signals.rs index 9c30d80ea..ad8d50cb0 100644 --- a/crates/cxx-qt-gen/test_outputs/signals.rs +++ b/crates/cxx-qt-gen/test_outputs/signals.rs @@ -73,6 +73,37 @@ mod ffi { self_value: Pin<&mut MyObject>, ); } + unsafe extern "C++" { + #[cxx_name = "const_ready"] + #[namespace = "cxx_qt::my_object"] + fn const_ready(self: &MyObject); + } + unsafe extern "C++" { + #[doc(hidden)] + #[namespace = "cxx_qt::my_object::rust::cxxqtgen1"] + type MyObjectCxxQtSignalHandlerconst_ready<'a> = cxx_qt::signalhandler::CxxQtSignalHandler< + 'a, + super::MyObjectCxxQtSignalClosureconst_ready, + >; + #[doc(hidden)] + #[namespace = "cxx_qt::my_object::rust::cxxqtgen1"] + #[cxx_name = "MyObject_const_readyConnect"] + fn MyObject_connect_const_ready( + self_value: &MyObject, + signal_handler: MyObjectCxxQtSignalHandlerconst_ready, + conn_type: CxxQtConnectionType, + ) -> CxxQtQMetaObjectConnection; + } + #[namespace = "cxx_qt::my_object::rust::cxxqtgen1"] + extern "Rust" { + #[doc(hidden)] + fn drop_MyObject_signal_handler_const_ready(handler: MyObjectCxxQtSignalHandlerconst_ready); + #[doc(hidden)] + fn call_MyObject_signal_handler_const_ready( + handler: &mut MyObjectCxxQtSignalHandlerconst_ready, + self_value: &MyObject, + ); + } unsafe extern "C++" { #[cxx_name = "data_changed"] #[namespace = "cxx_qt::my_object"] @@ -289,6 +320,66 @@ cxx_qt::static_assertions::assert_eq_size!( cxx_qt::signalhandler::CxxQtSignalHandler, [usize; 2] ); +impl ffi::MyObject { + #[doc = "Connect the given function pointer to the signal "] + #[doc = "const_ready"] + #[doc = ", so that when the signal is emitted the function pointer is executed."] + pub fn connect_const_ready<'a, F: FnMut(&ffi::MyObject) + 'a + Send>( + self: &ffi::MyObject, + closure: F, + conn_type: cxx_qt::ConnectionType, + ) -> cxx_qt::QScopedMetaObjectConnectionGuard<'a> { + cxx_qt::QScopedMetaObjectConnectionGuard::from(ffi::MyObject_connect_const_ready( + self, + cxx_qt::signalhandler::CxxQtSignalHandler::::new( + Box::new(closure), + ), + conn_type, + )) + } +} +impl ffi::MyObject { + #[doc = "Connect the given function pointer to the signal "] + #[doc = "const_ready"] + #[doc = ", so that when the signal is emitted the function pointer is executed."] + #[doc = "\n"] + #[doc = "Note that this method uses a AutoConnection connection type."] + pub fn on_const_ready<'a, F: FnMut(&ffi::MyObject) + 'a + Send>( + self: &ffi::MyObject, + closure: F, + ) -> cxx_qt::QScopedMetaObjectConnectionGuard<'a> { + cxx_qt::QScopedMetaObjectConnectionGuard::from(ffi::MyObject_connect_const_ready( + self, + cxx_qt::signalhandler::CxxQtSignalHandler::::new( + Box::new(closure), + ), + cxx_qt::ConnectionType::AutoConnection, + )) + } +} +#[doc(hidden)] +pub struct MyObjectCxxQtSignalClosureconst_ready {} +impl cxx_qt::signalhandler::CxxQtSignalHandlerClosure for MyObjectCxxQtSignalClosureconst_ready { + type Id = cxx::type_id!( + "::cxx_qt::my_object::rust::cxxqtgen1::MyObjectCxxQtSignalHandlerconst_ready" + ); + type FnType<'a> = dyn FnMut(&ffi::MyObject) + 'a + Send; +} +use core::mem::drop as drop_MyObject_signal_handler_const_ready; +fn call_MyObject_signal_handler_const_ready( + handler: &mut cxx_qt::signalhandler::CxxQtSignalHandler, + self_value: &ffi::MyObject, +) { + handler.closure()(self_value); +} +cxx_qt::static_assertions::assert_eq_align!( + cxx_qt::signalhandler::CxxQtSignalHandler, + usize +); +cxx_qt::static_assertions::assert_eq_size!( + cxx_qt::signalhandler::CxxQtSignalHandler, + [usize; 2] +); impl ffi::MyObject { #[doc = "Connect the given function pointer to the signal "] #[doc = "data_changed"] diff --git a/examples/qml_features/cpp/external_qobject.h b/examples/qml_features/cpp/external_qobject.h index c4a4b712f..6289545c2 100644 --- a/examples/qml_features/cpp/external_qobject.h +++ b/examples/qml_features/cpp/external_qobject.h @@ -22,4 +22,5 @@ class ExternalQObject : public QObject Q_SIGNALS: void triggered(); void triggeredPrivateSignal(QPrivateSignal); + void triggeredConstSignal() const; }; diff --git a/examples/qml_features/rust/src/externcxxqt.rs b/examples/qml_features/rust/src/externcxxqt.rs index efc9fd04b..9abb9cb6f 100644 --- a/examples/qml_features/rust/src/externcxxqt.rs +++ b/examples/qml_features/rust/src/externcxxqt.rs @@ -22,6 +22,11 @@ pub mod ffi { #[qsignal] fn triggered(self: Pin<&mut Self>); + /// const signal that is emitted when trigger is fired + #[qsignal] + #[rust_name = "triggered_const_signal"] + fn triggeredConstSignal(&self); + /// Private signal that is emitted when trigger is fired #[qsignal] #[rust_name = "triggered_private_signal"]