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

Add pure virtuals #1189

Merged
merged 4 commits into from
Feb 21, 2025
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
- CXX-Qt-build: Improved compile time and propagation of initializers between crates
- CXX-Qt-build: Multi-crate projects are now possible with Cargo and CMake (see `examples/qml_multi_crates`)
- CXX-Qt-build: Allow forcing initialization of crates/QML modules (`cxx_qt::init_crate!`/`cxx_qt::init_qml_module!`)
- Add pure virtual function specified through the `#[cxx_pure]` attribute

### Fixed

Expand Down
11 changes: 6 additions & 5 deletions book/src/bridge/extern_rustqt.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,11 +226,12 @@ For documentation see the [inheritance](../concepts/inheritance.md) page.

Generated methods can have C++ specifiers necessary to implement inheritance.

| C++ keyword | CXX-Qt attribute |
|-------------|-------------------------------|
| `override` | `#[cxx_override]` |
| `virtual` | `#[cxx_virtual]` |
| `final` | `#[cxx_final]` |
| C++ keyword | CXX-Qt attribute |
|--------------|-------------------|
| `override` | `#[cxx_override]` |
| `virtual` | `#[cxx_virtual]` |
| `final` | `#[cxx_final]` |
| `= 0` (pure) | `#[cxx_pure]` |

These are specified as an attribute on the method signature.

Expand Down
4 changes: 3 additions & 1 deletion crates/cxx-qt-gen/src/generator/cpp/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ pub fn generate_cpp_methods(
let mut is_final = "";
let mut is_override = "";
let mut is_virtual = "";
let mut is_pure = "";

// Set specifiers into string values
invokable
Expand All @@ -54,6 +55,7 @@ pub fn generate_cpp_methods(
ParsedQInvokableSpecifiers::Final => is_final = " final",
ParsedQInvokableSpecifiers::Override => is_override = " override",
ParsedQInvokableSpecifiers::Virtual => is_virtual = "virtual ",
ParsedQInvokableSpecifiers::Pure => is_pure = " = 0",
});

let is_qinvokable = invokable
Expand All @@ -74,7 +76,7 @@ pub fn generate_cpp_methods(
// CXX generates the source and we just need the matching header.
let has_noexcept = syn_return_type_to_cpp_except(&invokable.method.sig.output);
generated.methods.push(CppFragment::Header(format!(
"{is_qinvokable}{is_virtual}{return_cxx_ty} {ident}({parameter_types}){is_const} {has_noexcept}{is_final}{is_override};",
"{is_qinvokable}{is_virtual}{return_cxx_ty} {ident}({parameter_types}){is_const} {has_noexcept}{is_final}{is_override}{is_pure};",
ident = invokable.name.cxx_unqualified(),
)));
}
Expand Down
8 changes: 7 additions & 1 deletion crates/cxx-qt-gen/src/generator/rust/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,16 @@ pub fn generate_rust_methods(
let cfgs = &invokable.cfgs;
let cxx_namespace = qobject_names.namespace_tokens();

let (block_type, block_safety) = if invokable.is_pure {
("C++", Some(quote! { unsafe }))
} else {
("Rust", None)
};

GeneratedRustFragment::from_cxx_item(parse_quote_spanned! {
invokable.method.span() =>
// Note: extern "Rust" block does not need to be unsafe
extern "Rust" {
#block_safety extern #block_type {
// Note that we are exposing a Rust method on the C++ type to C++
//
// CXX ends up generating the source, then we generate the matching header.
Expand Down
10 changes: 9 additions & 1 deletion crates/cxx-qt-gen/src/parser/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub enum ParsedQInvokableSpecifiers {
Final,
Override,
Virtual,
Pure,
}

impl ParsedQInvokableSpecifiers {
Expand All @@ -26,6 +27,7 @@ impl ParsedQInvokableSpecifiers {
ParsedQInvokableSpecifiers::Final => "cxx_final",
ParsedQInvokableSpecifiers::Override => "cxx_override",
ParsedQInvokableSpecifiers::Virtual => "cxx_virtual",
ParsedQInvokableSpecifiers::Pure => "cxx_pure",
}
}

Expand All @@ -35,6 +37,7 @@ impl ParsedQInvokableSpecifiers {
ParsedQInvokableSpecifiers::Final,
ParsedQInvokableSpecifiers::Override,
ParsedQInvokableSpecifiers::Virtual,
ParsedQInvokableSpecifiers::Pure,
] {
if attrs.contains_key(specifier.as_str()) {
output.insert(specifier);
Expand All @@ -52,20 +55,23 @@ pub struct ParsedMethod {
pub specifiers: HashSet<ParsedQInvokableSpecifiers>,
/// Whether the method is qinvokable
pub is_qinvokable: bool,
/// Whether the method is a pure virtual method
pub is_pure: bool,
// No docs field since the docs should be on the method implementation outside the bridge
// This means any docs on the bridge declaration would be ignored
/// Cfgs for the method
pub cfgs: Vec<Attribute>,
}

impl ParsedMethod {
const ALLOWED_ATTRS: [&'static str; 8] = [
const ALLOWED_ATTRS: [&'static str; 9] = [
"cxx_name",
"rust_name",
"qinvokable",
"cxx_final",
"cxx_override",
"cxx_virtual",
"cxx_pure",
"doc",
"cfg",
];
Expand Down Expand Up @@ -113,12 +119,14 @@ impl ParsedMethod {

// Determine if the method is invokable
let is_qinvokable = attrs.contains_key("qinvokable");
let is_pure = attrs.contains_key("cxx_pure");
let specifiers = ParsedQInvokableSpecifiers::from_attrs(attrs);

Ok(Self {
method_fields: fields,
specifiers,
is_qinvokable,
is_pure,
cfgs,
})
}
Expand Down
5 changes: 5 additions & 0 deletions crates/cxx-qt-gen/test_inputs/invokables.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ mod ffi {
#[cxx_virtual]
fn invokable_virtual(self: &MyObject);

#[qinvokable]
#[cxx_virtual]
#[cxx_pure]
fn invokable_pure_virtual(self: &MyObject);

#[qinvokable]
fn invokable_result_tuple(self: &MyObject) -> Result<()>;

Expand Down
1 change: 1 addition & 0 deletions crates/cxx-qt-gen/test_outputs/invokables.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class MyObject
Q_INVOKABLE void invokable_final() const noexcept final;
Q_INVOKABLE void invokable_override() const noexcept override;
Q_INVOKABLE virtual void invokable_virtual() const noexcept;
Q_INVOKABLE virtual void invokable_pure_virtual() const noexcept = 0;
Q_INVOKABLE void invokable_result_tuple() const;
Q_INVOKABLE ::rust::String invokable_result_type() const;
explicit MyObject(::std::int32_t arg0, QString const& arg1);
Expand Down
6 changes: 6 additions & 0 deletions crates/cxx-qt-gen/test_outputs/invokables.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,12 @@ mod ffi {
#[doc(hidden)]
fn invokable_virtual(self: &MyObject);
}
unsafe extern "C++" {
#[cxx_name = "invokable_pure_virtual"]
#[namespace = "cxx_qt::my_object"]
#[doc(hidden)]
fn invokable_pure_virtual(self: &MyObject);
}
extern "Rust" {
#[cxx_name = "invokable_result_tuple"]
#[namespace = "cxx_qt::my_object"]
Expand Down
6 changes: 6 additions & 0 deletions examples/qml_features/qml/pages/CustomBaseClassPage.qml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ Page {
RowLayout {
anchors.fill: parent

ToolButton {
text: qsTr("Log")

onClicked: root.activeModel.log()
}

ToolButton {
text: qsTr("Add Row")

Expand Down
28 changes: 28 additions & 0 deletions examples/qml_features/rust/src/custom_base_class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ pub mod qobject {
extern "RustQt" {
#[qobject]
#[base = QAbstractListModel]
type AbstractBaseClass = super::AbstractBaseClassRust;

#[qobject]
#[base = AbstractBaseClass]
#[qml_element]
#[qproperty(State, state)]
type CustomBaseClass = super::CustomBaseClassRust;
Expand All @@ -86,6 +90,17 @@ pub mod qobject {
// ANCHOR_END: book_qsignals_inherit

unsafe extern "RustQt" {
/// Log the state of the abstract class
#[qinvokable]
#[cxx_virtual]
#[cxx_pure]
fn log(self: &AbstractBaseClass);

/// Override to Log the state of the custom base class
#[qinvokable]
#[cxx_override]
fn log(self: &CustomBaseClass);

/// Add a new row to the QAbstractListModel on the current thread
#[qinvokable]
fn add(self: Pin<&mut CustomBaseClass>);
Expand Down Expand Up @@ -257,6 +272,7 @@ pub mod qobject {
}
}

use crate::custom_base_class::qobject::CustomBaseClass;
use core::pin::Pin;
use cxx_qt::{CxxQtType, Threading};
use cxx_qt_lib::{QByteArray, QHash, QHashPair_i32_QByteArray, QModelIndex, QVariant, QVector};
Expand All @@ -269,6 +285,10 @@ impl Default for qobject::State {

/// A struct which inherits from QAbstractListModel
#[derive(Default)]
pub struct AbstractBaseClassRust {}

/// A struct which inherits from our custom abstract parent
#[derive(Default)]
pub struct CustomBaseClassRust {
state: qobject::State,
pending_adds: i32,
Expand All @@ -278,6 +298,14 @@ pub struct CustomBaseClassRust {
}

impl qobject::CustomBaseClass {
/// Virtual method for logging type
pub fn log(self: &CustomBaseClass) {
println!(
"state: {}\npending adds: {}\nid: {}\nvector: {:?}\n",
self.state.repr, self.pending_adds, self.id, self.vector
);
}

/// Add a new row to the QAbstractListModel on the current thread
pub fn add(self: Pin<&mut Self>) {
self.add_cpp_context();
Expand Down
Loading