diff --git a/crates/cxx-qt-gen/src/generator/rust/method.rs b/crates/cxx-qt-gen/src/generator/rust/method.rs index c251f16a8..559c67d5c 100644 --- a/crates/cxx-qt-gen/src/generator/rust/method.rs +++ b/crates/cxx-qt-gen/src/generator/rust/method.rs @@ -3,7 +3,7 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 -use crate::generator::rust::get_params_tokens; +use crate::generator::rust::{get_call_params_tokens, get_params_tokens}; use crate::{ generator::{naming::qobject::QObjectNames, rust::fragment::GeneratedRustFragment}, parser::method::ParsedMethod, @@ -31,6 +31,8 @@ pub fn generate_rust_methods( cpp_class_name_rust, ); + let call_parameters = get_call_params_tokens(&invokable.parameters); + let return_type = &invokable.method.sig.output; let cfgs = &invokable.cfgs; @@ -54,24 +56,38 @@ pub fn generate_rust_methods( Some(quote! { unsafe }) }; - GeneratedRustFragment::from_cxx_item(parse_quote_spanned! { - invokable.method.span() => - // Note: extern "Rust" block does not need to be unsafe - #block_safety extern #block_type { - // Note that we are exposing a Rust method on the C++ type to C++ - // - // CXX ends up generating the source, then we generate the matching header. - #[cxx_name = #invokable_ident_cpp] - // Needed for QObjects to have a namespace on their type or extern block - // - // A Namespace from cxx_qt::bridge would be automatically applied to all children - // but to apply it to only certain types, it is needed here too - #cxx_namespace - #(#cfgs)* - #[doc(hidden)] - #unsafe_call fn #invokable_ident_rust(#parameter_signatures) #return_type; - } - }) + let wrapper_fn = if invokable.wrap { + vec![parse_quote_spanned! { + invokable.method.span() => + #unsafe_call fn #invokable_ident_rust(#parameter_signatures) #return_type { + self.rust().#invokable_ident_rust(#call_parameters) + } + }] + } else { + vec![] + }; + + GeneratedRustFragment { + cxx_mod_contents: vec![parse_quote_spanned! { + invokable.method.span() => + // Note: extern "Rust" block does not need to be unsafe + #block_safety extern #block_type { + // Note that we are exposing a Rust method on the C++ type to C++ + // + // CXX ends up generating the source, then we generate the matching header. + #[cxx_name = #invokable_ident_cpp] + // Needed for QObjects to have a namespace on their type or extern block + // + // A Namespace from cxx_qt::bridge would be automatically applied to all children + // but to apply it to only certain types, it is needed here too + #cxx_namespace + #(#cfgs)* + #[doc(hidden)] + #unsafe_call fn #invokable_ident_rust(#parameter_signatures) #return_type; + } + }], + cxx_qt_mod_contents: wrapper_fn, + } }) .collect::>(); @@ -98,10 +114,12 @@ mod tests { }; let method3: ForeignItemFn = parse_quote! { #[cxx_name = "opaqueInvokable"] + #[auto_wrap] fn opaque_invokable(self: Pin<&mut MyObject>, param: &QColor) -> UniquePtr; }; let method4: ForeignItemFn = parse_quote! { #[cxx_name = "unsafeInvokable"] + #[auto_wrap] unsafe fn unsafe_invokable(self: &MyObject, param: *mut T) -> *mut T; }; let invokables = vec![ @@ -116,7 +134,7 @@ mod tests { generate_rust_methods(&invokables.iter().collect::>(), &qobject_names).unwrap(); assert_eq!(generated.cxx_mod_contents.len(), 4); - assert_eq!(generated.cxx_qt_mod_contents.len(), 0); + assert_eq!(generated.cxx_qt_mod_contents.len(), 2); // void_invokable assert_tokens_eq( @@ -154,6 +172,15 @@ mod tests { }, ); + assert_tokens_eq( + &generated.cxx_qt_mod_contents[0], + quote! { + fn opaque_invokable(self: Pin<&mut MyObject>, param: &QColor) -> UniquePtr { + self.rust().opaque_invokable(param) + } + }, + ); + // unsafe_invokable assert_tokens_eq( &generated.cxx_mod_contents[3], @@ -165,5 +192,14 @@ mod tests { } }, ); + + assert_tokens_eq( + &generated.cxx_qt_mod_contents[1], + quote! { + unsafe fn unsafe_invokable(self:&MyObject, param: *mut T) -> *mut T { + self.rust().unsafe_invokable(param) + } + }, + ); } } diff --git a/crates/cxx-qt-gen/src/generator/rust/mod.rs b/crates/cxx-qt-gen/src/generator/rust/mod.rs index dae4216bd..b1f69f570 100644 --- a/crates/cxx-qt-gen/src/generator/rust/mod.rs +++ b/crates/cxx-qt-gen/src/generator/rust/mod.rs @@ -146,6 +146,22 @@ pub fn get_params_tokens( } } +/// Return the [TokenStream] of the parsed parameters, which would be used to call the fn, for use in generation +pub fn get_call_params_tokens(parameters: &[ParsedFunctionParameter]) -> TokenStream { + if parameters.is_empty() { + quote! {} + } else { + let parameters = parameters + .iter() + .map(|parameter| { + let ident = ¶meter.ident; + quote! { #ident } + }) + .collect::>(); + quote! { #(#parameters),* } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/cxx-qt-gen/src/parser/method.rs b/crates/cxx-qt-gen/src/parser/method.rs index 7c161c4b8..0bd2cc101 100644 --- a/crates/cxx-qt-gen/src/parser/method.rs +++ b/crates/cxx-qt-gen/src/parser/method.rs @@ -57,6 +57,8 @@ pub struct ParsedMethod { pub is_qinvokable: bool, /// Whether the method is a pure virtual method pub is_pure: bool, + /// Whether to auto generate a wrapper for this method outside the bridge + pub wrap: bool, // No docs field since the docs should be on the method implementation outside the bridge // This means any docs on the bridge declaration would be ignored /// Cfgs for the method @@ -66,7 +68,7 @@ pub struct ParsedMethod { } impl ParsedMethod { - const ALLOWED_ATTRS: [&'static str; 9] = [ + const ALLOWED_ATTRS: [&'static str; 10] = [ "cxx_name", "rust_name", "qinvokable", @@ -74,6 +76,7 @@ impl ParsedMethod { "cxx_override", "cxx_virtual", "cxx_pure", + "auto_wrap", "doc", "cfg", ]; @@ -125,6 +128,7 @@ impl ParsedMethod { // Determine if the method is invokable let is_qinvokable = attrs.contains_key("qinvokable"); let is_pure = attrs.contains_key("cxx_pure"); + let wrap = attrs.contains_key("auto_wrap"); let specifiers = ParsedQInvokableSpecifiers::from_attrs(attrs); Ok(Self { @@ -132,6 +136,7 @@ impl ParsedMethod { specifiers, is_qinvokable, is_pure, + wrap, cfgs, unsafe_block, }) diff --git a/crates/cxx-qt-gen/test_inputs/passthrough_and_naming.rs b/crates/cxx-qt-gen/test_inputs/passthrough_and_naming.rs index cfa3bae78..77d2ec2a0 100644 --- a/crates/cxx-qt-gen/test_inputs/passthrough_and_naming.rs +++ b/crates/cxx-qt-gen/test_inputs/passthrough_and_naming.rs @@ -144,7 +144,8 @@ pub mod ffi { fn invokable_name(self: Pin<&mut SecondObject>); #[cxx_name = "myRenamedFunction"] - fn my_function(self: &SecondObject); + #[auto_wrap] + fn my_function(self: &SecondObject, param: i32); } extern "RustQt" { diff --git a/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.h b/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.h index 76dafdad2..a3cfd34be 100644 --- a/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.h +++ b/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.h @@ -165,7 +165,7 @@ class SecondObject Q_SLOT void setPropertyName(::std::int32_t value) noexcept; Q_SIGNAL void propertyNameChanged(); Q_INVOKABLE void invokableName() noexcept; - void myRenamedFunction() const noexcept; + void myRenamedFunction(::std::int32_t param) const noexcept; Q_SIGNAL void ready(); explicit SecondObject(QObject* parent = nullptr); }; 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 5e0f5b3a7..dcfcf37a5 100644 --- a/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.rs +++ b/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.rs @@ -263,7 +263,7 @@ pub mod ffi { #[cxx_name = "myRenamedFunction"] #[namespace = "second_object"] #[doc(hidden)] - unsafe fn my_function(self: &SecondObject); + unsafe fn my_function(self: &SecondObject, param: i32); } unsafe extern "C++" { #[cxx_name = "ready"] @@ -780,6 +780,9 @@ cxx_qt::static_assertions::assert_eq_size!( cxx_qt::signalhandler::CxxQtSignalHandler, [usize; 2] ); +unsafe fn my_function(self: &SecondObject, param: i32) { + self.rust().my_function(param) +} impl ffi::SecondObject { #[doc = "Connect the given function pointer to the signal "] #[doc = "ready"]