Skip to content

Commit

Permalink
cxx-qt-gen: use impl qobject::T to define the C++ context
Browse files Browse the repository at this point in the history
This then creates a natural way to add methods to the C++ context.

It also means later with nested objects they can be referred to
by crates::module::qobject::MyObject.

And any Rust traits or methods are natural as they are impl A for T,
with traits for the C++ object being natural with impl A for qobject::T
  • Loading branch information
ahayzen-kdab authored and Be-ing committed Oct 6, 2022
1 parent a92f7b4 commit 9576f44
Show file tree
Hide file tree
Showing 25 changed files with 71 additions and 141 deletions.
4 changes: 2 additions & 2 deletions book/src/qobject/qobject_struct.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ The `#[cxx_qt::qobject]` marked struct allows you to define the following items

## Invokables

A `impl cxx_qt::QObject<T>` is used to define invokables, the `impl cxx_qt::QObject<T>` defines that the methods are implemented onto the C++ QObject `T`.
A `impl qobject::T` is used to define invokables, the `impl qobject::T` defines that the methods are implemented onto the C++ QObject `T`.
Therefore they have access to both C++ and Rust methods. Also CXX-Qt adds wrapper code around your invokables to automatically perform any conversion between the [C++ and Rust types](../concepts/types.md).

To mark a method as invokable simply add the `#[qinvokable]` attribute to the Rust method. This then causes `Q_INVOKABLE` to be set on the C++ definition of the method, allowing QML to call the invokable.
Expand All @@ -42,6 +42,6 @@ If you want to provide default values for your QObject, then instead of deriving

Fields within the `#[cxx_qt::qobject]` marked struct that aren't tagged are not exposed as properties to Qt. These can be considered as "private to Rust" fields, and are useful for storing channels for threading or internal information for the QObject.

Methods implemented using `impl T` (and not `impl cxx_qt::QObject<T>`) are just normal Rust member methods.
Methods implemented using `impl T` (and not `impl qobject::T`) are just normal Rust member methods.
Therefore they do not have access to any C++ or QObject functionality (e.g. emitting Signals, changing properties, etc.)
You will usually only need to use `impl T` if you want to also use your struct as a normal Rust struct, that is not wrapped in a QObject.
24 changes: 10 additions & 14 deletions crates/cxx-qt-gen/src/parser/cxxqtdata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
use crate::parser::{qobject::ParsedQObject, signals::ParsedSignalsEnum};
use crate::syntax::{
attribute::{attribute_find_path, attribute_tokens_to_ident},
path::{path_angled_args_to_type_path, path_compare_str, path_to_single_ident},
path::path_to_single_ident,
};
use std::collections::HashMap;
use syn::{spanned::Spanned, Error, Ident, Item, ItemEnum, ItemImpl, Result, Type, TypePath};
Expand Down Expand Up @@ -93,25 +93,21 @@ impl ParsedCxxQtData {
/// 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<Option<Item>> {
// If the implementation has a cxx_qt::QObject
// If the implementation has a qobject::T
// then this is the block of methods to be implemented on the C++ object
if let Type::Path(TypePath { path, .. }) = imp.self_ty.as_ref() {
if path_compare_str(path, &["cxx_qt", "QObject"]) {
// Read the T from cxx_qt::QObject<T> and error if it's missing
let qobject_path = path_angled_args_to_type_path(path)?;
if let Some(qobject) = self
.qobjects
// Convert the path to a single ident, and error if it isn't
.get_mut(&path_to_single_ident(&qobject_path)?)
{
// Find if we are a impl qobject::T
if path.segments.len() == 2 && path.segments[0].ident == "qobject" {
if let Some(qobject) = self.qobjects.get_mut(&path.segments[1].ident) {
// Extract the ImplItem's from each Impl block
qobject.parse_impl_items(&imp.items)?;
} else {
return Err(Error::new(
imp.span(),
"No matching QObject found for the given cxx_qt::QObject<T> impl block.",
"No matching QObject found for the given qobject::T impl block.",
));
}

return Ok(None);
// Find if we are an impl block for a qobject
} else if let Some(qobject) = self.qobjects.get_mut(&path_to_single_ident(path)?) {
Expand Down Expand Up @@ -282,7 +278,7 @@ mod tests {
let mut cxx_qt_data = create_parsed_cxx_qt_data();

let item: Item = tokens_to_syn(quote! {
impl cxx_qt::QObject<MyObject> {
impl qobject::MyObject {
#[qinvokable]
fn invokable() {}

Expand All @@ -300,7 +296,7 @@ mod tests {
let mut cxx_qt_data = create_parsed_cxx_qt_data();

let item: Item = tokens_to_syn(quote! {
impl cxx_qt::QObject {
impl qobject::MyObject::Bad {
#[qinvokable]
fn invokable() {}
}
Expand All @@ -314,7 +310,7 @@ mod tests {
let mut cxx_qt_data = create_parsed_cxx_qt_data();

let item: Item = tokens_to_syn(quote! {
impl cxx_qt::QObject<UnknownObj> {
impl qobject::UnknownObj {
#[qinvokable]
fn invokable() {}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/cxx-qt-gen/src/parser/qobject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ impl ParsedQObject {

/// Extract all methods (both invokable and non-invokable) from [syn::ImplItem]'s from each Impl block
///
/// These will have come from a impl cxx_qt::QObject<T> block
/// These will have come from a impl qobject::T block
pub fn parse_impl_items(&mut self, items: &[ImplItem]) -> Result<()> {
for item in items {
// Check if this item is a method
Expand Down
78 changes: 1 addition & 77 deletions crates/cxx-qt-gen/src/syntax/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@
//
// SPDX-License-Identifier: MIT OR Apache-2.0

use syn::{
spanned::Spanned, AngleBracketedGenericArguments, Error, GenericArgument, Ident, Path,
PathArguments, Result, Type, TypePath,
};
use syn::{spanned::Spanned, Error, Ident, Path, Result};

/// Returns whether the [syn::Path] matches a given string slice
pub fn path_compare_str(path: &Path, string: &[&str]) -> bool {
Expand Down Expand Up @@ -35,46 +32,6 @@ pub fn path_to_single_ident(path: &Path) -> Result<Ident> {
}
}

/// Internal helper to extract angled brackets from a [syn::Path]
fn path_angled_to_arguments(path: &'_ Path) -> Option<&'_ AngleBracketedGenericArguments> {
if let Some(last) = path.segments.last() {
if let PathArguments::AngleBracketed(args) = &last.arguments {
return Some(args);
}
}

None
}

/// In std::collections::HashSet<T> extract the T as a [syn::Path]
///
/// Error if there isn't a single type path but none or many
pub fn path_angled_args_to_type_path(path: &Path) -> Result<Path> {
let paths = path_angled_args_to_type_path_list(path);
if paths.len() == 1 {
Ok(paths[0].clone())
} else {
Err(Error::new(
path.span(),
"Expected only one Path in the Path's angled bracketed generic arguments",
))
}
}

/// In std::collections::HashMap<K, V> extract the K, V as a Vec of [syn::Path]'s
pub fn path_angled_args_to_type_path_list(path: &Path) -> Vec<Path> {
let mut items = vec![];
if let Some(inner) = path_angled_to_arguments(path) {
for arg in &inner.args {
if let GenericArgument::Type(Type::Path(TypePath { path, .. })) = arg {
items.push(path.clone());
}
}
}

items
}

#[cfg(test)]
mod tests {
use super::*;
Expand All @@ -100,37 +57,4 @@ mod tests {
let ident = path_to_single_ident(&path).unwrap();
assert_eq!(ident, "a");
}

#[test]
fn test_path_angled_args_to_type_path() {
let path: Path = tokens_to_syn(quote! { std::collections::HashSet<a::b::c> });
let path = path_angled_args_to_type_path(&path).unwrap();
assert!(path_compare_str(&path, &["a", "b", "c"]));

let path: Path = tokens_to_syn(quote! { std::collections::HashMap<a::b::c, a::b::c> });
assert!(path_angled_args_to_type_path(&path).is_err());

let path: Path = tokens_to_syn(quote! { std::collections::HashMap<> });
assert!(path_angled_args_to_type_path(&path).is_err());

let path: Path = tokens_to_syn(quote! { a::b::c });
assert!(path_angled_args_to_type_path(&path).is_err());
}

#[test]
fn test_path_angled_args_to_type_path_list() {
let path: Path = tokens_to_syn(quote! { std::collections::HashSet<a::b::c> });
assert_eq!(path_angled_args_to_type_path_list(&path).len(), 1);
let path = &path_angled_args_to_type_path_list(&path)[0];
assert!(path_compare_str(path, &["a", "b", "c"]));

let path: Path = tokens_to_syn(quote! { std::collections::HashMap<a::b::c, a::b::c> });
assert_eq!(path_angled_args_to_type_path_list(&path).len(), 2);

let path: Path = tokens_to_syn(quote! { std::collections::HashMap<> });
assert_eq!(path_angled_args_to_type_path_list(&path).len(), 0);

let path: Path = tokens_to_syn(quote! { a::b::c });
assert_eq!(path_angled_args_to_type_path_list(&path).len(), 0);
}
}
25 changes: 25 additions & 0 deletions crates/cxx-qt-gen/src/writer/rust/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ pub fn write_rust(generated: &GeneratedRustBlocks) -> TokenStream {
.expect("Could not build CXX common block"),
);

let mut qobject_types = vec![];
for qobject in &generated.qobjects {
// Add the common blocks into the bridge which we need
cxx_mod_contents.extend_from_slice(&qobject.blocks.cxx_mod_contents);
Expand All @@ -131,8 +132,23 @@ pub fn write_rust(generated: &GeneratedRustBlocks) -> TokenStream {
.map(|block| syn::parse2(block).expect("Could not build CXX-Qt common block"))
.collect(),
);

// Add the type alias to the C++ struct
let cpp_struct_ident = &qobject.cpp_struct_ident;
let rust_struct_ident = &qobject.rust_struct_ident;
qobject_types.push(quote! { pub type #rust_struct_ident = super::#cpp_struct_ident; })
}

// Create the qobject block for the type alias
cxx_qt_mod_contents.push(
syn::parse2(quote! {
pub mod qobject {
#(#qobject_types)*
}
})
.expect("Could not build qobject block"),
);

// Inject the CXX blocks
if let Some((_, items)) = &mut cxx_mod.content {
items.extend(cxx_mod_contents.into_iter());
Expand Down Expand Up @@ -386,6 +402,10 @@ mod tests {
pub fn create_rs_my_object() -> std::boxed::Box<MyObject> {
std::default::Default::default()
}

pub mod qobject {
pub type MyObject = super::MyObjectQt;
}
}
}
.into_token_stream()
Expand Down Expand Up @@ -517,6 +537,11 @@ mod tests {
pub fn create_rs_second_object() -> std::boxed::Box<SecondObject> {
std::default::Default::default()
}

pub mod qobject {
pub type FirstObject = super::FirstObjectQt;
pub type SecondObject = super::SecondObjectQt;
}
}
}
.into_token_stream()
Expand Down
2 changes: 1 addition & 1 deletion crates/cxx-qt-gen/test_inputs/invokables.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ mod ffi {
#[derive(Default)]
pub struct MyObject;

impl cxx_qt::QObject<MyObject> {
impl qobject::MyObject {
#[qinvokable]
pub fn invokable(&self) {
println!("invokable");
Expand Down
2 changes: 1 addition & 1 deletion crates/cxx-qt-gen/test_inputs/passthrough_and_naming.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ pub mod ffi {
}
}

impl cxx_qt::QObject<MyObject> {
impl qobject::MyObject {
#[qinvokable]
pub fn invokable_name(self: Pin<&mut Self>) {
println!("Bye from Rust!");
Expand Down
2 changes: 1 addition & 1 deletion crates/cxx-qt-gen/test_inputs/signals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ mod ffi {
#[derive(Default)]
pub struct MyObject;

impl cxx_qt::QObject<MyObject> {
impl qobject::MyObject {
#[qinvokable]
pub fn invokable(self: Pin<&mut Self>) {
self.as_mut().emit(MySignals::DataChanged {
Expand Down
4 changes: 4 additions & 0 deletions crates/cxx-qt-gen/test_outputs/invokables.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,4 +259,8 @@ mod cxx_qt_ffi {
pub fn create_rs_my_object() -> std::boxed::Box<MyObject> {
std::default::Default::default()
}

pub mod qobject {
pub type MyObject = super::MyObjectQt;
}
}
4 changes: 4 additions & 0 deletions crates/cxx-qt-gen/test_outputs/passthrough_and_naming.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,4 +216,8 @@ mod cxx_qt_ffi {
pub fn create_rs_my_object() -> std::boxed::Box<MyObject> {
std::default::Default::default()
}

pub mod qobject {
pub type MyObject = super::MyObjectQt;
}
}
4 changes: 4 additions & 0 deletions crates/cxx-qt-gen/test_outputs/properties.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,4 +258,8 @@ mod cxx_qt_ffi {
pub fn create_rs_my_object() -> std::boxed::Box<MyObject> {
std::default::Default::default()
}

pub mod qobject {
pub type MyObject = super::MyObjectQt;
}
}
4 changes: 4 additions & 0 deletions crates/cxx-qt-gen/test_outputs/signals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,4 +126,8 @@ mod cxx_qt_ffi {
pub fn create_rs_my_object() -> std::boxed::Box<MyObject> {
std::default::Default::default()
}

pub mod qobject {
pub type MyObject = super::MyObjectQt;
}
}
33 changes: 1 addition & 32 deletions crates/cxx-qt/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use cxx_qt_gen::{write_rust, GeneratedRustBlocks, Parser};
/// property: i32,
/// }
///
/// impl cxx_qt::QObject<RustObj> {
/// impl qobject::RustObj {
/// #[qinvokable]
/// fn invokable(&self, a: i32, b: i32) -> i32 {
/// a + b
Expand Down Expand Up @@ -106,37 +106,6 @@ pub fn qobject(_args: TokenStream, _input: TokenStream) -> TokenStream {
unreachable!("cxx_qt::qobject should not be used as a macro by itself. Instead it should be used within a cxx_qt::bridge definition")
}

/// A macro which describes that the inner methods should be implemented on the C++ QObject.
/// This allows for defining C++ methods which are Q_INVOKABLE for QML in Rust.
///
/// It should not be used by itself and instead should be used inside a cxx_qt::bridge definition.
///
/// # Example
///
/// ```ignore
/// #[cxx_qt::bridge]
/// mod my_object {
/// #[cxx_qt::qobject]
/// #[derive(Default)]
/// struct RustObj {
/// #[qproperty]
/// property: i32,
/// }
///
/// impl cxx_qt::QObject<RustObj> {
/// #[qinvokable]
/// fn invokable(&self, a: i32, b: i32) -> i32 {
/// a + b
/// }
/// }
/// }
/// ```
#[proc_macro]
#[allow(non_snake_case)]
pub fn QObject(_input: TokenStream) -> TokenStream {
unreachable!("cxx_qt::QObject should not be used as a macro by itself. Instead it should be used within a cxx_qt::bridge definition")
}

// Take the module and C++ namespace and generate the rust code
fn extract_and_generate(module: ItemMod) -> TokenStream {
let parser = Parser::from(module).unwrap();
Expand Down
2 changes: 1 addition & 1 deletion examples/demo_threading/rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ mod ffi {
}
}

impl cxx_qt::QObject<EnergyUsage> {
impl qobject::EnergyUsage {
#[qinvokable]
pub fn start_server(self: Pin<&mut Self>) {
if self.rust().join_handles.is_some() {
Expand Down
2 changes: 1 addition & 1 deletion examples/qml_extension_plugin/plugin/rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ mod ffi {
}
}

impl cxx_qt::QObject<MyObject> {
impl qobject::MyObject {
#[qinvokable]
pub fn increment(self: Pin<&mut Self>) {
let new_number = self.number() + 1;
Expand Down
2 changes: 1 addition & 1 deletion examples/qml_features/rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ mod ffi {
}
}

impl cxx_qt::QObject<MyObject> {
impl qobject::MyObject {
#[qinvokable]
pub fn increment_number_self(mut self: Pin<&mut Self>) {
let value = self.number() + 1;
Expand Down
2 changes: 1 addition & 1 deletion examples/qml_features/rust/src/mock_qt_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ mod ffi {
}
}

impl cxx_qt::QObject<MockQtTypes> {
impl qobject::MockQtTypes {
#[qinvokable]
pub fn test_signal(mut self: Pin<&mut Self>) {
self.as_mut().emit(Signal::Ready);
Expand Down
Loading

0 comments on commit 9576f44

Please sign in to comment.