From 080cb5f9152b45f9828ae84271497363ceba4f91 Mon Sep 17 00:00:00 2001 From: Ben Ford Date: Tue, 17 Jun 2025 17:02:45 +0100 Subject: [PATCH] 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 e29e3e44d..8b98acfc2 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 diff --git a/crates/cxx-qt-gen/src/generator/rust/constructor.rs b/crates/cxx-qt-gen/src/generator/rust/constructor.rs index 433f729ea..85fb5856c 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 4d01c4102..86744e314 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 41fbe1668..dd0a46d78 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: &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>); }