From 5dc412fbf358ecb245b673bd31c2630db3967e1b Mon Sep 17 00:00:00 2001 From: Leon Matthes Date: Sat, 15 Jul 2023 11:54:21 +0200 Subject: [PATCH] cxx_qt::Constructor: Add tests --- .../src/generator/cpp/constructor.rs | 166 ++++++++++++++++-- .../cxx-qt-gen/src/generator/cpp/fragment.rs | 1 + .../src/generator/rust/constructor.rs | 31 ++-- crates/cxx-qt-gen/src/parser/constructor.rs | 91 +++++++++- .../test_outputs/passthrough_and_naming.cpp | 1 - 5 files changed, 250 insertions(+), 40 deletions(-) diff --git a/crates/cxx-qt-gen/src/generator/cpp/constructor.rs b/crates/cxx-qt-gen/src/generator/cpp/constructor.rs index 31e465a6e..9c41a1d91 100644 --- a/crates/cxx-qt-gen/src/generator/cpp/constructor.rs +++ b/crates/cxx-qt-gen/src/generator/cpp/constructor.rs @@ -27,8 +27,7 @@ fn default_constructor( r#" {class_name}::{class_name}(QObject* parent) : {base_class}(parent) - , m_rustObj({namespace_internals}::createRs()) - {initializers} + , m_rustObj({namespace_internals}::createRs()){initializers} {{ }} "#, @@ -62,15 +61,15 @@ fn expand_arguments(arguments: &[Type], cxx_mappings: &ParsedCxxMappings) -> Res pub fn generate( qobject: &GeneratedCppQObject, - constructors: &Vec, + constructors: &[Constructor], member_initializers: &[String], cxx_mappings: &ParsedCxxMappings, ) -> Result { let initializers = member_initializers .iter() - .map(|initializer| format!(", {initializer}")) + .map(|initializer| format!("\n , {initializer}")) .collect::>() - .join("\n"); + .join(""); if constructors.is_empty() { return Ok(default_constructor(qobject, initializers)); @@ -90,15 +89,15 @@ pub fn generate( source: formatdoc! { r#" {class_name}::{class_name}({argument_list}) - : {class_name}({namespace_internals}::routeArguments{index}({move_arguments})) - {{ }} + : {class_name}({namespace_internals}::routeArguments{index}({move_arguments})) + {{ }} "#, move_arguments = constructor_argument_names.iter().map(|arg| format!("std::move({arg})")).collect::>().join(", "), }, }); - let base_args = if let Some(base_args) = &constructor.base_arguments { - argument_names(base_args) + let base_args = if !constructor.base_arguments.is_empty() { + argument_names(&constructor.base_arguments) .into_iter() .map(|arg| format!("std::move(args.baseArguments.{arg})")) .collect::>() @@ -120,12 +119,11 @@ pub fn generate( source: formatdoc! { r#" {class_name}::{class_name}({namespace_internals}::CxxQtConstructorArguments{index}&& args) - : {base_class}({base_args}) - , m_rustObj({namespace_internals}::newRs{index}(std::move(args.newArguments))) - {initializers} - {{ - {namespace_internals}::initialize{index}(*this, std::move(args.initializeArguments)); - }} + : {base_class}({base_args}) + , m_rustObj({namespace_internals}::newRs{index}(std::move(args.newArguments))){initializers} + {{ + {namespace_internals}::initialize{index}(*this, std::move(args.initializeArguments)); + }} "#, }, }) @@ -133,3 +131,141 @@ pub fn generate( Ok(generated) } + +#[cfg(test)] +mod tests { + use super::*; + + use std::assert_eq; + use syn::parse_quote; + + fn qobject_for_testing() -> GeneratedCppQObject { + GeneratedCppQObject { + ident: "MyObject".to_string(), + rust_ident: "MyObjectQt".to_string(), + namespace_internals: "rust".to_string(), + base_class: "BaseClass".to_string(), + blocks: GeneratedCppQObjectBlocks::default(), + locking: true, + } + } + + #[test] + fn test_default_constructor() { + let blocks = generate( + &qobject_for_testing(), + &[], + &["member1(1)".to_string(), "member2{ 2 }".to_string()], + &ParsedCxxMappings::default(), + ) + .unwrap(); + + assert!(blocks.members.is_empty()); + assert!(blocks.private_methods.is_empty()); + assert!(blocks.metaobjects.is_empty()); + assert!(blocks.forward_declares.is_empty()); + assert!(blocks.deconstructors.is_empty()); + assert_eq!( + blocks.methods, + vec![CppFragment::Pair { + header: "explicit MyObject(QObject* parent = nullptr);".to_string(), + source: formatdoc!( + " + MyObject::MyObject(QObject* parent) + : BaseClass(parent) + , m_rustObj(rust::createRs()) + , member1(1) + , member2{{ 2 }} + {{ + }} + " + ), + }] + ); + } + #[test] + fn test_default_constructor_no_initializers() { + let blocks = generate( + &qobject_for_testing(), + &[], + &[], + &ParsedCxxMappings::default(), + ) + .unwrap(); + + assert!(blocks.members.is_empty()); + assert!(blocks.private_methods.is_empty()); + assert!(blocks.metaobjects.is_empty()); + assert!(blocks.forward_declares.is_empty()); + assert!(blocks.deconstructors.is_empty()); + assert_eq!( + blocks.methods, + vec![CppFragment::Pair { + header: "explicit MyObject(QObject* parent = nullptr);".to_string(), + source: formatdoc!( + " + MyObject::MyObject(QObject* parent) + : BaseClass(parent) + , m_rustObj(rust::createRs()) + {{ + }} + " + ), + }] + ); + } + + #[test] + fn test_single_constructor() { + let blocks = generate( + &qobject_for_testing(), + &[Constructor { + arguments: vec![parse_quote! { i32 }, parse_quote! { *mut QObject }], + new_arguments: vec![], + base_arguments: vec![], + initialize_arguments: vec![], + // dummy + imp: parse_quote! { + impl X {} + }, + }], + &[], + &ParsedCxxMappings::default(), + ) + .unwrap(); + + assert!(blocks.members.is_empty()); + assert!(blocks.forward_declares.is_empty()); + assert!(blocks.deconstructors.is_empty()); + assert!(blocks.metaobjects.is_empty()); + assert_eq!( + blocks.private_methods, + vec![CppFragment::Pair { + header: "explicit MyObject(rust::CxxQtConstructorArguments0&& args);".to_string(), + source: formatdoc!( + " + MyObject::MyObject(rust::CxxQtConstructorArguments0&& args) + : BaseClass() + , m_rustObj(rust::newRs0(std::move(args.newArguments))) + {{ + rust::initialize0(*this, std::move(args.initializeArguments)); + }} + " + ), + }] + ); + assert_eq!( + blocks.methods, + vec![CppFragment::Pair { + header: "explicit MyObject(::std::int32_t arg0, QObject* arg1);".to_string(), + source: formatdoc!( + " + MyObject::MyObject(::std::int32_t arg0, QObject* arg1) + : MyObject(rust::routeArguments0(std::move(arg0), std::move(arg1))) + {{ }} + " + ), + }] + ); + } +} diff --git a/crates/cxx-qt-gen/src/generator/cpp/fragment.rs b/crates/cxx-qt-gen/src/generator/cpp/fragment.rs index c881b9c65..09c62fa6f 100644 --- a/crates/cxx-qt-gen/src/generator/cpp/fragment.rs +++ b/crates/cxx-qt-gen/src/generator/cpp/fragment.rs @@ -5,6 +5,7 @@ use crate::generator::cpp::types::CppType; +#[derive(PartialEq, Eq, Debug)] pub enum CppFragment { Pair { header: String, source: String }, Header(String), diff --git a/crates/cxx-qt-gen/src/generator/rust/constructor.rs b/crates/cxx-qt-gen/src/generator/rust/constructor.rs index d2dde2468..7cbf8564c 100644 --- a/crates/cxx-qt-gen/src/generator/rust/constructor.rs +++ b/crates/cxx-qt-gen/src/generator/rust/constructor.rs @@ -21,16 +21,11 @@ const BASE_ARGUMENTS: &str = "CxxQtConstructorBaseArguments"; const NEW_ARGUMENTS: &str = "CxxQtConstructorNewArguments"; const INITIALIZE_ARGUMENTS: &str = "CxxQtConstructorInitializeArguments"; -fn map_types TokenStream>( - args: &Option>, - f: F, -) -> Vec { - args.as_ref() - .map(|args| args.iter().enumerate().map(f).collect()) - .unwrap_or_default() +fn map_types TokenStream>(args: &[Type], f: F) -> Vec { + args.iter().enumerate().map(f).collect() } -fn extract_arguments_from_tuple(args: &Option>, tuple_name: Ident) -> Vec { +fn extract_arguments_from_tuple(args: &[Type], tuple_name: Ident) -> Vec { map_types(args, |(index, _ty)| { let arg_name = format_ident!("arg{index}"); let index = syn::LitInt::new(index.to_string().as_str(), Span::call_site()); @@ -40,7 +35,7 @@ fn extract_arguments_from_tuple(args: &Option>, tuple_name: Ident) -> }) } -fn extract_arguments_from_struct(args: &Option>, struct_name: Ident) -> Vec { +fn extract_arguments_from_struct(args: &[Type], struct_name: Ident) -> Vec { map_types(args, |(index, _ty)| { let arg_name = format_ident!("arg{index}"); quote! { @@ -49,7 +44,7 @@ fn extract_arguments_from_struct(args: &Option>, struct_name: Ident) - }) } -fn argument_members(args: &Option>) -> Vec { +fn argument_members(args: &[Type]) -> Vec { map_types(args, |(index, ty)| { let arg_name = format_ident!("arg{index}"); quote! { @@ -89,13 +84,13 @@ fn generate_default_constructor( fn generate_arguments_struct( namespace_internals: &str, struct_name: &CombinedIdent, - argument_list: &Option>, + argument_list: &[Type], ) -> Item { let argument_members = argument_members(argument_list); - let not_empty = if argument_list.as_ref().is_some_and(|list| !list.is_empty()) { - None - } else { + let not_empty = if argument_list.is_empty() { Some(quote! { not_empty: i8 }) + } else { + None }; let rust_name = &struct_name.rust; // use to_string here, as the cxx_name needs to be in quotes for the attribute macro. @@ -115,14 +110,14 @@ fn generate_arguments_struct( fn generate_arguments_initialization( struct_name: &Ident, instance_name: Ident, - argument_list: &Option>, + argument_list: &[Type], ) -> Expr { let init_arguments = extract_arguments_from_tuple(argument_list, instance_name); println!("init_arguments: {:?}", init_arguments); - let not_empty = if argument_list.as_ref().is_some_and(|list| !list.is_empty()) { - None - } else { + let not_empty = if argument_list.is_empty() { Some(quote! { not_empty: 0 }) + } else { + None }; parse_quote! { diff --git a/crates/cxx-qt-gen/src/parser/constructor.rs b/crates/cxx-qt-gen/src/parser/constructor.rs index 7c9f92293..833fd942a 100644 --- a/crates/cxx-qt-gen/src/parser/constructor.rs +++ b/crates/cxx-qt-gen/src/parser/constructor.rs @@ -26,12 +26,12 @@ pub struct Constructor { /// Arguments to the new function. /// The `new` function needs to return the inner Rust struct for the QObject. - pub new_arguments: Option>, + pub new_arguments: Vec, /// Arguments to be passed to the base class constructor. - pub base_arguments: Option>, + pub base_arguments: Vec, /// Arguments to the initialize function. /// The `initialize` function is run after the QObject is created. - pub initialize_arguments: Option>, + pub initialize_arguments: Vec, /// The original impl that this constructor was parse from. pub imp: ItemImpl, @@ -150,10 +150,89 @@ impl Constructor { let (argument_list, arguments) = Self::parse_arguments(trait_path)?; Ok(Constructor { arguments: argument_list, - new_arguments: arguments.new, - base_arguments: arguments.base, - initialize_arguments: arguments.initialize, + new_arguments: arguments.new.unwrap_or_default(), + base_arguments: arguments.base.unwrap_or_default(), + initialize_arguments: arguments.initialize.unwrap_or_default(), imp, }) } } + +#[cfg(test)] +mod tests { + use syn::parse_quote; + + use super::*; + + fn assert_parse_error(item: ItemImpl) { + assert!(Constructor::parse(item).is_err()); + } + + #[test] + fn test_parse_errors() { + assert_parse_error(parse_quote! { + impl cxx_qt::Constructor for X {} + }); + assert_parse_error(parse_quote! { + impl cxx_qt::Constructor<()> for X { + fn some_impl() {} + } + }); + assert_parse_error(parse_quote! { + impl cxx_qt::Constructor<()> for T {} + }); + assert_parse_error(parse_quote! { + impl<'a> cxx_qt::Constructor<()> for T {} + }); + assert_parse_error(parse_quote! { + impl cxx_qt::Constructor for X {} + }); + assert_parse_error(parse_quote! { + impl cxx_qt::Constructor<(), UnknownArguments=()> for X {} + }); + assert_parse_error(parse_quote! { + impl cxx_qt::Constructor<(), NewArguments=(), NewArguments=()> for X {} + }); + // Not a tuple, missing `,` + assert_parse_error(parse_quote! { + impl cxx_qt::Constructor<(i32)> for X {} + }); + } + + #[test] + fn test_parse_default_arguments() { + let constructor = Constructor::parse(parse_quote! { + impl cxx_qt::Constructor<(i32, QString)> for X {} + }) + .unwrap(); + + assert_eq!( + constructor.arguments, + vec![parse_quote!(i32), parse_quote!(QString)] + ); + assert!(constructor.new_arguments.is_empty()); + assert!(constructor.base_arguments.is_empty()); + assert!(constructor.initialize_arguments.is_empty()); + } + + #[test] + fn test_parse_associated_types() { + let constructor = Constructor::parse(parse_quote! { + impl cxx_qt::Constructor< + (i32,), + NewArguments=(i8,QString), + InitializeArguments=(), + BaseArguments=(i64,) + > for X {} + }) + .unwrap(); + + assert_eq!(constructor.arguments, vec![parse_quote!(i32)]); + assert_eq!( + constructor.new_arguments, + vec![parse_quote!(i8), parse_quote!(QString)] + ); + assert!(constructor.initialize_arguments.is_empty()); + assert_eq!(constructor.base_arguments, vec![parse_quote!(i64)]); + } +} diff --git a/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.cpp b/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.cpp index 27a5986f6..405acf3e6 100644 --- a/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.cpp +++ b/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.cpp @@ -140,7 +140,6 @@ SecondObject::readyConnect(::rust::Fn func, SecondObject::SecondObject(QObject* parent) : QObject(parent) , m_rustObj(cxx_qt::multi_object::cxx_qt_second_object::createRs()) - { }