Skip to content

Commit

Permalink
WIP: cxx-qt-gen: move signals away from an enum to an extern "C++" block
Browse files Browse the repository at this point in the history
Related to KDAB#557
  • Loading branch information
ahayzen-kdab committed Jun 1, 2023
1 parent 93f313d commit 43d5a48
Show file tree
Hide file tree
Showing 16 changed files with 398 additions and 391 deletions.
12 changes: 5 additions & 7 deletions crates/cxx-qt-gen/src/generator/cpp/qobject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,11 @@ impl GeneratedCppQObject {
&qobject_idents,
cxx_mappings,
)?);
if let Some(signals_enum) = &qobject.signals {
generated.blocks.append(&mut generate_cpp_signals(
&signals_enum.signals,
&qobject_idents,
cxx_mappings,
)?);
}
generated.blocks.append(&mut generate_cpp_signals(
&qobject.signals,
&qobject_idents,
cxx_mappings,
)?);
generated.blocks.append(&mut inherit::generate(
&qobject.inherited_methods,
&qobject.base_class,
Expand Down
46 changes: 21 additions & 25 deletions crates/cxx-qt-gen/src/generator/naming/signals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use syn::Ident;

/// Names for parts of a Q_SIGNAL
pub struct QSignalName {
// TODO: this is removed
pub enum_name: Ident,
pub name: CombinedIdent,
pub emit_name: CombinedIdent,
Expand All @@ -19,19 +20,12 @@ pub struct QSignalName {

impl From<&ParsedSignal> for QSignalName {
fn from(signal: &ParsedSignal) -> Self {
// Check if there is a cxx ident that should be used
let cxx_ident = if let Some(cxx_name) = &signal.cxx_name {
format_ident!("{}", cxx_name)
} else {
signal.ident.clone()
};

Self {
enum_name: signal.ident.clone(),
name: CombinedIdent::from_signal(&signal.ident, &cxx_ident),
emit_name: CombinedIdent::emit_from_signal(&signal.ident, &cxx_ident),
connect_name: CombinedIdent::connect_from_signal(&signal.ident, &cxx_ident),
on_name: on_from_signal(&signal.ident),
enum_name: signal.ident.rust.clone(),
name: CombinedIdent::from_signal(&signal.ident),
emit_name: CombinedIdent::emit_from_signal(&signal.ident),
connect_name: CombinedIdent::connect_from_signal(&signal.ident),
on_name: on_from_signal(&signal.ident.rust),
}
}
}
Expand All @@ -42,28 +36,30 @@ fn on_from_signal(ident: &Ident) -> Ident {

impl CombinedIdent {
/// For a given signal ident generate the Rust and C++ names
fn from_signal(ident: &Ident, cxx_ident: &Ident) -> Self {
fn from_signal(ident: &CombinedIdent) -> Self {
Self {
cpp: format_ident!("{}", cxx_ident.to_string().to_case(Case::Camel)),
cpp: format_ident!("{}", ident.cpp.to_string().to_case(Case::Camel)),
// Note that signal names are in camel case so we need to convert to snake and can't clone
rust: format_ident!("{}", ident.to_string().to_case(Case::Snake)),
rust: format_ident!("{}", ident.rust.to_string().to_case(Case::Snake)),
}
}

/// For a given signal ident generate the Rust and C++ emit name
fn emit_from_signal(ident: &Ident, cxx_ident: &Ident) -> Self {
fn emit_from_signal(ident: &CombinedIdent) -> Self {
Self {
cpp: format_ident!("emit{}", cxx_ident.to_string().to_case(Case::Pascal)),
rust: format_ident!("emit_{}", ident.to_string().to_case(Case::Snake)),
cpp: format_ident!("emit{}", ident.cpp.to_string().to_case(Case::Pascal)),
// Note that the Rust emit name is the same name as the signal for now
// in the future this emit wrapper in C++ will be removed.
rust: format_ident!("{}", ident.rust.to_string().to_case(Case::Snake)),
}
}

fn connect_from_signal(ident: &Ident, cxx_ident: &Ident) -> Self {
fn connect_from_signal(ident: &CombinedIdent) -> Self {
Self {
// Use signalConnect instead of onSignal here so that we don't
// create a C++ name that is similar to the QML naming scheme for signals
cpp: format_ident!("{}Connect", cxx_ident.to_string().to_case(Case::Camel)),
rust: format_ident!("connect_{}", ident.to_string().to_case(Case::Snake)),
cpp: format_ident!("{}Connect", ident.cpp.to_string().to_case(Case::Camel)),
rust: format_ident!("connect_{}", ident.rust.to_string().to_case(Case::Snake)),
}
}
}
Expand All @@ -75,9 +71,9 @@ mod tests {
#[test]
fn test_parsed_signal() {
let qsignal = ParsedSignal {
ident: format_ident!("DataChanged"),
// TODO: this won't be pascal
ident: CombinedIdent { cpp: format_ident!("DataChanged"), rust: format_ident!("DataChanged") },
parameters: vec![],
cxx_name: None,
inherit: false,
};

Expand All @@ -98,9 +94,9 @@ mod tests {
#[test]
fn test_parsed_signal_existing_cxx_name() {
let qsignal = ParsedSignal {
ident: format_ident!("ExistingSignal"),
// TODO: this won't be pascal
ident: CombinedIdent { cpp: format_ident!("ExistingSignal"), rust: format_ident!("baseName") },
parameters: vec![],
cxx_name: Some("baseName".to_owned()),
inherit: true,
};

Expand Down
10 changes: 4 additions & 6 deletions crates/cxx-qt-gen/src/generator/rust/qobject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,10 @@ impl GeneratedRustQObject {
&qobject_idents,
&qobject.inherited_methods,
)?);

if let Some(signals_enum) = &qobject.signals {
generated
.blocks
.append(&mut generate_rust_signals(signals_enum, &qobject_idents)?);
}
generated.blocks.append(&mut generate_rust_signals(
&qobject.signals,
&qobject_idents,
)?);

// If this type is a singleton then we need to add an include
if let Some(qml_metadata) = &qobject.qml_metadata {
Expand Down
88 changes: 25 additions & 63 deletions crates/cxx-qt-gen/src/generator/rust/signals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,64 +11,51 @@ use crate::{
types::is_unsafe_cxx_type,
},
},
parser::signals::ParsedSignalsEnum,
parser::signals::ParsedSignal,
};
use proc_macro2::TokenStream;
use quote::quote;
use syn::{Ident, Result, Type};
use syn::Result;

pub fn generate_rust_signals(
signals_enum: &ParsedSignalsEnum,
signals: &Vec<ParsedSignal>,
qobject_idents: &QObjectName,
) -> Result<GeneratedRustQObjectBlocks> {
let mut generated = GeneratedRustQObjectBlocks::default();
let cpp_class_name_rust = &qobject_idents.cpp_class.rust;
let signal_enum_ident = &signals_enum.ident;
let mut signal_matches = vec![];

// Add the original enum into the implementation
generated
.cxx_qt_mod_contents
.push(syn::Item::Enum(signals_enum.item.clone()));
let qobject_name = &qobject_idents.cpp_class.rust;

// Create the methods for the other signals
for signal in &signals_enum.signals {
for signal in signals {
let idents = QSignalName::from(signal);
let signal_ident_rust = idents.enum_name;
let signal_ident_cpp_str = idents.name.cpp.to_string();
let emit_ident_cpp = &idents.emit_name.cpp;
let emit_ident_rust = &idents.emit_name.rust;
let emit_ident_rust_str = idents.emit_name.rust.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 mut parameters = signal
let parameters = signal
.parameters
.iter()
.map(|parameter| {
let ident = &parameter.ident;
let mut ty = parameter.ty.clone();
// Remove any lifetime from the signal, as this will be related
// to the enum. For the CXX methods these can just be
// normal references with inferred lifetimes.
if let Type::Reference(ty) = &mut ty {
ty.lifetime = None;
}
let ty = &parameter.ty;
quote! { #ident: #ty }
})
.collect::<Vec<TokenStream>>();
let parameter_signatures = if signal.parameters.is_empty() {
quote! { self: Pin<&mut #cpp_class_name_rust> }

let self_type = if signal.mutable {
quote! { Pin<&mut #qobject_name> }
} else {
quote! { self: Pin<&mut #cpp_class_name_rust>, #(#parameters),* }
quote! { &#qobject_name }
};
let parameter_names = signal
.parameters
.iter()
.map(|parameter| parameter.ident.clone())
.collect::<Vec<Ident>>();

let mut unsafe_block = None;
let mut unsafe_call = Some(quote! { unsafe });
if signal.safe {
std::mem::swap(&mut unsafe_call, &mut unsafe_block);
}

// Determine if unsafe is required due to an unsafe parameter
let has_unsafe = if signal
Expand All @@ -81,21 +68,16 @@ pub fn generate_rust_signals(
quote! {}
};

// Add the self context to parameters as this is used for the connection function pointer
parameters.insert(
0,
quote! {
Pin<&mut #cpp_class_name_rust>
},
);
let attrs = &signal.method.attrs;

let fragment = RustFragmentPair {
cxx_bridge: vec![
// TODO: this will not call our wrapper in the future
quote! {
unsafe extern "C++" {
#[doc(hidden)]
#unsafe_block extern "C++" {
#(#attrs)*
#[rust_name = #emit_ident_rust_str]
#has_unsafe fn #emit_ident_cpp(#parameter_signatures);
#unsafe_call fn #emit_ident_cpp(self: #self_type, #(#parameters),*);
}
},
quote! {
Expand All @@ -105,30 +87,27 @@ pub fn generate_rust_signals(
#[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: Pin<&mut #cpp_class_name_rust>, func: #has_unsafe fn(#(#parameters),*), conn_type: CxxQtConnectionType) -> CxxQtQMetaObjectConnection;
fn #connect_ident_cpp(self: #self_type, func: #has_unsafe fn(#self_type, #(#parameters),*), conn_type: CxxQtConnectionType) -> CxxQtQMetaObjectConnection;
}
},
],
// Note we do not need the #has_unsafe here as this only needs to be in the CXX bridge
// otherwise the function pointer itself needs to be unsafe
implementation: vec![quote! {
impl #cpp_class_name_rust {
impl #qobject_name {
#[doc = "Connect the given function pointer to the signal "]
#[doc = #signal_ident_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]
fn #on_ident_rust(self: Pin<&mut #cpp_class_name_rust>, func: fn(#(#parameters),*)) -> CxxQtQMetaObjectConnection
fn #on_ident_rust(self: #self_type, func: fn(#self_type, #(#parameters),*)) -> CxxQtQMetaObjectConnection
{
self.#connect_ident_rust(func, CxxQtConnectionType::AutoConnection)
}
}
}],
};
signal_matches.push(quote! {
#signal_enum_ident::#signal_ident_rust { #(#parameter_names),* } => #has_unsafe { self.#emit_ident_rust(#(#parameter_names),*) }
});

generated
.cxx_mod_contents
Expand All @@ -138,23 +117,6 @@ pub fn generate_rust_signals(
.append(&mut fragment.implementation_as_items()?);
}

// Add the Rust method using the enum to call the methods
let qobject_ident_str = qobject_idents.rust_struct.rust.to_string();
let signal_enum_ident_str = signal_enum_ident.to_string();
generated.cxx_qt_mod_contents.push(syn::parse2(quote! {
impl #cpp_class_name_rust {
#[doc = "Emit the signal from the enum "]
#[doc = #signal_enum_ident_str]
#[doc = " on the QObject "]
#[doc = #qobject_ident_str]
pub fn emit(self: Pin<&mut Self>, signal: #signal_enum_ident) {
match signal {
#(#signal_matches),*
}
}
}
})?);

Ok(generated)
}

Expand Down
Loading

0 comments on commit 43d5a48

Please sign in to comment.