diff --git a/crates/cxx-qt-build/src/lib.rs b/crates/cxx-qt-build/src/lib.rs index f7673209c..350a76cc0 100644 --- a/crates/cxx-qt-build/src/lib.rs +++ b/crates/cxx-qt-build/src/lib.rs @@ -407,7 +407,7 @@ impl CxxQtBuilder { // Use a separate cc::Build for the little amount of code that needs to be linked with +whole-archive // to avoid bloating the binary. let mut cc_builder_whole_archive = cc::Build::new(); - cc_builder_whole_archive.link_lib_modifier("+whole-archive"); + //cc_builder_whole_archive.link_lib_modifier("+whole-archive"); for builder in [&mut self.cc_builder, &mut cc_builder_whole_archive] { builder.cpp(true); // MSVC diff --git a/crates/cxx-qt-gen/src/generator/cpp/property/meta.rs b/crates/cxx-qt-gen/src/generator/cpp/property/meta.rs index 08d8e1d7b..36285c197 100644 --- a/crates/cxx-qt-gen/src/generator/cpp/property/meta.rs +++ b/crates/cxx-qt-gen/src/generator/cpp/property/meta.rs @@ -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; /// 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, ) } diff --git a/crates/cxx-qt-gen/src/generator/cpp/property/mod.rs b/crates/cxx-qt-gen/src/generator/cpp/property/mod.rs index 4687e5c17..7325fd76f 100644 --- a/crates/cxx-qt-gen/src/generator/cpp/property/mod.rs +++ b/crates/cxx-qt-gen/src/generator/cpp/property/mod.rs @@ -27,13 +27,23 @@ 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 + 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)); } @@ -59,12 +69,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 }), vis: syn::Visibility::Inherited, cxx_type: Some("QColor".to_owned()), + get: None, + set: None, }, ]; let qobject_idents = create_qobjectname(); @@ -178,6 +192,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(); diff --git a/crates/cxx-qt-gen/src/generator/naming/property.rs b/crates/cxx-qt-gen/src/generator/naming/property.rs index daee78986..b6801d13f 100644 --- a/crates/cxx-qt-gen/src/generator/naming/property.rs +++ b/crates/cxx-qt-gen/src/generator/naming/property.rs @@ -93,6 +93,8 @@ pub mod tests { ty, vis: syn::Visibility::Inherited, cxx_type: None, + get: None, + set: None, }; QPropertyName::from(&property) } diff --git a/crates/cxx-qt-gen/src/generator/rust/property/getter.rs b/crates/cxx-qt-gen/src/generator/rust/property/getter.rs index 522edf56e..0416ca0e0 100644 --- a/crates/cxx-qt-gen/src/generator/rust/property/getter.rs +++ b/crates/cxx-qt-gen/src/generator/rust/property/getter.rs @@ -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, @@ -54,3 +54,48 @@ 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; + + 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) -> #ty; + } + }], + implementation: vec![ + quote! { + impl #rust_struct_name_rust { + pub fn #getter_rust<'a>(&'a self, cpp: &'a #cpp_class_name_rust) -> #ty { + cpp.#getter_rust() + } + } + }, + quote! { + impl #cpp_class_name_rust { + pub fn #getter_rust(&self) -> #ty { + (#expr)(self) + } + } + }, + quote! { + impl #cpp_class_name_rust { + pub unsafe fn #getter_mutable_rust<'a>(mut self: Pin<&'a mut Self>) -> #ty { + (#expr)(&mut self) + } + } + }, + ], + } +} diff --git a/crates/cxx-qt-gen/src/generator/rust/property/mod.rs b/crates/cxx-qt-gen/src/generator/rust/property/mod.rs index dbe80f426..a04a04918 100644 --- a/crates/cxx-qt-gen/src/generator/rust/property/mod.rs +++ b/crates/cxx-qt-gen/src/generator/rust/property/mod.rs @@ -12,7 +12,7 @@ use crate::{ naming::{property::QPropertyName, qobject::QObjectName}, rust::qobject::GeneratedRustQObjectBlocks, }, - parser::property::ParsedQProperty, + parser::property::{ParsedQProperty, MaybeCustomFn}, }; use syn::Result; @@ -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); @@ -71,18 +95,32 @@ 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 }), vis: tokens_to_syn::(quote! { pub }), cxx_type: Some("QColor".to_owned()), + get: None, + set: None, }, ParsedQProperty { ident: format_ident!("unsafe_property"), ty: tokens_to_syn::(quote! { *mut T }), vis: syn::Visibility::Inherited, cxx_type: None, + get: None, + set: None, + }, + ParsedQProperty { + ident: format_ident!("custom_getter"), + ty: tokens_to_syn::(quote! { i32 }), + vis: syn::Visibility::Inherited, + cxx_type: None, + get: Some(MaybeCustomFn::Custom(Box::new(tokens_to_syn::(quote! { Self::custom_getter_fn })))), + set: None, }, ]; let qobject_idents = create_qobjectname(); @@ -90,8 +128,8 @@ mod tests { 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 @@ -365,5 +403,59 @@ mod tests { } }) ); + + // Custom getter + + // Getter + assert_eq!( + generated.cxx_mod_contents[9], + tokens_to_syn::(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::(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::(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::(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::(quote! { + unsafe extern "C++" { + #[rust_name = "custom_getter_changed"] + fn customGetterChanged(self: Pin<&mut MyObjectQt>); + } + }) + ); } } diff --git a/crates/cxx-qt-gen/src/generator/rust/qobject.rs b/crates/cxx-qt-gen/src/generator/rust/qobject.rs index 9c98c4bea..0986083d5 100644 --- a/crates/cxx-qt-gen/src/generator/rust/qobject.rs +++ b/crates/cxx-qt-gen/src/generator/rust/qobject.rs @@ -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, @@ -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, diff --git a/crates/cxx-qt-gen/src/parser/property.rs b/crates/cxx-qt-gen/src/parser/property.rs index b9a1c81ba..ea3f6b9f0 100644 --- a/crates/cxx-qt-gen/src/parser/property.rs +++ b/crates/cxx-qt-gen/src/parser/property.rs @@ -1,9 +1,25 @@ // SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company // SPDX-FileContributor: Andrew Hayzen +// SPDX-FileContributor: Carl Schwan // // SPDX-License-Identifier: MIT OR Apache-2.0 -use syn::{Ident, Type, Visibility}; +use syn::{Ident, Type, Visibility, Expr}; + +#[derive(Debug)] +pub enum MaybeCustomFn { + Custom(Box), + Default, +} + +impl std::convert::From> for MaybeCustomFn { + fn from(item: Option) -> Self { + match item { + Some(expr) => Self::Custom(Box::new(expr)), + None => Self::Default, + } + } +} /// Describes a single field for a struct pub struct ParsedRustField { @@ -25,4 +41,14 @@ pub struct ParsedQProperty { pub vis: Visibility, /// The name of the C++ type if one has been specified pub cxx_type: Option, + /// The custom getter [syn::Expr] of the property + /// + /// If this is not set and not custom setter was specified, both a + /// default getter and setter will be generated. + pub get: Option, + /// The custom setter [syn::Expr] of the property + /// + /// If this is not set and not custom setter was specified, both a + /// default getter and setter will be generated. + pub set: Option, } diff --git a/crates/cxx-qt-gen/src/parser/qobject.rs b/crates/cxx-qt-gen/src/parser/qobject.rs index 09e4452c1..289376435 100644 --- a/crates/cxx-qt-gen/src/parser/qobject.rs +++ b/crates/cxx-qt-gen/src/parser/qobject.rs @@ -6,18 +6,47 @@ use crate::parser::{ inherit::ParsedInheritedMethod, invokable::ParsedQInvokable, - property::{ParsedQProperty, ParsedRustField}, + property::{ParsedQProperty, ParsedRustField, MaybeCustomFn}, signals::ParsedSignalsEnum, }; use crate::syntax::{ - attribute::{attribute_find_path, attribute_tokens_to_map, AttributeDefault}, + attribute::{attribute_find_path, attribute_tokens_to_map, AttributeDefault, PropertyAttribute}, fields::fields_to_named_fields_mut, }; use syn::{ spanned::Spanned, Error, Fields, Ident, ImplItem, ImplItemMethod, Item, ItemStruct, LitStr, - Result, Visibility, + Result, Visibility, Attribute, Token, }; +#[derive(Default, Debug)] +struct ReceivedAttributes { + get: Option, + set: Option, + cxx_type: Option, +} + +impl ReceivedAttributes { + fn new(attributes: impl IntoIterator) -> Self { + attributes.into_iter().fold(Self::default(), |mut this, attribute| { + match attribute { + PropertyAttribute::Get(some_fn) => this.get = Some(some_fn.into()), + PropertyAttribute::Set(some_fn) => this.set = Some(some_fn.into()), + PropertyAttribute::CxxType(cxx_type) => this.cxx_type = Some(cxx_type.value()), + }; + this + }) + } +} + +fn parse_property(attribute: Attribute) -> Result { + Ok(ReceivedAttributes::new( + attribute.parse_args_with( + syn::punctuated::Punctuated::::parse_terminated, + )? + )) +} + + /// Metadata for registering QML element #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct QmlElementMetadata { @@ -227,12 +256,12 @@ impl ParsedQObject { // Try to find any properties defined within the struct if let Some(index) = attribute_find_path(&field.attrs, &["qproperty"]) { // Parse any cxx_type in the qproperty macro - let cxx_type = attribute_tokens_to_map::( - &field.attrs[index], - AttributeDefault::None, - )? - .get("e::format_ident!("cxx_type")) - .map(|lit_str| lit_str.value()); + + let (get, set, cxx_type) = if let Ok(attr) = parse_property(field.attrs[index].clone()) { + (attr.get, attr.set, attr.cxx_type) + } else { + (None, None, None) + }; // Remove the #[qproperty] attribute field.attrs.remove(index); @@ -242,6 +271,8 @@ impl ParsedQObject { ty: field.ty.clone(), vis: field.vis.clone(), cxx_type, + get, + set, }); } else { rust_fields.push(ParsedRustField { @@ -407,11 +438,14 @@ pub mod tests { #[qproperty(cxx_type = "f32")] property_with_cxx_type: f64, + #[qproperty(get = Self::custom_getter_fn)] + custom_getter: f64, + field: f64, } }); let properties = ParsedQObject::from_struct(&item, 0).unwrap().properties; - assert_eq!(properties.len(), 3); + assert_eq!(properties.len(), 4); assert_eq!(properties[0].ident, "f64_property"); assert_eq!(properties[0].ty, f64_type()); assert!(matches!(properties[0].vis, Visibility::Inherited)); @@ -426,6 +460,12 @@ pub mod tests { assert_eq!(properties[2].ty, f64_type()); assert!(matches!(properties[2].vis, Visibility::Inherited)); assert_eq!(properties[2].cxx_type.as_ref().unwrap(), "f32"); + + assert_eq!(properties[3].ident, "custom_getter"); + assert_eq!(properties[3].ty, f64_type()); + assert!(matches!(properties[3].vis, Visibility::Inherited)); + assert!(properties[3].cxx_type.is_none()); + assert!(properties[3].get.is_some()); } #[test] diff --git a/crates/cxx-qt-gen/src/syntax/attribute.rs b/crates/cxx-qt-gen/src/syntax/attribute.rs index 884278154..7346bb6c8 100644 --- a/crates/cxx-qt-gen/src/syntax/attribute.rs +++ b/crates/cxx-qt-gen/src/syntax/attribute.rs @@ -13,7 +13,7 @@ use syn::{ punctuated::Punctuated, spanned::Spanned, token::{Comma, Paren}, - Attribute, Error, Ident, Result, Token, + Attribute, Error, Expr, Ident, Result, Token, }; /// Representation of a list of idents in an attribute, eg attribute(A, B, C) @@ -143,6 +143,46 @@ pub fn attribute_tokens_to_value(attr: &Attribute) -> Result { parse_value.parse2(attr.tokens.clone()) } +#[derive(Debug)] +pub enum PropertyAttribute { + Get(Option), + Set(Option), + CxxType(syn::LitStr) +} + +impl Parse for PropertyAttribute { + fn parse (input: syn::parse::ParseStream) -> syn::Result { + let name = input.call(syn::Ident::parse_any)?; + let name_str = name.to_string(); + + let res = if input.peek(Token![=]) { + // attributes with only the identifier + // e.g. #[qproperty(get = Self::hello_fn)] + let _assign_token: Token![=] = input.parse()?; + // name = expr | type | ident + match &*name_str { + "get" => PropertyAttribute::Get(Some(input.parse()?)), + "set" => PropertyAttribute::Set(Some(input.parse()?)), + "cxx_type" => PropertyAttribute::CxxType(input.parse()?), + _ => { + panic!("Invalid attribute for property") + } + } + } else { + // attributes with only the identifier + // e.g. #[qproperty(get, set)] + match &*name_str { + "get" => PropertyAttribute::Get(None), + "set" => PropertyAttribute::Set(None), + _ => { + panic!("Invalid attribute for property") + } + } + }; + Ok(res) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/examples/qml_features/rust/src/properties.rs b/examples/qml_features/rust/src/properties.rs index def7d1d66..30af91ddd 100644 --- a/examples/qml_features/rust/src/properties.rs +++ b/examples/qml_features/rust/src/properties.rs @@ -26,6 +26,15 @@ mod ffi { #[qproperty] status_message: QString, + + #[qproperty(get)] + readonly_prop: QString, + + #[qproperty(get = Self::custom_prop_fn)] + custom_prop: bool, + + #[qproperty(get = Self::custom_prop_str_fn)] + readonly_prop_str: QString, } // ANCHOR_END: book_properties_struct @@ -37,6 +46,9 @@ mod ffi { connected_url: QUrl::default(), previous_connected_url: QUrl::default(), status_message: QString::from("Disconnected"), + readonly_prop: QString::from("Read only"), + custom_prop: false, + readonly_prop_str: QString::from("Not read"), } } } @@ -76,5 +88,13 @@ mod ffi { self.as_mut().set_previous_connected_url(previous_url); self.set_connected_url(QUrl::default()); } + + pub fn custom_prop_fn(&self) -> bool { + true + } + + pub fn custom_prop_str_fn(&self) -> QString { + QString::from("hello") + } } }