Skip to content

Commit 32371fd

Browse files
Add shorthand support for &self in RustQt blocks where the type can be inferred
- If exactly one QObject is present in a block with a method, it will infer `Self` to represent that qobject - Updates one of the examples to test this out
1 parent 64d4ee6 commit 32371fd

File tree

6 files changed

+151
-23
lines changed

6 files changed

+151
-23
lines changed

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

+111-5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use super::qnamespace::ParsedQNamespace;
77
use super::trait_impl::TraitImpl;
88
use crate::naming::cpp::err_unsupported_item;
9+
use crate::parser::method::MethodFields;
910
use crate::parser::CaseConversion;
1011
use crate::{
1112
parser::{
@@ -17,6 +18,8 @@ use crate::{
1718
path::path_compare_str,
1819
},
1920
};
21+
use quote::format_ident;
22+
use std::ops::DerefMut;
2023
use syn::{
2124
spanned::Spanned, Error, ForeignItem, Ident, Item, ItemEnum, ItemForeignMod, ItemImpl,
2225
ItemMacro, Meta, Result,
@@ -65,6 +68,33 @@ impl ParsedCxxQtData {
6568
}
6669
}
6770

71+
fn try_inline_self_types(
72+
inline: bool,
73+
type_to_inline: &Option<Ident>,
74+
invokables: &mut [impl DerefMut<Target = MethodFields>],
75+
) -> Result<()> {
76+
for method in invokables.iter_mut() {
77+
if method.self_unresolved {
78+
if inline {
79+
if let Some(inline_type) = type_to_inline.clone() {
80+
method.qobject_ident = inline_type;
81+
} else {
82+
return Err(Error::new(
83+
method.method.span(),
84+
"Expected a type to inline, no `qobject` typename was passed!",
85+
));
86+
}
87+
} else {
88+
return Err(Error::new(
89+
method.method.span(),
90+
"`Self` type can only be inferred if the extern block contains only one `qobject`.",
91+
));
92+
}
93+
}
94+
}
95+
Ok(())
96+
}
97+
6898
/// Determine if the given [syn::Item] is a CXX-Qt related item
6999
/// If it is then add the [syn::Item] into qobjects BTreeMap
70100
/// Otherwise return the [syn::Item] to pass through to CXX
@@ -139,6 +169,12 @@ impl ParsedCxxQtData {
139169

140170
let auto_case = CaseConversion::from_attrs(&attrs)?;
141171

172+
let mut qobjects = vec![];
173+
174+
let mut methods = vec![];
175+
let mut signals = vec![];
176+
let mut inherited = vec![];
177+
142178
let namespace = attrs
143179
.get("namespace")
144180
.map(|attr| expr_to_string(&attr.meta.require_name_value()?.value))
@@ -159,7 +195,7 @@ impl ParsedCxxQtData {
159195
return Err(Error::new(foreign_fn.span(), "block must be declared `unsafe extern \"RustQt\"` if it contains any safe-to-call #[inherit] qsignals"));
160196
}
161197

162-
self.signals.push(parsed_signal_method);
198+
signals.push(parsed_signal_method);
163199

164200
// Test if the function is an inheritance method
165201
//
@@ -175,15 +211,15 @@ impl ParsedCxxQtData {
175211
let parsed_inherited_method =
176212
ParsedInheritedMethod::parse(foreign_fn, auto_case)?;
177213

178-
self.inherited_methods.push(parsed_inherited_method);
214+
inherited.push(parsed_inherited_method);
179215
// Remaining methods are either C++ methods or invokables
180216
} else {
181217
let parsed_method = ParsedMethod::parse(
182218
foreign_fn,
183219
auto_case,
184220
foreign_mod.unsafety.is_some(),
185221
)?;
186-
self.methods.push(parsed_method);
222+
methods.push(parsed_method);
187223
}
188224
}
189225
ForeignItem::Verbatim(tokens) => {
@@ -199,12 +235,28 @@ impl ParsedCxxQtData {
199235

200236
// Note that we assume a compiler error will occur later
201237
// if you had two structs with the same name
202-
self.qobjects.push(qobject);
238+
qobjects.push(qobject);
203239
}
204-
// Const Macro, Type are unsupported in extern "RustQt" for now
240+
// Const, Macro, Type are unsupported in extern "RustQt" for now
205241
_ => return Err(err_unsupported_item(&item)),
206242
}
207243
}
244+
245+
// If there is exaclty one qobject in the block, it can be inlined as a self type.
246+
let inline_self = qobjects.len() == 1;
247+
let inline_ident = qobjects
248+
.last()
249+
.map(|obj| format_ident!("{}", obj.name.cxx_unqualified()));
250+
251+
Self::try_inline_self_types(inline_self, &inline_ident, &mut methods)?;
252+
Self::try_inline_self_types(inline_self, &inline_ident, &mut signals)?;
253+
Self::try_inline_self_types(inline_self, &inline_ident, &mut inherited)?;
254+
255+
self.qobjects.extend(qobjects);
256+
self.methods.extend(methods);
257+
self.signals.extend(signals);
258+
self.inherited_methods.extend(inherited);
259+
208260
Ok(())
209261
}
210262

@@ -735,4 +787,58 @@ mod tests {
735787
Some("b")
736788
);
737789
}
790+
791+
#[test]
792+
fn test_self_inlining_methods() {
793+
let mut parsed_cxxqtdata = ParsedCxxQtData::new(format_ident!("ffi"), None);
794+
let extern_rust_qt: Item = parse_quote! {
795+
unsafe extern "RustQt" {
796+
#[qobject]
797+
type MyObject = super::T;
798+
799+
fn my_method(&self);
800+
801+
#[inherit]
802+
fn my_inherited_method(&self);
803+
}
804+
};
805+
806+
parsed_cxxqtdata.parse_cxx_qt_item(extern_rust_qt).unwrap();
807+
}
808+
809+
#[test]
810+
fn test_self_inlining_methods_invalid() {
811+
assert_parse_errors! {
812+
|item| ParsedCxxQtData::new(format_ident!("ffi"), None).parse_cxx_qt_item(item) =>
813+
{
814+
extern "RustQt" {
815+
fn my_method(&self);
816+
}
817+
}
818+
819+
{
820+
extern "RustQt" {
821+
#[qobject]
822+
type MyObject = super::T;
823+
824+
#[qobject]
825+
type MyOtherObject = super::S;
826+
827+
fn my_method(&self);
828+
}
829+
}
830+
}
831+
}
832+
833+
#[test]
834+
fn test_invalid_inline_call() {
835+
let method_sig = parse_quote! {
836+
fn test(&self);
837+
};
838+
let mut methods = vec![ParsedMethod::mock_qinvokable(&method_sig)];
839+
840+
// If inlining is set to take place, an Ident is required to inline, here it is `None`
841+
let data = ParsedCxxQtData::try_inline_self_types(true, &None, &mut methods);
842+
assert!(data.is_err());
843+
}
738844
}

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

+15-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use crate::parser::{
88
};
99
use core::ops::Deref;
1010
use quote::format_ident;
11+
use std::ops::DerefMut;
1112
use syn::{Attribute, ForeignItemFn, Ident, Result};
1213

1314
/// Describes a method found in an extern "RustQt" with #[inherit]
@@ -56,6 +57,12 @@ impl Deref for ParsedInheritedMethod {
5657
}
5758
}
5859

60+
impl DerefMut for ParsedInheritedMethod {
61+
fn deref_mut(&mut self) -> &mut Self::Target {
62+
&mut self.method_fields
63+
}
64+
}
65+
5966
#[cfg(test)]
6067
mod tests {
6168
use super::*;
@@ -68,7 +75,6 @@ mod tests {
6875
|item| ParsedInheritedMethod::parse(item, CaseConversion::none()) =>
6976

7077
// Missing self type
71-
{ fn test(&self); }
7278
{ fn test(self: &mut T); }
7379
// Pointer types
7480
{ fn test(self: *const T); }
@@ -91,6 +97,14 @@ mod tests {
9197
CaseConversion::none()
9298
)
9399
.is_ok());
100+
// T by ref is ok in this shorthand (provided the block has one QObject)
101+
assert!(ParsedInheritedMethod::parse(
102+
parse_quote! {
103+
fn test(&self);
104+
},
105+
CaseConversion::none()
106+
)
107+
.is_ok());
94108
// T by Pin
95109
assert!(ParsedInheritedMethod::parse(
96110
parse_quote! {

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

+12
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ use crate::{
99
syntax::{foreignmod, types},
1010
};
1111
use core::ops::Deref;
12+
use quote::format_ident;
1213
use std::collections::{BTreeMap, HashSet};
14+
use std::ops::DerefMut;
1315
use syn::{Attribute, ForeignItemFn, Ident, Result};
1416

1517
/// Describes a C++ specifier for the Q_INVOKABLE
@@ -146,6 +148,12 @@ impl Deref for ParsedMethod {
146148
}
147149
}
148150

151+
impl DerefMut for ParsedMethod {
152+
fn deref_mut(&mut self) -> &mut Self::Target {
153+
&mut self.method_fields
154+
}
155+
}
156+
149157
/// Struct with common fields between Invokable types.
150158
/// These types are ParsedSignal, ParsedMethod and ParsedInheritedMethod
151159
#[derive(Clone)]
@@ -156,6 +164,7 @@ pub struct MethodFields {
156164
pub parameters: Vec<ParsedFunctionParameter>,
157165
pub safe: bool,
158166
pub name: Name,
167+
pub self_unresolved: bool,
159168
}
160169

161170
impl MethodFields {
@@ -164,6 +173,8 @@ impl MethodFields {
164173
let (qobject_ident, mutability) = types::extract_qobject_ident(&self_receiver.ty)?;
165174
let mutable = mutability.is_some();
166175

176+
let self_unresolved = qobject_ident == format_ident!("Self");
177+
167178
let parameters = ParsedFunctionParameter::parse_all_ignoring_receiver(&method.sig)?;
168179
let safe = method.sig.unsafety.is_none();
169180
let name =
@@ -176,6 +187,7 @@ impl MethodFields {
176187
parameters,
177188
safe,
178189
name,
190+
self_unresolved,
179191
})
180192
}
181193
}

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

+7
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use crate::{
88
syntax::path::path_compare_str,
99
};
1010
use core::ops::Deref;
11+
use std::ops::DerefMut;
1112
use syn::{spanned::Spanned, Attribute, Error, ForeignItemFn, Result, Visibility};
1213

1314
#[derive(Clone)]
@@ -74,6 +75,12 @@ impl Deref for ParsedSignal {
7475
}
7576
}
7677

78+
impl DerefMut for ParsedSignal {
79+
fn deref_mut(&mut self) -> &mut Self::Target {
80+
&mut self.method_fields
81+
}
82+
}
83+
7784
#[cfg(test)]
7885
mod tests {
7986
use syn::parse_quote;

crates/cxx-qt-gen/src/syntax/foreignmod.rs

+1-10
Original file line numberDiff line numberDiff line change
@@ -174,14 +174,7 @@ pub fn self_type_from_foreign_fn(signature: &Signature) -> Result<Receiver> {
174174
));
175175
}
176176

177-
if receiver.reference.is_some() {
178-
return Err(Error::new(
179-
receiver.span(),
180-
"Reference on self (i.e. `&self`) are not supported! Use `self: &T` instead",
181-
));
182-
}
183-
184-
if receiver.colon_token.is_none() {
177+
if receiver.colon_token.is_none() && receiver.reference.is_none() {
185178
return Err(Error::new(
186179
receiver.span(),
187180
"`self` is not supported as receiver! Use `self: T` to indicate a type.",
@@ -254,8 +247,6 @@ mod tests {
254247
{ fn foo(self); }
255248
// self with mut
256249
{ fn foo(mut self: T); }
257-
// self reference
258-
{ fn foo(&self); }
259250
// self reference with mut
260251
{ fn foo(&mut self); }
261252
// attribute on self type

examples/qml_minimal/rust/src/cxxqt_object.rs

+5-7
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ pub mod qobject {
2121
}
2222
// ANCHOR_END: book_qstring_import
2323

24-
// ANCHOR: book_rustobj_struct_signature
2524
extern "RustQt" {
25+
// ANCHOR: book_rustobj_struct_signature
2626
// The QObject definition
2727
// We tell CXX-Qt that we want a QObject class with the name MyObject
2828
// based on the Rust struct MyObjectRust.
@@ -32,21 +32,19 @@ pub mod qobject {
3232
#[qproperty(QString, string)]
3333
#[namespace = "my_object"]
3434
type MyObject = super::MyObjectRust;
35-
}
36-
// ANCHOR_END: book_rustobj_struct_signature
35+
// ANCHOR_END: book_rustobj_struct_signature
3736

38-
// ANCHOR: book_rustobj_invokable_signature
39-
extern "RustQt" {
37+
// ANCHOR: book_rustobj_invokable_signature
4038
// Declare the invokable methods we want to expose on the QObject
4139
#[qinvokable]
4240
#[cxx_name = "incrementNumber"]
4341
fn increment_number(self: Pin<&mut MyObject>);
4442

4543
#[qinvokable]
4644
#[cxx_name = "sayHi"]
47-
fn say_hi(self: &MyObject, string: &QString, number: i32);
45+
fn say_hi(&self, string: &QString, number: i32);
46+
// ANCHOR_END: book_rustobj_invokable_signature
4847
}
49-
// ANCHOR_END: book_rustobj_invokable_signature
5048
}
5149

5250
// ANCHOR: book_use

0 commit comments

Comments
 (0)