From 0b1b6813e82f4b1f31fec9bbceab731c350bf195 Mon Sep 17 00:00:00 2001 From: Ben Ford Date: Thu, 16 Jan 2025 11:55:21 +0000 Subject: [PATCH] Automatically generate upcasting implementations - Any bridges with qobjects that have no custom base class will get an upcasting implemention - Adds a new include with the necessary template function for calling static_cast --- .../cxx-qt-gen/src/generator/cpp/qobject.rs | 6 ++ crates/cxx-qt-gen/src/generator/rust/mod.rs | 33 +++++- .../cxx-qt-gen/src/generator/rust/qobject.rs | 101 ++++++++++++------ crates/cxx-qt-gen/src/writer/cpp/header.rs | 1 + crates/cxx-qt-gen/test_outputs/inheritance.h | 1 + crates/cxx-qt-gen/test_outputs/inheritance.rs | 16 ++- crates/cxx-qt-gen/test_outputs/invokables.h | 1 + crates/cxx-qt-gen/test_outputs/invokables.rs | 16 +++ .../test_outputs/passthrough_and_naming.h | 1 + .../test_outputs/passthrough_and_naming.rs | 42 +++++++- crates/cxx-qt-gen/test_outputs/properties.h | 1 + crates/cxx-qt-gen/test_outputs/properties.rs | 16 +++ crates/cxx-qt-gen/test_outputs/qenum.h | 1 + crates/cxx-qt-gen/test_outputs/qenum.rs | 27 +++++ crates/cxx-qt-gen/test_outputs/signals.h | 1 + crates/cxx-qt-gen/test_outputs/signals.rs | 16 +++ crates/cxx-qt/build.rs | 3 +- crates/cxx-qt/include/casting.h | 18 ++++ crates/cxx-qt/src/lib.rs | 42 +++++++- crates/cxx-qt/src/qobject.rs | 22 ++++ 20 files changed, 313 insertions(+), 52 deletions(-) create mode 100644 crates/cxx-qt/include/casting.h create mode 100644 crates/cxx-qt/src/qobject.rs diff --git a/crates/cxx-qt-gen/src/generator/cpp/qobject.rs b/crates/cxx-qt-gen/src/generator/cpp/qobject.rs index 937ff9203..ed1554c15 100644 --- a/crates/cxx-qt-gen/src/generator/cpp/qobject.rs +++ b/crates/cxx-qt-gen/src/generator/cpp/qobject.rs @@ -167,6 +167,12 @@ impl GeneratedCppQObject { class_initializers.push(initializer); } + // Include casting header + let mut result = GeneratedCppQObjectBlocks::default(); + result.includes.insert("#include ".into()); + + generated.blocks.append(&mut result); + generated.blocks.append(&mut constructor::generate( &generated, &structured_qobject.constructors, diff --git a/crates/cxx-qt-gen/src/generator/rust/mod.rs b/crates/cxx-qt-gen/src/generator/rust/mod.rs index 0bbb0f77e..9003f5f0a 100644 --- a/crates/cxx-qt-gen/src/generator/rust/mod.rs +++ b/crates/cxx-qt-gen/src/generator/rust/mod.rs @@ -16,7 +16,7 @@ pub mod signals; pub mod threading; use crate::generator::{rust::fragment::GeneratedRustFragment, structuring}; -use crate::parser::{parameter::ParsedFunctionParameter, Parser}; +use crate::parser::{parameter::ParsedFunctionParameter, qobject::ParsedQObject, Parser}; use proc_macro2::{Ident, TokenStream}; use quote::quote; use syn::{parse_quote, Item, ItemMod, Result}; @@ -60,6 +60,8 @@ impl GeneratedRustBlocks { let namespace = parser.cxx_qt_data.namespace.clone().unwrap_or_default(); let passthrough_mod = &parser.passthrough_module; + fragments.extend(vec![add_qobject_import(&parser.cxx_qt_data.qobjects)]); + let vis = &passthrough_mod.vis; let ident = &passthrough_mod.module_ident; let docs = &passthrough_mod.docs; @@ -91,6 +93,29 @@ impl GeneratedRustBlocks { } } +fn add_qobject_import(qobjects: &[ParsedQObject]) -> GeneratedRustFragment { + let includes = qobjects + .iter() + .any(|obj| obj.has_qobject_macro && obj.base_class.is_none()); + if includes { + GeneratedRustFragment { + cxx_mod_contents: vec![parse_quote! { + extern "C++" { + #[doc(hidden)] + #[namespace=""] + type QObject = cxx_qt::qobject::QObject; + } + }], + cxx_qt_mod_contents: vec![], + } + } else { + GeneratedRustFragment { + cxx_mod_contents: vec![], + cxx_qt_mod_contents: vec![], + } + } +} + /// Return the [TokenStream] of the parsed parameters for use in generation pub fn get_params_tokens( mutable: bool, @@ -139,7 +164,7 @@ mod tests { assert!(rust.cxx_mod.content.is_none()); assert_eq!(rust.cxx_mod_contents.len(), 0); assert_eq!(rust.namespace, ""); - assert_eq!(rust.fragments.len(), 1); + assert_eq!(rust.fragments.len(), 2); } #[test] @@ -159,7 +184,7 @@ mod tests { assert!(rust.cxx_mod.content.is_none()); assert_eq!(rust.cxx_mod_contents.len(), 0); assert_eq!(rust.namespace, "cxx_qt"); - assert_eq!(rust.fragments.len(), 1); + assert_eq!(rust.fragments.len(), 2); } #[test] @@ -179,6 +204,6 @@ mod tests { assert!(rust.cxx_mod.content.is_none()); assert_eq!(rust.cxx_mod_contents.len(), 0); assert_eq!(rust.namespace, ""); - assert_eq!(rust.fragments.len(), 1); + assert_eq!(rust.fragments.len(), 2); } } diff --git a/crates/cxx-qt-gen/src/generator/rust/qobject.rs b/crates/cxx-qt-gen/src/generator/rust/qobject.rs index 808c63e8b..f4eb3fd08 100644 --- a/crates/cxx-qt-gen/src/generator/rust/qobject.rs +++ b/crates/cxx-qt-gen/src/generator/rust/qobject.rs @@ -3,6 +3,7 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 use crate::generator::structuring::StructuredQObject; +use crate::naming::Name; use crate::{ generator::{ naming::{namespace::NamespaceName, qobject::QObjectNames}, @@ -18,8 +19,8 @@ use crate::{ }, naming::TypeNames, }; -use quote::quote; -use syn::{Ident, Result}; +use quote::{format_ident, quote}; +use syn::{parse_quote, Result}; impl GeneratedRustFragment { // Might need to be refactored to use a StructuredQObject instead (confirm with Leon) @@ -33,11 +34,7 @@ impl GeneratedRustFragment { let namespace_idents = NamespaceName::from(qobject); let mut generated = Self::default(); - generated.append(&mut generate_qobject_definitions( - &qobject_names, - qobject.base_class.clone(), - type_names, - )?); + generated.append(&mut generate_qobject_definitions(&qobject_names)?); // Generate methods for the properties, invokables, signals generated.append(&mut generate_rust_properties( @@ -86,6 +83,52 @@ impl GeneratedRustFragment { )?); } + // Generate casting impl + let mut blocks = GeneratedRustFragment::default(); + let base = structured_qobject + .declaration + .base_class + .as_ref() + .map(|name| type_names.lookup(name)) + .transpose()? + .cloned() + .unwrap_or( + Name::new(format_ident!("QObject")).with_module(parse_quote! {::cxx_qt::qobject}), + ); // TODO! is this default module here causing the issues in the threading examples + + let base_unqualified = base.rust_unqualified(); + let base_qualified = base.rust_qualified(); + + let struct_name = structured_qobject.declaration.name.rust_qualified(); + let struct_name_unqualified = structured_qobject.declaration.name.rust_unqualified(); + let (upcast_fn, upcast_fn_attrs, upcast_fn_qualified) = qobject_names + .cxx_qt_ffi_method("upcastPtr") + .into_cxx_parts(); + + let fragment = RustFragmentPair { + cxx_bridge: vec![quote! { + extern "C++" { + #[doc(hidden)] + #(#upcast_fn_attrs)* + unsafe fn #upcast_fn(thiz: *const #struct_name_unqualified) -> *const #base_unqualified; + } + }], + implementation: vec![quote! { + impl ::cxx_qt::Upcast<#base_qualified> for #struct_name{ + unsafe fn upcast_ptr(this: *const Self) -> *const #base_qualified { + #upcast_fn_qualified(this) + } + } + }], + }; + blocks + .cxx_mod_contents + .append(&mut fragment.cxx_bridge_as_items()?); + blocks + .cxx_qt_mod_contents + .append(&mut fragment.implementation_as_items()?); + generated.append(&mut blocks); + generated.append(&mut constructor::generate( &structured_qobject.constructors, &qobject_names, @@ -100,11 +143,7 @@ impl GeneratedRustFragment { } /// Generate the C++ and Rust CXX definitions for the QObject -fn generate_qobject_definitions( - qobject_idents: &QObjectNames, - base: Option, - type_names: &TypeNames, -) -> Result { +fn generate_qobject_definitions(qobject_idents: &QObjectNames) -> Result { let mut generated = GeneratedRustFragment::default(); let cpp_class_name_rust = &qobject_idents.name.rust_unqualified(); let cpp_class_name_cpp = &qobject_idents.name.cxx_unqualified(); @@ -123,25 +162,6 @@ fn generate_qobject_definitions( } }; - let cpp_struct_qualified = &qobject_idents.name.rust_qualified(); - - let base_upcast = if let Some(base) = base { - let base_name = type_names.lookup(&base)?.rust_qualified(); - vec![ - quote! { impl cxx_qt::Upcast<#base_name> for #cpp_struct_qualified {} }, - // Until we can actually implement the Upcast trait properly, we just need to silence - // the warning that the base class is otherwise unused. - // This can be done with an unnamed import and the right attributes - quote! { - #[allow(unused_imports)] - #[allow(dead_code)] - use #base_name as _; - }, - ] - } else { - vec![] - }; - let fragment = RustFragmentPair { cxx_bridge: vec![ quote! { @@ -168,7 +188,7 @@ fn generate_qobject_definitions( } }, ], - implementation: base_upcast, + implementation: vec![], }; generated @@ -224,7 +244,7 @@ mod tests { &parser.type_names, ) .unwrap(); - assert_eq!(rust.cxx_mod_contents.len(), 6); + assert_eq!(rust.cxx_mod_contents.len(), 7); assert_tokens_eq( &rust.cxx_mod_contents[0], quote! { @@ -259,6 +279,17 @@ mod tests { ); assert_tokens_eq( &rust.cxx_mod_contents[3], + quote! { + extern "C++" { + #[doc(hidden)] + #[cxx_name = "upcastPtr"] + #[namespace = "rust::cxxqt1"] + unsafe fn cxx_qt_ffi_MyObject_upcastPtr(thiz: *const MyObject) -> *const QObject; + } + }, + ); + assert_tokens_eq( + &rust.cxx_mod_contents[4], quote! { extern "Rust" { #[cxx_name = "createRs"] @@ -268,7 +299,7 @@ mod tests { }, ); assert_tokens_eq( - &rust.cxx_mod_contents[4], + &rust.cxx_mod_contents[5], quote! { unsafe extern "C++" { #[doc(hidden)] @@ -279,7 +310,7 @@ mod tests { }, ); assert_tokens_eq( - &rust.cxx_mod_contents[5], + &rust.cxx_mod_contents[6], quote! { unsafe extern "C++" { #[doc(hidden)] diff --git a/crates/cxx-qt-gen/src/writer/cpp/header.rs b/crates/cxx-qt-gen/src/writer/cpp/header.rs index 9972233bc..84a52ed22 100644 --- a/crates/cxx-qt-gen/src/writer/cpp/header.rs +++ b/crates/cxx-qt-gen/src/writer/cpp/header.rs @@ -255,6 +255,7 @@ mod tests { let expected = indoc! {r#" #pragma once +#include #include class MyObject; diff --git a/crates/cxx-qt-gen/test_outputs/inheritance.h b/crates/cxx-qt-gen/test_outputs/inheritance.h index ae42cf806..6e7700a3f 100644 --- a/crates/cxx-qt-gen/test_outputs/inheritance.h +++ b/crates/cxx-qt-gen/test_outputs/inheritance.h @@ -1,5 +1,6 @@ #pragma once +#include #include class MyObject; diff --git a/crates/cxx-qt-gen/test_outputs/inheritance.rs b/crates/cxx-qt-gen/test_outputs/inheritance.rs index 0595b099d..20be1cda4 100644 --- a/crates/cxx-qt-gen/test_outputs/inheritance.rs +++ b/crates/cxx-qt-gen/test_outputs/inheritance.rs @@ -60,6 +60,13 @@ mod inheritance { #[doc = " Inherited fetchMore from the base class"] unsafe fn fetch_more(self: Pin<&mut MyObject>, index: &QModelIndex); } + extern "C++" { + #[doc(hidden)] + #[cxx_name = "upcastPtr"] + #[namespace = "rust::cxxqt1"] + unsafe fn cxx_qt_ffi_MyObject_upcastPtr(thiz: *const MyObject) + -> *const QAbstractItemModel; + } extern "Rust" { #[cxx_name = "createRs"] #[namespace = "cxx_qt_MyObject"] @@ -78,10 +85,11 @@ mod inheritance { fn cxx_qt_ffi_MyObject_unsafeRustMut(outer: Pin<&mut MyObject>) -> Pin<&mut MyObjectRust>; } } -impl cxx_qt::Upcast for inheritance::MyObject {} -#[allow(unused_imports)] -#[allow(dead_code)] -use inheritance::QAbstractItemModel as _; +impl ::cxx_qt::Upcast for inheritance::MyObject { + unsafe fn upcast_ptr(this: *const Self) -> *const inheritance::QAbstractItemModel { + inheritance::cxx_qt_ffi_MyObject_upcastPtr(this) + } +} #[doc(hidden)] pub fn create_rs_MyObjectRust() -> std::boxed::Box { std::boxed::Box::new(core::default::Default::default()) diff --git a/crates/cxx-qt-gen/test_outputs/invokables.h b/crates/cxx-qt-gen/test_outputs/invokables.h index 4dc0dabc6..142dc98d7 100644 --- a/crates/cxx-qt-gen/test_outputs/invokables.h +++ b/crates/cxx-qt-gen/test_outputs/invokables.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include diff --git a/crates/cxx-qt-gen/test_outputs/invokables.rs b/crates/cxx-qt-gen/test_outputs/invokables.rs index 993923add..681cbf651 100644 --- a/crates/cxx-qt-gen/test_outputs/invokables.rs +++ b/crates/cxx-qt-gen/test_outputs/invokables.rs @@ -145,6 +145,12 @@ mod ffi { #[namespace = "cxx_qt::my_object::cxx_qt_MyObject"] type MyObjectCxxQtThreadQueuedFn; } + extern "C++" { + #[doc(hidden)] + #[cxx_name = "upcastPtr"] + #[namespace = "rust::cxxqt1"] + unsafe fn cxx_qt_ffi_MyObject_upcastPtr(thiz: *const MyObject) -> *const QObject; + } #[namespace = "cxx_qt::my_object::cxx_qt_MyObject"] #[cxx_name = "CxxQtConstructorArguments0"] #[doc(hidden)] @@ -246,6 +252,11 @@ mod ffi { #[namespace = "rust::cxxqt1"] fn cxx_qt_ffi_MyObject_unsafeRustMut(outer: Pin<&mut MyObject>) -> Pin<&mut MyObjectRust>; } + extern "C++" { + #[doc(hidden)] + #[namespace = ""] + type QObject = cxx_qt::qobject::QObject; + } } impl cxx_qt::Threading for ffi::MyObject { type BoxedQueuedFn = MyObjectCxxQtThreadQueuedFn; @@ -292,6 +303,11 @@ impl cxx_qt::Threading for ffi::MyObject { pub struct MyObjectCxxQtThreadQueuedFn { inner: std::boxed::Box) + Send>, } +impl ::cxx_qt::Upcast<::cxx_qt::qobject::QObject> for ffi::MyObject { + unsafe fn upcast_ptr(this: *const Self) -> *const ::cxx_qt::qobject::QObject { + ffi::cxx_qt_ffi_MyObject_upcastPtr(this) + } +} #[doc(hidden)] pub fn route_arguments_MyObject_0<'a>( arg0: i32, 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 ac35821e9..76dafdad2 100644 --- a/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.h +++ b/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include 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 717b4af81..64448c7d6 100644 --- a/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.rs +++ b/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.rs @@ -164,6 +164,12 @@ pub mod ffi { self_value: Pin<&mut MyObject>, ); } + extern "C++" { + #[doc(hidden)] + #[cxx_name = "upcastPtr"] + #[namespace = "rust::cxxqt1"] + unsafe fn cxx_qt_ffi_MyObject_upcastPtr(thiz: *const MyObject) -> *const QStringListModel; + } extern "Rust" { #[cxx_name = "createRs"] #[namespace = "cxx_qt::multi_object::cxx_qt_MyObject"] @@ -280,6 +286,12 @@ pub mod ffi { self_value: Pin<&mut SecondObject>, ); } + extern "C++" { + #[doc(hidden)] + #[cxx_name = "upcastPtr"] + #[namespace = "rust::cxxqt1"] + unsafe fn cxx_qt_ffi_SecondObject_upcastPtr(thiz: *const SecondObject) -> *const QObject; + } extern "Rust" { #[cxx_name = "createRs"] #[namespace = "second_object::cxx_qt_SecondObject"] @@ -316,6 +328,12 @@ pub mod ffi { #[namespace = "my_namespace"] type ThirdObjectRust; } + extern "C++" { + #[doc(hidden)] + #[cxx_name = "upcastPtr"] + #[namespace = "rust::cxxqt1"] + unsafe fn cxx_qt_ffi_MyCxxName_upcastPtr(thiz: *const MyRustName) -> *const QObject; + } extern "Rust" { #[cxx_name = "createRs"] #[namespace = "my_namespace::cxx_qt_MyRustName"] @@ -438,11 +456,12 @@ pub mod ffi { self_value: Pin<&mut ExternObject>, ); } + extern "C++" { + #[doc(hidden)] + #[namespace = ""] + type QObject = cxx_qt::qobject::QObject; + } } -impl cxx_qt::Upcast for ffi::MyObject {} -#[allow(unused_imports)] -#[allow(dead_code)] -use ffi::QStringListModel as _; impl ffi::MyObject { #[doc = "Getter for the Q_PROPERTY "] #[doc = "property_name"] @@ -589,6 +608,11 @@ cxx_qt::static_assertions::assert_eq_size!( cxx_qt::signalhandler::CxxQtSignalHandler, [usize; 2] ); +impl ::cxx_qt::Upcast for ffi::MyObject { + unsafe fn upcast_ptr(this: *const Self) -> *const ffi::QStringListModel { + ffi::cxx_qt_ffi_MyObject_upcastPtr(this) + } +} #[doc(hidden)] pub fn create_rs_MyObjectRust() -> std::boxed::Box { std::boxed::Box::new(core::default::Default::default()) @@ -754,6 +778,11 @@ cxx_qt::static_assertions::assert_eq_size!( cxx_qt::signalhandler::CxxQtSignalHandler, [usize; 2] ); +impl ::cxx_qt::Upcast<::cxx_qt::qobject::QObject> for ffi::SecondObject { + unsafe fn upcast_ptr(this: *const Self) -> *const ::cxx_qt::qobject::QObject { + ffi::cxx_qt_ffi_SecondObject_upcastPtr(this) + } +} #[doc(hidden)] pub fn create_rs_SecondObjectRust() -> std::boxed::Box { std::boxed::Box::new(core::default::Default::default()) @@ -773,6 +802,11 @@ impl ::cxx_qt::CxxQtType for ffi::SecondObject { ffi::cxx_qt_ffi_SecondObject_unsafeRustMut(self) } } +impl ::cxx_qt::Upcast<::cxx_qt::qobject::QObject> for ffi::MyRustName { + unsafe fn upcast_ptr(this: *const Self) -> *const ::cxx_qt::qobject::QObject { + ffi::cxx_qt_ffi_MyCxxName_upcastPtr(this) + } +} #[doc(hidden)] pub fn create_rs_ThirdObjectRust() -> std::boxed::Box { std::boxed::Box::new(core::default::Default::default()) diff --git a/crates/cxx-qt-gen/test_outputs/properties.h b/crates/cxx-qt-gen/test_outputs/properties.h index a75354ee1..ee9caf2b1 100644 --- a/crates/cxx-qt-gen/test_outputs/properties.h +++ b/crates/cxx-qt-gen/test_outputs/properties.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include diff --git a/crates/cxx-qt-gen/test_outputs/properties.rs b/crates/cxx-qt-gen/test_outputs/properties.rs index 521f44b38..d55ca4caf 100644 --- a/crates/cxx-qt-gen/test_outputs/properties.rs +++ b/crates/cxx-qt-gen/test_outputs/properties.rs @@ -388,6 +388,12 @@ mod ffi { self_value: Pin<&mut MyObject>, ); } + extern "C++" { + #[doc(hidden)] + #[cxx_name = "upcastPtr"] + #[namespace = "rust::cxxqt1"] + unsafe fn cxx_qt_ffi_MyObject_upcastPtr(thiz: *const MyObject) -> *const QObject; + } extern "Rust" { #[cxx_name = "createRs"] #[namespace = "cxx_qt::my_object::cxx_qt_MyObject"] @@ -405,6 +411,11 @@ mod ffi { #[namespace = "rust::cxxqt1"] fn cxx_qt_ffi_MyObject_unsafeRustMut(outer: Pin<&mut MyObject>) -> Pin<&mut MyObjectRust>; } + extern "C++" { + #[doc(hidden)] + #[namespace = ""] + type QObject = cxx_qt::qobject::QObject; + } } impl ffi::MyObject { #[doc = "Getter for the Q_PROPERTY "] @@ -1036,6 +1047,11 @@ cxx_qt::static_assertions::assert_eq_size!( cxx_qt::signalhandler::CxxQtSignalHandler, [usize; 2] ); +impl ::cxx_qt::Upcast<::cxx_qt::qobject::QObject> for ffi::MyObject { + unsafe fn upcast_ptr(this: *const Self) -> *const ::cxx_qt::qobject::QObject { + ffi::cxx_qt_ffi_MyObject_upcastPtr(this) + } +} #[doc(hidden)] pub fn create_rs_MyObjectRust() -> std::boxed::Box { std::boxed::Box::new(core::default::Default::default()) diff --git a/crates/cxx-qt-gen/test_outputs/qenum.h b/crates/cxx-qt-gen/test_outputs/qenum.h index 71e53b9e1..a81c424c2 100644 --- a/crates/cxx-qt-gen/test_outputs/qenum.h +++ b/crates/cxx-qt-gen/test_outputs/qenum.h @@ -3,6 +3,7 @@ #include #include #include +#include #include namespace cxx_qt::my_object { diff --git a/crates/cxx-qt-gen/test_outputs/qenum.rs b/crates/cxx-qt-gen/test_outputs/qenum.rs index 0e6954289..95804aac1 100644 --- a/crates/cxx-qt-gen/test_outputs/qenum.rs +++ b/crates/cxx-qt-gen/test_outputs/qenum.rs @@ -89,6 +89,12 @@ mod ffi { #[doc(hidden)] fn my_invokable(self: &MyObject, qenum: MyEnum, other_qenum: MyOtherEnum); } + extern "C++" { + #[doc(hidden)] + #[cxx_name = "upcastPtr"] + #[namespace = "rust::cxxqt1"] + unsafe fn cxx_qt_ffi_MyObject_upcastPtr(thiz: *const MyObject) -> *const QObject; + } extern "Rust" { #[cxx_name = "createRs"] #[namespace = "cxx_qt::my_object::cxx_qt_MyObject"] @@ -123,6 +129,12 @@ mod ffi { #[namespace = "cxx_qt::my_object"] type InternalObject; } + extern "C++" { + #[doc(hidden)] + #[cxx_name = "upcastPtr"] + #[namespace = "rust::cxxqt1"] + unsafe fn cxx_qt_ffi_CxxName_upcastPtr(thiz: *const MyRenamedObject) -> *const QObject; + } extern "Rust" { #[cxx_name = "createRs"] #[namespace = "cxx_qt::my_object::cxx_qt_MyRenamedObject"] @@ -142,6 +154,16 @@ mod ffi { outer: Pin<&mut MyRenamedObject>, ) -> Pin<&mut InternalObject>; } + extern "C++" { + #[doc(hidden)] + #[namespace = ""] + type QObject = cxx_qt::qobject::QObject; + } +} +impl ::cxx_qt::Upcast<::cxx_qt::qobject::QObject> for ffi::MyObject { + unsafe fn upcast_ptr(this: *const Self) -> *const ::cxx_qt::qobject::QObject { + ffi::cxx_qt_ffi_MyObject_upcastPtr(this) + } } #[doc(hidden)] pub fn create_rs_MyObjectRust() -> std::boxed::Box { @@ -162,6 +184,11 @@ impl ::cxx_qt::CxxQtType for ffi::MyObject { ffi::cxx_qt_ffi_MyObject_unsafeRustMut(self) } } +impl ::cxx_qt::Upcast<::cxx_qt::qobject::QObject> for ffi::MyRenamedObject { + unsafe fn upcast_ptr(this: *const Self) -> *const ::cxx_qt::qobject::QObject { + ffi::cxx_qt_ffi_CxxName_upcastPtr(this) + } +} #[doc(hidden)] pub fn create_rs_InternalObject() -> std::boxed::Box { std::boxed::Box::new(core::default::Default::default()) diff --git a/crates/cxx-qt-gen/test_outputs/signals.h b/crates/cxx-qt-gen/test_outputs/signals.h index 4a1092750..bad91af1b 100644 --- a/crates/cxx-qt-gen/test_outputs/signals.h +++ b/crates/cxx-qt-gen/test_outputs/signals.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include diff --git a/crates/cxx-qt-gen/test_outputs/signals.rs b/crates/cxx-qt-gen/test_outputs/signals.rs index 77cee71a3..35a845ba2 100644 --- a/crates/cxx-qt-gen/test_outputs/signals.rs +++ b/crates/cxx-qt-gen/test_outputs/signals.rs @@ -153,6 +153,12 @@ mod ffi { fourth: &'a QPoint, ); } + extern "C++" { + #[doc(hidden)] + #[cxx_name = "upcastPtr"] + #[namespace = "rust::cxxqt1"] + unsafe fn cxx_qt_ffi_MyObject_upcastPtr(thiz: *const MyObject) -> *const QObject; + } extern "Rust" { #[cxx_name = "createRs"] #[namespace = "cxx_qt::my_object::cxx_qt_MyObject"] @@ -200,6 +206,11 @@ mod ffi { self_value: Pin<&mut QTimer>, ); } + extern "C++" { + #[doc(hidden)] + #[namespace = ""] + type QObject = cxx_qt::qobject::QObject; + } } impl ffi::MyObject { #[doc = "Connect the given function pointer to the signal "] @@ -427,6 +438,11 @@ cxx_qt::static_assertions::assert_eq_size!( cxx_qt::signalhandler::CxxQtSignalHandler, [usize; 2] ); +impl ::cxx_qt::Upcast<::cxx_qt::qobject::QObject> for ffi::MyObject { + unsafe fn upcast_ptr(this: *const Self) -> *const ::cxx_qt::qobject::QObject { + ffi::cxx_qt_ffi_MyObject_upcastPtr(this) + } +} #[doc(hidden)] pub fn create_rs_MyObjectRust() -> std::boxed::Box { std::boxed::Box::new(core::default::Default::default()) diff --git a/crates/cxx-qt/build.rs b/crates/cxx-qt/build.rs index efe12c08d..7758806f3 100644 --- a/crates/cxx-qt/build.rs +++ b/crates/cxx-qt/build.rs @@ -19,6 +19,7 @@ fn write_headers() { for file_path in [ "connection.h", + "casting.h", "signalhandler.h", "thread.h", "threading.h", @@ -40,7 +41,7 @@ fn main() { let mut builder = CxxQtBuilder::library(interface); let cpp_files = ["src/connection.cpp"]; - let rust_bridges = ["src/connection.rs"]; + let rust_bridges = ["src/connection.rs", "src/qobject.rs"]; for bridge in &rust_bridges { builder = builder.file(bridge); diff --git a/crates/cxx-qt/include/casting.h b/crates/cxx-qt/include/casting.h new file mode 100644 index 000000000..77d4223d2 --- /dev/null +++ b/crates/cxx-qt/include/casting.h @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group +// company SPDX-FileContributor: Ben Ford +// +// SPDX-License-Identifier: MIT OR Apache-2.0 +#pragma once +#include + +namespace rust::cxxqt1 { + +template +const Base* +upcastPtr(const Sub* sub) +{ + static_assert(std::is_base_of_v); + return static_cast(sub); +} + +} diff --git a/crates/cxx-qt/src/lib.rs b/crates/cxx-qt/src/lib.rs index fd2c7d0da..d92ab45b2 100644 --- a/crates/cxx-qt/src/lib.rs +++ b/crates/cxx-qt/src/lib.rs @@ -9,10 +9,13 @@ //! //! See the [book](https://kdab.github.io/cxx-qt/book/) for more information. +use std::ops::Deref; +use std::pin::Pin; use std::{fs::File, io::Write, path::Path}; mod connection; mod connectionguard; +pub mod qobject; #[doc(hidden)] pub mod signalhandler; mod threading; @@ -121,10 +124,41 @@ pub trait Threading: Sized { fn threading_drop(cxx_qt_thread: &mut CxxQtThread); } -/// Placeholder for upcasting objects, suppresses dead code warning -#[allow(dead_code)] -#[doc(hidden)] -pub trait Upcast {} +/// This trait is automatically implemented by CXX-Qt and should not be manually implemented +/// Allows upcasting to either QObject or the provided base class of a type +/// Will not be implemented if no types inherit from QObject or base. +pub trait Upcast { + #[doc(hidden)] + /// Internal function, Should not be implemented manually + unsafe fn upcast_ptr(this: *const Self) -> *const T; + + /// Upcast a reference to a reference to the base class + fn upcast(&self) -> &T { + let ptr = self as *const Self; + unsafe { + let base = Self::upcast_ptr(ptr); + &*base + } + } + + /// Upcast a mutable reference to a mutable reference to the base class + fn upcast_mut(&mut self) -> &mut T { + let ptr = self as *const Self; + unsafe { + let base = Self::upcast_ptr(ptr) as *mut T; + &mut *base + } + } + + /// Upcast a pinned mutable reference to a pinned mutable reference to the base class + fn upcast_pin(self: Pin<&mut Self>) -> Pin<&mut T> { + let this = self.deref() as *const Self; + unsafe { + let base = Self::upcast_ptr(this) as *mut T; + Pin::new_unchecked(&mut *base) + } + } +} /// This trait can be implemented on any [CxxQtType] to define a /// custom constructor in C++ for the QObject. diff --git a/crates/cxx-qt/src/qobject.rs b/crates/cxx-qt/src/qobject.rs new file mode 100644 index 000000000..a1e0b6610 --- /dev/null +++ b/crates/cxx-qt/src/qobject.rs @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company +// SPDX-FileContributor: Ben Ford +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! QObject module + +#[cxx::bridge] +mod ffi { + unsafe extern "C++" { + // TODO! Implement QObject wrapper properly + include!(); + /// QObject type + type QObject; + + #[cxx_name = "dumpObjectInfo"] + /// Dump Object info method, added so that upcast methods can be tested. + fn dump_object_info(&self); + } +} + +pub use ffi::QObject;