Skip to content

Commit

Permalink
Add support for attributes to #[qproperty]
Browse files Browse the repository at this point in the history
Make it possible to define if only a getter should be generated or only
a setter and/or define special getter and setter.

Syntax is as follow

```rust
struct MyObject {
    #[qproperty]
    prop1: f32, // generate default getter and setter

    #[qproperty(get)]
    prop2: f32, // generate default getter only

    #[qproperty(set)]
    prop3: f32, // generate default setter only

    #[qproperty(get = Self::prop4_fn)]
    prop4: f32, // use prop4_fn as getter
}
```

WIP since the custom getter part doesn't work yet

Signed-off-by: Carl Schwan <[email protected]>
  • Loading branch information
Carl Schwan committed Feb 20, 2023
1 parent bf94b05 commit a6171e2
Show file tree
Hide file tree
Showing 9 changed files with 325 additions and 43 deletions.
23 changes: 19 additions & 4 deletions crates/cxx-qt-gen/src/generator/cpp/property/meta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,30 @@
// SPDX-License-Identifier: MIT OR Apache-2.0

use crate::generator::{cpp::types::CppType, naming::property::QPropertyName};
use crate::parser::property::{ParsedQProperty, MaybeCustomFn};

/// Generate the metaobject line for a given property
pub fn generate(idents: &QPropertyName, cxx_ty: &CppType) -> String {
pub fn generate(idents: &QPropertyName, property: &ParsedQProperty, cxx_ty: &CppType) -> String {
let getter_setter_not_explicit = property.get.is_none() && property.set.is_none();

let getter = if getter_setter_not_explicit || property.get.is_some() {
format!("READ {ident_getter}", ident_getter = idents.getter.cpp)
} else {
String::new()
};

let setter = if getter_setter_not_explicit || property.set.is_some() {
format!("WRITE {ident_setter}", ident_setter = idents.setter.cpp)
} else {
String::new()
};

format!(
"Q_PROPERTY({ty} {ident} READ {ident_getter} WRITE {ident_setter} NOTIFY {ident_notify})",
"Q_PROPERTY({ty} {ident} {getter} {setter} NOTIFY {ident_notify})",
ty = cxx_ty.as_cxx_ty(),
ident = idents.name.cpp,
ident_getter = idents.getter.cpp,
ident_setter = idents.setter.cpp,
getter = getter,
setter = setter,
ident_notify = idents.notify.cpp,
)
}
36 changes: 28 additions & 8 deletions crates/cxx-qt-gen/src/generator/cpp/property/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::generator::{
cpp::{qobject::GeneratedCppQObjectBlocks, types::CppType},
naming::{property::QPropertyName, qobject::QObjectName},
};
use crate::parser::{cxxqtdata::ParsedCxxMappings, property::ParsedQProperty};
use crate::parser::{cxxqtdata::ParsedCxxMappings, property::{ParsedQProperty, MaybeCustomFn}};
use syn::Result;

mod getter;
Expand All @@ -27,13 +27,27 @@ pub fn generate_cpp_properties(
let idents = QPropertyName::from(property);
let cxx_ty = CppType::from(&property.ty, &property.cxx_type, cxx_mappings)?;

generated.metaobjects.push(meta::generate(&idents, &cxx_ty));
generated
.methods
.push(getter::generate(&idents, &qobject_ident, &cxx_ty));
generated
.methods
.push(setter::generate(&idents, &qobject_ident, &cxx_ty));
generated.metaobjects.push(meta::generate(&idents, &property, &cxx_ty));

let getter_setter_not_explicit = property.get.is_none() && property.set.is_none();

// Getters
if getter_setter_not_explicit || property.get.is_some() {
generated
.methods
.push(getter::generate(&idents, &qobject_ident, &cxx_ty));
}

// Setters
let default_setter = match property.set {
Some(MaybeCustomFn::Default) => true,
_ => false,
};
if getter_setter_not_explicit || property.set.is_some() {
generated
.methods
.push(setter::generate(&idents, &qobject_ident, &cxx_ty));
}
generated.methods.push(signal::generate(&idents));
}

Expand All @@ -59,12 +73,16 @@ mod tests {
ty: tokens_to_syn(quote! { i32 }),
vis: syn::Visibility::Inherited,
cxx_type: None,
get: None,
set: None,
},
ParsedQProperty {
ident: format_ident!("opaque_property"),
ty: tokens_to_syn(quote! { UniquePtr<QColor> }),
vis: syn::Visibility::Inherited,
cxx_type: Some("QColor".to_owned()),
get: None,
set: None,
},
];
let qobject_idents = create_qobjectname();
Expand Down Expand Up @@ -178,6 +196,8 @@ mod tests {
ty: tokens_to_syn(quote! { A1 }),
vis: syn::Visibility::Inherited,
cxx_type: None,
get: None,
set: None,
}];
let qobject_idents = create_qobjectname();

Expand Down
2 changes: 2 additions & 0 deletions crates/cxx-qt-gen/src/generator/naming/property.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ pub mod tests {
ty,
vis: syn::Visibility::Inherited,
cxx_type: None,
get: None,
set: None,
};
QPropertyName::from(&property)
}
Expand Down
48 changes: 47 additions & 1 deletion crates/cxx-qt-gen/src/generator/rust/property/getter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::generator::{
rust::fragment::RustFragmentPair,
};
use quote::quote;
use syn::Type;
use syn::{Type, Expr};

pub fn generate(
idents: &QPropertyName,
Expand Down Expand Up @@ -54,3 +54,49 @@ pub fn generate(
],
}
}

pub fn generate_custom(
idents: &QPropertyName,
qobject_idents: &QObjectName,
expr: &Expr,
ty: &Type,
) -> RustFragmentPair {
let cpp_class_name_rust = &qobject_idents.cpp_class.rust;
let rust_struct_name_rust = &qobject_idents.rust_struct.rust;
let getter_cpp = idents.getter.cpp.to_string();
let getter_rust = &idents.getter.rust;
let getter_mutable_rust = &idents.getter_mutable.rust;
let ident = &idents.name.rust;

RustFragmentPair {
cxx_bridge: vec![quote! {
extern "Rust" {
#[cxx_name = #getter_cpp]
unsafe fn #getter_rust<'a>(self: &'a #rust_struct_name_rust, cpp: &'a #cpp_class_name_rust) -> &'a #ty;
}
}],
implementation: vec![
quote! {
impl #rust_struct_name_rust {
pub fn #getter_rust<'a>(&'a self, cpp: &'a #cpp_class_name_rust) -> &'a #ty {
cpp.#getter_rust()
}
}
},
quote! {
impl #cpp_class_name_rust {
pub fn #getter_rust(&self) -> &#ty {
(#expr)(&self.rust())
}
}
},
quote! {
impl #cpp_class_name_rust {
pub unsafe fn #getter_mutable_rust<'a>(mut self: Pin<&'a mut Self>) -> &'a mut #ty {
(#expr)(&mut self.rust_mut().get_unchecked_mut())
}
}
},
],
}
}
126 changes: 109 additions & 17 deletions crates/cxx-qt-gen/src/generator/rust/property/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::{
naming::{property::QPropertyName, qobject::QObjectName},
rust::qobject::GeneratedRustQObjectBlocks,
},
parser::property::ParsedQProperty,
parser::property::{ParsedQProperty, MaybeCustomFn},
};
use syn::Result;

Expand All @@ -25,23 +25,47 @@ pub fn generate_rust_properties(
for property in properties {
let idents = QPropertyName::from(property);

let getter_setter_not_explicit = property.get.is_none() && property.set.is_none();

// Getters
let getter = getter::generate(&idents, qobject_idents, &property.ty);
generated
.cxx_mod_contents
.append(&mut getter.cxx_bridge_as_items()?);
generated
.cxx_qt_mod_contents
.append(&mut getter.implementation_as_items()?);
let default_getter = match property.get {
Some(MaybeCustomFn::Default) => true,
_ => false,
};
if getter_setter_not_explicit || default_getter {
let getter = getter::generate(&idents, qobject_idents, &property.ty);
generated
.cxx_mod_contents
.append(&mut getter.cxx_bridge_as_items()?);
generated
.cxx_qt_mod_contents
.append(&mut getter.implementation_as_items()?);
} else if let Some(getter_attr) = &property.get {
if let MaybeCustomFn::Custom(getter_fn) = getter_attr {
let getter = getter::generate_custom(&idents, qobject_idents, getter_fn, &property.ty);
generated
.cxx_mod_contents
.append(&mut getter.cxx_bridge_as_items().unwrap());
generated
.cxx_qt_mod_contents
.append(&mut getter.implementation_as_items().unwrap());
}
}

// Setters
let setter = setter::generate(&idents, qobject_idents, &property.ty);
generated
.cxx_mod_contents
.append(&mut setter.cxx_bridge_as_items()?);
generated
.cxx_qt_mod_contents
.append(&mut setter.implementation_as_items()?);
let default_setter = match property.set {
Some(MaybeCustomFn::Default) => true,
_ => false,
};
if getter_setter_not_explicit || default_setter {
let setter = setter::generate(&idents, qobject_idents, &property.ty);
generated
.cxx_mod_contents
.append(&mut setter.cxx_bridge_as_items()?);
generated
.cxx_qt_mod_contents
.append(&mut setter.implementation_as_items()?);
}

// Signals
let notify = signal::generate(&idents, qobject_idents);
Expand Down Expand Up @@ -71,27 +95,41 @@ mod tests {
ty: tokens_to_syn::<syn::Type>(quote! { i32 }),
vis: syn::Visibility::Inherited,
cxx_type: None,
get: None,
set: None,
},
ParsedQProperty {
ident: format_ident!("opaque_property"),
ty: tokens_to_syn::<syn::Type>(quote! { UniquePtr<QColor> }),
vis: tokens_to_syn::<syn::Visibility>(quote! { pub }),
cxx_type: Some("QColor".to_owned()),
get: None,
set: None,
},
ParsedQProperty {
ident: format_ident!("unsafe_property"),
ty: tokens_to_syn::<syn::Type>(quote! { *mut T }),
vis: syn::Visibility::Inherited,
cxx_type: None,
get: None,
set: None,
},
ParsedQProperty {
ident: format_ident!("custom_getter"),
ty: tokens_to_syn::<syn::Type>(quote! { i32 }),
vis: syn::Visibility::Inherited,
cxx_type: None,
get: Some(MaybeCustomFn::Custom(Box::new(tokens_to_syn::<syn::Expr>(quote! { Self::custom_getter_fn })))),
set: None,
},
];
let qobject_idents = create_qobjectname();

let generated = generate_rust_properties(&properties, &qobject_idents).unwrap();

// Check that we have the expected number of blocks
assert_eq!(generated.cxx_mod_contents.len(), 9);
assert_eq!(generated.cxx_qt_mod_contents.len(), 15);
assert_eq!(generated.cxx_mod_contents.len(), 11);
assert_eq!(generated.cxx_qt_mod_contents.len(), 18);

// Trivial Property

Expand Down Expand Up @@ -365,5 +403,59 @@ mod tests {
}
})
);

// Custom getter

// Getter
assert_eq!(
generated.cxx_mod_contents[9],
tokens_to_syn::<syn::Item>(quote! {
extern "Rust" {
#[cxx_name = "getCustomGetter"]
unsafe fn custom_getter<'a>(self: &'a MyObject, cpp: &'a MyObjectQt) -> &'a i32;
}
})
);
assert_eq!(
generated.cxx_qt_mod_contents[15],
tokens_to_syn::<syn::Item>(quote! {
impl MyObject {
pub fn custom_getter<'a>(&'a self, cpp: &'a MyObjectQt) -> &'a i32 {
cpp.custom_getter()
}
}
})
);
assert_eq!(
generated.cxx_qt_mod_contents[16],
tokens_to_syn::<syn::Item>(quote! {
impl MyObjectQt {
pub fn custom_getter(&self) -> &i32 {
(Self::custom_getter_fn)(&self.rust())
}
}
})
);
assert_eq!(
generated.cxx_qt_mod_contents[17],
tokens_to_syn::<syn::Item>(quote! {
impl MyObjectQt {
pub unsafe fn custom_getter_mut<'a>(mut self: Pin<&'a mut Self>) -> &'a mut i32 {
(Self::custom_getter_fn)(&mut self.rust_mut().get_unchecked_mut())
}
}
})
);

// Notify
assert_eq!(
generated.cxx_mod_contents[10],
tokens_to_syn::<syn::Item>(quote! {
unsafe extern "C++" {
#[rust_name = "custom_getter_changed"]
fn customGetterChanged(self: Pin<&mut MyObjectQt>);
}
})
);
}
}
3 changes: 2 additions & 1 deletion crates/cxx-qt-gen/src/generator/rust/qobject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use crate::{
use quote::quote;
use syn::{Ident, ImplItemMethod, Item, Result};

#[derive(Default)]
#[derive(Default, Debug)]
pub struct GeneratedRustQObjectBlocks {
/// Module for the CXX bridge
pub cxx_mod_contents: Vec<Item>,
Expand All @@ -33,6 +33,7 @@ impl GeneratedRustQObjectBlocks {
}
}

#[derive(Debug)]
pub struct GeneratedRustQObject {
/// Ident of the Rust name for the C++ object
pub cpp_struct_ident: Ident,
Expand Down
Loading

0 comments on commit a6171e2

Please sign in to comment.