Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cxx-qt-gen: move #[qinvokable] to be inside extern "RustQt" #581

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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