Skip to content

Commit

Permalink
cxx-qt-gen: parse any #[property] attributes on struct fields
Browse files Browse the repository at this point in the history
Later when Data and RustObj structs are merged, properties will
be defined on the RustObj struct. This adds support for finding
these properties in the parser phase.
  • Loading branch information
ahayzen-kdab committed Aug 10, 2022
1 parent 621a79f commit bc56f89
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 17 deletions.
53 changes: 53 additions & 0 deletions cxx-qt-gen/src/parser/cxxqtdata.rs
Expand Up @@ -155,6 +155,10 @@ impl ParsedCxxQtData {
s.attrs.remove(index);

if let Some(qobject) = self.qobjects.get_mut(&s.ident) {
// Parse any properties in the struct
// and remove the #[property] attribute
qobject.parse_struct_fields(&mut s.fields)?;

qobject.qobject_struct = Some(s);
return Ok(None);
} else {
Expand Down Expand Up @@ -335,6 +339,55 @@ mod tests {
.is_some());
}

#[test]
fn test_find_and_merge_cxx_qt_item_struct_valid_properties() {
let mut cxx_qt_data = create_parsed_cxx_qt_data();

let item: Item = tokens_to_syn(quote! {
#[cxx_qt::qobject]
struct MyObject {
#[property]
property: i32,
}
});
let result = cxx_qt_data.parse_cxx_qt_item(item).unwrap();
assert!(result.is_none());
assert_eq!(cxx_qt_data.qobjects[&qobject_ident()].properties.len(), 1);
}

#[test]
fn test_find_and_merge_cxx_qt_item_struct_valid_properties_and_fields() {
let mut cxx_qt_data = create_parsed_cxx_qt_data();

let item: Item = tokens_to_syn(quote! {
#[cxx_qt::qobject]
struct MyObject {
#[property]
property: i32,

field: i32,
}
});
let result = cxx_qt_data.parse_cxx_qt_item(item).unwrap();
assert!(result.is_none());
assert_eq!(cxx_qt_data.qobjects[&qobject_ident()].properties.len(), 1);
}

#[test]
fn test_find_and_merge_cxx_qt_item_struct_valid_fields() {
let mut cxx_qt_data = create_parsed_cxx_qt_data();

let item: Item = tokens_to_syn(quote! {
#[cxx_qt::qobject]
struct MyObject {
field: i32,
}
});
let result = cxx_qt_data.parse_cxx_qt_item(item).unwrap();
assert!(result.is_none());
assert_eq!(cxx_qt_data.qobjects[&qobject_ident()].properties.len(), 0);
}

#[test]
fn test_find_and_merge_cxx_qt_item_struct_passthrough() {
let mut cxx_qt_data = create_parsed_cxx_qt_data();
Expand Down
1 change: 1 addition & 0 deletions cxx-qt-gen/src/parser/mod.rs
Expand Up @@ -5,6 +5,7 @@

pub mod cxxqtdata;
pub mod parameter;
pub mod property;
pub mod qobject;
pub mod signals;

Expand Down
18 changes: 18 additions & 0 deletions cxx-qt-gen/src/parser/property.rs
@@ -0,0 +1,18 @@
// SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
// SPDX-FileContributor: Andrew Hayzen <andrew.hayzen@kdab.com>
//
// SPDX-License-Identifier: MIT OR Apache-2.0

use syn::{Ident, Type, Visibility};

/// Describes a single property for a struct
pub struct ParsedProperty {
/// The [syn::Ident] of the property
pub ident: Ident,
/// The [syn::Type] of the property
pub ty: Type,
/// The [syn::Visiblity] of the property
pub vis: Visibility,
// TODO: later this will describe if the property has an attribute
// stating that the a conversion in C++ needs to occur (eg UniquePtr<T> to T)..
}
55 changes: 52 additions & 3 deletions cxx-qt-gen/src/parser/qobject.rs
Expand Up @@ -3,9 +3,11 @@
//
// SPDX-License-Identifier: MIT OR Apache-2.0

use crate::parser::signals::ParsedSignalsEnum;
use crate::syntax::attribute::attribute_find_path;
use syn::{spanned::Spanned, Error, ImplItem, ImplItemMethod, Item, ItemImpl, ItemStruct, Result};
use crate::parser::{property::ParsedProperty, signals::ParsedSignalsEnum};
use crate::syntax::{attribute::attribute_find_path, fields::fields_to_named_fields_mut};
use syn::{
spanned::Spanned, Error, Fields, ImplItem, ImplItemMethod, Item, ItemImpl, ItemStruct, Result,
};

/// A representation of a QObject within a CXX-Qt [syn::ItemMod]
///
Expand All @@ -29,6 +31,10 @@ pub struct ParsedQObject {
///
/// Note that they will only be visible on the Rust side
pub methods: Vec<ImplItemMethod>,
/// List of properties that need to be implemented on the C++ object
///
/// These will be exposed as Q_PROPERTY on the C++ object
pub properties: Vec<ParsedProperty>,
/// Update request handler for the QObject
///
/// In the future this may be removed
Expand Down Expand Up @@ -62,6 +68,25 @@ impl ParsedQObject {

Ok(())
}

/// Extract all the properties from [syn::Fields] from a [syn::ItemStruct
pub fn parse_struct_fields(&mut self, fields: &mut Fields) -> Result<()> {
for field in fields_to_named_fields_mut(fields)? {
// Try to find any properties defined within the struct
if let Some(index) = attribute_find_path(&field.attrs, &["property"]) {
// Remove the #[property] attribute
field.attrs.remove(index);

self.properties.push(ParsedProperty {
ident: field.ident.clone().unwrap(),
ty: field.ty.clone(),
vis: field.vis.clone(),
});
}
}

Ok(())
}
}

#[cfg(test)]
Expand Down Expand Up @@ -107,4 +132,28 @@ mod tests {
});
assert!(qobject.parse_impl_items(&item.items).is_err());
}

#[test]
fn test_parse_struct_fields_valid() {
let mut qobject = ParsedQObject::default();
let mut item: ItemStruct = tokens_to_syn(quote! {
struct T {
#[property]
property: f64,

field: f64,
}
});
assert!(qobject.parse_struct_fields(&mut item.fields).is_ok());
assert_eq!(qobject.properties.len(), 1);
}

#[test]
fn test_parse_struct_fields_invalid() {
let mut qobject = ParsedQObject::default();
let mut item: ItemStruct = tokens_to_syn(quote! {
struct T(f64);
});
assert!(qobject.parse_struct_fields(&mut item.fields).is_err());
}
}
59 changes: 45 additions & 14 deletions cxx-qt-gen/src/syntax/fields.rs
Expand Up @@ -10,22 +10,31 @@ use syn::{spanned::Spanned, Error, Field, Fields, FieldsNamed, Ident, Result, Ty
///
/// If there are [syn::FieldsUnnamed] then an error occurs
pub fn fields_named_to_ident_type(fields: &Fields) -> Result<Vec<(Ident, Type)>> {
Ok(fields_to_named_fields(fields)?
.iter()
// These are named fields so they have an ident
.map(|field| (field.ident.clone().unwrap(), field.ty.clone()))
.collect())
}

/// In a group of [syn::Fields] extract any [syn::FieldNamed] fields and allow for mutation
///
/// If there are [syn::FieldsUnnamed] then an error occurs
pub fn fields_to_named_fields_mut(fields: &mut Fields) -> Result<Vec<&mut Field>> {
match fields {
Fields::Named(FieldsNamed { named, .. }) => {
let mut fields = vec![];
for name in named {
if let Field {
ident: Some(ident),
ty,
..
} = name
{
fields.push((ident.clone(), ty.clone()));
}
}
Fields::Named(FieldsNamed { named, .. }) => Ok(named.iter_mut().collect()),
Fields::Unnamed(_) => Err(Error::new(fields.span(), "Fields cannot be unnamed")),
// Unit is an empty struct or enum etc
Fields::Unit => Ok(vec![]),
}
}

Ok(fields)
}
/// In a group of [syn::Fields] extract any [syn::FieldNamed] fields
///
/// If there are [syn::FieldsUnnamed] then an error occurs
pub fn fields_to_named_fields(fields: &Fields) -> Result<Vec<&Field>> {
match fields {
Fields::Named(FieldsNamed { named, .. }) => Ok(named.iter().collect()),
Fields::Unnamed(_) => Err(Error::new(fields.span(), "Fields cannot be unnamed")),
// Unit is an empty struct or enum etc
Fields::Unit => Ok(vec![]),
Expand Down Expand Up @@ -109,4 +118,26 @@ mod tests {
let result = fields_named_to_ident_type(&s.fields).unwrap();
assert_eq!(result.len(), 0);
}

#[test]
fn test_fields_to_named_fields_mut() {
let mut s: ItemStruct = tokens_to_syn(quote! {
struct Point {
#[attribute]
x: f64,
y: f64
}
});
let mut result = fields_to_named_fields_mut(&mut s.fields).unwrap();
assert_eq!(result.len(), 2);
result[0].attrs.clear();

let expected: ItemStruct = tokens_to_syn(quote! {
struct Point {
x: f64,
y: f64
}
});
assert_eq!(s, expected);
}
}

0 comments on commit bc56f89

Please sign in to comment.