Skip to content

Commit

Permalink
cxx-qt-gen: use extern "RustQt" block for inherit and signals
Browse files Browse the repository at this point in the history
Closes #557
  • Loading branch information
ahayzen-kdab committed Jun 9, 2023
1 parent c663fbf commit bb49c51
Show file tree
Hide file tree
Showing 23 changed files with 141 additions and 373 deletions.
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 <andrew.hayzen@kdab.com>
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

0 comments on commit bb49c51

Please sign in to comment.