diff --git a/book/src/bridge/attributes.md b/book/src/bridge/attributes.md index 4063c826a..72e72af58 100644 --- a/book/src/bridge/attributes.md +++ b/book/src/bridge/attributes.md @@ -43,3 +43,8 @@ For [`#[qproperty]`](./extern_rustqt.md#properties), a CXX or Rust name can be p The `#[auto_cxx_name]` and `#[auto_rust_name]` attributes can be used to automatically rename cxx and rust names. These are placed at a block level on `extern "RustQt"` or `extern "C++Qt"` blocks, and will automatically case convert the items inside, unless they specify either a `rust_name` or `cxx_name`. By default `#[auto_cxx_name]` will generate a camelCase conversion for`cxx_name` and `#[auto_rust_name]` will generate a snake_case conversion for `rust_name`. + +### Automatic wrapping + +A fairly common operation is calling a method on the inner rust type, via the `.rust()` accessor. This can be simplified +with the `#[auto_wrap]` attribute. This will generate a wrapper for your function which accesses the rust method of that name. 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::<Vec<_>>(); @@ -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<QColor>; }; 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::<Vec<_>>(), &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<QColor> { + 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 acd94e05d..301503768 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::<Vec<TokenStream>>(); + 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 89c708da5..3dcf2b28f 100644 --- a/crates/cxx-qt-gen/src/parser/method.rs +++ b/crates/cxx-qt-gen/src/parser/method.rs @@ -59,6 +59,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 @@ -68,7 +70,7 @@ pub struct ParsedMethod { } impl ParsedMethod { - const ALLOWED_ATTRS: [&'static str; 9] = [ + const ALLOWED_ATTRS: [&'static str; 10] = [ "cxx_name", "rust_name", "qinvokable", @@ -76,6 +78,7 @@ impl ParsedMethod { "cxx_override", "cxx_virtual", "cxx_pure", + "auto_wrap", "doc", "cfg", ]; @@ -127,6 +130,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 { @@ -134,6 +138,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 bb4fa2c2e..fb0dd1108 100644 --- a/crates/cxx-qt-gen/test_inputs/passthrough_and_naming.rs +++ b/crates/cxx-qt-gen/test_inputs/passthrough_and_naming.rs @@ -142,7 +142,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<SecondObjectCxxQtSignalClosurepropertyNameChanged>, [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"]