diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index c5917053f..3ef59c04d 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -20,7 +20,7 @@ SPDX-License-Identifier: MIT OR Apache-2.0 - [QObject](./qobject/index.md) - [`#[cxx_qt::bridge]` - Bridge Macro](./qobject/bridge-macro.md) - [`#[cxx_qt::qobject]` - Defining QObjects](./qobject/qobject_struct.md) - - [`#[cxx_qt::qsignals]` - Signals enum](./qobject/signals_enum.md) + - [`#[cxx_qt::qsignals]` - Signals macro](./qobject/signals.md) - [`qobject::T` - The generated QObject](./qobject/generated-qobject.md) - [CxxQtThread](./qobject/cxxqtthread.md) - [Concepts](./concepts/index.md) diff --git a/book/src/concepts/qt.md b/book/src/concepts/qt.md index a58aca306..1d6dbd2f4 100644 --- a/book/src/concepts/qt.md +++ b/book/src/concepts/qt.md @@ -19,4 +19,4 @@ Properties can be defined using the [QObject struct](../qobject/qobject_struct.m ## Signals -Signals can be defined using the [Signals enum](../qobject/signals_enum.md), these will be exposed as `Q_SIGNALS` on the C++ class and therefore to QML as well. +Signals can be defined using the [QSignals macros](../qobject/signals.md), these will be exposed as `Q_SIGNALS` on the C++ class and therefore to QML as well. diff --git a/book/src/getting-started/1-qobjects-in-rust.md b/book/src/getting-started/1-qobjects-in-rust.md index 1a599801e..368c7a484 100644 --- a/book/src/getting-started/1-qobjects-in-rust.md +++ b/book/src/getting-started/1-qobjects-in-rust.md @@ -47,7 +47,7 @@ Then you can use the afformentioned features with the help of more macros. - `#[cxx_qt::qobject]` - Expose a Rust struct to Qt as a QObject subclass. - `#[qproperty]` - Expose a field of the Rust struct to QML/C++ as a [`Q_PROPERTY`](https://doc.qt.io/qt-6/qtqml-cppintegration-exposecppattributes.html#exposing-properties). - `#[qinvokable]` - Expose a function on the QObject to QML and C++ as a [`Q_INVOKABLE`](https://doc.qt.io/qt-6/qtqml-cppintegration-exposecppattributes.html#exposing-methods-including-qt-slots). -- `#[cxx_qt::qsignals(T)]` - Use an enum to define the [Signals](https://doc.qt.io/qt-6/signalsandslots.html#signals) of a QObject T. +- `#[cxx_qt::qsignals]` - Define the [Signals](https://doc.qt.io/qt-6/signalsandslots.html#signals) of a QObject T. CXX-Qt will then expand this Rust module into two separate parts: - C++ files that define a QObject subclass for each `#[cxx_qt::qobject]` marked struct. diff --git a/book/src/qobject/generated-qobject.md b/book/src/qobject/generated-qobject.md index 6658374aa..3a3cfd7d1 100644 --- a/book/src/qobject/generated-qobject.md +++ b/book/src/qobject/generated-qobject.md @@ -80,14 +80,6 @@ The closure also takes a pinned mutable reference to the QObject, so that it can See the [CxxQtThread page](./cxxqtthread.md) for more details. -### Signal emission -``` rust,ignore,noplayground -fn emit(self: Pin<&mut Self>, signal: /*Your Signals enum goes here*/) -``` -If there is a [Signals enum](./signals_enum.md) defined, CXX-Qt will generate the appropriate `emit` function to allow you to emit signals. - -See the [Signals enum page](./signals_enum.md) for more details. - ### Access to internal Rust struct For every field in the Rust struct, CXX-Qt will generate appropriate getters and setters. See the [QObject page](./qobject_struct.md#properties) for details. diff --git a/book/src/qobject/index.md b/book/src/qobject/index.md index 5d87470e7..ef5770158 100644 --- a/book/src/qobject/index.md +++ b/book/src/qobject/index.md @@ -15,7 +15,7 @@ For a simpler introduction, take a look at our [Getting Started guide](../gettin QObject Features and Parts: * [`#[cxx_qt::bridge]` - The macro around the module](./bridge-macro.md) * [`#[cxx_qt::qobject]` - Marking a Rust struct as a QObject](./qobject_struct.md) - * [`#[cxx_qt::qsignals(T)]` - An enum for defining signals](./signals_enum.md) + * [`#[cxx_qt::qsignals]` - A macro for defining signals](./signals.md) * [`qobject:T` - The generated QObject](./generated-qobject.md) * [`CxxQtThread` - Queueing closures onto the Qt event loop](./cxxqtthread.md) diff --git a/book/src/qobject/qobject_struct.md b/book/src/qobject/qobject_struct.md index 8b761d2ff..ee3b3d300 100644 --- a/book/src/qobject/qobject_struct.md +++ b/book/src/qobject/qobject_struct.md @@ -27,7 +27,7 @@ The macro does multiple other things for you though: - Expose the generated QObject subclass to Rust as [`qobject::MyObject`](./generated-qobject.md) - Generate getters/setters for all fields. - Generate `Q_PROPERTY`s for all fields that are marked as `#[qproperty]`. -- Generate signals if paired with a [`#[cxx_qt::qsignals]` enum](./signals_enum.md). +- Generate signals if paired with a [`#[cxx_qt::qsignals]` macro](./signals.md). ## Exposing to QML `#[cxx_qt::qobject]` supports registering the Qt object as a QML type directly at build time. diff --git a/book/src/qobject/signals_enum.md b/book/src/qobject/signals.md similarity index 71% rename from book/src/qobject/signals_enum.md rename to book/src/qobject/signals.md index e315f01fd..0850b23b2 100644 --- a/book/src/qobject/signals_enum.md +++ b/book/src/qobject/signals.md @@ -7,17 +7,14 @@ SPDX-License-Identifier: MIT OR Apache-2.0 # Signals enum -The `cxx_qt::qsignals(T)` attribute is used on an [enum](https://doc.rust-lang.org/book/ch06-01-defining-an-enum.html) to define [signals](https://doc.qt.io/qt-6/signalsandslots.html) for the QObject `T`. +The `cxx_qt::qsignals` attribute is used on an `extern "C++"` block to define [signals](https://doc.qt.io/qt-6/signalsandslots.html) for the a QObject. ```rust,ignore,noplayground -{{#include ../../../examples/qml_features/rust/src/signals.rs:book_signals_enum}} +{{#include ../../../examples/qml_features/rust/src/signals.rs:book_signals_block}} ``` -For every enum variant, CXX-Qt will generate a signal on the corresponding QObject. -If the enum variant has members, they will become the parameters for the corresponding signal. - -Because CXX-Qt needs to know the names of each parameter, only enum variants with named members are supported. -The signal parameters are generated in order of appearance in the enum variant. +For every function signature in the extern block, CXX-Qt will generate a signal on the corresponding QObject. +If the function has parameters, they will become the parameters for the corresponding signal. If a signal is defined on the base class of the QObject then `#[inherit]` can be used to indicate to CXX-Qt that the `Q_SIGNAL` does not need to be created in C++. @@ -60,18 +57,16 @@ In this case, it is no longer possible to disconnect later. ## Emitting a signal -For every generated QObject [`qobject::T`](./generated-qobject.md) that has a signals enum, CXX-Qt will generate an `emit` function: -``` rust,ignore,noplayground -fn emit(self: Pin<&mut Self>, signal: /*Signals enum*/) -``` -`emit` can therefore be called from any mutable `#[qinvokable]`. +Call the function signature defined in the `extern "C++` block to emit the signal. + +Note that these are defined on the generated QObject [`qobject::T`](./generated-qobject.md), so can be called from any mutable `#[qinvokable]`. -The `emit` function will immediately emit the signal. +The function will immediately emit the signal. Depending on the connection type, the connected slots will be called either immediately or from the event loop (See [the different connection types](https://doc.qt.io/qt-6/qt.html#ConnectionType-enum)). -To queue the call to `emit` until the next cycle of the Qt event loop, you can use the [`CxxQtThread`](./cxxqtthread.md). +To queue the call until the next cycle of the Qt event loop, you can use the [`CxxQtThread`](./cxxqtthread.md). ### [Example](https://github.com/KDAB/cxx-qt/blob/main/examples/qml_features/rust/src/signals.rs) + ```rust,ignore,noplayground {{#include ../../../examples/qml_features/rust/src/signals.rs:book_macro_code}} ``` - diff --git a/crates/cxx-qt-gen/src/generator/cpp/property/mod.rs b/crates/cxx-qt-gen/src/generator/cpp/property/mod.rs index 1b96598c1..9a28663a4 100644 --- a/crates/cxx-qt-gen/src/generator/cpp/property/mod.rs +++ b/crates/cxx-qt-gen/src/generator/cpp/property/mod.rs @@ -4,7 +4,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 use crate::generator::{ - cpp::{qobject::GeneratedCppQObjectBlocks, types::CppType}, + cpp::{qobject::GeneratedCppQObjectBlocks, signal::generate_cpp_signals, types::CppType}, naming::{property::QPropertyName, qobject::QObjectName}, }; use crate::parser::{cxxqtdata::ParsedCxxMappings, property::ParsedQProperty}; @@ -22,7 +22,9 @@ pub fn generate_cpp_properties( lock_guard: Option<&str>, ) -> Result { let mut generated = GeneratedCppQObjectBlocks::default(); + let mut signals = vec![]; let qobject_ident = qobject_idents.cpp_class.cpp.to_string(); + for property in properties { // Cache the idents as they are used in multiple places let idents = QPropertyName::from(property); @@ -41,9 +43,16 @@ pub fn generate_cpp_properties( &cxx_ty, lock_guard, )); - generated.methods.push(signal::generate(&idents)); + signals.push(signal::generate(&idents, qobject_idents)); } + generated.append(&mut generate_cpp_signals( + &signals, + qobject_idents, + cxx_mappings, + lock_guard, + )?); + Ok(generated) } @@ -88,7 +97,7 @@ mod tests { assert_str_eq!(generated.metaobjects[1], "Q_PROPERTY(::std::unique_ptr opaqueProperty READ getOpaqueProperty WRITE setOpaqueProperty NOTIFY opaquePropertyChanged)"); // methods - assert_eq!(generated.methods.len(), 6); + assert_eq!(generated.methods.len(), 10); let (header, source) = if let CppFragment::Pair { header, source } = &generated.methods[0] { (header, source) } else { @@ -127,13 +136,8 @@ mod tests { } "#} ); - let header = if let CppFragment::Header(header) = &generated.methods[2] { - header - } else { - panic!("Expected header!") - }; - assert_str_eq!(header, "Q_SIGNAL void trivialPropertyChanged();"); - let (header, source) = if let CppFragment::Pair { header, source } = &generated.methods[3] { + + let (header, source) = if let CppFragment::Pair { header, source } = &generated.methods[2] { (header, source) } else { panic!("Expected pair!") @@ -154,7 +158,7 @@ mod tests { "#} ); - let (header, source) = if let CppFragment::Pair { header, source } = &generated.methods[4] { + let (header, source) = if let CppFragment::Pair { header, source } = &generated.methods[3] { (header, source) } else { panic!("Expected pair!") @@ -175,12 +179,105 @@ mod tests { "#} ); - let header = if let CppFragment::Header(header) = &generated.methods[5] { + let header = if let CppFragment::Header(header) = &generated.methods[4] { + header + } else { + panic!("Expected header!") + }; + assert_str_eq!(header, "Q_SIGNAL void trivialPropertyChanged();"); + + let (header, source) = if let CppFragment::Pair { header, source } = &generated.methods[5] { + (header, source) + } else { + panic!("Expected Pair") + }; + assert_str_eq!(header, "void emitTrivialPropertyChanged();"); + assert_str_eq!( + source, + indoc! {r#" + void + MyObject::emitTrivialPropertyChanged() + { + Q_EMIT trivialPropertyChanged(); + } + "#} + ); + + let (header, source) = if let CppFragment::Pair { header, source } = &generated.methods[6] { + (header, source) + } else { + panic!("Expected Pair") + }; + assert_str_eq!( + header, + "::QMetaObject::Connection trivialPropertyChangedConnect(::rust::Fn func, ::Qt::ConnectionType type);" + ); + assert_str_eq!( + source, + indoc! {r#" + ::QMetaObject::Connection + MyObject::trivialPropertyChangedConnect(::rust::Fn func, ::Qt::ConnectionType type) + { + return ::QObject::connect(this, + &MyObject::trivialPropertyChanged, + this, + [&, func = ::std::move(func)]() { + // ::std::lock_guard + func(*this); + }, type); + } + "#} + ); + + let header = if let CppFragment::Header(header) = &generated.methods[7] { header } else { panic!("Expected header!") }; assert_str_eq!(header, "Q_SIGNAL void opaquePropertyChanged();"); + + let (header, source) = if let CppFragment::Pair { header, source } = &generated.methods[8] { + (header, source) + } else { + panic!("Expected Pair") + }; + assert_str_eq!(header, "void emitOpaquePropertyChanged();"); + assert_str_eq!( + source, + indoc! {r#" + void + MyObject::emitOpaquePropertyChanged() + { + Q_EMIT opaquePropertyChanged(); + } + "#} + ); + + let (header, source) = if let CppFragment::Pair { header, source } = &generated.methods[9] { + (header, source) + } else { + panic!("Expected Pair") + }; + assert_str_eq!( + header, + "::QMetaObject::Connection opaquePropertyChangedConnect(::rust::Fn func, ::Qt::ConnectionType type);" + ); + assert_str_eq!( + source, + indoc! {r#" + ::QMetaObject::Connection + MyObject::opaquePropertyChangedConnect(::rust::Fn func, ::Qt::ConnectionType type) + { + return ::QObject::connect(this, + &MyObject::opaquePropertyChanged, + this, + [&, func = ::std::move(func)]() { + // ::std::lock_guard + func(*this); + }, type); + } + "#} + ); } #[test] @@ -210,7 +307,7 @@ mod tests { assert_str_eq!(generated.metaobjects[0], "Q_PROPERTY(A1 mappedProperty READ getMappedProperty WRITE setMappedProperty NOTIFY mappedPropertyChanged)"); // methods - assert_eq!(generated.methods.len(), 3); + assert_eq!(generated.methods.len(), 5); let (header, source) = if let CppFragment::Pair { header, source } = &generated.methods[0] { (header, source) } else { @@ -252,5 +349,48 @@ mod tests { panic!("Expected header!") }; assert_str_eq!(header, "Q_SIGNAL void mappedPropertyChanged();"); + + let (header, source) = if let CppFragment::Pair { header, source } = &generated.methods[3] { + (header, source) + } else { + panic!("Expected Pair") + }; + assert_str_eq!(header, "void emitMappedPropertyChanged();"); + assert_str_eq!( + source, + indoc! {r#" + void + MyObject::emitMappedPropertyChanged() + { + Q_EMIT mappedPropertyChanged(); + } + "#} + ); + + let (header, source) = if let CppFragment::Pair { header, source } = &generated.methods[4] { + (header, source) + } else { + panic!("Expected Pair") + }; + assert_str_eq!( + header, + "::QMetaObject::Connection mappedPropertyChangedConnect(::rust::Fn func, ::Qt::ConnectionType type);" + ); + assert_str_eq!( + source, + indoc! {r#" + ::QMetaObject::Connection + MyObject::mappedPropertyChangedConnect(::rust::Fn func, ::Qt::ConnectionType type) + { + return ::QObject::connect(this, + &MyObject::mappedPropertyChanged, + this, + [&, func = ::std::move(func)]() { + // ::std::lock_guard + func(*this); + }, type); + } + "#} + ); } } diff --git a/crates/cxx-qt-gen/src/generator/cpp/property/signal.rs b/crates/cxx-qt-gen/src/generator/cpp/property/signal.rs index ed4ff8294..b601efbbe 100644 --- a/crates/cxx-qt-gen/src/generator/cpp/property/signal.rs +++ b/crates/cxx-qt-gen/src/generator/cpp/property/signal.rs @@ -3,12 +3,27 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 -use crate::generator::naming::property::QPropertyName; -use crate::CppFragment; +use syn::ForeignItemFn; -pub fn generate(idents: &QPropertyName) -> CppFragment { - CppFragment::Header(format!( - "Q_SIGNAL void {ident_notify}();", - ident_notify = idents.notify.cpp - )) +use crate::{ + generator::naming::{property::QPropertyName, qobject::QObjectName}, + parser::signals::ParsedSignal, +}; + +pub fn generate(idents: &QPropertyName, qobject_idents: &QObjectName) -> ParsedSignal { + // We build our signal in the generation phase as we need to use the naming + // structs to build the signal name + let cpp_class_rust = &qobject_idents.cpp_class.rust; + let notify_cpp = &idents.notify.cpp; + let notify_rust_str = idents.notify.rust.to_string(); + let method: ForeignItemFn = syn::parse_quote! { + #[doc = "Notify for the Q_PROPERTY"] + #[rust_name = #notify_rust_str] + fn #notify_cpp(self: Pin<&mut #cpp_class_rust>); + }; + ParsedSignal::from_property_method( + method, + idents.notify.clone(), + qobject_idents.cpp_class.rust.clone(), + ) } diff --git a/crates/cxx-qt-gen/src/generator/cpp/qobject.rs b/crates/cxx-qt-gen/src/generator/cpp/qobject.rs index d7194fdc0..7fa110fad 100644 --- a/crates/cxx-qt-gen/src/generator/cpp/qobject.rs +++ b/crates/cxx-qt-gen/src/generator/cpp/qobject.rs @@ -116,14 +116,12 @@ impl GeneratedCppQObject { cxx_mappings, lock_guard, )?); - if let Some(signals_enum) = &qobject.signals { - generated.blocks.append(&mut generate_cpp_signals( - &signals_enum.signals, - &qobject_idents, - cxx_mappings, - lock_guard, - )?); - } + generated.blocks.append(&mut generate_cpp_signals( + &qobject.signals, + &qobject_idents, + cxx_mappings, + lock_guard, + )?); generated.blocks.append(&mut inherit::generate( &qobject.inherited_methods, &qobject.base_class, diff --git a/crates/cxx-qt-gen/src/generator/cpp/signal.rs b/crates/cxx-qt-gen/src/generator/cpp/signal.rs index bdda9dd15..212da6256 100644 --- a/crates/cxx-qt-gen/src/generator/cpp/signal.rs +++ b/crates/cxx-qt-gen/src/generator/cpp/signal.rs @@ -117,7 +117,7 @@ pub fn generate_cpp_signals( mod tests { use super::*; - use crate::generator::naming::qobject::tests::create_qobjectname; + use crate::generator::naming::{qobject::tests::create_qobjectname, CombinedIdent}; use crate::parser::parameter::ParsedFunctionParameter; use indoc::indoc; use pretty_assertions::assert_str_eq; @@ -127,9 +127,11 @@ mod tests { #[test] fn test_generate_cpp_signals() { let signals = vec![ParsedSignal { - ident: format_ident!("data_changed"), - cxx_name: None, - inherit: false, + method: parse_quote! { + fn data_changed(self: Pin<&mut MyObject>, trivial: i32, opaque: UniquePtr); + }, + qobject_ident: format_ident!("MyObject"), + mutable: true, parameters: vec![ ParsedFunctionParameter { ident: format_ident!("trivial"), @@ -140,6 +142,12 @@ mod tests { ty: parse_quote! { UniquePtr }, }, ], + ident: CombinedIdent { + cpp: format_ident!("dataChanged"), + rust: format_ident!("data_changed"), + }, + safe: true, + inherit: false, }]; let qobject_idents = create_qobjectname(); @@ -212,13 +220,21 @@ mod tests { #[test] fn test_generate_cpp_signals_mapped_cxx_name() { let signals = vec![ParsedSignal { - ident: format_ident!("data_changed"), - cxx_name: None, - inherit: false, + method: parse_quote! { + fn data_changed(self: Pin<&mut MyObject>, mapped: A1); + }, + qobject_ident: format_ident!("MyObject"), + mutable: true, parameters: vec![ParsedFunctionParameter { ident: format_ident!("mapped"), ty: parse_quote! { A1 }, }], + ident: CombinedIdent { + cpp: format_ident!("dataChanged"), + rust: format_ident!("data_changed"), + }, + safe: true, + inherit: false, }]; let qobject_idents = create_qobjectname(); @@ -290,10 +306,19 @@ mod tests { #[test] fn test_generate_cpp_signals_existing_cxx_name() { let signals = vec![ParsedSignal { - ident: format_ident!("ExistingSignal"), - cxx_name: Some("baseName".to_owned()), - inherit: true, + method: parse_quote! { + #[cxx_name = "baseName"] + fn existing_signal(self: Pin<&mut MyObject>); + }, + qobject_ident: format_ident!("MyObject"), + mutable: true, parameters: vec![], + ident: CombinedIdent { + cpp: format_ident!("baseName"), + rust: format_ident!("existing_signal"), + }, + safe: true, + inherit: true, }]; let qobject_idents = create_qobjectname(); diff --git a/crates/cxx-qt-gen/src/generator/naming/mod.rs b/crates/cxx-qt-gen/src/generator/naming/mod.rs index 8952dfd73..ba2f0e8de 100644 --- a/crates/cxx-qt-gen/src/generator/naming/mod.rs +++ b/crates/cxx-qt-gen/src/generator/naming/mod.rs @@ -12,6 +12,7 @@ pub mod signals; use syn::Ident; /// Describes an ident which potentially has a different name in C++ and Rust +#[derive(Clone, Debug, PartialEq)] pub struct CombinedIdent { /// The ident for C++ pub cpp: Ident, diff --git a/crates/cxx-qt-gen/src/generator/naming/signals.rs b/crates/cxx-qt-gen/src/generator/naming/signals.rs index 67ad81c48..a5cac4971 100644 --- a/crates/cxx-qt-gen/src/generator/naming/signals.rs +++ b/crates/cxx-qt-gen/src/generator/naming/signals.rs @@ -10,7 +10,6 @@ use syn::Ident; /// Names for parts of a Q_SIGNAL pub struct QSignalName { - pub enum_name: Ident, pub name: CombinedIdent, pub emit_name: CombinedIdent, pub connect_name: CombinedIdent, @@ -19,19 +18,11 @@ pub struct QSignalName { impl From<&ParsedSignal> for QSignalName { fn from(signal: &ParsedSignal) -> Self { - // Check if there is a cxx ident that should be used - let cxx_ident = if let Some(cxx_name) = &signal.cxx_name { - format_ident!("{}", cxx_name) - } else { - signal.ident.clone() - }; - Self { - enum_name: signal.ident.clone(), - name: CombinedIdent::from_signal(&signal.ident, &cxx_ident), - emit_name: CombinedIdent::emit_from_signal(&signal.ident, &cxx_ident), - connect_name: CombinedIdent::connect_from_signal(&signal.ident, &cxx_ident), - on_name: on_from_signal(&signal.ident), + name: signal.ident.clone(), + emit_name: CombinedIdent::emit_from_signal(&signal.ident), + connect_name: CombinedIdent::connect_from_signal(&signal.ident), + on_name: on_from_signal(&signal.ident.rust), } } } @@ -41,29 +32,22 @@ fn on_from_signal(ident: &Ident) -> Ident { } impl CombinedIdent { - /// For a given signal ident generate the Rust and C++ names - fn from_signal(ident: &Ident, cxx_ident: &Ident) -> Self { - Self { - cpp: format_ident!("{}", cxx_ident.to_string().to_case(Case::Camel)), - // Note that signal names are in camel case so we need to convert to snake and can't clone - rust: format_ident!("{}", ident.to_string().to_case(Case::Snake)), - } - } - /// For a given signal ident generate the Rust and C++ emit name - fn emit_from_signal(ident: &Ident, cxx_ident: &Ident) -> Self { + fn emit_from_signal(ident: &CombinedIdent) -> Self { Self { - cpp: format_ident!("emit{}", cxx_ident.to_string().to_case(Case::Pascal)), - rust: format_ident!("emit_{}", ident.to_string().to_case(Case::Snake)), + cpp: format_ident!("emit{}", ident.cpp.to_string().to_case(Case::Pascal)), + // Note that the Rust emit name is the same name as the signal for now + // in the future this emit wrapper in C++ will be removed. + rust: format_ident!("{}", ident.rust.to_string().to_case(Case::Snake)), } } - fn connect_from_signal(ident: &Ident, cxx_ident: &Ident) -> Self { + fn connect_from_signal(ident: &CombinedIdent) -> Self { Self { // Use signalConnect instead of onSignal here so that we don't // create a C++ name that is similar to the QML naming scheme for signals - cpp: format_ident!("{}Connect", cxx_ident.to_string().to_case(Case::Camel)), - rust: format_ident!("connect_{}", ident.to_string().to_case(Case::Snake)), + cpp: format_ident!("{}Connect", ident.cpp.to_string().to_case(Case::Camel)), + rust: format_ident!("connect_{}", ident.rust.to_string().to_case(Case::Snake)), } } } @@ -72,21 +56,30 @@ impl CombinedIdent { mod tests { use super::*; + use syn::parse_quote; + #[test] fn test_parsed_signal() { let qsignal = ParsedSignal { - ident: format_ident!("DataChanged"), + method: parse_quote! { + fn data_changed(self: Pin<&mut MyObject>); + }, + qobject_ident: format_ident!("MyObject"), + mutable: true, parameters: vec![], - cxx_name: None, + ident: CombinedIdent { + cpp: format_ident!("dataChanged"), + rust: format_ident!("data_changed"), + }, + safe: true, inherit: false, }; let names = QSignalName::from(&qsignal); - assert_eq!(names.enum_name, format_ident!("DataChanged")); assert_eq!(names.name.cpp, format_ident!("dataChanged")); assert_eq!(names.name.rust, format_ident!("data_changed")); assert_eq!(names.emit_name.cpp, format_ident!("emitDataChanged")); - assert_eq!(names.emit_name.rust, format_ident!("emit_data_changed")); + assert_eq!(names.emit_name.rust, format_ident!("data_changed")); assert_eq!(names.connect_name.cpp, format_ident!("dataChangedConnect")); assert_eq!( names.connect_name.rust, @@ -98,18 +91,26 @@ mod tests { #[test] fn test_parsed_signal_existing_cxx_name() { let qsignal = ParsedSignal { - ident: format_ident!("ExistingSignal"), + method: parse_quote! { + #[cxx_name = "baseName"] + fn existing_signal(self: Pin<&mut MyObject>); + }, + qobject_ident: format_ident!("MyObject"), + mutable: true, parameters: vec![], - cxx_name: Some("baseName".to_owned()), - inherit: true, + ident: CombinedIdent { + cpp: format_ident!("baseName"), + rust: format_ident!("existing_signal"), + }, + safe: true, + inherit: false, }; let names = QSignalName::from(&qsignal); - assert_eq!(names.enum_name, format_ident!("ExistingSignal")); assert_eq!(names.name.cpp, format_ident!("baseName")); assert_eq!(names.name.rust, format_ident!("existing_signal")); assert_eq!(names.emit_name.cpp, format_ident!("emitBaseName")); - assert_eq!(names.emit_name.rust, format_ident!("emit_existing_signal")); + assert_eq!(names.emit_name.rust, format_ident!("existing_signal")); assert_eq!(names.connect_name.cpp, format_ident!("baseNameConnect")); assert_eq!( names.connect_name.rust, diff --git a/crates/cxx-qt-gen/src/generator/rust/property/mod.rs b/crates/cxx-qt-gen/src/generator/rust/property/mod.rs index 7ee37c7d5..2168652e2 100644 --- a/crates/cxx-qt-gen/src/generator/rust/property/mod.rs +++ b/crates/cxx-qt-gen/src/generator/rust/property/mod.rs @@ -16,11 +16,14 @@ use crate::{ }; use syn::Result; +use super::signals::generate_rust_signals; + pub fn generate_rust_properties( properties: &Vec, qobject_idents: &QObjectName, ) -> Result { let mut generated = GeneratedRustQObjectBlocks::default(); + let mut signals = vec![]; for property in properties { let idents = QPropertyName::from(property); @@ -44,15 +47,11 @@ pub fn generate_rust_properties( .append(&mut setter.implementation_as_items()?); // Signals - let notify = signal::generate(&idents, qobject_idents); - generated - .cxx_mod_contents - .append(&mut notify.cxx_bridge_as_items()?); - generated - .cxx_qt_mod_contents - .append(&mut notify.implementation_as_items()?); + signals.push(signal::generate(&idents, qobject_idents)); } + generated.append(&mut generate_rust_signals(&signals, qobject_idents)?); + Ok(generated) } @@ -60,7 +59,7 @@ pub fn generate_rust_properties( mod tests { use super::*; - use crate::generator::naming::qobject::tests::create_qobjectname; + use crate::{generator::naming::qobject::tests::create_qobjectname, tests::assert_tokens_eq}; use quote::format_ident; use syn::parse_quote; @@ -88,23 +87,23 @@ mod tests { let generated = generate_rust_properties(&properties, &qobject_idents).unwrap(); // Check that we have the expected number of blocks - assert_eq!(generated.cxx_mod_contents.len(), 9); - assert_eq!(generated.cxx_qt_mod_contents.len(), 15); + assert_eq!(generated.cxx_mod_contents.len(), 12); + assert_eq!(generated.cxx_qt_mod_contents.len(), 18); // Trivial Property // Getter - assert_eq!( - generated.cxx_mod_contents[0], + assert_tokens_eq( + &generated.cxx_mod_contents[0], parse_quote! { extern "Rust" { #[cxx_name = "getTrivialProperty"] unsafe fn trivial_property<'a>(self: &'a MyObject, cpp: &'a MyObjectQt) -> &'a i32; } - } + }, ); - assert_eq!( - generated.cxx_qt_mod_contents[0], + assert_tokens_eq( + &generated.cxx_qt_mod_contents[0], parse_quote! { impl MyObject { #[doc(hidden)] @@ -112,10 +111,10 @@ mod tests { cpp.trivial_property() } } - } + }, ); - assert_eq!( - generated.cxx_qt_mod_contents[1], + assert_tokens_eq( + &generated.cxx_qt_mod_contents[1], parse_quote! { impl MyObjectQt { #[doc = "Getter for the Q_PROPERTY "] @@ -124,10 +123,10 @@ mod tests { &self.rust().trivial_property } } - } + }, ); - assert_eq!( - generated.cxx_qt_mod_contents[2], + assert_tokens_eq( + &generated.cxx_qt_mod_contents[2], parse_quote! { impl MyObjectQt { #[doc = "unsafe getter for the Q_PROPERTY "] @@ -141,21 +140,21 @@ mod tests { &mut self.rust_mut().get_unchecked_mut().trivial_property } } - } + }, ); // Setters - assert_eq!( - generated.cxx_mod_contents[1], + assert_tokens_eq( + &generated.cxx_mod_contents[1], parse_quote! { extern "Rust" { #[cxx_name = "setTrivialProperty"] fn set_trivial_property(self: &mut MyObject, cpp: Pin<&mut MyObjectQt>, value: i32); } - } + }, ); - assert_eq!( - generated.cxx_qt_mod_contents[3], + assert_tokens_eq( + &generated.cxx_qt_mod_contents[3], parse_quote! { impl MyObject { #[doc(hidden)] @@ -163,10 +162,10 @@ mod tests { cpp.set_trivial_property(value); } } - } + }, ); - assert_eq!( - generated.cxx_qt_mod_contents[4], + assert_tokens_eq( + &generated.cxx_qt_mod_contents[4], parse_quote! { impl MyObjectQt { #[doc = "Setter for the Q_PROPERTY "] @@ -181,40 +180,23 @@ mod tests { self.as_mut().trivial_property_changed(); } } - } - ); - - // Notify - assert_eq!( - generated.cxx_mod_contents[2], - parse_quote! { - unsafe extern "C++" { - #[doc = "Notify signal for the Q_PROPERTY"] - #[doc = "trivial_property"] - #[doc = "\n"] - #[doc = "This can be used to manually notify a change when the unsafe mutable getter,"] - #[doc = "trivial_property_mut"] - #[doc = ", is used."] - #[rust_name = "trivial_property_changed"] - fn trivialPropertyChanged(self: Pin<&mut MyObjectQt>); - } - } + }, ); // Opaque Property // Getter - assert_eq!( - generated.cxx_mod_contents[3], + assert_tokens_eq( + &generated.cxx_mod_contents[2], parse_quote! { extern "Rust" { #[cxx_name = "getOpaqueProperty"] unsafe fn opaque_property<'a>(self: &'a MyObject, cpp: &'a MyObjectQt) -> &'a UniquePtr; } - } + }, ); - assert_eq!( - generated.cxx_qt_mod_contents[5], + assert_tokens_eq( + &generated.cxx_qt_mod_contents[5], parse_quote! { impl MyObject { #[doc(hidden)] @@ -222,10 +204,10 @@ mod tests { cpp.opaque_property() } } - } + }, ); - assert_eq!( - generated.cxx_qt_mod_contents[6], + assert_tokens_eq( + &generated.cxx_qt_mod_contents[6], parse_quote! { impl MyObjectQt { #[doc = "Getter for the Q_PROPERTY "] @@ -234,10 +216,10 @@ mod tests { &self.rust().opaque_property } } - } + }, ); - assert_eq!( - generated.cxx_qt_mod_contents[7], + assert_tokens_eq( + &generated.cxx_qt_mod_contents[7], parse_quote! { impl MyObjectQt { #[doc = "unsafe getter for the Q_PROPERTY "] @@ -251,21 +233,21 @@ mod tests { &mut self.rust_mut().get_unchecked_mut().opaque_property } } - } + }, ); // Setters - assert_eq!( - generated.cxx_mod_contents[4], + assert_tokens_eq( + &generated.cxx_mod_contents[3], parse_quote! { extern "Rust" { #[cxx_name = "setOpaqueProperty"] fn set_opaque_property(self: &mut MyObject, cpp: Pin<&mut MyObjectQt>, value: UniquePtr); } - } + }, ); - assert_eq!( - generated.cxx_qt_mod_contents[8], + assert_tokens_eq( + &generated.cxx_qt_mod_contents[8], parse_quote! { impl MyObject { #[doc(hidden)] @@ -273,10 +255,10 @@ mod tests { cpp.set_opaque_property(value); } } - } + }, ); - assert_eq!( - generated.cxx_qt_mod_contents[9], + assert_tokens_eq( + &generated.cxx_qt_mod_contents[9], parse_quote! { impl MyObjectQt { #[doc = "Setter for the Q_PROPERTY "] @@ -291,40 +273,23 @@ mod tests { self.as_mut().opaque_property_changed(); } } - } - ); - - // Notify - assert_eq!( - generated.cxx_mod_contents[5], - parse_quote! { - unsafe extern "C++" { - #[doc = "Notify signal for the Q_PROPERTY"] - #[doc = "opaque_property"] - #[doc = "\n"] - #[doc = "This can be used to manually notify a change when the unsafe mutable getter,"] - #[doc = "opaque_property_mut"] - #[doc = ", is used."] - #[rust_name = "opaque_property_changed"] - fn opaquePropertyChanged(self: Pin<&mut MyObjectQt>); - } - } + }, ); // Unsafe Property // Getter - assert_eq!( - generated.cxx_mod_contents[6], + assert_tokens_eq( + &generated.cxx_mod_contents[4], parse_quote! { extern "Rust" { #[cxx_name = "getUnsafeProperty"] unsafe fn unsafe_property<'a>(self: &'a MyObject, cpp: &'a MyObjectQt) -> &'a *mut T; } - } + }, ); - assert_eq!( - generated.cxx_qt_mod_contents[10], + assert_tokens_eq( + &generated.cxx_qt_mod_contents[10], parse_quote! { impl MyObject { #[doc(hidden)] @@ -332,10 +297,10 @@ mod tests { cpp.unsafe_property() } } - } + }, ); - assert_eq!( - generated.cxx_qt_mod_contents[11], + assert_tokens_eq( + &generated.cxx_qt_mod_contents[11], parse_quote! { impl MyObjectQt { #[doc = "Getter for the Q_PROPERTY "] @@ -344,10 +309,10 @@ mod tests { &self.rust().unsafe_property } } - } + }, ); - assert_eq!( - generated.cxx_qt_mod_contents[12], + assert_tokens_eq( + &generated.cxx_qt_mod_contents[12], parse_quote! { impl MyObjectQt { #[doc = "unsafe getter for the Q_PROPERTY "] @@ -361,21 +326,21 @@ mod tests { &mut self.rust_mut().get_unchecked_mut().unsafe_property } } - } + }, ); // Setters - assert_eq!( - generated.cxx_mod_contents[7], + assert_tokens_eq( + &generated.cxx_mod_contents[5], parse_quote! { extern "Rust" { #[cxx_name = "setUnsafeProperty"] unsafe fn set_unsafe_property(self: &mut MyObject, cpp: Pin<&mut MyObjectQt>, value: *mut T); } - } + }, ); - assert_eq!( - generated.cxx_qt_mod_contents[13], + assert_tokens_eq( + &generated.cxx_qt_mod_contents[13], parse_quote! { impl MyObject { #[doc(hidden)] @@ -383,10 +348,10 @@ mod tests { cpp.set_unsafe_property(value); } } - } + }, ); - assert_eq!( - generated.cxx_qt_mod_contents[14], + assert_tokens_eq( + &generated.cxx_qt_mod_contents[14], parse_quote! { impl MyObjectQt { #[doc = "Setter for the Q_PROPERTY "] @@ -401,24 +366,132 @@ mod tests { self.as_mut().unsafe_property_changed(); } } - } + }, ); - // Notify - assert_eq!( - generated.cxx_mod_contents[8], + // Signals + + assert_tokens_eq( + &generated.cxx_mod_contents[6], parse_quote! { unsafe extern "C++" { - #[doc = "Notify signal for the Q_PROPERTY"] - #[doc = "unsafe_property"] + #[doc = "Notify for the Q_PROPERTY"] + #[rust_name = "trivial_property_changed"] + fn emitTrivialPropertyChanged(self: Pin<&mut MyObjectQt>, ); + } + }, + ); + assert_tokens_eq( + &generated.cxx_mod_contents[7], + parse_quote! { + unsafe extern "C++" { + #[doc = "Connect the given function pointer to the signal "] + #[doc = "trivialPropertyChanged"] + #[doc = ", so that when the signal is emitted the function pointer is executed."] + #[must_use] + #[rust_name = "connect_trivial_property_changed"] + fn trivialPropertyChangedConnect(self: Pin <&mut MyObjectQt>, func: fn(Pin<&mut MyObjectQt>, ), conn_type : CxxQtConnectionType) -> CxxQtQMetaObjectConnection; + } + }, + ); + assert_tokens_eq( + &generated.cxx_qt_mod_contents[15], + parse_quote! { + impl MyObjectQt { + #[doc = "Connect the given function pointer to the signal "] + #[doc = "trivialPropertyChanged"] + #[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."] + #[must_use] + fn on_trivial_property_changed(self: Pin<&mut MyObjectQt>, func: fn(Pin<&mut MyObjectQt>, )) -> CxxQtQMetaObjectConnection + { + self.connect_trivial_property_changed(func, CxxQtConnectionType::AutoConnection) + } + } + }, + ); + + assert_tokens_eq( + &generated.cxx_mod_contents[8], + parse_quote! { + unsafe extern "C++" { + #[doc = "Notify for the Q_PROPERTY"] + #[rust_name = "opaque_property_changed"] + fn emitOpaquePropertyChanged(self: Pin<&mut MyObjectQt>, ); + } + }, + ); + assert_tokens_eq( + &generated.cxx_mod_contents[9], + parse_quote! { + unsafe extern "C++" { + #[doc = "Connect the given function pointer to the signal "] + #[doc = "opaquePropertyChanged"] + #[doc = ", so that when the signal is emitted the function pointer is executed."] + #[must_use] + #[rust_name = "connect_opaque_property_changed"] + fn opaquePropertyChangedConnect(self: Pin <&mut MyObjectQt>, func: fn(Pin<&mut MyObjectQt>, ), conn_type : CxxQtConnectionType) -> CxxQtQMetaObjectConnection; + } + }, + ); + assert_tokens_eq( + &generated.cxx_qt_mod_contents[16], + parse_quote! { + impl MyObjectQt { + #[doc = "Connect the given function pointer to the signal "] + #[doc = "opaquePropertyChanged"] + #[doc = ", so that when the signal is emitted the function pointer is executed."] #[doc = "\n"] - #[doc = "This can be used to manually notify a change when the unsafe mutable getter,"] - #[doc = "unsafe_property_mut"] - #[doc = ", is used."] + #[doc = "Note that this method uses a AutoConnection connection type."] + #[must_use] + fn on_opaque_property_changed(self: Pin<&mut MyObjectQt>, func: fn(Pin<&mut MyObjectQt>, )) -> CxxQtQMetaObjectConnection + { + self.connect_opaque_property_changed(func, CxxQtConnectionType::AutoConnection) + } + } + }, + ); + + assert_tokens_eq( + &generated.cxx_mod_contents[10], + parse_quote! { + unsafe extern "C++" { + #[doc = "Notify for the Q_PROPERTY"] #[rust_name = "unsafe_property_changed"] - fn unsafePropertyChanged(self: Pin<&mut MyObjectQt>); + fn emitUnsafePropertyChanged(self: Pin<&mut MyObjectQt>, ); + } + }, + ); + assert_tokens_eq( + &generated.cxx_mod_contents[11], + parse_quote! { + unsafe extern "C++" { + #[doc = "Connect the given function pointer to the signal "] + #[doc = "unsafePropertyChanged"] + #[doc = ", so that when the signal is emitted the function pointer is executed."] + #[must_use] + #[rust_name = "connect_unsafe_property_changed"] + fn unsafePropertyChangedConnect(self: Pin <&mut MyObjectQt>, func: fn(Pin<&mut MyObjectQt>, ), conn_type : CxxQtConnectionType) -> CxxQtQMetaObjectConnection; + } + }, + ); + assert_tokens_eq( + &generated.cxx_qt_mod_contents[17], + parse_quote! { + impl MyObjectQt { + #[doc = "Connect the given function pointer to the signal "] + #[doc = "unsafePropertyChanged"] + #[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."] + #[must_use] + fn on_unsafe_property_changed(self: Pin<&mut MyObjectQt>, func: fn(Pin<&mut MyObjectQt>, )) -> CxxQtQMetaObjectConnection + { + self.connect_unsafe_property_changed(func, CxxQtConnectionType::AutoConnection) + } } - } + }, ); } } diff --git a/crates/cxx-qt-gen/src/generator/rust/property/signal.rs b/crates/cxx-qt-gen/src/generator/rust/property/signal.rs index 7f1e6d784..1611e754a 100644 --- a/crates/cxx-qt-gen/src/generator/rust/property/signal.rs +++ b/crates/cxx-qt-gen/src/generator/rust/property/signal.rs @@ -3,32 +3,25 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 -use crate::generator::{ - naming::{property::QPropertyName, qobject::QObjectName}, - rust::fragment::RustFragmentPair, -}; -use quote::quote; +use syn::ForeignItemFn; -pub fn generate(idents: &QPropertyName, qobject_idents: &QObjectName) -> RustFragmentPair { - let cpp_class_name_rust = &qobject_idents.cpp_class.rust; - let notify_cpp = &idents.notify.cpp; - let notify_rust = idents.notify.rust.to_string(); - let ident_str = idents.name.rust.to_string(); - let getter_mutable_rust_str = idents.getter_mutable.rust.to_string(); +use crate::{ + generator::naming::{property::QPropertyName, qobject::QObjectName}, + parser::signals::ParsedSignal, +}; - RustFragmentPair { - cxx_bridge: vec![quote! { - unsafe extern "C++" { - #[doc = "Notify signal for the Q_PROPERTY"] - #[doc = #ident_str] - #[doc = "\n"] - #[doc = "This can be used to manually notify a change when the unsafe mutable getter,"] - #[doc = #getter_mutable_rust_str] - #[doc = ", is used."] - #[rust_name = #notify_rust] - fn #notify_cpp(self: Pin<&mut #cpp_class_name_rust>); - } - }], - implementation: vec![], - } +pub fn generate(idents: &QPropertyName, qobject_idents: &QObjectName) -> ParsedSignal { + // We build our signal in the generation phase as we need to use the naming + // structs to build the signal name + let cpp_class_rust = &qobject_idents.cpp_class.rust; + let notify_rust = &idents.notify.rust; + let method: ForeignItemFn = syn::parse_quote! { + #[doc = "Notify for the Q_PROPERTY"] + fn #notify_rust(self: Pin<&mut #cpp_class_rust>); + }; + ParsedSignal::from_property_method( + method, + idents.notify.clone(), + qobject_idents.cpp_class.rust.clone(), + ) } diff --git a/crates/cxx-qt-gen/src/generator/rust/qobject.rs b/crates/cxx-qt-gen/src/generator/rust/qobject.rs index bd8f87a2a..b59e10d73 100644 --- a/crates/cxx-qt-gen/src/generator/rust/qobject.rs +++ b/crates/cxx-qt-gen/src/generator/rust/qobject.rs @@ -89,12 +89,10 @@ impl GeneratedRustQObject { &qobject_idents, &qobject.inherited_methods, )?); - - if let Some(signals_enum) = &qobject.signals { - generated - .blocks - .append(&mut generate_rust_signals(signals_enum, &qobject_idents)?); - } + generated.blocks.append(&mut generate_rust_signals( + &qobject.signals, + &qobject_idents, + )?); // If this type is a singleton then we need to add an include if let Some(qml_metadata) = &qobject.qml_metadata { diff --git a/crates/cxx-qt-gen/src/generator/rust/signals.rs b/crates/cxx-qt-gen/src/generator/rust/signals.rs index 9c3ad063d..fb7adadd9 100644 --- a/crates/cxx-qt-gen/src/generator/rust/signals.rs +++ b/crates/cxx-qt-gen/src/generator/rust/signals.rs @@ -6,96 +6,64 @@ use crate::{ generator::{ naming::{qobject::QObjectName, signals::QSignalName}, - rust::{ - fragment::RustFragmentPair, qobject::GeneratedRustQObjectBlocks, - types::is_unsafe_cxx_type, - }, + rust::{fragment::RustFragmentPair, qobject::GeneratedRustQObjectBlocks}, }, - parser::signals::ParsedSignalsEnum, + parser::signals::ParsedSignal, }; use proc_macro2::TokenStream; use quote::quote; -use syn::{Ident, Result, Type}; +use syn::Result; pub fn generate_rust_signals( - signals_enum: &ParsedSignalsEnum, + signals: &Vec, qobject_idents: &QObjectName, ) -> Result { let mut generated = GeneratedRustQObjectBlocks::default(); - let cpp_class_name_rust = &qobject_idents.cpp_class.rust; - let signal_enum_ident = &signals_enum.ident; - let mut signal_matches = vec![]; - - // Add the original enum into the implementation - generated - .cxx_qt_mod_contents - .push(syn::Item::Enum(signals_enum.item.clone())); + let qobject_name = &qobject_idents.cpp_class.rust; // Create the methods for the other signals - for signal in &signals_enum.signals { + for signal in signals { let idents = QSignalName::from(signal); - let signal_ident_rust = idents.enum_name; let signal_ident_cpp_str = idents.name.cpp.to_string(); let emit_ident_cpp = &idents.emit_name.cpp; - let emit_ident_rust = &idents.emit_name.rust; let emit_ident_rust_str = idents.emit_name.rust.to_string(); let connect_ident_cpp = idents.connect_name.cpp; let connect_ident_rust = idents.connect_name.rust; let connect_ident_rust_str = connect_ident_rust.to_string(); let on_ident_rust = idents.on_name; - let mut parameters = signal + let parameters = signal .parameters .iter() .map(|parameter| { let ident = ¶meter.ident; - let mut ty = parameter.ty.clone(); - // Remove any lifetime from the signal, as this will be related - // to the enum. For the CXX methods these can just be - // normal references with inferred lifetimes. - if let Type::Reference(ty) = &mut ty { - ty.lifetime = None; - } + let ty = ¶meter.ty; quote! { #ident: #ty } }) .collect::>(); - let parameter_signatures = if signal.parameters.is_empty() { - quote! { self: Pin<&mut #cpp_class_name_rust> } - } else { - quote! { self: Pin<&mut #cpp_class_name_rust>, #(#parameters),* } - }; - let parameter_names = signal - .parameters - .iter() - .map(|parameter| parameter.ident.clone()) - .collect::>(); - // Determine if unsafe is required due to an unsafe parameter - let has_unsafe = if signal - .parameters - .iter() - .any(|parameter| is_unsafe_cxx_type(¶meter.ty)) - { - quote! { unsafe } + let self_type = if signal.mutable { + quote! { Pin<&mut #qobject_name> } } else { - quote! {} + quote! { &#qobject_name } }; - // Add the self context to parameters as this is used for the connection function pointer - parameters.insert( - 0, - quote! { - Pin<&mut #cpp_class_name_rust> - }, - ); + let mut unsafe_block = None; + let mut unsafe_call = Some(quote! { unsafe }); + if signal.safe { + std::mem::swap(&mut unsafe_call, &mut unsafe_block); + } + + let attrs = &signal.method.attrs; let fragment = RustFragmentPair { cxx_bridge: vec![ + // TODO: this will not call our wrapper in the future quote! { - unsafe extern "C++" { - #[doc(hidden)] + #unsafe_block extern "C++" { + #(#attrs)* #[rust_name = #emit_ident_rust_str] - #has_unsafe fn #emit_ident_cpp(#parameter_signatures); + #unsafe_call fn #emit_ident_cpp(self: #self_type, #(#parameters),*); } }, quote! { @@ -105,30 +73,25 @@ pub fn generate_rust_signals( #[doc = ", so that when the signal is emitted the function pointer is executed."] #[must_use] #[rust_name = #connect_ident_rust_str] - fn #connect_ident_cpp(self: Pin<&mut #cpp_class_name_rust>, func: #has_unsafe fn(#(#parameters),*), conn_type: CxxQtConnectionType) -> CxxQtQMetaObjectConnection; + fn #connect_ident_cpp(self: #self_type, func: #unsafe_call fn(#self_type, #(#parameters),*), conn_type: CxxQtConnectionType) -> CxxQtQMetaObjectConnection; } }, ], - // Note we do not need the #has_unsafe here as this only needs to be in the CXX bridge - // otherwise the function pointer itself needs to be unsafe implementation: vec![quote! { - impl #cpp_class_name_rust { + impl #qobject_name { #[doc = "Connect the given function pointer to the signal "] #[doc = #signal_ident_cpp_str] #[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."] #[must_use] - fn #on_ident_rust(self: Pin<&mut #cpp_class_name_rust>, func: fn(#(#parameters),*)) -> CxxQtQMetaObjectConnection + fn #on_ident_rust(self: #self_type, func: fn(#self_type, #(#parameters),*)) -> CxxQtQMetaObjectConnection { self.#connect_ident_rust(func, CxxQtConnectionType::AutoConnection) } } }], }; - signal_matches.push(quote! { - #signal_enum_ident::#signal_ident_rust { #(#parameter_names),* } => #has_unsafe { self.#emit_ident_rust(#(#parameter_names),*) } - }); generated .cxx_mod_contents @@ -138,23 +101,6 @@ pub fn generate_rust_signals( .append(&mut fragment.implementation_as_items()?); } - // Add the Rust method using the enum to call the methods - let qobject_ident_str = qobject_idents.rust_struct.rust.to_string(); - let signal_enum_ident_str = signal_enum_ident.to_string(); - generated.cxx_qt_mod_contents.push(syn::parse2(quote! { - impl #cpp_class_name_rust { - #[doc = "Emit the signal from the enum "] - #[doc = #signal_enum_ident_str] - #[doc = " on the QObject "] - #[doc = #qobject_ident_str] - pub fn emit(self: Pin<&mut Self>, signal: #signal_enum_ident) { - match signal { - #(#signal_matches),* - } - } - } - })?); - Ok(generated) } @@ -162,47 +108,41 @@ pub fn generate_rust_signals( mod tests { use super::*; + use crate::generator::naming::{qobject::tests::create_qobjectname, CombinedIdent}; + use crate::parser::parameter::ParsedFunctionParameter; use crate::tests::assert_tokens_eq; - use crate::{ - generator::naming::qobject::tests::create_qobjectname, parser::signals::ParsedSignalsEnum, - }; - use quote::quote; - use syn::{parse_quote, ItemEnum}; + use quote::{format_ident, quote}; + use syn::parse_quote; #[test] - fn test_generate_rust_signals() { - let e: ItemEnum = parse_quote! { - #[cxx_qt::qsignals(MyObject)] - enum MySignals { - Ready, - DataChanged { - trivial: i32, - opaque: UniquePtr - }, - UnsafeSignal { - param: *mut T, - }, - #[cxx_name = "baseName"] - #[inherit] - ExistingSignal, - } + fn test_generate_rust_signal() { + let qsignal = ParsedSignal { + method: parse_quote! { + fn ready(self: Pin<&mut MyObject>); + }, + qobject_ident: format_ident!("MyObject"), + mutable: true, + parameters: vec![], + ident: CombinedIdent { + cpp: format_ident!("ready"), + rust: format_ident!("ready"), + }, + safe: true, + inherit: false, }; - let signals_enum = ParsedSignalsEnum::from(&e, 0).unwrap(); let qobject_idents = create_qobjectname(); - let generated = generate_rust_signals(&signals_enum, &qobject_idents).unwrap(); + let generated = generate_rust_signals(&vec![qsignal], &qobject_idents).unwrap(); - assert_eq!(generated.cxx_mod_contents.len(), 8); - assert_eq!(generated.cxx_qt_mod_contents.len(), 6); + assert_eq!(generated.cxx_mod_contents.len(), 2); + assert_eq!(generated.cxx_qt_mod_contents.len(), 1); - // Ready assert_tokens_eq( &generated.cxx_mod_contents[0], quote! { unsafe extern "C++" { - #[doc(hidden)] - #[rust_name = "emit_ready"] - fn emitReady(self: Pin<&mut MyObjectQt>); + #[rust_name = "ready"] + fn emitReady(self: Pin<&mut MyObjectQt>, ); } }, ); @@ -215,24 +155,74 @@ mod tests { #[doc = ", so that when the signal is emitted the function pointer is executed."] #[must_use] #[rust_name = "connect_ready"] - fn readyConnect(self: Pin<&mut MyObjectQt>, func: fn(Pin<&mut MyObjectQt>), conn_type : CxxQtConnectionType) -> CxxQtQMetaObjectConnection; + fn readyConnect(self: Pin<&mut MyObjectQt>, func: fn(Pin<&mut MyObjectQt>, ), conn_type : CxxQtConnectionType) -> CxxQtQMetaObjectConnection; + } + }, + ); + assert_tokens_eq( + &generated.cxx_qt_mod_contents[0], + quote! { + impl MyObjectQt { + #[doc = "Connect the given function pointer to the signal "] + #[doc = "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."] + #[must_use] + fn on_ready(self: Pin<&mut MyObjectQt>, func: fn(Pin<&mut MyObjectQt>, )) -> CxxQtQMetaObjectConnection + { + self.connect_ready(func, CxxQtConnectionType::AutoConnection) + } } }, ); + } + + #[test] + fn test_generate_rust_signal_parameters() { + let qsignal = ParsedSignal { + method: parse_quote! { + #[attribute] + fn data_changed(self: Pin<&mut MyObject>, trivial: i32, opaque: UniquePtr); + }, + qobject_ident: format_ident!("MyObject"), + mutable: true, + parameters: vec![ + ParsedFunctionParameter { + ident: format_ident!("trivial"), + ty: parse_quote! { i32 }, + }, + ParsedFunctionParameter { + ident: format_ident!("opaque"), + ty: parse_quote! { UniquePtr }, + }, + ], + ident: CombinedIdent { + cpp: format_ident!("dataChanged"), + rust: format_ident!("data_changed"), + }, + safe: true, + inherit: false, + }; + let qobject_idents = create_qobjectname(); + + let generated = generate_rust_signals(&vec![qsignal], &qobject_idents).unwrap(); + + assert_eq!(generated.cxx_mod_contents.len(), 2); + assert_eq!(generated.cxx_qt_mod_contents.len(), 1); - // DataChanged assert_tokens_eq( - &generated.cxx_mod_contents[2], + &generated.cxx_mod_contents[0], quote! { unsafe extern "C++" { - #[doc(hidden)] - #[rust_name = "emit_data_changed"] + #[attribute] + #[rust_name = "data_changed"] fn emitDataChanged(self: Pin<&mut MyObjectQt>, trivial: i32, opaque: UniquePtr); } }, ); assert_tokens_eq( - &generated.cxx_mod_contents[3], + &generated.cxx_mod_contents[1], quote! { unsafe extern "C++" { #[doc = "Connect the given function pointer to the signal "] @@ -244,127 +234,141 @@ mod tests { } }, ); - - // UnsafeSignal assert_tokens_eq( - &generated.cxx_mod_contents[4], - quote! { - unsafe extern "C++" { - #[doc(hidden)] - #[rust_name = "emit_unsafe_signal"] - unsafe fn emitUnsafeSignal(self: Pin<&mut MyObjectQt>, param: *mut T); - } - }, - ); - assert_tokens_eq( - &generated.cxx_mod_contents[5], + &generated.cxx_qt_mod_contents[0], quote! { - unsafe extern "C++" { + impl MyObjectQt { #[doc = "Connect the given function pointer to the signal "] - #[doc = "unsafeSignal"] + #[doc = "dataChanged"] #[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."] #[must_use] - #[rust_name = "connect_unsafe_signal"] - fn unsafeSignalConnect(self: Pin <&mut MyObjectQt>, func: unsafe fn(Pin<&mut MyObjectQt>, param: *mut T), conn_type : CxxQtConnectionType) -> CxxQtQMetaObjectConnection; + fn on_data_changed(self: Pin<&mut MyObjectQt>, func: fn(Pin<&mut MyObjectQt>, trivial: i32, opaque: UniquePtr)) -> CxxQtQMetaObjectConnection + { + self.connect_data_changed(func, CxxQtConnectionType::AutoConnection) + } } }, ); + } + + #[test] + fn test_generate_rust_signal_unsafe() { + let qsignal = ParsedSignal { + method: parse_quote! { + unsafe fn unsafe_signal(self: Pin<&mut MyObject>, param: *mut T); + }, + qobject_ident: format_ident!("MyObject"), + mutable: true, + parameters: vec![ParsedFunctionParameter { + ident: format_ident!("param"), + ty: parse_quote! { *mut T }, + }], + ident: CombinedIdent { + cpp: format_ident!("unsafeSignal"), + rust: format_ident!("unsafe_signal"), + }, + safe: false, + inherit: false, + }; + let qobject_idents = create_qobjectname(); + + let generated = generate_rust_signals(&vec![qsignal], &qobject_idents).unwrap(); + + assert_eq!(generated.cxx_mod_contents.len(), 2); + assert_eq!(generated.cxx_qt_mod_contents.len(), 1); - // ExistingSignal assert_tokens_eq( - &generated.cxx_mod_contents[6], + &generated.cxx_mod_contents[0], quote! { - unsafe extern "C++" { - #[doc(hidden)] - #[rust_name = "emit_existing_signal"] - fn emitBaseName(self: Pin<&mut MyObjectQt>); + extern "C++" { + #[rust_name = "unsafe_signal"] + unsafe fn emitUnsafeSignal(self: Pin<&mut MyObjectQt>, param: *mut T); } }, ); assert_tokens_eq( - &generated.cxx_mod_contents[7], + &generated.cxx_mod_contents[1], quote! { unsafe extern "C++" { #[doc = "Connect the given function pointer to the signal "] - #[doc = "baseName"] + #[doc = "unsafeSignal"] #[doc = ", so that when the signal is emitted the function pointer is executed."] #[must_use] - #[rust_name = "connect_existing_signal"] - fn baseNameConnect(self: Pin<& mut MyObjectQt>, func: fn(Pin<&mut MyObjectQt>), conn_type : CxxQtConnectionType) -> CxxQtQMetaObjectConnection; + #[rust_name = "connect_unsafe_signal"] + fn unsafeSignalConnect(self: Pin <&mut MyObjectQt>, func: unsafe fn(Pin<&mut MyObjectQt>, param: *mut T), conn_type : CxxQtConnectionType) -> CxxQtQMetaObjectConnection; } }, ); - - // enum assert_tokens_eq( &generated.cxx_qt_mod_contents[0], - quote! { - enum MySignals { - Ready, - DataChanged { - trivial: i32, - opaque: UniquePtr - }, - UnsafeSignal { - param: *mut T, - }, - ExistingSignal, - } - }, - ); - assert_tokens_eq( - &generated.cxx_qt_mod_contents[1], quote! { impl MyObjectQt { #[doc = "Connect the given function pointer to the signal "] - #[doc = "ready"] + #[doc = "unsafeSignal"] #[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."] #[must_use] - fn on_ready(self: Pin<&mut MyObjectQt>, func: fn(Pin<&mut MyObjectQt>)) -> CxxQtQMetaObjectConnection + fn on_unsafe_signal(self: Pin<&mut MyObjectQt>, func: fn(Pin<&mut MyObjectQt>, param: *mut T)) -> CxxQtQMetaObjectConnection { - self.connect_ready(func, CxxQtConnectionType::AutoConnection) + self.connect_unsafe_signal(func, CxxQtConnectionType::AutoConnection) } } }, ); + } + + #[test] + fn test_generate_rust_signal_existing() { + let qsignal = ParsedSignal { + method: parse_quote! { + #[inherit] + fn existing_signal(self: Pin<&mut MyObject>, ); + }, + qobject_ident: format_ident!("MyObject"), + mutable: true, + parameters: vec![], + ident: CombinedIdent { + cpp: format_ident!("baseName"), + rust: format_ident!("existing_signal"), + }, + safe: true, + inherit: true, + }; + let qobject_idents = create_qobjectname(); + + let generated = generate_rust_signals(&vec![qsignal], &qobject_idents).unwrap(); + + assert_eq!(generated.cxx_mod_contents.len(), 2); + assert_eq!(generated.cxx_qt_mod_contents.len(), 1); + assert_tokens_eq( - &generated.cxx_qt_mod_contents[2], + &generated.cxx_mod_contents[0], quote! { - impl MyObjectQt { - #[doc = "Connect the given function pointer to the signal "] - #[doc = "dataChanged"] - #[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."] - #[must_use] - fn on_data_changed(self: Pin<&mut MyObjectQt>, func: fn(Pin<&mut MyObjectQt>, trivial: i32, opaque: UniquePtr)) -> CxxQtQMetaObjectConnection - { - self.connect_data_changed(func, CxxQtConnectionType::AutoConnection) - } + unsafe extern "C++" { + #[inherit] + #[rust_name = "existing_signal"] + fn emitBaseName(self: Pin<&mut MyObjectQt>, ); } }, ); assert_tokens_eq( - &generated.cxx_qt_mod_contents[3], + &generated.cxx_mod_contents[1], quote! { - impl MyObjectQt { + unsafe extern "C++" { #[doc = "Connect the given function pointer to the signal "] - #[doc = "unsafeSignal"] + #[doc = "baseName"] #[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."] #[must_use] - fn on_unsafe_signal(self: Pin<&mut MyObjectQt>, func: fn(Pin<&mut MyObjectQt>, param: *mut T)) -> CxxQtQMetaObjectConnection - { - self.connect_unsafe_signal(func, CxxQtConnectionType::AutoConnection) - } + #[rust_name = "connect_existing_signal"] + fn baseNameConnect(self: Pin<& mut MyObjectQt>, func: fn(Pin<&mut MyObjectQt>, ), conn_type : CxxQtConnectionType) -> CxxQtQMetaObjectConnection; } }, ); assert_tokens_eq( - &generated.cxx_qt_mod_contents[4], + &generated.cxx_qt_mod_contents[0], quote! { impl MyObjectQt { #[doc = "Connect the given function pointer to the signal "] @@ -373,31 +377,12 @@ mod tests { #[doc = "\n"] #[doc = "Note that this method uses a AutoConnection connection type."] #[must_use] - fn on_existing_signal(self: Pin<&mut MyObjectQt>, func: fn(Pin<&mut MyObjectQt>)) -> CxxQtQMetaObjectConnection + fn on_existing_signal(self: Pin<&mut MyObjectQt>, func: fn(Pin<&mut MyObjectQt>, )) -> CxxQtQMetaObjectConnection { self.connect_existing_signal(func, CxxQtConnectionType::AutoConnection) } } }, ); - assert_tokens_eq( - &generated.cxx_qt_mod_contents[5], - quote! { - impl MyObjectQt { - #[doc = "Emit the signal from the enum "] - #[doc = "MySignals"] - #[doc = " on the QObject "] - #[doc = "MyObject"] - pub fn emit(self: Pin<&mut Self>, signal: MySignals) { - match signal { - MySignals::Ready {} => { self.emit_ready() }, - MySignals::DataChanged { trivial, opaque } => { self.emit_data_changed(trivial, opaque) }, - MySignals::UnsafeSignal { param } => unsafe { self.emit_unsafe_signal(param) }, - MySignals::ExistingSignal {} => { self.emit_existing_signal() } - } - } - } - }, - ); } } diff --git a/crates/cxx-qt-gen/src/parser/cxxqtdata.rs b/crates/cxx-qt-gen/src/parser/cxxqtdata.rs index 262a3cef3..d8feea979 100644 --- a/crates/cxx-qt-gen/src/parser/cxxqtdata.rs +++ b/crates/cxx-qt-gen/src/parser/cxxqtdata.rs @@ -4,15 +4,12 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 use crate::syntax::foreignmod::{foreign_mod_to_foreign_item_types, verbatim_to_foreign_mod}; -use crate::syntax::{ - attribute::{attribute_find_path, attribute_tokens_to_ident}, - path::path_to_single_ident, -}; +use crate::syntax::{attribute::attribute_find_path, path::path_to_single_ident}; use crate::{ parser::{ - inherit::{InheritMethods, ParsedInheritedMethod}, + inherit::{InheritMethods, MaybeInheritMethods, ParsedInheritedMethod}, qobject::ParsedQObject, - signals::ParsedSignalsEnum, + signals::{MaybeSignalMethods, ParsedSignal, SignalMethods}, }, syntax::expr::expr_to_string, }; @@ -20,12 +17,10 @@ use proc_macro2::TokenStream; use quote::ToTokens; use std::collections::BTreeMap; use syn::{ - spanned::Spanned, Attribute, Error, Ident, Item, ItemEnum, ItemForeignMod, ItemImpl, Result, - Type, TypePath, + spanned::Spanned, Attribute, Error, Ident, Item, ItemForeignMod, ItemImpl, Result, Type, + TypePath, }; -use super::inherit::MaybeInheritMethods; - #[derive(Default)] pub struct ParsedCxxMappings { /// Map of the cxx_name of any types defined in CXX extern blocks @@ -184,7 +179,6 @@ impl ParsedCxxQtData { /// Otherwise return the [syn::Item] to pass through to CXX pub fn parse_cxx_qt_item(&mut self, item: Item) -> Result> { match item { - Item::Enum(item_enum) => self.parse_enum(item_enum), Item::Impl(imp) => self.parse_impl(imp), // Ignore structs which are qobjects Item::Struct(s) if self.qobjects.contains_key(&s.ident) => Ok(None), @@ -193,21 +187,31 @@ impl ParsedCxxQtData { self.uses.push(item); Ok(None) } - Item::Verbatim(tokens) => self.try_parse_inherit_verbatim(tokens), + Item::Verbatim(tokens) => self.try_parse_verbatim(tokens), Item::ForeignMod(foreign_mod) => self.parse_foreign_mod(foreign_mod), _ => Ok(Some(item)), } } - fn try_parse_inherit_verbatim(&mut self, tokens: TokenStream) -> Result> { - let try_parse: MaybeInheritMethods = syn::parse2(tokens)?; + fn try_parse_verbatim(&mut self, tokens: TokenStream) -> Result> { + let try_parse: MaybeInheritMethods = syn::parse2(tokens.clone())?; match try_parse { MaybeInheritMethods::Found(inherited) => { self.add_inherited_methods(inherited)?; Ok(None) } - MaybeInheritMethods::PassThrough(item) => Ok(Some(item)), + MaybeInheritMethods::PassThrough(_item) => { + let try_parse: MaybeSignalMethods = syn::parse2(tokens)?; + + match try_parse { + MaybeSignalMethods::Found(signals) => { + self.add_signal_methods(signals)?; + Ok(None) + } + MaybeSignalMethods::PassThrough(item) => Ok(Some(item)), + } + } } } @@ -236,6 +240,30 @@ impl ParsedCxxQtData { Ok(()) } + fn parse_signals_mod(&mut self, tokens: TokenStream) -> Result<()> { + let signals: SignalMethods = syn::parse2(tokens)?; + + self.add_signal_methods(signals) + } + + fn add_signal_methods(&mut self, signals: SignalMethods) -> Result<()> { + for method in signals.base_functions.into_iter() { + let parsed_signal_method = ParsedSignal::parse(method, signals.safety)?; + + if let Some(ref mut qobject) = + self.qobjects.get_mut(&parsed_signal_method.qobject_ident) + { + qobject.signals.push(parsed_signal_method); + } else { + return Err(Error::new_spanned( + parsed_signal_method.qobject_ident, + "No QObject with this name found.", + )); + } + } + Ok(()) + } + fn parse_foreign_mod(&mut self, mut foreign_mod: ItemForeignMod) -> Result> { // Check if the foreign mod has cxx_qt::inherit on it if let Some(index) = attribute_find_path(&foreign_mod.attrs, &["cxx_qt", "inherit"]) { @@ -245,29 +273,17 @@ impl ParsedCxxQtData { self.parse_inherit_mod(foreign_mod.into_token_stream())?; return Ok(None); } - Ok(Some(Item::ForeignMod(foreign_mod))) - } - /// Parse a [syn::ItemEnum] into the qobjects if it's a CXX-Qt signal - /// otherwise return as a [syn::Item] to pass through. - fn parse_enum(&mut self, item_enum: ItemEnum) -> Result> { - // Check if the enum has cxx_qt::qsignals(T) - if let Some(index) = attribute_find_path(&item_enum.attrs, &["cxx_qt", "qsignals"]) { - let ident = attribute_tokens_to_ident(&item_enum.attrs[index])?; - // Find the matching QObject for the enum - if let Some(qobject) = self.qobjects.get_mut(&ident) { - qobject.signals = Some(ParsedSignalsEnum::from(&item_enum, index)?); - return Ok(None); - } else { - return Err(Error::new( - item_enum.span(), - "No matching QObject found for the given cxx_qt::qsignals enum.", - )); - } + // Check if the foreign mod has cxx_qt::qsignals on it + if let Some(index) = attribute_find_path(&foreign_mod.attrs, &["cxx_qt", "qsignals"]) { + // Remove the signals attribute + foreign_mod.attrs.remove(index); + + self.parse_signals_mod(foreign_mod.into_token_stream())?; + return Ok(None); } - // Passthrough this unknown enum - Ok(Some(Item::Enum(item_enum))) + Ok(Some(Item::ForeignMod(foreign_mod))) } /// Parse a [syn::ItemImpl] into the qobjects if it's a CXX-Qt implementation @@ -308,7 +324,7 @@ impl ParsedCxxQtData { mod tests { use super::*; - use crate::parser::qobject::tests::create_parsed_qobject; + use crate::{generator::naming::CombinedIdent, parser::qobject::tests::create_parsed_qobject}; use quote::format_ident; use syn::{parse_quote, ItemMod}; @@ -419,63 +435,6 @@ mod tests { assert_eq!(cxx_qt_data.qobjects.len(), 0); } - #[test] - fn test_find_and_merge_cxx_qt_item_enum_valid_signals() { - let mut cxx_qt_data = create_parsed_cxx_qt_data(); - - let item: Item = parse_quote! { - #[cxx_qt::qsignals(MyObject)] - enum MySignals { - Ready, - } - }; - let result = cxx_qt_data.parse_cxx_qt_item(item).unwrap(); - assert!(result.is_none()); - assert!(cxx_qt_data.qobjects[&qobject_ident()].signals.is_some()); - } - - #[test] - fn test_find_and_merge_cxx_qt_item_enum_unknown_qobject() { - let mut cxx_qt_data = create_parsed_cxx_qt_data(); - - // Valid signals enum but missing QObject - let item: Item = parse_quote! { - #[cxx_qt::qsignals(UnknownObj)] - enum MySignals { - Ready, - } - }; - let result = cxx_qt_data.parse_cxx_qt_item(item); - assert!(result.is_err()); - } - - #[test] - fn test_find_and_merge_cxx_qt_item_enum_passthrough() { - let mut cxx_qt_data = create_parsed_cxx_qt_data(); - - let item: Item = parse_quote! { - enum MySignals { - Ready, - } - }; - let result = cxx_qt_data.parse_cxx_qt_item(item).unwrap(); - assert!(result.is_some()); - } - - #[test] - fn test_find_and_merge_cxx_qt_item_enum_error() { - let mut cxx_qt_data = create_parsed_cxx_qt_data(); - - let item: Item = parse_quote! { - #[cxx_qt::qsignals] - enum MySignals { - Ready, - } - }; - let result = cxx_qt_data.parse_cxx_qt_item(item); - assert!(result.is_err()); - } - #[test] fn test_find_and_merge_cxx_qt_item_struct_qobject() { let mut cxx_qt_data = create_parsed_cxx_qt_data(); @@ -839,6 +798,91 @@ mod tests { assert_eq!(inherited[2].parameters[0].ident, "arg"); } + #[test] + fn test_parse_qsignals_safe() { + let mut cxxqtdata = create_parsed_cxx_qt_data(); + let block: Item = parse_quote! { + #[cxx_qt::qsignals] + unsafe extern "C++" { + fn ready(self: Pin<&mut qobject::MyObject>); + + #[cxx_name="cppDataChanged"] + #[inherit] + fn data_changed(self: Pin<&mut qobject::MyObject>, data: i32); + } + }; + cxxqtdata.parse_cxx_qt_item(block).unwrap(); + + let qobject = cxxqtdata.qobjects.get(&qobject_ident()).unwrap(); + + let signals = &qobject.signals; + assert_eq!(signals.len(), 2); + assert!(signals[0].mutable); + assert!(signals[1].mutable); + assert!(signals[0].safe); + assert!(signals[1].safe); + assert_eq!(signals[0].parameters.len(), 0); + assert_eq!(signals[1].parameters.len(), 1); + assert_eq!(signals[1].parameters[0].ident, "data"); + assert_eq!( + signals[0].ident, + CombinedIdent { + cpp: format_ident!("ready"), + rust: format_ident!("ready") + } + ); + assert_eq!( + signals[1].ident, + CombinedIdent { + cpp: format_ident!("cppDataChanged"), + rust: format_ident!("data_changed") + } + ); + assert!(!signals[0].inherit); + assert!(signals[1].inherit); + } + + #[test] + fn test_parse_qsignals_unknown_obj() { + let mut cxxqtdata = create_parsed_cxx_qt_data(); + let block: Item = parse_quote! { + #[cxx_qt::qsignals] + unsafe extern "C++" { + fn ready(self: Pin<&mut qobject::UnknownObj>); + } + }; + assert!(cxxqtdata.parse_cxx_qt_item(block).is_err()); + } + + #[test] + fn test_parse_qsignals_unsafe() { + let mut cxxqtdata = create_parsed_cxx_qt_data(); + let block: Item = parse_quote! { + #[cxx_qt::qsignals] + extern "C++" { + unsafe fn unsafe_signal(self: Pin<&mut qobject::MyObject>, arg: *mut T); + } + }; + cxxqtdata.parse_cxx_qt_item(block).unwrap(); + + let qobject = cxxqtdata.qobjects.get(&qobject_ident()).unwrap(); + + let signals = &qobject.signals; + assert_eq!(signals.len(), 1); + assert!(signals[0].mutable); + assert!(!signals[0].safe); + assert_eq!(signals[0].parameters.len(), 1); + assert_eq!(signals[0].parameters[0].ident, "arg"); + assert_eq!( + signals[0].ident, + CombinedIdent { + cpp: format_ident!("unsafeSignal"), + rust: format_ident!("unsafe_signal") + } + ); + assert!(!signals[0].inherit); + } + #[test] fn test_parse_threading() { let mut cxxqtdata = create_parsed_cxx_qt_data(); diff --git a/crates/cxx-qt-gen/src/parser/mod.rs b/crates/cxx-qt-gen/src/parser/mod.rs index 059ba3d7d..01240e5c9 100644 --- a/crates/cxx-qt-gen/src/parser/mod.rs +++ b/crates/cxx-qt-gen/src/parser/mod.rs @@ -162,9 +162,9 @@ mod tests { #[cxx_qt::qobject] pub struct MyObject; - #[cxx_qt::qsignals(MyObject)] - enum MySignals { - Ready, + #[cxx_qt::qsignals] + unsafe extern "C++" { + fn ready(self: Pin<&mut qobject::MyObject>); } } }; @@ -187,9 +187,9 @@ mod tests { #[cxx_qt::qobject] pub struct MyObject; - #[cxx_qt::qsignals(MyObject)] - enum MySignals { - Ready, + #[cxx_qt::qsignals] + unsafe extern "C++" { + fn ready(self: Pin<&mut qobject::MyObject>); } extern "Rust" { @@ -216,9 +216,9 @@ mod tests { #[cxx_qt::qobject] pub struct MyObject; - #[cxx_qt::qsignals(UnknownObj)] - enum MySignals { - Ready, + #[cxx_qt::qsignals] + unsafe extern "C++" { + fn ready(self: Pin<&mut qobject::UnknownObject>); } } }; diff --git a/crates/cxx-qt-gen/src/parser/parameter.rs b/crates/cxx-qt-gen/src/parser/parameter.rs index 91a9a154c..1c77392be 100644 --- a/crates/cxx-qt-gen/src/parser/parameter.rs +++ b/crates/cxx-qt-gen/src/parser/parameter.rs @@ -10,6 +10,7 @@ use syn::{ }; /// Describes a single parameter for a function +#[derive(Debug, PartialEq)] pub struct ParsedFunctionParameter { /// The [syn::Ident] of the parameter pub ident: Ident, diff --git a/crates/cxx-qt-gen/src/parser/qobject.rs b/crates/cxx-qt-gen/src/parser/qobject.rs index b5213d2e3..95c8347a4 100644 --- a/crates/cxx-qt-gen/src/parser/qobject.rs +++ b/crates/cxx-qt-gen/src/parser/qobject.rs @@ -12,7 +12,7 @@ use crate::{ inherit::ParsedInheritedMethod, invokable::ParsedQInvokable, property::{ParsedQProperty, ParsedRustField}, - signals::ParsedSignalsEnum, + signals::ParsedSignal, }, syntax::path::path_compare_str, }; @@ -44,8 +44,8 @@ pub struct ParsedQObject { /// The namespace of the QObject. If one isn't specified for the QObject, /// this will be the same as the module pub namespace: String, - /// Representation of the Signals enum that defines the Q_SIGNALS for the QObject - pub signals: Option, + /// Representation of the Q_SIGNALS for the QObject + pub signals: Vec, /// List of invokables that need to be implemented on the C++ object in Rust /// /// These will also be exposed as Q_INVOKABLE on the C++ object @@ -114,7 +114,7 @@ impl ParsedQObject { base_class, qobject_struct, namespace, - signals: None, + signals: vec![], invokables: vec![], inherited_methods: vec![], passthrough_impl_items: vec![], diff --git a/crates/cxx-qt-gen/src/parser/signals.rs b/crates/cxx-qt-gen/src/parser/signals.rs index d4e447cb2..159d0fcf5 100644 --- a/crates/cxx-qt-gen/src/parser/signals.rs +++ b/crates/cxx-qt-gen/src/parser/signals.rs @@ -4,91 +4,190 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 use crate::parser::parameter::ParsedFunctionParameter; +use crate::syntax::attribute::attribute_find_path; use crate::syntax::expr::expr_to_string; -use crate::syntax::{attribute::attribute_find_path, fields::fields_to_named_fields_mut}; -use syn::{Ident, ItemEnum, Result, Variant}; +use crate::syntax::foreignmod; +use crate::syntax::safety::Safety; +use crate::{generator::naming::CombinedIdent, syntax::types}; +use quote::format_ident; +use syn::Attribute; +use syn::{ + parse::{Parse, ParseStream}, + spanned::Spanned, + Error, ForeignItem, ForeignItemFn, Ident, Item, ItemForeignMod, LitStr, Result, Token, +}; + +/// Used when parsing a syn::Item::Verbatim, that we suspect may be a `#[cxx_qt::qsignals]` block, +/// but we don't yet know whether this is actually the case. +/// This is the case if `#[cxx_qt::qsignals]` is used with `unsafe extern "C++"`. +pub enum MaybeSignalMethods { + /// We found a `#[cxx_qt::qsignals]` block + Found(SignalMethods), + /// `#[cxx_qt::qsignals]` block not found, pass this Item through to outside code! + PassThrough(Item), +} + +impl Parse for MaybeSignalMethods { + fn parse(input: ParseStream) -> Result { + let lookahead = input.fork(); + if let Ok(attribute) = lookahead.call(Attribute::parse_outer) { + if attribute_find_path(attribute.as_slice(), &["cxx_qt", "qsignals"]).is_some() { + input.call(Attribute::parse_outer)?; + let methods = input.parse::()?; + return Ok(Self::Found(methods)); + } + } + + Ok(Self::PassThrough(input.parse()?)) + } +} + +/// This type is used when parsing the `#[cxx_qt::qsignals]` macro contents into raw ForeignItemFn items +pub struct SignalMethods { + pub safety: Safety, + pub base_functions: Vec, +} + +impl Parse for SignalMethods { + fn parse(input: ParseStream) -> Result { + let mut base_functions = Vec::new(); + + // Ensure that any attributes on the block have been removed + // + // Otherwise parsing of unsafe can fail due to #[doc] + let attrs = input.call(Attribute::parse_outer)?; + if !attrs.is_empty() { + return Err(Error::new( + attrs.first().span(), + "Unexpected attribute on #[cxx_qt::inherit] block.", + )); + } + + // This looks somewhat counter-intuitive, but if we add `unsafe` + // to the `extern "C++"` block, the contained functions will be safe to call. + let safety = if input.peek(Token![unsafe]) { + Safety::Safe + } else { + Safety::Unsafe + }; + if safety == Safety::Safe { + input.parse::()?; + } + + let extern_block = input.parse::()?; + if extern_block.abi.name != Some(LitStr::new("C++", extern_block.abi.span())) { + return Err(Error::new( + extern_block.abi.span(), + "qsignals blocks must be marked with `extern \"C++\"`", + )); + } + + for item in extern_block.items { + match item { + ForeignItem::Fn(function) => { + base_functions.push(function); + } + _ => { + return Err(Error::new( + item.span(), + "Only functions are allowed in #[cxx_qt::qsignals] blocks", + )) + } + } + } + + Ok(SignalMethods { + safety, + base_functions, + }) + } +} /// Describes an individual Signal pub struct ParsedSignal { - /// The name of the signal - pub ident: Ident, + /// The original [syn::ForeignItemFn] of the signal declaration + pub method: ForeignItemFn, + /// The type of the self argument + pub qobject_ident: Ident, + /// whether the signal is marked as mutable + pub mutable: bool, + /// Whether the method is safe to call. + pub safe: bool, /// The parameters of the signal pub parameters: Vec, - /// The name of the signal in C++ - pub cxx_name: Option, + /// The name of the signal + pub ident: CombinedIdent, /// If the signal is defined in the base class pub inherit: bool, } impl ParsedSignal { - pub fn from(variant: &mut Variant) -> Result { - // Find cxx_name and inherit - let inherit = if let Some(index) = attribute_find_path(&variant.attrs, &["inherit"]) { - // Remove the attribute from the original enum - // so that it doesn't end up in the Rust generation - variant.attrs.remove(index); - true - } else { - false - }; - let cxx_name = if let Some(index) = attribute_find_path(&variant.attrs, &["cxx_name"]) { - let str = expr_to_string(&variant.attrs[index].meta.require_name_value()?.value)?; - // Remove the attribute from the original enum - // so that it doesn't end up in the Rust generation - variant.attrs.remove(index); - Some(str) - } else { - None - }; - - // Read the fields into parameter blocks - let parameters = fields_to_named_fields_mut(&mut variant.fields)? - .into_iter() - .map(|field| { - Ok(ParsedFunctionParameter { - ident: field.ident.clone().unwrap(), - ty: field.ty.clone(), - }) - }) - .collect::>>()?; - - Ok(ParsedSignal { - ident: variant.ident.clone(), - parameters, - cxx_name, - inherit, - }) + /// Builds a signal from a given property method + pub fn from_property_method( + method: ForeignItemFn, + ident: CombinedIdent, + qobject_ident: Ident, + ) -> Self { + Self { + method, + qobject_ident, + mutable: true, + safe: true, + parameters: vec![], + ident, + inherit: false, + } } -} -/// Describes a Signals enum for a QObject -pub struct ParsedSignalsEnum { - /// The name of the signals enum - pub ident: Ident, - /// The original enum for the signals - pub item: ItemEnum, - /// A list of the signals defined in the enum - pub signals: Vec, -} + pub fn parse(mut method: ForeignItemFn, safety: Safety) -> Result { + if safety == Safety::Unsafe && method.sig.unsafety.is_none() { + return Err(Error::new( + method.span(), + "qsignals methods must be marked as unsafe or wrapped in an `unsafe extern \"C++\"` block!", + )); + } + + let mut inherit = false; + + let self_receiver = foreignmod::self_type_from_foreign_fn(&method.sig)?; + let (qobject_ident, mutability) = types::extract_qobject_ident(&self_receiver.ty)?; + let mutable = mutability.is_some(); + if !mutable { + return Err(Error::new( + method.span(), + "signals must be mutable, use Pin<&mut T> instead of T for the self type", + )); + } -impl ParsedSignalsEnum { - /// Constructs a ParsedSignals object from a given [syn::ItemEnum] block - pub fn from(item: &ItemEnum, attr_index: usize) -> Result { - // Remove the attribute index as we have processed it - let mut item = item.clone(); - item.attrs.remove(attr_index); + let parameters = ParsedFunctionParameter::parse_all_ignoring_receiver(&method.sig)?; - let signals = item - .variants - // Note we use mut here so that any attributes can be removed - .iter_mut() - .map(ParsedSignal::from) - .collect::>>()?; + let mut ident = CombinedIdent::from_rust_function(method.sig.ident.clone()); + + if let Some(index) = attribute_find_path(&method.attrs, &["cxx_name"]) { + ident.cpp = format_ident!( + "{}", + expr_to_string(&method.attrs[index].meta.require_name_value()?.value)? + ); + + method.attrs.remove(index); + } + + if let Some(index) = attribute_find_path(&method.attrs, &["inherit"]) { + inherit = true; + + method.attrs.remove(index); + } + + let safe = method.sig.unsafety.is_none(); Ok(Self { - ident: item.ident.clone(), - item, - signals, + method, + qobject_ident, + mutable, + parameters, + ident, + safe, + inherit, }) } } @@ -100,90 +199,159 @@ mod tests { use super::*; use crate::parser::tests::f64_type; - use crate::syntax::path::path_compare_str; #[test] - fn test_parsed_signals_from_empty() { - let e: ItemEnum = parse_quote! { - #[cxx_qt::qsignals(MyObject)] - enum MySignals {} + fn test_parse_signal() { + let method: ForeignItemFn = parse_quote! { + fn ready(self: Pin<&mut qobject::MyObject>); + }; + let signal = ParsedSignal::parse(method.clone(), Safety::Safe).unwrap(); + assert_eq!(signal.method, method); + assert_eq!(signal.qobject_ident, format_ident!("MyObject")); + assert!(signal.mutable); + assert_eq!(signal.parameters, vec![]); + assert_eq!( + signal.ident, + CombinedIdent { + cpp: format_ident!("ready"), + rust: format_ident!("ready") + } + ); + assert!(signal.safe); + assert!(!signal.inherit); + } + + #[test] + fn test_parse_signal_cxx_name() { + let method: ForeignItemFn = parse_quote! { + #[cxx_name = "cppReady"] + fn ready(self: Pin<&mut qobject::MyObject>); + }; + let signal = ParsedSignal::parse(method, Safety::Safe).unwrap(); + + let expected_method: ForeignItemFn = parse_quote! { + fn ready(self: Pin<&mut qobject::MyObject>); + }; + assert_eq!(signal.method, expected_method); + assert_eq!(signal.qobject_ident, format_ident!("MyObject")); + assert!(signal.mutable); + assert_eq!(signal.parameters, vec![]); + assert_eq!( + signal.ident, + CombinedIdent { + cpp: format_ident!("cppReady"), + rust: format_ident!("ready") + } + ); + assert!(signal.safe); + assert!(!signal.inherit); + } + + #[test] + fn test_parse_signal_inherit() { + let method: ForeignItemFn = parse_quote! { + #[inherit] + fn ready(self: Pin<&mut qobject::MyObject>); }; - let signals = ParsedSignalsEnum::from(&e, 0).unwrap(); - assert_eq!(signals.ident, "MySignals"); - assert_eq!(signals.item.attrs.len(), 0); - assert_eq!(signals.signals.len(), 0); + let signal = ParsedSignal::parse(method, Safety::Safe).unwrap(); + + let expected_method: ForeignItemFn = parse_quote! { + fn ready(self: Pin<&mut qobject::MyObject>); + }; + assert_eq!(signal.method, expected_method); + assert_eq!(signal.qobject_ident, format_ident!("MyObject")); + assert!(signal.mutable); + assert_eq!(signal.parameters, vec![]); + assert_eq!( + signal.ident, + CombinedIdent { + cpp: format_ident!("ready"), + rust: format_ident!("ready") + } + ); + assert!(signal.safe); + assert!(signal.inherit); } #[test] - fn test_parsed_signals_from_empty_attrs() { - let e: ItemEnum = parse_quote! { - #[before] - #[cxx_qt::qsignals(MyObject)] - #[after] - enum MySignals {} + fn test_parse_signal_mutable_err() { + let method: ForeignItemFn = parse_quote! { + fn ready(self: &qobject::MyObject); }; - let signals = ParsedSignalsEnum::from(&e, 1).unwrap(); - assert_eq!(signals.ident, "MySignals"); - assert_eq!(signals.item.attrs.len(), 2); - assert!(path_compare_str( - signals.item.attrs[0].meta.path(), - &["before"] - )); - assert!(path_compare_str( - signals.item.attrs[1].meta.path(), - &["after"] - )); - assert_eq!(signals.signals.len(), 0); + // Can't be immutable + assert!(ParsedSignal::parse(method, Safety::Safe).is_err()); } #[test] - fn test_parsed_signals_from_named() { - let e: ItemEnum = parse_quote! { - #[cxx_qt::qsignals(MyObject)] - enum MySignals { - Ready, - PointChanged { - x: f64, - y: f64 - }, - #[cxx_name = "baseName"] - #[inherit] - ExistingSignal, + fn test_parse_signal_parameters() { + let method: ForeignItemFn = parse_quote! { + fn ready(self: Pin<&mut qobject::MyObject>, x: f64, y: f64); + }; + let signal = ParsedSignal::parse(method.clone(), Safety::Safe).unwrap(); + assert_eq!(signal.method, method); + assert_eq!(signal.qobject_ident, format_ident!("MyObject")); + assert!(signal.mutable); + assert_eq!(signal.parameters.len(), 2); + assert_eq!(signal.parameters[0].ident, format_ident!("x")); + assert_eq!(signal.parameters[0].ty, f64_type()); + assert_eq!(signal.parameters[1].ident, format_ident!("y")); + assert_eq!(signal.parameters[1].ty, f64_type()); + assert_eq!( + signal.ident, + CombinedIdent { + cpp: format_ident!("ready"), + rust: format_ident!("ready") } + ); + assert!(signal.safe); + assert!(!signal.inherit); + } + + #[test] + fn test_parse_signal_qobject_self_missing() { + let method: ForeignItemFn = parse_quote! { + fn ready(x: f64); }; - let signals = ParsedSignalsEnum::from(&e, 0).unwrap(); - assert_eq!(signals.ident, "MySignals"); - assert_eq!(signals.item.attrs.len(), 0); - assert_eq!(signals.signals.len(), 3); - assert!(!signals.signals[0].inherit); - assert!(signals.signals[0].cxx_name.is_none()); - assert_eq!(signals.signals[0].ident, "Ready"); - assert_eq!(signals.signals[0].parameters.len(), 0); - assert!(!signals.signals[1].inherit); - assert!(signals.signals[1].cxx_name.is_none()); - assert_eq!(signals.signals[1].ident, "PointChanged"); - assert_eq!(signals.signals[1].parameters.len(), 2); - assert_eq!(signals.signals[1].parameters[0].ident, "x"); - assert_eq!(signals.signals[1].parameters[0].ty, f64_type()); - assert_eq!(signals.signals[1].parameters[1].ident, "y"); - assert_eq!(signals.signals[1].parameters[1].ty, f64_type()); - assert!(signals.signals[2].inherit); - assert!(signals.signals[2].cxx_name.is_some()); - assert_eq!(signals.signals[2].cxx_name.as_ref().unwrap(), "baseName"); - assert_eq!(signals.signals[2].ident, "ExistingSignal"); - assert_eq!(signals.signals[2].parameters.len(), 0); + // Can't have a missing self + assert!(ParsedSignal::parse(method, Safety::Safe).is_err()); } #[test] - fn test_parsed_signals_from_unnamed() { - let e: ItemEnum = parse_quote! { - #[cxx_qt::qsignals(MyObject)] - enum MySignals { - Ready, - PointChanged(f64, f64), + fn test_parse_signal_qobject_ident_missing() { + let method: ForeignItemFn = parse_quote! { + fn ready(&self); + }; + // Can't have a missing ident + assert!(ParsedSignal::parse(method, Safety::Safe).is_err()); + } + + #[test] + fn test_parse_signal_unsafe() { + let method: ForeignItemFn = parse_quote! { + unsafe fn ready(self: Pin<&mut qobject::MyObject>); + }; + let signal = ParsedSignal::parse(method.clone(), Safety::Unsafe).unwrap(); + assert_eq!(signal.method, method); + assert_eq!(signal.qobject_ident, format_ident!("MyObject")); + assert!(signal.mutable); + assert_eq!(signal.parameters, vec![]); + assert_eq!( + signal.ident, + CombinedIdent { + cpp: format_ident!("ready"), + rust: format_ident!("ready") } + ); + assert!(!signal.safe); + assert!(!signal.inherit); + } + + #[test] + fn test_parse_signal_unsafe_error() { + let method: ForeignItemFn = parse_quote! { + fn ready(self: Pin<&mut qobject::MyObject>); }; - let signals = ParsedSignalsEnum::from(&e, 0); - assert!(signals.is_err()); + // Can't be safe on the block and the method + assert!(ParsedSignal::parse(method, Safety::Unsafe).is_err()); } } diff --git a/crates/cxx-qt-gen/src/syntax/attribute.rs b/crates/cxx-qt-gen/src/syntax/attribute.rs index e5ee77569..4d17dfb89 100644 --- a/crates/cxx-qt-gen/src/syntax/attribute.rs +++ b/crates/cxx-qt-gen/src/syntax/attribute.rs @@ -7,10 +7,14 @@ use crate::syntax::path::path_compare_str; use proc_macro2::Span; use std::collections::HashMap; use syn::{ - ext::IdentExt, + // ext::IdentExt, parse::{Parse, ParseStream}, spanned::Spanned, - Attribute, Error, Ident, Meta, Result, Token, + Attribute, + Error, + Meta, + Result, + Token, }; /// Representation of a key and an optional value in an attribute, eg `attribute(key = value)` or `attribute(key)` @@ -43,32 +47,6 @@ pub fn attribute_find_path(attrs: &[Attribute], path: &[&str]) -> Option None } -/// Returns the [syn::Ident] T from attribute(T) and errors if there is none or many -pub fn attribute_tokens_to_ident(attr: &Attribute) -> Result { - let attrs = attribute_tokens_to_list(attr)?; - if attrs.len() == 1 { - Ok(attrs[0].clone()) - } else { - Err(Error::new( - attr.span(), - "Expected only one ident in the attribute", - )) - } -} - -/// Returns the list of [syn::Ident]'s A, B, C from attribute(A, B, C) -/// and errors if there is a parser error -pub fn attribute_tokens_to_list(attr: &Attribute) -> Result> { - attr.meta - .require_list()? - .parse_args_with(|input: ParseStream| -> Result> { - Ok(input - .parse_terminated(Ident::parse_any, Token![,])? - .into_iter() - .collect::>()) - }) -} - /// Whether the attribute has a default value if there is one missing /// /// This is useful in attribute maps where only a key may be specified. @@ -130,60 +108,6 @@ mod tests { assert!(attribute_find_path(&module.attrs, &["cxx_qt", "missing"]).is_none()); } - #[test] - fn test_attribute_tokens_to_ident() { - let module: ItemMod = parse_quote! { - #[qinvokable] - #[cxx_qt::bridge] - #[cxx_qt::qsignals(MyObject)] - #[cxx_qt::bridge(namespace = "my::namespace")] - #[cxx_qt::list(A, B, C)] - #[cxx_qt::empty()] - mod module; - }; - - assert!(attribute_tokens_to_ident(&module.attrs[0]).is_err()); - assert!(attribute_tokens_to_ident(&module.attrs[1]).is_err()); - assert!(attribute_tokens_to_ident(&module.attrs[2]).is_ok()); - assert_eq!( - attribute_tokens_to_ident(&module.attrs[2]).unwrap(), - "MyObject" - ); - assert!(attribute_tokens_to_ident(&module.attrs[3]).is_err()); - assert!(attribute_tokens_to_ident(&module.attrs[4]).is_err()); - assert!(attribute_tokens_to_ident(&module.attrs[5]).is_err()); - } - - #[test] - fn test_attribute_tokens_to_list() { - let module: ItemMod = parse_quote! { - #[qinvokable] - #[cxx_qt::bridge] - #[cxx_qt::qsignals(MyObject)] - #[cxx_qt::bridge(namespace = "my::namespace")] - #[cxx_qt::list(A, B, C)] - #[cxx_qt::list()] - mod module; - }; - - assert!(attribute_tokens_to_list(&module.attrs[0]).is_err()); - assert!(attribute_tokens_to_list(&module.attrs[1]).is_err()); - assert!(attribute_tokens_to_list(&module.attrs[2]).is_ok()); - assert_eq!(attribute_tokens_to_list(&module.attrs[2]).unwrap().len(), 1); - assert_eq!( - attribute_tokens_to_list(&module.attrs[2]).unwrap()[0], - "MyObject" - ); - assert!(attribute_tokens_to_list(&module.attrs[3]).is_err()); - assert!(attribute_tokens_to_list(&module.attrs[4]).is_ok()); - assert_eq!(attribute_tokens_to_list(&module.attrs[4]).unwrap().len(), 3); - assert_eq!(attribute_tokens_to_list(&module.attrs[4]).unwrap()[0], "A"); - assert_eq!(attribute_tokens_to_list(&module.attrs[4]).unwrap()[1], "B"); - assert_eq!(attribute_tokens_to_list(&module.attrs[4]).unwrap()[2], "C"); - assert!(attribute_tokens_to_list(&module.attrs[5]).is_ok()); - assert_eq!(attribute_tokens_to_list(&module.attrs[5]).unwrap().len(), 0); - } - #[test] fn test_attribute_tokens_to_map() { let module: ItemMod = parse_quote! { diff --git a/crates/cxx-qt-gen/test_inputs/passthrough_and_naming.rs b/crates/cxx-qt-gen/test_inputs/passthrough_and_naming.rs index ed0eb7086..823dc1325 100644 --- a/crates/cxx-qt-gen/test_inputs/passthrough_and_naming.rs +++ b/crates/cxx-qt-gen/test_inputs/passthrough_and_naming.rs @@ -91,9 +91,9 @@ pub mod ffi { } } - #[cxx_qt::qsignals(MyObject)] - pub enum MySignals { - Ready, + #[cxx_qt::qsignals] + unsafe extern "C++" { + fn ready(self: Pin<&mut qobject::MyObject>); } impl qobject::MyObject { @@ -140,9 +140,10 @@ pub mod ffi { } } - #[cxx_qt::qsignals(SecondObject)] - pub enum SecondSignals { - Ready, + #[cxx_qt::qsignals] + unsafe extern "C++" { + #[my_attribute] + fn ready(self: Pin<&mut qobject::SecondObject>); } impl qobject::SecondObject { diff --git a/crates/cxx-qt-gen/test_inputs/signals.rs b/crates/cxx-qt-gen/test_inputs/signals.rs index 3581ed07a..21b92e184 100644 --- a/crates/cxx-qt-gen/test_inputs/signals.rs +++ b/crates/cxx-qt-gen/test_inputs/signals.rs @@ -6,23 +6,27 @@ mod ffi { type QPoint = cxx_qt_lib::QPoint; } - #[cxx_qt::qsignals(MyObject)] - enum MySignals<'a> { - Ready, - DataChanged { + #[cxx_qt::qsignals] + unsafe extern "C++" { + fn ready(self: Pin<&mut qobject::MyObject>); + + fn data_changed( + self: Pin<&mut qobject::MyObject>, first: i32, second: UniquePtr, third: QPoint, fourth: &'a QPoint, - }, + ); + #[cxx_name = "newData"] #[inherit] - BaseClassNewData { + fn base_class_new_data( + self: Pin<&mut qobject::MyObject>, first: i32, second: UniquePtr, third: QPoint, fourth: &'a QPoint, - }, + ); } #[cxx_qt::qobject] @@ -38,12 +42,8 @@ mod ffi { }, cxx_qt_lib::ConnectionType::AutoConnection, ); - self.as_mut().emit(MySignals::DataChanged { - first: 1, - second: Opaque::new(), - third: QPoint::new(1, 2), - fourth: &QPoint::new(1, 2), - }); + self.as_mut() + .data_changed(1, Opaque::new(), QPoint::new(1, 2), &QPoint::new(1, 2)); } } } diff --git a/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.cpp b/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.cpp index c4dff461a..7f5779f9a 100644 --- a/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.cpp +++ b/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.cpp @@ -37,6 +37,27 @@ MyObject::setPropertyName(::std::int32_t const& value) m_rustObj->setPropertyName(*this, value); } +void +MyObject::emitPropertyNameChanged() +{ + Q_EMIT propertyNameChanged(); +} + +::QMetaObject::Connection +MyObject::propertyNameChangedConnect(::rust::Fn func, + ::Qt::ConnectionType type) +{ + return ::QObject::connect( + this, + &MyObject::propertyNameChanged, + this, + [&, func = ::std::move(func)]() { + const ::std::lock_guard<::std::recursive_mutex> guard(*m_rustObjMutex); + func(*this); + }, + type); +} + void MyObject::invokableName() { @@ -103,6 +124,24 @@ SecondObject::setPropertyName(::std::int32_t const& value) m_rustObj->setPropertyName(*this, value); } +void +SecondObject::emitPropertyNameChanged() +{ + Q_EMIT propertyNameChanged(); +} + +::QMetaObject::Connection +SecondObject::propertyNameChangedConnect(::rust::Fn func, + ::Qt::ConnectionType type) +{ + return ::QObject::connect( + this, + &SecondObject::propertyNameChanged, + this, + [&, func = ::std::move(func)]() { func(*this); }, + type); +} + void SecondObject::invokableName() { diff --git a/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.h b/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.h index baffd62a9..5886e2827 100644 --- a/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.h +++ b/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.h @@ -37,6 +37,10 @@ class MyObject : public QStringListModel ::std::int32_t const& getPropertyName() const; Q_SLOT void setPropertyName(::std::int32_t const& value); Q_SIGNAL void propertyNameChanged(); + void emitPropertyNameChanged(); + ::QMetaObject::Connection propertyNameChangedConnect( + ::rust::Fn func, + ::Qt::ConnectionType type); Q_INVOKABLE void invokableName(); Q_SIGNAL void ready(); void emitReady(); @@ -71,6 +75,10 @@ class SecondObject : public QObject ::std::int32_t const& getPropertyName() const; Q_SLOT void setPropertyName(::std::int32_t const& value); Q_SIGNAL void propertyNameChanged(); + void emitPropertyNameChanged(); + ::QMetaObject::Connection propertyNameChangedConnect( + ::rust::Fn func, + ::Qt::ConnectionType type); Q_INVOKABLE void invokableName(); Q_SIGNAL void ready(); void emitReady(); diff --git a/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.rs b/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.rs index f09f6cd4a..b2a902ef4 100644 --- a/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.rs +++ b/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.rs @@ -83,22 +83,28 @@ pub mod ffi { fn set_property_name(self: &mut MyObject, cpp: Pin<&mut MyObjectQt>, value: i32); } unsafe extern "C++" { - #[doc = "Notify signal for the Q_PROPERTY"] - #[doc = "property_name"] - #[doc = "\n"] - #[doc = "This can be used to manually notify a change when the unsafe mutable getter,"] - #[doc = "property_name_mut"] - #[doc = ", is used."] + #[doc = "Notify for the Q_PROPERTY"] #[rust_name = "property_name_changed"] - fn propertyNameChanged(self: Pin<&mut MyObjectQt>); + fn emitPropertyNameChanged(self: Pin<&mut MyObjectQt>); + } + unsafe extern "C++" { + #[doc = "Connect the given function pointer to the signal "] + #[doc = "propertyNameChanged"] + #[doc = ", so that when the signal is emitted the function pointer is executed."] + #[must_use] + #[rust_name = "connect_property_name_changed"] + fn propertyNameChangedConnect( + self: Pin<&mut MyObjectQt>, + func: fn(Pin<&mut MyObjectQt>), + conn_type: CxxQtConnectionType, + ) -> CxxQtQMetaObjectConnection; } extern "Rust" { #[cxx_name = "invokableNameWrapper"] fn invokable_name_wrapper(self: &mut MyObject, cpp: Pin<&mut MyObjectQt>); } unsafe extern "C++" { - #[doc(hidden)] - #[rust_name = "emit_ready"] + #[rust_name = "ready"] fn emitReady(self: Pin<&mut MyObjectQt>); } unsafe extern "C++" { @@ -151,22 +157,29 @@ pub mod ffi { fn set_property_name(self: &mut SecondObject, cpp: Pin<&mut SecondObjectQt>, value: i32); } unsafe extern "C++" { - #[doc = "Notify signal for the Q_PROPERTY"] - #[doc = "property_name"] - #[doc = "\n"] - #[doc = "This can be used to manually notify a change when the unsafe mutable getter,"] - #[doc = "property_name_mut"] - #[doc = ", is used."] + #[doc = "Notify for the Q_PROPERTY"] #[rust_name = "property_name_changed"] - fn propertyNameChanged(self: Pin<&mut SecondObjectQt>); + fn emitPropertyNameChanged(self: Pin<&mut SecondObjectQt>); + } + unsafe extern "C++" { + #[doc = "Connect the given function pointer to the signal "] + #[doc = "propertyNameChanged"] + #[doc = ", so that when the signal is emitted the function pointer is executed."] + #[must_use] + #[rust_name = "connect_property_name_changed"] + fn propertyNameChangedConnect( + self: Pin<&mut SecondObjectQt>, + func: fn(Pin<&mut SecondObjectQt>), + conn_type: CxxQtConnectionType, + ) -> CxxQtQMetaObjectConnection; } extern "Rust" { #[cxx_name = "invokableNameWrapper"] fn invokable_name_wrapper(self: &mut SecondObject, cpp: Pin<&mut SecondObjectQt>); } unsafe extern "C++" { - #[doc(hidden)] - #[rust_name = "emit_ready"] + #[my_attribute] + #[rust_name = "ready"] fn emitReady(self: Pin<&mut SecondObjectQt>); } unsafe extern "C++" { @@ -270,6 +283,20 @@ mod cxx_qt_ffi { self.as_mut().property_name_changed(); } } + impl MyObjectQt { + #[doc = "Connect the given function pointer to the signal "] + #[doc = "propertyNameChanged"] + #[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."] + #[must_use] + fn on_property_name_changed( + self: Pin<&mut MyObjectQt>, + func: fn(Pin<&mut MyObjectQt>), + ) -> CxxQtQMetaObjectConnection { + self.connect_property_name_changed(func, CxxQtConnectionType::AutoConnection) + } + } impl MyObject { #[doc(hidden)] pub fn invokable_name_wrapper(self: &mut MyObject, cpp: Pin<&mut MyObjectQt>) { @@ -291,9 +318,6 @@ mod cxx_qt_ffi { impl MyObjectQt { my_macro!(); } - pub enum MySignals { - Ready, - } impl MyObjectQt { #[doc = "Connect the given function pointer to the signal "] #[doc = "ready"] @@ -308,17 +332,6 @@ mod cxx_qt_ffi { self.connect_ready(func, CxxQtConnectionType::AutoConnection) } } - impl MyObjectQt { - #[doc = "Emit the signal from the enum "] - #[doc = "MySignals"] - #[doc = " on the QObject "] - #[doc = "MyObject"] - pub fn emit(self: Pin<&mut Self>, signal: MySignals) { - match signal { - MySignals::Ready {} => self.emit_ready(), - } - } - } impl cxx_qt::Locking for MyObjectQt {} impl cxx_qt::CxxQtType for MyObjectQt { type Rust = MyObject; @@ -385,6 +398,20 @@ mod cxx_qt_ffi { self.as_mut().property_name_changed(); } } + impl SecondObjectQt { + #[doc = "Connect the given function pointer to the signal "] + #[doc = "propertyNameChanged"] + #[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."] + #[must_use] + fn on_property_name_changed( + self: Pin<&mut SecondObjectQt>, + func: fn(Pin<&mut SecondObjectQt>), + ) -> CxxQtQMetaObjectConnection { + self.connect_property_name_changed(func, CxxQtConnectionType::AutoConnection) + } + } impl SecondObject { #[doc(hidden)] pub fn invokable_name_wrapper(self: &mut SecondObject, cpp: Pin<&mut SecondObjectQt>) { @@ -397,9 +424,6 @@ mod cxx_qt_ffi { self.as_mut().set_property_name(5); } } - pub enum SecondSignals { - Ready, - } impl SecondObjectQt { #[doc = "Connect the given function pointer to the signal "] #[doc = "ready"] @@ -414,17 +438,6 @@ mod cxx_qt_ffi { self.connect_ready(func, CxxQtConnectionType::AutoConnection) } } - impl SecondObjectQt { - #[doc = "Emit the signal from the enum "] - #[doc = "SecondSignals"] - #[doc = " on the QObject "] - #[doc = "SecondObject"] - pub fn emit(self: Pin<&mut Self>, signal: SecondSignals) { - match signal { - SecondSignals::Ready {} => self.emit_ready(), - } - } - } impl cxx_qt::CxxQtType for SecondObjectQt { type Rust = SecondObject; fn rust(&self) -> &Self::Rust { diff --git a/crates/cxx-qt-gen/test_outputs/properties.cpp b/crates/cxx-qt-gen/test_outputs/properties.cpp index d8ebb46ea..983bec3e2 100644 --- a/crates/cxx-qt-gen/test_outputs/properties.cpp +++ b/crates/cxx-qt-gen/test_outputs/properties.cpp @@ -51,4 +51,46 @@ MyObject::setTrivial(QPoint const& value) m_rustObj->setTrivial(*this, value); } +void +MyObject::emitPrimitiveChanged() +{ + Q_EMIT primitiveChanged(); +} + +::QMetaObject::Connection +MyObject::primitiveChangedConnect(::rust::Fn func, + ::Qt::ConnectionType type) +{ + return ::QObject::connect( + this, + &MyObject::primitiveChanged, + this, + [&, func = ::std::move(func)]() { + const ::std::lock_guard<::std::recursive_mutex> guard(*m_rustObjMutex); + func(*this); + }, + type); +} + +void +MyObject::emitTrivialChanged() +{ + Q_EMIT trivialChanged(); +} + +::QMetaObject::Connection +MyObject::trivialChangedConnect(::rust::Fn func, + ::Qt::ConnectionType type) +{ + return ::QObject::connect( + this, + &MyObject::trivialChanged, + this, + [&, func = ::std::move(func)]() { + const ::std::lock_guard<::std::recursive_mutex> guard(*m_rustObjMutex); + func(*this); + }, + type); +} + } // namespace cxx_qt::my_object diff --git a/crates/cxx-qt-gen/test_outputs/properties.h b/crates/cxx-qt-gen/test_outputs/properties.h index 90374c560..066363bda 100644 --- a/crates/cxx-qt-gen/test_outputs/properties.h +++ b/crates/cxx-qt-gen/test_outputs/properties.h @@ -33,10 +33,18 @@ class MyObject : public QObject public: ::std::int32_t const& getPrimitive() const; Q_SLOT void setPrimitive(::std::int32_t const& value); - Q_SIGNAL void primitiveChanged(); QPoint const& getTrivial() const; Q_SLOT void setTrivial(QPoint const& value); + Q_SIGNAL void primitiveChanged(); + void emitPrimitiveChanged(); + ::QMetaObject::Connection primitiveChangedConnect( + ::rust::Fn func, + ::Qt::ConnectionType type); Q_SIGNAL void trivialChanged(); + void emitTrivialChanged(); + ::QMetaObject::Connection trivialChangedConnect( + ::rust::Fn func, + ::Qt::ConnectionType type); private: ::rust::Box m_rustObj; diff --git a/crates/cxx-qt-gen/test_outputs/properties.rs b/crates/cxx-qt-gen/test_outputs/properties.rs index 806239ae9..675421cec 100644 --- a/crates/cxx-qt-gen/test_outputs/properties.rs +++ b/crates/cxx-qt-gen/test_outputs/properties.rs @@ -43,16 +43,6 @@ mod ffi { #[cxx_name = "setPrimitive"] fn set_primitive(self: &mut MyObject, cpp: Pin<&mut MyObjectQt>, value: i32); } - unsafe extern "C++" { - #[doc = "Notify signal for the Q_PROPERTY"] - #[doc = "primitive"] - #[doc = "\n"] - #[doc = "This can be used to manually notify a change when the unsafe mutable getter,"] - #[doc = "primitive_mut"] - #[doc = ", is used."] - #[rust_name = "primitive_changed"] - fn primitiveChanged(self: Pin<&mut MyObjectQt>); - } extern "Rust" { #[cxx_name = "getTrivial"] unsafe fn trivial<'a>(self: &'a MyObject, cpp: &'a MyObjectQt) -> &'a QPoint; @@ -62,14 +52,38 @@ mod ffi { fn set_trivial(self: &mut MyObject, cpp: Pin<&mut MyObjectQt>, value: QPoint); } unsafe extern "C++" { - #[doc = "Notify signal for the Q_PROPERTY"] - #[doc = "trivial"] - #[doc = "\n"] - #[doc = "This can be used to manually notify a change when the unsafe mutable getter,"] - #[doc = "trivial_mut"] - #[doc = ", is used."] + #[doc = "Notify for the Q_PROPERTY"] + #[rust_name = "primitive_changed"] + fn emitPrimitiveChanged(self: Pin<&mut MyObjectQt>); + } + unsafe extern "C++" { + #[doc = "Connect the given function pointer to the signal "] + #[doc = "primitiveChanged"] + #[doc = ", so that when the signal is emitted the function pointer is executed."] + #[must_use] + #[rust_name = "connect_primitive_changed"] + fn primitiveChangedConnect( + self: Pin<&mut MyObjectQt>, + func: fn(Pin<&mut MyObjectQt>), + conn_type: CxxQtConnectionType, + ) -> CxxQtQMetaObjectConnection; + } + unsafe extern "C++" { + #[doc = "Notify for the Q_PROPERTY"] #[rust_name = "trivial_changed"] - fn trivialChanged(self: Pin<&mut MyObjectQt>); + fn emitTrivialChanged(self: Pin<&mut MyObjectQt>); + } + unsafe extern "C++" { + #[doc = "Connect the given function pointer to the signal "] + #[doc = "trivialChanged"] + #[doc = ", so that when the signal is emitted the function pointer is executed."] + #[must_use] + #[rust_name = "connect_trivial_changed"] + fn trivialChangedConnect( + self: Pin<&mut MyObjectQt>, + func: fn(Pin<&mut MyObjectQt>), + conn_type: CxxQtConnectionType, + ) -> CxxQtQMetaObjectConnection; } unsafe extern "C++" { #[cxx_name = "unsafeRust"] @@ -190,6 +204,34 @@ mod cxx_qt_ffi { self.as_mut().trivial_changed(); } } + impl MyObjectQt { + #[doc = "Connect the given function pointer to the signal "] + #[doc = "primitiveChanged"] + #[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."] + #[must_use] + fn on_primitive_changed( + self: Pin<&mut MyObjectQt>, + func: fn(Pin<&mut MyObjectQt>), + ) -> CxxQtQMetaObjectConnection { + self.connect_primitive_changed(func, CxxQtConnectionType::AutoConnection) + } + } + impl MyObjectQt { + #[doc = "Connect the given function pointer to the signal "] + #[doc = "trivialChanged"] + #[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."] + #[must_use] + fn on_trivial_changed( + self: Pin<&mut MyObjectQt>, + func: fn(Pin<&mut MyObjectQt>), + ) -> CxxQtQMetaObjectConnection { + self.connect_trivial_changed(func, CxxQtConnectionType::AutoConnection) + } + } impl MyObjectQt { fn opaque(&self) -> &UniquePtr { &self.rust().opaque diff --git a/crates/cxx-qt-gen/test_outputs/signals.rs b/crates/cxx-qt-gen/test_outputs/signals.rs index 19a0cd342..8093e1514 100644 --- a/crates/cxx-qt-gen/test_outputs/signals.rs +++ b/crates/cxx-qt-gen/test_outputs/signals.rs @@ -40,8 +40,7 @@ mod ffi { fn invokable_wrapper(self: &mut MyObject, cpp: Pin<&mut MyObjectQt>); } unsafe extern "C++" { - #[doc(hidden)] - #[rust_name = "emit_ready"] + #[rust_name = "ready"] fn emitReady(self: Pin<&mut MyObjectQt>); } unsafe extern "C++" { @@ -57,14 +56,13 @@ mod ffi { ) -> CxxQtQMetaObjectConnection; } unsafe extern "C++" { - #[doc(hidden)] - #[rust_name = "emit_data_changed"] + #[rust_name = "data_changed"] fn emitDataChanged( self: Pin<&mut MyObjectQt>, first: i32, second: UniquePtr, third: QPoint, - fourth: &QPoint, + fourth: &'a QPoint, ); } unsafe extern "C++" { @@ -80,20 +78,19 @@ mod ffi { first: i32, second: UniquePtr, third: QPoint, - fourth: &QPoint, + fourth: &'a QPoint, ), conn_type: CxxQtConnectionType, ) -> CxxQtQMetaObjectConnection; } unsafe extern "C++" { - #[doc(hidden)] - #[rust_name = "emit_base_class_new_data"] + #[rust_name = "base_class_new_data"] fn emitNewData( self: Pin<&mut MyObjectQt>, first: i32, second: UniquePtr, third: QPoint, - fourth: &QPoint, + fourth: &'a QPoint, ); } unsafe extern "C++" { @@ -109,7 +106,7 @@ mod ffi { first: i32, second: UniquePtr, third: QPoint, - fourth: &QPoint, + fourth: &'a QPoint, ), conn_type: CxxQtConnectionType, ) -> CxxQtQMetaObjectConnection; @@ -153,29 +150,10 @@ mod cxx_qt_ffi { }, cxx_qt_lib::ConnectionType::AutoConnection, ); - self.as_mut().emit(MySignals::DataChanged { - first: 1, - second: Opaque::new(), - third: QPoint::new(1, 2), - fourth: &QPoint::new(1, 2), - }); + self.as_mut() + .data_changed(1, Opaque::new(), QPoint::new(1, 2), &QPoint::new(1, 2)); } } - enum MySignals<'a> { - Ready, - DataChanged { - first: i32, - second: UniquePtr, - third: QPoint, - fourth: &'a QPoint, - }, - BaseClassNewData { - first: i32, - second: UniquePtr, - third: QPoint, - fourth: &'a QPoint, - }, - } impl MyObjectQt { #[doc = "Connect the given function pointer to the signal "] #[doc = "ready"] @@ -204,7 +182,7 @@ mod cxx_qt_ffi { first: i32, second: UniquePtr, third: QPoint, - fourth: &QPoint, + fourth: &'a QPoint, ), ) -> CxxQtQMetaObjectConnection { self.connect_data_changed(func, CxxQtConnectionType::AutoConnection) @@ -224,35 +202,12 @@ mod cxx_qt_ffi { first: i32, second: UniquePtr, third: QPoint, - fourth: &QPoint, + fourth: &'a QPoint, ), ) -> CxxQtQMetaObjectConnection { self.connect_base_class_new_data(func, CxxQtConnectionType::AutoConnection) } } - impl MyObjectQt { - #[doc = "Emit the signal from the enum "] - #[doc = "MySignals"] - #[doc = " on the QObject "] - #[doc = "MyObject"] - pub fn emit(self: Pin<&mut Self>, signal: MySignals) { - match signal { - MySignals::Ready {} => self.emit_ready(), - MySignals::DataChanged { - first, - second, - third, - fourth, - } => self.emit_data_changed(first, second, third, fourth), - MySignals::BaseClassNewData { - first, - second, - third, - fourth, - } => self.emit_base_class_new_data(first, second, third, fourth), - } - } - } impl cxx_qt::Locking for MyObjectQt {} impl cxx_qt::CxxQtType for MyObjectQt { type Rust = MyObject; diff --git a/crates/cxx-qt-macro/src/lib.rs b/crates/cxx-qt-macro/src/lib.rs index d765032f9..859d44085 100644 --- a/crates/cxx-qt-macro/src/lib.rs +++ b/crates/cxx-qt-macro/src/lib.rs @@ -71,9 +71,9 @@ pub fn bridge(args: TokenStream, input: TokenStream) -> TokenStream { /// # // Note that we can't use properties as this confuses the linker on Windows /// pub struct MyObject; /// -/// #[cxx_qt::qsignals(MyObject)] -/// pub enum MySignals { -/// Ready, +/// #[cxx_qt::qsignals] +/// unsafe extern "C++" { +/// fn ready(self: Pin<&mut qobject::MyObject>); /// } /// } /// diff --git a/examples/demo_threading/rust/src/lib.rs b/examples/demo_threading/rust/src/lib.rs index a60405225..8c766c8cd 100644 --- a/examples/demo_threading/rust/src/lib.rs +++ b/examples/demo_threading/rust/src/lib.rs @@ -70,16 +70,14 @@ mod ffi { // Enabling threading on the qobject impl cxx_qt::Threading for qobject::EnergyUsage {} - /// Define Q_SIGNALS that are created on the QObject - #[cxx_qt::qsignals(EnergyUsage)] - #[allow(clippy::enum_variant_names)] - pub enum EnergySignals { + #[cxx_qt::qsignals] + unsafe extern "C++" { /// A new sensor has been detected - SensorAdded { uuid: QString }, + fn sensor_added(self: Pin<&mut qobject::EnergyUsage>, uuid: QString); /// A value on an existing sensor has changed - SensorChanged { uuid: QString }, + fn sensor_changed(self: Pin<&mut qobject::EnergyUsage>, uuid: QString); /// An existing sensor has been removed - SensorRemoved { uuid: QString }, + fn sensor_removed(self: Pin<&mut qobject::EnergyUsage>, uuid: QString); } impl qobject::EnergyUsage { diff --git a/examples/demo_threading/rust/src/workers/sensors.rs b/examples/demo_threading/rust/src/workers/sensors.rs index fcfbf0d73..ebd5bb2e9 100644 --- a/examples/demo_threading/rust/src/workers/sensors.rs +++ b/examples/demo_threading/rust/src/workers/sensors.rs @@ -8,7 +8,6 @@ use crate::{ constants::SENSOR_MAXIMUM_COUNT, ffi::{EnergyUsageCxxQtThread, EnergyUsageQt}, network::NetworkChannel, - EnergySignals, }; use cxx_qt_lib::QString; use std::{ @@ -89,14 +88,9 @@ impl SensorsWorker { // Queue a Signal that the sensor has been removed to Qt qt_thread .queue( - move |mut qobject_energy_usage: std::pin::Pin< - &mut EnergyUsageQt, - >| { - qobject_energy_usage.as_mut().emit( - EnergySignals::SensorRemoved { - uuid: QString::from(&uuid.to_string()), - }, - ); + move |qobject_energy_usage: std::pin::Pin<&mut EnergyUsageQt>| { + qobject_energy_usage + .sensor_removed(QString::from(&uuid.to_string())); }, ) .unwrap(); @@ -125,28 +119,22 @@ impl SensorsWorker { if is_occupied { qt_thread .queue( - move |mut qobject_energy_usage: std::pin::Pin< + move |qobject_energy_usage: std::pin::Pin< &mut EnergyUsageQt, >| { - qobject_energy_usage.as_mut().emit( - EnergySignals::SensorChanged { - uuid: QString::from(&uuid.to_string()), - }, - ); + qobject_energy_usage + .sensor_changed(QString::from(&uuid.to_string())); }, ) .unwrap(); } else { qt_thread .queue( - move |mut qobject_energy_usage: std::pin::Pin< + move |qobject_energy_usage: std::pin::Pin< &mut EnergyUsageQt, >| { - qobject_energy_usage.as_mut().emit( - EnergySignals::SensorAdded { - uuid: QString::from(&uuid.to_string()), - }, - ); + qobject_energy_usage + .sensor_added(QString::from(&uuid.to_string())); }, ) .unwrap(); diff --git a/examples/qml_features/rust/src/custom_base_class.rs b/examples/qml_features/rust/src/custom_base_class.rs index 437fca87d..3e8d5d314 100644 --- a/examples/qml_features/rust/src/custom_base_class.rs +++ b/examples/qml_features/rust/src/custom_base_class.rs @@ -50,20 +50,17 @@ pub mod ffi { // Enabling threading on the qobject impl cxx_qt::Threading for qobject::CustomBaseClass {} - /// The signals for our QAbstractListModel struct // ANCHOR: book_qsignals_inherit - #[cxx_qt::qsignals(CustomBaseClass)] - pub enum Signals<'a> { + #[cxx_qt::qsignals] + unsafe extern "C++" { /// Inherit the DataChanged signal from the QAbstractListModel base class #[inherit] - DataChanged { - /// Top left affected index - top_left: &'a QModelIndex, - /// Bottom right affected index - bottom_right: &'a QModelIndex, - /// Roles that have been modified - roles: &'a QVector_i32, - }, + fn data_changed( + self: Pin<&mut qobject::CustomBaseClass>, + top_left: &QModelIndex, + bottom_right: &QModelIndex, + roles: &QVector_i32, + ); } // ANCHOR_END: book_qsignals_inherit @@ -133,11 +130,8 @@ pub mod ffi { let model_index = self.index(index, 0, &QModelIndex::default()); let mut vector_roles = QVector_i32::default(); vector_roles.append(1); - self.as_mut().emit(Signals::DataChanged { - top_left: &model_index, - bottom_right: &model_index, - roles: &vector_roles, - }); + self.as_mut() + .data_changed(&model_index, &model_index, &vector_roles); } } diff --git a/examples/qml_features/rust/src/multiple_qobjects.rs b/examples/qml_features/rust/src/multiple_qobjects.rs index b28f6a15b..899a023df 100644 --- a/examples/qml_features/rust/src/multiple_qobjects.rs +++ b/examples/qml_features/rust/src/multiple_qobjects.rs @@ -38,13 +38,13 @@ pub mod ffi { // Enabling threading on the qobject impl cxx_qt::Threading for qobject::FirstObject {} - /// Signals for the first QObject - #[cxx_qt::qsignals(FirstObject)] - pub enum FirstSignals { + #[cxx_qt::qsignals] + unsafe extern "C++" { /// Accepted Q_SIGNAL - Accepted, + fn accepted(self: Pin<&mut qobject::FirstObject>); + /// Rejected Q_SIGNAL - Rejected, + fn rejected(self: Pin<&mut qobject::FirstObject>); } impl qobject::FirstObject { @@ -56,10 +56,10 @@ pub mod ffi { if new_value % 2 == 0 { self.as_mut().set_color(QColor::from_rgb(0, 0, 255)); - self.emit(FirstSignals::Accepted); + self.accepted(); } else { self.as_mut().set_color(QColor::from_rgb(255, 0, 0)); - self.emit(FirstSignals::Rejected); + self.rejected(); } } } @@ -85,13 +85,13 @@ pub mod ffi { // Enabling threading on the qobject impl cxx_qt::Threading for qobject::SecondObject {} - /// Signals for the second QObject - #[cxx_qt::qsignals(SecondObject)] - pub enum SecondSignals { + #[cxx_qt::qsignals] + unsafe extern "C++" { /// Accepted Q_SIGNAL - Accepted, + fn accepted(self: Pin<&mut qobject::SecondObject>); + /// Rejected Q_SIGNAL - Rejected, + fn rejected(self: Pin<&mut qobject::SecondObject>); } impl qobject::SecondObject { @@ -104,10 +104,10 @@ pub mod ffi { if new_value % 5 == 0 { self.as_mut() .set_url(QUrl::from("https://github.com/kdab/cxx-qt")); - self.emit(SecondSignals::Accepted); + self.accepted(); } else { self.as_mut().set_url(QUrl::from("https://kdab.com")); - self.emit(SecondSignals::Rejected); + self.rejected(); } } } diff --git a/examples/qml_features/rust/src/nested_qobjects.rs b/examples/qml_features/rust/src/nested_qobjects.rs index 27001b4a8..df9e014f8 100644 --- a/examples/qml_features/rust/src/nested_qobjects.rs +++ b/examples/qml_features/rust/src/nested_qobjects.rs @@ -25,14 +25,10 @@ pub mod ffi { counter: i32, } - /// Signals for the inner QObject - #[cxx_qt::qsignals(InnerObject)] - pub enum InnerSignals { + #[cxx_qt::qsignals] + extern "C++" { /// A signal showing how to refer to another QObject as an argument - Called { - /// Inner QObject being referred to - inner: *mut CxxInnerObject, - }, + unsafe fn called(self: Pin<&mut qobject::InnerObject>, inner: *mut CxxInnerObject); } /// The outer QObject which has a Q_PROPERTY pointing to the inner QObject @@ -50,14 +46,10 @@ pub mod ffi { } } - /// Signals for the outer QObject - #[cxx_qt::qsignals(OuterObject)] - pub enum OuterSignals { + #[cxx_qt::qsignals] + extern "C++" { /// A signal showing how to refer to another QObject as an argument - Called { - /// Inner QObject being referred to - inner: *mut CxxInnerObject, - }, + unsafe fn called(self: Pin<&mut qobject::OuterObject>, inner: *mut CxxInnerObject); } impl qobject::OuterObject { @@ -71,7 +63,9 @@ pub mod ffi { if let Some(inner) = unsafe { qobject.inner().as_mut() } { let pinned_inner = unsafe { Pin::new_unchecked(inner) }; // Now pinned inner can be used as normal - pinned_inner.emit(InnerSignals::Called { inner: obj }); + unsafe { + pinned_inner.called(obj); + } } }) .release(); @@ -87,7 +81,9 @@ pub mod ffi { println!("Inner object's counter property: {}", inner.counter()); } - self.emit(OuterSignals::Called { inner }); + unsafe { + self.called(inner); + } } /// Reset the counter of the inner QObject stored in the Q_PROPERTY @@ -102,7 +98,7 @@ pub mod ffi { // Retrieve *mut T let inner = *self.inner(); - self.emit(OuterSignals::Called { inner }); + unsafe { self.called(inner) }; } } } diff --git a/examples/qml_features/rust/src/serialisation.rs b/examples/qml_features/rust/src/serialisation.rs index 107d3dbc6..a1092f348 100644 --- a/examples/qml_features/rust/src/serialisation.rs +++ b/examples/qml_features/rust/src/serialisation.rs @@ -48,14 +48,10 @@ pub mod ffi { pub string: QString, } - /// Signals for the QObject - #[cxx_qt::qsignals(Serialisation)] - pub enum Connection { + #[cxx_qt::qsignals] + unsafe extern "C++" { /// An error signal - Error { - /// The message of the error - message: QString, - }, + fn error(self: Pin<&mut qobject::Serialisation>, message: QString); } impl Default for Serialisation { @@ -83,9 +79,7 @@ pub mod ffi { match serde_json::to_string(&data_serde) { Ok(data_string) => QString::from(&data_string), Err(err) => { - self.emit(Connection::Error { - message: QString::from(&err.to_string()), - }); + self.error(QString::from(&err.to_string())); QString::default() } } @@ -101,9 +95,7 @@ pub mod ffi { self.as_mut().set_string(QString::from(&data_serde.string)); } Err(err) => { - self.emit(Connection::Error { - message: QString::from(&err.to_string()), - }); + self.error(QString::from(&err.to_string())); } } } diff --git a/examples/qml_features/rust/src/signals.rs b/examples/qml_features/rust/src/signals.rs index 5e8fe3373..33132e90f 100644 --- a/examples/qml_features/rust/src/signals.rs +++ b/examples/qml_features/rust/src/signals.rs @@ -20,30 +20,19 @@ pub mod ffi { type QUrl = cxx_qt_lib::QUrl; } - /// Q_SIGNALs for the QObject - // ANCHOR: book_signals_enum - #[cxx_qt::qsignals(RustSignals)] - pub enum Connection<'a> { + // ANCHOR: book_signals_block + #[cxx_qt::qsignals] + unsafe extern "C++" { /// A Q_SIGNAL emitted when a connection occurs - Connected { - /// The url for the connection - url: &'a QUrl, - }, + fn connected(self: Pin<&mut qobject::RustSignals>, url: &QUrl); + /// A Q_SIGNAL emitted when a disconnect occurs - Disconnected, + fn disconnected(self: Pin<&mut qobject::RustSignals>); + /// A Q_SIGNAL emitted when an error occurs - Error { - /// The message of the error - message: QString, - }, - // Example of using #[inherit] so that connections to the logging_enabled property can be made - #[inherit] - // We don't ever emit this enum, so silence clippy warnings - #[allow(dead_code)] - /// The Q_SIGNAL emitted when the Q_PROPERTY logging_enabled changes - LoggingEnabledChanged, + fn error(self: Pin<&mut qobject::RustSignals>, message: QString); } - // ANCHOR_END: book_signals_enum + // ANCHOR_END: book_signals_block /// A QObject which has Q_SIGNALs // ANCHOR: book_signals_struct @@ -60,24 +49,22 @@ pub mod ffi { impl qobject::RustSignals { /// Connect to the given url #[qinvokable] - pub fn connect(mut self: Pin<&mut Self>, url: &QUrl) { + pub fn connect(self: Pin<&mut Self>, url: &QUrl) { // Check that the url starts with kdab if url.to_string().starts_with("https://kdab.com") { // Emit a signal to QML stating that we have connected - self.as_mut().emit(Connection::Connected { url }); + self.connected(url); } else { // Emit a signal to QML stating that the url was incorrect - self.emit(Connection::Error { - message: QString::from("URL does not start with https://kdab.com"), - }); + self.error(QString::from("URL does not start with https://kdab.com")); } } /// Disconnect #[qinvokable] - pub fn disconnect(mut self: Pin<&mut Self>) { + pub fn disconnect(self: Pin<&mut Self>) { // Emit a signal to QML stating that we have disconnected - self.as_mut().emit(Connection::Disconnected); + self.disconnected(); } /// Initialise the QObject, creating a connection reacting to the logging enabled property