Skip to content

Commit

Permalink
WIP: extern "C++Qt" block support and widgets example
Browse files Browse the repository at this point in the history
  • Loading branch information
ahayzen-kdab committed Jul 26, 2023
1 parent 0d74bbd commit d33623c
Show file tree
Hide file tree
Showing 20 changed files with 695 additions and 170 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ members = [
"examples/qml_extension_plugin/plugin/rust",
"examples/qml_features/rust",
"examples/qml_minimal/rust",
"examples/widgets_minimal/rust",

"tests/basic_cxx_only/rust",
"tests/basic_cxx_qt/rust",
Expand Down
5 changes: 5 additions & 0 deletions crates/cxx-qt-gen/src/generator/cpp/externcxxqt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// 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

87 changes: 87 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,87 @@
// 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 std::collections::BTreeMap;

use crate::{
generator::{
naming::CombinedIdent,
rust::{
fragment::RustFragmentPair, qobject::GeneratedRustQObject,
signals::generate_rust_signal,
},
},
parser::cxxqtdata::ParsedExternCxxBlocks,
};
use quote::{format_ident, quote};
use syn::{Ident, Item, Path, 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 From<GeneratedRustQObject> for GeneratedExternCxxQt {
fn from(value: GeneratedRustQObject) -> Self {
Self {
cxx_mod_contents: value.cxx_mod_contents,
cxx_qt_mod_contents: value.cxx_qt_mod_contents,
}
}
}

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_cxx_block: &ParsedExternCxxBlocks,
qualified_mappings: &BTreeMap<Ident, Path>,
) -> Result<Self> {
let mut generated = GeneratedExternCxxQt::default();

// Add the pass through blocks
let attrs = &extern_cxx_block.attrs;
let unsafety = &extern_cxx_block.unsafety;
let items = &extern_cxx_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_cxx_block.signals {
let custom_connect_ident_rust = CombinedIdent {
cpp: format_ident!("{}_{}", signal.qobject_ident, signal.ident.cpp),
rust: format_ident!("{}_{}", signal.qobject_ident, signal.ident.rust),
};
generated.append(
&mut generate_rust_signal(
signal,
&signal.qobject_ident,
qualified_mappings,
Some(custom_connect_ident_rust),
)?
.into(),
);
}

Ok(generated)
}
}
16 changes: 16 additions & 0 deletions 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 invokable;
Expand All @@ -18,6 +19,8 @@ use crate::parser::Parser;
use quote::quote;
use syn::{Item, ItemMod, Result};

use self::externcxxqt::GeneratedExternCxxQt;

/// Representation of the generated Rust code for a QObject
pub struct GeneratedRustBlocks {
/// Module for the CXX bridge with passthrough items
Expand All @@ -30,6 +33,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 @@ -51,6 +56,17 @@ impl GeneratedRustBlocks {
)
})
.collect::<Result<Vec<GeneratedRustQObject>>>()?,
extern_cxx_qt: parser
.cxx_qt_data
.extern_cxx_blocks
.iter()
.map(|extern_cxx_block| {
GeneratedExternCxxQt::from(
extern_cxx_block,
&parser.cxx_qt_data.qualified_mappings,
)
})
.collect::<Result<Vec<GeneratedExternCxxQt>>>()?,
})
}
}
Expand Down
209 changes: 117 additions & 92 deletions crates/cxx-qt-gen/src/generator/rust/signals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::collections::BTreeMap;

use crate::{
generator::{
naming::{qobject::QObjectName, signals::QSignalName},
naming::{qobject::QObjectName, signals::QSignalName, CombinedIdent},
rust::{fragment::RustFragmentPair, qobject::GeneratedRustQObject},
utils::rust::{syn_ident_cxx_bridge_to_qualified_impl, syn_type_cxx_bridge_to_qualified},
},
Expand All @@ -16,107 +16,132 @@ use crate::{
use quote::quote;
use syn::{parse_quote, FnArg, Ident, Path, Result};

pub fn generate_rust_signals(
signals: &Vec<ParsedSignal>,
qobject_idents: &QObjectName,
pub fn generate_rust_signal(
signal: &ParsedSignal,
qobject_name: &Ident,
qualified_mappings: &BTreeMap<Ident, Path>,
custom_connect_ident: Option<CombinedIdent>,
) -> Result<GeneratedRustQObject> {
let mut generated = GeneratedRustQObject::default();
let qobject_name = &qobject_idents.cpp_class.rust;
let idents = QSignalName::from(signal);
let signal_name_rust = idents.name.rust;
let signal_name_rust_str = signal_name_rust.to_string();
let signal_name_cpp = idents.name.cpp;
let signal_name_cpp_str = signal_name_cpp.to_string();
let connect_ident_cpp = &custom_connect_ident.as_ref().unwrap_or_else(|| &idents.connect_name).cpp;
let connect_ident_rust = &custom_connect_ident.as_ref().unwrap_or_else(|| &idents.connect_name).rust;
let connect_ident_rust_str = connect_ident_rust.to_string();
let on_ident_rust = idents.on_name;

// Create the methods for the other signals
for signal in signals {
let idents = QSignalName::from(signal);
let signal_name_rust = idents.name.rust;
let signal_name_rust_str = signal_name_rust.to_string();
let signal_name_cpp = idents.name.cpp;
let signal_name_cpp_str = signal_name_cpp.to_string();
let connect_ident_cpp = idents.connect_name.cpp;
let connect_ident_rust = idents.connect_name.rust;
let connect_ident_rust_str = connect_ident_rust.to_string();
let on_ident_rust = idents.on_name;

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, qualified_mappings);
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, qualified_mappings);
}
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, qualified_mappings);
let qualified_impl = syn_ident_cxx_bridge_to_qualified_impl(qobject_name, qualified_mappings);
let self_call = if custom_connect_ident.is_some() {
quote! { ffi:: }
} else {
quote! { self. }
};
let self_ident = if custom_connect_ident.is_some() {
quote! { self_value }
} else {
quote! { self }
};
let self_value = if custom_connect_ident.is_some() {
quote! { self, }
} else {
quote! { }
};

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 attrs = &signal.method.attrs;

let fragment = RustFragmentPair {
cxx_bridge: vec![
quote! {
#unsafe_block extern "C++" {
#(#attrs)*
#[rust_name = #signal_name_rust_str]
#unsafe_call fn #signal_name_cpp(self: #self_type_cxx, #(#parameters_cxx),*);
}
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, qualified_mappings);
let qualified_impl =
syn_ident_cxx_bridge_to_qualified_impl(qobject_name, qualified_mappings);

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 attrs = &signal.method.attrs;

let fragment = RustFragmentPair {
cxx_bridge: vec![
quote! {
#unsafe_block extern "C++" {
#(#attrs)*
#[rust_name = #signal_name_rust_str]
#unsafe_call fn #signal_name_cpp(self: #self_type_cxx, #(#parameters_cxx),*);
}
},
quote! {
unsafe extern "C++" {
#[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]
#[rust_name = #connect_ident_rust_str]
fn #connect_ident_cpp(self: #self_type_cxx, func: #unsafe_call fn(#self_type_cxx, #(#parameters_cxx),*), conn_type: CxxQtConnectionType) -> CxxQtQMetaObjectConnection;
}
},
],
implementation: vec![quote! {
impl #qualified_impl {
},
quote! {
unsafe extern "C++" {
#[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
{
self.#connect_ident_rust(func, cxx_qt_lib::ConnectionType::AutoConnection)
}
#[rust_name = #connect_ident_rust_str]
fn #connect_ident_cpp(#self_ident: #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
{
#self_call #connect_ident_rust(#self_value func, cxx_qt_lib::ConnectionType::AutoConnection)
}
}
}],
};

let mut generated = GeneratedRustQObject::default();
generated
.cxx_mod_contents
.append(&mut fragment.cxx_bridge_as_items()?);
generated
.cxx_qt_mod_contents
.append(&mut fragment.implementation_as_items()?);

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,
qualified_mappings: &BTreeMap<Ident, Path>,
) -> Result<GeneratedRustQObject> {
let mut generated = GeneratedRustQObject::default();
let qobject_name = &qobject_idents.cpp_class.rust;

// Create the methods for the other signals
for signal in signals {
generated.append(&mut generate_rust_signal(signal, qobject_name, qualified_mappings, None)?);
}

Ok(generated)
Expand Down
Loading

0 comments on commit d33623c

Please sign in to comment.