Skip to content

Commit

Permalink
cxx-qt-gen: add parsing phase of extern "C++Qt"
Browse files Browse the repository at this point in the history
Related to KDAB#577
  • Loading branch information
ahayzen-kdab committed Jul 27, 2023
1 parent 1292083 commit 0264453
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 5 deletions.
40 changes: 36 additions & 4 deletions crates/cxx-qt-gen/src/parser/cxxqtdata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ use crate::syntax::foreignmod::{foreign_mod_to_foreign_item_types, ForeignTypeId
use crate::syntax::path::path_from_idents;
use crate::syntax::safety::Safety;
use crate::{
parser::{inherit::ParsedInheritedMethod, qobject::ParsedQObject, signals::ParsedSignal},
parser::{
externcxxqt::ParsedExternCxxQt, inherit::ParsedInheritedMethod, method::ParsedMethod,
qobject::ParsedQObject, signals::ParsedSignal,
},
syntax::expr::expr_to_string,
};
use quote::format_ident;
Expand All @@ -18,8 +21,6 @@ use syn::{
Result, Type, TypePath,
};

use super::method::ParsedMethod;

#[derive(Default)]
pub struct ParsedCxxMappings {
/// Map of the cxx_name of any types defined in CXX extern blocks
Expand Down Expand Up @@ -60,6 +61,8 @@ pub struct ParsedCxxQtData {
// We have to use a BTreeMap here, instead of a HashMap, to keep the order of QObjects stable.
// Otherwise, the output order would be different, depending on the environment, which makes it hard to test/debug.
pub qobjects: BTreeMap<Ident, ParsedQObject>,
/// Blocks of extern "C++Qt"
pub extern_cxx_blocks: Vec<ParsedExternCxxQt>,
/// The namespace of the CXX-Qt module
pub namespace: String,
/// The ident of the module, used for mappings
Expand All @@ -73,6 +76,7 @@ impl ParsedCxxQtData {
cxx_mappings: ParsedCxxMappings::default(),
qualified_mappings: BTreeMap::<Ident, Path>::default(),
qobjects: BTreeMap::<Ident, ParsedQObject>::default(),
extern_cxx_blocks: Vec::<ParsedExternCxxQt>::default(),
module_ident,
namespace,
}
Expand Down Expand Up @@ -233,7 +237,12 @@ impl ParsedCxxQtData {
self.parse_foreign_mod_rust_qt(foreign_mod)?;
return Ok(None);
}
// TODO: look for "C++Qt" later
"C++Qt" => {
self.populate_mappings_from_foreign_mod_item(&foreign_mod)?;
self.extern_cxx_blocks
.push(ParsedExternCxxQt::parse(foreign_mod)?);
return Ok(None);
}
_others => {}
}
}
Expand Down Expand Up @@ -561,6 +570,29 @@ mod tests {
assert!(cxx_qt_data.qobjects[&qobject_ident()].threading);
}

#[test]
fn test_find_and_merge_cxx_qt_item_extern_cxx_qt() {
let mut cxx_qt_data = create_parsed_cxx_qt_data();

let item: Item = parse_quote! {
#[namespace = "rust"]
unsafe extern "C++Qt" {
type QPushButton;

#[qsignal]
fn clicked(self: Pin<&mut QPushButton>, checked: bool);
}
};
let result = cxx_qt_data.parse_cxx_qt_item(item).unwrap();
assert!(result.is_none());

assert_eq!(cxx_qt_data.extern_cxx_blocks.len(), 1);
assert_eq!(cxx_qt_data.extern_cxx_blocks[0].attrs.len(), 1);
assert_eq!(cxx_qt_data.extern_cxx_blocks[0].passthrough_items.len(), 1);
assert_eq!(cxx_qt_data.extern_cxx_blocks[0].signals.len(), 1);
assert!(cxx_qt_data.extern_cxx_blocks[0].unsafety.is_some());
}

#[test]
fn test_cxx_mappings_cxx_name_empty() {
let mut cxx_qt_data = create_parsed_cxx_qt_data();
Expand Down
86 changes: 86 additions & 0 deletions crates/cxx-qt-gen/src/parser/externcxxqt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
// SPDX-FileContributor: Andrew Hayzen <[email protected]>
//
// SPDX-License-Identifier: MIT OR Apache-2.0

use crate::{
parser::signals::ParsedSignal,
syntax::{attribute::attribute_find_path, safety::Safety},
};
use syn::{Attribute, ForeignItem, ItemForeignMod, Result, Token};

/// Representation of an extern "C++Qt" block
#[derive(Default)]
pub struct ParsedExternCxxQt {
/// Attributes for the extern "C++Qt" block
pub attrs: Vec<Attribute>,
/// Whether this block has an unsafe token
pub unsafety: Option<Token![unsafe]>,
/// Items which can be passed into the extern "C++Qt" block
pub passthrough_items: Vec<ForeignItem>,
/// Signals that need generation in the extern "C++Qt" block
pub signals: Vec<ParsedSignal>,
}

impl ParsedExternCxxQt {
pub fn parse(mut foreign_mod: ItemForeignMod) -> Result<Self> {
let mut extern_cxx_block = ParsedExternCxxQt {
attrs: foreign_mod.attrs.clone(),
unsafety: foreign_mod.unsafety,
..Default::default()
};

let safe_call = if foreign_mod.unsafety.is_some() {
Safety::Safe
} else {
Safety::Unsafe
};

// Parse any signals, other items are passed through
for item in foreign_mod.items.drain(..) {
if let ForeignItem::Fn(foreign_fn) = &item {
// Test if the function is a signal
if let Some(index) = attribute_find_path(&foreign_fn.attrs, &["qsignal"]) {
let mut foreign_fn = foreign_fn.clone();
// Remove the signals attribute
foreign_fn.attrs.remove(index);

extern_cxx_block
.signals
.push(ParsedSignal::parse(foreign_fn, safe_call)?);
continue;
}
}

extern_cxx_block.passthrough_items.push(item);
}

Ok(extern_cxx_block)
}
}

#[cfg(test)]
mod tests {
use super::*;

use syn::parse_quote;

#[test]
fn test_find_and_merge_cxx_qt_item_extern_cxx_qt() {
let extern_cxx_qt = ParsedExternCxxQt::parse(parse_quote! {
#[namespace = "rust"]
unsafe extern "C++Qt" {
type QPushButton;

#[qsignal]
fn clicked(self: Pin<&mut QPushButton>, checked: bool);
}
})
.unwrap();

assert_eq!(extern_cxx_qt.attrs.len(), 1);
assert_eq!(extern_cxx_qt.passthrough_items.len(), 1);
assert_eq!(extern_cxx_qt.signals.len(), 1);
assert!(extern_cxx_qt.unsafety.is_some());
}
}
3 changes: 2 additions & 1 deletion crates/cxx-qt-gen/src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

pub mod constructor;
pub mod cxxqtdata;
pub mod externcxxqt;
pub mod inherit;
pub mod method;
pub mod parameter;
Expand Down Expand Up @@ -71,7 +72,7 @@ impl Parser {
// Loop through items and load into qobject or others and populate mappings
for item in items.1.drain(..) {
// Try to find any CXX-Qt items, if found add them to the relevant
// qobject. Otherwise return them to be added to other
// qobject or extern C++Qt block. Otherwise return them to be added to other
if let Some(other) = cxx_qt_data.parse_cxx_qt_item(item)? {
// Load any CXX name mappings
cxx_qt_data.populate_mappings_from_item(&other)?;
Expand Down

0 comments on commit 0264453

Please sign in to comment.