Skip to content

Commit

Permalink
Add support for #[qenum] associated with a QObject
Browse files Browse the repository at this point in the history
This is the initial support for KDAB#34.

It only supports `Q_ENUM` within a given `Q_OBJECT` class.
`Q_ENUM_NS` is not (yet) supported.
Documentation will be added in a follow-up commit.
  • Loading branch information
LeonMatthesKDAB committed Aug 18, 2023
1 parent 288c079 commit 3edb6e3
Show file tree
Hide file tree
Showing 30 changed files with 686 additions and 25 deletions.
1 change: 1 addition & 0 deletions crates/cxx-qt-gen/src/generator/cpp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub mod inherit;
pub mod locking;
pub mod method;
pub mod property;
pub mod qenum;
pub mod qobject;
pub mod signal;
pub mod threading;
Expand Down
111 changes: 111 additions & 0 deletions crates/cxx-qt-gen/src/generator/cpp/qenum.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
// SPDX-FileContributor: Leon Matthes <leon.matthes@kdab.com>
//
// SPDX-License-Identifier: MIT OR Apache-2.0

use indoc::formatdoc;
use syn::Result;

use crate::{
generator::utils::cpp::Indent,
parser::{mappings::ParsedCxxMappings, qenum::ParsedQEnum},
};

use super::qobject::GeneratedCppQObjectBlocks;

pub fn generate(
qenums: &[ParsedQEnum],
cxx_mappings: &ParsedCxxMappings,
) -> Result<GeneratedCppQObjectBlocks> {
let mut generated = GeneratedCppQObjectBlocks::default();

for qenum in qenums {
let enum_name = &qenum.ident.to_string();

let mut qualified_name = cxx_mappings.cxx(enum_name);
// TODO: this is a workaround for cxx_mappings.cxx not always returning a fully-qualified
// identifier.
// Once https://github.com/KDAB/cxx-qt/issues/619 is fixed, this can be removed.
if !qualified_name.starts_with("::") {
qualified_name.insert_str(0, "::");
}

let enum_values = qenum
.variants
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join(",\n");

generated.includes.insert("#include <cstdint>".to_string());
let enum_definition = formatdoc! { r#"
enum class {enum_name} : ::std::int32_t {{
{enum_values}
}};
"#, enum_values = enum_values.indented(2) };
generated.forward_declares.push(enum_definition.clone());
generated.metaobjects.push(formatdoc! {r#"
#ifdef Q_MOC_RUN
{enum_definition}
Q_ENUM({enum_name})
#else
using {enum_name} = {qualified_name};
Q_ENUM({enum_name})
#endif
"#, enum_definition = enum_definition.indented(2) });
}

Ok(generated)
}

#[cfg(test)]
mod tests {
use std::assert_eq;

use super::*;
use indoc::indoc;
use pretty_assertions::assert_str_eq;
use syn::parse_quote;

#[test]
fn generates() {
let qenums = [ParsedQEnum::parse(parse_quote! {
enum MyEnum {
A, B, C
}
})
.unwrap()];

let generated = generate(&qenums, &ParsedCxxMappings::default()).unwrap();
assert_eq!(generated.includes.len(), 1);
assert!(generated.includes.contains("#include <cstdint>"));
assert_eq!(generated.metaobjects.len(), 1);
assert_str_eq!(
indoc! {r#"
#ifdef Q_MOC_RUN
enum class MyEnum : ::std::int32_t {
A,
B,
C
};
Q_ENUM(MyEnum)
#else
using MyEnum = ::MyEnum;
Q_ENUM(MyEnum)
#endif
"#},
generated.metaobjects[0],
);
assert_eq!(generated.forward_declares.len(), 1);
assert_str_eq!(
indoc! { r#"
enum class MyEnum : ::std::int32_t {
A,
B,
C
};
"# },
generated.forward_declares[0],
);
}
}
5 changes: 4 additions & 1 deletion crates/cxx-qt-gen/src/generator/cpp/qobject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
use crate::generator::{
cpp::{
constructor, cxxqttype, fragment::CppFragment, inherit, locking,
method::generate_cpp_methods, property::generate_cpp_properties,
method::generate_cpp_methods, property::generate_cpp_properties, qenum,
signal::generate_cpp_signals, threading,
},
naming::{namespace::NamespaceName, qobject::QObjectName},
Expand Down Expand Up @@ -134,6 +134,9 @@ impl GeneratedCppQObject {
&qobject.base_class,
cxx_mappings,
)?);
generated
.blocks
.append(&mut qenum::generate(&qobject.qenums, cxx_mappings)?);

let mut class_initializers = vec![];

Expand Down
1 change: 1 addition & 0 deletions crates/cxx-qt-gen/src/generator/rust/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub mod fragment;
pub mod inherit;
pub mod method;
pub mod property;
pub mod qenum;
pub mod qobject;
pub mod signals;
pub mod threading;
Expand Down
77 changes: 77 additions & 0 deletions crates/cxx-qt-gen/src/generator/rust/qenum.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
// SPDX-FileContributor: Leon Matthes <leon.matthes@kdab.com>
//
// SPDX-License-Identifier: MIT OR Apache-2.0

use crate::{generator::rust::qobject::GeneratedRustQObject, parser::qenum::ParsedQEnum};
use syn::parse_quote;

pub fn generate(qenums: &[ParsedQEnum]) -> GeneratedRustQObject {
let mut result = GeneratedRustQObject::default();
for qenum in qenums {
let qenum_item = &qenum.item;
let qenum_ident = &qenum.ident;
result.append(&mut GeneratedRustQObject {
cxx_mod_contents: vec![
parse_quote! {
#[repr(i32)]
#qenum_item
},
parse_quote! {
extern "C++" {
type #qenum_ident;
}
},
],
..Default::default()
});
}
result
}

#[cfg(test)]
mod tests {
use crate::tests::assert_tokens_eq;
use quote::quote;
use syn::parse_quote;

use super::*;

#[test]
fn generates() {
let qenums = vec![ParsedQEnum::parse(parse_quote! {
/// Doc comment
enum MyEnum {
/// Document Variant1
Variant1,
/// Document Variant2
Variant2,
}
})
.unwrap()];

let generated = generate(&qenums);
assert_eq!(generated.cxx_mod_contents.len(), 2);
assert_tokens_eq(
&generated.cxx_mod_contents[0],
quote! {
#[repr(i32)]
#[doc = r" Doc comment"]
enum MyEnum {
#[doc = r" Document Variant1"]
Variant1,
#[doc = r" Document Variant2"]
Variant2,
}
},
);
assert_tokens_eq(
&generated.cxx_mod_contents[1],
quote! {
extern "C++" {
type MyEnum;
}
},
)
}
}
3 changes: 3 additions & 0 deletions crates/cxx-qt-gen/src/generator/rust/qobject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ use crate::{
use quote::quote;
use syn::{Ident, ImplItem, Item, Path, Result};

use super::qenum;

#[derive(Default)]
pub struct GeneratedRustQObject {
/// Module for the CXX bridge
Expand Down Expand Up @@ -73,6 +75,7 @@ impl GeneratedRustQObject {
&qobject_idents,
qualified_mappings,
)?);
generated.append(&mut qenum::generate(&qobject.qenums));

// If this type is a singleton then we need to add an include
if let Some(qml_metadata) = &qobject.qml_metadata {
Expand Down
41 changes: 41 additions & 0 deletions crates/cxx-qt-gen/src/generator/utils/cpp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,8 +292,27 @@ fn possible_built_in_template_base(ty: &str) -> Option<&str> {
}
}

/// A trait to allow indenting multi-line string
/// This is specifically useful when using formatdoc! with a multi-line string argument.
/// As the formatdoc! formatting doesn't support indenting multi-line arguments, we can indent
/// those ourselves.
pub(crate) trait Indent {
fn indented(&self, indent: usize) -> String;
}

impl Indent for str {
fn indented(&self, indent: usize) -> String {
self.lines()
.map(|line| " ".repeat(indent) + line)
.collect::<Vec<_>>()
.join("\n")
}
}

#[cfg(test)]
mod tests {
use indoc::{formatdoc, indoc};
use pretty_assertions::assert_str_eq;
use syn::parse_quote;

use super::*;
Expand Down Expand Up @@ -695,4 +714,26 @@ mod tests {
None
);
}

#[test]
fn indent_string() {
let multiline_string = indoc! { r#"
A,
B,
"#};

assert_str_eq!(
formatdoc! { r#"
enum Test {{
{multiline_string}
}}
"#, multiline_string = multiline_string.indented(2) },
indoc! { r#"
enum Test {
A,
B,
}
"#}
);
}
}
5 changes: 5 additions & 0 deletions crates/cxx-qt-gen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,4 +211,9 @@ mod tests {
fn generates_inheritance() {
test_code_generation!("inheritance");
}

#[test]
fn generates_qenum() {
test_code_generation!("qenum");
}
}
31 changes: 29 additions & 2 deletions crates/cxx-qt-gen/src/parser/cxxqtdata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ use crate::syntax::safety::Safety;
use crate::{
parser::{
externcxxqt::ParsedExternCxxQt, inherit::ParsedInheritedMethod,
mappings::ParsedCxxMappings, method::ParsedMethod, qobject::ParsedQObject,
signals::ParsedSignal,
mappings::ParsedCxxMappings, method::ParsedMethod, qenum::ParsedQEnum,
qobject::ParsedQObject, signals::ParsedSignal,
},
syntax::expr::expr_to_string,
};
Expand Down Expand Up @@ -166,10 +166,37 @@ impl ParsedCxxQtData {
match item {
Item::Impl(imp) => self.parse_impl(imp),
Item::ForeignMod(foreign_mod) => self.parse_foreign_mod(foreign_mod),
Item::Enum(enum_item) => self.parse_enum(enum_item),
_ => Ok(Some(item)),
}
}

fn parse_enum(&mut self, mut item: ItemEnum) -> Result<Option<Item>> {
if let Some(qenum_attribute) = attribute_take_path(&mut item.attrs, &["qenum"]) {
let qobject: Ident = qenum_attribute.parse_args()?;
if let Some(qobject) = self.qobjects.get_mut(&qobject) {
let qenum = ParsedQEnum::parse(item)?;
self.cxx_mappings.populate(
&qenum.ident,
&qenum.item.attrs,
&self.namespace,
&self.module_ident,
)?;

qobject.qenums.push(qenum);

Ok(None)
} else {
Err(Error::new_spanned(
&qobject,
format!("Could not find qobject {qobject}!"),
))
}
} else {
Ok(Some(Item::Enum(item)))
}
}

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() {
Expand Down
2 changes: 1 addition & 1 deletion crates/cxx-qt-gen/src/parser/mappings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use syn::{spanned::Spanned, Attribute, Ident, Path, Result};

use crate::syntax::{attribute::attribute_find_path, expr::expr_to_string, path::path_from_idents};

#[derive(Default)]
#[derive(Default, Debug)]
pub struct ParsedCxxMappings {
/// Map of the cxx_name of any types defined in CXX extern blocks
///
Expand Down
1 change: 1 addition & 0 deletions crates/cxx-qt-gen/src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub mod mappings;
pub mod method;
pub mod parameter;
pub mod property;
pub mod qenum;
pub mod qobject;
pub mod signals;

Expand Down
Loading

0 comments on commit 3edb6e3

Please sign in to comment.