Skip to content

Commit f05b4a3

Browse files
ahayzen-kdabBe-ing
authored andcommitted
cxx-qt-gen: use #[qproperty] on the struct instead of field
This is a stepping stone to moving qproperty to be defined on the type in the extern "RustQt" block. Related to #559
1 parent d897640 commit f05b4a3

File tree

30 files changed

+153
-216
lines changed

30 files changed

+153
-216
lines changed

Diff for: CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3131
- `#[cxx_qt::qsignals]` and `#[cxx_qt::inherit]` are now used in an `extern "RustQt"` block as `#[qsignal]` and `#[inherit]`
3232
- `#[qinvokable]` is now defined as a signature in `extern "RustQt"`
3333
- `rust_mut` is now safe to call
34+
- `#[qproperty]` is now defined as an attribute on the qobject rather than the field
3435

3536
### Fixed
3637

Diff for: book/src/getting-started/1-qobjects-in-rust.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ Typically this will be instantiated by QML and the lifetime will be directly ass
7171

7272
The generated QObject subclass will then defer to the Rust struct for any behavior, which is then defined in Rust.
7373
For example, using the `#[qinvokable]` attribute, we can define functions that will be exposed to C++, but will execute Rust code.
74-
Also, any fields in the Rust struct marked with `#[qproperty]` will be exposed to Qt as `Q_PROPERTY` fields.
74+
Also, any fields in the Rust struct can be exposed to Qt as `Q_PROPERTY` fields by using the `#[qproperty(T, NAME)]` attribute on the struct.
7575
Therefore allowing you to assign them from QML as well.
7676

7777
But enough theory for now, lets jump in and write [our first CXX-Qt module](./2-our-first-cxx-qt-module.md).

Diff for: book/src/getting-started/2-our-first-cxx-qt-module.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,13 @@ Additionally, we need to either `impl Default` or `#[derive(Default)]` for our s
6969
```
7070

7171
The Rust struct can be defined just like a normal Rust struct and can contain any kind of field, even Rust-only types.
72-
If a field is marked as `#[qproperty]` it will be exposed to the C++ side as a `Q_PROPERTY`.
72+
If a field is tagged as `#[qproperty]` it will be exposed to the C++ side as a `Q_PROPERTY`.
7373

7474
That means the newly created QObject subclass will have two properties as members: `number` and `string`. For names that contain multiple words, like `my_number`, CXX-Qt will automatically rename the field from snake_case to camelCase to fit with C++/QML naming conventions (e.g. `myNumber`).
7575

7676
### Types
7777

78-
Do note though that any fields marked as `#[qproperty]` must be types that CXX can translate to C++ types.
78+
Do note though that any fields tagged as `#[qproperty]` must be types that CXX can translate to C++ types.
7979
In our case that means:
8080
- `number: i32` -> `::std::int32_t number`
8181
- `string: QString` -> `QString string`

Diff for: book/src/qobject/qobject_struct.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ The macro does multiple other things for you though:
2626
- Generate a C++ QObject subclass that wraps the `MyObject` Rust struct.
2727
- Expose the generated QObject subclass to Rust as [`qobject::MyObject`](./generated-qobject.md)
2828
- Generate getters/setters for all fields.
29-
- Generate `Q_PROPERTY`s for all fields that are marked as `#[qproperty]`.
29+
- Generate `Q_PROPERTY`s for all fields that are tagged as `#[qproperty]`.
3030
- Generate signals if paired with a [`#[qsignal]` macro](./signals.md).
3131

3232
## Exposing to QML
@@ -71,7 +71,7 @@ Fields within the `#[cxx_qt::qobject]` marked struct can be tagged with `#[qprop
7171
{{#include ../../../examples/qml_features/rust/src/properties.rs:book_properties_struct}}
7272
```
7373

74-
Any type that CXX supports may be marked as a `#[qproperty]`.
74+
Any type that CXX supports may be tagged as a `#[qproperty]`.
7575
See the [Types page](../concepts/types.md) for a list of supported types.
7676

7777
For every `#[qproperty]`, CXX-Qt will generate setters and getters, as well as a "changed" signal.
@@ -90,7 +90,7 @@ where `<Property>` is the name of the property.
9090

9191
These setters and getters assure that the changed signal is emitted every time the property is edited.
9292

93-
Any field that's not marked as `#[qproperty]` won't be accessible from C++, but it will be accessible from Rust.
93+
Any field that's not tagged as `#[qproperty]` won't be accessible from C++, but it will be accessible from Rust.
9494
See the [Private fields section](#private-methods-and-fields)
9595

9696
## Default

Diff for: crates/cxx-qt-gen/src/generator/cpp/property/mod.rs

-3
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,10 @@ mod tests {
7373
ParsedQProperty {
7474
ident: format_ident!("trivial_property"),
7575
ty: parse_quote! { i32 },
76-
vis: syn::Visibility::Inherited,
7776
},
7877
ParsedQProperty {
7978
ident: format_ident!("opaque_property"),
8079
ty: parse_quote! { UniquePtr<QColor> },
81-
vis: syn::Visibility::Inherited,
8280
},
8381
];
8482
let qobject_idents = create_qobjectname();
@@ -251,7 +249,6 @@ mod tests {
251249
let properties = vec![ParsedQProperty {
252250
ident: format_ident!("mapped_property"),
253251
ty: parse_quote! { A1 },
254-
vis: syn::Visibility::Inherited,
255252
}];
256253
let qobject_idents = create_qobjectname();
257254

Diff for: crates/cxx-qt-gen/src/generator/naming/property.rs

-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@ pub mod tests {
8080
let property = ParsedQProperty {
8181
ident: format_ident!("my_property"),
8282
ty,
83-
vis: syn::Visibility::Inherited,
8483
};
8584
QPropertyName::from(&property)
8685
}

Diff for: crates/cxx-qt-gen/src/generator/rust/property/mod.rs

-3
Original file line numberDiff line numberDiff line change
@@ -69,17 +69,14 @@ mod tests {
6969
ParsedQProperty {
7070
ident: format_ident!("trivial_property"),
7171
ty: parse_quote! { i32 },
72-
vis: syn::Visibility::Inherited,
7372
},
7473
ParsedQProperty {
7574
ident: format_ident!("opaque_property"),
7675
ty: parse_quote! { UniquePtr<QColor> },
77-
vis: parse_quote! { pub },
7876
},
7977
ParsedQProperty {
8078
ident: format_ident!("unsafe_property"),
8179
ty: parse_quote! { *mut T },
82-
vis: syn::Visibility::Inherited,
8380
},
8481
];
8582
let qobject_idents = create_qobjectname();

Diff for: crates/cxx-qt-gen/src/parser/property.rs

+75-3
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,86 @@
33
//
44
// SPDX-License-Identifier: MIT OR Apache-2.0
55

6-
use syn::{Ident, Type, Visibility};
6+
use syn::{parse::ParseStream, Attribute, Ident, Result, Token, Type};
77

88
/// Describes a single Q_PROPERTY for a struct
99
pub struct ParsedQProperty {
1010
/// The [syn::Ident] of the property
1111
pub ident: Ident,
1212
/// The [syn::Type] of the property
1313
pub ty: Type,
14-
/// The [syn::Visibility] of the property
15-
pub vis: Visibility,
14+
}
15+
16+
impl ParsedQProperty {
17+
pub fn parse(attr: Attribute) -> Result<Self> {
18+
attr.parse_args_with(|input: ParseStream| -> Result<Self> {
19+
let ty = input.parse()?;
20+
let _comma = input.parse::<Token![,]>()?;
21+
let ident = input.parse()?;
22+
23+
// TODO: later we'll need to parse setters and getters here
24+
// which are key-value, hence this not being parsed as a list
25+
26+
Ok(Self { ident, ty })
27+
})
28+
}
29+
}
30+
31+
#[cfg(test)]
32+
mod tests {
33+
use super::*;
34+
35+
use quote::format_ident;
36+
use syn::{parse_quote, ItemStruct};
37+
38+
#[test]
39+
fn test_parse_property() {
40+
let mut input: ItemStruct = parse_quote! {
41+
#[qproperty(T, name)]
42+
struct MyStruct;
43+
};
44+
let property = ParsedQProperty::parse(input.attrs.remove(0)).unwrap();
45+
assert_eq!(property.ident, format_ident!("name"));
46+
assert_eq!(property.ty, parse_quote! { T });
47+
}
48+
49+
#[test]
50+
fn test_parse_property_arg_extra() {
51+
let mut input: ItemStruct = parse_quote! {
52+
#[qproperty(T, name, A = B)]
53+
struct MyStruct;
54+
};
55+
let property = ParsedQProperty::parse(input.attrs.remove(0));
56+
assert!(property.is_err());
57+
}
58+
59+
#[test]
60+
fn test_parse_property_arg_wrong() {
61+
let mut input: ItemStruct = parse_quote! {
62+
#[qproperty(A = B, name)]
63+
struct MyStruct;
64+
};
65+
let property = ParsedQProperty::parse(input.attrs.remove(0));
66+
assert!(property.is_err());
67+
}
68+
69+
#[test]
70+
fn test_parse_property_no_name() {
71+
let mut input: ItemStruct = parse_quote! {
72+
#[qproperty(T)]
73+
struct MyStruct;
74+
};
75+
let property = ParsedQProperty::parse(input.attrs.remove(0));
76+
assert!(property.is_err());
77+
}
78+
79+
#[test]
80+
fn test_parse_property_no_type() {
81+
let mut input: ItemStruct = parse_quote! {
82+
#[qproperty(T)]
83+
struct MyStruct;
84+
};
85+
let property = ParsedQProperty::parse(input.attrs.remove(0));
86+
assert!(property.is_err());
87+
}
1688
}

Diff for: crates/cxx-qt-gen/src/parser/qobject.rs

+18-29
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,12 @@ use crate::{
1010
},
1111
syntax::{
1212
attribute::{attribute_find_path, attribute_tokens_to_map, AttributeDefault},
13-
fields::fields_to_named_fields_mut,
1413
path::path_compare_str,
1514
},
1615
};
1716
use syn::{
18-
spanned::Spanned, Error, Fields, Ident, ImplItem, Item, ItemImpl, ItemStruct, LitStr, Result,
19-
Visibility,
17+
spanned::Spanned, Attribute, Error, Ident, ImplItem, Item, ItemImpl, ItemStruct, LitStr,
18+
Result, Visibility,
2019
};
2120

2221
/// Metadata for registering QML element
@@ -97,7 +96,7 @@ impl ParsedQObject {
9796

9897
// Parse any properties in the struct
9998
// and remove the #[qproperty] attribute
100-
let properties = Self::parse_struct_fields(&mut qobject_struct.fields)?;
99+
let properties = Self::parse_struct_attributes(&mut qobject_struct.attrs)?;
101100

102101
// Ensure that the QObject is marked as pub otherwise the error is non obvious
103102
// https://github.com/KDAB/cxx-qt/issues/457
@@ -261,21 +260,15 @@ impl ParsedQObject {
261260
}
262261
}
263262

264-
/// Extract all the properties from [syn::Fields] from a [syn::ItemStruct]
265-
fn parse_struct_fields(fields: &mut Fields) -> Result<Vec<ParsedQProperty>> {
263+
fn parse_struct_attributes(attrs: &mut Vec<Attribute>) -> Result<Vec<ParsedQProperty>> {
266264
let mut properties = vec![];
267-
for field in fields_to_named_fields_mut(fields)? {
268-
// Try to find any properties defined within the struct
269-
if let Some(index) = attribute_find_path(&field.attrs, &["qproperty"]) {
270-
// Remove the #[qproperty] attribute
271-
field.attrs.remove(index);
272-
273-
properties.push(ParsedQProperty {
274-
ident: field.ident.clone().unwrap(),
275-
ty: field.ty.clone(),
276-
vis: field.vis.clone(),
277-
});
278-
}
265+
266+
// Note that once extract_if is stable, this would allow for comparing all the
267+
// elements once using path_compare_str and then building ParsedQProperty
268+
// from the extracted elements.
269+
// https://doc.rust-lang.org/nightly/std/vec/struct.Vec.html#method.extract_if
270+
while let Some(index) = attribute_find_path(attrs, &["qproperty"]) {
271+
properties.push(ParsedQProperty::parse(attrs.remove(index))?);
279272
}
280273

281274
Ok(properties)
@@ -287,7 +280,7 @@ pub mod tests {
287280
use super::*;
288281

289282
use crate::parser::tests::f64_type;
290-
use syn::{parse_quote, ItemImpl, Visibility};
283+
use syn::{parse_quote, ItemImpl};
291284

292285
pub fn create_parsed_qobject() -> ParsedQObject {
293286
let qobject_struct: ItemStruct = parse_quote! {
@@ -324,11 +317,10 @@ pub mod tests {
324317
fn test_from_struct_properties_and_fields() {
325318
let qobject_struct: ItemStruct = parse_quote! {
326319
#[cxx_qt::qobject]
320+
#[qproperty(i32, int_property)]
321+
#[qproperty(i32, public_property)]
327322
pub struct MyObject {
328-
#[qproperty]
329323
int_property: i32,
330-
331-
#[qproperty]
332324
pub public_property: i32,
333325

334326
field: i32,
@@ -398,11 +390,10 @@ pub mod tests {
398390
fn test_parse_struct_fields_valid() {
399391
let item: ItemStruct = parse_quote! {
400392
#[cxx_qt::qobject]
393+
#[qproperty(f64, f64_property)]
394+
#[qproperty(f64, public_property)]
401395
pub struct T {
402-
#[qproperty]
403396
f64_property: f64,
404-
405-
#[qproperty]
406397
pub public_property: f64,
407398

408399
field: f64,
@@ -413,20 +404,18 @@ pub mod tests {
413404

414405
assert_eq!(properties[0].ident, "f64_property");
415406
assert_eq!(properties[0].ty, f64_type());
416-
assert!(matches!(properties[0].vis, Visibility::Inherited));
417407

418408
assert_eq!(properties[1].ident, "public_property");
419409
assert_eq!(properties[1].ty, f64_type());
420-
assert!(matches!(properties[1].vis, Visibility::Public(_)));
421410
}
422411

423412
#[test]
424-
fn test_parse_struct_fields_invalid() {
413+
fn test_parse_struct_fields() {
425414
let item: ItemStruct = parse_quote! {
426415
#[cxx_qt::qobject]
427416
pub struct T(f64);
428417
};
429-
assert!(ParsedQObject::from_struct(&item, 0).is_err());
418+
assert!(ParsedQObject::from_struct(&item, 0).is_ok());
430419
}
431420

432421
#[test]

0 commit comments

Comments
 (0)