From da268d6135125eac359f25b05a4fe940300fb1ef Mon Sep 17 00:00:00 2001 From: Ben Ford Date: Fri, 4 Apr 2025 15:53:20 +0100 Subject: [PATCH 1/9] Add shorthand support for &self and Pin<&mut 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 --- crates/cxx-qt-gen/src/parser/cxxqtdata.rs | 139 +++++++++++++++++- crates/cxx-qt-gen/src/parser/inherit.rs | 16 +- crates/cxx-qt-gen/src/parser/method.rs | 12 ++ crates/cxx-qt-gen/src/parser/signals.rs | 7 + crates/cxx-qt-gen/src/syntax/foreignmod.rs | 11 +- crates/cxx-qt-gen/test_inputs/signals.rs | 8 +- examples/qml_minimal/rust/src/cxxqt_object.rs | 14 +- 7 files changed, 179 insertions(+), 28 deletions(-) diff --git a/crates/cxx-qt-gen/src/parser/cxxqtdata.rs b/crates/cxx-qt-gen/src/parser/cxxqtdata.rs index ebb572f5a..c3ccabf49 100644 --- a/crates/cxx-qt-gen/src/parser/cxxqtdata.rs +++ b/crates/cxx-qt-gen/src/parser/cxxqtdata.rs @@ -6,6 +6,7 @@ use super::qnamespace::ParsedQNamespace; use super::trait_impl::TraitImpl; use crate::naming::cpp::err_unsupported_item; +use crate::parser::method::MethodFields; use crate::parser::CaseConversion; use crate::{ parser::{ @@ -17,6 +18,8 @@ use crate::{ path::path_compare_str, }, }; +use quote::format_ident; +use std::ops::DerefMut; use syn::{ spanned::Spanned, Error, ForeignItem, Ident, Item, ItemEnum, ItemForeignMod, ItemImpl, ItemMacro, Meta, Result, @@ -65,6 +68,33 @@ impl ParsedCxxQtData { } } + fn try_inline_self_types( + inline: bool, + type_to_inline: &Option, + invokables: &mut [impl DerefMut], + ) -> Result<()> { + for method in invokables.iter_mut() { + if method.self_unresolved { + if inline { + if let Some(inline_type) = type_to_inline.clone() { + method.qobject_ident = inline_type; + } else { + return Err(Error::new( + method.method.span(), + "Expected a type to inline, no `qobject` typename was passed!", + )); + } + } else { + return Err(Error::new( + method.method.span(), + "`Self` type can only be inferred if the extern block contains only one `qobject`.", + )); + } + } + } + Ok(()) + } + /// Determine if the given [syn::Item] is a CXX-Qt related item /// If it is then add the [syn::Item] into qobjects BTreeMap /// Otherwise return the [syn::Item] to pass through to CXX @@ -139,6 +169,12 @@ impl ParsedCxxQtData { let auto_case = CaseConversion::from_attrs(&attrs)?; + let mut qobjects = vec![]; + + let mut methods = vec![]; + let mut signals = vec![]; + let mut inherited = vec![]; + let namespace = attrs .get("namespace") .map(|attr| expr_to_string(&attr.meta.require_name_value()?.value)) @@ -159,7 +195,7 @@ impl ParsedCxxQtData { return Err(Error::new(foreign_fn.span(), "block must be declared `unsafe extern \"RustQt\"` if it contains any safe-to-call #[inherit] qsignals")); } - self.signals.push(parsed_signal_method); + signals.push(parsed_signal_method); // Test if the function is an inheritance method // @@ -175,7 +211,7 @@ impl ParsedCxxQtData { let parsed_inherited_method = ParsedInheritedMethod::parse(foreign_fn, auto_case)?; - self.inherited_methods.push(parsed_inherited_method); + inherited.push(parsed_inherited_method); // Remaining methods are either C++ methods or invokables } else { let parsed_method = ParsedMethod::parse( @@ -183,7 +219,7 @@ impl ParsedCxxQtData { auto_case, foreign_mod.unsafety.is_some(), )?; - self.methods.push(parsed_method); + methods.push(parsed_method); } } ForeignItem::Verbatim(tokens) => { @@ -199,12 +235,28 @@ impl ParsedCxxQtData { // Note that we assume a compiler error will occur later // if you had two structs with the same name - self.qobjects.push(qobject); + qobjects.push(qobject); } - // Const Macro, Type are unsupported in extern "RustQt" for now + // Const, Macro, Type are unsupported in extern "RustQt" for now _ => return Err(err_unsupported_item(&item)), } } + + // If there is exaclty one qobject in the block, it can be inlined as a self type. + let inline_self = qobjects.len() == 1; + let inline_ident = qobjects + .last() + .map(|obj| format_ident!("{}", obj.name.cxx_unqualified())); + + Self::try_inline_self_types(inline_self, &inline_ident, &mut methods)?; + Self::try_inline_self_types(inline_self, &inline_ident, &mut signals)?; + Self::try_inline_self_types(inline_self, &inline_ident, &mut inherited)?; + + self.qobjects.extend(qobjects); + self.methods.extend(methods); + self.signals.extend(signals); + self.inherited_methods.extend(inherited); + Ok(()) } @@ -735,4 +787,81 @@ mod tests { Some("b") ); } + + #[test] + fn test_self_inlining_ref() { + let mut parsed_cxxqtdata = ParsedCxxQtData::new(format_ident!("ffi"), None); + let extern_rust_qt: Item = parse_quote! { + unsafe extern "RustQt" { + #[qobject] + type MyObject = super::T; + + fn my_method(&self); + + #[inherit] + fn my_inherited_method(&self); + } + }; + + parsed_cxxqtdata.parse_cxx_qt_item(extern_rust_qt).unwrap(); + } + + #[test] + fn test_self_inlining_pin() { + let mut parsed_cxxqtdata = ParsedCxxQtData::new(format_ident!("ffi"), None); + let extern_rust_qt: Item = parse_quote! { + unsafe extern "RustQt" { + #[qobject] + type MyObject = super::T; + + #[qsignal] + fn my_signal(self: Pin<&mut Self>); + } + }; + + parsed_cxxqtdata.parse_cxx_qt_item(extern_rust_qt).unwrap(); + } + + #[test] + fn test_self_inlining_methods_invalid() { + assert_parse_errors! { + |item| ParsedCxxQtData::new(format_ident!("ffi"), None).parse_cxx_qt_item(item) => + // No QObject in block + { + extern "RustQt" { + fn my_method(&self); + } + } + + { + extern "RustQt" { + fn my_method(self: Pin<&mut Self>); + } + } + // More than 1 QObjects in block + { + extern "RustQt" { + #[qobject] + type MyObject = super::T; + + #[qobject] + type MyOtherObject = super::S; + + fn my_method(&self); + } + } + } + } + + #[test] + fn test_invalid_inline_call() { + let method_sig = parse_quote! { + fn test(&self); + }; + let mut methods = vec![ParsedMethod::mock_qinvokable(&method_sig)]; + + // If inlining is set to take place, an Ident is required to inline, here it is `None` + let data = ParsedCxxQtData::try_inline_self_types(true, &None, &mut methods); + assert!(data.is_err()); + } } diff --git a/crates/cxx-qt-gen/src/parser/inherit.rs b/crates/cxx-qt-gen/src/parser/inherit.rs index a09081710..9ebb5af17 100644 --- a/crates/cxx-qt-gen/src/parser/inherit.rs +++ b/crates/cxx-qt-gen/src/parser/inherit.rs @@ -8,6 +8,7 @@ use crate::parser::{ }; use core::ops::Deref; use quote::format_ident; +use std::ops::DerefMut; use syn::{Attribute, ForeignItemFn, Ident, Result}; /// Describes a method found in an extern "RustQt" with #[inherit] @@ -56,6 +57,12 @@ impl Deref for ParsedInheritedMethod { } } +impl DerefMut for ParsedInheritedMethod { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.method_fields + } +} + #[cfg(test)] mod tests { use super::*; @@ -68,7 +75,6 @@ mod tests { |item| ParsedInheritedMethod::parse(item, CaseConversion::none()) => // Missing self type - { fn test(&self); } { fn test(self: &mut T); } // Pointer types { fn test(self: *const T); } @@ -91,6 +97,14 @@ mod tests { CaseConversion::none() ) .is_ok()); + // T by ref is ok in this shorthand (provided the block has one QObject) + assert!(ParsedInheritedMethod::parse( + parse_quote! { + fn test(&self); + }, + CaseConversion::none() + ) + .is_ok()); // T by Pin assert!(ParsedInheritedMethod::parse( parse_quote! { diff --git a/crates/cxx-qt-gen/src/parser/method.rs b/crates/cxx-qt-gen/src/parser/method.rs index 7c161c4b8..baecdccf7 100644 --- a/crates/cxx-qt-gen/src/parser/method.rs +++ b/crates/cxx-qt-gen/src/parser/method.rs @@ -9,7 +9,9 @@ use crate::{ syntax::{foreignmod, types}, }; use core::ops::Deref; +use quote::format_ident; use std::collections::{BTreeMap, HashSet}; +use std::ops::DerefMut; use syn::{Attribute, ForeignItemFn, Ident, Result}; /// Describes a C++ specifier for the Q_INVOKABLE @@ -146,6 +148,12 @@ impl Deref for ParsedMethod { } } +impl DerefMut for ParsedMethod { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.method_fields + } +} + /// Struct with common fields between Invokable types. /// These types are ParsedSignal, ParsedMethod and ParsedInheritedMethod #[derive(Clone)] @@ -156,6 +164,7 @@ pub struct MethodFields { pub parameters: Vec, pub safe: bool, pub name: Name, + pub self_unresolved: bool, } impl MethodFields { @@ -164,6 +173,8 @@ impl MethodFields { let (qobject_ident, mutability) = types::extract_qobject_ident(&self_receiver.ty)?; let mutable = mutability.is_some(); + let self_unresolved = qobject_ident == format_ident!("Self"); + let parameters = ParsedFunctionParameter::parse_all_ignoring_receiver(&method.sig)?; let safe = method.sig.unsafety.is_none(); let name = @@ -176,6 +187,7 @@ impl MethodFields { parameters, safe, name, + self_unresolved, }) } } diff --git a/crates/cxx-qt-gen/src/parser/signals.rs b/crates/cxx-qt-gen/src/parser/signals.rs index 3cbf7e754..81e264ebb 100644 --- a/crates/cxx-qt-gen/src/parser/signals.rs +++ b/crates/cxx-qt-gen/src/parser/signals.rs @@ -8,6 +8,7 @@ use crate::{ syntax::path::path_compare_str, }; use core::ops::Deref; +use std::ops::DerefMut; use syn::{spanned::Spanned, Attribute, Error, ForeignItemFn, Result, Visibility}; #[derive(Clone)] @@ -74,6 +75,12 @@ impl Deref for ParsedSignal { } } +impl DerefMut for ParsedSignal { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.method_fields + } +} + #[cfg(test)] mod tests { use syn::parse_quote; diff --git a/crates/cxx-qt-gen/src/syntax/foreignmod.rs b/crates/cxx-qt-gen/src/syntax/foreignmod.rs index 7b00734bf..be1565fef 100644 --- a/crates/cxx-qt-gen/src/syntax/foreignmod.rs +++ b/crates/cxx-qt-gen/src/syntax/foreignmod.rs @@ -174,14 +174,7 @@ pub fn self_type_from_foreign_fn(signature: &Signature) -> Result { )); } - if receiver.reference.is_some() { - return Err(Error::new( - receiver.span(), - "Reference on self (i.e. `&self`) are not supported! Use `self: &T` instead", - )); - } - - if receiver.colon_token.is_none() { + if receiver.colon_token.is_none() && receiver.reference.is_none() { return Err(Error::new( receiver.span(), "`self` is not supported as receiver! Use `self: T` to indicate a type.", @@ -254,8 +247,6 @@ mod tests { { fn foo(self); } // self with mut { fn foo(mut self: T); } - // self reference - { fn foo(&self); } // self reference with mut { fn foo(&mut self); } // attribute on self type diff --git a/crates/cxx-qt-gen/test_inputs/signals.rs b/crates/cxx-qt-gen/test_inputs/signals.rs index 3aca1f5bd..8411a9549 100644 --- a/crates/cxx-qt-gen/test_inputs/signals.rs +++ b/crates/cxx-qt-gen/test_inputs/signals.rs @@ -24,11 +24,11 @@ mod ffi { type MyObject = super::MyObjectRust; #[qsignal] - fn ready(self: Pin<&mut MyObject>); + fn ready(self: Pin<&mut Self>); #[qsignal] fn data_changed( - self: Pin<&mut MyObject>, + self: Pin<&mut Self>, first: i32, second: UniquePtr, third: QPoint, @@ -39,7 +39,7 @@ mod ffi { #[inherit] #[qsignal] fn base_class_new_data( - self: Pin<&mut MyObject>, + self: Pin<&mut Self>, first: i32, second: UniquePtr, third: QPoint, @@ -47,6 +47,6 @@ mod ffi { ); #[qinvokable] - fn invokable(self: Pin<&mut MyObject>); + fn invokable(self: Pin<&mut Self>); } } diff --git a/examples/qml_minimal/rust/src/cxxqt_object.rs b/examples/qml_minimal/rust/src/cxxqt_object.rs index 84ac60084..15878e1e3 100644 --- a/examples/qml_minimal/rust/src/cxxqt_object.rs +++ b/examples/qml_minimal/rust/src/cxxqt_object.rs @@ -21,8 +21,8 @@ pub mod qobject { } // ANCHOR_END: book_qstring_import - // ANCHOR: book_rustobj_struct_signature extern "RustQt" { + // ANCHOR: book_rustobj_struct_signature // The QObject definition // We tell CXX-Qt that we want a QObject class with the name MyObject // based on the Rust struct MyObjectRust. @@ -32,21 +32,19 @@ pub mod qobject { #[qproperty(QString, string)] #[namespace = "my_object"] type MyObject = super::MyObjectRust; - } - // ANCHOR_END: book_rustobj_struct_signature + // ANCHOR_END: book_rustobj_struct_signature - // ANCHOR: book_rustobj_invokable_signature - extern "RustQt" { + // ANCHOR: book_rustobj_invokable_signature // Declare the invokable methods we want to expose on the QObject #[qinvokable] #[cxx_name = "incrementNumber"] - fn increment_number(self: Pin<&mut MyObject>); + fn increment_number(self: Pin<&mut Self>); #[qinvokable] #[cxx_name = "sayHi"] - fn say_hi(self: &MyObject, string: &QString, number: i32); + fn say_hi(&self, string: &QString, number: i32); + // ANCHOR_END: book_rustobj_invokable_signature } - // ANCHOR_END: book_rustobj_invokable_signature } // ANCHOR: book_use From e0493a0e101a62f4b1e9c1cc19da0078c4f85056 Mon Sep 17 00:00:00 2001 From: Ben Ford Date: Fri, 4 Apr 2025 17:17:06 +0100 Subject: [PATCH 2/9] Update book and docs --- book/src/getting-started/2-our-first-cxx-qt-module.md | 7 +++++++ crates/cxx-qt-gen/src/parser/cxxqtdata.rs | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/book/src/getting-started/2-our-first-cxx-qt-module.md b/book/src/getting-started/2-our-first-cxx-qt-module.md index b718f9986..d001750bc 100644 --- a/book/src/getting-started/2-our-first-cxx-qt-module.md +++ b/book/src/getting-started/2-our-first-cxx-qt-module.md @@ -143,6 +143,13 @@ These functions then need to be implemented **outside** the bridge using `impl q {{#include ../../../examples/qml_minimal/rust/src/cxxqt_object.rs:book_rustobj_invokable_impl}} ``` +### Inlining the self receiver + +If an `extern "RustQt"` block contains exactly one `QObject`, the self type of methods can be inferred. +For instance, in a block with multiple or no `QObject`s, a function like `foo(&self)` or `foo(self: Pin<&mut Self>)` +would not compile, but will compile with the `Self` type set to that blocks `QObject`. +This is how CXX [handles it](https://cxx.rs/extern-rust.html) (see the Methods heading). + This setup is a bit unusual, as the type `qobject::MyObject` is actually defined in C++. However, it is still possible to add member functions to it in Rust and then expose them back to C++. This is the usual workflow for `QObject`s in CXX-Qt. diff --git a/crates/cxx-qt-gen/src/parser/cxxqtdata.rs b/crates/cxx-qt-gen/src/parser/cxxqtdata.rs index c3ccabf49..cf20ed2a9 100644 --- a/crates/cxx-qt-gen/src/parser/cxxqtdata.rs +++ b/crates/cxx-qt-gen/src/parser/cxxqtdata.rs @@ -68,6 +68,11 @@ impl ParsedCxxQtData { } } + /// Inline any `Self` types in the methods signatures with the Ident of a qobject passed in + /// + /// If there are unresolved methods in the list, but inline is false, it will error, + /// as the self inlining is only available if there is exactly one `QObject` in the block, + /// and this indicates that no inlining can be done, but some `Self` types were present. fn try_inline_self_types( inline: bool, type_to_inline: &Option, From c15c2bdf47f23a2ecf2b85b0df3a216eabae5e5c Mon Sep 17 00:00:00 2001 From: Ben Ford Date: Fri, 4 Apr 2025 17:50:18 +0100 Subject: [PATCH 3/9] Use Self type in examples --- .../test_inputs/passthrough_and_naming.rs | 8 ++--- examples/qml_features/rust/src/containers.rs | 12 +++---- .../rust/src/custom_parent_class.rs | 6 ++-- examples/qml_features/rust/src/externcxxqt.rs | 7 ++-- .../rust/src/multiple_qobjects.rs | 32 +++++++------------ examples/qml_features/rust/src/naming.rs | 3 +- .../qml_features/rust/src/nested_qobjects.rs | 2 +- examples/qml_features/rust/src/properties.rs | 6 ++-- .../qml_features/rust/src/serialisation.rs | 6 ++-- examples/qml_features/rust/src/singleton.rs | 2 +- examples/qml_features/rust/src/threading.rs | 16 ++++------ examples/qml_features/rust/src/types.rs | 4 +-- 12 files changed, 44 insertions(+), 60 deletions(-) diff --git a/crates/cxx-qt-gen/test_inputs/passthrough_and_naming.rs b/crates/cxx-qt-gen/test_inputs/passthrough_and_naming.rs index cfa3bae78..bb4fa2c2e 100644 --- a/crates/cxx-qt-gen/test_inputs/passthrough_and_naming.rs +++ b/crates/cxx-qt-gen/test_inputs/passthrough_and_naming.rs @@ -113,19 +113,17 @@ pub mod ffi { fn errorOccurred(self: Pin<&mut ExternObject>); } - extern "RustQt" { + unsafe extern "RustQt" { #[qobject] #[base = QStringListModel] #[qproperty(i32, property_name, cxx_name = "propertyName")] type MyObject = super::MyObjectRust; - } - unsafe extern "RustQt" { #[qsignal] - fn ready(self: Pin<&mut MyObject>); + fn ready(self: Pin<&mut Self>); #[qinvokable] - fn invokable_name(self: Pin<&mut MyObject>); + fn invokable_name(self: Pin<&mut Self>); } extern "RustQt" { diff --git a/examples/qml_features/rust/src/containers.rs b/examples/qml_features/rust/src/containers.rs index cd7135b3e..a63455219 100644 --- a/examples/qml_features/rust/src/containers.rs +++ b/examples/qml_features/rust/src/containers.rs @@ -46,32 +46,32 @@ pub mod qobject { /// Reset all the containers #[qinvokable] - fn reset(self: Pin<&mut RustContainers>); + fn reset(self: Pin<&mut Self>); /// Append the given number to the vector container #[qinvokable] #[cxx_name = "appendVector"] - fn append_vector(self: Pin<&mut RustContainers>, value: i32); + fn append_vector(self: Pin<&mut Self>, value: i32); /// Append the given number to the list container #[qinvokable] #[cxx_name = "appendList"] - fn append_list(self: Pin<&mut RustContainers>, value: i32); + fn append_list(self: Pin<&mut Self>, value: i32); /// Insert the given number into the set container #[qinvokable] #[cxx_name = "insertSet"] - fn insert_set(self: Pin<&mut RustContainers>, value: i32); + fn insert_set(self: Pin<&mut Self>, value: i32); /// Insert the given string and variant to the hash container #[qinvokable] #[cxx_name = "insertHash"] - fn insert_hash(self: Pin<&mut RustContainers>, key: QString, value: QVariant); + fn insert_hash(self: Pin<&mut Self>, key: QString, value: QVariant); /// Insert the given string and variant to the map container #[qinvokable] #[cxx_name = "insertMap"] - fn insert_map(self: Pin<&mut RustContainers>, key: QString, value: QVariant); + fn insert_map(self: Pin<&mut Self>, key: QString, value: QVariant); } } diff --git a/examples/qml_features/rust/src/custom_parent_class.rs b/examples/qml_features/rust/src/custom_parent_class.rs index 3b36884ba..c0237cece 100644 --- a/examples/qml_features/rust/src/custom_parent_class.rs +++ b/examples/qml_features/rust/src/custom_parent_class.rs @@ -43,15 +43,15 @@ pub mod qobject { /// Override QQuickPaintedItem::paint to draw two rectangles in Rust using QPainter #[qinvokable] #[cxx_override] - unsafe fn paint(self: Pin<&mut CustomParentClass>, painter: *mut QPainter); + unsafe fn paint(self: Pin<&mut Self>, painter: *mut QPainter); /// Define that we need to inherit size() from the base class #[inherit] - fn size(self: &CustomParentClass) -> QSizeF; + fn size(self: &Self) -> QSizeF; /// Define that we need to inherit update() from the base class #[inherit] - fn update(self: Pin<&mut CustomParentClass>); + fn update(self: Pin<&mut Self>); } impl cxx_qt::Initialize for CustomParentClass {} diff --git a/examples/qml_features/rust/src/externcxxqt.rs b/examples/qml_features/rust/src/externcxxqt.rs index 4d2a74a46..239c7a0e1 100644 --- a/examples/qml_features/rust/src/externcxxqt.rs +++ b/examples/qml_features/rust/src/externcxxqt.rs @@ -36,15 +36,12 @@ pub mod ffi { #[qinvokable] #[cxx_name = "connectToExternal"] - unsafe fn connect_to_external( - self: Pin<&mut ExternalCxxQtHelper>, - external: *mut ExternalQObject, - ); + unsafe fn connect_to_external(self: Pin<&mut Self>, external: *mut ExternalQObject); #[qinvokable] #[cxx_name = "triggerOnExternal"] unsafe fn trigger_on_external( - self: Pin<&mut ExternalCxxQtHelper>, + self: Pin<&mut Self>, external: *mut ExternalQObject, amount: u32, ); diff --git a/examples/qml_features/rust/src/multiple_qobjects.rs b/examples/qml_features/rust/src/multiple_qobjects.rs index f1a24ea6b..128063126 100644 --- a/examples/qml_features/rust/src/multiple_qobjects.rs +++ b/examples/qml_features/rust/src/multiple_qobjects.rs @@ -23,53 +23,45 @@ pub mod qobject { #[qproperty(i32, counter)] #[qproperty(QColor, color)] type FirstObject = super::FirstObjectRust; - } - // Enabling threading on the qobject - impl cxx_qt::Threading for FirstObject {} - - extern "RustQt" { /// Accepted Q_SIGNAL #[qsignal] - fn accepted(self: Pin<&mut FirstObject>); + fn accepted(self: Pin<&mut Self>); /// Rejected Q_SIGNAL #[qsignal] - fn rejected(self: Pin<&mut FirstObject>); - } + fn rejected(self: Pin<&mut Self>); - extern "RustQt" { /// A Q_INVOKABLE on the first QObject which increments a counter #[qinvokable] - fn increment(self: Pin<&mut FirstObject>); + fn increment(self: Pin<&mut Self>); } + // Enabling threading on the qobject + impl cxx_qt::Threading for FirstObject {} + extern "RustQt" { #[qobject] #[qml_element] #[qproperty(i32, counter)] #[qproperty(QUrl, url)] type SecondObject = super::SecondObjectRust; - } - // Enabling threading on the qobject - impl cxx_qt::Threading for SecondObject {} - - extern "RustQt" { /// Accepted Q_SIGNAL #[qsignal] - fn accepted(self: Pin<&mut SecondObject>); + fn accepted(self: Pin<&mut Self>); /// Rejected Q_SIGNAL #[qsignal] - fn rejected(self: Pin<&mut SecondObject>); - } + fn rejected(self: Pin<&mut Self>); - extern "RustQt" { /// A Q_INVOKABLE on the second QObject which increments a counter #[qinvokable] - fn increment(self: Pin<&mut SecondObject>); + fn increment(self: Pin<&mut Self>); } + + // Enabling threading on the qobject + impl cxx_qt::Threading for SecondObject {} } use core::pin::Pin; diff --git a/examples/qml_features/rust/src/naming.rs b/examples/qml_features/rust/src/naming.rs index 0f25fe278..26c92a6aa 100644 --- a/examples/qml_features/rust/src/naming.rs +++ b/examples/qml_features/rust/src/naming.rs @@ -15,13 +15,12 @@ pub mod qobject { #[cxx_name = "RenamedObject"] #[namespace = "my_namespace"] type NamedObject = super::NamedObjectRust; - } - extern "RustQt" { #[qinvokable] #[cxx_name = "increment"] #[rust_name = "plus_one"] fn increment_number(self: Pin<&mut NamedObject>); + } #[auto_cxx_name] diff --git a/examples/qml_features/rust/src/nested_qobjects.rs b/examples/qml_features/rust/src/nested_qobjects.rs index 14f013f83..70e5d79b7 100644 --- a/examples/qml_features/rust/src/nested_qobjects.rs +++ b/examples/qml_features/rust/src/nested_qobjects.rs @@ -44,7 +44,7 @@ pub mod qobject { /// /// Due to a raw pointer this is considered unsafe in CXX #[qsignal] - unsafe fn called(self: Pin<&mut OuterObject>, inner: *mut InnerObject); + unsafe fn called(self: Pin<&mut Self>, inner: *mut InnerObject); } extern "RustQt" { diff --git a/examples/qml_features/rust/src/properties.rs b/examples/qml_features/rust/src/properties.rs index a1ed2330c..54cca0a86 100644 --- a/examples/qml_features/rust/src/properties.rs +++ b/examples/qml_features/rust/src/properties.rs @@ -31,15 +31,15 @@ pub mod qobject { /// Custom on changed signal, used for all the properties #[qsignal] #[cxx_name = "connectedStateChanged"] - fn connected_state_changed(self: Pin<&mut RustProperties>); + fn connected_state_changed(self: Pin<&mut Self>); /// Custom setter for connected_url, which also handles setting the other qproperties #[cxx_name = "setUrl"] - fn set_url(self: Pin<&mut RustProperties>, url: QUrl); + fn set_url(self: Pin<&mut Self>, url: QUrl); /// Resets value of connected_url to empty, as well as calling the other disconnected logic #[cxx_name = "resetUrl"] - fn reset_url(self: Pin<&mut RustProperties>); + fn reset_url(self: Pin<&mut Self>); } impl cxx_qt::Initialize for RustProperties {} diff --git a/examples/qml_features/rust/src/serialisation.rs b/examples/qml_features/rust/src/serialisation.rs index ea14a3f97..939f4bad3 100644 --- a/examples/qml_features/rust/src/serialisation.rs +++ b/examples/qml_features/rust/src/serialisation.rs @@ -28,18 +28,18 @@ pub mod qobject { /// An error signal #[qsignal] - fn error(self: Pin<&mut Serialisation>, message: QString); + fn error(self: Pin<&mut Self>, message: QString); /// Retrieve the JSON form of this QObject #[qinvokable] #[cxx_name = "asJsonStr"] - fn as_json_str(self: Pin<&mut Serialisation>) -> QString; + fn as_json_str(self: Pin<&mut Self>) -> QString; /// From a given JSON string try to load values for the Q_PROPERTYs // ANCHOR: book_grab_values #[qinvokable] #[cxx_name = "fromJsonStr"] - fn from_json_str(self: Pin<&mut Serialisation>, string: &QString); + fn from_json_str(self: Pin<&mut Self>, string: &QString); // ANCHOR_END: book_grab_values } } diff --git a/examples/qml_features/rust/src/singleton.rs b/examples/qml_features/rust/src/singleton.rs index 98fbdbc41..9c02aec0b 100644 --- a/examples/qml_features/rust/src/singleton.rs +++ b/examples/qml_features/rust/src/singleton.rs @@ -18,7 +18,7 @@ pub mod qobject { /// Increment the persistent value Q_PROPERTY of the QML_SINGLETON #[qinvokable] - fn increment(self: Pin<&mut RustSingleton>); + fn increment(self: Pin<&mut Self>); } } diff --git a/examples/qml_features/rust/src/threading.rs b/examples/qml_features/rust/src/threading.rs index 5111a5396..5e590b2db 100644 --- a/examples/qml_features/rust/src/threading.rs +++ b/examples/qml_features/rust/src/threading.rs @@ -27,24 +27,22 @@ pub mod qobject { #[qproperty(QString, title)] #[qproperty(QUrl, url)] type ThreadingWebsite = super::ThreadingWebsiteRust; - } - - // ANCHOR: book_threading_trait - // Enabling threading on the qobject - impl cxx_qt::Threading for ThreadingWebsite {} - // ANCHOR_END: book_threading_trait - extern "RustQt" { /// Swap the URL between kdab.com and github.com #[qinvokable] #[cxx_name = "changeUrl"] - fn change_url(self: Pin<&mut ThreadingWebsite>); + fn change_url(self: Pin<&mut Self>); /// Simulate delay of a network request to retrieve the title of the website #[qinvokable] #[cxx_name = "fetchTitle"] - fn fetch_title(self: Pin<&mut ThreadingWebsite>); + fn fetch_title(self: Pin<&mut Self>); } + + // ANCHOR: book_threading_trait + // Enabling threading on the qobject + impl cxx_qt::Threading for ThreadingWebsite {} + // ANCHOR_END: book_threading_trait } use core::pin::Pin; diff --git a/examples/qml_features/rust/src/types.rs b/examples/qml_features/rust/src/types.rs index 63d8ba9e6..90907c7e7 100644 --- a/examples/qml_features/rust/src/types.rs +++ b/examples/qml_features/rust/src/types.rs @@ -83,12 +83,12 @@ pub mod ffi { /// Load the value from a QVariant #[qinvokable] #[cxx_name = "loadFromVariant"] - fn load_from_variant(self: Pin<&mut Types>, variant: &QVariant); + fn load_from_variant(self: Pin<&mut Self>, variant: &QVariant); /// Toggle the boolean Q_PROPERTY #[qinvokable] #[cxx_name = "toggleBoolean"] - fn toggle_boolean(self: Pin<&mut Types>); + fn toggle_boolean(self: Pin<&mut Self>); } } From d7de3f855d834e8f4252be8c22791fdc9b0681f8 Mon Sep 17 00:00:00 2001 From: Ben Ford Date: Mon, 7 Apr 2025 11:07:20 +0100 Subject: [PATCH 4/9] Add support for Self inlining in extern C++Qt blocks --- crates/cxx-qt-gen/src/parser/cxxqtdata.rs | 13 ++++++++++++- crates/cxx-qt-gen/src/parser/externcxxqt.rs | 19 +++++++++++++++++-- crates/cxx-qt-gen/test_inputs/signals.rs | 2 +- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/crates/cxx-qt-gen/src/parser/cxxqtdata.rs b/crates/cxx-qt-gen/src/parser/cxxqtdata.rs index cf20ed2a9..5b358b6e4 100644 --- a/crates/cxx-qt-gen/src/parser/cxxqtdata.rs +++ b/crates/cxx-qt-gen/src/parser/cxxqtdata.rs @@ -73,7 +73,7 @@ impl ParsedCxxQtData { /// If there are unresolved methods in the list, but inline is false, it will error, /// as the self inlining is only available if there is exactly one `QObject` in the block, /// and this indicates that no inlining can be done, but some `Self` types were present. - fn try_inline_self_types( + pub fn try_inline_self_types( inline: bool, type_to_inline: &Option, invokables: &mut [impl DerefMut], @@ -824,7 +824,18 @@ mod tests { } }; + let extern_cpp_qt: Item = parse_quote! { + unsafe extern "C++Qt" { + #[qobject] + type MyObject; + + #[qsignal] + fn my_signal(self: Pin<&mut Self>); + } + }; + parsed_cxxqtdata.parse_cxx_qt_item(extern_rust_qt).unwrap(); + parsed_cxxqtdata.parse_cxx_qt_item(extern_cpp_qt).unwrap(); } #[test] diff --git a/crates/cxx-qt-gen/src/parser/externcxxqt.rs b/crates/cxx-qt-gen/src/parser/externcxxqt.rs index 2031d165e..0b78f2ae2 100644 --- a/crates/cxx-qt-gen/src/parser/externcxxqt.rs +++ b/crates/cxx-qt-gen/src/parser/externcxxqt.rs @@ -3,6 +3,7 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 +use crate::parser::cxxqtdata::ParsedCxxQtData; use crate::{ parser::{ externqobject::ParsedExternQObject, require_attributes, signals::ParsedSignal, @@ -10,6 +11,7 @@ use crate::{ }, syntax::{attribute::attribute_get_path, expr::expr_to_string}, }; +use quote::format_ident; use syn::{spanned::Spanned, Error, ForeignItem, Ident, ItemForeignMod, Result, Token}; /// Representation of an extern "C++Qt" block @@ -54,6 +56,9 @@ impl ParsedExternCxxQt { ..Default::default() }; + let mut qobjects = vec![]; + let mut signals = vec![]; + // Parse any signals, other items are passed through for item in foreign_mod.items.drain(..) { match item { @@ -74,7 +79,7 @@ impl ParsedExternCxxQt { // extern "C++Qt" signals are always inherit = true // as they always exist on an existing QObject signal.inherit = true; - extern_cxx_block.signals.push(signal); + signals.push(signal); } else { extern_cxx_block .passthrough_items @@ -89,7 +94,7 @@ impl ParsedExternCxxQt { let extern_ty = ParsedExternQObject::parse(foreign_ty, module_ident, parent_namespace)?; // Pass through types separately for generation - extern_cxx_block.qobjects.push(extern_ty); + qobjects.push(extern_ty); } else { return Err(Error::new( foreign_ty.span(), @@ -103,6 +108,16 @@ impl ParsedExternCxxQt { } } + let inline_self = qobjects.len() == 1; + let inline_ident = qobjects + .last() + .map(|obj| format_ident!("{}", obj.name.cxx_unqualified())); + + ParsedCxxQtData::try_inline_self_types(inline_self, &inline_ident, &mut signals)?; + + extern_cxx_block.qobjects.extend(qobjects); + extern_cxx_block.signals.extend(signals); + Ok(extern_cxx_block) } } diff --git a/crates/cxx-qt-gen/test_inputs/signals.rs b/crates/cxx-qt-gen/test_inputs/signals.rs index 8411a9549..ec298cca7 100644 --- a/crates/cxx-qt-gen/test_inputs/signals.rs +++ b/crates/cxx-qt-gen/test_inputs/signals.rs @@ -16,7 +16,7 @@ mod ffi { /// When the QTimer timeout occurs #[qsignal] - pub(self) fn timeout(self: Pin<&mut QTimer>); + pub(self) fn timeout(self: Pin<&mut Self>); } unsafe extern "RustQt" { From 5352f52bd765bdbeaa522bcba6f3cb3dc8b200d2 Mon Sep 17 00:00:00 2001 From: Ben Ford Date: Mon, 7 Apr 2025 12:32:25 +0100 Subject: [PATCH 5/9] Update more examples and fix bug with inlining ident --- crates/cxx-qt-gen/src/parser/cxxqtdata.rs | 2 +- crates/cxx-qt-gen/src/parser/externcxxqt.rs | 2 +- examples/demo_threading/rust/src/lib.rs | 18 +++++++----------- examples/qml_basics/src/main.rs | 2 +- examples/qml_features/rust/src/externcxxqt.rs | 5 +++-- examples/qml_features/rust/src/naming.rs | 2 +- .../rust/main/src/main_object.rs | 2 +- .../rust/sub1/src/sub1_object.rs | 2 +- .../rust/sub2/src/sub2_object.rs | 2 +- examples/todo_app/src/todo_list.rs | 16 +++++++--------- 10 files changed, 24 insertions(+), 29 deletions(-) diff --git a/crates/cxx-qt-gen/src/parser/cxxqtdata.rs b/crates/cxx-qt-gen/src/parser/cxxqtdata.rs index 5b358b6e4..9c01a5ab0 100644 --- a/crates/cxx-qt-gen/src/parser/cxxqtdata.rs +++ b/crates/cxx-qt-gen/src/parser/cxxqtdata.rs @@ -251,7 +251,7 @@ impl ParsedCxxQtData { let inline_self = qobjects.len() == 1; let inline_ident = qobjects .last() - .map(|obj| format_ident!("{}", obj.name.cxx_unqualified())); + .map(|obj| format_ident!("{}", obj.declaration.ident_left)); Self::try_inline_self_types(inline_self, &inline_ident, &mut methods)?; Self::try_inline_self_types(inline_self, &inline_ident, &mut signals)?; diff --git a/crates/cxx-qt-gen/src/parser/externcxxqt.rs b/crates/cxx-qt-gen/src/parser/externcxxqt.rs index 0b78f2ae2..b03ab4ffb 100644 --- a/crates/cxx-qt-gen/src/parser/externcxxqt.rs +++ b/crates/cxx-qt-gen/src/parser/externcxxqt.rs @@ -111,7 +111,7 @@ impl ParsedExternCxxQt { let inline_self = qobjects.len() == 1; let inline_ident = qobjects .last() - .map(|obj| format_ident!("{}", obj.name.cxx_unqualified())); + .map(|obj| format_ident!("{}", obj.declaration.ident)); ParsedCxxQtData::try_inline_self_types(inline_self, &inline_ident, &mut signals)?; diff --git a/examples/demo_threading/rust/src/lib.rs b/examples/demo_threading/rust/src/lib.rs index bafd88910..101d1d3b0 100644 --- a/examples/demo_threading/rust/src/lib.rs +++ b/examples/demo_threading/rust/src/lib.rs @@ -17,6 +17,9 @@ pub mod qobject { type QString = cxx_qt_lib::QString; } + // Enabling threading on the qobject + impl cxx_qt::Threading for EnergyUsage {} + extern "RustQt" { #[qobject] #[qml_element] @@ -24,31 +27,24 @@ pub mod qobject { #[qproperty(u32, sensors)] #[qproperty(f64, total_use)] type EnergyUsage = super::EnergyUsageRust; - } - - // Enabling threading on the qobject - impl cxx_qt::Threading for EnergyUsage {} - extern "RustQt" { /// A new sensor has been detected #[qsignal] #[cxx_name = "sensorAdded"] - fn sensor_added(self: Pin<&mut EnergyUsage>, uuid: QString); + fn sensor_added(self: Pin<&mut Self>, uuid: QString); /// A value on an existing sensor has changed #[qsignal] #[cxx_name = "sensorChanged"] - fn sensor_changed(self: Pin<&mut EnergyUsage>, uuid: QString); + fn sensor_changed(self: Pin<&mut Self>, uuid: QString); /// An existing sensor has been removed #[qsignal] #[cxx_name = "sensorRemoved"] - fn sensor_removed(self: Pin<&mut EnergyUsage>, uuid: QString); - } + fn sensor_removed(self: Pin<&mut Self>, uuid: QString); - extern "RustQt" { /// A Q_INVOKABLE that returns the current power usage for a given uuid #[qinvokable] #[cxx_name = "sensorPower"] - fn sensor_power(self: Pin<&mut EnergyUsage>, uuid: &QString) -> f64; + fn sensor_power(self: Pin<&mut Self>, uuid: &QString) -> f64; } impl cxx_qt::Initialize for EnergyUsage {} diff --git a/examples/qml_basics/src/main.rs b/examples/qml_basics/src/main.rs index 01c86c096..a23f3dd9c 100644 --- a/examples/qml_basics/src/main.rs +++ b/examples/qml_basics/src/main.rs @@ -31,7 +31,7 @@ mod qobject { type Greeter = super::GreeterRust; #[qinvokable] - fn greet(self: &Greeter) -> QString; + fn greet(&self) -> QString; } } diff --git a/examples/qml_features/rust/src/externcxxqt.rs b/examples/qml_features/rust/src/externcxxqt.rs index 239c7a0e1..efc9fd04b 100644 --- a/examples/qml_features/rust/src/externcxxqt.rs +++ b/examples/qml_features/rust/src/externcxxqt.rs @@ -14,17 +14,18 @@ pub mod ffi { #[qobject] type ExternalQObject; + // Since functions are just passed through the inlining isn't yet supported /// Trigger emitting the signal "amount" times fn trigger(self: Pin<&mut ExternalQObject>, amount: u32); /// Signal that is emitted when trigger is fired #[qsignal] - fn triggered(self: Pin<&mut ExternalQObject>); + fn triggered(self: Pin<&mut Self>); /// Private signal that is emitted when trigger is fired #[qsignal] #[rust_name = "triggered_private_signal"] - pub(self) fn triggeredPrivateSignal(self: Pin<&mut ExternalQObject>); + pub(self) fn triggeredPrivateSignal(self: Pin<&mut Self>); } extern "RustQt" { diff --git a/examples/qml_features/rust/src/naming.rs b/examples/qml_features/rust/src/naming.rs index 26c92a6aa..440b58547 100644 --- a/examples/qml_features/rust/src/naming.rs +++ b/examples/qml_features/rust/src/naming.rs @@ -19,7 +19,7 @@ pub mod qobject { #[qinvokable] #[cxx_name = "increment"] #[rust_name = "plus_one"] - fn increment_number(self: Pin<&mut NamedObject>); + fn increment_number(self: Pin<&mut Self>); } diff --git a/examples/qml_multi_crates/rust/main/src/main_object.rs b/examples/qml_multi_crates/rust/main/src/main_object.rs index 3fd832125..b4fb5f6a9 100644 --- a/examples/qml_multi_crates/rust/main/src/main_object.rs +++ b/examples/qml_multi_crates/rust/main/src/main_object.rs @@ -17,7 +17,7 @@ pub mod qobject { type MainObject = super::MainObjectRust; #[qinvokable] - fn increment(self: Pin<&mut MainObject>); + fn increment(self: Pin<&mut Self>); } } diff --git a/examples/qml_multi_crates/rust/sub1/src/sub1_object.rs b/examples/qml_multi_crates/rust/sub1/src/sub1_object.rs index 3b949b0db..084e17692 100644 --- a/examples/qml_multi_crates/rust/sub1/src/sub1_object.rs +++ b/examples/qml_multi_crates/rust/sub1/src/sub1_object.rs @@ -17,7 +17,7 @@ pub mod qobject { type Sub1Object = super::Sub1ObjectRust; #[qinvokable] - fn increment(self: Pin<&mut Sub1Object>); + fn increment(self: Pin<&mut Self>); } } diff --git a/examples/qml_multi_crates/rust/sub2/src/sub2_object.rs b/examples/qml_multi_crates/rust/sub2/src/sub2_object.rs index 73ba275ce..9a849f296 100644 --- a/examples/qml_multi_crates/rust/sub2/src/sub2_object.rs +++ b/examples/qml_multi_crates/rust/sub2/src/sub2_object.rs @@ -17,7 +17,7 @@ pub mod qobject { type Sub2Object = super::Sub2ObjectRust; #[qinvokable] - fn increment(self: Pin<&mut Sub2Object>); + fn increment(self: Pin<&mut Self>); } } diff --git a/examples/todo_app/src/todo_list.rs b/examples/todo_app/src/todo_list.rs index e5cfe6953..7ad696c2d 100644 --- a/examples/todo_app/src/todo_list.rs +++ b/examples/todo_app/src/todo_list.rs @@ -41,32 +41,30 @@ mod qobject { #[cxx_override] #[rust_name = "row_count"] - fn rowCount(self: &TodoList, parent: &QModelIndex) -> i32; + fn rowCount(&self, parent: &QModelIndex) -> i32; #[cxx_override] - fn data(self: &TodoList, index: &QModelIndex, role: i32) -> QVariant; + fn data(&self, index: &QModelIndex, role: i32) -> QVariant; #[cxx_override] #[rust_name = "role_names"] - fn roleNames(self: &TodoList) -> QHash_i32_QByteArray; - } + fn roleNames(&self) -> QHash_i32_QByteArray; - unsafe extern "RustQt" { #[qinvokable] #[rust_name = "set_checked"] - fn setChecked(self: Pin<&mut TodoList>, row: i32, checked: bool); + fn setChecked(self: Pin<&mut Self>, row: i32, checked: bool); #[inherit] #[rust_name = "begin_reset_model"] - fn beginResetModel(self: Pin<&mut TodoList>); + fn beginResetModel(self: Pin<&mut Self>); #[inherit] #[rust_name = "end_reset_model"] - fn endResetModel(self: Pin<&mut TodoList>); + fn endResetModel(self: Pin<&mut Self>); #[qinvokable] #[rust_name = "add_todo"] - fn addTodo(self: Pin<&mut TodoList>, todo: &QString); + fn addTodo(self: Pin<&mut Self>, todo: &QString); } } From 6c9895082b7e4ed6161c043d4d17e868482ea177 Mon Sep 17 00:00:00 2001 From: Ben Ford Date: Mon, 7 Apr 2025 13:20:05 +0100 Subject: [PATCH 6/9] Move inlining to it's own phase --- crates/cxx-qt-gen/src/lib.rs | 1 + crates/cxx-qt-gen/src/parser/cxxqtdata.rs | 129 +--------------- crates/cxx-qt-gen/src/parser/externcxxqt.rs | 4 +- crates/cxx-qt-gen/src/preprocessor/mod.rs | 6 + .../src/preprocessor/self_inlining.rs | 140 ++++++++++++++++++ 5 files changed, 153 insertions(+), 127 deletions(-) create mode 100644 crates/cxx-qt-gen/src/preprocessor/mod.rs create mode 100644 crates/cxx-qt-gen/src/preprocessor/self_inlining.rs diff --git a/crates/cxx-qt-gen/src/lib.rs b/crates/cxx-qt-gen/src/lib.rs index 89e83e6b9..aacb16019 100644 --- a/crates/cxx-qt-gen/src/lib.rs +++ b/crates/cxx-qt-gen/src/lib.rs @@ -11,6 +11,7 @@ mod generator; mod naming; mod parser; +mod preprocessor; mod syntax; mod writer; diff --git a/crates/cxx-qt-gen/src/parser/cxxqtdata.rs b/crates/cxx-qt-gen/src/parser/cxxqtdata.rs index 9c01a5ab0..e4781fae1 100644 --- a/crates/cxx-qt-gen/src/parser/cxxqtdata.rs +++ b/crates/cxx-qt-gen/src/parser/cxxqtdata.rs @@ -6,8 +6,8 @@ use super::qnamespace::ParsedQNamespace; use super::trait_impl::TraitImpl; use crate::naming::cpp::err_unsupported_item; -use crate::parser::method::MethodFields; use crate::parser::CaseConversion; +use crate::preprocessor::self_inlining::try_inline_self_invokables; use crate::{ parser::{ externcxxqt::ParsedExternCxxQt, inherit::ParsedInheritedMethod, method::ParsedMethod, @@ -19,7 +19,6 @@ use crate::{ }, }; use quote::format_ident; -use std::ops::DerefMut; use syn::{ spanned::Spanned, Error, ForeignItem, Ident, Item, ItemEnum, ItemForeignMod, ItemImpl, ItemMacro, Meta, Result, @@ -68,38 +67,6 @@ impl ParsedCxxQtData { } } - /// Inline any `Self` types in the methods signatures with the Ident of a qobject passed in - /// - /// If there are unresolved methods in the list, but inline is false, it will error, - /// as the self inlining is only available if there is exactly one `QObject` in the block, - /// and this indicates that no inlining can be done, but some `Self` types were present. - pub fn try_inline_self_types( - inline: bool, - type_to_inline: &Option, - invokables: &mut [impl DerefMut], - ) -> Result<()> { - for method in invokables.iter_mut() { - if method.self_unresolved { - if inline { - if let Some(inline_type) = type_to_inline.clone() { - method.qobject_ident = inline_type; - } else { - return Err(Error::new( - method.method.span(), - "Expected a type to inline, no `qobject` typename was passed!", - )); - } - } else { - return Err(Error::new( - method.method.span(), - "`Self` type can only be inferred if the extern block contains only one `qobject`.", - )); - } - } - } - Ok(()) - } - /// Determine if the given [syn::Item] is a CXX-Qt related item /// If it is then add the [syn::Item] into qobjects BTreeMap /// Otherwise return the [syn::Item] to pass through to CXX @@ -253,9 +220,9 @@ impl ParsedCxxQtData { .last() .map(|obj| format_ident!("{}", obj.declaration.ident_left)); - Self::try_inline_self_types(inline_self, &inline_ident, &mut methods)?; - Self::try_inline_self_types(inline_self, &inline_ident, &mut signals)?; - Self::try_inline_self_types(inline_self, &inline_ident, &mut inherited)?; + try_inline_self_invokables(inline_self, &inline_ident, &mut methods)?; + try_inline_self_invokables(inline_self, &inline_ident, &mut signals)?; + try_inline_self_invokables(inline_self, &inline_ident, &mut inherited)?; self.qobjects.extend(qobjects); self.methods.extend(methods); @@ -792,92 +759,4 @@ mod tests { Some("b") ); } - - #[test] - fn test_self_inlining_ref() { - let mut parsed_cxxqtdata = ParsedCxxQtData::new(format_ident!("ffi"), None); - let extern_rust_qt: Item = parse_quote! { - unsafe extern "RustQt" { - #[qobject] - type MyObject = super::T; - - fn my_method(&self); - - #[inherit] - fn my_inherited_method(&self); - } - }; - - parsed_cxxqtdata.parse_cxx_qt_item(extern_rust_qt).unwrap(); - } - - #[test] - fn test_self_inlining_pin() { - let mut parsed_cxxqtdata = ParsedCxxQtData::new(format_ident!("ffi"), None); - let extern_rust_qt: Item = parse_quote! { - unsafe extern "RustQt" { - #[qobject] - type MyObject = super::T; - - #[qsignal] - fn my_signal(self: Pin<&mut Self>); - } - }; - - let extern_cpp_qt: Item = parse_quote! { - unsafe extern "C++Qt" { - #[qobject] - type MyObject; - - #[qsignal] - fn my_signal(self: Pin<&mut Self>); - } - }; - - parsed_cxxqtdata.parse_cxx_qt_item(extern_rust_qt).unwrap(); - parsed_cxxqtdata.parse_cxx_qt_item(extern_cpp_qt).unwrap(); - } - - #[test] - fn test_self_inlining_methods_invalid() { - assert_parse_errors! { - |item| ParsedCxxQtData::new(format_ident!("ffi"), None).parse_cxx_qt_item(item) => - // No QObject in block - { - extern "RustQt" { - fn my_method(&self); - } - } - - { - extern "RustQt" { - fn my_method(self: Pin<&mut Self>); - } - } - // More than 1 QObjects in block - { - extern "RustQt" { - #[qobject] - type MyObject = super::T; - - #[qobject] - type MyOtherObject = super::S; - - fn my_method(&self); - } - } - } - } - - #[test] - fn test_invalid_inline_call() { - let method_sig = parse_quote! { - fn test(&self); - }; - let mut methods = vec![ParsedMethod::mock_qinvokable(&method_sig)]; - - // If inlining is set to take place, an Ident is required to inline, here it is `None` - let data = ParsedCxxQtData::try_inline_self_types(true, &None, &mut methods); - assert!(data.is_err()); - } } diff --git a/crates/cxx-qt-gen/src/parser/externcxxqt.rs b/crates/cxx-qt-gen/src/parser/externcxxqt.rs index b03ab4ffb..1aa7c12f5 100644 --- a/crates/cxx-qt-gen/src/parser/externcxxqt.rs +++ b/crates/cxx-qt-gen/src/parser/externcxxqt.rs @@ -3,7 +3,7 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 -use crate::parser::cxxqtdata::ParsedCxxQtData; +use crate::preprocessor::self_inlining::try_inline_self_invokables; use crate::{ parser::{ externqobject::ParsedExternQObject, require_attributes, signals::ParsedSignal, @@ -113,7 +113,7 @@ impl ParsedExternCxxQt { .last() .map(|obj| format_ident!("{}", obj.declaration.ident)); - ParsedCxxQtData::try_inline_self_types(inline_self, &inline_ident, &mut signals)?; + try_inline_self_invokables(inline_self, &inline_ident, &mut signals)?; extern_cxx_block.qobjects.extend(qobjects); extern_cxx_block.signals.extend(signals); diff --git a/crates/cxx-qt-gen/src/preprocessor/mod.rs b/crates/cxx-qt-gen/src/preprocessor/mod.rs new file mode 100644 index 000000000..ff9cbee80 --- /dev/null +++ b/crates/cxx-qt-gen/src/preprocessor/mod.rs @@ -0,0 +1,6 @@ +// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company +// SPDX-FileContributor: Ben Ford +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +pub mod self_inlining; diff --git a/crates/cxx-qt-gen/src/preprocessor/self_inlining.rs b/crates/cxx-qt-gen/src/preprocessor/self_inlining.rs new file mode 100644 index 000000000..600f331ee --- /dev/null +++ b/crates/cxx-qt-gen/src/preprocessor/self_inlining.rs @@ -0,0 +1,140 @@ +// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company +// SPDX-FileContributor: Ben Ford +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use crate::parser::method::MethodFields; +use proc_macro2::Ident; +use std::ops::DerefMut; +use syn::spanned::Spanned; +use syn::Error; + +/// Inline any `Self` types in the methods signatures with the Ident of a qobject passed in +/// +/// If there are unresolved methods in the list, but inline is false, it will error, +/// as the self inlining is only available if there is exactly one `QObject` in the block, +/// and this indicates that no inlining can be done, but some `Self` types were present. +pub fn try_inline_self_invokables( + inline: bool, + type_to_inline: &Option, + invokables: &mut [impl DerefMut], +) -> syn::Result<()> { + for method in invokables.iter_mut() { + if method.self_unresolved { + if inline { + if let Some(inline_type) = type_to_inline.clone() { + method.qobject_ident = inline_type; + } else { + return Err(Error::new( + method.method.span(), + "Expected a type to inline, no `qobject` typename was passed!", + )); + } + } else { + return Err(Error::new( + method.method.span(), + "`Self` type can only be inferred if the extern block contains only one `qobject`.", + )); + } + } + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::parser::cxxqtdata::ParsedCxxQtData; + use crate::parser::method::ParsedMethod; + use crate::tests::assert_parse_errors; + use quote::format_ident; + use syn::{parse_quote, Item}; + + #[test] + fn test_self_inlining_ref() { + let mut parsed_cxxqtdata = ParsedCxxQtData::new(format_ident!("ffi"), None); + let extern_rust_qt: Item = parse_quote! { + unsafe extern "RustQt" { + #[qobject] + type MyObject = super::T; + + fn my_method(&self); + + #[inherit] + fn my_inherited_method(&self); + } + }; + + parsed_cxxqtdata.parse_cxx_qt_item(extern_rust_qt).unwrap(); + } + + #[test] + fn test_self_inlining_pin() { + let mut parsed_cxxqtdata = ParsedCxxQtData::new(format_ident!("ffi"), None); + let extern_rust_qt: Item = parse_quote! { + unsafe extern "RustQt" { + #[qobject] + type MyObject = super::T; + + #[qsignal] + fn my_signal(self: Pin<&mut Self>); + } + }; + + let extern_cpp_qt: Item = parse_quote! { + unsafe extern "C++Qt" { + #[qobject] + type MyObject; + + #[qsignal] + fn my_signal(self: Pin<&mut Self>); + } + }; + + parsed_cxxqtdata.parse_cxx_qt_item(extern_rust_qt).unwrap(); + parsed_cxxqtdata.parse_cxx_qt_item(extern_cpp_qt).unwrap(); + } + + #[test] + fn test_self_inlining_methods_invalid() { + assert_parse_errors! { + |item| ParsedCxxQtData::new(format_ident!("ffi"), None).parse_cxx_qt_item(item) => + // No QObject in block + { + extern "RustQt" { + fn my_method(&self); + } + } + + { + extern "RustQt" { + fn my_method(self: Pin<&mut Self>); + } + } + // More than 1 QObjects in block + { + extern "RustQt" { + #[qobject] + type MyObject = super::T; + + #[qobject] + type MyOtherObject = super::S; + + fn my_method(&self); + } + } + } + } + + #[test] + fn test_invalid_inline_call() { + let method_sig = parse_quote! { + fn test(&self); + }; + let mut methods = vec![ParsedMethod::mock_qinvokable(&method_sig)]; + + // If inlining is set to take place, an Ident is required to inline, here it is `None` + let data = try_inline_self_invokables(true, &None, &mut methods); + assert!(data.is_err()); + } +} From 8723ab622d94c8171d4f97b59cef33d5a717937f Mon Sep 17 00:00:00 2001 From: Ben Ford Date: Tue, 8 Apr 2025 12:05:17 +0100 Subject: [PATCH 7/9] Move parser to store some things together based on extern block - QObjects, and invokables are now 2D Vecs, kept in their original block - Adds some helper methods for iterating like they are still flat --- crates/cxx-qt-gen/src/generator/rust/mod.rs | 3 +- .../src/generator/structuring/mod.rs | 9 +- crates/cxx-qt-gen/src/naming/name.rs | 2 +- crates/cxx-qt-gen/src/naming/type_names.rs | 2 +- crates/cxx-qt-gen/src/parser/cxxqtdata.rs | 87 ++++++++++++------- crates/cxx-qt-gen/src/parser/mod.rs | 4 +- 6 files changed, 64 insertions(+), 43 deletions(-) diff --git a/crates/cxx-qt-gen/src/generator/rust/mod.rs b/crates/cxx-qt-gen/src/generator/rust/mod.rs index dae4216bd..45213f0fe 100644 --- a/crates/cxx-qt-gen/src/generator/rust/mod.rs +++ b/crates/cxx-qt-gen/src/generator/rust/mod.rs @@ -103,8 +103,7 @@ impl GeneratedRustBlocks { // Generate a type declaration for `QObject` if necessary fn add_qobject_import(cxx_qt_data: &ParsedCxxQtData) -> Option { let includes = cxx_qt_data - .qobjects - .iter() + .qobjects() .any(|obj| obj.has_qobject_macro && obj.base_class.is_none()); if includes || cxx_qt_data diff --git a/crates/cxx-qt-gen/src/generator/structuring/mod.rs b/crates/cxx-qt-gen/src/generator/structuring/mod.rs index 18313697a..ed45cdc5b 100644 --- a/crates/cxx-qt-gen/src/generator/structuring/mod.rs +++ b/crates/cxx-qt-gen/src/generator/structuring/mod.rs @@ -79,8 +79,7 @@ impl<'a> Structures<'a> { /// Returns an error, if any references could not be resolved. pub fn new(cxxqtdata: &'a ParsedCxxQtData) -> Result { let mut qobjects: Vec<_> = cxxqtdata - .qobjects - .iter() + .qobjects() .map(StructuredQObject::from_qobject) .collect(); @@ -92,19 +91,19 @@ impl<'a> Structures<'a> { } // Associate each method parsed with its appropriate qobject - for method in &cxxqtdata.methods { + for method in cxxqtdata.methods() { let qobject = find_qobject(&mut qobjects, &method.qobject_ident)?; qobject.methods.push(method); } // Associate each inherited method parsed with its appropriate qobject - for inherited_method in &cxxqtdata.inherited_methods { + for inherited_method in cxxqtdata.inherited_methods() { let qobject = find_qobject(&mut qobjects, &inherited_method.qobject_ident)?; qobject.inherited_methods.push(inherited_method); } // Associate each signal parsed with its appropriate qobject - for signal in &cxxqtdata.signals { + for signal in cxxqtdata.signals() { let qobject = find_qobject(&mut qobjects, &signal.qobject_ident)?; qobject.signals.push(signal); } diff --git a/crates/cxx-qt-gen/src/naming/name.rs b/crates/cxx-qt-gen/src/naming/name.rs index fac11ec27..ef4bbb6a0 100644 --- a/crates/cxx-qt-gen/src/naming/name.rs +++ b/crates/cxx-qt-gen/src/naming/name.rs @@ -17,7 +17,7 @@ use syn::{spanned::Spanned, Attribute, Error, Ident, Path, Result}; /// Naming in CXX can be rather complex. /// The following Rules apply: /// - If only a cxx_name **or** a rust_name is given, the identifier of the type/function will be -/// used for part that wasn't specified explicitly. +/// used for part that wasn't specified explicitly. /// - If **both** attributes are present, the identifier itself is not used! /// - The `rust_name` is always used to refer to the type within the bridge!. #[derive(Clone, Debug, PartialEq, Eq)] diff --git a/crates/cxx-qt-gen/src/naming/type_names.rs b/crates/cxx-qt-gen/src/naming/type_names.rs index d0f885811..5a4630896 100644 --- a/crates/cxx-qt-gen/src/naming/type_names.rs +++ b/crates/cxx-qt-gen/src/naming/type_names.rs @@ -161,7 +161,7 @@ impl TypeNames { module_ident: &Ident, ) -> Result<()> { // Find and register the QObjects in the bridge - for qobject in cxx_qt_data.qobjects.iter() { + for qobject in cxx_qt_data.qobjects() { self.populate_qobject(qobject)?; } diff --git a/crates/cxx-qt-gen/src/parser/cxxqtdata.rs b/crates/cxx-qt-gen/src/parser/cxxqtdata.rs index e4781fae1..b33061e3f 100644 --- a/crates/cxx-qt-gen/src/parser/cxxqtdata.rs +++ b/crates/cxx-qt-gen/src/parser/cxxqtdata.rs @@ -25,19 +25,16 @@ use syn::{ }; pub struct ParsedCxxQtData { - /// Map of the QObjects defined in the module that will be used for code generation - // - // We have to use a BTreeMap here, instead of a HashMap, to keep the order of QObjects stable. - // Otherwise, the output order would be different, depending on the environment, which makes it hard to test/debug. - pub qobjects: Vec, + /// List of QObjects defined in the module, separated by block + pub qobjects: Vec>, /// List of QEnums defined in the module, that aren't associated with a QObject pub qenums: Vec, - /// List of methods and Q_INVOKABLES found - pub methods: Vec, - /// List of the Q_SIGNALS found - pub signals: Vec, - /// List of the inherited methods found - pub inherited_methods: Vec, + /// List of methods and Q_INVOKABLES found, separated by block + pub methods: Vec>, + /// List of the Q_SIGNALS found, separated by block + pub signals: Vec>, + /// List of the inherited methods found, separated by block + pub inherited_methods: Vec>, /// List of QNamespace declarations pub qnamespaces: Vec, /// Blocks of extern "C++Qt" @@ -50,6 +47,11 @@ pub struct ParsedCxxQtData { pub module_ident: Ident, } +/// Used to get a flat iterator view into a 2D Vector +fn flat_view(v: &[Vec]) -> impl Iterator { + v.iter().flat_map(|v| v.iter()) +} + impl ParsedCxxQtData { /// Create a ParsedCxxQtData from a given module and namespace pub fn new(module_ident: Ident, namespace: Option) -> Self { @@ -67,6 +69,26 @@ impl ParsedCxxQtData { } } + /// Iterator wrapper for methods + pub fn methods(&self) -> impl Iterator { + flat_view(&self.methods) + } + + /// Iterator wrapper for signals + pub fn signals(&self) -> impl Iterator { + flat_view(&self.signals) + } + + /// Iterator wrapper for inherited methods + pub fn inherited_methods(&self) -> impl Iterator { + flat_view(&self.inherited_methods) + } + + /// Iterator wrapper for QObjects + pub fn qobjects(&self) -> impl Iterator { + flat_view(&self.qobjects) + } + /// Determine if the given [syn::Item] is a CXX-Qt related item /// If it is then add the [syn::Item] into qobjects BTreeMap /// Otherwise return the [syn::Item] to pass through to CXX @@ -224,10 +246,10 @@ impl ParsedCxxQtData { try_inline_self_invokables(inline_self, &inline_ident, &mut signals)?; try_inline_self_invokables(inline_self, &inline_ident, &mut inherited)?; - self.qobjects.extend(qobjects); - self.methods.extend(methods); - self.signals.extend(signals); - self.inherited_methods.extend(inherited); + self.qobjects.push(qobjects); + self.methods.push(methods); + self.signals.push(signals); + self.inherited_methods.push(inherited); Ok(()) } @@ -247,8 +269,7 @@ impl ParsedCxxQtData { #[cfg(test)] fn find_object(&self, id: &Ident) -> Option<&ParsedQObject> { - self.qobjects - .iter() + self.qobjects() .find(|obj| obj.name.rust_unqualified() == id) } } @@ -265,8 +286,9 @@ mod tests { /// Creates a ParsedCxxQtData with a QObject definition already found pub fn create_parsed_cxx_qt_data() -> ParsedCxxQtData { let mut cxx_qt_data = ParsedCxxQtData::new(format_ident!("ffi"), None); - cxx_qt_data.qobjects.push(create_parsed_qobject()); - cxx_qt_data.qobjects.push(create_parsed_qobject()); + cxx_qt_data + .qobjects + .push(vec![create_parsed_qobject(), create_parsed_qobject()]); cxx_qt_data } @@ -376,9 +398,9 @@ mod tests { let result = cxx_qt_data.parse_cxx_qt_item(item).unwrap(); assert!(result.is_none()); - assert_eq!(cxx_qt_data.methods.len(), 2); - assert!(cxx_qt_data.methods[0].is_qinvokable); - assert!(!cxx_qt_data.methods[1].is_qinvokable) + assert_eq!(cxx_qt_data.methods().collect::>().len(), 2); + assert!(cxx_qt_data.methods[0][0].is_qinvokable); + assert!(!cxx_qt_data.methods[0][1].is_qinvokable) } #[test] @@ -406,7 +428,7 @@ mod tests { }; cxx_qt_data.parse_cxx_qt_item(item).unwrap(); assert_eq!(cxx_qt_data.methods.len(), 1); - assert_eq!(cxx_qt_data.methods[0].name.cxx_unqualified(), "fooBar"); + assert_eq!(cxx_qt_data.methods[0][0].name.cxx_unqualified(), "fooBar"); } #[test] @@ -421,9 +443,10 @@ mod tests { } }; cxx_qt_data.parse_cxx_qt_item(item).unwrap(); - assert_eq!(cxx_qt_data.methods.len(), 1); - assert_eq!(cxx_qt_data.methods[0].name.cxx_unqualified(), "foo_bar"); - assert_eq!(cxx_qt_data.methods[0].name.rust_unqualified(), "foo_bar"); + let methods = &cxx_qt_data.methods[0]; + assert_eq!(methods.len(), 1); + assert_eq!(methods[0].name.cxx_unqualified(), "foo_bar"); + assert_eq!(methods[0].name.rust_unqualified(), "foo_bar"); } #[test] @@ -438,8 +461,8 @@ mod tests { } }; cxx_qt_data.parse_cxx_qt_item(item).unwrap(); - assert_eq!(cxx_qt_data.methods.len(), 1); - assert_eq!(cxx_qt_data.methods[0].name.cxx_unqualified(), "renamed"); + assert_eq!(cxx_qt_data.methods[0].len(), 1); + assert_eq!(cxx_qt_data.methods[0][0].name.cxx_unqualified(), "renamed"); } #[test] @@ -611,7 +634,7 @@ mod tests { } }; cxxqtdata.parse_cxx_qt_item(block).unwrap(); - let signals = &cxxqtdata.signals; + let signals = &cxxqtdata.signals().collect::>(); assert_eq!(signals.len(), 2); assert!(signals[0].mutable); assert!(signals[1].mutable); @@ -641,7 +664,7 @@ mod tests { }; cxxqtdata.parse_cxx_qt_item(block).unwrap(); - let signals = &cxxqtdata.signals; + let signals = &cxxqtdata.signals().collect::>(); assert_eq!(signals.len(), 1); assert!(signals[0].mutable); assert!(!signals[0].safe); @@ -716,7 +739,7 @@ mod tests { }; parsed_cxxqtdata.parse_cxx_qt_item(extern_rust_qt).unwrap(); - assert_eq!(parsed_cxxqtdata.qobjects.len(), 2); + assert_eq!(parsed_cxxqtdata.qobjects().collect::>().len(), 2); assert!(parsed_cxxqtdata .find_object(&format_ident!("MyObject")) @@ -741,7 +764,7 @@ mod tests { }; parsed_cxxqtdata.parse_cxx_qt_item(extern_rust_qt).unwrap(); - assert_eq!(parsed_cxxqtdata.qobjects.len(), 2); + assert_eq!(parsed_cxxqtdata.qobjects().collect::>().len(), 2); assert_eq!( parsed_cxxqtdata .find_object(&format_ident!("MyObject")) diff --git a/crates/cxx-qt-gen/src/parser/mod.rs b/crates/cxx-qt-gen/src/parser/mod.rs index 3a0821761..ec98e29f7 100644 --- a/crates/cxx-qt-gen/src/parser/mod.rs +++ b/crates/cxx-qt-gen/src/parser/mod.rs @@ -395,7 +395,7 @@ mod tests { assert_eq!(parser.passthrough_module.module_ident, "ffi"); assert_eq!(parser.passthrough_module.vis, Visibility::Inherited); assert_eq!(parser.cxx_qt_data.namespace, Some("cxx_qt".to_owned())); - assert_eq!(parser.cxx_qt_data.qobjects.len(), 1); + assert_eq!(parser.cxx_qt_data.qobjects().collect::>().len(), 1); assert_eq!(parser.type_names.num_types(), 19); assert_eq!( parser @@ -441,7 +441,7 @@ mod tests { assert_eq!(parser.passthrough_module.module_ident, "ffi"); assert_eq!(parser.passthrough_module.vis, Visibility::Inherited); assert_eq!(parser.cxx_qt_data.namespace, None); - assert_eq!(parser.cxx_qt_data.qobjects.len(), 1); + assert_eq!(parser.cxx_qt_data.qobjects().collect::>().len(), 1); } #[test] From a2695b9854ce0a80b2fe3d357b00ad43a51ab7bc Mon Sep 17 00:00:00 2001 From: Ben Ford Date: Tue, 8 Apr 2025 13:45:47 +0100 Subject: [PATCH 8/9] Move inlining to a separate phase which processes a Parser - Add function call to cxx-qt-build and cxx-qt-macro - Inlining now happens after parsing and before structuring --- crates/cxx-qt-build/src/lib.rs | 9 +- crates/cxx-qt-gen/src/lib.rs | 4 +- crates/cxx-qt-gen/src/parser/cxxqtdata.rs | 12 -- .../src/preprocessor/self_inlining.rs | 141 ++++++++++++------ crates/cxx-qt-macro/src/lib.rs | 6 +- 5 files changed, 110 insertions(+), 62 deletions(-) diff --git a/crates/cxx-qt-build/src/lib.rs b/crates/cxx-qt-build/src/lib.rs index 4da40ba5b..c58bcccf9 100644 --- a/crates/cxx-qt-build/src/lib.rs +++ b/crates/cxx-qt-build/src/lib.rs @@ -43,8 +43,8 @@ use std::{ }; use cxx_qt_gen::{ - parse_qt_file, write_cpp, write_rust, CppFragment, CxxQtItem, GeneratedCppBlocks, GeneratedOpt, - GeneratedRustBlocks, Parser, + parse_qt_file, qualify_self_types, write_cpp, write_rust, CppFragment, CxxQtItem, + GeneratedCppBlocks, GeneratedOpt, GeneratedRustBlocks, Parser, }; // TODO: we need to eventually support having multiple modules defined in a single file. This @@ -132,7 +132,10 @@ impl GeneratedCpp { } found_bridge = true; - let parser = Parser::from(*m.clone()) + let mut parser = Parser::from(*m.clone()) + .map_err(GeneratedError::from) + .map_err(to_diagnostic)?; + qualify_self_types(&mut parser) .map_err(GeneratedError::from) .map_err(to_diagnostic)?; let generated_cpp = GeneratedCppBlocks::from(&parser, &cxx_qt_opt) diff --git a/crates/cxx-qt-gen/src/lib.rs b/crates/cxx-qt-gen/src/lib.rs index aacb16019..6d0631a2f 100644 --- a/crates/cxx-qt-gen/src/lib.rs +++ b/crates/cxx-qt-gen/src/lib.rs @@ -21,6 +21,7 @@ pub use generator::{ GeneratedOpt, }; pub use parser::Parser; +pub use preprocessor::self_inlining::qualify_self_types; pub use syntax::{parse_qt_file, CxxQtFile, CxxQtItem}; pub use writer::{cpp::write_cpp, rust::write_rust}; @@ -175,7 +176,8 @@ mod tests { expected_cpp_header: &str, expected_cpp_source: &str, ) { - let parser = Parser::from(syn::parse_str(input).unwrap()).unwrap(); + let mut parser = Parser::from(syn::parse_str(input).unwrap()).unwrap(); + qualify_self_types(&mut parser).unwrap(); let mut cfg_evaluator = CfgEvaluatorTest::default(); cfg_evaluator.cfgs.insert("crate", Some("cxx-qt-gen")); diff --git a/crates/cxx-qt-gen/src/parser/cxxqtdata.rs b/crates/cxx-qt-gen/src/parser/cxxqtdata.rs index b33061e3f..388aab328 100644 --- a/crates/cxx-qt-gen/src/parser/cxxqtdata.rs +++ b/crates/cxx-qt-gen/src/parser/cxxqtdata.rs @@ -7,7 +7,6 @@ use super::qnamespace::ParsedQNamespace; use super::trait_impl::TraitImpl; use crate::naming::cpp::err_unsupported_item; use crate::parser::CaseConversion; -use crate::preprocessor::self_inlining::try_inline_self_invokables; use crate::{ parser::{ externcxxqt::ParsedExternCxxQt, inherit::ParsedInheritedMethod, method::ParsedMethod, @@ -18,7 +17,6 @@ use crate::{ path::path_compare_str, }, }; -use quote::format_ident; use syn::{ spanned::Spanned, Error, ForeignItem, Ident, Item, ItemEnum, ItemForeignMod, ItemImpl, ItemMacro, Meta, Result, @@ -236,16 +234,6 @@ impl ParsedCxxQtData { } } - // If there is exaclty one qobject in the block, it can be inlined as a self type. - let inline_self = qobjects.len() == 1; - let inline_ident = qobjects - .last() - .map(|obj| format_ident!("{}", obj.declaration.ident_left)); - - try_inline_self_invokables(inline_self, &inline_ident, &mut methods)?; - try_inline_self_invokables(inline_self, &inline_ident, &mut signals)?; - try_inline_self_invokables(inline_self, &inline_ident, &mut inherited)?; - self.qobjects.push(qobjects); self.methods.push(methods); self.signals.push(signals); diff --git a/crates/cxx-qt-gen/src/preprocessor/self_inlining.rs b/crates/cxx-qt-gen/src/preprocessor/self_inlining.rs index 600f331ee..4689923e7 100644 --- a/crates/cxx-qt-gen/src/preprocessor/self_inlining.rs +++ b/crates/cxx-qt-gen/src/preprocessor/self_inlining.rs @@ -3,11 +3,17 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 -use crate::parser::method::MethodFields; +use crate::parser::cxxqtdata::ParsedCxxQtData; +use crate::parser::inherit::ParsedInheritedMethod; +use crate::parser::method::{MethodFields, ParsedMethod}; +use crate::parser::qobject::ParsedQObject; +use crate::parser::signals::ParsedSignal; +use crate::Parser; use proc_macro2::Ident; +use quote::format_ident; use std::ops::DerefMut; use syn::spanned::Spanned; -use syn::Error; +use syn::{Error, Result}; /// Inline any `Self` types in the methods signatures with the Ident of a qobject passed in /// @@ -41,86 +47,131 @@ pub fn try_inline_self_invokables( Ok(()) } +/// A collection of items found in an extern block when parsing +type BlockComponents<'a> = ( + &'a mut Vec, + &'a mut Vec, + &'a mut Vec, + &'a mut Vec, +); + +// Separates the parsed data by block and returns tuples of the components +fn separate_blocks(data: &mut ParsedCxxQtData) -> Vec { + data.qobjects + .iter_mut() + .zip(data.methods.iter_mut()) + .zip(data.inherited_methods.iter_mut()) + .zip(data.signals.iter_mut()) + .map(|(((qobject, method), inherited_method), signal)| { + (qobject, method, inherited_method, signal) + }) + .collect() +} + +/// For a given parser, attempt to inline the `Self` type used in any of the blocks with that blocks unique QObject +pub fn qualify_self_types(parser: &mut Parser) -> Result<()> { + for (qobjects, methods, inherited, signals) in separate_blocks(&mut parser.cxx_qt_data) { + let inline_self = qobjects.len() == 1; + let inline_ident = qobjects + .last() + .map(|obj| format_ident!("{}", obj.declaration.ident_left)); + + try_inline_self_invokables(inline_self, &inline_ident, methods)?; + try_inline_self_invokables(inline_self, &inline_ident, signals)?; + try_inline_self_invokables(inline_self, &inline_ident, inherited)?; + } + + Ok(()) +} + #[cfg(test)] mod tests { use super::*; - use crate::parser::cxxqtdata::ParsedCxxQtData; use crate::parser::method::ParsedMethod; use crate::tests::assert_parse_errors; - use quote::format_ident; - use syn::{parse_quote, Item}; + use syn::parse_quote; #[test] fn test_self_inlining_ref() { - let mut parsed_cxxqtdata = ParsedCxxQtData::new(format_ident!("ffi"), None); - let extern_rust_qt: Item = parse_quote! { - unsafe extern "RustQt" { - #[qobject] - type MyObject = super::T; + let module = parse_quote! { + #[cxx_qt::bridge] + mod ffi { + unsafe extern "RustQt" { + #[qobject] + type MyObject = super::T; - fn my_method(&self); + fn my_method(&self); - #[inherit] - fn my_inherited_method(&self); + #[inherit] + fn my_inherited_method(&self); + } } }; - - parsed_cxxqtdata.parse_cxx_qt_item(extern_rust_qt).unwrap(); + let mut parser = Parser::from(module).unwrap(); + assert!(qualify_self_types(&mut parser).is_ok()); } #[test] fn test_self_inlining_pin() { - let mut parsed_cxxqtdata = ParsedCxxQtData::new(format_ident!("ffi"), None); - let extern_rust_qt: Item = parse_quote! { - unsafe extern "RustQt" { - #[qobject] - type MyObject = super::T; - - #[qsignal] - fn my_signal(self: Pin<&mut Self>); - } - }; + let module = parse_quote! { + #[cxx_qt::bridge] + mod ffi { + unsafe extern "RustQt" { + #[qobject] + type MyObject = super::T; - let extern_cpp_qt: Item = parse_quote! { - unsafe extern "C++Qt" { - #[qobject] - type MyObject; + #[qsignal] + fn my_signal(self: Pin<&mut Self>); + } - #[qsignal] - fn my_signal(self: Pin<&mut Self>); + unsafe extern "C++Qt" { + #[qobject] + type MyOtherObject; + + #[qsignal] + fn my_signal(self: Pin<&mut Self>); + } } }; - - parsed_cxxqtdata.parse_cxx_qt_item(extern_rust_qt).unwrap(); - parsed_cxxqtdata.parse_cxx_qt_item(extern_cpp_qt).unwrap(); + let mut parser = Parser::from(module).unwrap(); + assert!(qualify_self_types(&mut parser).is_ok()); } #[test] fn test_self_inlining_methods_invalid() { assert_parse_errors! { - |item| ParsedCxxQtData::new(format_ident!("ffi"), None).parse_cxx_qt_item(item) => + |item| qualify_self_types(&mut Parser::from(item)?) => // No QObject in block { - extern "RustQt" { - fn my_method(&self); + #[cxx_qt::bridge] + mod ffi { + extern "RustQt" { + fn my_method(&self); + } } } { - extern "RustQt" { - fn my_method(self: Pin<&mut Self>); + #[cxx_qt::bridge] + mod ffi { + extern "RustQt" { + fn my_method(self: Pin<&mut Self>); + } } } // More than 1 QObjects in block { - extern "RustQt" { - #[qobject] - type MyObject = super::T; + #[cxx_qt::bridge] + mod ffi { + extern "RustQt" { + #[qobject] + type MyObject = super::T; - #[qobject] - type MyOtherObject = super::S; + #[qobject] + type MyOtherObject = super::S; - fn my_method(&self); + fn my_method(&self); + } } } } diff --git a/crates/cxx-qt-macro/src/lib.rs b/crates/cxx-qt-macro/src/lib.rs index fcd9c01f2..9c40102f0 100644 --- a/crates/cxx-qt-macro/src/lib.rs +++ b/crates/cxx-qt-macro/src/lib.rs @@ -15,7 +15,7 @@ use proc_macro::TokenStream; use syn::{parse_macro_input, ItemMod}; -use cxx_qt_gen::{write_rust, GeneratedRustBlocks, Parser}; +use cxx_qt_gen::{qualify_self_types, write_rust, GeneratedRustBlocks, Parser}; #[proc_macro_attribute] pub fn bridge(args: TokenStream, input: TokenStream) -> TokenStream { @@ -75,6 +75,10 @@ pub fn init_qml_module(args: TokenStream) -> TokenStream { // Take the module and C++ namespace and generate the rust code fn extract_and_generate(module: ItemMod) -> TokenStream { Parser::from(module) + .and_then(|mut parser| { + qualify_self_types(&mut parser)?; + Ok(parser) + }) .and_then(|parser| GeneratedRustBlocks::from(&parser)) .map(|generated_rust| write_rust(&generated_rust, None)) .unwrap_or_else(|err| err.to_compile_error()) From 8618301693845ecb7f93a9ebc63384dd5986e0fc Mon Sep 17 00:00:00 2001 From: Ben Ford Date: Tue, 20 May 2025 17:32:18 +0100 Subject: [PATCH 9/9] Separate parsing RustQt blocks, like we do with C++Qt - Refactor parsing steps to be more readable - Refactor tests to be better separated --- crates/cxx-qt-build/src/lib.rs | 4 +- crates/cxx-qt-gen/src/generator/rust/mod.rs | 1 + .../src/generator/structuring/mod.rs | 1 + crates/cxx-qt-gen/src/lib.rs | 5 +- crates/cxx-qt-gen/src/parser/cxxqtdata.rs | 344 +++--------------- crates/cxx-qt-gen/src/parser/externcxxqt.rs | 125 +++---- crates/cxx-qt-gen/src/parser/externrustqt.rs | 260 +++++++++++++ crates/cxx-qt-gen/src/parser/method.rs | 8 +- crates/cxx-qt-gen/src/parser/mod.rs | 9 +- .../src/{preprocessor => shorthand}/mod.rs | 1 + .../self_inlining.rs | 116 +++--- crates/cxx-qt-macro/src/lib.rs | 2 +- examples/qml_basics/src/main.rs | 2 +- 13 files changed, 445 insertions(+), 433 deletions(-) create mode 100644 crates/cxx-qt-gen/src/parser/externrustqt.rs rename crates/cxx-qt-gen/src/{preprocessor => shorthand}/mod.rs (81%) rename crates/cxx-qt-gen/src/{preprocessor => shorthand}/self_inlining.rs (61%) diff --git a/crates/cxx-qt-build/src/lib.rs b/crates/cxx-qt-build/src/lib.rs index c58bcccf9..6522a27c7 100644 --- a/crates/cxx-qt-build/src/lib.rs +++ b/crates/cxx-qt-build/src/lib.rs @@ -43,8 +43,8 @@ use std::{ }; use cxx_qt_gen::{ - parse_qt_file, qualify_self_types, write_cpp, write_rust, CppFragment, CxxQtItem, - GeneratedCppBlocks, GeneratedOpt, GeneratedRustBlocks, Parser, + parse_qt_file, self_inlining::qualify_self_types, write_cpp, write_rust, CppFragment, + CxxQtItem, GeneratedCppBlocks, GeneratedOpt, GeneratedRustBlocks, Parser, }; // TODO: we need to eventually support having multiple modules defined in a single file. This diff --git a/crates/cxx-qt-gen/src/generator/rust/mod.rs b/crates/cxx-qt-gen/src/generator/rust/mod.rs index 45213f0fe..acd94e05d 100644 --- a/crates/cxx-qt-gen/src/generator/rust/mod.rs +++ b/crates/cxx-qt-gen/src/generator/rust/mod.rs @@ -104,6 +104,7 @@ impl GeneratedRustBlocks { fn add_qobject_import(cxx_qt_data: &ParsedCxxQtData) -> Option { let includes = cxx_qt_data .qobjects() + .iter() .any(|obj| obj.has_qobject_macro && obj.base_class.is_none()); if includes || cxx_qt_data diff --git a/crates/cxx-qt-gen/src/generator/structuring/mod.rs b/crates/cxx-qt-gen/src/generator/structuring/mod.rs index ed45cdc5b..b63b1af03 100644 --- a/crates/cxx-qt-gen/src/generator/structuring/mod.rs +++ b/crates/cxx-qt-gen/src/generator/structuring/mod.rs @@ -80,6 +80,7 @@ impl<'a> Structures<'a> { pub fn new(cxxqtdata: &'a ParsedCxxQtData) -> Result { let mut qobjects: Vec<_> = cxxqtdata .qobjects() + .into_iter() .map(StructuredQObject::from_qobject) .collect(); diff --git a/crates/cxx-qt-gen/src/lib.rs b/crates/cxx-qt-gen/src/lib.rs index 6d0631a2f..3b50410d5 100644 --- a/crates/cxx-qt-gen/src/lib.rs +++ b/crates/cxx-qt-gen/src/lib.rs @@ -11,7 +11,7 @@ mod generator; mod naming; mod parser; -mod preprocessor; +mod shorthand; mod syntax; mod writer; @@ -21,7 +21,7 @@ pub use generator::{ GeneratedOpt, }; pub use parser::Parser; -pub use preprocessor::self_inlining::qualify_self_types; +pub use shorthand::self_inlining; pub use syntax::{parse_qt_file, CxxQtFile, CxxQtItem}; pub use writer::{cpp::write_cpp, rust::write_rust}; @@ -88,6 +88,7 @@ mod tests { $(assert!($parse_fn(syn::parse_quote! $input).is_err());)* } } + use crate::self_inlining::qualify_self_types; pub(crate) use assert_parse_errors; /// Helper for formating C++ code diff --git a/crates/cxx-qt-gen/src/parser/cxxqtdata.rs b/crates/cxx-qt-gen/src/parser/cxxqtdata.rs index 388aab328..3b7793b20 100644 --- a/crates/cxx-qt-gen/src/parser/cxxqtdata.rs +++ b/crates/cxx-qt-gen/src/parser/cxxqtdata.rs @@ -5,34 +5,19 @@ use super::qnamespace::ParsedQNamespace; use super::trait_impl::TraitImpl; -use crate::naming::cpp::err_unsupported_item; -use crate::parser::CaseConversion; +use crate::parser::externrustqt::ParsedExternRustQt; use crate::{ parser::{ externcxxqt::ParsedExternCxxQt, inherit::ParsedInheritedMethod, method::ParsedMethod, - qenum::ParsedQEnum, qobject::ParsedQObject, require_attributes, signals::ParsedSignal, + qenum::ParsedQEnum, qobject::ParsedQObject, signals::ParsedSignal, }, - syntax::{ - attribute::attribute_get_path, expr::expr_to_string, foreignmod::ForeignTypeIdentAlias, - path::path_compare_str, - }, -}; -use syn::{ - spanned::Spanned, Error, ForeignItem, Ident, Item, ItemEnum, ItemForeignMod, ItemImpl, - ItemMacro, Meta, Result, + syntax::{attribute::attribute_get_path, path::path_compare_str}, }; +use syn::{Ident, Item, ItemEnum, ItemForeignMod, ItemImpl, ItemMacro, Meta, Result}; pub struct ParsedCxxQtData { - /// List of QObjects defined in the module, separated by block - pub qobjects: Vec>, /// List of QEnums defined in the module, that aren't associated with a QObject pub qenums: Vec, - /// List of methods and Q_INVOKABLES found, separated by block - pub methods: Vec>, - /// List of the Q_SIGNALS found, separated by block - pub signals: Vec>, - /// List of the inherited methods found, separated by block - pub inherited_methods: Vec>, /// List of QNamespace declarations pub qnamespaces: Vec, /// Blocks of extern "C++Qt" @@ -43,48 +28,47 @@ pub struct ParsedCxxQtData { pub trait_impls: Vec, /// The ident of the module, used for mappings pub module_ident: Ident, -} - -/// Used to get a flat iterator view into a 2D Vector -fn flat_view(v: &[Vec]) -> impl Iterator { - v.iter().flat_map(|v| v.iter()) + /// All the `extern "RustQt"` blocks + pub extern_rustqt_blocks: Vec, } impl ParsedCxxQtData { /// Create a ParsedCxxQtData from a given module and namespace pub fn new(module_ident: Ident, namespace: Option) -> Self { Self { - qobjects: Vec::new(), + module_ident, + namespace, qenums: vec![], - methods: vec![], - signals: vec![], - inherited_methods: vec![], qnamespaces: vec![], trait_impls: vec![], - extern_cxxqt_blocks: Vec::::default(), - module_ident, - namespace, + extern_cxxqt_blocks: vec![], + extern_rustqt_blocks: vec![], } } - /// Iterator wrapper for methods - pub fn methods(&self) -> impl Iterator { - flat_view(&self.methods) + /// Flatten a vector from each rust block into one larger vector, + /// e.g. all the methods in every block. + fn flatten_rust_blocks(&self, accessor: fn(&ParsedExternRustQt) -> &Vec) -> Vec<&T> { + self.extern_rustqt_blocks + .iter() + .flat_map(accessor) + .collect() + } + + pub fn methods(&self) -> Vec<&ParsedMethod> { + self.flatten_rust_blocks(|block| &block.methods) } - /// Iterator wrapper for signals - pub fn signals(&self) -> impl Iterator { - flat_view(&self.signals) + pub fn signals(&self) -> Vec<&ParsedSignal> { + self.flatten_rust_blocks(|block| &block.signals) } - /// Iterator wrapper for inherited methods - pub fn inherited_methods(&self) -> impl Iterator { - flat_view(&self.inherited_methods) + pub fn inherited_methods(&self) -> Vec<&ParsedInheritedMethod> { + self.flatten_rust_blocks(|block| &block.inherited_methods) } - /// Iterator wrapper for QObjects - pub fn qobjects(&self) -> impl Iterator { - flat_view(&self.qobjects) + pub fn qobjects(&self) -> Vec<&ParsedQObject> { + self.flatten_rust_blocks(|block| &block.qobjects) } /// Determine if the given [syn::Item] is a CXX-Qt related item @@ -134,7 +118,11 @@ impl ParsedCxxQtData { if let Some(lit_str) = &foreign_mod.abi.name { match lit_str.value().as_str() { "RustQt" => { - self.parse_foreign_mod_rust_qt(foreign_mod)?; + self.extern_rustqt_blocks.push(ParsedExternRustQt::parse( + foreign_mod, + &self.module_ident, + self.namespace.as_deref(), + )?); return Ok(None); } "C++Qt" => { @@ -152,96 +140,6 @@ impl ParsedCxxQtData { Ok(Some(Item::ForeignMod(foreign_mod))) } - fn parse_foreign_mod_rust_qt(&mut self, mut foreign_mod: ItemForeignMod) -> Result<()> { - // TODO: support cfg on foreign mod blocks - let attrs = require_attributes( - &foreign_mod.attrs, - &["namespace", "auto_cxx_name", "auto_rust_name"], - )?; - - let auto_case = CaseConversion::from_attrs(&attrs)?; - - let mut qobjects = vec![]; - - let mut methods = vec![]; - let mut signals = vec![]; - let mut inherited = vec![]; - - let namespace = attrs - .get("namespace") - .map(|attr| expr_to_string(&attr.meta.require_name_value()?.value)) - .transpose()? - .or_else(|| self.namespace.clone()); - - for item in foreign_mod.items.drain(..) { - match item { - ForeignItem::Fn(foreign_fn) => { - // Test if the function is a signal - if attribute_get_path(&foreign_fn.attrs, &["qsignal"]).is_some() { - let parsed_signal_method = - ParsedSignal::parse(foreign_fn.clone(), auto_case)?; - if parsed_signal_method.inherit - && foreign_fn.sig.unsafety.is_none() - && foreign_mod.unsafety.is_none() - { - return Err(Error::new(foreign_fn.span(), "block must be declared `unsafe extern \"RustQt\"` if it contains any safe-to-call #[inherit] qsignals")); - } - - signals.push(parsed_signal_method); - - // Test if the function is an inheritance method - // - // Note that we need to test for qsignal first as qsignals have their own inherit meaning - } else if attribute_get_path(&foreign_fn.attrs, &["inherit"]).is_some() { - // We need to check that any safe functions are defined inside an unsafe block - // as with inherit we cannot fully prove the implementation and we can then - // directly copy the unsafetyness into the generated extern C++ block - if foreign_fn.sig.unsafety.is_none() && foreign_mod.unsafety.is_none() { - return Err(Error::new(foreign_fn.span(), "block must be declared `unsafe extern \"RustQt\"` if it contains any safe-to-call #[inherit] functions")); - } - - let parsed_inherited_method = - ParsedInheritedMethod::parse(foreign_fn, auto_case)?; - - inherited.push(parsed_inherited_method); - // Remaining methods are either C++ methods or invokables - } else { - let parsed_method = ParsedMethod::parse( - foreign_fn, - auto_case, - foreign_mod.unsafety.is_some(), - )?; - methods.push(parsed_method); - } - } - ForeignItem::Verbatim(tokens) => { - let foreign_alias: ForeignTypeIdentAlias = syn::parse2(tokens.clone())?; - - // Load the QObject - let qobject = ParsedQObject::parse( - foreign_alias, - namespace.as_deref(), - &self.module_ident, - auto_case, - )?; - - // Note that we assume a compiler error will occur later - // if you had two structs with the same name - qobjects.push(qobject); - } - // Const, Macro, Type are unsupported in extern "RustQt" for now - _ => return Err(err_unsupported_item(&item)), - } - } - - self.qobjects.push(qobjects); - self.methods.push(methods); - self.signals.push(signals); - self.inherited_methods.push(inherited); - - Ok(()) - } - /// Parse a [syn::ItemImpl] into the qobjects if it's a CXX-Qt implementation /// otherwise return as a [syn::Item] to pass through. fn parse_impl(&mut self, imp: ItemImpl) -> Result> { @@ -258,6 +156,7 @@ impl ParsedCxxQtData { #[cfg(test)] fn find_object(&self, id: &Ident) -> Option<&ParsedQObject> { self.qobjects() + .into_iter() .find(|obj| obj.name.rust_unqualified() == id) } } @@ -267,16 +166,20 @@ mod tests { use super::*; use crate::generator::structuring::Structures; - use crate::{naming::Name, parser::qobject::tests::create_parsed_qobject}; + use crate::parser::qobject::tests::create_parsed_qobject; use quote::format_ident; use syn::parse_quote; /// Creates a ParsedCxxQtData with a QObject definition already found pub fn create_parsed_cxx_qt_data() -> ParsedCxxQtData { let mut cxx_qt_data = ParsedCxxQtData::new(format_ident!("ffi"), None); - cxx_qt_data - .qobjects - .push(vec![create_parsed_qobject(), create_parsed_qobject()]); + cxx_qt_data.extern_rustqt_blocks.push(ParsedExternRustQt { + unsafety: None, + qobjects: vec![create_parsed_qobject(), create_parsed_qobject()], + methods: vec![], + signals: vec![], + inherited_methods: vec![], + }); cxx_qt_data } @@ -287,62 +190,29 @@ mod tests { assert_parse_errors! { |item| create_parsed_cxx_qt_data().parse_cxx_qt_item(item) => + // Qenum without namespace { - // Invalid QObject - unsafe extern "RustQt" { - #[qinvokable] - fn invokable(self: &MyObject::Bad); - } - } - { - // Namespaces aren't allowed on qinvokables - unsafe extern "RustQt" { - #[qinvokable] - #[namespace = "disallowed"] - fn invokable(self: &MyObject); - } - } - { - // Block or fn must be unsafe for inherit methods - extern "RustQt" { - #[inherit] - fn invokable(self: &MyObject); - } - } - { - // Block or fn must be unsafe for inherit qsignals - extern "RustQt" { - #[inherit] - #[qsignal] - fn signal(self: Pin<&mut MyObject>); - } - } - { - // Qenum without namespace #[qenum] enum MyEnum { A, B } } + + // Unsupported name for case conversion { - // Unsupported Item - extern "RustQt" { - static COUNTER: usize; - } - } - { - // Unsupported name for case conversion #[auto_cxx_name = Foo] extern "RustQt" {} } + + // Auto case uses ident not string { - // Auto case uses ident not string #[auto_cxx_name = "Camel"] extern "RustQt" {} } + + // Unsupported format for case conversion macro { - // Unsupported format for case conversion macro #[auto_cxx_name(a, b)] extern "RustQt" {} } @@ -371,26 +241,6 @@ mod tests { assert!(result.is_some()); } - #[test] - fn test_find_and_merge_cxx_qt_item_impl_valid_qobject() { - let mut cxx_qt_data = create_parsed_cxx_qt_data(); - - let item: Item = parse_quote! { - unsafe extern "RustQt" { - #[qinvokable] - fn invokable(self: &MyObject); - - fn cpp_context(self: &MyObject); - } - }; - let result = cxx_qt_data.parse_cxx_qt_item(item).unwrap(); - assert!(result.is_none()); - - assert_eq!(cxx_qt_data.methods().collect::>().len(), 2); - assert!(cxx_qt_data.methods[0][0].is_qinvokable); - assert!(!cxx_qt_data.methods[0][1].is_qinvokable) - } - #[test] fn test_parse_unnamed_extern_mod() { let mut cxx_qt_data = create_parsed_cxx_qt_data(); @@ -415,8 +265,8 @@ mod tests { } }; cxx_qt_data.parse_cxx_qt_item(item).unwrap(); - assert_eq!(cxx_qt_data.methods.len(), 1); - assert_eq!(cxx_qt_data.methods[0][0].name.cxx_unqualified(), "fooBar"); + assert_eq!(cxx_qt_data.methods().len(), 1); + assert_eq!(cxx_qt_data.methods()[0].name.cxx_unqualified(), "fooBar"); } #[test] @@ -431,7 +281,7 @@ mod tests { } }; cxx_qt_data.parse_cxx_qt_item(item).unwrap(); - let methods = &cxx_qt_data.methods[0]; + let methods = cxx_qt_data.methods(); assert_eq!(methods.len(), 1); assert_eq!(methods[0].name.cxx_unqualified(), "foo_bar"); assert_eq!(methods[0].name.rust_unqualified(), "foo_bar"); @@ -449,8 +299,8 @@ mod tests { } }; cxx_qt_data.parse_cxx_qt_item(item).unwrap(); - assert_eq!(cxx_qt_data.methods[0].len(), 1); - assert_eq!(cxx_qt_data.methods[0][0].name.cxx_unqualified(), "renamed"); + assert_eq!(cxx_qt_data.methods().len(), 1); + assert_eq!(cxx_qt_data.methods()[0].name.cxx_unqualified(), "renamed"); } #[test] @@ -540,30 +390,6 @@ mod tests { assert!(result.is_some()); } - #[test] - fn test_find_and_merge_cxx_qt_item_extern_cxx_qt() { - let mut cxx_qt_data = create_parsed_cxx_qt_data(); - - let item: Item = parse_quote! { - #[namespace = "rust"] - unsafe extern "C++Qt" { - #[qobject] - type QPushButton; - - #[qsignal] - fn clicked(self: Pin<&mut QPushButton>, checked: bool); - } - }; - let result = cxx_qt_data.parse_cxx_qt_item(item).unwrap(); - assert!(result.is_none()); - - assert_eq!(cxx_qt_data.extern_cxxqt_blocks.len(), 1); - assert!(cxx_qt_data.extern_cxxqt_blocks[0].namespace.is_some()); - assert_eq!(cxx_qt_data.extern_cxxqt_blocks[0].qobjects.len(), 1); - assert_eq!(cxx_qt_data.extern_cxxqt_blocks[0].signals.len(), 1); - assert!(cxx_qt_data.extern_cxxqt_blocks[0].unsafety.is_some()); - } - #[test] fn test_parse_inherited_methods() { let mut cxxqtdata = create_parsed_cxx_qt_data(); @@ -607,64 +433,6 @@ mod tests { assert_eq!(inherited[2].parameters[0].ident, "arg"); } - #[test] - fn test_parse_qsignals_safe() { - let mut cxxqtdata = create_parsed_cxx_qt_data(); - let block: Item = parse_quote! { - unsafe extern "RustQt" { - #[qsignal] - fn ready(self: Pin<&mut MyObject>); - - #[cxx_name="cppDataChanged"] - #[inherit] - #[qsignal] - fn data_changed(self: Pin<&mut MyObject>, data: i32); - } - }; - cxxqtdata.parse_cxx_qt_item(block).unwrap(); - let signals = &cxxqtdata.signals().collect::>(); - assert_eq!(signals.len(), 2); - assert!(signals[0].mutable); - assert!(signals[1].mutable); - assert!(signals[0].safe); - assert!(signals[1].safe); - assert_eq!(signals[0].parameters.len(), 0); - assert_eq!(signals[1].parameters.len(), 1); - assert_eq!(signals[1].parameters[0].ident, "data"); - assert_eq!(signals[0].name, Name::new(format_ident!("ready"))); - assert_eq!( - signals[1].name, - Name::mock_name_with_cxx("data_changed", "cppDataChanged") - ); - assert!(!signals[0].inherit); - assert!(signals[1].inherit); - } - - #[test] - fn test_parse_qsignals_unsafe() { - let mut cxxqtdata = create_parsed_cxx_qt_data(); - let block: Item = parse_quote! { - extern "RustQt" { - #[qsignal] - #[cxx_name = "unsafeSignal"] - unsafe fn unsafe_signal(self: Pin<&mut MyObject>, arg: *mut T); - } - }; - cxxqtdata.parse_cxx_qt_item(block).unwrap(); - - let signals = &cxxqtdata.signals().collect::>(); - assert_eq!(signals.len(), 1); - assert!(signals[0].mutable); - assert!(!signals[0].safe); - assert_eq!(signals[0].parameters.len(), 1); - assert_eq!(signals[0].parameters[0].ident, "arg"); - assert_eq!( - signals[0].name, - Name::mock_name_with_cxx("unsafe_signal", "unsafeSignal") - ); - assert!(!signals[0].inherit); - } - #[test] fn test_parse_threading() { let mut cxxqtdata = create_parsed_cxx_qt_data(); @@ -715,7 +483,7 @@ mod tests { } #[test] - fn test_qobjects() { + fn test_find_qobjects() { let mut parsed_cxxqtdata = ParsedCxxQtData::new(format_ident!("ffi"), None); let extern_rust_qt: Item = parse_quote! { extern "RustQt" { @@ -727,7 +495,7 @@ mod tests { }; parsed_cxxqtdata.parse_cxx_qt_item(extern_rust_qt).unwrap(); - assert_eq!(parsed_cxxqtdata.qobjects().collect::>().len(), 2); + assert_eq!(parsed_cxxqtdata.qobjects().len(), 2); assert!(parsed_cxxqtdata .find_object(&format_ident!("MyObject")) @@ -752,7 +520,7 @@ mod tests { }; parsed_cxxqtdata.parse_cxx_qt_item(extern_rust_qt).unwrap(); - assert_eq!(parsed_cxxqtdata.qobjects().collect::>().len(), 2); + assert_eq!(parsed_cxxqtdata.qobjects().len(), 2); assert_eq!( parsed_cxxqtdata .find_object(&format_ident!("MyObject")) diff --git a/crates/cxx-qt-gen/src/parser/externcxxqt.rs b/crates/cxx-qt-gen/src/parser/externcxxqt.rs index 1aa7c12f5..4364c9027 100644 --- a/crates/cxx-qt-gen/src/parser/externcxxqt.rs +++ b/crates/cxx-qt-gen/src/parser/externcxxqt.rs @@ -3,7 +3,6 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 -use crate::preprocessor::self_inlining::try_inline_self_invokables; use crate::{ parser::{ externqobject::ParsedExternQObject, require_attributes, signals::ParsedSignal, @@ -11,8 +10,9 @@ use crate::{ }, syntax::{attribute::attribute_get_path, expr::expr_to_string}, }; -use quote::format_ident; -use syn::{spanned::Spanned, Error, ForeignItem, Ident, ItemForeignMod, Result, Token}; +use syn::{ + spanned::Spanned, Error, ForeignItem, ForeignItemFn, Ident, ItemForeignMod, Result, Token, +}; /// Representation of an extern "C++Qt" block #[derive(Default)] @@ -57,34 +57,12 @@ impl ParsedExternCxxQt { }; let mut qobjects = vec![]; - let mut signals = vec![]; // Parse any signals, other items are passed through for item in foreign_mod.items.drain(..) { match item { ForeignItem::Fn(foreign_fn) => { - // We need to check that any safe functions are defined inside an unsafe block - // as with C++Qt blocks we directly copy the unsafetyness into the generated - // extern C++ block - if foreign_fn.sig.unsafety.is_none() && extern_cxx_block.unsafety.is_none() { - return Err(Error::new(foreign_fn.span(), "block must be declared `unsafe extern \"C++Qt\"` if it contains any safe-to-call C++ functions")); - } - - // Test if the function is a signal - if attribute_get_path(&foreign_fn.attrs, &["qsignal"]).is_some() { - if attribute_get_path(&foreign_fn.attrs, &["inherit"]).is_some() { - return Err(Error::new(foreign_fn.span(), "#[inherit] is not allowed or necessary in extern \"C++Qt\" blocks, as all signals are inherited by default")); - } - let mut signal = ParsedSignal::parse(foreign_fn, auto_case)?; - // extern "C++Qt" signals are always inherit = true - // as they always exist on an existing QObject - signal.inherit = true; - signals.push(signal); - } else { - extern_cxx_block - .passthrough_items - .push(ForeignItem::Fn(foreign_fn)); - } + extern_cxx_block.parse_invokable(foreign_fn, auto_case)?; } ForeignItem::Type(foreign_ty) => { // Test that there is a #[qobject] attribute on any type @@ -108,18 +86,39 @@ impl ParsedExternCxxQt { } } - let inline_self = qobjects.len() == 1; - let inline_ident = qobjects - .last() - .map(|obj| format_ident!("{}", obj.declaration.ident)); - - try_inline_self_invokables(inline_self, &inline_ident, &mut signals)?; - extern_cxx_block.qobjects.extend(qobjects); - extern_cxx_block.signals.extend(signals); Ok(extern_cxx_block) } + + fn parse_invokable( + &mut self, + foreign_fn: ForeignItemFn, + auto_case: CaseConversion, + ) -> Result<()> { + // We need to check that any safe functions are defined inside an unsafe block + // as with C++Qt blocks we directly copy the unsafetyness into the generated + // extern C++ block + if foreign_fn.sig.unsafety.is_none() && self.unsafety.is_none() { + return Err(Error::new(foreign_fn.span(), "block must be declared `unsafe extern \"C++Qt\"` if it contains any safe-to-call C++ functions")); + } + + // Test if the function is a signal + if attribute_get_path(&foreign_fn.attrs, &["qsignal"]).is_some() { + if attribute_get_path(&foreign_fn.attrs, &["inherit"]).is_some() { + return Err(Error::new(foreign_fn.span(), "#[inherit] is not allowed or necessary in extern \"C++Qt\" blocks, as all signals are inherited by default")); + } + let mut signal = ParsedSignal::parse(foreign_fn, auto_case)?; + // extern "C++Qt" signals are always inherit = true + // as they always exist on an existing QObject + signal.inherit = true; + self.signals.push(signal); + } else { + self.passthrough_items.push(ForeignItem::Fn(foreign_fn)); + } + + Ok(()) + } } #[cfg(test)] @@ -127,6 +126,7 @@ mod tests { use super::*; use quote::format_ident; + use crate::tests::assert_parse_errors; use syn::parse_quote; #[test] @@ -156,20 +156,6 @@ mod tests { assert!(extern_cxx_qt.unsafety.is_some()); } - #[test] - fn test_extern_cxxqt_type_missing_qobject() { - let extern_cxx_qt = ParsedExternCxxQt::parse( - parse_quote! { - unsafe extern "C++Qt" { - type QPushButton; - } - }, - &format_ident!("qobject"), - None, - ); - assert!(extern_cxx_qt.is_err()); - } - #[test] fn test_extern_cxxqt_type_qobject_attr() { let extern_cxx_qt = ParsedExternCxxQt::parse( @@ -203,41 +189,38 @@ mod tests { None, ) .unwrap(); - // Check that the non Type object is detected and error assert!(extern_cxx_qt.qobjects.is_empty()); assert!(extern_cxx_qt.signals.is_empty()); assert!(extern_cxx_qt.unsafety.is_some()); } #[test] - fn test_extern_cxxqt_invalid_safe() { - let extern_cxx_qt = ParsedExternCxxQt::parse( - parse_quote! { - extern "C++Qt" { - fn myFunction(); - } - }, - &format_ident!("qobject"), - None, - ); - // Ensure that a safe - assert!(extern_cxx_qt.is_err()); - } + fn test_parse_invalid() { + assert_parse_errors!( + |item| ParsedExternCxxQt::parse(item, &format_ident!("qobject"), None) => - #[test] - fn test_extern_cxxqt_inherit_on_signal() { - let extern_cxx_qt = ParsedExternCxxQt::parse( - parse_quote! { + // Inherit is not allowed in "C++Qt" blocks + { unsafe extern "C++Qt" { #[qsignal] #[inherit] fn myFunction(self: Pin<&mut MyObject>); } - }, - &format_ident!("qobject"), - None, + } + + // "C++Qt" blocks must be unsafe + { + extern "C++Qt" { + fn myFunction(); + } + } + + // All types in "C++Qt" blocks must be marked as QObjects + { + unsafe extern "C++Qt" { + type QPushButton; + } + } ); - // Inherit is not allowed in "C++Qt" blocks - assert!(extern_cxx_qt.is_err()); } } diff --git a/crates/cxx-qt-gen/src/parser/externrustqt.rs b/crates/cxx-qt-gen/src/parser/externrustqt.rs new file mode 100644 index 000000000..6da520bdf --- /dev/null +++ b/crates/cxx-qt-gen/src/parser/externrustqt.rs @@ -0,0 +1,260 @@ +// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company +// SPDX-FileContributor: Ben Ford +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use crate::naming::cpp::err_unsupported_item; +use crate::parser::inherit::ParsedInheritedMethod; +use crate::parser::method::ParsedMethod; +use crate::parser::qobject::ParsedQObject; +use crate::parser::signals::ParsedSignal; +use crate::parser::{require_attributes, CaseConversion}; +use crate::syntax::attribute::attribute_get_path; +use crate::syntax::expr::expr_to_string; +use crate::syntax::foreignmod::ForeignTypeIdentAlias; +use proc_macro2::Ident; +use syn::spanned::Spanned; +use syn::{Error, ForeignItem, ForeignItemFn, ItemForeignMod, Result, Token}; + +/// Representation of an extern "RustQt" block +#[derive(Default)] +pub struct ParsedExternRustQt { + /// Whether this block has an unsafe token + pub unsafety: Option, + /// List of QObjects defined in the module + pub qobjects: Vec, + /// List of methods and Q_INVOKABLES found + pub methods: Vec, + /// List of the Q_SIGNALS found + pub signals: Vec, + /// List of the inherited methods found + pub inherited_methods: Vec, +} + +impl ParsedExternRustQt { + pub fn parse( + mut foreign_mod: ItemForeignMod, + module_ident: &Ident, + parent_namespace: Option<&str>, + ) -> Result { + // TODO: support cfg on foreign mod blocks + let attrs = require_attributes( + &foreign_mod.attrs, + &["namespace", "auto_cxx_name", "auto_rust_name"], + )?; + + let auto_case = CaseConversion::from_attrs(&attrs)?; + + let mut extern_rustqt_block = Self { + unsafety: foreign_mod.unsafety, + ..Default::default() + }; + + let namespace = attrs + .get("namespace") + .map(|attr| expr_to_string(&attr.meta.require_name_value()?.value)) + .transpose()? + .or_else(|| parent_namespace.map(String::from)); + + for item in foreign_mod.items.drain(..) { + match item { + ForeignItem::Fn(foreign_fn) => { + extern_rustqt_block.parse_invokable(foreign_fn, auto_case)?; + } + ForeignItem::Verbatim(tokens) => { + let foreign_alias: ForeignTypeIdentAlias = syn::parse2(tokens.clone())?; + + // Load the QObject + let qobject = ParsedQObject::parse( + foreign_alias, + namespace.as_deref(), + module_ident, + auto_case, + )?; + + // Note that we assume a compiler error will occur later + // if you had two structs with the same name + extern_rustqt_block.qobjects.push(qobject); + } + // Const, Macro, Type are unsupported in extern "RustQt" for now + _ => return Err(err_unsupported_item(&item)), + } + } + + Ok(extern_rustqt_block) + } + + fn parse_invokable( + &mut self, + foreign_fn: ForeignItemFn, + auto_case: CaseConversion, + ) -> Result<()> { + // Test if the function is a signal + if attribute_get_path(&foreign_fn.attrs, &["qsignal"]).is_some() { + let parsed_signal_method = ParsedSignal::parse(foreign_fn.clone(), auto_case)?; + if parsed_signal_method.inherit + && foreign_fn.sig.unsafety.is_none() + && self.unsafety.is_none() + { + return Err(Error::new(foreign_fn.span(), "block must be declared `unsafe extern \"RustQt\"` if it contains any safe-to-call #[inherit] qsignals")); + } + + self.signals.push(parsed_signal_method); + + // Test if the function is an inheritance method + // + // Note that we need to test for qsignal first as qsignals have their own inherit meaning + } else if attribute_get_path(&foreign_fn.attrs, &["inherit"]).is_some() { + // We need to check that any safe functions are defined inside an unsafe block + // as with inherit we cannot fully prove the implementation and we can then + // directly copy the unsafety into the generated extern C++ block + if foreign_fn.sig.unsafety.is_none() && self.unsafety.is_none() { + return Err(Error::new(foreign_fn.span(), "block must be declared `unsafe extern \"RustQt\"` if it contains any safe-to-call #[inherit] functions")); + } + + let parsed_inherited_method = ParsedInheritedMethod::parse(foreign_fn, auto_case)?; + + self.inherited_methods.push(parsed_inherited_method); + // Remaining methods are either C++ methods or invokables + } else { + let parsed_method = + ParsedMethod::parse(foreign_fn, auto_case, self.unsafety.is_some())?; + self.methods.push(parsed_method); + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::naming::Name; + use crate::tests::assert_parse_errors; + use quote::format_ident; + use syn::parse_quote; + + #[test] + fn test_parse_qsignals_safe() { + let block: ItemForeignMod = parse_quote! { + unsafe extern "RustQt" { + #[qsignal] + fn ready(self: Pin<&mut MyObject>); + + #[cxx_name="cppDataChanged"] + #[inherit] + #[qsignal] + fn data_changed(self: Pin<&mut MyObject>, data: i32); + } + }; + let parsed_rust_qt = + ParsedExternRustQt::parse(block, &format_ident!("qobject"), None).unwrap(); + let signals = parsed_rust_qt.signals; + assert_eq!(signals.len(), 2); + assert!(signals[0].mutable); + assert!(signals[1].mutable); + assert!(signals[0].safe); + assert!(signals[1].safe); + assert_eq!(signals[0].parameters.len(), 0); + assert_eq!(signals[1].parameters.len(), 1); + assert_eq!(signals[1].parameters[0].ident, "data"); + assert_eq!(signals[0].name, Name::new(format_ident!("ready"))); + assert_eq!( + signals[1].name, + Name::mock_name_with_cxx("data_changed", "cppDataChanged") + ); + assert!(!signals[0].inherit); + assert!(signals[1].inherit); + } + + #[test] + fn test_parse_qsignals_unsafe() { + let block: ItemForeignMod = parse_quote! { + extern "RustQt" { + #[qsignal] + #[cxx_name = "unsafeSignal"] + unsafe fn unsafe_signal(self: Pin<&mut MyObject>, arg: *mut T); + } + }; + let parsed_rust_qt = + ParsedExternRustQt::parse(block, &format_ident!("qobject"), None).unwrap(); + let signals = parsed_rust_qt.signals; + assert_eq!(signals.len(), 1); + assert!(signals[0].mutable); + assert!(!signals[0].safe); + assert_eq!(signals[0].parameters.len(), 1); + assert_eq!(signals[0].parameters[0].ident, "arg"); + assert_eq!( + signals[0].name, + Name::mock_name_with_cxx("unsafe_signal", "unsafeSignal") + ); + assert!(!signals[0].inherit); + } + + #[test] + fn test_find_and_merge_cxx_qt_item_impl_valid_qobject() { + let block: ItemForeignMod = parse_quote! { + unsafe extern "RustQt" { + #[qinvokable] + fn invokable(self: &MyObject); + + fn cpp_context(self: &MyObject); + } + }; + let parsed_rust_qt = + ParsedExternRustQt::parse(block, &format_ident!("qobject"), None).unwrap(); + + let methods = parsed_rust_qt.methods; + assert_eq!(methods.len(), 2); + assert!(methods[0].is_qinvokable); + assert!(!methods[1].is_qinvokable); + } + + #[test] + fn test_parse_invalid() { + assert_parse_errors!( + |item| ParsedExternRustQt::parse(item, &format_ident!("qobject"), None) => + + // Invalid QObject + { + unsafe extern "RustQt" { + #[qinvokable] + fn invokable(self: &MyObject::Bad); + } + } + + // Namespaces aren't allowed on qinvokables + { + unsafe extern "RustQt" { + #[qinvokable] + #[namespace = "disallowed"] + fn invokable(self: &MyObject); + } + } + + // Block or fn must be unsafe for inherit methods + { + extern "RustQt" { + #[inherit] + fn invokable(self: &MyObject); + } + } + + // Block or fn must be unsafe for inherit qsignals + { + extern "RustQt" { + #[inherit] + #[qsignal] + fn signal(self: Pin<&mut MyObject>); + } + } + + // Unsupported Item + { + extern "RustQt" { + static COUNTER: usize; + } + } + ); + } +} diff --git a/crates/cxx-qt-gen/src/parser/method.rs b/crates/cxx-qt-gen/src/parser/method.rs index baecdccf7..89c708da5 100644 --- a/crates/cxx-qt-gen/src/parser/method.rs +++ b/crates/cxx-qt-gen/src/parser/method.rs @@ -164,7 +164,6 @@ pub struct MethodFields { pub parameters: Vec, pub safe: bool, pub name: Name, - pub self_unresolved: bool, } impl MethodFields { @@ -173,8 +172,6 @@ impl MethodFields { let (qobject_ident, mutability) = types::extract_qobject_ident(&self_receiver.ty)?; let mutable = mutability.is_some(); - let self_unresolved = qobject_ident == format_ident!("Self"); - let parameters = ParsedFunctionParameter::parse_all_ignoring_receiver(&method.sig)?; let safe = method.sig.unsafety.is_none(); let name = @@ -187,7 +184,10 @@ impl MethodFields { parameters, safe, name, - self_unresolved, }) } + + pub(crate) fn self_unresolved(&self) -> bool { + self.qobject_ident == format_ident!("Self") + } } diff --git a/crates/cxx-qt-gen/src/parser/mod.rs b/crates/cxx-qt-gen/src/parser/mod.rs index ec98e29f7..97b11ec1f 100644 --- a/crates/cxx-qt-gen/src/parser/mod.rs +++ b/crates/cxx-qt-gen/src/parser/mod.rs @@ -7,6 +7,7 @@ pub mod constructor; pub mod cxxqtdata; pub mod externcxxqt; pub mod externqobject; +mod externrustqt; pub mod inherit; pub mod method; pub mod parameter; @@ -327,7 +328,7 @@ mod tests { assert_eq!(parser.passthrough_module.module_ident, "ffi"); assert_eq!(parser.passthrough_module.vis, Visibility::Inherited); assert_eq!(parser.cxx_qt_data.namespace, None); - assert_eq!(parser.cxx_qt_data.qobjects.len(), 0); + assert_eq!(parser.cxx_qt_data.qobjects().len(), 0); } #[test] @@ -369,7 +370,7 @@ mod tests { assert_eq!(parser.passthrough_module.module_ident, "ffi"); assert_eq!(parser.passthrough_module.vis, Visibility::Inherited); assert_eq!(parser.cxx_qt_data.namespace, None); - assert_eq!(parser.cxx_qt_data.qobjects.len(), 0); + assert_eq!(parser.cxx_qt_data.qobjects().len(), 0); } #[test] @@ -395,7 +396,7 @@ mod tests { assert_eq!(parser.passthrough_module.module_ident, "ffi"); assert_eq!(parser.passthrough_module.vis, Visibility::Inherited); assert_eq!(parser.cxx_qt_data.namespace, Some("cxx_qt".to_owned())); - assert_eq!(parser.cxx_qt_data.qobjects().collect::>().len(), 1); + assert_eq!(parser.cxx_qt_data.qobjects().len(), 1); assert_eq!(parser.type_names.num_types(), 19); assert_eq!( parser @@ -441,7 +442,7 @@ mod tests { assert_eq!(parser.passthrough_module.module_ident, "ffi"); assert_eq!(parser.passthrough_module.vis, Visibility::Inherited); assert_eq!(parser.cxx_qt_data.namespace, None); - assert_eq!(parser.cxx_qt_data.qobjects().collect::>().len(), 1); + assert_eq!(parser.cxx_qt_data.qobjects().len(), 1); } #[test] diff --git a/crates/cxx-qt-gen/src/preprocessor/mod.rs b/crates/cxx-qt-gen/src/shorthand/mod.rs similarity index 81% rename from crates/cxx-qt-gen/src/preprocessor/mod.rs rename to crates/cxx-qt-gen/src/shorthand/mod.rs index ff9cbee80..f8d4aef95 100644 --- a/crates/cxx-qt-gen/src/preprocessor/mod.rs +++ b/crates/cxx-qt-gen/src/shorthand/mod.rs @@ -3,4 +3,5 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 +// TODO! Add our other shorthands into this module? pub mod self_inlining; diff --git a/crates/cxx-qt-gen/src/preprocessor/self_inlining.rs b/crates/cxx-qt-gen/src/shorthand/self_inlining.rs similarity index 61% rename from crates/cxx-qt-gen/src/preprocessor/self_inlining.rs rename to crates/cxx-qt-gen/src/shorthand/self_inlining.rs index 4689923e7..b7eefb2a1 100644 --- a/crates/cxx-qt-gen/src/preprocessor/self_inlining.rs +++ b/crates/cxx-qt-gen/src/shorthand/self_inlining.rs @@ -3,11 +3,13 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 -use crate::parser::cxxqtdata::ParsedCxxQtData; -use crate::parser::inherit::ParsedInheritedMethod; -use crate::parser::method::{MethodFields, ParsedMethod}; -use crate::parser::qobject::ParsedQObject; -use crate::parser::signals::ParsedSignal; +//! This module contains functions which can inline a Self type to the appropriate `QObject`. +//! +//! If ***exactly one*** `QObject` exists in a particular extern block, the self shorthand can be used, +//! and this modules functions will replace this `Self` type with the appropriate name. +//! `qualify_self_types` is called at the highest level, on an already built parser + +use crate::parser::method::MethodFields; use crate::Parser; use proc_macro2::Ident; use quote::format_ident; @@ -21,25 +23,17 @@ use syn::{Error, Result}; /// as the self inlining is only available if there is exactly one `QObject` in the block, /// and this indicates that no inlining can be done, but some `Self` types were present. pub fn try_inline_self_invokables( - inline: bool, type_to_inline: &Option, invokables: &mut [impl DerefMut], -) -> syn::Result<()> { +) -> Result<()> { for method in invokables.iter_mut() { - if method.self_unresolved { - if inline { - if let Some(inline_type) = type_to_inline.clone() { - method.qobject_ident = inline_type; - } else { - return Err(Error::new( - method.method.span(), - "Expected a type to inline, no `qobject` typename was passed!", - )); - } + if method.self_unresolved() { + if let Some(inline_type) = type_to_inline.clone() { + method.qobject_ident = inline_type; } else { return Err(Error::new( method.method.span(), - "`Self` type can only be inferred if the extern block contains only one `qobject`.", + "`Self` type can only be inferred if the extern block contains exactly one `qobject`.", )); } } @@ -47,38 +41,36 @@ pub fn try_inline_self_invokables( Ok(()) } -/// A collection of items found in an extern block when parsing -type BlockComponents<'a> = ( - &'a mut Vec, - &'a mut Vec, - &'a mut Vec, - &'a mut Vec, -); - -// Separates the parsed data by block and returns tuples of the components -fn separate_blocks(data: &mut ParsedCxxQtData) -> Vec { - data.qobjects - .iter_mut() - .zip(data.methods.iter_mut()) - .zip(data.inherited_methods.iter_mut()) - .zip(data.signals.iter_mut()) - .map(|(((qobject, method), inherited_method), signal)| { - (qobject, method, inherited_method, signal) - }) - .collect() -} - /// For a given parser, attempt to inline the `Self` type used in any of the blocks with that blocks unique QObject pub fn qualify_self_types(parser: &mut Parser) -> Result<()> { - for (qobjects, methods, inherited, signals) in separate_blocks(&mut parser.cxx_qt_data) { - let inline_self = qobjects.len() == 1; - let inline_ident = qobjects - .last() + // Inlining `extern "RustQt"` blocks + for rust_block in &mut parser.cxx_qt_data.extern_rustqt_blocks { + let mut iter = rust_block.qobjects.iter(); + let mut inline_ident = iter + .next() .map(|obj| format_ident!("{}", obj.declaration.ident_left)); - try_inline_self_invokables(inline_self, &inline_ident, methods)?; - try_inline_self_invokables(inline_self, &inline_ident, signals)?; - try_inline_self_invokables(inline_self, &inline_ident, inherited)?; + if iter.next().is_some() { + inline_ident = None; + } + + try_inline_self_invokables(&inline_ident, &mut rust_block.methods)?; + try_inline_self_invokables(&inline_ident, &mut rust_block.signals)?; + try_inline_self_invokables(&inline_ident, &mut rust_block.inherited_methods)?; + } + + // Inlining `extern "C++Qt"` blocks + for cpp_block in &mut parser.cxx_qt_data.extern_cxxqt_blocks { + let mut iter = cpp_block.qobjects.iter(); + let mut inline_ident = iter + .next() + .map(|obj| format_ident!("{}", obj.declaration.ident)); + + if iter.next().is_some() { + inline_ident = None; + } + + try_inline_self_invokables(&inline_ident, &mut cpp_block.signals)?; } Ok(()) @@ -87,7 +79,6 @@ pub fn qualify_self_types(parser: &mut Parser) -> Result<()> { #[cfg(test)] mod tests { use super::*; - use crate::parser::method::ParsedMethod; use crate::tests::assert_parse_errors; use syn::parse_quote; @@ -160,6 +151,23 @@ mod tests { } } // More than 1 QObjects in block + { + #[cxx_qt::bridge] + mod ffi { + unsafe extern "C++Qt" { + #[qobject] + type MyObject = super::T; + + #[qobject] + type SecondObject = super::S; + + #[qsignal] + fn my_method(self: Pin<&mut Self>); + } + } + } + + // More than one qobject, in RustQt { #[cxx_qt::bridge] mod ffi { @@ -168,7 +176,7 @@ mod tests { type MyObject = super::T; #[qobject] - type MyOtherObject = super::S; + type SecondObject = super::S; fn my_method(&self); } @@ -176,16 +184,4 @@ mod tests { } } } - - #[test] - fn test_invalid_inline_call() { - let method_sig = parse_quote! { - fn test(&self); - }; - let mut methods = vec![ParsedMethod::mock_qinvokable(&method_sig)]; - - // If inlining is set to take place, an Ident is required to inline, here it is `None` - let data = try_inline_self_invokables(true, &None, &mut methods); - assert!(data.is_err()); - } } diff --git a/crates/cxx-qt-macro/src/lib.rs b/crates/cxx-qt-macro/src/lib.rs index 9c40102f0..96c1a7517 100644 --- a/crates/cxx-qt-macro/src/lib.rs +++ b/crates/cxx-qt-macro/src/lib.rs @@ -15,7 +15,7 @@ use proc_macro::TokenStream; use syn::{parse_macro_input, ItemMod}; -use cxx_qt_gen::{qualify_self_types, write_rust, GeneratedRustBlocks, Parser}; +use cxx_qt_gen::{self_inlining::qualify_self_types, write_rust, GeneratedRustBlocks, Parser}; #[proc_macro_attribute] pub fn bridge(args: TokenStream, input: TokenStream) -> TokenStream { diff --git a/examples/qml_basics/src/main.rs b/examples/qml_basics/src/main.rs index a23f3dd9c..01c86c096 100644 --- a/examples/qml_basics/src/main.rs +++ b/examples/qml_basics/src/main.rs @@ -31,7 +31,7 @@ mod qobject { type Greeter = super::GreeterRust; #[qinvokable] - fn greet(&self) -> QString; + fn greet(self: &Greeter) -> QString; } }