From 8be5e4d9330ca22b6246c66be0d3b46603ab5f0e Mon Sep 17 00:00:00 2001 From: Ben Ford Date: Tue, 17 Jun 2025 17:02:45 +0100 Subject: [PATCH 1/2] Check constructor declarations - A shim trait is used to check whether the user declared a constructor - This is implemented by the bridge if you add the constructor trait in bridge - This will then allow you to implemented constructor for your type - This prevents constructors not being used silently --- book/src/bridge/extern_rustqt.md | 2 +- book/src/bridge/traits.md | 2 +- .../src/generator/rust/constructor.rs | 27 ++++++++++++++----- crates/cxx-qt-gen/test_outputs/invokables.rs | 1 + crates/cxx-qt/src/lib.rs | 7 +++-- 5 files changed, 29 insertions(+), 10 deletions(-) diff --git a/book/src/bridge/extern_rustqt.md b/book/src/bridge/extern_rustqt.md index 85fb4d6f3..a41321397 100644 --- a/book/src/bridge/extern_rustqt.md +++ b/book/src/bridge/extern_rustqt.md @@ -111,7 +111,7 @@ is equivalent to writing impl cxx_qt::Constructor<()> for x {} ``` -inside the bridge. +inside the bridge. You can then implement your constructors outside the bridge, using either of these traits. For further documentation see the [traits page](./traits.md). diff --git a/book/src/bridge/traits.md b/book/src/bridge/traits.md index a85fe3fc0..3774dac9b 100644 --- a/book/src/bridge/traits.md +++ b/book/src/bridge/traits.md @@ -23,7 +23,7 @@ For further documentation, refer to the documentation of the individual traits: - [CxxQtType](https://docs.rs/cxx-qt/latest/cxx_qt/trait.CxxQtType.html) - trait to reach the Rust implementation of a `QObject` - This trait is automatically implemented for any `#[qobject]` type inside `extern "RustQt"` blocks. -- [Constructor](https://docs.rs/cxx-qt/latest/cxx_qt/trait.Constructor.html) - custom constructor +- [Constructor](https://docs.rs/cxx-qt/latest/cxx_qt/trait.Constructor.html) - custom constructor. This must be declared in the bridge in order for you to implement it outside the bridge - [Initialize](https://docs.rs/cxx-qt/latest/cxx_qt/trait.Initialize.html) - execute Rust code when the object is constructed, or as shorthand for an empty constructor - [Threading](https://docs.rs/cxx-qt/latest/cxx_qt/trait.Threading.html) - marker trait whether CXX-Qt threading should be enabled - [QObjectExt](https://docs.rs/cxx-qt/latest/cxx_qt_lib/trait.QObjectExt.html) - Trait which exposes some key methods of QObject diff --git a/crates/cxx-qt-gen/src/generator/rust/constructor.rs b/crates/cxx-qt-gen/src/generator/rust/constructor.rs index 67b1a195b..3382c5a87 100644 --- a/crates/cxx-qt-gen/src/generator/rust/constructor.rs +++ b/crates/cxx-qt-gen/src/generator/rust/constructor.rs @@ -207,6 +207,13 @@ pub fn generate( let rust_struct_name_rust = qobject_names.rust_struct.rust_unqualified(); + result + .cxx_qt_mod_contents + .append(&mut vec![parse_quote_spanned! { + qobject_name_rust.span() => // TODO! Improve this span + impl ::cxx_qt::ConstructorDeclared for #qobject_name_rust_qualified {} + }]); + for (index, constructor) in constructors.iter().enumerate() { let lifetime = constructor.lifetime.as_ref().map(|lifetime| { quote! { @@ -599,8 +606,16 @@ mod tests { }, ); + // Shim impl only appears once assert_tokens_eq( &blocks.cxx_qt_mod_contents[0], + quote! { + impl ::cxx_qt::ConstructorDeclared for qobject::MyObject {} + }, + ); + + assert_tokens_eq( + &blocks.cxx_qt_mod_contents[1], quote! { #[doc(hidden)] pub fn route_arguments_MyObject_0() -> qobject::CxxQtConstructorArgumentsMyObject0 @@ -619,7 +634,7 @@ mod tests { }, ); assert_tokens_eq( - &blocks.cxx_qt_mod_contents[1], + &blocks.cxx_qt_mod_contents[2], quote! { #[doc(hidden)] #[allow(unused_variables)] @@ -633,7 +648,7 @@ mod tests { }, ); assert_tokens_eq( - &blocks.cxx_qt_mod_contents[2], + &blocks.cxx_qt_mod_contents[3], quote! { #[doc(hidden)] #[allow(unused_variables)] @@ -725,7 +740,7 @@ mod tests { ); assert_tokens_eq( - &blocks.cxx_qt_mod_contents[3], + &blocks.cxx_qt_mod_contents[4], quote! { #[doc(hidden)] pub fn route_arguments_MyObject_1<'lifetime>(arg0: *const QObject) -> qobject::CxxQtConstructorArgumentsMyObject1<'lifetime> @@ -753,7 +768,7 @@ mod tests { }, ); assert_tokens_eq( - &blocks.cxx_qt_mod_contents[4], + &blocks.cxx_qt_mod_contents[5], quote! { #[doc(hidden)] #[allow(unused_variables)] @@ -767,7 +782,7 @@ mod tests { }, ); assert_tokens_eq( - &blocks.cxx_qt_mod_contents[5], + &blocks.cxx_qt_mod_contents[6], quote! { #[doc(hidden)] #[allow(unused_variables)] @@ -806,7 +821,7 @@ mod tests { ]); assert_eq!(blocks.cxx_mod_contents.len(), 10); - assert_eq!(blocks.cxx_qt_mod_contents.len(), 6); + assert_eq!(blocks.cxx_qt_mod_contents.len(), 7); let namespace_attr = quote! { #[namespace = "qobject::cxx_qt_MyObject"] diff --git a/crates/cxx-qt-gen/test_outputs/invokables.rs b/crates/cxx-qt-gen/test_outputs/invokables.rs index e42865ce6..ac67c2e90 100644 --- a/crates/cxx-qt-gen/test_outputs/invokables.rs +++ b/crates/cxx-qt-gen/test_outputs/invokables.rs @@ -334,6 +334,7 @@ unsafe impl ::cxx_qt::casting::Upcast<::cxx_qt::QObject> for ffi::MyObject { ffi::cxx_qt_ffi_MyObject_downcastPtr(base) } } +impl ::cxx_qt::ConstructorDeclared for ffi::MyObject {} #[doc(hidden)] pub fn route_arguments_MyObject_0<'a>( arg0: i32, diff --git a/crates/cxx-qt/src/lib.rs b/crates/cxx-qt/src/lib.rs index ec0dc9477..b4257dc5d 100644 --- a/crates/cxx-qt/src/lib.rs +++ b/crates/cxx-qt/src/lib.rs @@ -216,6 +216,9 @@ pub trait Threading: Sized { fn threading_drop(cxx_qt_thread: core::pin::Pin<&mut CxxQtThread>); } +#[doc(hidden)] +pub trait ConstructorDeclared {} + /// This trait can be implemented on any [CxxQtType] to define a /// custom constructor in C++ for the QObject. /// @@ -307,7 +310,7 @@ pub trait Threading: Sized { /// /// If a QObject implements the `Initialize` trait, and the inner Rust struct is [Default]-constructible it will automatically implement `cxx_qt::Constructor<()>`. /// Additionally, implementing `impl cxx_qt::Initialize` will act as shorthand for `cxx_qt::Constructor<()>`. -pub trait Constructor: CxxQtType { +pub trait Constructor: CxxQtType + ConstructorDeclared { /// The arguments that are passed to the [`new()`](Self::new) function to construct the inner Rust struct. /// This must be a tuple of CXX compatible types. /// @@ -394,7 +397,7 @@ pub trait Constructor: CxxQtType { /// ``` // TODO: Once the QObject type is available in the cxx-qt crate, also auto-generate a default // constructor that takes QObject and passes it to the parent. -pub trait Initialize: CxxQtType { +pub trait Initialize: CxxQtType + ConstructorDeclared { /// This function is called to initialize the QObject after construction. fn initialize(self: core::pin::Pin<&mut Self>); } From 101ca81facc3d2132ee316d70c8a8e4f545e2668 Mon Sep 17 00:00:00 2001 From: Ben Ford Date: Fri, 5 Sep 2025 14:06:52 +0100 Subject: [PATCH 2/2] Fix constructor generic args - Add lifetime - Update span --- .../src/generator/rust/constructor.rs | 35 +++++++++++++------ crates/cxx-qt-gen/test_outputs/invokables.rs | 3 +- crates/cxx-qt/src/lib.rs | 6 ++-- 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/crates/cxx-qt-gen/src/generator/rust/constructor.rs b/crates/cxx-qt-gen/src/generator/rust/constructor.rs index 3382c5a87..ba59e2577 100644 --- a/crates/cxx-qt-gen/src/generator/rust/constructor.rs +++ b/crates/cxx-qt-gen/src/generator/rust/constructor.rs @@ -207,19 +207,25 @@ pub fn generate( let rust_struct_name_rust = qobject_names.rust_struct.rust_unqualified(); - result - .cxx_qt_mod_contents - .append(&mut vec![parse_quote_spanned! { - qobject_name_rust.span() => // TODO! Improve this span - impl ::cxx_qt::ConstructorDeclared for #qobject_name_rust_qualified {} - }]); - for (index, constructor) in constructors.iter().enumerate() { + let mut arguments = TokenStream::new(); + for elem in &constructor.arguments { + arguments.extend(quote!(#elem,)); + } + let lifetime = constructor.lifetime.as_ref().map(|lifetime| { quote! { < #lifetime > } }); + + result + .cxx_qt_mod_contents + .append(&mut vec![parse_quote_spanned! { + constructor.imp.span() => + impl #lifetime ::cxx_qt::ConstructorDeclared<(#arguments) > for #qobject_name_rust_qualified {} + }]); + let arguments_lifetime = lifetime_of_arguments(&constructor.lifetime, &constructor.arguments)?; let base_lifetime = @@ -610,7 +616,7 @@ mod tests { assert_tokens_eq( &blocks.cxx_qt_mod_contents[0], quote! { - impl ::cxx_qt::ConstructorDeclared for qobject::MyObject {} + impl ::cxx_qt::ConstructorDeclared<()> for qobject::MyObject {} }, ); @@ -741,6 +747,13 @@ mod tests { assert_tokens_eq( &blocks.cxx_qt_mod_contents[4], + quote! { + impl<'lifetime> ::cxx_qt::ConstructorDeclared<(*const QObject,)> for qobject::MyObject {} + }, + ); + + assert_tokens_eq( + &blocks.cxx_qt_mod_contents[5], quote! { #[doc(hidden)] pub fn route_arguments_MyObject_1<'lifetime>(arg0: *const QObject) -> qobject::CxxQtConstructorArgumentsMyObject1<'lifetime> @@ -768,7 +781,7 @@ mod tests { }, ); assert_tokens_eq( - &blocks.cxx_qt_mod_contents[5], + &blocks.cxx_qt_mod_contents[6], quote! { #[doc(hidden)] #[allow(unused_variables)] @@ -782,7 +795,7 @@ mod tests { }, ); assert_tokens_eq( - &blocks.cxx_qt_mod_contents[6], + &blocks.cxx_qt_mod_contents[7], quote! { #[doc(hidden)] #[allow(unused_variables)] @@ -821,7 +834,7 @@ mod tests { ]); assert_eq!(blocks.cxx_mod_contents.len(), 10); - assert_eq!(blocks.cxx_qt_mod_contents.len(), 7); + assert_eq!(blocks.cxx_qt_mod_contents.len(), 8); let namespace_attr = quote! { #[namespace = "qobject::cxx_qt_MyObject"] diff --git a/crates/cxx-qt-gen/test_outputs/invokables.rs b/crates/cxx-qt-gen/test_outputs/invokables.rs index ac67c2e90..acad10d2b 100644 --- a/crates/cxx-qt-gen/test_outputs/invokables.rs +++ b/crates/cxx-qt-gen/test_outputs/invokables.rs @@ -334,7 +334,7 @@ unsafe impl ::cxx_qt::casting::Upcast<::cxx_qt::QObject> for ffi::MyObject { ffi::cxx_qt_ffi_MyObject_downcastPtr(base) } } -impl ::cxx_qt::ConstructorDeclared for ffi::MyObject {} +impl<'a> ::cxx_qt::ConstructorDeclared<(i32, &'a QString)> for ffi::MyObject {} #[doc(hidden)] pub fn route_arguments_MyObject_0<'a>( arg0: i32, @@ -377,6 +377,7 @@ pub fn initialize_MyObject_0<'a>( ) { >::initialize(qobject, ()); } +impl ::cxx_qt::ConstructorDeclared<()> for ffi::MyObject {} #[doc(hidden)] pub fn route_arguments_MyObject_1() -> ffi::CxxQtConstructorArgumentsMyObject1 { #[allow(unused_variables)] diff --git a/crates/cxx-qt/src/lib.rs b/crates/cxx-qt/src/lib.rs index b4257dc5d..4b5e582f2 100644 --- a/crates/cxx-qt/src/lib.rs +++ b/crates/cxx-qt/src/lib.rs @@ -217,7 +217,7 @@ pub trait Threading: Sized { } #[doc(hidden)] -pub trait ConstructorDeclared {} +pub trait ConstructorDeclared {} /// This trait can be implemented on any [CxxQtType] to define a /// custom constructor in C++ for the QObject. @@ -310,7 +310,7 @@ pub trait ConstructorDeclared {} /// /// If a QObject implements the `Initialize` trait, and the inner Rust struct is [Default]-constructible it will automatically implement `cxx_qt::Constructor<()>`. /// Additionally, implementing `impl cxx_qt::Initialize` will act as shorthand for `cxx_qt::Constructor<()>`. -pub trait Constructor: CxxQtType + ConstructorDeclared { +pub trait Constructor: CxxQtType + ConstructorDeclared { /// The arguments that are passed to the [`new()`](Self::new) function to construct the inner Rust struct. /// This must be a tuple of CXX compatible types. /// @@ -397,7 +397,7 @@ pub trait Constructor: CxxQtType + ConstructorDeclared { /// ``` // TODO: Once the QObject type is available in the cxx-qt crate, also auto-generate a default // constructor that takes QObject and passes it to the parent. -pub trait Initialize: CxxQtType + ConstructorDeclared { +pub trait Initialize: CxxQtType + ConstructorDeclared<()> { /// This function is called to initialize the QObject after construction. fn initialize(self: core::pin::Pin<&mut Self>); }