-
Notifications
You must be signed in to change notification settings - Fork 77
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement cxx_qt::inherit! to allow inheriting methods from super cla…
…ss (#363) * Parse cxx_qt::inherit! into ParsedInheritedMethod * cxx_qt::inherit! - add Rust generation * CombinedIdent: Revert From<Ident> implementation Use associated function `from_rust_function` instead. * cxx_qt::inherit: add rust generation * Allow cxx_qt::inherit on qobjects without base. Base class defaults to `QObject`.
- Loading branch information
1 parent
bf94b05
commit 0f8d04c
Showing
39 changed files
with
1,753 additions
and
190 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
<!-- | ||
SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]> | ||
SPDX-FileContributor: Andrew Hayzen <[email protected]> | ||
SPDX-FileContributor: Leon Matthes <[email protected]> | ||
SPDX-License-Identifier: MIT OR Apache-2.0 | ||
--> | ||
|
@@ -21,3 +22,4 @@ SPDX-License-Identifier: MIT OR Apache-2.0 | |
|
||
* [Threading concept and safety](./threading.md) | ||
* [Nesting Rust objects](./nested_objects.md) | ||
* [Inheriting QObjects and overriding methods](./inheritance.md) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
<!-- | ||
SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]> | ||
SPDX-FileContributor: Leon Matthes <[email protected]> | ||
SPDX-License-Identifier: MIT OR Apache-2.0 | ||
--> | ||
|
||
# Inheritance | ||
|
||
Some Qt APIs require you to override certain methods from an abstract base class, for example [QAbstractItemModel](https://doc.qt.io/qt-6/qabstractitemmodel.html). | ||
|
||
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]`. | ||
|
||
```rust,ignore | ||
{{#include ../../../examples/qml_features/rust/src/custom_base_class.rs:book_inherit_qalm}} | ||
{{#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}} | ||
} | ||
``` | ||
[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]`. | ||
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. | ||
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. | ||
To explicitly declare the C++ method name, use the `#[cxx_name="myFunctionName"]` attribute. | ||
|
||
## Overriding base class methods | ||
|
||
CXX-Qt allows invokables to be generated with the C++ modifiers necessary to implement inheritance. | ||
This way methods can be overridden, declared as `virtual` or `final`. | ||
|
||
| C++ keyword | CXX-Qt attribute | | ||
|-------------|-------------------------------| | ||
| `override` | `#[qinvokable(cxx_override)]` | | ||
| `virtual` | `#[qinvokable(cxx_virtual)]` | | ||
| `final` | `#[qinvokable(cxx_final)]` | | ||
|
||
The below example overrides the [`data`](https://doc.qt.io/qt-6/qabstractitemmodel.html#data) method inherited from the QAbstractListModel. | ||
```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}} | ||
} | ||
``` | ||
[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. | ||
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: | ||
```rust,ignore | ||
{{#include ../../../examples/qml_features/rust/src/custom_base_class.rs:book_inherit_qalm}} | ||
{{#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}} | ||
} | ||
``` | ||
[Full example](https://github.com/KDAB/cxx-qt/blob/main/examples/qml_features/rust/src/custom_base_class.rs) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]> | ||
// SPDX-FileContributor: Leon Matthes <[email protected]> | ||
// SPDX-License-Identifier: MIT OR Apache-2.0 | ||
|
||
use indoc::formatdoc; | ||
|
||
use crate::{ | ||
generator::cpp::{fragment::CppFragment, qobject::GeneratedCppQObjectBlocks}, | ||
parser::{cxxqtdata::ParsedCxxMappings, inherit::ParsedInheritedMethod}, | ||
}; | ||
|
||
use syn::{Result, ReturnType}; | ||
|
||
use super::types::CppType; | ||
|
||
pub fn generate( | ||
inherited_methods: &[ParsedInheritedMethod], | ||
base_class: &Option<String>, | ||
cxx_mappings: &ParsedCxxMappings, | ||
) -> Result<GeneratedCppQObjectBlocks> { | ||
let mut result = GeneratedCppQObjectBlocks::default(); | ||
|
||
for method in inherited_methods { | ||
let return_type = if let ReturnType::Type(_, ty) = &method.method.sig.output { | ||
CppType::from(ty, &None, cxx_mappings)? | ||
.as_cxx_ty() | ||
.to_owned() | ||
} else { | ||
"void".to_owned() | ||
}; | ||
|
||
let base_class = base_class.as_deref().unwrap_or("QObject"); | ||
|
||
result.methods.push(CppFragment::Header(formatdoc! { | ||
r#" | ||
template <class... Args> | ||
{return_type} {wrapper_ident}(Args ...args){mutability} | ||
{{ | ||
return {base_class}::{func_ident}(args...); | ||
}}"#, | ||
mutability = if method.mutable { "" } else { " const" }, | ||
func_ident = method.ident.cpp, | ||
wrapper_ident = method.wrapper_ident(), | ||
return_type = return_type, | ||
base_class = base_class | ||
})); | ||
} | ||
|
||
Ok(result) | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use pretty_assertions::assert_str_eq; | ||
use syn::ForeignItemFn; | ||
|
||
use crate::{ | ||
parser::inherit::ParsedInheritedMethod, syntax::safety::Safety, tests::tokens_to_syn, | ||
}; | ||
|
||
use super::*; | ||
use quote::quote; | ||
|
||
fn generate_from_foreign( | ||
tokens: proc_macro2::TokenStream, | ||
base_class: Option<&str>, | ||
) -> Result<GeneratedCppQObjectBlocks> { | ||
let method: ForeignItemFn = tokens_to_syn(tokens); | ||
let inherited_methods = vec![ParsedInheritedMethod::parse(method, Safety::Safe).unwrap()]; | ||
let base_class = base_class.map(|s| s.to_owned()); | ||
generate( | ||
&inherited_methods, | ||
&base_class, | ||
&ParsedCxxMappings::default(), | ||
) | ||
} | ||
|
||
fn assert_generated_eq(expected: &str, generated: &GeneratedCppQObjectBlocks) { | ||
assert_eq!(generated.methods.len(), 1); | ||
if let CppFragment::Header(header) = &generated.methods[0] { | ||
assert_str_eq!(header, expected); | ||
} else { | ||
panic!("Expected header fragment"); | ||
} | ||
} | ||
|
||
#[test] | ||
fn test_immutable() { | ||
let generated = generate_from_foreign( | ||
quote! { | ||
fn test(self: &qobject::T, a: B, b: C); | ||
}, | ||
Some("TestBaseClass"), | ||
) | ||
.unwrap(); | ||
|
||
assert_generated_eq( | ||
indoc::indoc! {" | ||
template <class... Args> | ||
void testCxxQtInherit(Args ...args) const | ||
{ | ||
return TestBaseClass::test(args...); | ||
}" | ||
}, | ||
&generated, | ||
); | ||
} | ||
|
||
#[test] | ||
fn test_mutable() { | ||
let generated = generate_from_foreign( | ||
quote! { | ||
fn test(self: Pin<&mut qobject::T>); | ||
}, | ||
Some("TestBaseClass"), | ||
) | ||
.unwrap(); | ||
|
||
assert_generated_eq( | ||
indoc::indoc! {" | ||
template <class... Args> | ||
void testCxxQtInherit(Args ...args) | ||
{ | ||
return TestBaseClass::test(args...); | ||
}" | ||
}, | ||
&generated, | ||
); | ||
} | ||
|
||
#[test] | ||
fn test_default_base_class() { | ||
let generated = generate_from_foreign( | ||
quote! { | ||
fn test(self: &qobject::T); | ||
}, | ||
None, | ||
) | ||
.unwrap(); | ||
|
||
assert_generated_eq( | ||
indoc::indoc! {" | ||
template <class... Args> | ||
void testCxxQtInherit(Args ...args) const | ||
{ | ||
return QObject::test(args...); | ||
}" | ||
}, | ||
&generated, | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]> | ||
// SPDX-FileContributor: Leon Matthes <[email protected]> | ||
|
||
// SPDX-License-Identifier: MIT OR Apache-2.0 | ||
|
||
use convert_case::{Case, Casing}; | ||
use quote::format_ident; | ||
use syn::Ident; | ||
|
||
use super::CombinedIdent; | ||
|
||
impl CombinedIdent { | ||
/// Generate a CombinedIdent from a rust function name. | ||
/// C++ will use the CamelCase version of the function name. | ||
pub fn from_rust_function(ident: Ident) -> Self { | ||
Self { | ||
cpp: format_ident!("{}", ident.to_string().to_case(Case::Camel)), | ||
rust: ident, | ||
} | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn test_from_rust_function_camel_case_conversion() { | ||
let ident = format_ident!("test_function"); | ||
let combined = CombinedIdent::from_rust_function(ident.clone()); | ||
assert_eq!(combined.cpp, format_ident!("testFunction")); | ||
assert_eq!(combined.rust, ident); | ||
} | ||
|
||
#[test] | ||
fn test_from_rust_function_single_word() { | ||
let ident = format_ident!("test"); | ||
let combined = CombinedIdent::from_rust_function(ident.clone()); | ||
assert_eq!(combined.cpp, ident); | ||
assert_eq!(combined.rust, ident); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ | |
// SPDX-FileContributor: Andrew Hayzen <[email protected]> | ||
// | ||
// SPDX-License-Identifier: MIT OR Apache-2.0 | ||
pub mod functions; | ||
pub mod invokable; | ||
pub mod namespace; | ||
pub mod property; | ||
|
Oops, something went wrong.