diff --git a/crates/cxx-qt-gen/src/generator/cpp/constructor.rs b/crates/cxx-qt-gen/src/generator/cpp/constructor.rs index 41dab904a..17b188099 100644 --- a/crates/cxx-qt-gen/src/generator/cpp/constructor.rs +++ b/crates/cxx-qt-gen/src/generator/cpp/constructor.rs @@ -81,7 +81,7 @@ fn expand_arguments(arguments: &[Type], type_names: &TypeNames) -> Result Option { } pub fn generate( - constructors: &[Constructor], + constructors: &[&Constructor], qobject_idents: &QObjectNames, namespace: &NamespaceName, type_names: &TypeNames, @@ -463,7 +463,7 @@ mod tests { NamespaceName::from_namespace_and_ident("qobject", &format_ident!("MyObject")) } - fn generate_mocked(constructors: &[Constructor]) -> GeneratedRustFragment { + fn generate_mocked(constructors: &[&Constructor]) -> GeneratedRustFragment { let mut type_names = TypeNames::mock(); type_names.mock_insert("QString", None, None, None); @@ -768,8 +768,8 @@ mod tests { #[test] fn multiple_constructors() { let blocks = generate_mocked(&[ - mock_constructor(), - Constructor { + &mock_constructor(), + &Constructor { arguments: vec![parse_quote! { *const QObject }], new_arguments: vec![parse_quote! { i16 }], initialize_arguments: vec![ @@ -801,7 +801,7 @@ mod tests { #[test] fn constructor_impl_with_unused_lifetime() { let result = super::generate( - &[Constructor { + &[&Constructor { lifetime: Some(parse_quote! { 'a }), ..mock_constructor() }], diff --git a/crates/cxx-qt-gen/src/generator/rust/qobject.rs b/crates/cxx-qt-gen/src/generator/rust/qobject.rs index 41b432cf4..11902d232 100644 --- a/crates/cxx-qt-gen/src/generator/rust/qobject.rs +++ b/crates/cxx-qt-gen/src/generator/rust/qobject.rs @@ -81,7 +81,7 @@ impl GeneratedRustFragment { } // If this type has threading enabled then add generation - if qobject.threading { + if structured_qobject.threading { generated.append(&mut threading::generate( &qobject_idents, &namespace_idents, @@ -94,7 +94,7 @@ impl GeneratedRustFragment { // // This could be implemented using an auto trait in the future once stable // https://doc.rust-lang.org/beta/unstable-book/language-features/auto-traits.html - if qobject.locking { + if structured_qobject.locking { let qualified_impl = type_names.rust_qualified(qobject_idents.name.rust_unqualified())?; generated.cxx_qt_mod_contents.push(syn::parse_quote! { @@ -103,7 +103,7 @@ impl GeneratedRustFragment { } generated.append(&mut constructor::generate( - &qobject.constructors, + &structured_qobject.constructors, &qobject_idents, &namespace_idents, type_names, diff --git a/crates/cxx-qt-gen/src/generator/structuring/mod.rs b/crates/cxx-qt-gen/src/generator/structuring/mod.rs index 1845b2408..8f53681a4 100644 --- a/crates/cxx-qt-gen/src/generator/structuring/mod.rs +++ b/crates/cxx-qt-gen/src/generator/structuring/mod.rs @@ -14,9 +14,12 @@ /// All resulting structures are listed in the `Structures` struct. pub mod qobject; -use crate::parser::cxxqtdata::ParsedCxxQtData; +use crate::parser::{ + cxxqtdata::ParsedCxxQtData, + trait_impl::{TraitImpl, TraitKind}, +}; pub use qobject::StructuredQObject; -use syn::{Error, Result}; +use syn::{Error, Ident, Result}; /// The list of all structures that could be associated from the parsed data. /// Most importantly, this includes the list of qobjects. @@ -25,72 +28,99 @@ pub struct Structures<'a> { pub qobjects: Vec>, } +fn find_qobject<'a, 'b>( + qobjects: &'b mut [StructuredQObject<'a>], + ident: &Ident, +) -> Result<&'b mut StructuredQObject<'a>> { + qobjects + .iter_mut() + .find(|qobject| qobject.has_qobject_name(ident)) + .ok_or_else(|| Error::new_spanned(ident, format!("Unknown QObject: {ident}"))) +} + impl<'a> Structures<'a> { + fn structure_trait_impls( + qobjects: &mut [StructuredQObject<'a>], + trait_impls: &'a [TraitImpl], + ) -> Result<()> { + // Associate each trait impl with its appropriate qobject + for imp in trait_impls { + let qobject = find_qobject(qobjects, &imp.qobject)?; + match imp.kind { + TraitKind::Threading => { + if qobject.threading { + return Err(Error::new_spanned( + &imp.declaration, + format!( + "Threading already enabled on QObject {qobject}!", + qobject = imp.qobject + ), + )); + } + qobject.threading = true; + } + TraitKind::DisableLocking => { + if !qobject.locking { + return Err(Error::new_spanned( + &imp.declaration, + format!( + "Locking already disabled on QObject {qobject}!", + qobject = imp.qobject + ), + )); + } + qobject.locking = false; + } + // TODO: Check for duplicate declarations? + TraitKind::Constructor(ref constructor) => qobject.constructors.push(constructor), + } + + if !qobject.locking && qobject.threading { + return Err(Error::new_spanned( + &imp.declaration, + "QObject cannot have cxx_qt::Threading enabled and cxx_qt::Locking disabled!", + )); + } + } + Ok(()) + } + /// Create a new `Structures` object from the given `ParsedCxxQtData` /// Returns an error, if any references could not be resolved. pub fn new(cxxqtdata: &'a ParsedCxxQtData) -> Result { let mut qobjects: Vec<_> = cxxqtdata .qobjects - .values() + .iter() .map(StructuredQObject::from_qobject) .collect(); for qenum in &cxxqtdata.qenums { if let Some(qobject_ident) = &qenum.qobject { - let qobject = qobjects - .iter_mut() - .find(|qobject| qobject.has_qobject_name(qobject_ident)) - .ok_or_else(|| { - Error::new_spanned( - qobject_ident, - format!("Unknown QObject: {qobject_ident}"), - ) - })?; + let qobject = find_qobject(&mut qobjects, 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), - ) - })?; + let qobject = find_qobject(&mut qobjects, &method.qobject_ident)?; qobject.methods.push(method); } // Associate each inherited method parsed with its appropriate qobject for inherited_method in &cxxqtdata.inherited_methods { - let qobject = qobjects - .iter_mut() - .find(|qobject| qobject.has_qobject_name(&inherited_method.qobject_ident)) - .ok_or_else(|| { - Error::new_spanned( - &inherited_method.qobject_ident, - format!("Unknown QObject: {:?}", &inherited_method.qobject_ident), - ) - })?; + let qobject = find_qobject(&mut qobjects, &inherited_method.qobject_ident)?; qobject.inherited_methods.push(inherited_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), - ) - })?; + let qobject = find_qobject(&mut qobjects, &signal.qobject_ident)?; qobject.signals.push(signal); } + + Self::structure_trait_impls(&mut qobjects, &cxxqtdata.trait_impls)?; + Ok(Structures { qobjects }) } } @@ -104,7 +134,7 @@ mod tests { #[test] fn test_structuring_unknown_qobject() { - let module: ItemMod = parse_quote! { + let module = parse_quote! { #[cxx_qt::bridge] mod ffi { extern "RustQt" { @@ -126,7 +156,7 @@ mod tests { #[test] fn test_module_invalid_qobject_qenum() { - let module: ItemMod = parse_quote! { + let module = parse_quote! { #[cxx_qt::bridge] mod ffi { #[qenum(MyObject)] @@ -136,13 +166,13 @@ mod tests { } }; - let parser = Parser::from(module.clone()).unwrap(); + let parser = Parser::from(module).unwrap(); assert!(Structures::new(&parser.cxx_qt_data).is_err()); } #[test] fn test_module_invalid_qobject_method() { - let module: ItemMod = parse_quote! { + let module = parse_quote! { #[cxx_qt::bridge] mod ffi { unsafe extern "RustQt" { @@ -152,13 +182,13 @@ mod tests { } }; - let parser = Parser::from(module.clone()).unwrap(); + let parser = Parser::from(module).unwrap(); assert!(Structures::new(&parser.cxx_qt_data).is_err()); } #[test] fn test_module_invalid_qobject_signal() { - let module: ItemMod = parse_quote! { + let module = parse_quote! { #[cxx_qt::bridge] mod ffi { unsafe extern "RustQt" { @@ -168,13 +198,13 @@ mod tests { } }; - let parser = Parser::from(module.clone()).unwrap(); + let parser = Parser::from(module).unwrap(); assert!(Structures::new(&parser.cxx_qt_data).is_err()); } #[test] fn test_module_invalid_qobject_inherited() { - let module: ItemMod = parse_quote! { + let module = parse_quote! { #[cxx_qt::bridge] mod ffi { unsafe extern "RustQt" { @@ -184,13 +214,13 @@ mod tests { } }; - let parser = Parser::from(module.clone()).unwrap(); + let parser = Parser::from(module).unwrap(); assert!(Structures::new(&parser.cxx_qt_data).is_err()); } #[test] fn test_invalid_lookup() { - let module: ItemMod = parse_quote! { + let module = parse_quote! { #[cxx_qt::bridge] mod ffi { extern "RustQt" { @@ -200,7 +230,7 @@ mod tests { } }; - let parser = Parser::from(module.clone()).unwrap(); + let parser = Parser::from(module).unwrap(); let structures = Structures::new(&parser.cxx_qt_data).unwrap(); let qobject = structures.qobjects.first().unwrap(); @@ -212,7 +242,7 @@ mod tests { #[test] fn test_structures() { - let module: ItemMod = parse_quote! { + let module = parse_quote! { #[cxx_qt::bridge] mod ffi { extern "RustQt" { @@ -239,7 +269,7 @@ mod tests { } }; - let parser = Parser::from(module.clone()).unwrap(); + let parser = Parser::from(module).unwrap(); let structures = Structures::new(&parser.cxx_qt_data).unwrap(); assert_eq!(structures.qobjects.len(), 2); @@ -279,4 +309,46 @@ mod tests { format_ident!("ready") ); } + + fn mock_bridge() -> ItemMod { + parse_quote! { + #[cxx_qt::bridge] + mod ffi { + extern "RustQt" { + #[qobject] + type MyObject = super::MyObjectRust; + } + } + } + } + + fn assert_structuring_error(additional_items: impl IntoIterator) { + let mut bridge = mock_bridge(); + bridge.content.as_mut().unwrap().1.extend(additional_items); + let parser = Parser::from(bridge).unwrap(); + assert!(Structures::new(&parser.cxx_qt_data).is_err()); + } + + #[test] + fn test_incompatible_trait_impl() { + assert_structuring_error([ + parse_quote! {impl cxx_qt::Threading for MyObject {}}, + parse_quote! {impl cxx_qt::Threading for MyObject {}}, + ]); + + assert_structuring_error([ + parse_quote! {unsafe impl !cxx_qt::Locking for MyObject {}}, + parse_quote! {unsafe impl !cxx_qt::Locking for MyObject {}}, + ]); + + assert_structuring_error([ + parse_quote! {unsafe impl !cxx_qt::Locking for MyObject {}}, + parse_quote! {impl cxx_qt::Threading for MyObject {}}, + ]); + + assert_structuring_error([ + parse_quote! {impl cxx_qt::Threading for MyObject {}}, + parse_quote! {unsafe impl !cxx_qt::Locking for MyObject {}}, + ]); + } } diff --git a/crates/cxx-qt-gen/src/generator/structuring/qobject.rs b/crates/cxx-qt-gen/src/generator/structuring/qobject.rs index 5436263f3..791c05187 100644 --- a/crates/cxx-qt-gen/src/generator/structuring/qobject.rs +++ b/crates/cxx-qt-gen/src/generator/structuring/qobject.rs @@ -4,6 +4,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 use crate::naming::Name; +use crate::parser::constructor::Constructor; use crate::parser::inherit::ParsedInheritedMethod; use crate::parser::method::ParsedMethod; use crate::parser::signals::ParsedSignal; @@ -19,6 +20,9 @@ pub struct StructuredQObject<'a> { pub methods: Vec<&'a ParsedMethod>, pub inherited_methods: Vec<&'a ParsedInheritedMethod>, pub signals: Vec<&'a ParsedSignal>, + pub constructors: Vec<&'a Constructor>, + pub locking: bool, + pub threading: bool, } impl<'a> StructuredQObject<'a> { @@ -34,6 +38,9 @@ impl<'a> StructuredQObject<'a> { methods: vec![], inherited_methods: vec![], signals: vec![], + constructors: vec![], + locking: true, + threading: false, } } diff --git a/crates/cxx-qt-gen/src/lib.rs b/crates/cxx-qt-gen/src/lib.rs index 14e4b912e..2a3454f31 100644 --- a/crates/cxx-qt-gen/src/lib.rs +++ b/crates/cxx-qt-gen/src/lib.rs @@ -63,6 +63,13 @@ mod tests { } } + macro_rules! assert_parse_errors { + { $type:ident: $($input:tt)* } => { + $(assert!($type::parse(syn::parse_quote! $input).is_err());)* + } + } + pub(crate) use assert_parse_errors; + /// Helper for formating C++ code pub(crate) fn format_cpp(cpp_code: &str) -> String { clang_format_with_style(cpp_code, &ClangFormatStyle::File).unwrap() diff --git a/crates/cxx-qt-gen/src/naming/type_names.rs b/crates/cxx-qt-gen/src/naming/type_names.rs index d821e881d..77895995d 100644 --- a/crates/cxx-qt-gen/src/naming/type_names.rs +++ b/crates/cxx-qt-gen/src/naming/type_names.rs @@ -111,7 +111,7 @@ impl Default for TypeNames { } impl TypeNames { - /// Part of the The "Naming" phase. + /// Part of the "Naming" phase. /// Extract all nameable types from the CXX-Qt data and the CXX items. /// /// This allows the generator to fully-qualify all types in the generated code. @@ -159,7 +159,7 @@ impl TypeNames { bridge_namespace: Option<&str>, module_ident: &Ident, ) -> Result<()> { - for qobject in cxx_qt_data.qobjects.values() { + for qobject in cxx_qt_data.qobjects.iter() { self.populate_qobject(qobject)?; } @@ -294,7 +294,7 @@ impl TypeNames { /// For a given rust ident return the CXX name with its namespace /// /// Ideally we'd want this type name to always be **fully** qualified, starting with `::`. - /// Unfortunately, this isn't always possible, as the Qt5 meta object system doesn't register + /// Unfortunately, this isn't always possible, as the Qt5 metaobject system doesn't register /// types with the fully qualified path :( /// E.g. it will recognize `QString`, but not `::QString` from QML. /// @@ -315,7 +315,7 @@ impl TypeNames { self.lookup(ident).map(|name| name.namespace.clone()) } - /// Return a qualified version of the ident that can by used to refer to the type T outside of a CXX bridge + /// Return a qualified version of the ident that can be used to refer to the type T outside a CXX bridge /// /// Eg MyObject -> ffi::MyObject /// diff --git a/crates/cxx-qt-gen/src/parser/constructor.rs b/crates/cxx-qt-gen/src/parser/constructor.rs index 185221e90..d7e64c920 100644 --- a/crates/cxx-qt-gen/src/parser/constructor.rs +++ b/crates/cxx-qt-gen/src/parser/constructor.rs @@ -20,6 +20,7 @@ struct ConstructorArguments { } /// A parsed cxx_qt::Constructor trait impl. +#[derive(Debug, PartialEq, Eq)] pub struct Constructor { /// The arguments to the constructor defined by this trait impl. pub arguments: Vec, @@ -37,6 +38,7 @@ pub struct Constructor { pub lifetime: Option, /// The original impl that this constructor was parse from. + // TODO: This has moved into MarkerTrait pub imp: ItemImpl, } @@ -150,20 +152,20 @@ impl Constructor { )); } - if !imp.items.is_empty() { + let (not, trait_path, _) = &imp + .trait_ + .as_ref() + .ok_or_else(|| Error::new_spanned(imp.clone(), "Expected trait impl!"))?; + + if not.is_some() { return Err(Error::new_spanned( - imp.items.first(), - "cxx_qt::Constructor must only be declared, not implemented inside cxx_qt::bridge!", + trait_path, + "Negative impls for cxx_qt::Constructor are not allowed", )); } let lifetime = Self::parse_impl_generics(&imp.generics)?; - let (_, trait_path, _) = &imp - .trait_ - .as_ref() - .ok_or_else(|| Error::new_spanned(imp.clone(), "Expected trait impl!"))?; - let (argument_list, arguments) = Self::parse_arguments(trait_path)?; Ok(Constructor { arguments: argument_list, @@ -207,13 +209,12 @@ mod tests { "missing main argument list", ); + // Hard to tell if this actually hits the error as the rest of the project isn't compiling assert_parse_error( parse_quote! { - impl cxx_qt::Constructor<()> for X { - fn some_impl() {} - } + impl !cxx_qt::Constructor<(i32, i32)> for T {} }, - "item in impl block", + "Negative impls for cxx_qt::Constructor are not allowed", ); assert_parse_error( diff --git a/crates/cxx-qt-gen/src/parser/cxxqtdata.rs b/crates/cxx-qt-gen/src/parser/cxxqtdata.rs index c0827bcab..14936ea3d 100644 --- a/crates/cxx-qt-gen/src/parser/cxxqtdata.rs +++ b/crates/cxx-qt-gen/src/parser/cxxqtdata.rs @@ -14,21 +14,20 @@ use crate::{ }, syntax::expr::expr_to_string, }; -use std::collections::BTreeMap; -use syn::{ - spanned::Spanned, Error, ForeignItem, Ident, Item, ItemEnum, ItemForeignMod, ItemImpl, Result, - Type, TypePath, -}; +use syn::{Error, ForeignItem, Ident, Item, ItemEnum, ItemForeignMod, ItemImpl, Result}; use syn::{ItemMacro, Meta}; use super::qnamespace::ParsedQNamespace; +use super::trait_impl::TraitImpl; + +// Test comment pub struct ParsedCxxQtData { /// Map of the QObjects defined in the module that will be used for code generation // // 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, + pub qobjects: Vec, /// List of QEnums defined in the module, that aren't associated with a QObject pub qenums: Vec, /// List of methods and Q_INVOKABLES found @@ -43,6 +42,8 @@ pub struct ParsedCxxQtData { pub extern_cxxqt_blocks: Vec, /// The namespace of the CXX-Qt module pub namespace: Option, + /// All trait implementations found + pub trait_impls: Vec, /// The ident of the module, used for mappings pub module_ident: Ident, } @@ -51,95 +52,19 @@ impl ParsedCxxQtData { /// Create a ParsedCxxQtData from a given module and namespace pub fn new(module_ident: Ident, namespace: Option) -> Self { Self { - qobjects: BTreeMap::::default(), + qobjects: Vec::new(), qenums: vec![], methods: vec![], signals: vec![], inherited_methods: vec![], qnamespaces: vec![], + trait_impls: vec![], extern_cxxqt_blocks: Vec::::default(), module_ident, namespace, } } - /// Find the QObjects within the module and add into the qobjects BTreeMap - pub fn find_qobject_types(&mut self, items: &[Item]) -> Result<()> { - for item in items { - if let Item::ForeignMod(foreign_mod) = item { - if foreign_mod.abi.name.as_ref().map(|lit_str| lit_str.value()) - == Some("RustQt".to_string()) - { - // Find the namespace on the foreign mod block if there is one - let namespace = attribute_find_path(&foreign_mod.attrs, &["namespace"]) - .map(|index| { - expr_to_string( - &foreign_mod.attrs[index].meta.require_name_value()?.value, - ) - }) - .transpose()? - .or_else(|| self.namespace.clone()); - - for foreign_item in &foreign_mod.items { - match foreign_item { - // Fn are parsed later in parse_foreign_mod_rust_qt - ForeignItem::Fn(_) => {} - ForeignItem::Verbatim(tokens) => { - let mut foreign_alias: ForeignTypeIdentAlias = - syn::parse2(tokens.clone())?; - - // Check this type is tagged with a #[qobject] - let has_qobject_macro = - attribute_take_path(&mut foreign_alias.attrs, &["qobject"]) - .is_some(); - - // Load the QObject - let mut qobject = ParsedQObject::parse( - foreign_alias, - namespace.as_deref(), - &self.module_ident, - )?; - qobject.has_qobject_macro = has_qobject_macro; - - // Ensure that the base class attribute is not empty, as this is not valid in both cases - // - when there is a qobject macro it is not valid - // - when there is not a qobject macro it is not valid - if qobject - .base_class - .as_ref() - .is_some_and(|base| base.is_empty()) - { - return Err(Error::new( - foreign_item.span(), - "The #[base] attribute cannot be empty", // TODO: unreachable because of a require_name_value so the error for empty base is there - )); - } - - // Ensure that if there is no qobject macro that a base class is specificed - // - // Note this assumes the check above - if !qobject.has_qobject_macro && qobject.base_class.is_none() { - return Err(Error::new(foreign_item.span(), "A type without a #[qobject] attribute must specify a #[base] attribute")); - } - - // Note that we assume a compiler error will occur later - // if you had two structs with the same name - self.qobjects - .insert(qobject.name.rust_unqualified().clone(), qobject); - } - // Const Macro, Type are unsupported in extern "RustQt" for now - _others => { - return Err(Error::new(foreign_item.span(), "Unsupported item")) - } - } - } - } - } - } - - Ok(()) - } - /// Determine if the given [syn::Item] is a CXX-Qt related item /// If it is then add the [syn::Item] into qobjects BTreeMap /// Otherwise return the [syn::Item] to pass through to CXX @@ -203,6 +128,11 @@ impl ParsedCxxQtData { } fn parse_foreign_mod_rust_qt(&mut self, mut foreign_mod: ItemForeignMod) -> Result<()> { + let namespace = attribute_find_path(&foreign_mod.attrs, &["namespace"]) + .map(|index| expr_to_string(&foreign_mod.attrs[index].meta.require_name_value()?.value)) + .transpose()? + .or_else(|| self.namespace.clone()); + let safe_call = if foreign_mod.unsafety.is_some() { Safety::Safe } else { @@ -210,25 +140,43 @@ impl ParsedCxxQtData { }; for item in foreign_mod.items.drain(..) { - 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)?; - self.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 - } else if attribute_take_path(&mut foreign_fn.attrs, &["inherit"]).is_some() { - let parsed_inherited_method = - ParsedInheritedMethod::parse(foreign_fn, safe_call)?; - - self.inherited_methods.push(parsed_inherited_method); - // Remaining methods are either C++ methods or invokables - } else { - let parsed_method = ParsedMethod::parse(foreign_fn, safe_call)?; - self.methods.push(parsed_method); + match item { + ForeignItem::Fn(mut foreign_fn) => { + // 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)?; + self.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 + } else if attribute_take_path(&mut foreign_fn.attrs, &["inherit"]).is_some() { + let parsed_inherited_method = + ParsedInheritedMethod::parse(foreign_fn, safe_call)?; + + self.inherited_methods.push(parsed_inherited_method); + // Remaining methods are either C++ methods or invokables + } else { + let parsed_method = ParsedMethod::parse(foreign_fn, safe_call)?; + self.methods.push(parsed_method); + } } + ForeignItem::Verbatim(tokens) => { + let foreign_alias: ForeignTypeIdentAlias = syn::parse2(tokens.clone())?; + + // Load the QObject + let qobject = ParsedQObject::parse( + foreign_alias, + namespace.as_deref(), + &self.module_ident, + )?; + + // Note that we assume a compiler error will occur later + // if you had two structs with the same name + self.qobjects.push(qobject); + } + // Const Macro, Type are unsupported in extern "RustQt" for now + _ => return Err(Error::new_spanned(item, "Unsupported item")), } } Ok(()) @@ -237,23 +185,21 @@ impl ParsedCxxQtData { /// Parse a [syn::ItemImpl] into the qobjects if it's a CXX-Qt implementation /// otherwise return as a [syn::Item] to pass through. fn parse_impl(&mut self, imp: ItemImpl) -> Result> { - // If the implementation has a T - // then this is the block of methods to be implemented on the C++ object - if let Type::Path(TypePath { path, .. }) = imp.self_ty.as_ref() { - // If this path is an ident then try to match to a QObject - if let Some(ident) = path.get_ident() { - // Find if we are an impl block for a qobject - if let Some(qobject) = self.qobjects.get_mut(ident) { - // If we are a trait then process it otherwise add to others - if imp.trait_.is_some() { - qobject.parse_trait_impl(imp)?; - return Ok(None); - } - } - } + // If it is a trait impl compared to a regular impl block + // This allows the cxx shim trait feature + if imp.trait_.is_some() { + self.trait_impls.push(TraitImpl::parse(imp)?); + Ok(None) + } else { + Ok(Some(Item::Impl(imp))) } + } - Ok(Some(Item::Impl(imp))) + #[cfg(test)] + fn find_object(&self, id: &Ident) -> Option<&ParsedQObject> { + self.qobjects + .iter() + .find(|obj| obj.name.rust_unqualified() == id) } } @@ -264,164 +210,13 @@ mod tests { use crate::generator::structuring::Structures; use crate::{naming::Name, parser::qobject::tests::create_parsed_qobject}; use quote::format_ident; - use syn::{parse_quote, ItemMod}; - - /// The QObject ident used in these tests as the ident that already - /// has been found. - fn qobject_ident() -> Ident { - format_ident!("MyObject") - } + use syn::parse_quote; /// Creates a ParsedCxxQtData with a QObject definition already found pub fn create_parsed_cxx_qt_data() -> ParsedCxxQtData { let mut cxx_qt_data = ParsedCxxQtData::new(format_ident!("ffi"), None); + cxx_qt_data.qobjects.push(create_parsed_qobject()); cxx_qt_data - .qobjects - .insert(qobject_ident(), create_parsed_qobject()); - cxx_qt_data - } - - #[test] - fn test_find_qobjects_one_qobject() { - let mut cxx_qt_data = ParsedCxxQtData::new(format_ident!("ffi"), None); - - let module: ItemMod = parse_quote! { - mod module { - extern "RustQt" { - #[qobject] - type MyObject = super::MyObjectRust; - } - } - }; - let result = cxx_qt_data.find_qobject_types(&module.content.unwrap().1); - assert!(result.is_ok()); - assert_eq!(cxx_qt_data.qobjects.len(), 1); - assert!(cxx_qt_data.qobjects.contains_key(&qobject_ident())); - assert!( - cxx_qt_data - .qobjects - .get(&qobject_ident()) - .unwrap() - .has_qobject_macro - ); - } - - #[test] - fn test_find_qobjects_multiple_qobject() { - let mut cxx_qt_data = ParsedCxxQtData::new(format_ident!("ffi"), None); - - let module: ItemMod = parse_quote! { - mod module { - extern "RustQt" { - #[qobject] - type MyObject = super::MyObjectRust; - #[qobject] - type SecondObject = super::SecondObjectRust; - #[qobject] - #[rust_name="ThirdObjectQt"] - type ThirdObject = super::ThirdObjectRust; - } - } - }; - let result = cxx_qt_data.find_qobject_types(&module.content.unwrap().1); - assert!(result.is_ok()); - let qobjects = &cxx_qt_data.qobjects; - assert_eq!(qobjects.len(), 3); - assert!(qobjects.contains_key(&qobject_ident())); - assert!(qobjects.contains_key(&format_ident!("SecondObject"))); - // Ensure the rust_name attribute is used as the key. - assert!(qobjects.contains_key(&format_ident!("ThirdObjectQt"))); - } - - #[test] - fn test_find_qobjects_namespace() { - let mut cxx_qt_data = - ParsedCxxQtData::new(format_ident!("ffi"), Some("bridge_namespace".to_string())); - - let module: ItemMod = parse_quote! { - mod module { - extern "RustQt" { - #[qobject] - #[namespace = "qobject_namespace"] - type MyObject = super::MyObjectRust; - #[qobject] - type SecondObject = super::SecondObjectRust; - } - } - }; - cxx_qt_data - .find_qobject_types(&module.content.unwrap().1) - .unwrap(); - assert_eq!(cxx_qt_data.qobjects.len(), 2); - assert_eq!( - cxx_qt_data - .qobjects - .get(&format_ident!("MyObject")) - .unwrap() - .name - .namespace() - .unwrap(), - "qobject_namespace" - ); - assert_eq!( - cxx_qt_data - .qobjects - .get(&format_ident!("SecondObject")) - .unwrap() - .name - .namespace() - .unwrap(), - "bridge_namespace" - ); - } - - #[test] - fn test_find_qobjects_no_qobject_no_base() { - let mut cxx_qt_data = ParsedCxxQtData::new(format_ident!("ffi"), None); - - let module: ItemMod = parse_quote! { - mod module { - extern "RustQt" { - type Other = super::OtherRust; - type MyObject = super::MyObjectRust; - } - } - }; - let result = cxx_qt_data.find_qobject_types(&module.content.unwrap().1); - assert!(result.is_err()); - } - - #[test] - fn test_find_qobjects_no_qobject_with_base() { - let mut cxx_qt_data = ParsedCxxQtData::new(format_ident!("ffi"), None); - - let module: ItemMod = parse_quote! { - mod module { - extern "RustQt" { - #[base = "OtherBase"] - type Other = super::OtherRust; - #[base = "MyObjectBase"] - type MyObject = super::MyObjectRust; - } - } - }; - let result = cxx_qt_data.find_qobject_types(&module.content.unwrap().1); - assert!(result.is_ok()); - assert_eq!(cxx_qt_data.qobjects.len(), 2); - assert!( - !cxx_qt_data - .qobjects - .get(&format_ident!("Other")) - .unwrap() - .has_qobject_macro - ); - assert!( - !cxx_qt_data - .qobjects - .get(&format_ident!("MyObject")) - .unwrap() - .has_qobject_macro - ); } #[test] @@ -584,19 +379,6 @@ mod tests { assert!(result.is_some()); } - #[test] - fn test_find_and_merge_cxx_qt_item_threading() { - let mut cxx_qt_data = create_parsed_cxx_qt_data(); - assert!(!cxx_qt_data.qobjects[&qobject_ident()].threading); - - let item: Item = parse_quote! { - impl cxx_qt::Threading for MyObject {} - }; - let result = cxx_qt_data.parse_cxx_qt_item(item).unwrap(); - assert!(result.is_none()); - 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(); @@ -727,18 +509,27 @@ mod tests { #[test] fn test_parse_threading() { let mut cxxqtdata = create_parsed_cxx_qt_data(); - - let qobject = cxxqtdata.qobjects.get(&qobject_ident()).unwrap(); - assert!(!qobject.threading); + assert!(cxxqtdata.trait_impls.is_empty()); let threading_block: Item = parse_quote! { impl cxx_qt::Threading for MyObject {} }; + let result = cxxqtdata.parse_cxx_qt_item(threading_block).unwrap(); + assert!(result.is_none()); + assert!(!cxxqtdata.trait_impls.is_empty()); + } - cxxqtdata.parse_cxx_qt_item(threading_block).unwrap(); + #[test] + fn test_passthrough_non_trait_impl() { + let mut cxxqtdata = create_parsed_cxx_qt_data(); - let qobject = cxxqtdata.qobjects.get(&qobject_ident()).unwrap(); - assert!(qobject.threading); + let result = cxxqtdata + .parse_cxx_qt_item(parse_quote! { + impl T {} + }) + .unwrap(); + assert!(result.is_some()); + assert!(matches!(result, Some(Item::Impl(_)))); } #[test] @@ -791,34 +582,70 @@ mod tests { } #[test] - fn test_parse_empty_base() { + fn test_parse_unsupported_type() { let mut cxx_qt_data = ParsedCxxQtData::new(format_ident!("ffi"), None); - let module: ItemMod = parse_quote! { - mod module { - extern "RustQt" { - #[qobject] - #[base = ""] - type MyObject = super::MyObjectRust; - } + let extern_rust_qt: Item = parse_quote! { + extern "RustQt" { + static COUNTER: usize; } }; - assert!(cxx_qt_data - .find_qobject_types(&module.content.unwrap().1) - .is_err()); + assert!(cxx_qt_data.parse_cxx_qt_item(extern_rust_qt).is_err()); } #[test] - fn test_parse_unsupported_type() { - let mut cxx_qt_data = ParsedCxxQtData::new(format_ident!("ffi"), None); - let module: ItemMod = parse_quote! { - mod module { - extern "RustQt" { - static COUNTER: usize; - } + fn test_qobjects() { + let mut parsed_cxxqtdata = ParsedCxxQtData::new(format_ident!("ffi"), None); + let extern_rust_qt: Item = parse_quote! { + extern "RustQt" { + #[qobject] + type MyObject = super::T; + #[qobject] + type MyOtherObject = super::MyOtherT; } }; - assert!(cxx_qt_data - .find_qobject_types(&module.content.unwrap().1) - .is_err()); + + parsed_cxxqtdata.parse_cxx_qt_item(extern_rust_qt).unwrap(); + assert_eq!(parsed_cxxqtdata.qobjects.len(), 2); + + assert!(parsed_cxxqtdata + .find_object(&format_ident!("MyObject")) + .is_some()); + assert!(parsed_cxxqtdata + .find_object(&format_ident!("MyOtherObject")) + .is_some()); + } + + #[test] + fn test_qobject_namespaces() { + let mut parsed_cxxqtdata = ParsedCxxQtData::new(format_ident!("ffi"), None); + let extern_rust_qt: Item = parse_quote! { + #[namespace="b"] + extern "RustQt" { + #[qobject] + #[namespace="a"] + type MyObject = super::T; + #[qobject] + type MyOtherObject = super::MyOtherT; + } + }; + + parsed_cxxqtdata.parse_cxx_qt_item(extern_rust_qt).unwrap(); + assert_eq!(parsed_cxxqtdata.qobjects.len(), 2); + assert_eq!( + parsed_cxxqtdata + .find_object(&format_ident!("MyObject")) + .unwrap() + .name + .namespace(), + Some("a") + ); + assert_eq!( + parsed_cxxqtdata + .find_object(&format_ident!("MyOtherObject")) + .unwrap() + .name + .namespace(), + Some("b") + ); } } diff --git a/crates/cxx-qt-gen/src/parser/mod.rs b/crates/cxx-qt-gen/src/parser/mod.rs index 3a57501db..815a2b307 100644 --- a/crates/cxx-qt-gen/src/parser/mod.rs +++ b/crates/cxx-qt-gen/src/parser/mod.rs @@ -14,6 +14,7 @@ pub mod qenum; pub mod qnamespace; pub mod qobject; pub mod signals; +pub mod trait_impl; use crate::{ // Used for error handling when resolving the namespace of the qenum. @@ -88,12 +89,9 @@ impl Parser { let mut cxx_qt_data = ParsedCxxQtData::new(module.ident.clone(), namespace); // Check that there are items in the module - if let Some(mut items) = module.content { - // Find any QObject structs - cxx_qt_data.find_qobject_types(&items.1)?; - + if let Some((_, items)) = module.content { // Loop through items and load into qobject or others and populate mappings - for item in items.1.drain(..) { + for item in items.into_iter() { // Try to find any CXX-Qt items, if found add them to the relevant // 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)? { diff --git a/crates/cxx-qt-gen/src/parser/qobject.rs b/crates/cxx-qt-gen/src/parser/qobject.rs index d8fb8ddfd..583479006 100644 --- a/crates/cxx-qt-gen/src/parser/qobject.rs +++ b/crates/cxx-qt-gen/src/parser/qobject.rs @@ -5,16 +5,15 @@ use crate::{ naming::Name, - parser::{constructor::Constructor, property::ParsedQProperty}, + parser::property::ParsedQProperty, syntax::{ attribute::attribute_take_path, expr::expr_to_string, foreignmod::ForeignTypeIdentAlias, - path::path_compare_str, }, }; #[cfg(test)] use quote::format_ident; -use syn::{Attribute, Error, Ident, ItemImpl, Meta, Result}; +use syn::{Attribute, Error, Ident, Meta, Result}; /// Metadata for registering QML element #[derive(Clone, Debug, Default, Eq, PartialEq)] @@ -33,19 +32,13 @@ pub struct ParsedQObject { /// The name of the QObject pub name: Name, /// The ident of the inner type of the QObject - pub rust_type: Ident, - /// Any user-defined constructors - pub constructors: Vec, /// List of properties that need to be implemented on the C++ object /// /// These will be exposed as Q_PROPERTY on the C++ object pub properties: Vec, /// List of specifiers to register with in QML pub qml_metadata: Option, - /// Whether locking is enabled for this QObject - pub locking: bool, - /// Whether threading has been enabled for this QObject - pub threading: bool, + pub rust_type: Ident, /// Whether this type has a #[qobject] / Q_OBJECT macro pub has_qobject_macro: bool, @@ -60,11 +53,8 @@ impl ParsedQObject { base_class: None, name: Name::new(format_ident!("MyObject")), rust_type: format_ident!("MyObjectRust"), - constructors: vec![], properties: vec![], qml_metadata: None, - locking: false, - threading: false, has_qobject_macro: false, declaration: ForeignTypeIdentAlias { attrs: vec![], @@ -80,15 +70,39 @@ impl ParsedQObject { namespace: Option<&str>, module: &Ident, ) -> Result { - // Find any QML metadata - let qml_metadata = - Self::parse_qml_metadata(&declaration.ident_left, &mut declaration.attrs)?; + let has_qobject_macro = attribute_take_path(&mut declaration.attrs, &["qobject"]).is_some(); // Find if there is any base class let base_class = attribute_take_path(&mut declaration.attrs, &["base"]) - .map(|attr| expr_to_string(&attr.meta.require_name_value()?.value)) + .map(|attr| { + let string = expr_to_string(&attr.meta.require_name_value()?.value)?; + if string.is_empty() { + // Ensure that the base class attribute is not empty, as this is not valid in both cases + // - when there is a qobject macro it is not valid + // - when there is not a qobject macro it is not valid + return Err(Error::new_spanned( + attr, + "The #[base] attribute cannot be empty", + )); + } + Ok(string) + }) .transpose()?; + // Ensure that if there is no qobject macro that a base class is specificed + // + // Note this assumes the check above + if !has_qobject_macro && base_class.is_none() { + return Err(Error::new_spanned( + declaration.ident_left, + "A type without a #[qobject] attribute must specify a #[base] attribute", + )); + } + + // Find any QML metadata + let qml_metadata = + Self::parse_qml_metadata(&declaration.ident_left, &mut declaration.attrs)?; + let name = Name::from_ident_and_attrs( &declaration.ident_left, &declaration.attrs, @@ -106,12 +120,9 @@ impl ParsedQObject { declaration, name, rust_type: inner, - constructors: vec![], properties, qml_metadata, - locking: true, - threading: false, - has_qobject_macro: false, + has_qobject_macro, }) } @@ -143,74 +154,6 @@ impl ParsedQObject { Ok(None) } - pub fn parse_trait_impl(&mut self, imp: ItemImpl) -> Result<()> { - let (not, trait_path, _) = &imp - .trait_ - .as_ref() - .ok_or_else(|| Error::new_spanned(imp.clone(), "Expected trait impl!"))?; - - if let Some(attr) = imp.attrs.first() { - return Err(Error::new_spanned( - attr, - "Attributes are not allowed on trait impls in cxx_qt::bridge", - )); - } - - if path_compare_str(trait_path, &["cxx_qt", "Locking"]) { - if imp.unsafety.is_none() { - return Err(Error::new_spanned( - trait_path, - "cxx_qt::Locking must be an unsafe impl", - )); - } - - if not.is_none() { - return Err(Error::new_spanned( - trait_path, - "cxx_qt::Locking is enabled by default, it can only be negated.", - )); - } - - // Check that cxx_qt::Threading is not enabled - if self.threading { - return Err(Error::new_spanned( - trait_path, - "cxx_qt::Locking must be enabled if cxx_qt::Threading is enabled", - )); - } - - self.locking = false; - Ok(()) - } else if path_compare_str(trait_path, &["cxx_qt", "Threading"]) { - if not.is_some() { - return Err(Error::new_spanned( - trait_path, - "Negative impls for cxx_qt::Threading are not allowed", - )); - } - - // Check that cxx_qt::Locking is not disabled - if !self.locking { - return Err(Error::new_spanned( - trait_path, - "cxx_qt::Locking must be enabled if cxx_qt::Threading is enabled", - )); - } - - self.threading = true; - Ok(()) - } else if path_compare_str(trait_path, &["cxx_qt", "Constructor"]) { - self.constructors.push(Constructor::parse(imp)?); - Ok(()) - } else { - // TODO: Give suggestions on which trait might have been meant - Err(Error::new_spanned( - trait_path, - "Unsupported trait!\nCXX-Qt currently only supports:\n- cxx_qt::Threading\n- cxx_qt::Constructor\n- cxx_qt::Locking\nNote that the trait must always be fully-qualified." - )) - } - } - fn parse_property_attributes(attrs: &mut Vec) -> Result> { let mut properties = vec![]; @@ -232,7 +175,7 @@ pub mod tests { use crate::parser::tests::f64_type; use quote::format_ident; - use syn::{parse_quote, ItemImpl}; + use syn::parse_quote; pub fn create_parsed_qobject() -> ParsedQObject { let qobject_struct: ForeignTypeIdentAlias = parse_quote! { @@ -242,6 +185,31 @@ pub mod tests { ParsedQObject::parse(qobject_struct, None, &format_ident!("qobject")).unwrap() } + macro_rules! parse_qobject { + { $($input:tt)* } => { + { + let input = parse_quote! { + $($input)* + }; + ParsedQObject::parse(input, None, &format_ident!("qobject")).unwrap() + } + } + } + + #[test] + fn test_has_qobject_name() { + let qobject = parse_qobject! { + #[qobject] + type MyObject = super::MyObjectRust; + }; + assert!(qobject.has_qobject_macro); + let qobject = parse_qobject! { + #[base="Thing"] + type MyObject = super::MyObjectRust; + }; + assert!(!qobject.has_qobject_macro); + } + #[test] fn test_from_struct_no_base_class() { let qobject_struct: ForeignTypeIdentAlias = parse_quote! { @@ -294,77 +262,6 @@ pub mod tests { assert_eq!(qobject.properties.len(), 0); } - #[test] - fn test_parse_trait_impl_valid() { - let mut qobject = create_parsed_qobject(); - let item: ItemImpl = parse_quote! { - impl cxx_qt::Threading for MyObject {} - }; - assert!(!qobject.threading); - assert!(qobject.parse_trait_impl(item).is_ok()); - assert!(qobject.threading); - } - - #[test] - fn test_parse_trait_impl_invalid() { - let mut qobject = create_parsed_qobject(); - - // must be a trait - let item: ItemImpl = parse_quote! { - impl T {} - }; - assert!(qobject.parse_trait_impl(item).is_err()); - - // no attribute allowed - let item: ItemImpl = parse_quote! { - #[attr] - impl cxx_qt::Threading for T {} - }; - assert!(qobject.parse_trait_impl(item).is_err()); - - // Threading cannot be negative - let item: ItemImpl = parse_quote! { - impl !cxx_qt::Threading for T {} - }; - assert!(qobject.parse_trait_impl(item).is_err()); - - // must be a known trait - let item: ItemImpl = parse_quote! { - impl cxx_qt::ABC for T {} - }; - assert!(qobject.parse_trait_impl(item).is_err()); - - // locking unsafe impl - let item: ItemImpl = parse_quote! { - impl cxx_qt::Locking for T {} - }; - assert!(qobject.parse_trait_impl(item).is_err()); - - // locking can only be negated - let item: ItemImpl = parse_quote! { - unsafe impl cxx_qt::Locking for T {} - }; - assert!(qobject.parse_trait_impl(item).is_err()); - - // locking must be enabled if threading is enabled - let mut non_lock_qobject = create_parsed_qobject(); - non_lock_qobject.locking = false; - - let item: ItemImpl = parse_quote! { - impl cxx_qt::Threading for T {} - }; - assert!(non_lock_qobject.parse_trait_impl(item).is_err()); - - // if threading is enabled, !Locking cannot be implemented - let mut threaded_qobject = create_parsed_qobject(); - threaded_qobject.threading = true; - - let item: ItemImpl = parse_quote! { - unsafe impl !cxx_qt::Locking for T {} - }; - assert!(threaded_qobject.parse_trait_impl(item).is_err()); - } - #[test] fn test_parse_struct_fields_valid() { let item: ForeignTypeIdentAlias = parse_quote! { @@ -458,4 +355,24 @@ pub mod tests { }) ); } + + macro_rules! assert_parse_errors { + { $($input:tt)* } => { + $(assert!(ParsedQObject::parse(syn::parse_quote! $input, None, &format_ident!("qobject")).is_err());)* + } + } + + #[test] + fn test_parse_errors() { + assert_parse_errors! { + { + #[qobject] + #[base = ""] + type MyObject = super::T; + } + { + type MyObject = super::T; + } + } + } } diff --git a/crates/cxx-qt-gen/src/parser/trait_impl.rs b/crates/cxx-qt-gen/src/parser/trait_impl.rs new file mode 100644 index 000000000..c5ed089c9 --- /dev/null +++ b/crates/cxx-qt-gen/src/parser/trait_impl.rs @@ -0,0 +1,196 @@ +// SPDX-FileCopyrightText: 2024 Klarälvdalens Datakonsult AB, a KDAB Group company +// SPDX-FileContributor: Leon Matthes +// +// SPDX-License-Identifier: MIT OR Apache-2.0 +use syn::{Error, Ident, ItemImpl, Path, Result, Token, Type, TypePath}; + +use crate::{parser::constructor::Constructor, syntax::path::path_compare_str}; + +/// The kind of marker trait implementation. +#[derive(Debug, PartialEq, Eq)] +pub enum TraitKind { + Threading, + DisableLocking, + Constructor(Constructor), +} + +impl TraitKind { + fn parse_disable_locking(not: &Option, path: &Path, imp: &ItemImpl) -> Result { + if imp.unsafety.is_none() { + return Err(Error::new_spanned( + path, + "cxx_qt::Locking must be an unsafe impl", + )); + } + + if not.is_none() { + return Err(Error::new_spanned( + path, + "cxx_qt::Locking is enabled by default, use `!cxx_qt::Locking` to disable it.", + )); + } + + // TODO: Check that threading isn't also enabled + + Ok(Self::DisableLocking) + } + + fn parse_threading(not: &Option, path: &Path, imp: &ItemImpl) -> Result { + if let Some(unsafety) = imp.unsafety.as_ref() { + return Err(Error::new_spanned( + unsafety, + "cxx_qt::Threading is safe to implement!", + )); + } + if not.is_some() { + return Err(Error::new_spanned( + path, + "Negative impls for cxx_qt::Threading are not allowed", + )); + } + Ok(Self::Threading) + } + + fn parse_constructor(imp: &ItemImpl) -> Result { + let constructor = Constructor::parse(imp.clone())?; + Ok(Self::Constructor(constructor)) + } + + fn parse(imp: &ItemImpl) -> Result { + let (not, path, _) = &imp + .trait_ + .as_ref() + .ok_or_else(|| Error::new_spanned(imp.clone(), "Expected trait impl!"))?; + + if path_compare_str(path, &["cxx_qt", "Locking"]) { + Self::parse_disable_locking(not, path, imp) + } else if path_compare_str(path, &["cxx_qt", "Threading"]) { + Self::parse_threading(not, path, imp) + } else if path_compare_str(path, &["cxx_qt", "Constructor"]) { + Self::parse_constructor(imp) + } else { + // TODO: Give suggestions on which trait might have been meant + Err(Error::new_spanned( + path, + "Unsupported trait!\nCXX-Qt currently only supports:\n- cxx_qt::Threading\n- cxx_qt::Constructor\n- cxx_qt::Locking\nNote that the trait must always be fully-qualified." + )) + } + } +} + +/// A marker trait implementation that has been picked up by the CXX-Qt parser. +#[derive(Debug)] +pub struct TraitImpl { + pub qobject: Ident, + pub declaration: ItemImpl, + pub kind: TraitKind, +} + +impl TraitImpl { + pub fn parse(imp: ItemImpl) -> Result { + if let Some(attr) = imp.attrs.first() { + return Err(Error::new_spanned( + attr, + "Attributes are not allowed on trait impls in cxx_qt::bridge", + )); + } + + if !imp.items.is_empty() { + return Err(Error::new_spanned( + imp.items.first(), + "Only trait declarations, not implementations are allowed in the bridge!", + )); + } + + let invalid_path = + || Error::new_spanned(&imp.self_ty, "Invalid type! Expected a single identifier!"); + let qobject = if let Type::Path(TypePath { path, .. }) = imp.self_ty.as_ref() { + path.get_ident().cloned().ok_or_else(invalid_path) + } else { + Err(invalid_path()) + }?; + let kind = TraitKind::parse(&imp)?; + Ok(Self { + qobject, + kind, + declaration: imp, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use quote::format_ident; + use syn::parse_quote; + + #[test] + fn parse_threading() { + let imp = parse_quote! { + impl cxx_qt::Threading for QObject {} + }; + let marker = TraitImpl::parse(imp).unwrap(); + assert_eq!(marker.qobject, format_ident!("QObject")); + assert_eq!(marker.kind, TraitKind::Threading); + } + + #[test] + fn parse_disable_locking() { + let imp = parse_quote! { + unsafe impl !cxx_qt::Locking for MyObject {} + }; + let marker = TraitImpl::parse(imp).unwrap(); + assert_eq!(marker.qobject, format_ident!("MyObject")); + assert_eq!(marker.kind, TraitKind::DisableLocking); + } + + #[test] + fn parse_constructor() { + let imp = parse_quote! { + impl cxx_qt::Constructor<(i32, i32)> for MyObject {} + }; + let marker = TraitImpl::parse(imp).unwrap(); + assert_eq!(marker.qobject, format_ident!("MyObject")); + assert!(matches!(marker.kind, TraitKind::Constructor(_))) + } + + use crate::tests::assert_parse_errors; + + #[test] + fn test_parse_errors() { + assert_parse_errors! { + TraitImpl: + + // Threading is safe to implement + { unsafe impl cxx_qt::Threading for QObject {} } + // Threading cannot be negated + { impl !cxx_qt::Threading for QObject {} } + + // Locking must be unsafe + { impl !cxx_qt::Locking for QObject {} } + // Locking must always be negated + { unsafe impl cxx_qt::Locking for QObject {} } + + // Invalid QObject name + { impl cxx_qt::Locking for my::path {} } + // Invalid trait name + { impl cxx_qt::AnotherTrait for QObject {} } + // Invalid Path to self type + { impl cxx_qt::Threading for *mut QObject{} } + + { + // Attributes are not allowed + #[my_attribute] + impl cxx_qt::Threading for QObject {} + } + + { + // Item in the impl block + impl cxx_qt::Threading for X { + fn some_impl() {} + } + } + } + } +} 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 6dff54d5e..eeb586541 100644 --- a/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.cpp +++ b/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.cpp @@ -318,17 +318,6 @@ MyObject::MyObject(QObject* parent) } // namespace cxx_qt::multi_object -namespace my_namespace { -MyCxxName::MyCxxName(QObject* parent) - : QObject(parent) - , ::rust::cxxqt1::CxxQtType( - ::my_namespace::cxx_qt_my_rust_name::createRs()) - , ::rust::cxxqt1::CxxQtLocking() -{ -} - -} // namespace my_namespace - // Define namespace otherwise we hit a GCC bug // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56480 namespace rust::cxxqt1 { @@ -475,3 +464,14 @@ SecondObject::SecondObject(QObject* parent) } } // namespace second_object + +namespace my_namespace { +MyCxxName::MyCxxName(QObject* parent) + : QObject(parent) + , ::rust::cxxqt1::CxxQtType( + ::my_namespace::cxx_qt_my_rust_name::createRs()) + , ::rust::cxxqt1::CxxQtLocking() +{ +} + +} // namespace my_namespace diff --git a/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.h b/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.h index 6d63ece3c..f9e230cbd 100644 --- a/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.h +++ b/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.h @@ -21,11 +21,6 @@ using MyObjectCxxQtSignalHandlerready = ::rust::cxxqt1::SignalHandler; } // namespace cxx_qt::multi_object::rust::cxxqtgen1 -namespace my_namespace { -class MyCxxName; - -} // namespace my_namespace - namespace second_object { class SecondObject; @@ -42,6 +37,11 @@ using SecondObjectCxxQtSignalHandlerready = ::rust::cxxqt1::SignalHandler; } // namespace second_object::rust::cxxqtgen1 +namespace my_namespace { +class MyCxxName; + +} // namespace my_namespace + namespace rust::cxxqtgen1 { using QPushButtonCxxQtSignalHandlerclicked = ::rust::cxxqt1::SignalHandler; @@ -137,26 +137,6 @@ static_assert(::std::is_base_of::value, Q_DECLARE_METATYPE(cxx_qt::multi_object::MyObject*) -namespace my_namespace { -class MyCxxName - : public QObject - , public ::rust::cxxqt1::CxxQtType - , public ::rust::cxxqt1::CxxQtLocking -{ - Q_OBJECT -public: - virtual ~MyCxxName() = default; - -public: - explicit MyCxxName(QObject* parent = nullptr); -}; - -static_assert(::std::is_base_of::value, - "MyCxxName must inherit from QObject"); -} // namespace my_namespace - -Q_DECLARE_METATYPE(my_namespace::MyCxxName*) - namespace second_object::rust::cxxqtgen1 { ::QMetaObject::Connection SecondObject_propertyNameChangedConnect( @@ -205,3 +185,23 @@ static_assert(::std::is_base_of::value, } // namespace second_object Q_DECLARE_METATYPE(second_object::SecondObject*) + +namespace my_namespace { +class MyCxxName + : public QObject + , public ::rust::cxxqt1::CxxQtType + , public ::rust::cxxqt1::CxxQtLocking +{ + Q_OBJECT +public: + virtual ~MyCxxName() = default; + +public: + explicit MyCxxName(QObject* parent = nullptr); +}; + +static_assert(::std::is_base_of::value, + "MyCxxName must inherit from QObject"); +} // namespace my_namespace + +Q_DECLARE_METATYPE(my_namespace::MyCxxName*) diff --git a/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.rs b/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.rs index afb70e660..337a073cd 100644 --- a/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.rs +++ b/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.rs @@ -171,37 +171,6 @@ pub mod ffi { #[doc(hidden)] fn cxx_qt_ffi_rust_mut(self: Pin<&mut MyObject>) -> Pin<&mut MyObjectRust>; } - unsafe extern "C++" { - #[doc = "The C++ type for the QObject "] - #[doc = "ThirdObjectRust"] - #[doc = "\n"] - #[doc = "Use this type when referring to the QObject as a pointer"] - #[doc = "\n"] - #[doc = "See the book for more information: "] - #[namespace = "my_namespace"] - #[doc = "\n\nNote: The C++ name of this QObject is: "] - #[doc = "MyCxxName"] - #[cxx_name = "MyCxxName"] - type MyRustName; - } - extern "Rust" { - type ThirdObjectRust; - } - extern "Rust" { - #[cxx_name = "createRs"] - #[namespace = "my_namespace::cxx_qt_my_rust_name"] - fn create_rs_third_object_rust() -> Box; - } - unsafe extern "C++" { - #[cxx_name = "unsafeRust"] - #[doc(hidden)] - fn cxx_qt_ffi_rust(self: &MyRustName) -> &ThirdObjectRust; - } - unsafe extern "C++" { - #[cxx_name = "unsafeRustMut"] - #[doc(hidden)] - fn cxx_qt_ffi_rust_mut(self: Pin<&mut MyRustName>) -> Pin<&mut ThirdObjectRust>; - } unsafe extern "C++" { #[doc = "The C++ type for the QObject "] #[doc = "SecondObjectRust"] @@ -304,6 +273,37 @@ pub mod ffi { #[doc(hidden)] fn cxx_qt_ffi_rust_mut(self: Pin<&mut SecondObject>) -> Pin<&mut SecondObjectRust>; } + unsafe extern "C++" { + #[doc = "The C++ type for the QObject "] + #[doc = "ThirdObjectRust"] + #[doc = "\n"] + #[doc = "Use this type when referring to the QObject as a pointer"] + #[doc = "\n"] + #[doc = "See the book for more information: "] + #[namespace = "my_namespace"] + #[doc = "\n\nNote: The C++ name of this QObject is: "] + #[doc = "MyCxxName"] + #[cxx_name = "MyCxxName"] + type MyRustName; + } + extern "Rust" { + type ThirdObjectRust; + } + extern "Rust" { + #[cxx_name = "createRs"] + #[namespace = "my_namespace::cxx_qt_my_rust_name"] + fn create_rs_third_object_rust() -> Box; + } + unsafe extern "C++" { + #[cxx_name = "unsafeRust"] + #[doc(hidden)] + fn cxx_qt_ffi_rust(self: &MyRustName) -> &ThirdObjectRust; + } + unsafe extern "C++" { + #[cxx_name = "unsafeRustMut"] + #[doc(hidden)] + fn cxx_qt_ffi_rust_mut(self: Pin<&mut MyRustName>) -> Pin<&mut ThirdObjectRust>; + } #[namespace = ""] unsafe extern "C++" { type QPushButton; @@ -566,26 +566,6 @@ impl cxx_qt::CxxQtType for ffi::MyObject { self.cxx_qt_ffi_rust_mut() } } -impl cxx_qt::Locking for ffi::MyRustName {} -#[doc(hidden)] -pub fn create_rs_third_object_rust() -> std::boxed::Box { - std::boxed::Box::new(core::default::Default::default()) -} -impl core::ops::Deref for ffi::MyRustName { - type Target = ThirdObjectRust; - fn deref(&self) -> &Self::Target { - self.cxx_qt_ffi_rust() - } -} -impl cxx_qt::CxxQtType for ffi::MyRustName { - type Rust = ThirdObjectRust; - fn rust(&self) -> &Self::Rust { - self.cxx_qt_ffi_rust() - } - fn rust_mut(self: core::pin::Pin<&mut Self>) -> core::pin::Pin<&mut Self::Rust> { - self.cxx_qt_ffi_rust_mut() - } -} impl ffi::SecondObject { #[doc = "Getter for the Q_PROPERTY "] #[doc = "property_name"] @@ -749,6 +729,26 @@ impl cxx_qt::CxxQtType for ffi::SecondObject { self.cxx_qt_ffi_rust_mut() } } +impl cxx_qt::Locking for ffi::MyRustName {} +#[doc(hidden)] +pub fn create_rs_third_object_rust() -> std::boxed::Box { + std::boxed::Box::new(core::default::Default::default()) +} +impl core::ops::Deref for ffi::MyRustName { + type Target = ThirdObjectRust; + fn deref(&self) -> &Self::Target { + self.cxx_qt_ffi_rust() + } +} +impl cxx_qt::CxxQtType for ffi::MyRustName { + type Rust = ThirdObjectRust; + fn rust(&self) -> &Self::Rust { + self.cxx_qt_ffi_rust() + } + fn rust_mut(self: core::pin::Pin<&mut Self>) -> core::pin::Pin<&mut Self::Rust> { + self.cxx_qt_ffi_rust_mut() + } +} impl ffi::QPushButton { #[doc = "Connect the given function pointer to the signal "] #[doc = "clicked"]