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: use extern "RustQt" block for inherit and signals #580

Merged
merged 1 commit into from
Jun 9, 2023
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 @@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Always call `qt_build_utils::setup_linker()` in `CxxQtBuilder` and remove the proxy method
- 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]`

### Removed

Expand Down
2 changes: 1 addition & 1 deletion book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ SPDX-License-Identifier: MIT OR Apache-2.0
- [QObject](./qobject/index.md)
- [`#[cxx_qt::bridge]` - Bridge Macro](./qobject/bridge-macro.md)
- [`#[cxx_qt::qobject]` - Defining QObjects](./qobject/qobject_struct.md)
- [`#[cxx_qt::qsignals]` - Signals macro](./qobject/signals.md)
- [`#[qsignal]` - Signal macro](./qobject/signals.md)
- [`qobject::T` - The generated QObject](./qobject/generated-qobject.md)
- [CxxQtThread](./qobject/cxxqtthread.md)
- [Concepts](./concepts/index.md)
Expand Down
10 changes: 5 additions & 5 deletions book/src/concepts/inheritance.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ Some Qt APIs require you to override certain methods from an abstract base class
To support creating such subclasses directly from within Rust, CXX-Qt provides you with multiple helpers.

## Accessing base class methods
To access the methods of a base class in Rust, use the `#[cxx_qt::inherit]` macro.
It can be placed in front of an `extern "C++"` block in a `#[cxx_qt::bridge]`.
To access the methods of a base class in Rust, use the `#[inherit]` macro.
It can be placed in front of a function in a `extern "RustQt"` block in a `#[cxx_qt::bridge]`.

```rust,ignore
{{#include ../../../examples/qml_features/rust/src/custom_base_class.rs:book_inherit_qalm}}
Expand All @@ -27,10 +27,10 @@ It can be placed in front of an `extern "C++"` block in a `#[cxx_qt::bridge]`.
[Full example](https://github.com/KDAB/cxx-qt/blob/main/examples/qml_features/rust/src/custom_base_class.rs)

This code implements a QAbstractListModel subclass.
For this, the `clear` method implemented in Rust needs to call `beginResetModel` and related methods from the base class, which are made accessible by using `#[cxx_qt::inherit]`.
For this, the `clear` method implemented in Rust needs to call `beginResetModel` and related methods from the base class, which are made accessible by using `#[inherit]`.
See [the Qt docs](https://doc.qt.io/qt-6/qabstractlistmodel.html) for more details on the specific subclassing requirements.

Methods can be declared inside `#[cxx_qt::inherit]` in `extern "C++"` blocks similar to CXX, with the same restrictions regarding which types can be used.
Methods in a `extern "RustQt"` block similar to CXX can be tagged with an `#[inherit]` attribute, with the same restrictions regarding which types can be used.
Additionally, the `self` type must be either `self: Pin<&mut qobject::T>` or `self: &qobject::T`, where `qobject::T` must refer to a QObject marked with `#[cxx_qt::qobject]` in the `#[cxx_qt::bridge]`

The declared methods will be case-converted as in other CXX-Qt APIs.
Expand Down Expand Up @@ -58,7 +58,7 @@ The below example overrides the [`data`](https://doc.qt.io/qt-6/qabstractitemmod
```
[Full example](https://github.com/KDAB/cxx-qt/blob/main/examples/qml_features/rust/src/custom_base_class.rs)

When a method is overridden using `cxx_override`, the base class version of the method can be accessed by using `#[cxx_qt::inherit]` in combination with the `#[cxx_name]` attribute.
When a method is overridden using `cxx_override`, the base class version of the method can be accessed by using `#[inherit]` in combination with the `#[cxx_name]` attribute.
In this case the base class version of the function must get a different name because Rust can't have two functions with the same name on one type.

Example:
Expand Down
2 changes: 1 addition & 1 deletion book/src/getting-started/1-qobjects-in-rust.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ Then you can use the afformentioned features with the help of more macros.
- `#[cxx_qt::qobject]` - Expose a Rust struct to Qt as a QObject subclass.
- `#[qproperty]` - Expose a field of the Rust struct to QML/C++ as a [`Q_PROPERTY`](https://doc.qt.io/qt-6/qtqml-cppintegration-exposecppattributes.html#exposing-properties).
- `#[qinvokable]` - Expose a function on the QObject to QML and C++ as a [`Q_INVOKABLE`](https://doc.qt.io/qt-6/qtqml-cppintegration-exposecppattributes.html#exposing-methods-including-qt-slots).
- `#[cxx_qt::qsignals]` - Define the [Signals](https://doc.qt.io/qt-6/signalsandslots.html#signals) of a QObject T.
- `#[qsignal]` - Define the [Signals](https://doc.qt.io/qt-6/signalsandslots.html#signals) of a QObject T.

CXX-Qt will then expand this Rust module into two separate parts:
- C++ files that define a QObject subclass for each `#[cxx_qt::qobject]` marked struct.
Expand Down
2 changes: 1 addition & 1 deletion book/src/qobject/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ For a simpler introduction, take a look at our [Getting Started guide](../gettin
QObject Features and Parts:
* [`#[cxx_qt::bridge]` - The macro around the module](./bridge-macro.md)
* [`#[cxx_qt::qobject]` - Marking a Rust struct as a QObject](./qobject_struct.md)
* [`#[cxx_qt::qsignals]` - A macro for defining signals](./signals.md)
* [`#[qsignal]` - A macro for defining signals](./signals.md)
* [`qobject:T` - The generated QObject](./generated-qobject.md)
* [`CxxQtThread` - Queueing closures onto the Qt event loop](./cxxqtthread.md)

Expand Down
2 changes: 1 addition & 1 deletion book/src/qobject/qobject_struct.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ The macro does multiple other things for you though:
- Expose the generated QObject subclass to Rust as [`qobject::MyObject`](./generated-qobject.md)
- Generate getters/setters for all fields.
- Generate `Q_PROPERTY`s for all fields that are marked as `#[qproperty]`.
- Generate signals if paired with a [`#[cxx_qt::qsignals]` macro](./signals.md).
- Generate signals if paired with a [`#[qsignal]` macro](./signals.md).

## Exposing to QML
`#[cxx_qt::qobject]` supports registering the Qt object as a QML type directly at build time.
Expand Down
6 changes: 3 additions & 3 deletions book/src/qobject/signals.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ SPDX-FileContributor: Andrew Hayzen <[email protected]>
SPDX-License-Identifier: MIT OR Apache-2.0
-->

# Signals enum
# Signals

The `cxx_qt::qsignals` attribute is used on an `extern "C++"` block to define [signals](https://doc.qt.io/qt-6/signalsandslots.html) for the a QObject.
The `qsignal` attribute is used in an `extern "RustQt"` block to define [signals](https://doc.qt.io/qt-6/signalsandslots.html) for the a QObject.

```rust,ignore,noplayground
{{#include ../../../examples/qml_features/rust/src/signals.rs:book_signals_block}}
Expand Down Expand Up @@ -57,7 +57,7 @@ In this case, it is no longer possible to disconnect later.

## Emitting a signal

Call the function signature defined in the `extern "C++` block to emit the signal.
Call the function signature defined in the `extern "RustQt"` block to emit the signal.

Note that these are defined on the generated QObject [`qobject::T`](./generated-qobject.md), so can be called from any mutable `#[qinvokable]`.

Expand Down
148 changes: 71 additions & 77 deletions crates/cxx-qt-gen/src/parser/cxxqtdata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,14 @@
// SPDX-License-Identifier: MIT OR Apache-2.0

use crate::syntax::foreignmod::foreign_mod_to_foreign_item_types;
use crate::syntax::safety::Safety;
use crate::syntax::{attribute::attribute_find_path, path::path_to_single_ident};
use crate::{
parser::{
inherit::{InheritMethods, ParsedInheritedMethod},
qobject::ParsedQObject,
signals::{ParsedSignal, SignalMethods},
},
parser::{inherit::ParsedInheritedMethod, qobject::ParsedQObject, signals::ParsedSignal},
syntax::expr::expr_to_string,
};
use proc_macro2::TokenStream;
use quote::ToTokens;
use std::collections::BTreeMap;
use syn::ForeignItem;
use syn::{
spanned::Spanned, Attribute, Error, Ident, Item, ItemForeignMod, ItemImpl, Result, Type,
TypePath,
Expand Down Expand Up @@ -184,75 +180,60 @@ impl ParsedCxxQtData {
}
}

fn parse_inherit_mod(&mut self, tokens: TokenStream) -> Result<()> {
let inherited: InheritMethods = syn::parse2(tokens)?;

self.add_inherited_methods(inherited)
}

fn add_inherited_methods(&mut self, inherited: InheritMethods) -> Result<()> {
for method in inherited.base_functions.into_iter() {
let parsed_inherited_method = ParsedInheritedMethod::parse(method, inherited.safety)?;

if let Some(ref mut qobject) = self
.qobjects
.get_mut(&parsed_inherited_method.qobject_ident)
{
qobject.inherited_methods.push(parsed_inherited_method);
} else {
return Err(Error::new_spanned(
parsed_inherited_method.qobject_ident,
"No QObject with this name found.",
));
fn parse_foreign_mod(&mut self, foreign_mod: ItemForeignMod) -> Result<Option<Item>> {
if let Some(lit_str) = &foreign_mod.abi.name {
match lit_str.value().as_str() {
"RustQt" => {
self.parse_foreign_mod_rust_qt(foreign_mod)?;
return Ok(None);
}
// TODO: look for "C++Qt" later
_others => {}
}
}
Ok(())
}

fn parse_signals_mod(&mut self, tokens: TokenStream) -> Result<()> {
let signals: SignalMethods = syn::parse2(tokens)?;

self.add_signal_methods(signals)
}

fn add_signal_methods(&mut self, signals: SignalMethods) -> Result<()> {
for method in signals.base_functions.into_iter() {
let parsed_signal_method = ParsedSignal::parse(method, signals.safety)?;

if let Some(ref mut qobject) =
self.qobjects.get_mut(&parsed_signal_method.qobject_ident)
{
qobject.signals.push(parsed_signal_method);
} else {
return Err(Error::new_spanned(
parsed_signal_method.qobject_ident,
"No QObject with this name found.",
));
}
}
Ok(())
Ok(Some(Item::ForeignMod(foreign_mod)))
}

fn parse_foreign_mod(&mut self, mut foreign_mod: ItemForeignMod) -> Result<Option<Item>> {
// Check if the foreign mod has cxx_qt::inherit on it
if let Some(index) = attribute_find_path(&foreign_mod.attrs, &["cxx_qt", "inherit"]) {
// Remove the inherit attribute
foreign_mod.attrs.remove(index);

self.parse_inherit_mod(foreign_mod.into_token_stream())?;
return Ok(None);
}
fn parse_foreign_mod_rust_qt(&mut self, mut foreign_mod: ItemForeignMod) -> Result<()> {
let safe_call = if foreign_mod.unsafety.is_some() {
Safety::Safe
} else {
Safety::Unsafe
};

// Check if the foreign mod has cxx_qt::qsignals on it
if let Some(index) = attribute_find_path(&foreign_mod.attrs, &["cxx_qt", "qsignals"]) {
// Remove the signals attribute
foreign_mod.attrs.remove(index);
for item in foreign_mod.items.drain(..) {
if let ForeignItem::Fn(mut foreign_fn) = item {
// Test if the function is a signal
if let Some(index) = attribute_find_path(&foreign_fn.attrs, &["qsignal"]) {
// Remove the signals attribute
foreign_fn.attrs.remove(index);

let parsed_signal_method = ParsedSignal::parse(foreign_fn, safe_call)?;

self.with_qobject(&parsed_signal_method.qobject_ident)?
.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 let Some(index) = attribute_find_path(&foreign_fn.attrs, &["inherit"]) {
// Remove the inherit attribute
foreign_fn.attrs.remove(index);

let parsed_inherited_method =
ParsedInheritedMethod::parse(foreign_fn, safe_call)?;

self.with_qobject(&parsed_inherited_method.qobject_ident)?
.inherited_methods
.push(parsed_inherited_method);
}

self.parse_signals_mod(foreign_mod.into_token_stream())?;
return Ok(None);
// TODO: test for qinvokable later
}
}

Ok(Some(Item::ForeignMod(foreign_mod)))
Ok(())
}

/// Parse a [syn::ItemImpl] into the qobjects if it's a CXX-Qt implementation
Expand Down Expand Up @@ -287,6 +268,17 @@ impl ParsedCxxQtData {

Ok(Some(Item::Impl(imp)))
}

fn with_qobject(&mut self, qobject_ident: &Ident) -> Result<&mut ParsedQObject> {
if let Some(qobject) = self.qobjects.get_mut(qobject_ident) {
Ok(qobject)
} else {
Err(Error::new_spanned(
qobject_ident,
"No QObject with this name found.",
))
}
}
}

#[cfg(test)]
Expand Down Expand Up @@ -732,17 +724,18 @@ mod tests {
let mut cxxqtdata = create_parsed_cxx_qt_data();

let unsafe_block: Item = parse_quote! {
#[cxx_qt::inherit]
unsafe extern "C++" {
unsafe extern "RustQt" {
#[inherit]
fn test(self: &qobject::MyObject);

#[inherit]
fn with_args(self: &qobject::MyObject, arg: i32);
}
};
let safe_block: Item = parse_quote! {
#[cxx_qt::inherit]
extern "C++" {
extern "RustQt" {
#[cxx_name="withRename"]
#[inherit]
unsafe fn with_rename(self: Pin<&mut qobject::MyObject>, arg: i32);
}
};
Expand Down Expand Up @@ -771,12 +764,13 @@ mod tests {
fn test_parse_qsignals_safe() {
let mut cxxqtdata = create_parsed_cxx_qt_data();
let block: Item = parse_quote! {
#[cxx_qt::qsignals]
unsafe extern "C++" {
unsafe extern "RustQt" {
#[qsignal]
fn ready(self: Pin<&mut qobject::MyObject>);

#[cxx_name="cppDataChanged"]
#[inherit]
#[qsignal]
fn data_changed(self: Pin<&mut qobject::MyObject>, data: i32);
}
};
Expand Down Expand Up @@ -815,8 +809,8 @@ mod tests {
fn test_parse_qsignals_unknown_obj() {
let mut cxxqtdata = create_parsed_cxx_qt_data();
let block: Item = parse_quote! {
#[cxx_qt::qsignals]
unsafe extern "C++" {
unsafe extern "RustQt" {
#[qsignal]
fn ready(self: Pin<&mut qobject::UnknownObj>);
}
};
Expand All @@ -827,8 +821,8 @@ mod tests {
fn test_parse_qsignals_unsafe() {
let mut cxxqtdata = create_parsed_cxx_qt_data();
let block: Item = parse_quote! {
#[cxx_qt::qsignals]
extern "C++" {
extern "RustQt" {
#[qsignal]
unsafe fn unsafe_signal(self: Pin<&mut qobject::MyObject>, arg: *mut T);
}
};
Expand Down
Loading