Skip to content

Commit ef8a3c9

Browse files
Add attribute which generates wrappers for a function (#1278)
* Add attribute which generates wrappers for a function * Fix qualification of function names, and adds testing. - Updates tests - Adds necessary import to impl block - Extract wrapper generation to function - Update book
1 parent 0fba618 commit ef8a3c9

File tree

10 files changed

+223
-40
lines changed

10 files changed

+223
-40
lines changed

book/src/bridge/attributes.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,8 @@ For [`#[qproperty]`](./extern_rustqt.md#properties), a CXX or Rust name can be p
4343
The `#[auto_cxx_name]` and `#[auto_rust_name]` attributes can be used to automatically rename cxx and rust names.
4444
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`.
4545
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`.
46+
47+
### Automatic wrapping
48+
49+
A fairly common operation is calling a method on the inner rust type, via the `.rust()` accessor. This can be simplified
50+
with the `#[auto_wrap]` attribute. This will generate a wrapper for your function which accesses the rust method of that name.

crates/cxx-qt-gen/src/generator/naming/qobject.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,14 @@ pub mod tests {
112112
QObjectNames::from_qobject(&create_parsed_qobject(), &TypeNames::mock()).unwrap()
113113
}
114114

115+
/// Used for opaqure objects mocking, adding the QColor type
116+
pub fn create_qobjectname_with_qcolor() -> QObjectNames {
117+
let mut type_names = TypeNames::mock();
118+
type_names.mock_insert("QColor", Some(format_ident!("qobject")), None, None);
119+
120+
QObjectNames::from_qobject(&create_parsed_qobject(), &type_names).unwrap()
121+
}
122+
115123
#[test]
116124
fn test_parsed_property() {
117125
let names =

crates/cxx-qt-gen/src/generator/rust/method.rs

Lines changed: 133 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,22 @@
33
//
44
// SPDX-License-Identifier: MIT OR Apache-2.0
55

6-
use crate::generator::rust::get_params_tokens;
6+
use crate::generator::rust::{
7+
get_call_params_tokens, get_params_tokens, get_params_tokens_qualified,
8+
};
9+
use crate::naming::rust::syn_type_cxx_bridge_to_qualified;
10+
use crate::naming::TypeNames;
711
use crate::{
812
generator::{naming::qobject::QObjectNames, rust::fragment::GeneratedRustFragment},
913
parser::method::ParsedMethod,
1014
};
1115
use quote::quote;
12-
use syn::{parse_quote_spanned, spanned::Spanned, Result};
16+
use syn::{parse_quote_spanned, spanned::Spanned, Item, Result, ReturnType};
1317

1418
pub fn generate_rust_methods(
1519
invokables: &[&ParsedMethod],
1620
qobject_names: &QObjectNames,
21+
type_names: &TypeNames,
1722
) -> Result<GeneratedRustFragment> {
1823
let cpp_class_name_rust = &qobject_names.name.rust_unqualified();
1924

@@ -34,6 +39,7 @@ pub fn generate_rust_methods(
3439
let return_type = &invokable.method.sig.output;
3540

3641
let cfgs = &invokable.cfgs;
42+
3743
let cxx_namespace = qobject_names.namespace_tokens();
3844

3945
let (block_type, block_safety) = if invokable.is_pure {
@@ -54,35 +60,101 @@ pub fn generate_rust_methods(
5460
Some(quote! { unsafe })
5561
};
5662

57-
GeneratedRustFragment::from_cxx_item(parse_quote_spanned! {
58-
invokable.method.span() =>
59-
// Note: extern "Rust" block does not need to be unsafe
60-
#block_safety extern #block_type {
61-
// Note that we are exposing a Rust method on the C++ type to C++
62-
//
63-
// CXX ends up generating the source, then we generate the matching header.
64-
#[cxx_name = #invokable_ident_cpp]
65-
// Needed for QObjects to have a namespace on their type or extern block
66-
//
67-
// A Namespace from cxx_qt::bridge would be automatically applied to all children
68-
// but to apply it to only certain types, it is needed here too
69-
#cxx_namespace
70-
#(#cfgs)*
71-
#[doc(hidden)]
72-
#unsafe_call fn #invokable_ident_rust(#parameter_signatures) #return_type;
73-
}
63+
let wrapper_fn = if invokable.wrap {
64+
vec![generate_auto_wrap_fn(type_names, qobject_names, invokable)?]
65+
} else {
66+
vec![]
67+
};
68+
69+
Ok(GeneratedRustFragment {
70+
cxx_mod_contents: vec![parse_quote_spanned! {
71+
invokable.method.span() =>
72+
// Note: extern "Rust" block does not need to be unsafe
73+
#block_safety extern #block_type {
74+
// Note that we are exposing a Rust method on the C++ type to C++
75+
//
76+
// CXX ends up generating the source, then we generate the matching header.
77+
#[cxx_name = #invokable_ident_cpp]
78+
// Needed for QObjects to have a namespace on their type or extern block
79+
//
80+
// A Namespace from cxx_qt::bridge would be automatically applied to all children
81+
// but to apply it to only certain types, it is needed here too
82+
#cxx_namespace
83+
#(#cfgs)*
84+
#[doc(hidden)]
85+
#unsafe_call fn #invokable_ident_rust(#parameter_signatures) #return_type;
86+
}
87+
}],
88+
cxx_qt_mod_contents: wrapper_fn,
7489
})
7590
})
76-
.collect::<Vec<_>>();
91+
.collect::<Result<Vec<_>>>()?;
7792

7893
Ok(GeneratedRustFragment::flatten(generated))
7994
}
8095

96+
pub fn generate_auto_wrap_fn(
97+
type_names: &TypeNames,
98+
qobject_names: &QObjectNames,
99+
invokable: &ParsedMethod,
100+
) -> Result<Item> {
101+
let docs = &invokable.docs;
102+
let cfgs = &invokable.cfgs;
103+
104+
let qualified_impl = type_names.rust_qualified(&invokable.qobject_ident)?;
105+
106+
let invokable_ident_rust = invokable.name.rust_unqualified();
107+
108+
let inner_fn = if invokable.mutable {
109+
quote! { rust_mut() }
110+
} else {
111+
quote! { rust() }
112+
};
113+
114+
let qualified_return_type = match &invokable.method.sig.output {
115+
ReturnType::Default => ReturnType::Default,
116+
ReturnType::Type(arrow, boxed_type) => {
117+
let ty = boxed_type.as_ref();
118+
let qualified_type = syn_type_cxx_bridge_to_qualified(ty, type_names)?;
119+
ReturnType::Type(*arrow, Box::new(qualified_type))
120+
}
121+
};
122+
123+
let parameter_signatures_qualified = get_params_tokens_qualified(
124+
invokable.mutable,
125+
&invokable.parameters,
126+
&qobject_names.name.rust_qualified(),
127+
type_names,
128+
)?;
129+
130+
let call_parameters = get_call_params_tokens(&invokable.parameters);
131+
132+
let method_safety = if invokable.safe {
133+
None
134+
} else {
135+
Some(quote! { unsafe })
136+
};
137+
138+
Ok(parse_quote_spanned! {
139+
invokable.method.span() =>
140+
#(#cfgs)*
141+
impl #qualified_impl {
142+
#(#docs)*
143+
pub #method_safety fn #invokable_ident_rust(#parameter_signatures_qualified) #qualified_return_type {
144+
use ::cxx_qt::CxxQtType;
145+
146+
self.#inner_fn.#invokable_ident_rust(#call_parameters)
147+
}
148+
}
149+
})
150+
}
151+
81152
#[cfg(test)]
82153
mod tests {
83154
use super::*;
155+
use quote::format_ident;
84156

85-
use crate::generator::naming::qobject::tests::create_qobjectname;
157+
use crate::generator::naming::qobject::tests::create_qobjectname_with_qcolor;
86158
use crate::tests::assert_tokens_eq;
87159
use syn::{parse_quote, ForeignItemFn};
88160

@@ -98,10 +170,12 @@ mod tests {
98170
};
99171
let method3: ForeignItemFn = parse_quote! {
100172
#[cxx_name = "opaqueInvokable"]
173+
#[auto_wrap]
101174
fn opaque_invokable(self: Pin<&mut MyObject>, param: &QColor) -> UniquePtr<QColor>;
102175
};
103176
let method4: ForeignItemFn = parse_quote! {
104177
#[cxx_name = "unsafeInvokable"]
178+
#[auto_wrap]
105179
unsafe fn unsafe_invokable(self: &MyObject, param: *mut T) -> *mut T;
106180
};
107181
let invokables = vec![
@@ -110,13 +184,21 @@ mod tests {
110184
ParsedMethod::mock_qinvokable(&method3).make_mutable(),
111185
ParsedMethod::mock_qinvokable(&method4).make_unsafe(),
112186
];
113-
let qobject_names = create_qobjectname();
187+
let qobject_names = create_qobjectname_with_qcolor();
188+
189+
let mut type_names = TypeNames::mock();
190+
type_names.mock_insert("QColor", Some(format_ident!("qobject")), None, None);
191+
type_names.mock_insert("T", Some(format_ident!("qobject")), None, None);
114192

115-
let generated =
116-
generate_rust_methods(&invokables.iter().collect::<Vec<_>>(), &qobject_names).unwrap();
193+
let generated = generate_rust_methods(
194+
&invokables.iter().collect::<Vec<_>>(),
195+
&qobject_names,
196+
&type_names,
197+
)
198+
.unwrap();
117199

118200
assert_eq!(generated.cxx_mod_contents.len(), 4);
119-
assert_eq!(generated.cxx_qt_mod_contents.len(), 0);
201+
assert_eq!(generated.cxx_qt_mod_contents.len(), 2);
120202

121203
// void_invokable
122204
assert_tokens_eq(
@@ -154,6 +236,19 @@ mod tests {
154236
},
155237
);
156238

239+
assert_tokens_eq(
240+
&generated.cxx_qt_mod_contents[0],
241+
quote! {
242+
impl qobject::MyObject {
243+
pub fn opaque_invokable(self: Pin<&mut qobject::MyObject>, param: &qobject::QColor) -> cxx::UniquePtr<qobject::QColor> {
244+
use ::cxx_qt::CxxQtType;
245+
246+
self.rust_mut().opaque_invokable(param)
247+
}
248+
}
249+
},
250+
);
251+
157252
// unsafe_invokable
158253
assert_tokens_eq(
159254
&generated.cxx_mod_contents[3],
@@ -165,5 +260,18 @@ mod tests {
165260
}
166261
},
167262
);
263+
264+
assert_tokens_eq(
265+
&generated.cxx_qt_mod_contents[1],
266+
quote! {
267+
impl qobject::MyObject {
268+
pub unsafe fn unsafe_invokable(self:&qobject::MyObject, param: *mut qobject::T) -> *mut qobject::T {
269+
use ::cxx_qt::CxxQtType;
270+
271+
self.rust().unsafe_invokable(param)
272+
}
273+
}
274+
},
275+
);
168276
}
169277
}

crates/cxx-qt-gen/src/generator/rust/mod.rs

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@ pub mod signals;
1616
pub mod threading;
1717

1818
use crate::generator::{rust::fragment::GeneratedRustFragment, structuring};
19+
use crate::naming::rust::syn_type_cxx_bridge_to_qualified;
20+
use crate::naming::TypeNames;
1921
use crate::parser::cxxqtdata::ParsedCxxQtData;
2022
use crate::parser::{parameter::ParsedFunctionParameter, Parser};
2123
use proc_macro2::{Ident, TokenStream};
2224
use quote::quote;
23-
use syn::{parse_quote, ItemMod, Result};
25+
use syn::{parse_quote, ItemMod, Path, Result};
2426

2527
/// Representation of the generated Rust code for a QObject
2628
pub struct GeneratedRustBlocks {
@@ -148,6 +150,49 @@ pub fn get_params_tokens(
148150
}
149151
}
150152

153+
/// Return the [TokenStream] of the parsed parameters, with self type qualified for use in generation
154+
pub fn get_params_tokens_qualified(
155+
mutable: bool,
156+
parameters: &[ParsedFunctionParameter],
157+
class_name_path: &Path,
158+
type_names: &TypeNames,
159+
) -> Result<TokenStream> {
160+
let struct_sig = if mutable {
161+
quote! { Pin<&mut #class_name_path> }
162+
} else {
163+
quote! { &#class_name_path }
164+
};
165+
if parameters.is_empty() {
166+
Ok(quote! { self: #struct_sig })
167+
} else {
168+
let parameters = parameters
169+
.iter()
170+
.map(|parameter| {
171+
let ident = &parameter.ident;
172+
let qualified_ty = &syn_type_cxx_bridge_to_qualified(&parameter.ty, type_names)?;
173+
Ok(quote! { #ident: #qualified_ty })
174+
})
175+
.collect::<Result<Vec<TokenStream>>>()?;
176+
Ok(quote! { self: #struct_sig, #(#parameters),* })
177+
}
178+
}
179+
180+
/// Return the [TokenStream] of the parsed parameters, which would be used to call the fn, for use in generation
181+
pub fn get_call_params_tokens(parameters: &[ParsedFunctionParameter]) -> TokenStream {
182+
if parameters.is_empty() {
183+
quote! {}
184+
} else {
185+
let parameters = parameters
186+
.iter()
187+
.map(|parameter| {
188+
let ident = &parameter.ident;
189+
quote! { #ident }
190+
})
191+
.collect::<Vec<TokenStream>>();
192+
quote! { #(#parameters),* }
193+
}
194+
}
195+
151196
#[cfg(test)]
152197
mod tests {
153198
use super::*;

crates/cxx-qt-gen/src/generator/rust/qobject.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ impl GeneratedRustFragment {
3535
type_names,
3636
structured_qobject,
3737
)?,
38-
generate_rust_methods(&structured_qobject.methods, &qobject_names)?,
38+
generate_rust_methods(&structured_qobject.methods, &qobject_names, type_names)?,
3939
inherit::generate(&qobject_names, &structured_qobject.inherited_methods)?,
4040
generate_rust_signals(&structured_qobject.signals, &qobject_names, type_names)?,
4141
];

crates/cxx-qt-gen/src/parser/method.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// SPDX-FileContributor: Andrew Hayzen <andrew.hayzen@kdab.com>
33
//
44
// SPDX-License-Identifier: MIT OR Apache-2.0
5-
use crate::parser::CaseConversion;
5+
use crate::parser::{extract_docs, CaseConversion};
66
use crate::{
77
naming::Name,
88
parser::{extract_cfgs, parameter::ParsedFunctionParameter, require_attributes},
@@ -59,23 +59,26 @@ pub struct ParsedMethod {
5959
pub is_qinvokable: bool,
6060
/// Whether the method is a pure virtual method
6161
pub is_pure: bool,
62-
// No docs field since the docs should be on the method implementation outside the bridge
63-
// This means any docs on the bridge declaration would be ignored
62+
/// Whether to auto generate a wrapper for this method outside the bridge
63+
pub wrap: bool,
6464
/// Cfgs for the method
6565
pub cfgs: Vec<Attribute>,
66+
/// Docs for the method, for passing onto the auto_wrap generated methods
67+
pub docs: Vec<Attribute>,
6668
/// Whether the block containing the method is safe or unsafe
6769
pub unsafe_block: bool,
6870
}
6971

7072
impl ParsedMethod {
71-
const ALLOWED_ATTRS: [&'static str; 9] = [
73+
const ALLOWED_ATTRS: [&'static str; 10] = [
7274
"cxx_name",
7375
"rust_name",
7476
"qinvokable",
7577
"cxx_final",
7678
"cxx_override",
7779
"cxx_virtual",
7880
"cxx_pure",
81+
"auto_wrap",
7982
"doc",
8083
"cfg",
8184
];
@@ -123,18 +126,22 @@ impl ParsedMethod {
123126
let fields = MethodFields::parse(method, auto_case)?;
124127
let attrs = require_attributes(&fields.method.attrs, &Self::ALLOWED_ATTRS)?;
125128
let cfgs = extract_cfgs(&fields.method.attrs);
129+
let docs = extract_docs(&fields.method.attrs);
126130

127131
// Determine if the method is invokable
128132
let is_qinvokable = attrs.contains_key("qinvokable");
129133
let is_pure = attrs.contains_key("cxx_pure");
134+
let wrap = attrs.contains_key("auto_wrap");
130135
let specifiers = ParsedQInvokableSpecifiers::from_attrs(attrs);
131136

132137
Ok(Self {
133138
method_fields: fields,
134139
specifiers,
135140
is_qinvokable,
136141
is_pure,
142+
wrap,
137143
cfgs,
144+
docs,
138145
unsafe_block,
139146
})
140147
}

0 commit comments

Comments
 (0)