diff --git a/CHANGELOG.md b/CHANGELOG.md index 82be164fe..1f63a4196 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Support for C++ only methods by not having a `#[qinvokable]` attribute - Ability to define a custom C++ Constructor using `cxx_qt::Constructor` - `cxx_qt::Initialize` trait for easier default-constructor implementation +- `extern "C++Qt"` block support for declaring existing types with methods and signals ### Changed diff --git a/crates/cxx-qt-gen/src/generator/rust/externcxxqt.rs b/crates/cxx-qt-gen/src/generator/rust/externcxxqt.rs new file mode 100644 index 000000000..6e779634f --- /dev/null +++ b/crates/cxx-qt-gen/src/generator/rust/externcxxqt.rs @@ -0,0 +1,63 @@ +// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company +// SPDX-FileContributor: Andrew Hayzen +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use crate::{ + generator::rust::{fragment::RustFragmentPair, signals::generate_rust_free_signal}, + parser::{externcxxqt::ParsedExternCxxQt, mappings::ParsedCxxMappings}, +}; +use quote::quote; +use syn::{Ident, Item, Result}; + +#[derive(Default)] +pub struct GeneratedExternCxxQt { + /// Module for the CXX bridge + pub cxx_mod_contents: Vec, + /// Items for the CXX-Qt module + pub cxx_qt_mod_contents: Vec, +} + +impl GeneratedExternCxxQt { + pub fn append(&mut self, other: &mut Self) { + self.cxx_mod_contents.append(&mut other.cxx_mod_contents); + self.cxx_qt_mod_contents + .append(&mut other.cxx_qt_mod_contents); + } + + pub fn from( + extern_cxxqt_block: &ParsedExternCxxQt, + cxx_mappings: &ParsedCxxMappings, + module_ident: &Ident, + ) -> Result { + let mut generated = GeneratedExternCxxQt::default(); + + // Add the pass through blocks + let attrs = &extern_cxxqt_block.attrs; + let unsafety = &extern_cxxqt_block.unsafety; + let items = &extern_cxxqt_block.passthrough_items; + let fragment = RustFragmentPair { + cxx_bridge: vec![quote! { + #(#attrs)* + #unsafety extern "C++" { + #(#items)* + } + }], + implementation: vec![], + }; + generated + .cxx_mod_contents + .append(&mut fragment.cxx_bridge_as_items()?); + + // Build the signals + for signal in &extern_cxxqt_block.signals { + generated.append(&mut generate_rust_free_signal( + signal, + cxx_mappings, + module_ident, + )?); + } + + Ok(generated) + } +} diff --git a/crates/cxx-qt-gen/src/generator/rust/mod.rs b/crates/cxx-qt-gen/src/generator/rust/mod.rs index 5e07a2cfe..213e9295b 100644 --- a/crates/cxx-qt-gen/src/generator/rust/mod.rs +++ b/crates/cxx-qt-gen/src/generator/rust/mod.rs @@ -5,6 +5,7 @@ pub mod constructor; pub mod cxxqttype; +pub mod externcxxqt; pub mod fragment; pub mod inherit; pub mod method; @@ -14,7 +15,7 @@ pub mod qobject; pub mod signals; pub mod threading; -use crate::generator::rust::qobject::GeneratedRustQObject; +use crate::generator::rust::{externcxxqt::GeneratedExternCxxQt, qobject::GeneratedRustQObject}; use crate::parser::Parser; use quote::quote; use syn::{Item, ItemMod, Result}; @@ -31,6 +32,8 @@ pub struct GeneratedRustBlocks { pub namespace: String, /// Generated QObject blocks pub qobjects: Vec, + /// Generated extern "C++Qt" blocks + pub extern_cxx_qt: Vec, } impl GeneratedRustBlocks { @@ -52,6 +55,18 @@ impl GeneratedRustBlocks { ) }) .collect::>>()?, + extern_cxx_qt: parser + .cxx_qt_data + .extern_cxxqt_blocks + .iter() + .map(|extern_cxx_block| { + GeneratedExternCxxQt::from( + extern_cxx_block, + &parser.cxx_qt_data.cxx_mappings, + &parser.passthrough_module.ident, + ) + }) + .collect::>>()?, }) } } diff --git a/crates/cxx-qt-gen/src/generator/rust/signals.rs b/crates/cxx-qt-gen/src/generator/rust/signals.rs index 4891d14cd..9cf42308e 100644 --- a/crates/cxx-qt-gen/src/generator/rust/signals.rs +++ b/crates/cxx-qt-gen/src/generator/rust/signals.rs @@ -8,14 +8,146 @@ use std::collections::BTreeMap; use crate::{ generator::{ naming::{qobject::QObjectName, signals::QSignalName}, - rust::{fragment::RustFragmentPair, qobject::GeneratedRustQObject}, + rust::{ + externcxxqt::GeneratedExternCxxQt, fragment::RustFragmentPair, + qobject::GeneratedRustQObject, + }, utils::rust::{syn_ident_cxx_bridge_to_qualified_impl, syn_type_cxx_bridge_to_qualified}, }, - parser::signals::ParsedSignal, + parser::{mappings::ParsedCxxMappings, signals::ParsedSignal}, }; -use quote::quote; +use quote::{format_ident, quote}; use syn::{parse_quote, FnArg, Ident, Path, Result}; +pub fn generate_rust_free_signal( + signal: &ParsedSignal, + cxx_mappings: &ParsedCxxMappings, + module_ident: &Ident, +) -> Result { + let qobject_name = &signal.qobject_ident; + let idents = QSignalName::from(signal); + let signal_name_cpp = idents.name.cpp; + let signal_name_cpp_str = signal_name_cpp.to_string(); + let free_connect_ident_cpp = + format_ident!("{}_{}", signal.qobject_ident, idents.connect_name.cpp); + let free_connect_ident_rust = + format_ident!("{}_{}", signal.qobject_ident, idents.connect_name.rust); + let free_connect_ident_rust_str = + format_ident!("{}_{}", signal.qobject_ident, idents.connect_name.rust).to_string(); + let connect_ident_rust = idents.connect_name.rust; + let on_ident_rust = idents.on_name; + let original_method = &signal.method; + + // TODO: share this in the naming module with generator/cpp + let connect_namespace = { + // Build a namespace that includes any namespace for the T + let ident_namespace = if let Some(namespace) = cxx_mappings + .namespaces + .get(&signal.qobject_ident.to_string()) + { + format!("::{namespace}") + } else { + "".to_owned() + }; + + format!("rust::cxxqtgen1::externcxxqt{ident_namespace}") + }; + + let parameters_cxx: Vec = signal + .parameters + .iter() + .map(|parameter| { + let ident = ¶meter.ident; + let ty = ¶meter.ty; + parse_quote! { #ident: #ty } + }) + .collect(); + let parameters_qualified: Vec = parameters_cxx + .iter() + .cloned() + .map(|mut parameter| { + if let FnArg::Typed(pat_type) = &mut parameter { + *pat_type.ty = + syn_type_cxx_bridge_to_qualified(&pat_type.ty, &cxx_mappings.qualified); + } + parameter + }) + .collect(); + + let self_type_cxx = if signal.mutable { + parse_quote! { Pin<&mut #qobject_name> } + } else { + parse_quote! { &#qobject_name } + }; + let self_type_qualified = + syn_type_cxx_bridge_to_qualified(&self_type_cxx, &cxx_mappings.qualified); + let qualified_impl = + syn_ident_cxx_bridge_to_qualified_impl(qobject_name, &cxx_mappings.qualified); + + 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 fragment = RustFragmentPair { + cxx_bridge: vec![ + quote! { + #unsafe_block extern "C++" { + #original_method + } + }, + quote! { + unsafe extern "C++" { + #[doc(hidden)] + #[namespace = #connect_namespace] + #[must_use] + #[rust_name = #free_connect_ident_rust_str] + fn #free_connect_ident_cpp(self_value: #self_type_cxx, func: #unsafe_call fn(#self_type_cxx, #(#parameters_cxx),*), conn_type: CxxQtConnectionType) -> CxxQtQMetaObjectConnection; + } + }, + ], + implementation: vec![ + quote! { + impl #qualified_impl { + #[doc = "Connect the given function pointer to the signal "] + #[doc = #signal_name_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] + pub fn #on_ident_rust(self: #self_type_qualified, func: fn(#self_type_qualified, #(#parameters_qualified),*)) -> cxx_qt_lib::QMetaObjectConnection + { + #module_ident::#free_connect_ident_rust(self, func, cxx_qt_lib::ConnectionType::AutoConnection) + } + } + }, + quote! { + impl #qualified_impl { + #[doc = "Connect the given function pointer to the signal "] + #[doc = #signal_name_cpp_str] + #[doc = ", so that when the signal is emitted the function pointer is executed."] + #[must_use] + pub fn #connect_ident_rust(self: #self_type_qualified, func: fn(#self_type_qualified, #(#parameters_qualified),*), conn_type: cxx_qt_lib::ConnectionType) -> cxx_qt_lib::QMetaObjectConnection + { + #module_ident::#free_connect_ident_rust(self, func, conn_type) + } + } + }, + ], + }; + + let mut generated = GeneratedExternCxxQt::default(); + generated + .cxx_mod_contents + .append(&mut fragment.cxx_bridge_as_items()?); + generated + .cxx_qt_mod_contents + .append(&mut fragment.implementation_as_items()?); + + Ok(generated) +} + pub fn generate_rust_signals( signals: &Vec, qobject_idents: &QObjectName, diff --git a/crates/cxx-qt-gen/src/writer/rust/mod.rs b/crates/cxx-qt-gen/src/writer/rust/mod.rs index 9360a5cfb..7c6852b1a 100644 --- a/crates/cxx-qt-gen/src/writer/rust/mod.rs +++ b/crates/cxx-qt-gen/src/writer/rust/mod.rs @@ -46,6 +46,12 @@ pub fn write_rust(generated: &GeneratedRustBlocks) -> TokenStream { cxx_qt_mod_contents.extend_from_slice(&qobject.cxx_qt_mod_contents); } + for extern_cxx_qt in &generated.extern_cxx_qt { + // Add the blocks from the extern "C++Qt" + cxx_mod_contents.extend_from_slice(&extern_cxx_qt.cxx_mod_contents); + cxx_qt_mod_contents.extend_from_slice(&extern_cxx_qt.cxx_qt_mod_contents); + } + // Inject the CXX blocks if let Some((_, items)) = &mut cxx_mod.content { items.extend(cxx_mod_contents); @@ -85,6 +91,7 @@ mod tests { use module::Struct; }], namespace: "cxx_qt::my_object".to_owned(), + extern_cxx_qt: vec![], qobjects: vec![GeneratedRustQObject { cxx_mod_contents: vec![ parse_quote! { @@ -130,6 +137,7 @@ mod tests { use module::Struct; }], namespace: "cxx_qt".to_owned(), + extern_cxx_qt: vec![], qobjects: vec![ GeneratedRustQObject { cxx_mod_contents: vec![ 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 b2ec89912..a8416418e 100644 --- a/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.rs +++ b/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.rs @@ -210,6 +210,57 @@ pub mod ffi { #[doc(hidden)] fn cxx_qt_ffi_rust_mut(self: Pin<&mut SecondObject>) -> Pin<&mut SecondObjectRust>; } + #[namespace = ""] + unsafe extern "C++" { + type QPushButton; + #[namespace = "mynamespace"] + #[cxx_name = "ExternObjectCpp"] + type ExternObject; + } + unsafe extern "C++" { + fn clicked(self: Pin<&mut QPushButton>, checked: bool); + } + unsafe extern "C++" { + #[doc(hidden)] + #[namespace = "rust::cxxqtgen1::externcxxqt"] + #[must_use] + #[rust_name = "QPushButton_connect_clicked"] + fn QPushButton_clickedConnect( + self_value: Pin<&mut QPushButton>, + func: fn(Pin<&mut QPushButton>, checked: bool), + conn_type: CxxQtConnectionType, + ) -> CxxQtQMetaObjectConnection; + } + unsafe extern "C++" { + #[cxx_name = "dataReady"] + fn data_ready(self: Pin<&mut ExternObject>); + } + unsafe extern "C++" { + #[doc(hidden)] + #[namespace = "rust::cxxqtgen1::externcxxqt::mynamespace"] + #[must_use] + #[rust_name = "ExternObject_connect_data_ready"] + fn ExternObject_dataReadyConnect( + self_value: Pin<&mut ExternObject>, + func: fn(Pin<&mut ExternObject>), + conn_type: CxxQtConnectionType, + ) -> CxxQtQMetaObjectConnection; + } + unsafe extern "C++" { + #[rust_name = "error_occurred"] + fn errorOccurred(self: Pin<&mut ExternObject>); + } + unsafe extern "C++" { + #[doc(hidden)] + #[namespace = "rust::cxxqtgen1::externcxxqt::mynamespace"] + #[must_use] + #[rust_name = "ExternObject_connect_error_occurred"] + fn ExternObject_errorOccurredConnect( + self_value: Pin<&mut ExternObject>, + func: fn(Pin<&mut ExternObject>), + conn_type: CxxQtConnectionType, + ) -> CxxQtQMetaObjectConnection; + } } impl ffi::MyObject { #[doc = "Getter for the Q_PROPERTY "] @@ -344,3 +395,88 @@ impl cxx_qt::CxxQtType for ffi::SecondObject { self.cxx_qt_ffi_rust_mut() } } +impl ffi::QPushButton { + #[doc = "Connect the given function pointer to the signal "] + #[doc = "clicked"] + #[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] + pub fn on_clicked( + self: core::pin::Pin<&mut ffi::QPushButton>, + func: fn(core::pin::Pin<&mut ffi::QPushButton>, checked: bool), + ) -> cxx_qt_lib::QMetaObjectConnection { + ffi::QPushButton_connect_clicked(self, func, cxx_qt_lib::ConnectionType::AutoConnection) + } +} +impl ffi::QPushButton { + #[doc = "Connect the given function pointer to the signal "] + #[doc = "clicked"] + #[doc = ", so that when the signal is emitted the function pointer is executed."] + #[must_use] + pub fn connect_clicked( + self: core::pin::Pin<&mut ffi::QPushButton>, + func: fn(core::pin::Pin<&mut ffi::QPushButton>, checked: bool), + conn_type: cxx_qt_lib::ConnectionType, + ) -> cxx_qt_lib::QMetaObjectConnection { + ffi::QPushButton_connect_clicked(self, func, conn_type) + } +} +impl ffi::ExternObject { + #[doc = "Connect the given function pointer to the signal "] + #[doc = "dataReady"] + #[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] + pub fn on_data_ready( + self: core::pin::Pin<&mut ffi::ExternObject>, + func: fn(core::pin::Pin<&mut ffi::ExternObject>), + ) -> cxx_qt_lib::QMetaObjectConnection { + ffi::ExternObject_connect_data_ready(self, func, cxx_qt_lib::ConnectionType::AutoConnection) + } +} +impl ffi::ExternObject { + #[doc = "Connect the given function pointer to the signal "] + #[doc = "dataReady"] + #[doc = ", so that when the signal is emitted the function pointer is executed."] + #[must_use] + pub fn connect_data_ready( + self: core::pin::Pin<&mut ffi::ExternObject>, + func: fn(core::pin::Pin<&mut ffi::ExternObject>), + conn_type: cxx_qt_lib::ConnectionType, + ) -> cxx_qt_lib::QMetaObjectConnection { + ffi::ExternObject_connect_data_ready(self, func, conn_type) + } +} +impl ffi::ExternObject { + #[doc = "Connect the given function pointer to the signal "] + #[doc = "errorOccurred"] + #[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] + pub fn on_error_occurred( + self: core::pin::Pin<&mut ffi::ExternObject>, + func: fn(core::pin::Pin<&mut ffi::ExternObject>), + ) -> cxx_qt_lib::QMetaObjectConnection { + ffi::ExternObject_connect_error_occurred( + self, + func, + cxx_qt_lib::ConnectionType::AutoConnection, + ) + } +} +impl ffi::ExternObject { + #[doc = "Connect the given function pointer to the signal "] + #[doc = "errorOccurred"] + #[doc = ", so that when the signal is emitted the function pointer is executed."] + #[must_use] + pub fn connect_error_occurred( + self: core::pin::Pin<&mut ffi::ExternObject>, + func: fn(core::pin::Pin<&mut ffi::ExternObject>), + conn_type: cxx_qt_lib::ConnectionType, + ) -> cxx_qt_lib::QMetaObjectConnection { + ffi::ExternObject_connect_error_occurred(self, func, conn_type) + } +}