Skip to content

Commit

Permalink
Automatically generate upcasting implementations
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
BenFordTytherington committed Jan 16, 2025
1 parent 4455c86 commit b15aae6
Show file tree
Hide file tree
Showing 20 changed files with 332 additions and 52 deletions.
6 changes: 6 additions & 0 deletions crates/cxx-qt-gen/src/generator/cpp/qobject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,12 @@ impl GeneratedCppQObject {
class_initializers.push(initializer);
}

// Include casting header
let mut result = GeneratedCppQObjectBlocks::default();
result.includes.insert("#include <cxx-qt/casting.h>".into());

generated.blocks.append(&mut result);

generated.blocks.append(&mut constructor::generate(
&generated,
&structured_qobject.constructors,
Expand Down
33 changes: 29 additions & 4 deletions crates/cxx-qt-gen/src/generator/rust/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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]
Expand All @@ -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]
Expand All @@ -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);
}
}
120 changes: 85 additions & 35 deletions crates/cxx-qt-gen/src/generator/rust/qobject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand All @@ -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)
Expand All @@ -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(
Expand Down Expand Up @@ -86,6 +83,71 @@ 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;
}
}],
<<<<<<< Updated upstream
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)
}
}
}],
=======
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)
}

unsafe fn from_base_ptr(base: *const T) -> Option<*const Self> {
None //TODO! placeholder, use the right dynamic_cast function
}
}
},
quote! {
impl ::cxx_qt::Downcast for #struct_name {}
},
],
>>>>>>> Stashed changes
};
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,
Expand All @@ -100,11 +162,7 @@ impl GeneratedRustFragment {
}

/// Generate the C++ and Rust CXX definitions for the QObject
fn generate_qobject_definitions(
qobject_idents: &QObjectNames,
base: Option<Ident>,
type_names: &TypeNames,
) -> Result<GeneratedRustFragment> {
fn generate_qobject_definitions(qobject_idents: &QObjectNames) -> Result<GeneratedRustFragment> {
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();
Expand All @@ -123,25 +181,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! {
Expand All @@ -168,7 +207,7 @@ fn generate_qobject_definitions(
}
},
],
implementation: base_upcast,
implementation: vec![],
};

generated
Expand Down Expand Up @@ -224,7 +263,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! {
Expand Down Expand Up @@ -259,6 +298,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"]
Expand All @@ -268,7 +318,7 @@ mod tests {
},
);
assert_tokens_eq(
&rust.cxx_mod_contents[4],
&rust.cxx_mod_contents[5],
quote! {
unsafe extern "C++" {
#[doc(hidden)]
Expand All @@ -279,7 +329,7 @@ mod tests {
},
);
assert_tokens_eq(
&rust.cxx_mod_contents[5],
&rust.cxx_mod_contents[6],
quote! {
unsafe extern "C++" {
#[doc(hidden)]
Expand Down
1 change: 1 addition & 0 deletions crates/cxx-qt-gen/src/writer/cpp/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ mod tests {
let expected = indoc! {r#"
#pragma once
#include <cxx-qt/casting.h>
#include <cxx-qt/type.h>
class MyObject;
Expand Down
1 change: 1 addition & 0 deletions crates/cxx-qt-gen/test_outputs/inheritance.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once

#include <cxx-qt/casting.h>
#include <cxx-qt/type.h>

class MyObject;
Expand Down
16 changes: 12 additions & 4 deletions crates/cxx-qt-gen/test_outputs/inheritance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand All @@ -78,10 +85,11 @@ mod inheritance {
fn cxx_qt_ffi_MyObject_unsafeRustMut(outer: Pin<&mut MyObject>) -> Pin<&mut MyObjectRust>;
}
}
impl cxx_qt::Upcast<inheritance::QAbstractItemModel> for inheritance::MyObject {}
#[allow(unused_imports)]
#[allow(dead_code)]
use inheritance::QAbstractItemModel as _;
impl ::cxx_qt::Upcast<inheritance::QAbstractItemModel> 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<MyObjectRust> {
std::boxed::Box::new(core::default::Default::default())
Expand Down
1 change: 1 addition & 0 deletions crates/cxx-qt-gen/test_outputs/invokables.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once

#include <cxx-qt/casting.h>
#include <cxx-qt/threading.h>
#include <cxx-qt/type.h>

Expand Down
16 changes: 16 additions & 0 deletions crates/cxx-qt-gen/test_outputs/invokables.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -292,6 +303,11 @@ impl cxx_qt::Threading for ffi::MyObject {
pub struct MyObjectCxxQtThreadQueuedFn {
inner: std::boxed::Box<dyn FnOnce(core::pin::Pin<&mut ffi::MyObject>) + 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,
Expand Down
1 change: 1 addition & 0 deletions crates/cxx-qt-gen/test_outputs/passthrough_and_naming.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once

#include <cxx-qt/casting.h>
#include <cxx-qt/signalhandler.h>
#include <cxx-qt/type.h>

Expand Down
Loading

0 comments on commit b15aae6

Please sign in to comment.