Skip to content

Commit 899b1b1

Browse files
Add book entry
1 parent 17a4112 commit 899b1b1

File tree

8 files changed

+132
-28
lines changed

8 files changed

+132
-28
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: 76 additions & 15 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_call_params_tokens, 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, 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

@@ -31,11 +36,28 @@ pub fn generate_rust_methods(
3136
cpp_class_name_rust,
3237
);
3338

39+
let parameter_signatures_qualified = get_params_tokens_qualified(
40+
invokable.mutable,
41+
&invokable.parameters,
42+
&qobject_names.name.rust_qualified(),
43+
type_names,
44+
)?;
45+
3446
let call_parameters = get_call_params_tokens(&invokable.parameters);
3547

3648
let return_type = &invokable.method.sig.output;
49+
let qualified_return_type = match return_type {
50+
ReturnType::Default => ReturnType::Default,
51+
ReturnType::Type(arrow, boxed_type) => {
52+
let ty = boxed_type.as_ref();
53+
let qualified_type = syn_type_cxx_bridge_to_qualified(ty, type_names)?;
54+
ReturnType::Type(*arrow, Box::new(qualified_type))
55+
}
56+
};
3757

3858
let cfgs = &invokable.cfgs;
59+
let docs = &invokable.docs;
60+
3961
let cxx_namespace = qobject_names.namespace_tokens();
4062

4163
let (block_type, block_safety) = if invokable.is_pure {
@@ -56,18 +78,38 @@ pub fn generate_rust_methods(
5678
Some(quote! { unsafe })
5779
};
5880

81+
let method_safety = if invokable.safe {
82+
None
83+
} else {
84+
Some(quote! { unsafe })
85+
};
86+
87+
let qualified_impl = type_names.rust_qualified(&invokable.qobject_ident)?;
88+
89+
let inner_fn = if invokable.mutable {
90+
quote! { rust_mut() }
91+
} else {
92+
quote! { rust() }
93+
};
94+
5995
let wrapper_fn = if invokable.wrap {
6096
vec![parse_quote_spanned! {
6197
invokable.method.span() =>
62-
#unsafe_call fn #invokable_ident_rust(#parameter_signatures) #return_type {
63-
self.rust().#invokable_ident_rust(#call_parameters)
98+
#(#cfgs)*
99+
impl #qualified_impl {
100+
#(#docs)*
101+
pub #method_safety fn #invokable_ident_rust(#parameter_signatures_qualified) #qualified_return_type {
102+
use cxx_qt::CxxQtType;
103+
104+
self.#inner_fn.#invokable_ident_rust(#call_parameters)
105+
}
64106
}
65107
}]
66108
} else {
67109
vec![]
68110
};
69111

70-
GeneratedRustFragment {
112+
Ok(GeneratedRustFragment {
71113
cxx_mod_contents: vec![parse_quote_spanned! {
72114
invokable.method.span() =>
73115
// Note: extern "Rust" block does not need to be unsafe
@@ -87,18 +129,21 @@ pub fn generate_rust_methods(
87129
}
88130
}],
89131
cxx_qt_mod_contents: wrapper_fn,
90-
}
132+
})
91133
})
92-
.collect::<Vec<_>>();
134+
.collect::<Result<Vec<_>>>()?;
93135

94136
Ok(GeneratedRustFragment::flatten(generated))
95137
}
96138

97139
#[cfg(test)]
98140
mod tests {
99141
use super::*;
142+
use quote::format_ident;
100143

101-
use crate::generator::naming::qobject::tests::create_qobjectname;
144+
use crate::generator::naming::qobject::tests::{
145+
create_qobjectname, create_qobjectname_with_qcolor,
146+
};
102147
use crate::tests::assert_tokens_eq;
103148
use syn::{parse_quote, ForeignItemFn};
104149

@@ -128,10 +173,18 @@ mod tests {
128173
ParsedMethod::mock_qinvokable(&method3).make_mutable(),
129174
ParsedMethod::mock_qinvokable(&method4).make_unsafe(),
130175
];
131-
let qobject_names = create_qobjectname();
176+
let qobject_names = create_qobjectname_with_qcolor();
132177

133-
let generated =
134-
generate_rust_methods(&invokables.iter().collect::<Vec<_>>(), &qobject_names).unwrap();
178+
let mut type_names = TypeNames::mock();
179+
type_names.mock_insert("QColor", Some(format_ident!("qobject")), None, None);
180+
type_names.mock_insert("T", Some(format_ident!("qobject")), None, None);
181+
182+
let generated = generate_rust_methods(
183+
&invokables.iter().collect::<Vec<_>>(),
184+
&qobject_names,
185+
&type_names,
186+
)
187+
.unwrap();
135188

136189
assert_eq!(generated.cxx_mod_contents.len(), 4);
137190
assert_eq!(generated.cxx_qt_mod_contents.len(), 2);
@@ -175,8 +228,12 @@ mod tests {
175228
assert_tokens_eq(
176229
&generated.cxx_qt_mod_contents[0],
177230
quote! {
178-
fn opaque_invokable(self: Pin<&mut MyObject>, param: &QColor) -> UniquePtr<QColor> {
179-
self.rust().opaque_invokable(param)
231+
impl qobject::MyObject {
232+
pub fn opaque_invokable(self: Pin<&mut qobject::MyObject>, param: &qobject::QColor) -> cxx::UniquePtr<qobject::QColor> {
233+
use cxx_qt::CxxQtType;
234+
235+
self.rust_mut().opaque_invokable(param)
236+
}
180237
}
181238
},
182239
);
@@ -196,8 +253,12 @@ mod tests {
196253
assert_tokens_eq(
197254
&generated.cxx_qt_mod_contents[1],
198255
quote! {
199-
unsafe fn unsafe_invokable(self:&MyObject, param: *mut T) -> *mut T {
200-
self.rust().unsafe_invokable(param)
256+
impl qobject::MyObject {
257+
pub unsafe fn unsafe_invokable(self:&qobject::MyObject, param: *mut qobject::T) -> *mut qobject::T {
258+
use cxx_qt::CxxQtType;
259+
260+
self.rust().unsafe_invokable(param)
261+
}
201262
}
202263
},
203264
);

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

Lines changed: 30 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, Item, ItemMod, Result};
25+
use syn::{parse_quote, Item, ItemMod, Path, Result};
2426

2527
/// Representation of the generated Rust code for a QObject
2628
pub struct GeneratedRustBlocks {
@@ -146,6 +148,33 @@ pub fn get_params_tokens(
146148
}
147149
}
148150

151+
/// Return the [TokenStream] of the parsed parameters, with self type qualified for use in generation
152+
pub fn get_params_tokens_qualified(
153+
mutable: bool,
154+
parameters: &[ParsedFunctionParameter],
155+
class_name_path: &Path,
156+
type_names: &TypeNames,
157+
) -> Result<TokenStream> {
158+
let struct_sig = if mutable {
159+
quote! { Pin<&mut #class_name_path> }
160+
} else {
161+
quote! { &#class_name_path }
162+
};
163+
if parameters.is_empty() {
164+
Ok(quote! { self: #struct_sig })
165+
} else {
166+
let parameters = parameters
167+
.iter()
168+
.map(|parameter| {
169+
let ident = &parameter.ident;
170+
let qualified_ty = &syn_type_cxx_bridge_to_qualified(&parameter.ty, type_names)?;
171+
Ok(quote! { #ident: #qualified_ty })
172+
})
173+
.collect::<Result<Vec<TokenStream>>>()?;
174+
Ok(quote! { self: #struct_sig, #(#parameters),* })
175+
}
176+
}
177+
149178
/// Return the [TokenStream] of the parsed parameters, which would be used to call the fn, for use in generation
150179
pub fn get_call_params_tokens(parameters: &[ParsedFunctionParameter]) -> TokenStream {
151180
if parameters.is_empty() {

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: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// SPDX-FileContributor: Andrew Hayzen <[email protected]>
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},
@@ -61,10 +61,10 @@ pub struct ParsedMethod {
6161
pub is_pure: bool,
6262
/// Whether to auto generate a wrapper for this method outside the bridge
6363
pub wrap: bool,
64-
// No docs field since the docs should be on the method implementation outside the bridge
65-
// This means any docs on the bridge declaration would be ignored
6664
/// Cfgs for the method
6765
pub cfgs: Vec<Attribute>,
66+
/// Docs for the method, for passing onto the auto_wrap generated methods
67+
pub docs: Vec<Attribute>,
6868
/// Whether the block containing the method is safe or unsafe
6969
pub unsafe_block: bool,
7070
}
@@ -126,6 +126,7 @@ impl ParsedMethod {
126126
let fields = MethodFields::parse(method, auto_case)?;
127127
let attrs = require_attributes(&fields.method.attrs, &Self::ALLOWED_ATTRS)?;
128128
let cfgs = extract_cfgs(&fields.method.attrs);
129+
let docs = extract_docs(&fields.method.attrs);
129130

130131
// Determine if the method is invokable
131132
let is_qinvokable = attrs.contains_key("qinvokable");
@@ -140,6 +141,7 @@ impl ParsedMethod {
140141
is_pure,
141142
wrap,
142143
cfgs,
144+
docs,
143145
unsafe_block,
144146
})
145147
}

crates/cxx-qt-gen/test_outputs/passthrough_and_naming.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -780,8 +780,11 @@ cxx_qt::static_assertions::assert_eq_size!(
780780
cxx_qt::signalhandler::CxxQtSignalHandler<SecondObjectCxxQtSignalClosurepropertyNameChanged>,
781781
[usize; 2]
782782
);
783-
unsafe fn my_function(self: &SecondObject, param: i32) {
784-
self.rust().my_function(param)
783+
impl ffi::SecondObject {
784+
pub fn my_function(self: &ffi::SecondObject, param: i32) {
785+
use cxx_qt::CxxQtType;
786+
self.rust().my_function(param)
787+
}
785788
}
786789
impl ffi::SecondObject {
787790
#[doc = "Connect the given function pointer to the signal "]

examples/qml_features/rust/src/invokables.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ pub mod qobject {
4545
/// Immutable invokable method that returns the QColor
4646
#[qinvokable]
4747
#[cxx_name = "loadColor"]
48+
#[auto_wrap]
4849
fn load_color(self: &RustInvokables) -> QColor;
4950

5051
/// Mutable invokable method that stores a color
@@ -95,11 +96,6 @@ impl Default for RustInvokablesRust {
9596

9697
// ANCHOR: book_invokable_impl
9798
impl qobject::RustInvokables {
98-
/// Immutable invokable method that returns the QColor
99-
pub fn load_color(&self) -> QColor {
100-
self.as_qcolor()
101-
}
102-
10399
/// Mutable invokable method that stores a color
104100
pub fn store_color(self: Pin<&mut Self>, red: f32, green: f32, blue: f32) {
105101
self.store_helper(red, green, blue);
@@ -145,7 +141,7 @@ impl qobject::RustInvokables {
145141

146142
impl RustInvokablesRust {
147143
/// Immutable Rust context method that returns the QColor
148-
pub fn as_qcolor(&self) -> QColor {
144+
pub fn load_color(&self) -> QColor {
149145
QColor::from_rgb(
150146
(self.red * 255.0).round() as i32,
151147
(self.green * 255.0).round() as i32,

0 commit comments

Comments
 (0)