Skip to content

Commit

Permalink
cxx-qt-gen: add Rust generator and writer of extern "C++Qt"
Browse files Browse the repository at this point in the history
Related to KDAB#577
  • Loading branch information
ahayzen-kdab authored and Be-ing committed Aug 24, 2023
1 parent 6493b93 commit 6d3f0cc
Show file tree
Hide file tree
Showing 6 changed files with 359 additions and 4 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
63 changes: 63 additions & 0 deletions crates/cxx-qt-gen/src/generator/rust/externcxxqt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
// SPDX-FileContributor: Andrew Hayzen <[email protected]>
//
// 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<Item>,
/// Items for the CXX-Qt module
pub cxx_qt_mod_contents: Vec<Item>,
}

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<Self> {
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)
}
}
17 changes: 16 additions & 1 deletion crates/cxx-qt-gen/src/generator/rust/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

pub mod constructor;
pub mod cxxqttype;
pub mod externcxxqt;
pub mod fragment;
pub mod inherit;
pub mod method;
Expand All @@ -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};
Expand All @@ -31,6 +32,8 @@ pub struct GeneratedRustBlocks {
pub namespace: String,
/// Generated QObject blocks
pub qobjects: Vec<GeneratedRustQObject>,
/// Generated extern "C++Qt" blocks
pub extern_cxx_qt: Vec<GeneratedExternCxxQt>,
}

impl GeneratedRustBlocks {
Expand All @@ -52,6 +55,18 @@ impl GeneratedRustBlocks {
)
})
.collect::<Result<Vec<GeneratedRustQObject>>>()?,
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::<Result<Vec<GeneratedExternCxxQt>>>()?,
})
}
}
Expand Down
138 changes: 135 additions & 3 deletions crates/cxx-qt-gen/src/generator/rust/signals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<GeneratedExternCxxQt> {
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<FnArg> = signal
.parameters
.iter()
.map(|parameter| {
let ident = &parameter.ident;
let ty = &parameter.ty;
parse_quote! { #ident: #ty }
})
.collect();
let parameters_qualified: Vec<FnArg> = 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<ParsedSignal>,
qobject_idents: &QObjectName,
Expand Down
8 changes: 8 additions & 0 deletions crates/cxx-qt-gen/src/writer/rust/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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! {
Expand Down Expand Up @@ -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![
Expand Down
Loading

0 comments on commit 6d3f0cc

Please sign in to comment.