Skip to content

Commit

Permalink
cxx-qt-gen: move #[qinvokable] to be inside extern "RustQt"
Browse files Browse the repository at this point in the history
Closes KDAB#558
  • Loading branch information
ahayzen-kdab committed Jun 12, 2023
1 parent 10e324c commit afbdbc3
Show file tree
Hide file tree
Showing 46 changed files with 1,273 additions and 1,236 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Moved to `syn` 2.0 internally and for any exported `syn` types
- `impl cxx_qt::Threading for qobject::T` now needs to be specified for `qt_thread()` to be available
- `#[cxx_qt::qsignals]` and `#[cxx_qt::inherit]` are now used in an `extern "RustQt"` block as `#[qsignal]` and `#[inherit]`
- `#[qinvokable]` is now defined as a signature in `extern "RustQt"`

### Removed

Expand Down
17 changes: 11 additions & 6 deletions book/src/concepts/inheritance.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ It can be placed in front of a function in a `extern "RustQt"` block in a `#[cxx
{{#include ../../../examples/qml_features/rust/src/custom_base_class.rs:book_inherit_qalm_impl_unsafe}}
impl qobject::CustomBaseClass {
{{#include ../../../examples/qml_features/rust/src/custom_base_class.rs:book_inherit_clear_signature}}
```

```rust,ignore
{{#include ../../../examples/qml_features/rust/src/custom_base_class.rs:book_inherit_clear}}
}
```
[Full example](https://github.com/KDAB/cxx-qt/blob/main/examples/qml_features/rust/src/custom_base_class.rs)

Expand Down Expand Up @@ -51,10 +53,11 @@ The below example overrides the [`data`](https://doc.qt.io/qt-6/qabstractitemmod
```rust,ignore
{{#include ../../../examples/qml_features/rust/src/custom_base_class.rs:book_inherit_qalm}}
impl qobject::CustomBaseClass {
{{#include ../../../examples/qml_features/rust/src/custom_base_class.rs:book_inherit_data_signature}}
```

```rust,ignore
{{#include ../../../examples/qml_features/rust/src/custom_base_class.rs:book_inherit_data}}
}
```
[Full example](https://github.com/KDAB/cxx-qt/blob/main/examples/qml_features/rust/src/custom_base_class.rs)

Expand All @@ -67,8 +70,10 @@ Example:
{{#include ../../../examples/qml_features/rust/src/custom_base_class.rs:book_inherit_qalm_impl_safe}}
impl qobject::CustomBaseClass {
{{#include ../../../examples/qml_features/rust/src/custom_base_class.rs:book_inherit_can_fetch_more_signature}}
```

```rust,ignore
{{#include ../../../examples/qml_features/rust/src/custom_base_class.rs:book_inherit_can_fetch_more}}
}
```
[Full example](https://github.com/KDAB/cxx-qt/blob/main/examples/qml_features/rust/src/custom_base_class.rs)
9 changes: 7 additions & 2 deletions book/src/getting-started/2-our-first-cxx-qt-module.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,14 @@ For any Rust struct `T` that is marked with `#[cxx_qt::qobject]`, CXX-Qt will ex
In our case, this means we can refer to the C++ QObject for our `MyObject` struct, as `qobject::MyObject`.

This type can be used like any other CXX opaque type.
Additionally, CXX-Qt allows us to add functionality to this QObject by using `impl qobject::MyObject` together with `#[qinvokable]`.
Additionally, CXX-Qt allows us to add functionality to this QObject by referring to the type as the self type of functions in an `extern "RustQt"` block in together with `#[qinvokable]`.
```rust,ignore
{{#include ../../../examples/qml_minimal/rust/src/cxxqt_object.rs:book_rustobj_impl}}
{{#include ../../../examples/qml_minimal/rust/src/cxxqt_object.rs:book_rustobj_invokable_signature}}
```

And then implementing the invokables outside the bridge using `impl qobject::MyObject`.
```rust,ignore
{{#include ../../../examples/qml_minimal/rust/src/cxxqt_object.rs:book_rustobj_invokable_impl}}
```

In our case, we define two new functions:
Expand Down
8 changes: 6 additions & 2 deletions book/src/qobject/qobject_struct.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,15 @@ For details on this, see the [`qobject::T` page](./generated-qobject.md).
The important part for invokables is that they need to be implemented on the `qobject::T`, not `T`.
Therefore they have access to both C++ and Rust methods. CXX-Qt adds wrapper code around your invokables to automatically convert 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 tells CXX-Qt to expose the method on the generated C++ class.
To mark a method as invokable, simply add the `#[qinvokable]` attribute to the Rust function in the `extern "RustQt"` block. This tells CXX-Qt to expose the method on the generated C++ class.
`Q_INVOKABLE` will be added to the C++ definition of the method, allowing QML to call the invokable.

``` rust,ignore,noplayground
{{#include ../../../examples/qml_features/rust/src/invokables.rs:book_impl_qobject}}
{{#include ../../../examples/qml_features/rust/src/invokables.rs:book_invokable_signature}}
```

``` rust,ignore,noplayground
{{#include ../../../examples/qml_features/rust/src/invokables.rs:book_invokable_impl}}
```

Note that an invokable may only use `self: Pin<&mut Self>` or `&self` as self types.
Expand Down
20 changes: 15 additions & 5 deletions crates/cxx-qt-gen/src/generator/cpp/invokable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,32 +161,40 @@ mod tests {
fn test_generate_cpp_invokables() {
let invokables = vec![
ParsedQInvokable {
method: parse_quote! { fn void_invokable(&self) {} },
method: parse_quote! { fn void_invokable(self: &qobject::MyObject); },
qobject_ident: format_ident!("MyObject"),
mutable: false,
safe: true,
parameters: vec![],
specifiers: HashSet::new(),
},
ParsedQInvokable {
method: parse_quote! { fn trivial_invokable(&self, param: i32) -> i32 {} },
method: parse_quote! { fn trivial_invokable(self: &qobject::MyObject, param: i32) -> i32; },
qobject_ident: format_ident!("MyObject"),
mutable: false,
safe: true,
parameters: vec![ParsedFunctionParameter {
ident: format_ident!("param"),
ty: parse_quote! { i32 },
}],
specifiers: HashSet::new(),
},
ParsedQInvokable {
method: parse_quote! { fn opaque_invokable(self: Pin<&mut Self>, param: &QColor) -> UniquePtr<QColor> {} },
method: parse_quote! { fn opaque_invokable(self: Pin<&mut qobject::MyObject>, param: &QColor) -> UniquePtr<QColor>; },
qobject_ident: format_ident!("MyObject"),
mutable: true,
safe: true,
parameters: vec![ParsedFunctionParameter {
ident: format_ident!("param"),
ty: parse_quote! { &QColor },
}],
specifiers: HashSet::new(),
},
ParsedQInvokable {
method: parse_quote! { fn specifiers_invokable(&self, param: i32) -> i32 {} },
method: parse_quote! { fn specifiers_invokable(self: &qobject::MyObject, param: i32) -> i32; },
qobject_ident: format_ident!("MyObject"),
mutable: false,
safe: true,
parameters: vec![ParsedFunctionParameter {
ident: format_ident!("param"),
ty: parse_quote! { i32 },
Expand Down Expand Up @@ -298,8 +306,10 @@ mod tests {
#[test]
fn test_generate_cpp_invokables_mapped_cxx_name() {
let invokables = vec![ParsedQInvokable {
method: parse_quote! { fn trivial_invokable(&self, param: A) -> B {} },
method: parse_quote! { fn trivial_invokable(self: &qobject::MyObject, param: A) -> B; },
qobject_ident: format_ident!("MyObject"),
mutable: false,
safe: true,
parameters: vec![ParsedFunctionParameter {
ident: format_ident!("param"),
ty: parse_quote! { i32 },
Expand Down
17 changes: 8 additions & 9 deletions crates/cxx-qt-gen/src/generator/naming/invokable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
use crate::{generator::naming::CombinedIdent, parser::invokable::ParsedQInvokable};
use convert_case::{Case, Casing};
use quote::format_ident;
use syn::{Ident, ImplItemFn};
use syn::{ForeignItemFn, Ident};

/// Names for parts of a Q_INVOKABLE
pub struct QInvokableName {
Expand All @@ -19,8 +19,8 @@ impl From<&ParsedQInvokable> for QInvokableName {
}
}

impl From<&ImplItemFn> for QInvokableName {
fn from(method: &ImplItemFn) -> Self {
impl From<&ForeignItemFn> for QInvokableName {
fn from(method: &ForeignItemFn) -> Self {
let ident = &method.sig.ident;
Self {
name: CombinedIdent::from_rust_function(ident.clone()),
Expand Down Expand Up @@ -50,14 +50,13 @@ mod tests {

#[test]
fn test_from_impl_method() {
let item: ImplItemFn = parse_quote! {
fn my_invokable() {

}
};
let parsed = ParsedQInvokable {
method: item,
method: parse_quote! {
fn my_invokable(self: &qobject::MyObject);
},
qobject_ident: format_ident!("MyObject"),
mutable: false,
safe: true,
parameters: vec![],
specifiers: HashSet::new(),
};
Expand Down
94 changes: 27 additions & 67 deletions crates/cxx-qt-gen/src/generator/rust/invokable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@
use crate::{
generator::{
naming::{invokable::QInvokableName, qobject::QObjectName},
rust::{
fragment::RustFragmentPair, qobject::GeneratedRustQObjectBlocks,
types::is_unsafe_cxx_type,
},
rust::{fragment::RustFragmentPair, qobject::GeneratedRustQObjectBlocks},
},
parser::invokable::ParsedQInvokable,
};
Expand All @@ -30,7 +27,6 @@ pub fn generate_rust_invokables(
let wrapper_ident_cpp = idents.wrapper.cpp.to_string();
let wrapper_ident_rust = &idents.wrapper.rust;
let invokable_ident_rust = &idents.name.rust;
let original_method = &invokable.method;

let cpp_struct = if invokable.mutable {
quote! { Pin<&mut #cpp_class_name_rust> }
Expand Down Expand Up @@ -62,21 +58,13 @@ pub fn generate_rust_invokables(
} else {
quote! { return }
};
// Determine if unsafe is required due to an unsafe parameter or return type
let has_unsafe_param = invokable
.parameters
.iter()
.any(|parameter| is_unsafe_cxx_type(&parameter.ty));
let has_unsafe_return = if let ReturnType::Type(_, ty) = return_type {
is_unsafe_cxx_type(ty)
} else {
false
};
let has_unsafe = if has_unsafe_param || has_unsafe_return {
quote! { unsafe }
} else {
quote! {}
};

let mut unsafe_block = None;
let mut unsafe_call = Some(quote! { unsafe });
if invokable.safe {
std::mem::swap(&mut unsafe_call, &mut unsafe_block);
}

let parameter_names = invokable
.parameters
.iter()
Expand All @@ -85,26 +73,22 @@ pub fn generate_rust_invokables(

let fragment = RustFragmentPair {
cxx_bridge: vec![quote! {
// TODO: is an unsafe block valid?
extern "Rust" {
#[cxx_name = #wrapper_ident_cpp]
#has_unsafe fn #wrapper_ident_rust(#parameter_signatures) #return_type;
#unsafe_call fn #wrapper_ident_rust(#parameter_signatures) #return_type;
}
}],
implementation: vec![
// TODO: not all methods have a wrapper
quote! {
impl #rust_struct_name_rust {
#[doc(hidden)]
pub #has_unsafe fn #wrapper_ident_rust(#parameter_signatures) #return_type {
pub #unsafe_call fn #wrapper_ident_rust(#parameter_signatures) #return_type {
#has_return cpp.#invokable_ident_rust(#(#parameter_names),*);
}
}
},
quote! {
impl #cpp_class_name_rust {
#original_method
}
},
],
};

Expand Down Expand Up @@ -134,32 +118,40 @@ mod tests {
fn test_generate_rust_invokables() {
let invokables = vec![
ParsedQInvokable {
method: parse_quote! { fn void_invokable(&self) {} },
method: parse_quote! { fn void_invokable(self: &qobject::MyObject); },
qobject_ident: format_ident!("MyObject"),
mutable: false,
safe: true,
parameters: vec![],
specifiers: HashSet::new(),
},
ParsedQInvokable {
method: parse_quote! { fn trivial_invokable(&self, param: i32) -> i32 {} },
method: parse_quote! { fn trivial_invokable(self: &qobject::MyObject, param: i32) -> i32; },
qobject_ident: format_ident!("MyObject"),
mutable: false,
safe: true,
parameters: vec![ParsedFunctionParameter {
ident: format_ident!("param"),
ty: parse_quote! { i32 },
}],
specifiers: HashSet::new(),
},
ParsedQInvokable {
method: parse_quote! { fn opaque_invokable(self: Pin<&mut Self>, param: &QColor) -> UniquePtr<QColor> {} },
method: parse_quote! { fn opaque_invokable(self: Pin<&mut qobject::MyObject>, param: &QColor) -> UniquePtr<QColor>; },
qobject_ident: format_ident!("MyObject"),
mutable: true,
safe: true,
parameters: vec![ParsedFunctionParameter {
ident: format_ident!("param"),
ty: parse_quote! { &QColor },
}],
specifiers: HashSet::new(),
},
ParsedQInvokable {
method: parse_quote! { fn unsafe_invokable(&self, param: *mut T) -> *mut T {} },
method: parse_quote! { unsafe fn unsafe_invokable(self: &qobject::MyObject, param: *mut T) -> *mut T; },
qobject_ident: format_ident!("MyObject"),
mutable: false,
safe: false,
parameters: vec![ParsedFunctionParameter {
ident: format_ident!("param"),
ty: parse_quote! { *mut T },
Expand All @@ -172,7 +164,7 @@ mod tests {
let generated = generate_rust_invokables(&invokables, &qobject_idents).unwrap();

assert_eq!(generated.cxx_mod_contents.len(), 4);
assert_eq!(generated.cxx_qt_mod_contents.len(), 8);
assert_eq!(generated.cxx_qt_mod_contents.len(), 4);

// void_invokable
assert_tokens_eq(
Expand All @@ -195,14 +187,6 @@ mod tests {
}
},
);
assert_tokens_eq(
&generated.cxx_qt_mod_contents[1],
quote! {
impl MyObjectQt {
fn void_invokable(&self) {}
}
},
);

// trivial_invokable
assert_tokens_eq(
Expand All @@ -215,7 +199,7 @@ mod tests {
},
);
assert_tokens_eq(
&generated.cxx_qt_mod_contents[2],
&generated.cxx_qt_mod_contents[1],
quote! {
impl MyObject {
#[doc(hidden)]
Expand All @@ -225,14 +209,6 @@ mod tests {
}
},
);
assert_tokens_eq(
&generated.cxx_qt_mod_contents[3],
quote! {
impl MyObjectQt {
fn trivial_invokable(&self, param: i32) -> i32 {}
}
},
);

// opaque_invokable
assert_tokens_eq(
Expand All @@ -245,7 +221,7 @@ mod tests {
},
);
assert_tokens_eq(
&generated.cxx_qt_mod_contents[4],
&generated.cxx_qt_mod_contents[2],
quote! {
impl MyObject {
#[doc(hidden)]
Expand All @@ -255,14 +231,6 @@ mod tests {
}
},
);
assert_tokens_eq(
&generated.cxx_qt_mod_contents[5],
quote! {
impl MyObjectQt {
fn opaque_invokable(self: Pin<&mut Self>, param: &QColor) -> UniquePtr<QColor> {}
}
},
);

// unsafe_invokable
assert_tokens_eq(
Expand All @@ -275,7 +243,7 @@ mod tests {
},
);
assert_tokens_eq(
&generated.cxx_qt_mod_contents[6],
&generated.cxx_qt_mod_contents[3],
quote! {
impl MyObject {
#[doc(hidden)]
Expand All @@ -285,13 +253,5 @@ mod tests {
}
},
);
assert_tokens_eq(
&generated.cxx_qt_mod_contents[7],
quote! {
impl MyObjectQt {
fn unsafe_invokable(&self, param: *mut T) -> *mut T {}
}
},
);
}
}
Loading

0 comments on commit afbdbc3

Please sign in to comment.