Skip to content

Commit

Permalink
reimplementation of KDAB#667
Browse files Browse the repository at this point in the history
  • Loading branch information
knoxfighter committed Apr 4, 2024
1 parent c2320fc commit 2684a32
Show file tree
Hide file tree
Showing 10 changed files with 174 additions and 160 deletions.
102 changes: 89 additions & 13 deletions crates/cxx-qt-gen/src/generator/naming/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,114 @@

use convert_case::{Case, Casing};
use quote::format_ident;
use syn::Ident;
use syn::{Attribute, Ident};

use crate::syntax::attribute::attribute_find_path;
use crate::syntax::expr::expr_to_string;

use super::CombinedIdent;

impl CombinedIdent {
/// Generate a CombinedIdent from a rust function name.
/// C++ will use the CamelCase version of the function name.
pub fn from_rust_function(ident: Ident) -> Self {
Self {
pub fn from_rust_function(attrs: &[Attribute], ident: &Ident) -> Self {
// set default cpp name, based of convention in #828
// https://github.com/KDAB/cxx-qt/issues/828#issuecomment-1920288043
// println!("Custom backtrace: {}", Backtrace::force_capture());

// rust:
// - rust_name attribute
// - if cxx_name exists: as_is
// - snake_case
// c++:
// - cxx_name attribute
// - if rust_name exists: as_is
// - camelCase
// in theory differentiate between extern C++Qt and extern RustQt
// TODO: different defaults for C++Qt and RustQt

let mut res = Self {
cpp: format_ident!("{}", ident.to_string().to_case(Case::Camel)),
rust: ident,
rust: format_ident!("{}", ident.to_string().to_case(Case::Snake)),
};

let mut cxx_name_found = false;

// Find any cxx_name
if let Some(index) = attribute_find_path(attrs, &["cxx_name"]) {
if let Ok(name_value) = &attrs[index].meta.require_name_value() {
if let Ok(value_str) = expr_to_string(&name_value.value) {
cxx_name_found = true;
res.cpp = format_ident!("{value_str}");
res.rust = ident.clone();
}
}
}

// Find any rust_name
if let Some(index) = attribute_find_path(attrs, &["rust_name"]) {
if let Ok(name_value) = &attrs[index].meta.require_name_value() {
if let Ok(value_str) = expr_to_string(&name_value.value) {
res.rust = format_ident!("{value_str}");
if !cxx_name_found {
res.cpp = ident.clone();
}
}
}
}

res
}
}

#[cfg(test)]
mod tests {
use syn::{ForeignItemFn, parse_quote};

use super::*;

#[test]
fn test_from_rust_function_camel_case_conversion() {
let ident = format_ident!("test_function");
let combined = CombinedIdent::from_rust_function(ident.clone());
fn test_from_rust_function() {
let method: ForeignItemFn = parse_quote! {
extern "C++Qt"
fn Test_Function();
};
let combined = CombinedIdent::from_rust_function(&method.attrs, &method.sig.ident);
assert_eq!(combined.cpp, format_ident!("testFunction"));
assert_eq!(combined.rust, ident);
assert_eq!(combined.rust, format_ident!("test_function"));
}

#[test]
fn test_from_rust_function_cxx_name() {
let method: ForeignItemFn = parse_quote! {
#[cxx_name = "TestFunction"]
fn Test_Function();
};
let combined = CombinedIdent::from_rust_function(&method.attrs, &method.sig.ident);
assert_eq!(combined.cpp, format_ident!("TestFunction"));
assert_eq!(combined.rust, format_ident!("Test_Function"));
}

#[test]
fn test_from_rust_function_rust_name() {
let method: ForeignItemFn = parse_quote! {
#[rust_name = "Test_Function"]
fn TestFunction();
};
let combined = CombinedIdent::from_rust_function(&method.attrs, &method.sig.ident);
assert_eq!(combined.cpp, format_ident!("TestFunction"));
assert_eq!(combined.rust, format_ident!("Test_Function"));
}

#[test]
fn test_from_rust_function_single_word() {
let ident = format_ident!("test");
let combined = CombinedIdent::from_rust_function(ident.clone());
assert_eq!(combined.cpp, ident);
assert_eq!(combined.rust, ident);
fn test_from_rust_function_both() {
let method: ForeignItemFn = parse_quote! {
#[cxx_name = "TestFunction"]
#[rust_name = "Test_Function"]
fn test_function();
};
let combined = CombinedIdent::from_rust_function(&method.attrs, &method.sig.ident);
assert_eq!(combined.cpp, format_ident!("TestFunction"));
assert_eq!(combined.rust, format_ident!("Test_Function"));
}
}
24 changes: 11 additions & 13 deletions crates/cxx-qt-gen/src/generator/naming/method.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use quote::format_ident;
use syn::ForeignItemFn;

// SPDX-FileCopyrightText: 2022 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::naming::CombinedIdent, parser::method::ParsedMethod};
use convert_case::{Case, Casing};
use quote::format_ident;
use syn::{ForeignItemFn, Ident};

/// Names for parts of a method (which could be a Q_INVOKABLE)
pub struct QMethodName {
Expand All @@ -22,32 +22,30 @@ impl From<&ParsedMethod> for QMethodName {
impl From<&ForeignItemFn> for QMethodName {
fn from(method: &ForeignItemFn) -> Self {
let ident = &method.sig.ident;
Self {
name: CombinedIdent::from_rust_function(ident.clone()),
wrapper: CombinedIdent::wrapper_from_invokable(ident),
}
let name = CombinedIdent::from_rust_function(&method.attrs, &ident.clone());
let wrapper = CombinedIdent::wrapper_from_invokable(&name);
Self { name, wrapper}
}
}

impl CombinedIdent {
/// For a given ident generate the Rust and C++ wrapper names
fn wrapper_from_invokable(ident: &Ident) -> Self {
let ident = format_ident!("{ident}_wrapper");
fn wrapper_from_invokable(ident: &CombinedIdent) -> Self {
Self {
cpp: format_ident!("{}", ident.to_string().to_case(Case::Camel)),
rust: ident,
cpp: format_ident!("{}Wrapper", ident.cpp),
rust: format_ident!("{}_wrapper", ident.rust),
}
}
}

#[cfg(test)]
mod tests {
use std::collections::HashSet;

use syn::parse_quote;

use super::*;

use std::collections::HashSet;

#[test]
fn test_from_impl_method() {
let parsed = ParsedMethod {
Expand Down
18 changes: 9 additions & 9 deletions crates/cxx-qt-gen/src/generator/naming/signals.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use quote::format_ident;
use syn::{Ident, Result};

use crate::{generator::naming::CombinedIdent, naming::TypeNames};
// SPDX-FileCopyrightText: 2022 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::parser::signals::ParsedSignal;
use crate::{generator::naming::CombinedIdent, naming::TypeNames};
use convert_case::{Case, Casing};
use quote::format_ident;
use syn::{Ident, Result};

/// Names for parts of a Q_SIGNAL
pub struct QSignalName {
Expand All @@ -26,16 +26,16 @@ impl From<&ParsedSignal> for QSignalName {
}

fn on_from_signal(ident: &Ident) -> Ident {
format_ident!("on_{}", ident.to_string().to_case(Case::Snake))
format_ident!("on_{}", ident.to_string())
}

impl CombinedIdent {
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", ident.cpp.to_string().to_case(Case::Camel)),
rust: format_ident!("connect_{}", ident.rust.to_string().to_case(Case::Snake)),
cpp: format_ident!("{}Connect", ident.cpp.to_string()),
rust: format_ident!("connect_{}", ident.rust.to_string()),
}
}
}
Expand Down Expand Up @@ -100,10 +100,10 @@ impl QSignalHelperName {

#[cfg(test)]
mod tests {
use super::*;

use syn::parse_quote;

use super::*;

#[test]
fn test_parsed_signal() {
let qsignal = ParsedSignal {
Expand Down
54 changes: 22 additions & 32 deletions crates/cxx-qt-gen/src/generator/rust/inherit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,54 +3,43 @@

// SPDX-License-Identifier: MIT OR Apache-2.0

use quote::{quote, quote_spanned};
use syn::{Item, Result, spanned::Spanned};

use crate::{
generator::{naming::qobject::QObjectName, rust::fragment::GeneratedRustFragment},
generator::rust::fragment::GeneratedRustFragment,
parser::inherit::ParsedInheritedMethod,
};
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned};
use syn::{spanned::Spanned, Item, Result};
use crate::syntax::attribute::attribute_take_path;

pub fn generate(
qobject_ident: &QObjectName,
methods: &[ParsedInheritedMethod],
) -> Result<GeneratedRustFragment> {
let mut blocks = GeneratedRustFragment::default();
let qobject_name = &qobject_ident.cpp_class.rust;

let mut bridges = methods
.iter()
.map(|method| {
let parameters = method
.parameters
.iter()
.map(|parameter| {
let ident = &parameter.ident;
let ty = &parameter.ty;
quote! { #ident: #ty }
})
.collect::<Vec<TokenStream>>();
let ident = &method.method.sig.ident;
let cxx_name_string = &method.wrapper_ident().to_string();
let self_param = if method.mutable {
quote! { self: Pin<&mut #qobject_name> }
} else {
quote! { self: &#qobject_name }
let wrapper_ident_str = method.wrapper_ident().to_string();

// Remove any cxx_name attribute on the original method
// As we need it to use the wrapper ident
let original_method = {
let mut original_method = method.method.clone();
attribute_take_path(&mut original_method.attrs, &["cxx_name"]);
original_method
};
let return_type = &method.method.sig.output;

let mut unsafe_block = None;
let mut unsafe_call = Some(quote! { unsafe });
if method.safe {
std::mem::swap(&mut unsafe_call, &mut unsafe_block);
}
let attrs = &method.method.attrs;
syn::parse2(quote_spanned! {
method.method.span() =>
#unsafe_block extern "C++" {
#(#attrs)*
#[cxx_name = #cxx_name_string]
#unsafe_call fn #ident(#self_param, #(#parameters),*) #return_type;
#[cxx_name = #wrapper_ident_str]
#original_method
}
})
})
Expand All @@ -62,19 +51,21 @@ pub fn generate(

#[cfg(test)]
mod tests {
use super::*;
use syn::{ForeignItemFn, parse_quote};

use crate::{
generator::naming::qobject::tests::create_qobjectname, syntax::safety::Safety,
syntax::safety::Safety,
tests::assert_tokens_eq,
};
use syn::{parse_quote, ForeignItemFn};

use super::*;

fn generate_from_foreign(
method: ForeignItemFn,
safety: Safety,
) -> Result<GeneratedRustFragment> {
let inherited_methods = vec![ParsedInheritedMethod::parse(method, safety).unwrap()];
generate(&create_qobjectname(), &inherited_methods)
generate(&inherited_methods)
}

#[test]
Expand Down Expand Up @@ -140,11 +131,10 @@ mod tests {

assert_tokens_eq(
&generated.cxx_mod_contents[0],
// TODO: Maybe remove the trailing comma after self?
quote! {
extern "C++" {
#[cxx_name = "testCxxQtInherit"]
unsafe fn test(self: &MyObject,);
unsafe fn test(self: &MyObject);
}
},
);
Expand Down
Loading

0 comments on commit 2684a32

Please sign in to comment.