Skip to content

Add attribute which generates wrappers for a function #1278

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 56 additions & 20 deletions crates/cxx-qt-gen/src/generator/rust/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand All @@ -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<_>>();

Expand All @@ -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![
Expand All @@ -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(
Expand Down Expand Up @@ -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],
Expand All @@ -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)
}
},
);
}
}
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 @@ -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 = &parameter.ident;
quote! { #ident }
})
.collect::<Vec<TokenStream>>();
quote! { #(#parameters),* }
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
7 changes: 6 additions & 1 deletion crates/cxx-qt-gen/src/parser/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -66,14 +68,15 @@ pub struct ParsedMethod {
}

impl ParsedMethod {
const ALLOWED_ATTRS: [&'static str; 9] = [
const ALLOWED_ATTRS: [&'static str; 10] = [
"cxx_name",
"rust_name",
"qinvokable",
"cxx_final",
"cxx_override",
"cxx_virtual",
"cxx_pure",
"auto_wrap",
"doc",
"cfg",
];
Expand Down Expand Up @@ -125,13 +128,15 @@ 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 {
method_fields: fields,
specifiers,
is_qinvokable,
is_pure,
wrap,
cfgs,
unsafe_block,
})
Expand Down
3 changes: 2 additions & 1 deletion crates/cxx-qt-gen/test_inputs/passthrough_and_naming.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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" {
Expand Down
2 changes: 1 addition & 1 deletion crates/cxx-qt-gen/test_outputs/passthrough_and_naming.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};
Expand Down
5 changes: 4 additions & 1 deletion crates/cxx-qt-gen/test_outputs/passthrough_and_naming.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down Expand Up @@ -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"]
Expand Down
Loading