From e235c03dedd8827a90383f7fe1fa56084a41d235 Mon Sep 17 00:00:00 2001 From: Andrew Hayzen Date: Thu, 1 Jun 2023 15:15:40 +0100 Subject: [PATCH 1/2] cxx-qt-gen: ensure that attributes are handled correctly for cxx_qt::inherit Ensure that attributes on the extern block are empty, otherwise unsafe detection can fail. Ensure that attributes on the method as passed through, otherwise doc lines don't work. Related to #557 --- .../cxx-qt-gen/src/generator/rust/inherit.rs | 17 ++------ crates/cxx-qt-gen/src/parser/inherit.rs | 43 +++++++++++++------ crates/cxx-qt-gen/test_inputs/inheritance.rs | 2 + crates/cxx-qt-gen/test_outputs/inheritance.rs | 8 +--- .../rust/src/custom_base_class.rs | 8 ++++ 5 files changed, 45 insertions(+), 33 deletions(-) diff --git a/crates/cxx-qt-gen/src/generator/rust/inherit.rs b/crates/cxx-qt-gen/src/generator/rust/inherit.rs index ef1d7ae1c..d4b8f3708 100644 --- a/crates/cxx-qt-gen/src/generator/rust/inherit.rs +++ b/crates/cxx-qt-gen/src/generator/rust/inherit.rs @@ -32,7 +32,6 @@ pub fn generate( .collect::>(); let ident = &method.method.sig.ident; let cxx_name_string = &method.wrapper_ident().to_string(); - let ident_cpp_str = method.ident.cpp.to_string(); let self_param = if method.mutable { quote! { self: Pin<&mut #qobject_name> } } else { @@ -45,11 +44,10 @@ pub fn generate( if method.safe { std::mem::swap(&mut unsafe_call, &mut unsafe_block); } + let attrs = &method.method.attrs; syn::parse2(quote! { #unsafe_block extern "C++" { - #[doc = "CXX-Qt generated method which calls the C++ method"] - #[doc = #ident_cpp_str] - #[doc = "on the base class"] + #(#attrs)* #[cxx_name = #cxx_name_string] #unsafe_call fn #ident(#self_param, #(#parameters),*) #return_type; } @@ -82,7 +80,7 @@ mod tests { fn test_mutable() { let generated = generate_from_foreign( parse_quote! { - fn test(self: Pin<&mut qobject::MyObject>, a: B, b: C); + fn test(self: Pin<&mut qobject::MyObject>, a: B, b: C); }, Safety::Safe, ) @@ -95,9 +93,6 @@ mod tests { &generated.cxx_mod_contents[0], quote! { unsafe extern "C++" { - #[doc = "CXX-Qt generated method which calls the C++ method"] - #[doc = "test"] - #[doc = "on the base class"] #[cxx_name = "testCxxQtInherit"] fn test(self: Pin<&mut MyObjectQt>, a: B, b: C); } @@ -122,9 +117,6 @@ mod tests { &generated.cxx_mod_contents[0], quote! { unsafe extern "C++" { - #[doc = "CXX-Qt generated method which calls the C++ method"] - #[doc = "test"] - #[doc = "on the base class"] #[cxx_name = "testCxxQtInherit"] fn test(self: &MyObjectQt, a: B, b: C); } @@ -150,9 +142,6 @@ mod tests { // TODO: Maybe remove the trailing comma after self? quote! { extern "C++" { - #[doc = "CXX-Qt generated method which calls the C++ method"] - #[doc = "test"] - #[doc = "on the base class"] #[cxx_name = "testCxxQtInherit"] unsafe fn test(self: &MyObjectQt,); } diff --git a/crates/cxx-qt-gen/src/parser/inherit.rs b/crates/cxx-qt-gen/src/parser/inherit.rs index 2b2105c57..b681cc78f 100644 --- a/crates/cxx-qt-gen/src/parser/inherit.rs +++ b/crates/cxx-qt-gen/src/parser/inherit.rs @@ -53,6 +53,17 @@ impl Parse for InheritMethods { 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::qsignals] 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]) { @@ -110,7 +121,7 @@ pub struct ParsedInheritedMethod { } impl ParsedInheritedMethod { - pub fn parse(method: ForeignItemFn, safety: Safety) -> Result { + pub fn parse(mut method: ForeignItemFn, safety: Safety) -> Result { if safety == Safety::Unsafe && method.sig.unsafety.is_none() { return Err(Error::new( method.span(), @@ -125,19 +136,16 @@ impl ParsedInheritedMethod { let parameters = ParsedFunctionParameter::parse_all_ignoring_receiver(&method.sig)?; let mut ident = CombinedIdent::from_rust_function(method.sig.ident.clone()); - for attribute in &method.attrs { - if !attribute.meta.path().is_ident(&format_ident!("cxx_name")) { - return Err(Error::new( - attribute.span(), - "Unsupported attribute in #[cxx_qt::inherit]", - )); - } + if let Some(index) = attribute_find_path(&method.attrs, &["cxx_name"]) { ident.cpp = format_ident!( "{}", - expr_to_string(&attribute.meta.require_name_value()?.value)? + expr_to_string(&method.attrs[index].meta.require_name_value()?.value)? ); + + method.attrs.remove(index); } + let safe = method.sig.unsafety.is_none(); Ok(Self { @@ -195,6 +203,19 @@ mod tests { } } + #[test] + fn test_parse_attributes() { + let module = quote! { + unsafe extern "C++" { + #[attribute] + fn test(self: &qobject::T); + } + }; + let parsed: InheritMethods = syn::parse2(module).unwrap(); + assert_eq!(parsed.base_functions.len(), 1); + assert_eq!(parsed.base_functions[0].attrs.len(), 1); + } + fn assert_parse_error(function: ForeignItemFn) { let result = ParsedInheritedMethod::parse(function, Safety::Safe); assert!(result.is_err()); @@ -231,10 +252,6 @@ mod tests { fn test(self: &mut T); }); // Attributes - assert_parse_error(parse_quote! { - #[myattribute] - fn test(self: &qobject::T); - }); assert_parse_error(parse_quote! { fn test(#[test] self: &qobject::T); }); diff --git a/crates/cxx-qt-gen/test_inputs/inheritance.rs b/crates/cxx-qt-gen/test_inputs/inheritance.rs index e98ff8188..feed2de87 100644 --- a/crates/cxx-qt-gen/test_inputs/inheritance.rs +++ b/crates/cxx-qt-gen/test_inputs/inheritance.rs @@ -15,12 +15,14 @@ mod inheritance { #[cxx_qt::inherit] unsafe extern "C++" { + /// Inherited hasChildren from the base class #[cxx_name = "hasChildren"] fn has_children_super(self: &qobject::MyObject, parent: &QModelIndex) -> bool; } #[cxx_qt::inherit] extern "C++" { + /// Inherited fetchMore from the base class unsafe fn fetch_more(self: Pin<&mut qobject::MyObject>, index: &QModelIndex); } diff --git a/crates/cxx-qt-gen/test_outputs/inheritance.rs b/crates/cxx-qt-gen/test_outputs/inheritance.rs index f021c7753..374ef4320 100644 --- a/crates/cxx-qt-gen/test_outputs/inheritance.rs +++ b/crates/cxx-qt-gen/test_outputs/inheritance.rs @@ -50,16 +50,12 @@ mod inheritance { fn has_children_wrapper(self: &MyObject, cpp: &MyObjectQt, _parent: &QModelIndex) -> bool; } unsafe extern "C++" { - #[doc = "CXX-Qt generated method which calls the C++ method"] - #[doc = "hasChildren"] - #[doc = "on the base class"] + #[doc = " Inherited hasChildren from the base class"] #[cxx_name = "hasChildrenCxxQtInherit"] fn has_children_super(self: &MyObjectQt, parent: &QModelIndex) -> bool; } extern "C++" { - #[doc = "CXX-Qt generated method which calls the C++ method"] - #[doc = "fetchMore"] - #[doc = "on the base class"] + #[doc = " Inherited fetchMore from the base class"] #[cxx_name = "fetchMoreCxxQtInherit"] unsafe fn fetch_more(self: Pin<&mut MyObjectQt>, index: &QModelIndex); } diff --git a/examples/qml_features/rust/src/custom_base_class.rs b/examples/qml_features/rust/src/custom_base_class.rs index e26ac2410..437fca87d 100644 --- a/examples/qml_features/rust/src/custom_base_class.rs +++ b/examples/qml_features/rust/src/custom_base_class.rs @@ -161,23 +161,29 @@ pub mod ffi { // Create Rust bindings for C++ functions of the base class (QAbstractItemModel) #[cxx_qt::inherit] extern "C++" { + /// Inherited beginInsertRows from the base class unsafe fn begin_insert_rows( self: Pin<&mut qobject::CustomBaseClass>, parent: &QModelIndex, first: i32, last: i32, ); + /// Inherited endInsertRows from the base class unsafe fn end_insert_rows(self: Pin<&mut qobject::CustomBaseClass>); + /// Inherited beginRemoveRows from the base class unsafe fn begin_remove_rows( self: Pin<&mut qobject::CustomBaseClass>, parent: &QModelIndex, first: i32, last: i32, ); + /// Inherited endRemoveRows from the base class unsafe fn end_remove_rows(self: Pin<&mut qobject::CustomBaseClass>); + /// Inherited beginResetModel from the base class unsafe fn begin_reset_model(self: Pin<&mut qobject::CustomBaseClass>); + /// Inherited endResetModel from the base class unsafe fn end_reset_model(self: Pin<&mut qobject::CustomBaseClass>); } // ANCHOR_END: book_inherit_qalm_impl_unsafe @@ -185,9 +191,11 @@ pub mod ffi { // ANCHOR: book_inherit_qalm_impl_safe #[cxx_qt::inherit] unsafe extern "C++" { + /// Inherited canFetchMore from the base class #[cxx_name = "canFetchMore"] fn base_can_fetch_more(self: &qobject::CustomBaseClass, parent: &QModelIndex) -> bool; + /// Inherited index from the base class fn index( self: &qobject::CustomBaseClass, row: i32, From 3c5156061ee137b7c3f89245c495fb4c7394eb5a Mon Sep 17 00:00:00 2001 From: Andrew Hayzen Date: Thu, 1 Jun 2023 15:55:28 +0100 Subject: [PATCH 2/2] cxx-qt-gen: move signals away from an enum to an extern "C++" block Related to #557 --- book/src/SUMMARY.md | 2 +- book/src/concepts/qt.md | 2 +- .../src/getting-started/1-qobjects-in-rust.md | 2 +- book/src/qobject/generated-qobject.md | 8 - book/src/qobject/index.md | 2 +- book/src/qobject/qobject_struct.md | 2 +- .../qobject/{signals_enum.md => signals.md} | 25 +- .../src/generator/cpp/property/mod.rs | 166 ++++++- .../src/generator/cpp/property/signal.rs | 29 +- .../cxx-qt-gen/src/generator/cpp/qobject.rs | 14 +- crates/cxx-qt-gen/src/generator/cpp/signal.rs | 45 +- crates/cxx-qt-gen/src/generator/naming/mod.rs | 1 + .../src/generator/naming/signals.rs | 75 +-- .../src/generator/rust/property/mod.rs | 307 +++++++----- .../src/generator/rust/property/signal.rs | 45 +- .../cxx-qt-gen/src/generator/rust/qobject.rs | 10 +- .../cxx-qt-gen/src/generator/rust/signals.rs | 393 ++++++++-------- crates/cxx-qt-gen/src/parser/cxxqtdata.rs | 230 +++++---- crates/cxx-qt-gen/src/parser/mod.rs | 18 +- crates/cxx-qt-gen/src/parser/parameter.rs | 1 + crates/cxx-qt-gen/src/parser/qobject.rs | 8 +- crates/cxx-qt-gen/src/parser/signals.rs | 440 ++++++++++++------ crates/cxx-qt-gen/src/syntax/attribute.rs | 88 +--- .../test_inputs/passthrough_and_naming.rs | 13 +- crates/cxx-qt-gen/test_inputs/signals.rs | 26 +- .../test_outputs/passthrough_and_naming.cpp | 39 ++ .../test_outputs/passthrough_and_naming.h | 8 + .../test_outputs/passthrough_and_naming.rs | 105 +++-- crates/cxx-qt-gen/test_outputs/properties.cpp | 42 ++ crates/cxx-qt-gen/test_outputs/properties.h | 10 +- crates/cxx-qt-gen/test_outputs/properties.rs | 76 ++- crates/cxx-qt-gen/test_outputs/signals.rs | 67 +-- crates/cxx-qt-macro/src/lib.rs | 6 +- examples/demo_threading/rust/src/lib.rs | 12 +- .../rust/src/workers/sensors.rs | 30 +- .../rust/src/custom_base_class.rs | 26 +- .../rust/src/multiple_qobjects.rs | 28 +- .../qml_features/rust/src/nested_qobjects.rs | 30 +- .../qml_features/rust/src/serialisation.rs | 18 +- examples/qml_features/rust/src/signals.rs | 41 +- 40 files changed, 1453 insertions(+), 1037 deletions(-) rename book/src/qobject/{signals_enum.md => signals.md} (71%) 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