Skip to content

Commit

Permalink
Move ParsedMethods into structuring phase
Browse files Browse the repository at this point in the history
* Start refactor of methods and signals int o structuring phase.
ParsedCxxQtData now has method and signal fields, and StructuredQObject has a methods hashmap

* Assemble HashMap of methods from structure phase.
StructuredQObject now should have a correct HashMap of it's methods, as built in Structure

* Add signals HashMap to StructuredQObject.
* Refactor Structuring phase
Added a name check function to StructuredQObject for searching for matches with method definitions
Switched methods and signals to use a Vec instead of HashMap in StructuredQObject
Rewrite test in Structuring due to using Vec over HashMap

Related to #1004
  • Loading branch information
BenFordTytherington authored Jul 29, 2024
1 parent fd77bcb commit 4ed6351
Show file tree
Hide file tree
Showing 8 changed files with 179 additions and 39 deletions.
3 changes: 2 additions & 1 deletion crates/cxx-qt-gen/src/generator/cpp/qobject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ impl GeneratedCppQObject {
&qobject_idents,
type_names,
)?);

generated.blocks.append(&mut inherit::generate(
&qobject.inherited_methods,
&qobject.base_class,
Expand All @@ -175,7 +176,7 @@ impl GeneratedCppQObject {
let (initializer, mut blocks) = threading::generate(&qobject_idents)?;
generated.blocks.append(&mut blocks);
class_initializers.push(initializer);
// If this type has locking enabled then add generation
// If this type has locking enabled then add generation
} else if qobject.locking {
let (initializer, mut blocks) = locking::generate()?;
generated.blocks.append(&mut blocks);
Expand Down
2 changes: 1 addition & 1 deletion crates/cxx-qt-gen/src/generator/cpp/signal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -521,7 +521,7 @@ mod tests {
let mut type_names = TypeNames::default();
type_names.mock_insert("ObjRust", None, None, None);
let qobject_name = type_names.lookup(&signal.qobject_ident).unwrap();
let generated = generate_cpp_signal(&signal, &qobject_name, &type_names).unwrap();
let generated = generate_cpp_signal(&signal, qobject_name, &type_names).unwrap();

assert_eq!(generated.methods.len(), 0);

Expand Down
1 change: 1 addition & 0 deletions crates/cxx-qt-gen/src/generator/rust/qobject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use quote::quote;
use syn::{Ident, Result};

impl GeneratedRustFragment {
// Might need to be refactored to use a StructuredQObject instead (confirm with Leon)
pub fn from_qobject(
qobject: &ParsedQObject,
type_names: &TypeNames,
Expand Down
135 changes: 118 additions & 17 deletions crates/cxx-qt-gen/src/generator/structuring/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,20 @@
/// cxx-qt-gen, especially to simplify parsing.
/// This module is responsible for structuring the parsed data into a form that is easier to work
/// with when generating C++ code.
/// This mostly means grouping QObjects with their QEnums, QSignals, etc..
/// This mostly means grouping QObjects with their QEnums, QSignals, etc...
///
/// All resulting structures are listed in the `Structures` struct.
pub mod qobject;
pub use qobject::StructuredQObject;

use crate::parser::cxxqtdata::ParsedCxxQtData;
pub use qobject::StructuredQObject;
use syn::{Error, Result};

/// The list of all structures that could be associated from the parsed data.
/// Most importantly, this includes the list of qobjects.
pub struct Structures<'a> {
/// The list of qobjects
pub qobjects: Vec<qobject::StructuredQObject<'a>>,
pub qobjects: Vec<StructuredQObject<'a>>,
}

impl<'a> Structures<'a> {
Expand All @@ -32,28 +32,129 @@ impl<'a> Structures<'a> {
let mut qobjects: Vec<_> = cxxqtdata
.qobjects
.values()
.map(|qobject| StructuredQObject {
declaration: qobject,
qenums: Vec::new(),
})
.map(StructuredQObject::from_qobject)
.collect();

for qenum in &cxxqtdata.qenums {
if let Some(qobject_ident) = &qenum.qobject {
if let Some(qobject) = qobjects
let qobject = qobjects
.iter_mut()
.find(|qobject| qobject.declaration.name.rust_unqualified() == qobject_ident)
{
qobject.qenums.push(qenum);
} else {
return Err(Error::new_spanned(
qobject_ident,
format!("Unknown QObject: {qobject_ident}"),
));
}
.find(|qobject| qobject.has_qobject_name(qobject_ident))
.ok_or_else(|| {
Error::new_spanned(
qobject_ident,
format!("Unknown QObject: {qobject_ident}"),
)
})?;
qobject.qenums.push(qenum);
}
}

// Associate each method parsed with its appropriate qobject
for method in &cxxqtdata.methods {
let qobject = qobjects
.iter_mut()
.find(|qobject| qobject.has_qobject_name(&method.qobject_ident))
.ok_or_else(|| {
Error::new_spanned(
&method.qobject_ident,
format!("Unknown QObject: {:?}", &method.qobject_ident),
)
})?;
qobject.methods.push(method);
}

// Associate each signal parsed with its appropriate qobject
for signal in &cxxqtdata.signals {
let qobject = qobjects
.iter_mut()
.find(|qobject| qobject.has_qobject_name(&signal.qobject_ident))
.ok_or_else(|| {
Error::new_spanned(
&signal.qobject_ident,
format!("Unknown QObject: {:?}", &signal.qobject_ident),
)
})?;
qobject.signals.push(signal);
}
Ok(Structures { qobjects })
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::Parser;
use quote::format_ident;
use syn::{parse_quote, ItemMod};

#[test]
fn test_structures() {
let module: ItemMod = parse_quote! {
#[cxx_qt::bridge]
mod ffi {
extern "RustQt" {
#[qobject]
type MyObject = super::MyObjectRust;

#[qobject]
type MyOtherObject = super::MyOtherObjectRust;
}

unsafe extern "RustQt" {
#[qinvokable]
fn test_fn(self: Pin<&mut MyObject>);

#[qinvokable]
fn test_fn_two(self: Pin<&mut MyObject>);

#[qinvokable]
fn test_fn_again(self: Pin<&mut MyOtherObject>);

#[qsignal]
fn ready(self: Pin<&mut MyOtherObject>);
}
}
};

let parser = Parser::from(module.clone()).unwrap();
let structures = Structures::new(&parser.cxx_qt_data).unwrap();

assert_eq!(structures.qobjects.len(), 2);
let my_object = &structures.qobjects[0];
let my_other_object = &structures.qobjects[1];

assert_eq!(
*my_object.declaration.name.rust_unqualified(),
format_ident!("MyObject")
);
assert_eq!(
*my_other_object.declaration.name.rust_unqualified(),
format_ident!("MyOtherObject")
);

assert_eq!(my_object.methods.len(), 2);
assert_eq!(my_other_object.methods.len(), 1);

assert!(my_object.signals.is_empty());
assert_eq!(my_other_object.signals.len(), 1);

// Checking methods were registered
assert_eq!(
*my_object.methods[0].name.rust_unqualified(),
format_ident!("test_fn")
);
assert_eq!(
*my_object.methods[1].name.rust_unqualified(),
format_ident!("test_fn_two")
);
assert_eq!(
*my_other_object.methods[0].name.rust_unqualified(),
format_ident!("test_fn_again")
);
assert_eq!(
*my_other_object.signals[0].name.rust_unqualified(),
format_ident!("ready")
);
}
}
21 changes: 21 additions & 0 deletions crates/cxx-qt-gen/src/generator/structuring/qobject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,32 @@
//
// SPDX-License-Identifier: MIT OR Apache-2.0

use crate::parser::method::ParsedMethod;
use crate::parser::signals::ParsedSignal;
use crate::parser::{qenum::ParsedQEnum, qobject::ParsedQObject};
use proc_macro2::Ident;

/// The StructuredQObject contains the parsed QObject and all members.
/// This includes QEnums, QSignals, methods, etc.
pub struct StructuredQObject<'a> {
pub declaration: &'a ParsedQObject,
pub qenums: Vec<&'a ParsedQEnum>,
pub methods: Vec<&'a ParsedMethod>,
pub signals: Vec<&'a ParsedSignal>,
}

impl<'a> StructuredQObject<'a> {
pub fn has_qobject_name(&self, ident: &Ident) -> bool {
self.declaration.name.rust_unqualified() == ident
}

/// Creates a [StructuredQObject] from a [ParsedQObject] with empty enum, method and signal collections
pub fn from_qobject(qobject: &'a ParsedQObject) -> Self {
Self {
declaration: qobject,
qenums: vec![],
methods: vec![],
signals: vec![],
}
}
}
46 changes: 28 additions & 18 deletions crates/cxx-qt-gen/src/parser/cxxqtdata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ pub struct ParsedCxxQtData {
pub qobjects: BTreeMap<Ident, ParsedQObject>,
/// List of QEnums defined in the module, that aren't associated with a QObject
pub qenums: Vec<ParsedQEnum>,
/// List of methods and Q_INVOKABLES found
pub methods: Vec<ParsedMethod>,
/// List of the Q_SIGNALS found
pub signals: Vec<ParsedSignal>,
/// List of QNamespace declarations
pub qnamespaces: Vec<ParsedQNamespace>,
/// Blocks of extern "C++Qt"
Expand All @@ -47,6 +51,8 @@ impl ParsedCxxQtData {
Self {
qobjects: BTreeMap::<Ident, ParsedQObject>::default(),
qenums: vec![],
methods: vec![],
signals: vec![],
qnamespaces: vec![],
extern_cxxqt_blocks: Vec::<ParsedExternCxxQt>::default(),
module_ident,
Expand Down Expand Up @@ -204,31 +210,38 @@ impl ParsedCxxQtData {
if let ForeignItem::Fn(mut foreign_fn) = item {
// Test if the function is a signal
if attribute_take_path(&mut foreign_fn.attrs, &["qsignal"]).is_some() {
let parsed_signal_method = ParsedSignal::parse(foreign_fn, safe_call)?;
let parsed_signal_method = ParsedSignal::parse(foreign_fn.clone(), safe_call)?;

let parsed_signal_method_self = ParsedSignal::parse(foreign_fn, safe_call)?;
self.signals.push(parsed_signal_method_self);

self.with_qobject(&parsed_signal_method.qobject_ident)?
.signals
.push(parsed_signal_method);
// Test if the function is an inheritance method
//
// Note that we need to test for qsignal first as qsignals have their own inherit meaning

// Test if the function is an inheritance method
//
// Note that we need to test for qsignal first as qsignals have their own inherit meaning
} else if attribute_take_path(&mut foreign_fn.attrs, &["inherit"]).is_some() {
let parsed_inherited_method =
ParsedInheritedMethod::parse(foreign_fn, safe_call)?;

self.with_qobject(&parsed_inherited_method.qobject_ident)?
.inherited_methods
.push(parsed_inherited_method);
// Remaining methods are either C++ methods or invokables
// Remaining methods are either C++ methods or invokables
} else {
let parsed_method = ParsedMethod::parse(foreign_fn, safe_call)?;
let parsed_method = ParsedMethod::parse(foreign_fn.clone(), safe_call)?;

let parsed_method_self = ParsedMethod::parse(foreign_fn, safe_call)?;
self.methods.push(parsed_method_self);

self.with_qobject(&parsed_method.qobject_ident)?
.methods
.push(parsed_method);
}
}
}

Ok(())
}

Expand Down Expand Up @@ -468,9 +481,10 @@ mod tests {
};
let result = cxx_qt_data.parse_cxx_qt_item(item).unwrap();
assert!(result.is_none());
assert_eq!(cxx_qt_data.qobjects[&qobject_ident()].methods.len(), 2);
assert!(cxx_qt_data.qobjects[&qobject_ident()].methods[0].is_qinvokable);
assert!(!cxx_qt_data.qobjects[&qobject_ident()].methods[1].is_qinvokable);

assert_eq!(cxx_qt_data.methods.len(), 2);
assert!(cxx_qt_data.methods[0].is_qinvokable);
assert!(!cxx_qt_data.methods[1].is_qinvokable)
}

#[test]
Expand Down Expand Up @@ -623,10 +637,7 @@ mod tests {
}
};
cxxqtdata.parse_cxx_qt_item(block).unwrap();

let qobject = cxxqtdata.qobjects.get(&qobject_ident()).unwrap();

let signals = &qobject.signals;
let signals = &cxxqtdata.signals;
assert_eq!(signals.len(), 2);
assert!(signals[0].mutable);
assert!(signals[1].mutable);
Expand All @@ -653,7 +664,8 @@ mod tests {
fn ready(self: Pin<&mut UnknownObj>);
}
};
assert!(cxxqtdata.parse_cxx_qt_item(block).is_err());
let parsed_block = cxxqtdata.parse_cxx_qt_item(block);
assert!(parsed_block.is_err());
}

#[test]
Expand All @@ -667,9 +679,7 @@ mod tests {
};
cxxqtdata.parse_cxx_qt_item(block).unwrap();

let qobject = cxxqtdata.qobjects.get(&qobject_ident()).unwrap();

let signals = &qobject.signals;
let signals = &cxxqtdata.signals;
assert_eq!(signals.len(), 1);
assert!(signals[0].mutable);
assert!(!signals[0].safe);
Expand Down
7 changes: 7 additions & 0 deletions crates/cxx-qt-gen/src/parser/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,13 @@ impl ParsedMethod {

let name = Name::from_rust_ident_and_attrs(&method.sig.ident, &method.attrs, None, None)?;

if name.namespace().is_some() {
return Err(Error::new_spanned(
method.sig.ident,
"Methods / QInvokables cannot have a namespace attribute",
));
}

Ok(ParsedMethod {
method,
qobject_ident,
Expand Down
3 changes: 1 addition & 2 deletions crates/cxx-qt-gen/src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ impl Parser {
// Parse any namespace in the cxx_qt::bridge macro
if name_value.path.is_ident("namespace") {
namespace = Some(expr_to_string(&name_value.value)?);
// Parse any custom file stem
// Parse any custom file stem
} else if name_value.path.is_ident("cxx_file_stem") {
cxx_file_stem = expr_to_string(&name_value.value)?;
}
Expand Down Expand Up @@ -156,7 +156,6 @@ mod tests {
pub fn f64_type() -> Type {
parse_quote! { f64 }
}

#[test]
fn test_parser_from_empty_module() {
let module: ItemMod = parse_quote! {
Expand Down

0 comments on commit 4ed6351

Please sign in to comment.