Skip to content

Commit c4821da

Browse files
ahayzen-kdabBe-ing
authored andcommitted
cxx-qt-gen: support base class for the qobject macro
1 parent 09f4f14 commit c4821da

36 files changed

+162
-45
lines changed

book/src/qobject/macro.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,8 @@ Then when registering the type you use the type with your namespace as usual.
3131
{{#include ../../../examples/qml_with_threaded_logic/src/lib.rs:book_namespace_register}}
3232
```
3333

34-
Note: this might change in the future to allow for defining the base class or options when exporting to QML and could become namespaced to `#[cxx_qt::qobject(base = "QAbstractListModel")]` ( [https://github.com/KDAB/cxx-qt/issues/22](https://github.com/KDAB/cxx-qt/issues/22) ).
34+
You can also specify the base class by using `#[cxx_qt::qobject(base = "QStringListModel")]`. Note that you need to use CXX to include the header that the base class is declared in.
35+
36+
```rust,ignore,noplayground
37+
{{#include ../../../examples/qml_features/src/custom_base.rs:book_macro_code}}
38+
```

cxx-qt-gen/src/extract.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,8 @@ pub struct QObject {
191191
pub(crate) original_passthrough_decls: Vec<Item>,
192192
/// The Rust impl that has optionally been provided to handle updates
193193
pub(crate) handle_updates_impl: Option<ItemImpl>,
194+
/// The base class of the QObject
195+
pub(crate) base_class: Option<String>,
194196
}
195197

196198
/// Describe the error type from extract_qt_type and extract_type_ident
@@ -744,6 +746,7 @@ pub fn extract_qobject(module: &ItemMod) -> Result<QObject, TokenStream> {
744746
original_rust_struct,
745747
original_passthrough_decls,
746748
handle_updates_impl,
749+
base_class: qobject.base_class,
747750
})
748751
}
749752

cxx-qt-gen/src/gen_cpp.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -856,6 +856,10 @@ pub fn generate_qobject_cpp(obj: &QObject) -> Result<CppObject, TokenStream> {
856856
rust_ident: rust_struct_ident,
857857
namespace: obj.namespace.clone(),
858858
namespace_internals: namespace_internals.join("::"),
859+
base_class: obj
860+
.base_class
861+
.clone()
862+
.unwrap_or_else(|| "QObject".to_owned()),
859863
metaobjects,
860864
methods,
861865
slots,

cxx-qt-gen/src/gen_rs.rs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -369,13 +369,6 @@ pub fn generate_qobject_cxx(obj: &QObject) -> Result<ItemMod, TokenStream> {
369369
// Retrieve the passthrough items to CXX
370370
let cxx_items = &obj.cxx_items;
371371

372-
// For now we always include QObject, but when a base class can be specified this will
373-
// become None and require the include to be in the extern "C++" block
374-
//
375-
// Note that quote formats this with spaces but it is valid syntax for CXX
376-
// https://github.com/dtolnay/cxx/blob/1862c5dad56c3da71420c5dca6e80ab788bb193d/syntax/parse.rs#L1101
377-
let qt_include = quote! { <QtCore/QObject> };
378-
379372
// Add an include for the UpdateRequester if it is used
380373
let update_requester_include = if obj.handle_updates_impl.is_some() {
381374
quote! { include!("cxx-qt-lib/include/update_requester.h"); }
@@ -392,7 +385,6 @@ pub fn generate_qobject_cxx(obj: &QObject) -> Result<ItemMod, TokenStream> {
392385
unsafe extern "C++" {
393386
include!(#import_path);
394387
#update_requester_include
395-
include!(#qt_include);
396388

397389
#[cxx_name = #class_name_str]
398390
type #rust_class_name_cpp;

cxx-qt-gen/src/generator/cpp/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ pub struct GeneratedCppBlocks {
1919
pub namespace: String,
2020
/// Ident of the namespace for CXX-Qt internals of the QObject
2121
pub namespace_internals: String,
22+
/// Base class of the QObject
23+
pub base_class: String,
2224
/// List of Qt Meta Object items (eg Q_PROPERTY)
2325
pub metaobjects: Vec<String>,
2426
/// List of public methods for the QObject

cxx-qt-gen/src/parser/cxxqtdata.rs

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@
55

66
use crate::parser::{qobject::ParsedQObject, signals::ParsedSignalsEnum};
77
use crate::syntax::{
8-
attribute::{attribute_find_path, attribute_tokens_to_ident},
8+
attribute::{attribute_find_path, attribute_tokens_to_ident, attribute_tokens_to_map},
99
path::{path_angled_args_to_type_path, path_compare_str, path_to_single_ident},
1010
};
1111
use std::collections::HashMap;
1212
use syn::{
13-
spanned::Spanned, Error, Ident, Item, ItemEnum, ItemImpl, ItemStruct, Result, Type, TypePath,
13+
spanned::Spanned, Error, Ident, Item, ItemEnum, ItemImpl, ItemStruct, LitStr, Result, Type,
14+
TypePath,
1415
};
1516

1617
#[derive(Default)]
@@ -145,25 +146,31 @@ impl ParsedCxxQtData {
145146

146147
/// Parse a [syn::ItemStruct] into the qobjects if it's a CXX-Qt struct
147148
/// otherwise return as a [syn::Item] to pass through.
148-
fn parse_struct(&mut self, s: ItemStruct) -> Result<Option<Item>> {
149+
fn parse_struct(&mut self, item_struct: ItemStruct) -> Result<Option<Item>> {
149150
// If the attribute is #[cxx_qt::qobject] then this the struct defining a qobject
150-
if let Some(index) = attribute_find_path(&s.attrs, &["cxx_qt", "qobject"]) {
151-
// Remove the macro from the struct
152-
//
153-
// TODO: we need to read the base class from the macro
154-
let mut s = s.clone();
155-
s.attrs.remove(index);
156-
157-
if let Some(qobject) = self.qobjects.get_mut(&s.ident) {
151+
if let Some(index) = attribute_find_path(&item_struct.attrs, &["cxx_qt", "qobject"]) {
152+
if let Some(qobject) = self.qobjects.get_mut(&item_struct.ident) {
153+
// Find if there is any base class
154+
if let Some(base) =
155+
attribute_tokens_to_map::<Ident, LitStr>(&item_struct.attrs[index])?
156+
.get(&quote::format_ident!("base"))
157+
{
158+
qobject.base_class = Some(base.value());
159+
}
160+
161+
// Remove the macro from the struct
162+
let mut item_struct = item_struct.clone();
163+
item_struct.attrs.remove(index);
164+
158165
// Parse any properties in the struct
159166
// and remove the #[qproperty] attribute
160-
qobject.parse_struct_fields(&mut s.fields)?;
167+
qobject.parse_struct_fields(&mut item_struct.fields)?;
161168

162-
qobject.qobject_struct = Some(s);
169+
qobject.qobject_struct = Some(item_struct);
163170
return Ok(None);
164171
} else {
165172
return Err(Error::new(
166-
s.span(),
173+
item_struct.span(),
167174
"cxx_qt::qobject struct was not found by find_qobject_keys",
168175
));
169176
}
@@ -172,15 +179,15 @@ impl ParsedCxxQtData {
172179
// TODO: for now we assume that Data is related to the only struct in the qobjects
173180
//
174181
// Once Data and "RustObj" have been merged this can be removed
175-
match s.ident.to_string().as_str() {
182+
match item_struct.ident.to_string().as_str() {
176183
"Data" if !self.qobjects.is_empty() => {
177184
let qobject_ident = self.qobjects.keys().next().unwrap().clone();
178185
self.qobjects
179186
.entry(qobject_ident)
180-
.and_modify(|qobject| qobject.data_struct = Some(s.clone()));
187+
.and_modify(|qobject| qobject.data_struct = Some(item_struct.clone()));
181188
Ok(None)
182189
}
183-
_others => Ok(Some(Item::Struct(s))),
190+
_others => Ok(Some(Item::Struct(item_struct))),
184191
}
185192
}
186193
}
@@ -324,6 +331,25 @@ mod tests {
324331
assert!(cxx_qt_data.qobjects[&qobject_ident()].data_struct.is_some());
325332
}
326333

334+
#[test]
335+
fn test_find_and_merge_cxx_qt_item_struct_valid_base_class() {
336+
let mut cxx_qt_data = create_parsed_cxx_qt_data();
337+
338+
let item: Item = tokens_to_syn(quote! {
339+
#[cxx_qt::qobject(base = "QStringListModel")]
340+
struct MyObject;
341+
});
342+
let result = cxx_qt_data.parse_cxx_qt_item(item).unwrap();
343+
assert!(result.is_none());
344+
assert_eq!(
345+
cxx_qt_data.qobjects[&qobject_ident()]
346+
.base_class
347+
.as_ref()
348+
.unwrap(),
349+
"QStringListModel"
350+
);
351+
}
352+
327353
#[test]
328354
fn test_find_and_merge_cxx_qt_item_struct_valid_rustobj() {
329355
let mut cxx_qt_data = create_parsed_cxx_qt_data();
@@ -337,6 +363,7 @@ mod tests {
337363
assert!(cxx_qt_data.qobjects[&qobject_ident()]
338364
.qobject_struct
339365
.is_some());
366+
assert!(cxx_qt_data.qobjects[&qobject_ident()].base_class.is_none());
340367
}
341368

342369
#[test]

cxx-qt-gen/src/parser/qobject.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ use syn::{
1515
/// then mutate these [syn::Item]'s for generation purposes.
1616
#[derive(Default)]
1717
pub struct ParsedQObject {
18+
/// The base class of the struct
19+
pub base_class: Option<String>,
1820
/// Data struct that currently stores the properties for the QObject
1921
///
2022
/// In the future this will be removed

cxx-qt-gen/src/writer/cpp/header.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ pub fn write_cpp_header(generated: &GeneratedCppBlocks) -> String {
4949
5050
{namespace_start}
5151
52-
class {ident} : public QObject
52+
class {ident} : public {base_class}
5353
{{
5454
Q_OBJECT
5555
{metaobjects}
@@ -71,6 +71,8 @@ pub fn write_cpp_header(generated: &GeneratedCppBlocks) -> String {
7171
{members}
7272
}};
7373
74+
static_assert(std::is_base_of<QObject, {ident}>::value, "{ident} must inherit from QObject");
75+
7476
{namespace_end}
7577
7678
namespace {namespace_internals} {{
@@ -94,6 +96,7 @@ pub fn write_cpp_header(generated: &GeneratedCppBlocks) -> String {
9496
},
9597
namespace_internals = generated.namespace_internals,
9698
rust_ident = generated.rust_ident,
99+
base_class = generated.base_class,
97100
metaobjects = generated.metaobjects.join("\n "),
98101
methods = create_block("public", &generated.methods.iter().map(pair_as_header).collect::<Vec<&str>>()),
99102
slots = create_block("public Q_SLOTS", &generated.slots.iter().map(pair_as_header).collect::<Vec<&str>>()),

cxx-qt-gen/src/writer/cpp/mod.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ mod tests {
3737
rust_ident: "MyObjectRust".to_owned(),
3838
namespace: "cxx_qt::my_object".to_owned(),
3939
namespace_internals: "cxx_qt::my_object::cxx_qt_my_object".to_owned(),
40+
base_class: "QStringListModel".to_owned(),
4041
metaobjects: vec![
4142
"Q_PROPERTY(int count READ count WRITE setCount NOTIFY countChanged)".to_owned(),
4243
"Q_PROPERTY(bool longPropertyNameThatWrapsInClangFormat READ getToggle WRITE setToggle NOTIFY toggleChanged)"
@@ -152,7 +153,7 @@ mod tests {
152153
153154
namespace cxx_qt::my_object {
154155
155-
class MyObject : public QObject
156+
class MyObject : public QStringListModel
156157
{
157158
Q_OBJECT
158159
Q_PROPERTY(int count READ count WRITE setCount NOTIFY countChanged)
@@ -187,6 +188,8 @@ mod tests {
187188
bool m_toggle;
188189
};
189190
191+
static_assert(std::is_base_of<QObject, MyObject>::value, "MyObject must inherit from QObject");
192+
190193
} // namespace cxx_qt::my_object
191194
192195
namespace cxx_qt::my_object::cxx_qt_my_object {
@@ -214,7 +217,7 @@ mod tests {
214217
215218
216219
217-
class MyObject : public QObject
220+
class MyObject : public QStringListModel
218221
{
219222
Q_OBJECT
220223
Q_PROPERTY(int count READ count WRITE setCount NOTIFY countChanged)
@@ -249,6 +252,8 @@ mod tests {
249252
bool m_toggle;
250253
};
251254
255+
static_assert(std::is_base_of<QObject, MyObject>::value, "MyObject must inherit from QObject");
256+
252257
253258
254259
namespace cxx_qt_my_object {
@@ -268,7 +273,7 @@ mod tests {
268273
namespace cxx_qt::my_object {
269274
270275
MyObject::MyObject(QObject* parent)
271-
: QObject(parent)
276+
: QStringListModel(parent)
272277
, m_rustObj(cxx_qt::my_object::cxx_qt_my_object::createRs())
273278
{
274279
cxx_qt::my_object::cxx_qt_my_object::initialiseCpp(*this);
@@ -353,7 +358,7 @@ mod tests {
353358
354359
355360
MyObject::MyObject(QObject* parent)
356-
: QObject(parent)
361+
: QStringListModel(parent)
357362
, m_rustObj(cxx_qt_my_object::createRs())
358363
{
359364
cxx_qt_my_object::initialiseCpp(*this);

cxx-qt-gen/src/writer/cpp/source.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ pub fn write_cpp_source(generated: &GeneratedCppBlocks) -> String {
1919
{namespace_start}
2020
2121
{ident}::{ident}(QObject* parent)
22-
: QObject(parent)
22+
: {base_class}(parent)
2323
, m_rustObj({namespace_internals}::createRs())
2424
{{
2525
{namespace_internals}::initialiseCpp(*this);
@@ -65,6 +65,7 @@ pub fn write_cpp_source(generated: &GeneratedCppBlocks) -> String {
6565
format!("}} // namespace {namespace}", namespace = generated.namespace)
6666
},
6767
namespace_internals = generated.namespace_internals,
68+
base_class = generated.base_class,
6869
rust_ident = generated.rust_ident,
6970
methods = generated.methods.iter().map(pair_as_source).collect::<Vec<String>>().join("\n"),
7071
slots = generated.slots.iter().map(pair_as_source).collect::<Vec<String>>().join("\n"),

cxx-qt-gen/src/writer/rust/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ fn cxx_common_blocks(
1717
vec![
1818
quote! {
1919
unsafe extern "C++" {
20+
include ! (< QtCore / QObject >);
2021
include!("cxx-qt-lib/include/convert.h");
2122

2223
#[cxx_name = "unsafeRust"]
@@ -155,6 +156,7 @@ mod tests {
155156
}
156157

157158
unsafe extern "C++" {
159+
include ! (< QtCore / QObject >);
158160
include!("cxx-qt-lib/include/convert.h");
159161

160162
#[cxx_name = "unsafeRust"]

cxx-qt-gen/test_inputs/naming.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ mod ffi {
55
property_name: i32,
66
}
77

8-
#[cxx_qt::qobject]
8+
unsafe extern "C++" {
9+
include!(<QtCore/QStringListModel>);
10+
}
11+
12+
#[cxx_qt::qobject(base = "QStringListModel")]
913
#[derive(Default)]
1014
pub struct MyObject;
1115

cxx-qt-gen/test_outputs/custom_default.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
mod ffi {
33
unsafe extern "C++" {
44
include!("cxx-qt-gen/include/my_object.cxxqt.h");
5-
include ! (< QtCore / QObject >);
65

76
#[cxx_name = "MyObject"]
87
type MyObjectQt;
@@ -19,6 +18,7 @@ mod ffi {
1918
}
2019

2120
unsafe extern "C++" {
21+
include ! (< QtCore / QObject >);
2222
include!("cxx-qt-lib/include/convert.h");
2323

2424
#[cxx_name = "unsafeRust"]

cxx-qt-gen/test_outputs/handlers.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ public Q_SLOTS:
4646
QString m_string;
4747
};
4848

49+
static_assert(std::is_base_of<QObject, MyObject>::value,
50+
"MyObject must inherit from QObject");
51+
4952
} // namespace cxx_qt::my_object
5053

5154
namespace cxx_qt::my_object::cxx_qt_my_object {

cxx-qt-gen/test_outputs/handlers.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ mod ffi {
33
unsafe extern "C++" {
44
include!("cxx-qt-gen/include/my_object.cxxqt.h");
55
include!("cxx-qt-lib/include/update_requester.h");
6-
include ! (< QtCore / QObject >);
76

87
#[cxx_name = "MyObject"]
98
type MyObjectQt;
@@ -40,6 +39,7 @@ mod ffi {
4039
}
4140

4241
unsafe extern "C++" {
42+
include ! (< QtCore / QObject >);
4343
include!("cxx-qt-lib/include/convert.h");
4444

4545
#[cxx_name = "unsafeRust"]

cxx-qt-gen/test_outputs/invokables.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ class MyObject : public QObject
4040
bool m_initialised = false;
4141
};
4242

43+
static_assert(std::is_base_of<QObject, MyObject>::value,
44+
"MyObject must inherit from QObject");
45+
4346
} // namespace cxx_qt::my_object
4447

4548
namespace cxx_qt::my_object::cxx_qt_my_object {

cxx-qt-gen/test_outputs/invokables.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
mod ffi {
33
unsafe extern "C++" {
44
include!("cxx-qt-gen/include/my_object.cxxqt.h");
5-
include ! (< QtCore / QObject >);
65

76
#[cxx_name = "MyObject"]
87
type MyObjectQt;
@@ -45,6 +44,7 @@ mod ffi {
4544
}
4645

4746
unsafe extern "C++" {
47+
include ! (< QtCore / QObject >);
4848
include!("cxx-qt-lib/include/convert.h");
4949

5050
#[cxx_name = "unsafeRust"]

0 commit comments

Comments
 (0)