Skip to content

Commit e81283c

Browse files
cxx-qt: Add a QtObject trait
This trait is automatically implemented on any `qobject::T`. It currently exposes the inner Rust type for a given QObject. First step of implementing KDAB#550 This also requires us to split our macros into a cxx-qt-macro crate, as otherwise only exporting proc-macros is allowed. We could now potentially move CxxQtThread into the cxx-qt crate now to remove the dependency on cxx-qt-lib.
1 parent d83cb40 commit e81283c

File tree

11 files changed

+254
-184
lines changed

11 files changed

+254
-184
lines changed

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ version = "0.5.3"
3333
# we publish, otherwise crates.io complains as it doesn't know the version.
3434
[workspace.dependencies]
3535
cxx-qt = { path = "crates/cxx-qt" }
36+
cxx-qt-macro = { path = "crates/cxx-qt-macro" }
3637
cxx-qt-build = { path = "crates/cxx-qt-build" }
3738
cxx-qt-gen = { path = "crates/cxx-qt-gen", version = "0.5.3" }
3839
cxx-qt-lib = { path = "crates/cxx-qt-lib" }

crates/cxx-qt-gen/src/generator/rust/qobject.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -179,12 +179,19 @@ fn generate_qobject_definitions(
179179
}
180180
},
181181
],
182-
implementation: vec![],
182+
implementation: vec![quote! {
183+
impl cxx_qt::QtObject for #cpp_class_name_rust {
184+
type Rust = #rust_struct_name_rust;
185+
}
186+
}],
183187
};
184188

185189
generated
186190
.cxx_mod_contents
187191
.append(&mut fragment.cxx_bridge_as_items()?);
192+
generated
193+
.cxx_qt_mod_contents
194+
.append(&mut fragment.implementation_as_items()?);
188195

189196
Ok(generated)
190197
}

crates/cxx-qt-gen/test_outputs/inheritance.rs

+3
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,9 @@ mod cxx_qt_inheritance {
117117
use std::pin::Pin;
118118
#[doc(hidden)]
119119
type UniquePtr<T> = cxx::UniquePtr<T>;
120+
impl cxx_qt::QtObject for MyObjectQt {
121+
type Rust = MyObject;
122+
}
120123
#[derive(Default)]
121124
pub struct MyObject {
122125
data: Vec<i32>,

crates/cxx-qt-gen/test_outputs/invokables.rs

+3
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,9 @@ mod cxx_qt_ffi {
140140
println!("QML or C++ can't call this :)");
141141
}
142142
}
143+
impl cxx_qt::QtObject for MyObjectQt {
144+
type Rust = MyObject;
145+
}
143146
#[derive(Default)]
144147
pub struct MyObject;
145148
impl MyObject {

crates/cxx-qt-gen/test_outputs/passthrough_and_naming.rs

+6
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,9 @@ mod cxx_qt_ffi {
284284
"Hello".to_owned()
285285
}
286286
}
287+
impl cxx_qt::QtObject for MyObjectQt {
288+
type Rust = MyObject;
289+
}
287290
pub struct MyObject {
288291
property_name: i32,
289292
}
@@ -415,6 +418,9 @@ mod cxx_qt_ffi {
415418
Self { property_name: 32 }
416419
}
417420
}
421+
impl cxx_qt::QtObject for SecondObjectQt {
422+
type Rust = SecondObject;
423+
}
418424
pub struct SecondObject {
419425
property_name: i32,
420426
}

crates/cxx-qt-gen/test_outputs/properties.rs

+3
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,9 @@ mod cxx_qt_ffi {
143143
use std::pin::Pin;
144144
#[doc(hidden)]
145145
type UniquePtr<T> = cxx::UniquePtr<T>;
146+
impl cxx_qt::QtObject for MyObjectQt {
147+
type Rust = MyObject;
148+
}
146149
#[derive(Default)]
147150
pub struct MyObject {
148151
primitive: i32,

crates/cxx-qt-gen/test_outputs/signals.rs

+3
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,9 @@ mod cxx_qt_ffi {
168168
use std::pin::Pin;
169169
#[doc(hidden)]
170170
type UniquePtr<T> = cxx::UniquePtr<T>;
171+
impl cxx_qt::QtObject for MyObjectQt {
172+
type Rust = MyObject;
173+
}
171174
#[derive(Default)]
172175
pub struct MyObject;
173176
impl MyObject {

crates/cxx-qt-macro/Cargo.toml

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
2+
# SPDX-FileContributor: Andrew Hayzen <[email protected]>
3+
# SPDX-FileContributor: Gerhard de Clercq <[email protected]>
4+
# SPDX-FileContributor: Leon Matthes <[email protected]>
5+
#
6+
# SPDX-License-Identifier: MIT OR Apache-2.0
7+
[package]
8+
name = "cxx-qt-macro"
9+
version.workspace = true
10+
authors = ["Andrew Hayzen <[email protected]>","Leon Matthes <[email protected]>", "Gerhard de Clercq <[email protected]>"]
11+
edition.workspace = true
12+
license.workspace = true
13+
description = "A macro for Qt/C++ interop in Rust"
14+
homepage = "https://kdab.github.io/cxx-qt/book/"
15+
readme = "README.md"
16+
keywords = ["cxx", "ffi", "QML", "Qt"]
17+
categories = ["api-bindings", "gui"]
18+
19+
[lib]
20+
proc-macro = true
21+
22+
[dependencies]
23+
cxx-qt-gen.workspace = true
24+
proc-macro2.workspace = true
25+
syn.workspace = true
26+
27+
[dev-dependencies]
28+
cxx.workspace = true
29+
cxx-qt-lib.workspace = true

crates/cxx-qt-macro/src/lib.rs

+181
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
// SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
2+
// SPDX-FileContributor: Andrew Hayzen <[email protected]>
3+
// SPDX-FileContributor: Gerhard de Clercq <[email protected]>
4+
//
5+
// SPDX-License-Identifier: MIT OR Apache-2.0
6+
7+
#![deny(missing_docs)]
8+
//! The cxx-qt-macro crate provides the procedural attribute macros which are used with cxx-qt.
9+
10+
use proc_macro::TokenStream;
11+
use syn::{parse_macro_input, ItemMod};
12+
13+
use cxx_qt_gen::{write_rust, GeneratedRustBlocks, Parser};
14+
15+
/// A procedural macro which generates a QObject for a struct inside a module.
16+
///
17+
/// # Example
18+
///
19+
/// ```rust
20+
/// #[cxx_qt::bridge(namespace = "cxx_qt::my_object")]
21+
/// mod my_object {
22+
/// #[cxx_qt::qobject]
23+
/// #[derive(Default)]
24+
/// # // Note that we can't use properties as this confuses the linker on Windows
25+
/// pub struct MyObject;
26+
///
27+
/// impl qobject::MyObject {
28+
/// #[qinvokable]
29+
/// fn invokable(&self, a: i32, b: i32) -> i32 {
30+
/// a + b
31+
/// }
32+
/// }
33+
/// }
34+
///
35+
/// # // Note that we need a fake main for doc tests to build
36+
/// # fn main() {}
37+
/// ```
38+
#[proc_macro_attribute]
39+
pub fn bridge(args: TokenStream, input: TokenStream) -> TokenStream {
40+
// Parse the TokenStream of a macro
41+
// this triggers a compile failure if the tokens fail to parse.
42+
let mut module = parse_macro_input!(input as ItemMod);
43+
44+
// Macros do not typically need to do anything with their own attribute name,
45+
// so rustc does not include that in the `args` or `input` TokenStreams.
46+
//
47+
// However, other code paths that use the parser do not enter from a macro invocation,
48+
// so they rely on parsing the `cxx_qt::bridge` attribute to identify where to start parsing.
49+
//
50+
// To keep the inputs to the parser consistent for all code paths,
51+
// add the attribute to the module before giving it to the parser.
52+
let args_input = format!("#[cxx_qt::bridge({args})] mod dummy;");
53+
let attrs = syn::parse_str::<ItemMod>(&args_input).unwrap().attrs;
54+
module.attrs = attrs.into_iter().chain(module.attrs.into_iter()).collect();
55+
56+
// Extract and generate the rust code
57+
extract_and_generate(module)
58+
}
59+
60+
/// A macro which describes that an enum defines the signals for a QObject.
61+
///
62+
/// It should not be used by itself and instead should be used inside a cxx_qt::bridge definition.
63+
///
64+
/// # Example
65+
///
66+
/// ```rust
67+
/// #[cxx_qt::bridge]
68+
/// mod my_object {
69+
/// #[cxx_qt::qobject]
70+
/// #[derive(Default)]
71+
/// # // Note that we can't use properties as this confuses the linker on Windows
72+
/// pub struct MyObject;
73+
///
74+
/// #[cxx_qt::qsignals(MyObject)]
75+
/// pub enum MySignals {
76+
/// Ready,
77+
/// }
78+
/// }
79+
///
80+
/// # // Note that we need a fake main for doc tests to build
81+
/// # fn main() {}
82+
/// ```
83+
#[proc_macro_attribute]
84+
pub fn qsignals(_args: TokenStream, _input: TokenStream) -> TokenStream {
85+
unreachable!("cxx_qt::qsignals should not be used as a macro by itself. Instead it should be used within a cxx_qt::bridge definition")
86+
}
87+
88+
/// A macro which describes that a struct should be made into a QObject.
89+
///
90+
/// It should not be used by itself and instead should be used inside a cxx_qt::bridge definition.
91+
///
92+
/// # Example
93+
///
94+
/// ```rust
95+
/// #[cxx_qt::bridge]
96+
/// mod my_object {
97+
/// #[cxx_qt::qobject]
98+
/// #[derive(Default)]
99+
/// # // Note that we can't use properties as this confuses the linker on Windows
100+
/// pub struct MyObject;
101+
/// }
102+
///
103+
/// # // Note that we need a fake main for doc tests to build
104+
/// # fn main() {}
105+
/// ```
106+
///
107+
/// You can also specify a custom base class by using `#[cxx_qt::qobject(base = "QStringListModel")]`, you must then use CXX to add any includes needed.
108+
///
109+
/// # Example
110+
///
111+
/// ```rust
112+
/// #[cxx_qt::bridge]
113+
/// mod my_object {
114+
/// #[cxx_qt::qobject(base = "QStringListModel")]
115+
/// #[derive(Default)]
116+
/// # // Note that we can't use properties as this confuses the linker on Windows
117+
/// pub struct MyModel;
118+
///
119+
/// unsafe extern "C++" {
120+
/// include!(<QtCore/QStringListModel>);
121+
/// }
122+
/// }
123+
///
124+
/// # // Note that we need a fake main for doc tests to build
125+
/// # fn main() {}
126+
/// ```
127+
#[proc_macro_attribute]
128+
pub fn qobject(_args: TokenStream, _input: TokenStream) -> TokenStream {
129+
unreachable!("cxx_qt::qobject should not be used as a macro by itself. Instead it should be used within a cxx_qt::bridge definition")
130+
}
131+
132+
/// A macro which allows you to access base class methods from within Rust.
133+
///
134+
/// It should not be used by itself and instead should be used inside a cxx_qt::bridge definition.
135+
/// Furthermore, the macro must be placed within the `impl` block of a `qobject::T`.
136+
/// See [the book page](https://kdab.github.io/cxx-qt/book/concepts/inheritance.html) for more
137+
/// details.
138+
///
139+
/// # Example
140+
/// ``` rust
141+
/// #[cxx_qt::bridge]
142+
/// mod my_object {
143+
/// extern "C++" {
144+
/// include!("cxx-qt-lib/qmodelindex.h");
145+
/// type QModelIndex = cxx_qt_lib::QModelIndex;
146+
/// }
147+
///
148+
/// #[cxx_qt::qobject(base="QAbstractListModel")]
149+
/// #[derive(Default)]
150+
/// # // Note that we can't use properties as this confuses the linker on Windows
151+
/// pub struct MyObject;
152+
///
153+
/// #[cxx_qt::inherit]
154+
/// extern "C++" {
155+
/// // Unsafe to call
156+
/// unsafe fn begin_insert_rows(self: Pin<&mut qobject::MyObject>, parent: &QModelIndex, first: i32, last: i32);
157+
/// }
158+
///
159+
/// #[cxx_qt::inherit]
160+
/// unsafe extern "C++" {
161+
/// // Safe to call - you are responsible to ensure this is true!
162+
/// fn end_insert_rows(self: Pin<&mut qobject::MyObject>);
163+
/// }
164+
/// }
165+
///
166+
/// # // Note that we need a fake main for doc tests to build
167+
/// # fn main() {}
168+
/// ```
169+
#[proc_macro_attribute]
170+
pub fn inherit(_args: TokenStream, _input: TokenStream) -> TokenStream {
171+
unreachable!("cxx_qt::inherit should not be used as a macro by itself. Instead it should be used within a cxx_qt::bridge definition")
172+
}
173+
174+
// Take the module and C++ namespace and generate the rust code
175+
fn extract_and_generate(module: ItemMod) -> TokenStream {
176+
Parser::from(module)
177+
.and_then(|parser| GeneratedRustBlocks::from(&parser))
178+
.map(|generated_rust| write_rust(&generated_rust))
179+
.unwrap_or_else(|err| err.to_compile_error())
180+
.into()
181+
}

crates/cxx-qt/Cargo.toml

+2-10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
22
# SPDX-FileContributor: Andrew Hayzen <[email protected]>
33
# SPDX-FileContributor: Gerhard de Clercq <[email protected]>
4+
# SPDX-FileContributor: Leon Matthes <[email protected]>
45
#
56
# SPDX-License-Identifier: MIT OR Apache-2.0
67
[package]
@@ -16,14 +17,5 @@ readme = "README.md"
1617
keywords = ["cxx", "ffi", "QML", "Qt"]
1718
categories = ["api-bindings", "gui"]
1819

19-
[lib]
20-
proc-macro = true
21-
2220
[dependencies]
23-
cxx-qt-gen.workspace = true
24-
proc-macro2.workspace = true
25-
syn.workspace = true
26-
27-
[dev-dependencies]
28-
cxx.workspace = true
29-
cxx-qt-lib.workspace = true
21+
cxx-qt-macro.workspace = true

0 commit comments

Comments
 (0)