From 8ac1f79dc99e199328d71e2866b7cd833f04732f Mon Sep 17 00:00:00 2001 From: BenFordTytherington <110855418+BenFordTytherington@users.noreply.github.com> Date: Fri, 20 Sep 2024 10:55:21 +0000 Subject: [PATCH] cxx-qt-gen: Improve attribute parsing * Refactor so that all unknown attrributes cause errors. WIP * Refactor has_invalid_attrs function - Rename to check_attribute_validity - Refactor to return `Result<()>` and use with ? operator - Update some comments * Use check_attribute_validity when parsing bridge * Update error message to be closer to cxx message * Remove passing through attributes * Removing mutating attributes * Fix extern types generation error * Add parse_attributes function which not only checks for invalid attributes, but returns a BTreeMap of the attributes found - Started using in this in a few places to try it out - Removed check_attribute_validity function * Remove taking attributes * Add PassthroughMod struct and refactor methods - PassthroughMod is a struct which stores the components of a mod to pass through to cxx - It is parsed from the ItemMod originally stored in Parser, and then in generation, rebuilt correctly - Separate_docs has been renamed to extract_docs as it no longer mutates, and the mutable references used for it have been removed in the code - Tests have been refactored for the new PassthroughMod * Add namespace to signals and inherited --- .../src/generator/cpp/externcxxqt.rs | 49 +++--- .../cxx-qt-gen/src/generator/cpp/inherit.rs | 2 +- .../src/generator/cpp/property/mod.rs | 25 ++- .../src/generator/rust/externcxxqt.rs | 51 +++++- .../cxx-qt-gen/src/generator/rust/inherit.rs | 7 +- crates/cxx-qt-gen/src/generator/rust/mod.rs | 60 +++++-- crates/cxx-qt-gen/src/generator/rust/qenum.rs | 22 ++- .../cxx-qt-gen/src/generator/rust/signals.rs | 6 + crates/cxx-qt-gen/src/naming/name.rs | 23 ++- crates/cxx-qt-gen/src/naming/rust.rs | 2 +- crates/cxx-qt-gen/src/naming/type_names.rs | 64 ++++---- crates/cxx-qt-gen/src/parser/cxxqtdata.rs | 45 +++--- crates/cxx-qt-gen/src/parser/externcxxqt.rs | 153 +++++++++--------- crates/cxx-qt-gen/src/parser/externqobject.rs | 32 ++++ crates/cxx-qt-gen/src/parser/inherit.rs | 20 ++- crates/cxx-qt-gen/src/parser/method.rs | 71 ++++---- crates/cxx-qt-gen/src/parser/mod.rs | 153 ++++++++++++------ crates/cxx-qt-gen/src/parser/parameter.rs | 4 +- crates/cxx-qt-gen/src/parser/property.rs | 35 ++-- crates/cxx-qt-gen/src/parser/qenum.rs | 16 +- crates/cxx-qt-gen/src/parser/qnamespace.rs | 15 +- crates/cxx-qt-gen/src/parser/qobject.rs | 73 +++++---- crates/cxx-qt-gen/src/parser/signals.rs | 36 ++--- crates/cxx-qt-gen/src/syntax/attribute.rs | 44 ++--- crates/cxx-qt-gen/src/syntax/lifetimes.rs | 60 +++---- crates/cxx-qt-gen/src/writer/rust/mod.rs | 4 +- .../test_inputs/passthrough_and_naming.rs | 5 +- crates/cxx-qt-gen/test_inputs/properties.rs | 1 - crates/cxx-qt-gen/test_inputs/qenum.rs | 1 - .../test_outputs/passthrough_and_naming.cpp | 44 ++--- .../test_outputs/passthrough_and_naming.h | 13 +- .../test_outputs/passthrough_and_naming.rs | 22 ++- crates/cxx-qt-gen/test_outputs/properties.rs | 6 + crates/cxx-qt-gen/test_outputs/qenum.rs | 3 + crates/cxx-qt-gen/test_outputs/signals.rs | 4 + 35 files changed, 670 insertions(+), 501 deletions(-) create mode 100644 crates/cxx-qt-gen/src/parser/externqobject.rs diff --git a/crates/cxx-qt-gen/src/generator/cpp/externcxxqt.rs b/crates/cxx-qt-gen/src/generator/cpp/externcxxqt.rs index af36e910d..09453dbbd 100644 --- a/crates/cxx-qt-gen/src/generator/cpp/externcxxqt.rs +++ b/crates/cxx-qt-gen/src/generator/cpp/externcxxqt.rs @@ -44,24 +44,29 @@ pub fn generate( #[cfg(test)] mod tests { + use quote::format_ident; use syn::parse_quote; use super::*; #[test] fn test_generate_cpp_extern_qt() { - let blocks = vec![ParsedExternCxxQt::parse(parse_quote! { - unsafe extern "C++Qt" { - #[qobject] - type MyObject; + let blocks = vec![ParsedExternCxxQt::parse( + parse_quote! { + unsafe extern "C++Qt" { + #[qobject] + type MyObject; - #[qsignal] - fn signal1(self: Pin<&mut MyObject>); + #[qsignal] + fn signal1(self: Pin<&mut MyObject>); - #[qsignal] - fn signal2(self: Pin<&mut MyObject>); - } - }) + #[qsignal] + fn signal2(self: Pin<&mut MyObject>); + } + }, + &format_ident!("qobject"), + None, + ) .unwrap()]; // Unknown types @@ -73,17 +78,21 @@ mod tests { #[test] fn test_generate_cpp_extern_qt_mapping() { - let blocks = vec![ParsedExternCxxQt::parse(parse_quote! { - unsafe extern "C++Qt" { - #[cxx_name = "ObjCpp"] - #[namespace = "mynamespace"] - #[qobject] - type ObjRust; + let blocks = vec![ParsedExternCxxQt::parse( + parse_quote! { + unsafe extern "C++Qt" { + #[cxx_name = "ObjCpp"] + #[namespace = "mynamespace"] + #[qobject] + type ObjRust; - #[qsignal] - fn signal(self: Pin<&mut ObjRust>); - } - }) + #[qsignal] + fn signal(self: Pin<&mut ObjRust>); + } + }, + &format_ident!("qobject"), + None, + ) .unwrap()]; let mut type_names = TypeNames::default(); type_names.mock_insert("ObjRust", None, Some("ObjCpp"), Some("mynamespace")); diff --git a/crates/cxx-qt-gen/src/generator/cpp/inherit.rs b/crates/cxx-qt-gen/src/generator/cpp/inherit.rs index ac599fd3e..ff14b87d9 100644 --- a/crates/cxx-qt-gen/src/generator/cpp/inherit.rs +++ b/crates/cxx-qt-gen/src/generator/cpp/inherit.rs @@ -58,7 +58,7 @@ mod tests { method: ForeignItemFn, base_class: Option<&str>, ) -> Result { - let method = ParsedInheritedMethod::parse(method, Safety::Safe).unwrap(); + let method = ParsedInheritedMethod::parse(method, Safety::Safe)?; let inherited_methods = vec![&method]; let base_class = base_class.map(|s| s.to_owned()); generate(&inherited_methods, &base_class, &TypeNames::default()) diff --git a/crates/cxx-qt-gen/src/generator/cpp/property/mod.rs b/crates/cxx-qt-gen/src/generator/cpp/property/mod.rs index 143ac8010..79d541386 100644 --- a/crates/cxx-qt-gen/src/generator/cpp/property/mod.rs +++ b/crates/cxx-qt-gen/src/generator/cpp/property/mod.rs @@ -65,7 +65,7 @@ pub mod tests { use crate::generator::naming::property::property_name_from_rust_name; use crate::generator::naming::qobject::tests::create_qobjectname; use crate::generator::structuring::Structures; - use crate::parser::property::QPropertyFlags; + use crate::parser::property::{mock_property, QPropertyFlags}; use crate::parser::qobject::ParsedQObject; use crate::{CppFragment, Parser}; use indoc::indoc; @@ -95,7 +95,7 @@ pub mod tests { } fn setup_generated(input: &mut ItemStruct) -> Result { - let property = ParsedQProperty::parse(input.attrs.remove(0)).unwrap(); + let property = ParsedQProperty::parse(&input.attrs.remove(0))?; let properties = vec![property]; @@ -152,12 +152,12 @@ pub mod tests { #[test] fn test_unexpected_headers() { - let mut input: ItemStruct = parse_quote! { + let input: ItemStruct = parse_quote! { #[qproperty(i32, num, READ, WRITE = mySetter)] struct MyStruct; }; - let property = ParsedQProperty::parse(input.attrs.remove(0)).unwrap(); + let property = mock_property(input); let properties = vec![property]; @@ -190,12 +190,12 @@ pub mod tests { #[test] fn test_custom_setter() { - let mut input: ItemStruct = parse_quote! { + let input: ItemStruct = parse_quote! { #[qproperty(i32, num, READ, WRITE = mySetter)] struct MyStruct; }; - let property = ParsedQProperty::parse(input.attrs.remove(0)).unwrap(); + let property = mock_property(input); let properties = vec![property]; @@ -231,12 +231,12 @@ pub mod tests { #[test] fn test_reset() { - let mut input: ItemStruct = parse_quote! { + let input: ItemStruct = parse_quote! { #[qproperty(i32, num, READ, WRITE = mySetter, RESET = my_resetter)] struct MyStruct; }; - let property = ParsedQProperty::parse(input.attrs.remove(0)).unwrap(); + let property = mock_property(input); let properties = vec![property]; @@ -280,20 +280,17 @@ pub mod tests { #[test] fn test_generate_cpp_properties() { - let mut input1: ItemStruct = parse_quote! { + let input1: ItemStruct = parse_quote! { #[qproperty(i32, trivial_property, READ, WRITE, NOTIFY)] struct MyStruct; }; - let mut input2: ItemStruct = parse_quote! { + let input2: ItemStruct = parse_quote! { #[qproperty(UniquePtr, opaque_property)] struct MyStruct; }; - let properties = vec![ - ParsedQProperty::parse(input1.attrs.remove(0)).unwrap(), - ParsedQProperty::parse(input2.attrs.remove(0)).unwrap(), - ]; + let properties = vec![mock_property(input1), mock_property(input2)]; let qobject_idents = create_qobjectname(); diff --git a/crates/cxx-qt-gen/src/generator/rust/externcxxqt.rs b/crates/cxx-qt-gen/src/generator/rust/externcxxqt.rs index 0912da3c7..433c6ed6a 100644 --- a/crates/cxx-qt-gen/src/generator/rust/externcxxqt.rs +++ b/crates/cxx-qt-gen/src/generator/rust/externcxxqt.rs @@ -2,7 +2,6 @@ // SPDX-FileContributor: Andrew Hayzen // // SPDX-License-Identifier: MIT OR Apache-2.0 - use crate::{ generator::rust::{ fragment::{GeneratedRustFragment, RustFragmentPair}, @@ -10,9 +9,10 @@ use crate::{ }, naming::TypeNames, parser::externcxxqt::ParsedExternCxxQt, + syntax::path::path_compare_str, }; use quote::quote; -use syn::Result; +use syn::{Attribute, Result}; impl GeneratedRustFragment { pub fn from_extern_cxx_qt( @@ -21,15 +21,58 @@ impl GeneratedRustFragment { ) -> Result { let mut generated = Self::default(); + let extern_block_namespace = if let Some(namespace) = &extern_cxxqt_block.namespace { + quote! { #[namespace = #namespace ] } + } else { + quote! {} + }; + // Add the pass through blocks - let attrs = &extern_cxxqt_block.attrs; let unsafety = &extern_cxxqt_block.unsafety; let items = &extern_cxxqt_block.passthrough_items; + let types = &extern_cxxqt_block + .qobjects + .iter() + .map(|ty| { + let namespace = if let Some(namespace) = &ty.name.namespace() { + quote! { #[namespace = #namespace ] } + } else { + quote! {} + }; + let cpp_name = &ty.name.cxx_unqualified(); + let rust_name = &ty.name.rust_unqualified(); + let vis = &ty.declaration.vis; + let ident = &ty.name.rust_unqualified(); + let cxx_name = if &rust_name.to_string() == cpp_name { + quote! {} + } else { + let cxx_name = cpp_name.to_string(); + quote! { + #[cxx_name = #cxx_name] + } + }; + let docs: Vec<&Attribute> = ty + .declaration + .attrs + .iter() + .filter(|attr| path_compare_str(attr.meta.path(), &["doc"])) + .collect(); + quote! { + #namespace + #cxx_name + #(#docs)* + #vis type #ident; + } + }) + .collect::>(); + let fragment = RustFragmentPair { cxx_bridge: vec![quote! { - #(#attrs)* + #extern_block_namespace #unsafety extern "C++" { #(#items)* + + #(#types)* } }], implementation: vec![], diff --git a/crates/cxx-qt-gen/src/generator/rust/inherit.rs b/crates/cxx-qt-gen/src/generator/rust/inherit.rs index 9e4955800..351bb92a8 100644 --- a/crates/cxx-qt-gen/src/generator/rust/inherit.rs +++ b/crates/cxx-qt-gen/src/generator/rust/inherit.rs @@ -45,15 +45,14 @@ pub fn generate( if method.safe { std::mem::swap(&mut unsafe_call, &mut unsafe_block); } - - let attrs = &method.method.attrs; let doc_comments = &method.docs; + let namespace = qobject_names.namespace_tokens(); syn::parse2(quote_spanned! { method.method.span() => #unsafe_block extern "C++" { - #(#attrs)* #[cxx_name = #cxx_name_string] + #namespace #(#doc_comments)* #unsafe_call fn #ident(#self_param, #(#parameters),*) #return_type; } @@ -78,7 +77,7 @@ mod tests { method: ForeignItemFn, safety: Safety, ) -> Result { - let method = ParsedInheritedMethod::parse(method, safety).unwrap(); + let method = ParsedInheritedMethod::parse(method, safety)?; let inherited_methods = vec![&method]; generate(&create_qobjectname(), &inherited_methods) } diff --git a/crates/cxx-qt-gen/src/generator/rust/mod.rs b/crates/cxx-qt-gen/src/generator/rust/mod.rs index 02aa54da6..0bbb0f77e 100644 --- a/crates/cxx-qt-gen/src/generator/rust/mod.rs +++ b/crates/cxx-qt-gen/src/generator/rust/mod.rs @@ -15,13 +15,11 @@ pub mod qobject; pub mod signals; pub mod threading; -use crate::generator::rust::fragment::GeneratedRustFragment; -use crate::generator::structuring; -use crate::parser::parameter::ParsedFunctionParameter; -use crate::parser::Parser; +use crate::generator::{rust::fragment::GeneratedRustFragment, structuring}; +use crate::parser::{parameter::ParsedFunctionParameter, Parser}; use proc_macro2::{Ident, TokenStream}; use quote::quote; -use syn::{Item, ItemMod, Result}; +use syn::{parse_quote, Item, ItemMod, Result}; /// Representation of the generated Rust code for a QObject pub struct GeneratedRustBlocks { @@ -59,10 +57,35 @@ impl GeneratedRustBlocks { .collect::>>()?, ); + let namespace = parser.cxx_qt_data.namespace.clone().unwrap_or_default(); + let passthrough_mod = &parser.passthrough_module; + + let vis = &passthrough_mod.vis; + let ident = &passthrough_mod.module_ident; + let docs = &passthrough_mod.docs; + let module = passthrough_mod.items.clone().map_or( + // If no items are present, curly braces aren't needed + quote! { + #vis mod #ident; + }, + |items| { + quote! { + #vis mod #ident { + #(#items)* + } + } + }, + ); + let cxx_mod = parse_quote! { + #[cxx::bridge(namespace = #namespace)] + #(#docs)* + #module + }; + Ok(GeneratedRustBlocks { - cxx_mod: parser.passthrough_module.clone(), + cxx_mod, cxx_mod_contents: qenum::generate_cxx_mod_contents(&parser.cxx_qt_data.qenums), - namespace: parser.cxx_qt_data.namespace.clone().unwrap_or_default(), + namespace, fragments, }) } @@ -96,9 +119,8 @@ pub fn get_params_tokens( #[cfg(test)] mod tests { - use syn::parse_quote; - use super::*; + use syn::parse_quote; #[test] fn test_generated_rust_blocks() { @@ -139,4 +161,24 @@ mod tests { assert_eq!(rust.namespace, "cxx_qt"); assert_eq!(rust.fragments.len(), 1); } + + #[test] + fn test_generated_rust_blocks_foreign_qobject() { + let module: ItemMod = parse_quote! { + #[cxx_qt::bridge] + mod ffi { + extern "C++Qt" { + #[qobject] + type MyObject; + } + } + }; + let parser = Parser::from(module).unwrap(); + + let rust = GeneratedRustBlocks::from(&parser).unwrap(); + assert!(rust.cxx_mod.content.is_none()); + assert_eq!(rust.cxx_mod_contents.len(), 0); + assert_eq!(rust.namespace, ""); + assert_eq!(rust.fragments.len(), 1); + } } diff --git a/crates/cxx-qt-gen/src/generator/rust/qenum.rs b/crates/cxx-qt-gen/src/generator/rust/qenum.rs index ba0c15e5f..0a3cad5ca 100644 --- a/crates/cxx-qt-gen/src/generator/rust/qenum.rs +++ b/crates/cxx-qt-gen/src/generator/rust/qenum.rs @@ -4,16 +4,24 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 use crate::parser::qenum::ParsedQEnum; +use crate::syntax::path::path_compare_str; use quote::quote; -use syn::{parse_quote_spanned, spanned::Spanned, Item}; +use syn::{parse_quote_spanned, spanned::Spanned, Attribute, Item}; pub fn generate_cxx_mod_contents(qenums: &[ParsedQEnum]) -> Vec { qenums .iter() .flat_map(|qenum| { - let qenum_item = &qenum.item; let qenum_ident = &qenum.name.rust_unqualified(); let namespace = &qenum.name.namespace(); + let item = &qenum.item; + let vis = &item.vis; + let variants = &item.variants; + let docs: Vec<&Attribute> = item + .attrs + .iter() + .filter(|attr| path_compare_str(attr.meta.path(), &["doc"])) + .collect(); let cxx_namespace = if namespace.is_none() { quote! {} @@ -22,12 +30,16 @@ pub fn generate_cxx_mod_contents(qenums: &[ParsedQEnum]) -> Vec { }; vec![ parse_quote_spanned! { - qenum.item.span() => + item.span() => #[repr(i32)] - #qenum_item + #(#docs)* + #cxx_namespace + #vis enum #qenum_ident { + #variants + } }, parse_quote_spanned! { - qenum.item.span() => + item.span() => extern "C++" { #cxx_namespace type #qenum_ident; diff --git a/crates/cxx-qt-gen/src/generator/rust/signals.rs b/crates/cxx-qt-gen/src/generator/rust/signals.rs index 4a5d48560..fcc0aa287 100644 --- a/crates/cxx-qt-gen/src/generator/rust/signals.rs +++ b/crates/cxx-qt-gen/src/generator/rust/signals.rs @@ -101,6 +101,11 @@ pub fn generate_rust_signal( let cpp_ident = idents.name.cxx_unqualified(); let doc_comments = &signal.docs; + let namespace = if let Some(namespace) = qobject_name.namespace() { + quote! { #[namespace = #namespace ] } + } else { + quote! {} + }; let signal_ident_cpp = idents.name.rust_unqualified(); let parameter_signatures = @@ -114,6 +119,7 @@ pub fn generate_rust_signal( #unsafe_block extern "C++" { #[cxx_name = #cpp_ident] #(#doc_comments)* + #namespace #unsafe_call fn #signal_ident_cpp(#parameter_signatures) #return_type; } }); diff --git a/crates/cxx-qt-gen/src/naming/name.rs b/crates/cxx-qt-gen/src/naming/name.rs index 73b88320c..a36fe14b5 100644 --- a/crates/cxx-qt-gen/src/naming/name.rs +++ b/crates/cxx-qt-gen/src/naming/name.rs @@ -3,12 +3,11 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 +use crate::syntax::{attribute::attribute_get_path, expr::expr_to_string}; use convert_case::{Case, Casing}; use quote::format_ident; use syn::{spanned::Spanned, Attribute, Error, Ident, Path, Result}; -use crate::syntax::{attribute::attribute_find_path, expr::expr_to_string}; - pub enum AutoCamel { Enabled, Disabled, @@ -133,10 +132,8 @@ impl Name { module: Option<&Ident>, ) -> Result { // Find if there is a namespace (for C++ generation) - let mut namespace = if let Some(index) = attribute_find_path(attrs, &["namespace"]) { - Some(expr_to_string( - &attrs[index].meta.require_name_value()?.value, - )?) + let mut namespace = if let Some(attr) = attribute_get_path(attrs, &["namespace"]) { + Some(expr_to_string(&attr.meta.require_name_value()?.value)?) } else { parent_namespace.map(|namespace| namespace.to_owned()) }; @@ -150,19 +147,17 @@ impl Name { } // Find if there is a cxx_name mapping (for C++ generation) - let cxx_name = attribute_find_path(attrs, &["cxx_name"]) - .map(|index| -> Result<_> { - expr_to_string(&attrs[index].meta.require_name_value()?.value) - }) + let cxx_name = attribute_get_path(attrs, &["cxx_name"]) + .map(|attr| -> Result<_> { expr_to_string(&attr.meta.require_name_value()?.value) }) .transpose()?; // Find if there is a rust_name mapping - let rust_name = attribute_find_path(attrs, &["rust_name"]) - .map(|index| -> Result<_> { + let rust_name = attribute_get_path(attrs, &["rust_name"]) + .map(|attr| -> Result<_> { Ok(format_ident!( "{}", - expr_to_string(&attrs[index].meta.require_name_value()?.value)?, - span = attrs[index].span() + expr_to_string(&attr.meta.require_name_value()?.value)?, + span = attr.span() )) }) .transpose()?; diff --git a/crates/cxx-qt-gen/src/naming/rust.rs b/crates/cxx-qt-gen/src/naming/rust.rs index 11d0f6a29..5ecaa9506 100644 --- a/crates/cxx-qt-gen/src/naming/rust.rs +++ b/crates/cxx-qt-gen/src/naming/rust.rs @@ -51,7 +51,7 @@ fn qualify_type_path(ty_path: &TypePath, type_names: &TypeNames) -> Result // Inject the qualified prefix into the path if there is one if let Some(qualified_prefix) = qualified_prefix { for part in qualified_prefix.iter().rev() { - let segment: PathSegment = syn::parse_str(part).unwrap(); + let segment: PathSegment = syn::parse_str(part)?; ty_path.path.segments.insert(0, segment); } } diff --git a/crates/cxx-qt-gen/src/naming/type_names.rs b/crates/cxx-qt-gen/src/naming/type_names.rs index 9871dc0cb..b914b7d62 100644 --- a/crates/cxx-qt-gen/src/naming/type_names.rs +++ b/crates/cxx-qt-gen/src/naming/type_names.rs @@ -2,26 +2,18 @@ // SPDX-FileContributor: Leon Matthes // // SPDX-License-Identifier: MIT OR Apache-2.0 - +use super::Name; +use crate::syntax::attribute::attribute_get_path; +use crate::{ + parser::{cxxqtdata::ParsedCxxQtData, qobject::ParsedQObject}, + syntax::{expr::expr_to_string, foreignmod::foreign_mod_to_foreign_item_types}, +}; +use quote::{format_ident, quote}; use std::collections::{btree_map::Entry, BTreeMap, BTreeSet}; - -use quote::format_ident; use syn::{ - parse_quote, token::Brace, Attribute, Error, Ident, Item, ItemEnum, ItemForeignMod, ItemStruct, - Path, Result, + parse_quote, Attribute, Error, Ident, Item, ItemEnum, ItemForeignMod, ItemStruct, Path, Result, }; -use crate::{ - parser::qobject::ParsedQObject, - syntax::{ - attribute::attribute_find_path, expr::expr_to_string, - foreignmod::foreign_mod_to_foreign_item_types, - }, -}; - -use super::Name; -use crate::parser::cxxqtdata::ParsedCxxQtData; - /// The purpose of this struct is to store all nameable types. /// /// This is used by the generator phase to find types by their identifier, such that they can be @@ -159,27 +151,41 @@ impl TypeNames { bridge_namespace: Option<&str>, module_ident: &Ident, ) -> Result<()> { + // Find and register the QObjects in the bridge for qobject in cxx_qt_data.qobjects.iter() { self.populate_qobject(qobject)?; } + // Find and register the names of any QEnums in the bridge for qenum in &cxx_qt_data.qenums { self.insert(qenum.name.clone())?; } for extern_cxxqt in &cxx_qt_data.extern_cxxqt_blocks { - // TODO: Refactor, this is a hack to reconstruct the original ItemForeignMod - let foreign_mod = ItemForeignMod { - attrs: extern_cxxqt.attrs.clone(), - unsafety: None, - brace_token: Brace::default(), - items: extern_cxxqt.passthrough_items.clone(), - abi: syn::Abi { - extern_token: syn::parse_quote!(extern), - name: None, - }, + let namespace = if let Some(namespace) = &extern_cxxqt.namespace { + quote! { #[namespace = #namespace ] } + } else { + quote! {} + }; + + let items = extern_cxxqt.passthrough_items.clone(); + let foreign_mod: ItemForeignMod = parse_quote! { + #namespace + extern "C++" { + #(#items)* + } }; self.populate_from_foreign_mod_item(&foreign_mod, bridge_namespace, module_ident)?; + + // Find and register the names of any qobjects in extern "C++Qt" + for qobject in extern_cxxqt.qobjects.iter() { + self.insert(qobject.name.clone())?; + } + + // Find and register the names of any signals in extern "C++Qt" + for signal in extern_cxxqt.signals.iter() { + self.insert(signal.name.clone())?; + } } Ok(()) @@ -193,10 +199,8 @@ impl TypeNames { ) -> Result<()> { // Retrieve a namespace from the mod or the bridge let block_namespace = - if let Some(index) = attribute_find_path(&foreign_mod.attrs, &["namespace"]) { - Some(expr_to_string( - &foreign_mod.attrs[index].meta.require_name_value()?.value, - )?) + if let Some(attr) = attribute_get_path(&foreign_mod.attrs, &["namespace"]) { + Some(expr_to_string(&attr.meta.require_name_value()?.value)?) } else { bridge_namespace.map(str::to_owned) }; diff --git a/crates/cxx-qt-gen/src/parser/cxxqtdata.rs b/crates/cxx-qt-gen/src/parser/cxxqtdata.rs index e07645fe0..33bbb8294 100644 --- a/crates/cxx-qt-gen/src/parser/cxxqtdata.rs +++ b/crates/cxx-qt-gen/src/parser/cxxqtdata.rs @@ -6,19 +6,17 @@ use super::qnamespace::ParsedQNamespace; use super::trait_impl::TraitImpl; use crate::naming::cpp::err_unsupported_item; -use crate::syntax::attribute::{attribute_find_path, attribute_take_path}; -use crate::syntax::foreignmod::ForeignTypeIdentAlias; -use crate::syntax::path::path_compare_str; -use crate::syntax::safety::Safety; use crate::{ parser::{ externcxxqt::ParsedExternCxxQt, inherit::ParsedInheritedMethod, method::ParsedMethod, - qenum::ParsedQEnum, qobject::ParsedQObject, signals::ParsedSignal, + qenum::ParsedQEnum, qobject::ParsedQObject, require_attributes, signals::ParsedSignal, + }, + syntax::{ + attribute::attribute_get_path, expr::expr_to_string, foreignmod::ForeignTypeIdentAlias, + path::path_compare_str, safety::Safety, }, - syntax::expr::expr_to_string, }; -use syn::{ForeignItem, Ident, Item, ItemEnum, ItemForeignMod, ItemImpl, Result}; -use syn::{ItemMacro, Meta}; +use syn::{ForeignItem, Ident, Item, ItemEnum, ItemForeignMod, ItemImpl, ItemMacro, Meta, Result}; pub struct ParsedCxxQtData { /// Map of the QObjects defined in the module that will be used for code generation @@ -76,8 +74,8 @@ impl ParsedCxxQtData { } } - fn parse_enum(&mut self, mut item: ItemEnum) -> Result> { - if let Some(qenum_attribute) = attribute_take_path(&mut item.attrs, &["qenum"]) { + fn parse_enum(&mut self, item: ItemEnum) -> Result> { + if let Some(qenum_attribute) = attribute_get_path(&item.attrs, &["qenum"]) { // A Meta::Path indicates no arguments were provided to the enum // It only contains the "qenum" path and nothing else. let qobject: Option = if let Meta::Path(_) = qenum_attribute.meta { @@ -114,8 +112,11 @@ impl ParsedCxxQtData { return Ok(None); } "C++Qt" => { - self.extern_cxxqt_blocks - .push(ParsedExternCxxQt::parse(foreign_mod)?); + self.extern_cxxqt_blocks.push(ParsedExternCxxQt::parse( + foreign_mod, + &self.module_ident, + self.namespace.as_deref(), + )?); return Ok(None); } _others => {} @@ -126,8 +127,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)) + let attrs = require_attributes(&foreign_mod.attrs, &["namespace"])?; + + let namespace = attrs + .get("namespace") + .map(|attr| expr_to_string(&attr.meta.require_name_value()?.value)) .transpose()? .or_else(|| self.namespace.clone()); @@ -139,16 +143,16 @@ impl ParsedCxxQtData { for item in foreign_mod.items.drain(..) { match item { - ForeignItem::Fn(mut foreign_fn) => { + ForeignItem::Fn(foreign_fn) => { // Test if the function is a signal - if attribute_take_path(&mut foreign_fn.attrs, &["qsignal"]).is_some() { + if attribute_get_path(&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() { + } else if attribute_get_path(&foreign_fn.attrs, &["inherit"]).is_some() { let parsed_inherited_method = ParsedInheritedMethod::parse(foreign_fn, safe_call)?; @@ -399,11 +403,8 @@ mod tests { assert!(result.is_none()); assert_eq!(cxx_qt_data.extern_cxxqt_blocks.len(), 1); - assert_eq!(cxx_qt_data.extern_cxxqt_blocks[0].attrs.len(), 1); - assert_eq!( - cxx_qt_data.extern_cxxqt_blocks[0].passthrough_items.len(), - 1 - ); + assert!(cxx_qt_data.extern_cxxqt_blocks[0].namespace.is_some()); + assert_eq!(cxx_qt_data.extern_cxxqt_blocks[0].qobjects.len(), 1); assert_eq!(cxx_qt_data.extern_cxxqt_blocks[0].signals.len(), 1); assert!(cxx_qt_data.extern_cxxqt_blocks[0].unsafety.is_some()); } diff --git a/crates/cxx-qt-gen/src/parser/externcxxqt.rs b/crates/cxx-qt-gen/src/parser/externcxxqt.rs index 78cd31b24..45fe65043 100644 --- a/crates/cxx-qt-gen/src/parser/externcxxqt.rs +++ b/crates/cxx-qt-gen/src/parser/externcxxqt.rs @@ -4,31 +4,43 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 use crate::{ - parser::signals::ParsedSignal, - syntax::{attribute::attribute_find_path, safety::Safety}, + parser::{externqobject::ParsedExternQObject, require_attributes, signals::ParsedSignal}, + syntax::{attribute::attribute_get_path, expr::expr_to_string, safety::Safety}, }; -use syn::{spanned::Spanned, Attribute, Error, ForeignItem, ItemForeignMod, Result, Token}; - -#[cfg(test)] -use syn::ForeignItemType; +use syn::{spanned::Spanned, Error, ForeignItem, Ident, 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, + /// The namespace of the type in C++. + pub namespace: Option, /// Whether this block has an unsafe token pub unsafety: Option, /// Items which can be passed into the extern "C++Qt" block pub passthrough_items: Vec, /// Signals that need generation in the extern "C++Qt" block pub signals: Vec, + /// QObject types that need generation in the extern "C++Qt" block + pub qobjects: Vec, } impl ParsedExternCxxQt { - pub fn parse(mut foreign_mod: ItemForeignMod) -> Result { + pub fn parse( + mut foreign_mod: ItemForeignMod, + module_ident: &Ident, + parent_namespace: Option<&str>, + ) -> Result { + let attrs = require_attributes(&foreign_mod.attrs, &["namespace"])?; + + let namespace = attrs + .get("namespace") + .map(|attr| -> Result { + expr_to_string(&attr.meta.require_name_value()?.value) + }) + .transpose()?; + let mut extern_cxx_block = ParsedExternCxxQt { - attrs: foreign_mod.attrs.clone(), + namespace: namespace.clone(), unsafety: foreign_mod.unsafety, ..Default::default() }; @@ -42,12 +54,9 @@ impl ParsedExternCxxQt { // Parse any signals, other items are passed through for item in foreign_mod.items.drain(..) { match item { - ForeignItem::Fn(mut foreign_fn) => { + ForeignItem::Fn(foreign_fn) => { // Test if the function is a signal - if let Some(index) = attribute_find_path(&foreign_fn.attrs, &["qsignal"]) { - // Remove the signals attribute - foreign_fn.attrs.remove(index); - + if attribute_get_path(&foreign_fn.attrs, &["qsignal"]).is_some() { let mut signal = ParsedSignal::parse(foreign_fn, safe_call)?; // extern "C++Qt" signals are always inherit = true // as they always exist on an existing QObject @@ -59,16 +68,13 @@ impl ParsedExternCxxQt { .push(ForeignItem::Fn(foreign_fn)); } } - ForeignItem::Type(mut foreign_ty) => { + ForeignItem::Type(foreign_ty) => { // Test that there is a #[qobject] attribute on any type - if let Some(index) = attribute_find_path(&foreign_ty.attrs, &["qobject"]) { - // Remove the #[qobject] attribute - foreign_ty.attrs.remove(index); - - // Pass through the item as it's the same - extern_cxx_block - .passthrough_items - .push(ForeignItem::Type(foreign_ty)); + if attribute_get_path(&foreign_ty.attrs, &["qobject"]).is_some() { + let extern_ty = + ParsedExternQObject::parse(foreign_ty, module_ident, parent_namespace)?; + // Pass through types separately for generation + extern_cxx_block.qobjects.push(extern_ty); } else { return Err(Error::new( foreign_ty.span(), @@ -84,73 +90,72 @@ impl ParsedExternCxxQt { Ok(extern_cxx_block) } - - #[cfg(test)] - fn get_passthrough_foreign_type(&self) -> Result { - if let ForeignItem::Type(foreign_ty) = &self.passthrough_items[0] { - Ok(foreign_ty.clone()) - } else { - Err(Error::new_spanned( - &self.passthrough_items[0], - "Item should be `ForeignItem::Type`!", - )) - } - } } #[cfg(test)] mod tests { use super::*; + use quote::format_ident; 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" { - #[qobject] - type QPushButton; + let extern_cxx_qt = ParsedExternCxxQt::parse( + parse_quote! { + #[namespace = "rust"] + unsafe extern "C++Qt" { + #[qobject] + type QPushButton; - fn method(self: Pin<&mut QPushButton>); + fn method(self: Pin<&mut QPushButton>); - #[qsignal] - fn clicked(self: Pin<&mut QPushButton>, checked: bool); - } - }) + #[qsignal] + fn clicked(self: Pin<&mut QPushButton>, checked: bool); + } + }, + &format_ident!("qobject"), + None, + ) .unwrap(); - assert_eq!(extern_cxx_qt.attrs.len(), 1); - assert_eq!(extern_cxx_qt.passthrough_items.len(), 2); + assert!(extern_cxx_qt.namespace.is_some()); + assert_eq!(extern_cxx_qt.passthrough_items.len(), 1); + assert_eq!(extern_cxx_qt.qobjects.len(), 1); assert_eq!(extern_cxx_qt.signals.len(), 1); assert!(extern_cxx_qt.unsafety.is_some()); } #[test] fn test_extern_cxxqt_type_missing_qobject() { - let extern_cxx_qt = ParsedExternCxxQt::parse(parse_quote! { - unsafe extern "C++Qt" { - type QPushButton; - } - }); + let extern_cxx_qt = ParsedExternCxxQt::parse( + parse_quote! { + unsafe extern "C++Qt" { + type QPushButton; + } + }, + &format_ident!("qobject"), + None, + ); assert!(extern_cxx_qt.is_err()); } #[test] fn test_extern_cxxqt_type_qobject_attr() { - let extern_cxx_qt = ParsedExternCxxQt::parse(parse_quote! { - extern "C++Qt" { - #[qobject] - type QPushButton; - } - }) + let extern_cxx_qt = ParsedExternCxxQt::parse( + parse_quote! { + extern "C++Qt" { + #[qobject] + type QPushButton; + } + }, + &format_ident!("qobject"), + None, + ) .unwrap(); - assert_eq!(extern_cxx_qt.attrs.len(), 0); - assert_eq!(extern_cxx_qt.passthrough_items.len(), 1); - // Check that the attribute is removed - let foreign_ty = extern_cxx_qt.get_passthrough_foreign_type(); - assert!(foreign_ty.unwrap().attrs.is_empty()); + assert!(extern_cxx_qt.namespace.is_none()); + assert_eq!(extern_cxx_qt.qobjects.len(), 1); assert_eq!(extern_cxx_qt.signals.len(), 0); assert!(extern_cxx_qt.unsafety.is_none()); @@ -158,17 +163,19 @@ mod tests { #[test] fn test_extern_cxxqt_type_non_type() { - let extern_cxx_qt = ParsedExternCxxQt::parse(parse_quote! { - extern "C++Qt" { - fn myFunction(); - } - }) + let extern_cxx_qt = ParsedExternCxxQt::parse( + parse_quote! { + extern "C++Qt" { + fn myFunction(); + } + }, + &format_ident!("qobject"), + None, + ) .unwrap(); // Check that the non Type object is detected and error - let foreign_ty = extern_cxx_qt.get_passthrough_foreign_type(); - assert!(foreign_ty.is_err()); - - assert_eq!(extern_cxx_qt.signals.len(), 0); + assert!(extern_cxx_qt.qobjects.is_empty()); + assert!(extern_cxx_qt.signals.is_empty()); assert!(extern_cxx_qt.unsafety.is_none()); } } diff --git a/crates/cxx-qt-gen/src/parser/externqobject.rs b/crates/cxx-qt-gen/src/parser/externqobject.rs new file mode 100644 index 000000000..232b8a423 --- /dev/null +++ b/crates/cxx-qt-gen/src/parser/externqobject.rs @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2024 Klarälvdalens Datakonsult AB, a KDAB Group company +// SPDX-FileContributor: Ben Ford +// +// SPDX-License-Identifier: MIT OR Apache-2.0 +use crate::naming::Name; +use syn::{ForeignItemType, Ident, Result}; + +/// A representation of a QObject to be generated in an extern C++ block +pub struct ParsedExternQObject { + /// The name of the ExternQObject + pub name: Name, + /// Original declaration + pub declaration: ForeignItemType, +} + +impl ParsedExternQObject { + pub fn parse( + ty: ForeignItemType, + module_ident: &Ident, + parent_namespace: Option<&str>, + ) -> Result { + Ok(Self { + name: Name::from_ident_and_attrs( + &ty.ident, + &ty.attrs, + parent_namespace, + Some(module_ident), + )?, + declaration: ty, + }) + } +} diff --git a/crates/cxx-qt-gen/src/parser/inherit.rs b/crates/cxx-qt-gen/src/parser/inherit.rs index 8340301e6..55cab7c3d 100644 --- a/crates/cxx-qt-gen/src/parser/inherit.rs +++ b/crates/cxx-qt-gen/src/parser/inherit.rs @@ -3,9 +3,8 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 -use crate::parser::method::MethodFields; -use crate::parser::{check_safety, separate_docs}; -use crate::syntax::{attribute::attribute_take_path, safety::Safety}; +use crate::parser::{check_safety, extract_docs, method::MethodFields, require_attributes}; +use crate::syntax::safety::Safety; use core::ops::Deref; use quote::format_ident; use syn::{Attribute, ForeignItemFn, Ident, Result}; @@ -19,17 +18,16 @@ pub struct ParsedInheritedMethod { } impl ParsedInheritedMethod { - pub fn parse(mut method: ForeignItemFn, safety: Safety) -> Result { - check_safety(&method, &safety)?; - - let docs = separate_docs(&mut method); - let mut fields = MethodFields::parse(method)?; + const ALLOWED_ATTRS: [&'static str; 5] = + ["cxx_name", "rust_name", "qinvokable", "doc", "inherit"]; - // This block seems unnecessary but since attrs are passed through on generator/rust/inherit.rs a duplicate attr would occur without it - attribute_take_path(&mut fields.method.attrs, &["cxx_name"]); + pub fn parse(method: ForeignItemFn, safety: Safety) -> Result { + check_safety(&method, &safety)?; + require_attributes(&method.attrs, &Self::ALLOWED_ATTRS)?; + let docs = extract_docs(&method.attrs); Ok(Self { - method_fields: fields, + method_fields: MethodFields::parse(method)?, docs, }) } diff --git a/crates/cxx-qt-gen/src/parser/method.rs b/crates/cxx-qt-gen/src/parser/method.rs index 47a829786..0c76d8325 100644 --- a/crates/cxx-qt-gen/src/parser/method.rs +++ b/crates/cxx-qt-gen/src/parser/method.rs @@ -2,18 +2,14 @@ // SPDX-FileContributor: Andrew Hayzen // // SPDX-License-Identifier: MIT OR Apache-2.0 - use crate::{ naming::Name, - parser::parameter::ParsedFunctionParameter, - syntax::{attribute::attribute_take_path, safety::Safety}, + parser::{check_safety, parameter::ParsedFunctionParameter, require_attributes}, + syntax::{foreignmod, safety::Safety, types}, }; use core::ops::Deref; -use std::collections::HashSet; -use syn::{Error, ForeignItemFn, Ident, Result}; - -use crate::parser::check_safety; -use crate::syntax::{foreignmod, types}; +use std::collections::{BTreeMap, HashSet}; +use syn::{Attribute, ForeignItemFn, Ident, Result}; /// Describes a C++ specifier for the Q_INVOKABLE #[derive(Eq, Hash, PartialEq)] @@ -24,12 +20,26 @@ pub enum ParsedQInvokableSpecifiers { } impl ParsedQInvokableSpecifiers { - fn as_str_slice(&self) -> &[&str] { + fn as_str(&self) -> &str { match self { - ParsedQInvokableSpecifiers::Final => &["cxx_final"], - ParsedQInvokableSpecifiers::Override => &["cxx_override"], - ParsedQInvokableSpecifiers::Virtual => &["cxx_virtual"], + ParsedQInvokableSpecifiers::Final => "cxx_final", + ParsedQInvokableSpecifiers::Override => "cxx_override", + ParsedQInvokableSpecifiers::Virtual => "cxx_virtual", + } + } + + fn from_attrs(attrs: BTreeMap<&str, &Attribute>) -> HashSet { + let mut output = HashSet::new(); + for specifier in [ + ParsedQInvokableSpecifiers::Final, + ParsedQInvokableSpecifiers::Override, + ParsedQInvokableSpecifiers::Virtual, + ] { + if attrs.contains_key(specifier.as_str()) { + output.insert(specifier); + } } + output } } @@ -46,6 +56,16 @@ pub struct ParsedMethod { } impl ParsedMethod { + const ALLOWED_ATTRS: [&'static str; 7] = [ + "cxx_name", + "rust_name", + "qinvokable", + "cxx_final", + "cxx_override", + "cxx_virtual", + "doc", + ]; + #[cfg(test)] pub fn mock_qinvokable(method: &ForeignItemFn) -> Self { Self { @@ -83,31 +103,12 @@ impl ParsedMethod { pub fn parse(method: ForeignItemFn, safety: Safety) -> Result { check_safety(&method, &safety)?; - - let mut fields = MethodFields::parse(method)?; - - if fields.name.namespace().is_some() { - return Err(Error::new_spanned( - fields.method.sig.ident, - "Methods / QInvokables cannot have a namespace attribute", - )); - } + let fields = MethodFields::parse(method)?; + let attrs = require_attributes(&fields.method.attrs, &Self::ALLOWED_ATTRS)?; // Determine if the method is invokable - let is_qinvokable = - attribute_take_path(&mut fields.method.attrs, &["qinvokable"]).is_some(); - - // Parse any C++ specifiers - let mut specifiers = HashSet::new(); - for specifier in [ - ParsedQInvokableSpecifiers::Final, - ParsedQInvokableSpecifiers::Override, - ParsedQInvokableSpecifiers::Virtual, - ] { - if attribute_take_path(&mut fields.method.attrs, specifier.as_str_slice()).is_some() { - specifiers.insert(specifier); // Should a fn be able to be Override AND Virtual? - } - } + let is_qinvokable = attrs.contains_key("qinvokable"); + let specifiers = ParsedQInvokableSpecifiers::from_attrs(attrs); Ok(Self { method_fields: fields, diff --git a/crates/cxx-qt-gen/src/parser/mod.rs b/crates/cxx-qt-gen/src/parser/mod.rs index 6376cc81d..14eef3611 100644 --- a/crates/cxx-qt-gen/src/parser/mod.rs +++ b/crates/cxx-qt-gen/src/parser/mod.rs @@ -6,6 +6,7 @@ pub mod constructor; pub mod cxxqtdata; pub mod externcxxqt; +pub mod externqobject; pub mod inherit; pub mod method; pub mod parameter; @@ -16,20 +17,20 @@ pub mod qobject; pub mod signals; pub mod trait_impl; -use crate::syntax::safety::Safety; use crate::{ - // Used for error handling when resolving the namespace of the qenum. naming::TypeNames, - syntax::{attribute::attribute_take_path, expr::expr_to_string}, + syntax::{expr::expr_to_string, path::path_compare_str, safety::Safety}, }; use cxxqtdata::ParsedCxxQtData; +use std::collections::BTreeMap; use syn::{ punctuated::Punctuated, spanned::Spanned, token::{Brace, Semi}, - Attribute, Error, ForeignItemFn, Ident, Item, ItemMod, Meta, Result, Token, + Attribute, Error, ForeignItemFn, Ident, Item, ItemMod, Meta, Result, Token, Visibility, }; +/// Validates that an invokable is either unsafe, or is in an unsafe extern block fn check_safety(method: &ForeignItemFn, safety: &Safety) -> Result<()> { if safety == &Safety::Unsafe && method.sig.unsafety.is_none() { Err(Error::new( @@ -42,14 +43,70 @@ fn check_safety(method: &ForeignItemFn, safety: &Safety) -> Result<()> { } /// Iterate the attributes of the method to extract Doc attributes (doc comments are parsed as this) -/// -/// Note: This modifies the method by removing those doc attributes -pub fn separate_docs(method: &mut ForeignItemFn) -> Vec { - let mut docs = vec![]; - while let Some(doc) = attribute_take_path(&mut method.attrs, &["doc"]) { - docs.push(doc); +pub fn extract_docs(attrs: &[Attribute]) -> Vec { + attrs + .iter() + .filter(|attr| path_compare_str(attr.meta.path(), &["doc"])) + .cloned() + .collect() +} + +/// Splits a path by :: separators e.g. "cxx_qt::bridge" becomes ["cxx_qt", "bridge"] +fn split_path(path_str: &str) -> Vec<&str> { + let path = if path_str.contains("::") { + path_str.split("::").collect::>() + } else { + vec![path_str] + }; + path +} + +/// Collects a Map of all attributes found from the allowed list +/// Will error if an attribute which is not in the allowed list is found +pub fn require_attributes<'a>( + attrs: &'a [Attribute], + allowed: &'a [&str], +) -> Result> { + let mut output = BTreeMap::default(); + for attr in attrs { + let index = allowed + .iter() + .position(|string| path_compare_str(attr.meta.path(), &split_path(string))); + if let Some(index) = index { + output.insert(allowed[index], attr); // Doesn't error on duplicates + } else { + return Err(Error::new( + attr.span(), + format!( + "Unsupported attribute! The only attributes allowed on this item are\n{}", + allowed.join(", ") + ), + )); + } + } + Ok(output) +} + +/// Struct representing the necessary components of a cxx mod to be passed through to generation +pub struct PassthroughMod { + pub(crate) items: Option>, + pub(crate) docs: Vec, + pub(crate) module_ident: Ident, + pub(crate) vis: Visibility, +} + +impl PassthroughMod { + /// Parse an item mod into it's components + pub fn parse(module: ItemMod) -> Self { + let items = module.content.map(|(_, items)| items); + + Self { + items, + docs: extract_docs(&module.attrs), + module_ident: module.ident, + vis: module.vis, + } } - docs } /// A struct representing a module block with CXX-Qt relevant [syn::Item]'s @@ -58,7 +115,7 @@ pub fn separate_docs(method: &mut ForeignItemFn) -> Vec { /// [syn::Item]'s that are not handled specially by CXX-Qt are passed through for CXX to process. pub struct Parser { /// The module which unknown (eg CXX) blocks are stored into - pub(crate) passthrough_module: ItemMod, + pub(crate) passthrough_module: PassthroughMod, /// Any CXX-Qt data that needs generation later pub(crate) cxx_qt_data: ParsedCxxQtData, /// all type names that were found in this module, including CXX types @@ -67,11 +124,12 @@ pub struct Parser { impl Parser { fn parse_mod_attributes(module: &mut ItemMod) -> Result> { + let attrs = require_attributes(&module.attrs, &["doc", "cxx_qt::bridge"])?; let mut namespace = None; - // Remove the cxx_qt::bridge attribute - if let Some(attr) = attribute_take_path(&mut module.attrs, &["cxx_qt", "bridge"]) { - // If we are no #[cxx_qt::bridge] but #[cxx_qt::bridge(A = B)] then process + // Check for the cxx_qt::bridge attribute + if let Some(attr) = attrs.get("cxx_qt::bridge") { + // If we are not #[cxx_qt::bridge] but #[cxx_qt::bridge(A = B)] then process if !matches!(attr.meta, Meta::Path(_)) { let nested = attr.parse_args_with(Punctuated::::parse_terminated)?; @@ -165,7 +223,7 @@ impl Parser { // Return the successful Parser object Ok(Self { - passthrough_module: module, + passthrough_module: PassthroughMod::parse(module), type_names, cxx_qt_data, }) @@ -192,28 +250,15 @@ mod tests { mod ffi {} }; let parser = Parser::from(module).unwrap(); - let expected_module: ItemMod = parse_quote! { - mod ffi; - }; - assert_eq!(parser.passthrough_module, expected_module); + + assert!(parser.passthrough_module.items.is_none()); + assert!(parser.passthrough_module.docs.is_empty()); + assert_eq!(parser.passthrough_module.module_ident, "ffi"); + assert_eq!(parser.passthrough_module.vis, Visibility::Inherited); assert_eq!(parser.cxx_qt_data.namespace, None); assert_eq!(parser.cxx_qt_data.qobjects.len(), 0); } - #[test] - fn test_bridge_cxx_file_stem() { - let module: ItemMod = parse_quote! { - #[cxx_qt::bridge(cxx_file_stem = "stem")] - mod ffi { - extern "Rust" { - fn test(); - } - } - }; - let parser = Parser::from(module); - assert!(parser.is_err()); - } - #[test] fn test_incorrect_bridge_args() { let module: ItemMod = parse_quote! { @@ -248,14 +293,10 @@ mod tests { } }; let parser = Parser::from(module).unwrap(); - let expected_module: ItemMod = parse_quote! { - mod ffi { - extern "Rust" { - fn test(); - } - } - }; - assert_eq!(parser.passthrough_module, expected_module); + assert_eq!(parser.passthrough_module.items.unwrap().len(), 1); + assert!(parser.passthrough_module.docs.is_empty()); + assert_eq!(parser.passthrough_module.module_ident, "ffi"); + assert_eq!(parser.passthrough_module.vis, Visibility::Inherited); assert_eq!(parser.cxx_qt_data.namespace, None); assert_eq!(parser.cxx_qt_data.qobjects.len(), 0); } @@ -278,11 +319,10 @@ mod tests { }; let parser = Parser::from(module.clone()).unwrap(); - assert_ne!(parser.passthrough_module, module); - - assert_eq!(parser.passthrough_module.attrs.len(), 0); - assert_eq!(parser.passthrough_module.ident, "ffi"); - assert!(parser.passthrough_module.content.is_none()); + assert!(parser.passthrough_module.items.is_none()); + assert!(parser.passthrough_module.docs.is_empty()); + assert_eq!(parser.passthrough_module.module_ident, "ffi"); + assert_eq!(parser.passthrough_module.vis, Visibility::Inherited); assert_eq!(parser.cxx_qt_data.namespace, Some("cxx_qt".to_owned())); assert_eq!(parser.cxx_qt_data.qobjects.len(), 1); assert_eq!(parser.type_names.num_types(), 18); @@ -306,6 +346,7 @@ mod tests { fn test_parser_from_cxx_and_cxx_qt_items() { let module: ItemMod = parse_quote! { #[cxx_qt::bridge] + /// A cxx_qt::bridge module mod ffi { extern "RustQt" { #[qobject] @@ -324,11 +365,10 @@ mod tests { }; let parser = Parser::from(module.clone()).unwrap(); - assert_ne!(parser.passthrough_module, module); - - assert_eq!(parser.passthrough_module.attrs.len(), 0); - assert_eq!(parser.passthrough_module.ident, "ffi"); - assert_eq!(parser.passthrough_module.content.unwrap().1.len(), 1); + assert_eq!(parser.passthrough_module.items.unwrap().len(), 1); + assert_eq!(parser.passthrough_module.docs.len(), 1); + assert_eq!(parser.passthrough_module.module_ident, "ffi"); + assert_eq!(parser.passthrough_module.vis, Visibility::Inherited); assert_eq!(parser.cxx_qt_data.namespace, None); assert_eq!(parser.cxx_qt_data.qobjects.len(), 1); } @@ -356,6 +396,15 @@ mod tests { } } } + { + // Cxx_file_stem is deprecated + #[cxx_qt::bridge(cxx_file_stem = "stem")] + mod ffi { + extern "Rust" { + fn test(); + } + } + } } } diff --git a/crates/cxx-qt-gen/src/parser/parameter.rs b/crates/cxx-qt-gen/src/parser/parameter.rs index 56ac432f6..4faf75078 100644 --- a/crates/cxx-qt-gen/src/parser/parameter.rs +++ b/crates/cxx-qt-gen/src/parser/parameter.rs @@ -61,9 +61,7 @@ impl ParsedFunctionParameter { let missing_self_arg = "First argument must be a supported `self` receiver type!\nUse `&self` or `self: Pin<&mut Self>` instead."; match iter.next() { Some(FnArg::Receiver(Receiver { - reference: None, - ty, - .. + reference: _, ty, .. })) if types::is_pin_of_self(ty) => Ok(()), // Okay, found a Pin<&Self> or Pin<&mut Self> Some(FnArg::Receiver(Receiver { reference: Some(_), // `self` needs to be by-ref, by-value is not supported. diff --git a/crates/cxx-qt-gen/src/parser/property.rs b/crates/cxx-qt-gen/src/parser/property.rs index 152785625..a7ea6848e 100644 --- a/crates/cxx-qt-gen/src/parser/property.rs +++ b/crates/cxx-qt-gen/src/parser/property.rs @@ -13,6 +13,9 @@ use syn::{ Attribute, Expr, Ident, Meta, MetaNameValue, Result, Token, Type, }; +#[cfg(test)] +use syn::ItemStruct; + #[derive(Debug, Clone, PartialEq, Eq)] /// Enum representing the possible states of a flag passed to a QProperty /// Auto is the state where a user passed for example `read` but no custom function @@ -99,7 +102,7 @@ fn parse_meta(meta: Meta) -> Result<(Ident, Option)> { } impl ParsedQProperty { - pub fn parse(attr: Attribute) -> Result { + pub fn parse(attr: &Attribute) -> Result { attr.parse_args_with(|input: ParseStream| -> Result { let ty = input.parse()?; let _comma = input.parse::()?; @@ -222,6 +225,10 @@ impl ParsedQProperty { }) } } +#[cfg(test)] +pub fn mock_property(mut input: ItemStruct) -> ParsedQProperty { + ParsedQProperty::parse(&input.attrs.remove(0)).unwrap() +} #[cfg(test)] mod tests { @@ -232,11 +239,11 @@ mod tests { #[test] fn test_parse_named_property() { - let mut input: ItemStruct = parse_quote! { + let input: ItemStruct = parse_quote! { #[qproperty(T, name, cxx_name = "myName", rust_name = "my_name")] struct MyStruct; }; - let property = ParsedQProperty::parse(input.attrs.remove(0)).unwrap(); + let property = mock_property(input); assert_eq!(property.name.cxx_unqualified(), "myName"); assert_eq!(property.name.rust_unqualified(), "my_name"); @@ -245,7 +252,7 @@ mod tests { #[test] fn test_parse_invalid() { assert_parse_errors! { - ParsedQProperty::parse => + |attr| ParsedQProperty::parse(&attr) => // Non-constant property with constant flag { #[qproperty(T, name, READ, WRITE, NOTIFY, CONSTANT)] } @@ -278,43 +285,43 @@ mod tests { #[test] fn test_parse_constant() { - let mut input: ItemStruct = parse_quote! { + let input: ItemStruct = parse_quote! { #[qproperty(T, name, READ, CONSTANT)] struct MyStruct; }; - let property = ParsedQProperty::parse(input.attrs.remove(0)).unwrap(); + let property = mock_property(input); assert!(property.flags.constant); } #[test] fn test_parse_property() { - let mut input: ItemStruct = parse_quote! { + let input: ItemStruct = parse_quote! { #[qproperty(T, name, READ, WRITE = myGetter,)] struct MyStruct; }; - let property = ParsedQProperty::parse(input.attrs.remove(0)).unwrap(); + let property = mock_property(input); assert_eq!(property.name.rust_unqualified(), "name"); assert_eq!(property.ty, parse_quote! { T }); } #[test] fn test_parse_flags_read() { - let mut input: ItemStruct = parse_quote! { + let input: ItemStruct = parse_quote! { #[qproperty(T, name, READ)] struct MyStruct; }; - let property = ParsedQProperty::parse(input.attrs.remove(0)).unwrap(); + let property = mock_property(input); assert_eq!(property.name.rust_unqualified(), "name"); assert_eq!(property.ty, parse_quote! { T }); } #[test] fn test_parse_flags_all() { - let mut input: ItemStruct = parse_quote! { + let input: ItemStruct = parse_quote! { #[qproperty(T, name, READ, WRITE, NOTIFY, REQUIRED, RESET = my_reset, FINAL)] struct MyStruct; }; - let property = ParsedQProperty::parse(input.attrs.remove(0)).unwrap(); + let property = mock_property(input); assert_eq!(property.name.rust_unqualified(), "name"); assert_eq!(property.ty, parse_quote! { T }); @@ -334,11 +341,11 @@ mod tests { #[test] fn test_parse_flags_kw() { - let mut input: ItemStruct = parse_quote! { + let input: ItemStruct = parse_quote! { #[qproperty(T, name, READ = my_getter, WRITE, NOTIFY = my_notifier)] struct MyStruct; }; - let property = ParsedQProperty::parse(input.attrs.remove(0)).unwrap(); + let property = mock_property(input); assert_eq!(property.name.rust_unqualified(), "name"); assert_eq!(property.ty, parse_quote! { T }); diff --git a/crates/cxx-qt-gen/src/parser/qenum.rs b/crates/cxx-qt-gen/src/parser/qenum.rs index 08fe86164..473a776ac 100644 --- a/crates/cxx-qt-gen/src/parser/qenum.rs +++ b/crates/cxx-qt-gen/src/parser/qenum.rs @@ -3,11 +3,10 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 +use crate::{naming::Name, parser::require_attributes, syntax::path::path_compare_str}; use quote::ToTokens; use syn::{Ident, ItemEnum, Result, Variant}; -use crate::{naming::Name, syntax::path::path_compare_str}; - pub struct ParsedQEnum { /// The name of the QObject pub name: Name, @@ -20,6 +19,7 @@ pub struct ParsedQEnum { } impl ParsedQEnum { + const ALLOWED_ATTRS: [&'static str; 5] = ["doc", "cxx_name", "rust_name", "namespace", "qenum"]; fn parse_variant(variant: &Variant) -> Result { fn err(spanned: &impl ToTokens, message: &str) -> Result { Err(syn::Error::new_spanned(spanned, message)) @@ -54,6 +54,7 @@ impl ParsedQEnum { parent_namespace: Option<&str>, module: &Ident, ) -> Result { + require_attributes(&qenum.attrs, &Self::ALLOWED_ATTRS)?; if qenum.variants.is_empty() { return Err(syn::Error::new_spanned( qenum, @@ -71,17 +72,6 @@ impl ParsedQEnum { )); } - if let Some(attr) = qenum.attrs.iter().find(|attr| { - !["doc", "namespace", "cxx_name", "rust_name"] - .iter() - .any(|allowed_attr| path_compare_str(attr.path(), &[allowed_attr])) - }) { - return Err(syn::Error::new_spanned( - attr, - "Additional attributes are not allowed on #[qenum] enums!", - )); - } - let variants = qenum .variants .iter() diff --git a/crates/cxx-qt-gen/src/parser/qnamespace.rs b/crates/cxx-qt-gen/src/parser/qnamespace.rs index cff7a9161..ab483ec20 100644 --- a/crates/cxx-qt-gen/src/parser/qnamespace.rs +++ b/crates/cxx-qt-gen/src/parser/qnamespace.rs @@ -3,10 +3,9 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 +use crate::parser::require_attributes; use syn::{ItemMacro, LitStr, Result}; -use crate::syntax::attribute::attribute_take_path; - pub struct ParsedQNamespace { /// The name of the namespace pub namespace: String, @@ -15,7 +14,8 @@ pub struct ParsedQNamespace { } impl ParsedQNamespace { - pub fn parse(mut mac: ItemMacro) -> Result { + pub fn parse(mac: ItemMacro) -> Result { + let attrs = require_attributes(&mac.attrs, &["qml_element"])?; let namespace_literal: LitStr = syn::parse2(mac.mac.tokens)?; let namespace = namespace_literal.value(); if namespace.contains(char::is_whitespace) { @@ -31,14 +31,7 @@ impl ParsedQNamespace { )); } - let qml_element = attribute_take_path(&mut mac.attrs, &["qml_element"]).is_some(); - - if let Some(attr) = mac.attrs.first() { - return Err(syn::Error::new_spanned( - attr, - "qnamespace! macro must not have any attributes other than qml_element!", - )); - } + let qml_element = attrs.contains_key("qml_element"); if let Some(ident) = mac.ident { return Err(syn::Error::new_spanned( diff --git a/crates/cxx-qt-gen/src/parser/qobject.rs b/crates/cxx-qt-gen/src/parser/qobject.rs index 36564af1e..0b9674897 100644 --- a/crates/cxx-qt-gen/src/parser/qobject.rs +++ b/crates/cxx-qt-gen/src/parser/qobject.rs @@ -5,10 +5,8 @@ use crate::{ naming::Name, - parser::property::ParsedQProperty, - syntax::{ - attribute::attribute_take_path, expr::expr_to_string, foreignmod::ForeignTypeIdentAlias, - }, + parser::{property::ParsedQProperty, require_attributes}, + syntax::{expr::expr_to_string, foreignmod::ForeignTypeIdentAlias, path::path_compare_str}, }; #[cfg(test)] use quote::format_ident; @@ -46,6 +44,18 @@ pub struct ParsedQObject { } impl ParsedQObject { + const ALLOWED_ATTRS: [&'static str; 10] = [ + "cxx_name", + "rust_name", + "namespace", + "doc", + "qobject", + "base", + "qml_element", + "qml_uncreatable", + "qml_singleton", + "qproperty", + ]; #[cfg(test)] pub fn mock() -> Self { ParsedQObject { @@ -65,12 +75,15 @@ impl ParsedQObject { /// Parse a ForeignTypeIdentAlias into a [ParsedQObject] with the index of the #[qobject] specified pub fn parse( - mut declaration: ForeignTypeIdentAlias, + declaration: ForeignTypeIdentAlias, namespace: Option<&str>, module: &Ident, ) -> Result { - let has_qobject_macro = attribute_take_path(&mut declaration.attrs, &["qobject"]).is_some(); - let base_class = attribute_take_path(&mut declaration.attrs, &["base"]) + let attributes = require_attributes(&declaration.attrs, &Self::ALLOWED_ATTRS)?; + let has_qobject_macro = attributes.contains_key("qobject"); + + let base_class = attributes + .get("base") .map(|attr| -> Result { let expr = &attr.meta.require_name_value()?.value; if let Expr::Path(path_expr) = expr { @@ -85,8 +98,6 @@ impl ParsedQObject { .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, @@ -102,11 +113,11 @@ impl ParsedQObject { )?; // Find any QML metadata - let qml_metadata = Self::parse_qml_metadata(&name, &mut declaration.attrs)?; + let qml_metadata = Self::parse_qml_metadata(&name, &declaration.attrs)?; // Parse any properties in the type // and remove the #[qproperty] attribute - let properties = Self::parse_property_attributes(&mut declaration.attrs)?; + let properties = Self::parse_property_attributes(&declaration.attrs)?; let inner = declaration.ident_right.clone(); Ok(Self { @@ -120,47 +131,35 @@ impl ParsedQObject { }) } - fn parse_qml_metadata( - name: &Name, - attrs: &mut Vec, - ) -> Result> { - // Find if there is a qml_element attribute - if let Some(attr) = attribute_take_path(attrs, &["qml_element"]) { + fn parse_qml_metadata(name: &Name, attrs: &[Attribute]) -> Result> { + let attributes = require_attributes(attrs, &Self::ALLOWED_ATTRS)?; + if let Some(attr) = attributes.get("qml_element") { // Extract the name of the qml_element from macro, else use the c++ name // This will use the name provided by cxx_name if that attr was present - let name = match attr.meta { + let name = match &attr.meta { Meta::NameValue(name_value) => expr_to_string(&name_value.value)?, _ => name.cxx_unqualified(), }; - - // Determine if this element is uncreatable - let uncreatable = attribute_take_path(attrs, &["qml_uncreatable"]).is_some(); - - // Determine if this element is a singleton - let singleton = attribute_take_path(attrs, &["qml_singleton"]).is_some(); - + let uncreatable = attributes.contains_key("qml_uncreatable"); + let singleton = attributes.contains_key("qml_singleton"); return Ok(Some(QmlElementMetadata { name, uncreatable, singleton, })); } - Ok(None) } - fn parse_property_attributes(attrs: &mut Vec) -> Result> { - let mut properties = vec![]; - - // Note that once extract_if is stable, this would allow for comparing all the - // elements once using path_compare_str and then building ParsedQProperty - // from the extracted elements. + fn parse_property_attributes(attrs: &[Attribute]) -> Result> { + // Once extract_if is stable, this would allow comparing all the elements using + // path_compare_str and building ParsedQProperty from the extracted elements. // https://doc.rust-lang.org/nightly/std/vec/struct.Vec.html#method.extract_if - while let Some(attr) = attribute_take_path(attrs, &["qproperty"]) { - properties.push(ParsedQProperty::parse(attr)?); - } - - Ok(properties) + attrs + .iter() + .filter(|attr| path_compare_str(attr.meta.path(), &["qproperty"])) + .map(ParsedQProperty::parse) + .collect::>>() } } diff --git a/crates/cxx-qt-gen/src/parser/signals.rs b/crates/cxx-qt-gen/src/parser/signals.rs index 4e7574af5..956a5208d 100644 --- a/crates/cxx-qt-gen/src/parser/signals.rs +++ b/crates/cxx-qt-gen/src/parser/signals.rs @@ -2,14 +2,12 @@ // SPDX-FileContributor: Andrew Hayzen // // SPDX-License-Identifier: MIT OR Apache-2.0 - -use crate::syntax::{attribute::attribute_take_path, path::path_compare_str, safety::Safety}; +use crate::{ + parser::{check_safety, extract_docs, method::MethodFields, require_attributes}, + syntax::{path::path_compare_str, safety::Safety}, +}; use core::ops::Deref; use syn::{spanned::Spanned, Attribute, Error, ForeignItemFn, Result, Visibility}; - -use crate::parser::method::MethodFields; -use crate::parser::{check_safety, separate_docs}; - #[derive(Clone)] /// Describes an individual Signal pub struct ParsedSignal { @@ -24,24 +22,20 @@ pub struct ParsedSignal { } impl ParsedSignal { + const ALLOWED_ATTRS: [&'static str; 5] = ["cxx_name", "rust_name", "inherit", "doc", "qsignal"]; + #[cfg(test)] /// Test fn for creating a mocked signal from a method body pub fn mock(method: &ForeignItemFn) -> Self { Self::parse(method.clone(), Safety::Safe).unwrap() } - pub fn parse(mut method: ForeignItemFn, safety: Safety) -> Result { + pub fn parse(method: ForeignItemFn, safety: Safety) -> Result { check_safety(&method, &safety)?; - let docs = separate_docs(&mut method); - let mut fields = MethodFields::parse(method)?; - - if fields.name.namespace().is_some() { - return Err(Error::new_spanned( - fields.method.sig.ident, - "Signals cannot have a namespace attribute!", - )); - } + let docs = extract_docs(&method.attrs); + let fields = MethodFields::parse(method)?; + let attrs = require_attributes(&fields.method.attrs, &Self::ALLOWED_ATTRS)?; if !fields.mutable { return Err(Error::new( @@ -50,7 +44,8 @@ impl ParsedSignal { )); } - let inherit = attribute_take_path(&mut fields.method.attrs, &["inherit"]).is_some(); + let inherit = attrs.contains_key("inherit"); + let private = if let Visibility::Restricted(vis_restricted) = &fields.method.vis { path_compare_str(&vis_restricted.path, &["self"]) } else { @@ -150,12 +145,9 @@ mod tests { #[inherit] fn ready(self: Pin<&mut MyObject>); }; - let signal = ParsedSignal::parse(method, Safety::Safe).unwrap(); + let signal = ParsedSignal::parse(method.clone(), Safety::Safe).unwrap(); - let expected_method: ForeignItemFn = parse_quote! { - fn ready(self: Pin<&mut MyObject>); - }; - assert_eq!(signal.method, expected_method); + assert_eq!(signal.method, method); assert_eq!(signal.qobject_ident, format_ident!("MyObject")); assert!(signal.mutable); assert_eq!(signal.parameters, vec![]); diff --git a/crates/cxx-qt-gen/src/syntax/attribute.rs b/crates/cxx-qt-gen/src/syntax/attribute.rs index fb9154687..2d947705c 100644 --- a/crates/cxx-qt-gen/src/syntax/attribute.rs +++ b/crates/cxx-qt-gen/src/syntax/attribute.rs @@ -6,20 +6,11 @@ use crate::syntax::path::path_compare_str; use syn::Attribute; -/// Returns the index of the first [syn::Attribute] that matches a given path -pub fn attribute_find_path(attrs: &[Attribute], path: &[&str]) -> Option { - for (i, attr) in attrs.iter().enumerate() { - if path_compare_str(attr.meta.path(), path) { - return Some(i); - } - } - - None -} - -/// Takes and returns the first [syn::Attribute] that matches a given path -pub fn attribute_take_path(attrs: &mut Vec, path: &[&str]) -> Option { - attribute_find_path(attrs, path).map(|index| attrs.remove(index)) +/// Returns the first [syn::Attribute] that matches a given path +pub fn attribute_get_path<'a>(attrs: &'a [Attribute], path: &[&str]) -> Option<&'a Attribute> { + attrs + .iter() + .find(|attr| path_compare_str(attr.meta.path(), path)) } #[cfg(test)] @@ -29,7 +20,7 @@ mod tests { use syn::{parse_quote, ItemMod}; #[test] - fn test_attribute_find_path() { + fn test_attribute_get_path() { let module: ItemMod = parse_quote! { #[qinvokable] #[cxx_qt::bridge] @@ -38,24 +29,9 @@ mod tests { mod module; }; - assert!(attribute_find_path(&module.attrs, &["qinvokable"]).is_some()); - assert!(attribute_find_path(&module.attrs, &["cxx_qt", "bridge"]).is_some()); - assert!(attribute_find_path(&module.attrs, &["cxx_qt", "object"]).is_some()); - assert!(attribute_find_path(&module.attrs, &["cxx_qt", "missing"]).is_none()); - } - - #[test] - fn test_attribute_take_path() { - let mut module: ItemMod = parse_quote! { - #[qinvokable] - #[cxx_qt::bridge] - #[cxx_qt::object(MyObject)] - #[cxx_qt::bridge(namespace = "my::namespace")] - mod module; - }; - - assert_eq!(module.attrs.len(), 4); - assert!(attribute_take_path(&mut module.attrs, &["qinvokable"]).is_some()); - assert_eq!(module.attrs.len(), 3); + assert!(attribute_get_path(&module.attrs, &["qinvokable"]).is_some()); + assert!(attribute_get_path(&module.attrs, &["cxx_qt", "bridge"]).is_some()); + assert!(attribute_get_path(&module.attrs, &["cxx_qt", "object"]).is_some()); + assert!(attribute_get_path(&module.attrs, &["cxx_qt", "missing"]).is_none()); } } diff --git a/crates/cxx-qt-gen/src/syntax/lifetimes.rs b/crates/cxx-qt-gen/src/syntax/lifetimes.rs index 96f8246e4..8f48a218e 100644 --- a/crates/cxx-qt-gen/src/syntax/lifetimes.rs +++ b/crates/cxx-qt-gen/src/syntax/lifetimes.rs @@ -89,19 +89,19 @@ mod tests { #[test] fn extract_no_lifetimes() { - assert_no_lifetimes! { () }; - assert_no_lifetimes! { T }; - assert_no_lifetimes! { T }; - assert_no_lifetimes! { *mut X }; - assert_no_lifetimes! { *const X }; - assert_no_lifetimes! { Pin<*mut T> }; - assert_no_lifetimes! { &T }; - assert_no_lifetimes! { &mut T }; - assert_no_lifetimes! { [T] }; - assert_no_lifetimes! { [T;4] }; - assert_no_lifetimes! { (X, Y) }; - assert_no_lifetimes! { (Y) }; - assert_no_lifetimes! { std::collections::Vec }; + assert_no_lifetimes! { () } + assert_no_lifetimes! { T } + assert_no_lifetimes! { T } + assert_no_lifetimes! { *mut X } + assert_no_lifetimes! { *const X } + assert_no_lifetimes! { Pin<*mut T> } + assert_no_lifetimes! { &T } + assert_no_lifetimes! { &mut T } + assert_no_lifetimes! { [T] } + assert_no_lifetimes! { [T;4] } + assert_no_lifetimes! { (X, Y) } + assert_no_lifetimes! { (Y) } + assert_no_lifetimes! { std::collections::Vec } } macro_rules! assert_lifetimes { @@ -115,18 +115,18 @@ mod tests { #[test] fn assert_lifetimes() { - assert_lifetimes! { ['a] &'a T }; - assert_lifetimes! { ['a] [&'a T] }; - assert_lifetimes! { ['a] [&'a T;5] }; + assert_lifetimes! { ['a] &'a T } + assert_lifetimes! { ['a] [&'a T] } + assert_lifetimes! { ['a] [&'a T;5] } - assert_lifetimes! { ['a, 'a] (&'a A, &'a mut B) }; - assert_lifetimes! { ['a, 'a] &'a A<'a> }; + assert_lifetimes! { ['a, 'a] (&'a A, &'a mut B) } + assert_lifetimes! { ['a, 'a] &'a A<'a> } - assert_lifetimes! { ['a, 'b] &'b &'a mut T }; - assert_lifetimes! { ['a, 'b] Pin<&'a X, &'b mut Y> }; - assert_lifetimes! { ['a, 'b] (&'a A, &'b mut B) }; + assert_lifetimes! { ['a, 'b] &'b &'a mut T } + assert_lifetimes! { ['a, 'b] Pin<&'a X, &'b mut Y> } + assert_lifetimes! { ['a, 'b] (&'a A, &'b mut B) } - assert_lifetimes! { ['lifetime] A<'lifetime> }; + assert_lifetimes! { ['lifetime] A<'lifetime> } } macro_rules! assert_unsupported_type { @@ -137,13 +137,13 @@ mod tests { #[test] fn extract_lifetimes_unsupported_types() { - assert_unsupported_type! { dyn Foo }; - assert_unsupported_type! { &dyn Foo }; - assert_unsupported_type! { fn(A) }; - assert_unsupported_type! { fn(i64) -> i32 }; - assert_unsupported_type! { Vec }; - assert_unsupported_type! { fn(A, B) -> C }; - assert_unsupported_type! { ::Associated }; - assert_unsupported_type! { impl Fn(A,B) }; + assert_unsupported_type! { dyn Foo } + assert_unsupported_type! { &dyn Foo } + assert_unsupported_type! { fn(A) } + assert_unsupported_type! { fn(i64) -> i32 } + assert_unsupported_type! { Vec } + assert_unsupported_type! { fn(A, B) -> C } + assert_unsupported_type! { ::Associated } + assert_unsupported_type! { impl Fn(A,B) } } } diff --git a/crates/cxx-qt-gen/src/writer/rust/mod.rs b/crates/cxx-qt-gen/src/writer/rust/mod.rs index 212dbbc81..c92f0cb68 100644 --- a/crates/cxx-qt-gen/src/writer/rust/mod.rs +++ b/crates/cxx-qt-gen/src/writer/rust/mod.rs @@ -14,7 +14,6 @@ pub fn write_rust(generated: &GeneratedRustBlocks, include_path: Option<&str>) - let mut cxx_mod = generated.cxx_mod.clone(); let mut cxx_mod_contents = generated.cxx_mod_contents.clone(); let mut cxx_qt_mod_contents = vec![]; - let namespace = &generated.namespace; // Add common includes for all objects cxx_mod_contents.insert( @@ -56,7 +55,6 @@ pub fn write_rust(generated: &GeneratedRustBlocks, include_path: Option<&str>) - } quote! { - #[cxx::bridge(namespace = #namespace)] #cxx_mod #(#cxx_qt_mod_contents)* @@ -98,6 +96,7 @@ mod tests { pub fn create_generated_rust() -> GeneratedRustBlocks { GeneratedRustBlocks { cxx_mod: parse_quote! { + #[cxx::bridge(namespace = "cxx_qt::my_object")] mod ffi {} }, cxx_mod_contents: vec![], @@ -136,6 +135,7 @@ mod tests { pub fn create_generated_rust_multi_qobjects() -> GeneratedRustBlocks { GeneratedRustBlocks { cxx_mod: parse_quote! { + #[cxx::bridge(namespace = "cxx_qt")] mod ffi {} }, cxx_mod_contents: vec![], diff --git a/crates/cxx-qt-gen/test_inputs/passthrough_and_naming.rs b/crates/cxx-qt-gen/test_inputs/passthrough_and_naming.rs index 5fe7d070b..0a42627c5 100644 --- a/crates/cxx-qt-gen/test_inputs/passthrough_and_naming.rs +++ b/crates/cxx-qt-gen/test_inputs/passthrough_and_naming.rs @@ -1,6 +1,4 @@ -#[attrA] #[cxx_qt::bridge(namespace = "cxx_qt::multi_object")] -#[attrB] pub mod ffi { // ItemConst const MAX: u16 = 65535; @@ -37,9 +35,9 @@ pub mod ffi { extern "C" {} #[namespace = "namespace"] - #[custom_attr = "test"] extern "C" {} + #[custom_attr = "test"] unsafe extern "C++" {} #[namespace = "namespace"] @@ -138,7 +136,6 @@ pub mod ffi { } unsafe extern "RustQt" { - #[my_attribute] #[qsignal] fn ready(self: Pin<&mut SecondObject>); diff --git a/crates/cxx-qt-gen/test_inputs/properties.rs b/crates/cxx-qt-gen/test_inputs/properties.rs index c34c32c6c..3ec4f6ce9 100644 --- a/crates/cxx-qt-gen/test_inputs/properties.rs +++ b/crates/cxx-qt-gen/test_inputs/properties.rs @@ -8,7 +8,6 @@ mod ffi { extern "RustQt" { #[qobject] - #[derive(Default)] #[qproperty(i32, primitive)] #[qproperty(QPoint, trivial)] #[qproperty(i32, custom_function_prop, READ = my_getter, WRITE = my_setter, NOTIFY)] diff --git a/crates/cxx-qt-gen/test_inputs/qenum.rs b/crates/cxx-qt-gen/test_inputs/qenum.rs index c485c8380..ef781889d 100644 --- a/crates/cxx-qt-gen/test_inputs/qenum.rs +++ b/crates/cxx-qt-gen/test_inputs/qenum.rs @@ -35,7 +35,6 @@ mod ffi { unsafe extern "RustQt" { #[qobject] - #[derive(Default)] type MyObject = super::MyObjectRust; #[qinvokable] 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 5228d753a..432360be6 100644 --- a/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.cpp +++ b/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.cpp @@ -4,8 +4,8 @@ // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56480 namespace rust::cxxqt1 { template<> -SignalHandler<::rust::cxxqtgen1::QPushButtonCxxQtSignalParamsclicked*>:: - ~SignalHandler() noexcept +SignalHandler<::cxx_qt::multi_object::rust::cxxqtgen1:: + QPushButtonCxxQtSignalParamsclicked*>::~SignalHandler() noexcept { if (data[0] == nullptr && data[1] == nullptr) { return; @@ -17,42 +17,44 @@ SignalHandler<::rust::cxxqtgen1::QPushButtonCxxQtSignalParamsclicked*>:: template<> template<> void -SignalHandler<::rust::cxxqtgen1::QPushButtonCxxQtSignalParamsclicked*>:: -operator()(QPushButton& self, bool checked) +SignalHandler<::cxx_qt::multi_object::rust::cxxqtgen1:: + QPushButtonCxxQtSignalParamsclicked*>:: +operator()( + cxx_qt::multi_object::QPushButton& self, + bool checked) { call_QPushButton_signal_handler_clicked(*this, self, ::std::move(checked)); } -static_assert( - alignof( - SignalHandler<::rust::cxxqtgen1::QPushButtonCxxQtSignalParamsclicked*>) <= - alignof(::std::size_t), - "unexpected aligment"); -static_assert( - sizeof( - SignalHandler<::rust::cxxqtgen1::QPushButtonCxxQtSignalParamsclicked*>) == - sizeof(::std::size_t[2]), - "unexpected size"); +static_assert(alignof(SignalHandler<::cxx_qt::multi_object::rust::cxxqtgen1:: + QPushButtonCxxQtSignalParamsclicked*>) <= + alignof(::std::size_t), + "unexpected aligment"); +static_assert(sizeof(SignalHandler<::cxx_qt::multi_object::rust::cxxqtgen1:: + QPushButtonCxxQtSignalParamsclicked*>) == + sizeof(::std::size_t[2]), + "unexpected size"); } // namespace rust::cxxqt1 -namespace rust::cxxqtgen1 { +namespace cxx_qt::multi_object::rust::cxxqtgen1 { ::QMetaObject::Connection QPushButton_clickedConnect( - QPushButton& self, - ::rust::cxxqtgen1::QPushButtonCxxQtSignalHandlerclicked closure, + cxx_qt::multi_object::QPushButton& self, + ::cxx_qt::multi_object::rust::cxxqtgen1::QPushButtonCxxQtSignalHandlerclicked + closure, ::Qt::ConnectionType type) { return ::QObject::connect( &self, - &QPushButton::clicked, + &cxx_qt::multi_object::QPushButton::clicked, &self, [&, closure = ::std::move(closure)](bool checked) mutable { - closure.template operator()(self, - ::std::move(checked)); + closure.template operator()( + self, ::std::move(checked)); }, type); } -} // namespace rust::cxxqtgen1 +} // namespace cxx_qt::multi_object::rust::cxxqtgen1 // Define namespace otherwise we hit a GCC bug // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56480 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 64227bf20..29fb4a25f 100644 --- a/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.h +++ b/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.h @@ -40,10 +40,10 @@ class MyCxxName; } // namespace my_namespace -namespace rust::cxxqtgen1 { +namespace cxx_qt::multi_object::rust::cxxqtgen1 { using QPushButtonCxxQtSignalHandlerclicked = ::rust::cxxqt1::SignalHandler; -} // namespace rust::cxxqtgen1 +} // namespace cxx_qt::multi_object::rust::cxxqtgen1 namespace mynamespace::rust::cxxqtgen1 { using ExternObjectCxxQtSignalHandlerdataReady = @@ -58,13 +58,14 @@ using ExternObjectCxxQtSignalHandlererrorOccurred = #include "directory/file_ident.cxx.h" -namespace rust::cxxqtgen1 { +namespace cxx_qt::multi_object::rust::cxxqtgen1 { ::QMetaObject::Connection QPushButton_clickedConnect( - QPushButton& self, - ::rust::cxxqtgen1::QPushButtonCxxQtSignalHandlerclicked closure, + cxx_qt::multi_object::QPushButton& self, + ::cxx_qt::multi_object::rust::cxxqtgen1::QPushButtonCxxQtSignalHandlerclicked + closure, ::Qt::ConnectionType type); -} // namespace rust::cxxqtgen1 +} // namespace cxx_qt::multi_object::rust::cxxqtgen1 namespace mynamespace::rust::cxxqtgen1 { ::QMetaObject::Connection 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 9b4ad51ad..de00d8f9e 100644 --- a/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.rs +++ b/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.rs @@ -1,6 +1,4 @@ #[cxx::bridge(namespace = "cxx_qt::multi_object")] -#[attrA] -#[attrB] pub mod ffi { const MAX: u16 = 65535; enum Event { @@ -23,8 +21,8 @@ pub mod ffi { #[namespace = "namespace"] extern "C" {} #[namespace = "namespace"] - #[custom_attr = "test"] extern "C" {} + #[custom_attr = "test"] unsafe extern "C++" {} #[namespace = "namespace"] unsafe extern "C++" {} @@ -100,6 +98,7 @@ pub mod ffi { unsafe extern "C++" { #[cxx_name = "propertyNameChanged"] #[doc = "Notify for the Q_PROPERTY"] + #[namespace = "cxx_qt::multi_object"] fn property_name_changed(self: Pin<&mut MyObject>); } unsafe extern "C++" { @@ -138,6 +137,7 @@ pub mod ffi { } unsafe extern "C++" { #[cxx_name = "ready"] + #[namespace = "cxx_qt::multi_object"] fn ready(self: Pin<&mut MyObject>); } unsafe extern "C++" { @@ -210,6 +210,7 @@ pub mod ffi { unsafe extern "C++" { #[cxx_name = "propertyNameChanged"] #[doc = "Notify for the Q_PROPERTY"] + #[namespace = "second_object"] fn property_name_changed(self: Pin<&mut SecondObject>); } unsafe extern "C++" { @@ -248,6 +249,7 @@ pub mod ffi { } unsafe extern "C++" { #[cxx_name = "ready"] + #[namespace = "second_object"] fn ready(self: Pin<&mut SecondObject>); } unsafe extern "C++" { @@ -331,6 +333,7 @@ pub mod ffi { } #[namespace = ""] unsafe extern "C++" { + #[namespace = "cxx_qt::multi_object"] type QPushButton; #[namespace = "mynamespace"] #[cxx_name = "ExternObjectCpp"] @@ -338,15 +341,16 @@ pub mod ffi { } unsafe extern "C++" { #[cxx_name = "clicked"] + #[namespace = "cxx_qt::multi_object"] fn clicked(self: Pin<&mut QPushButton>, checked: bool); } unsafe extern "C++" { #[doc(hidden)] - #[namespace = "rust::cxxqtgen1"] + #[namespace = "cxx_qt::multi_object::rust::cxxqtgen1"] type QPushButtonCxxQtSignalHandlerclicked = cxx_qt::signalhandler::CxxQtSignalHandler; #[doc(hidden)] - #[namespace = "rust::cxxqtgen1"] + #[namespace = "cxx_qt::multi_object::rust::cxxqtgen1"] #[cxx_name = "QPushButton_clickedConnect"] fn QPushButton_connect_clicked( self_value: Pin<&mut QPushButton>, @@ -354,7 +358,7 @@ pub mod ffi { conn_type: CxxQtConnectionType, ) -> CxxQtQMetaObjectConnection; } - #[namespace = "rust::cxxqtgen1"] + #[namespace = "cxx_qt::multi_object::rust::cxxqtgen1"] extern "Rust" { #[doc(hidden)] fn drop_QPushButton_signal_handler_clicked(handler: QPushButtonCxxQtSignalHandlerclicked); @@ -367,6 +371,7 @@ pub mod ffi { } unsafe extern "C++" { #[cxx_name = "dataReady"] + #[namespace = "mynamespace"] fn data_ready(self: Pin<&mut ExternObject>); } unsafe extern "C++" { @@ -398,6 +403,7 @@ pub mod ffi { } unsafe extern "C++" { #[cxx_name = "errorOccurred"] + #[namespace = "mynamespace"] fn error_occurred(self: Pin<&mut ExternObject>); } unsafe extern "C++" { @@ -816,7 +822,9 @@ impl ffi::QPushButton { #[doc(hidden)] pub struct QPushButtonCxxQtSignalClosureclicked {} impl cxx_qt::signalhandler::CxxQtSignalHandlerClosure for QPushButtonCxxQtSignalClosureclicked { - type Id = cxx::type_id!("::rust::cxxqtgen1::QPushButtonCxxQtSignalHandlerclicked"); + type Id = cxx::type_id!( + "::cxx_qt::multi_object::rust::cxxqtgen1::QPushButtonCxxQtSignalHandlerclicked" + ); type FnType = dyn FnMut(core::pin::Pin<&mut ffi::QPushButton>, bool); } use core::mem::drop as drop_QPushButton_signal_handler_clicked; diff --git a/crates/cxx-qt-gen/test_outputs/properties.rs b/crates/cxx-qt-gen/test_outputs/properties.rs index ca189fc7b..0d2f7ecd3 100644 --- a/crates/cxx-qt-gen/test_outputs/properties.rs +++ b/crates/cxx-qt-gen/test_outputs/properties.rs @@ -129,6 +129,7 @@ mod ffi { unsafe extern "C++" { #[cxx_name = "primitiveChanged"] #[doc = "Notify for the Q_PROPERTY"] + #[namespace = "cxx_qt::my_object"] fn primitive_changed(self: Pin<&mut MyObject>); } unsafe extern "C++" { @@ -161,6 +162,7 @@ mod ffi { unsafe extern "C++" { #[cxx_name = "trivialChanged"] #[doc = "Notify for the Q_PROPERTY"] + #[namespace = "cxx_qt::my_object"] fn trivial_changed(self: Pin<&mut MyObject>); } unsafe extern "C++" { @@ -193,6 +195,7 @@ mod ffi { unsafe extern "C++" { #[cxx_name = "customFunctionPropChanged"] #[doc = "Notify for the Q_PROPERTY"] + #[namespace = "cxx_qt::my_object"] fn custom_function_prop_changed(self: Pin<&mut MyObject>); } unsafe extern "C++" { @@ -226,6 +229,7 @@ mod ffi { unsafe extern "C++" { #[cxx_name = "renamedPropertyChanged"] #[doc = "Notify for the Q_PROPERTY"] + #[namespace = "cxx_qt::my_object"] fn renamed_property_changed(self: Pin<&mut MyObject>); } unsafe extern "C++" { @@ -259,6 +263,7 @@ mod ffi { unsafe extern "C++" { #[cxx_name = "named_prop_2Changed"] #[doc = "Notify for the Q_PROPERTY"] + #[namespace = "cxx_qt::my_object"] fn renamed_property_2_changed(self: Pin<&mut MyObject>); } unsafe extern "C++" { @@ -309,6 +314,7 @@ mod ffi { } unsafe extern "C++" { #[cxx_name = "myOnChanged"] + #[namespace = "cxx_qt::my_object"] fn my_on_changed(self: Pin<&mut MyObject>); } unsafe extern "C++" { diff --git a/crates/cxx-qt-gen/test_outputs/qenum.rs b/crates/cxx-qt-gen/test_outputs/qenum.rs index b32256dad..e3e1eb5ff 100644 --- a/crates/cxx-qt-gen/test_outputs/qenum.rs +++ b/crates/cxx-qt-gen/test_outputs/qenum.rs @@ -18,6 +18,7 @@ mod ffi { include!("directory/file_ident.cxxqt.h"); } #[repr(i32)] + #[namespace = "cxx_qt::my_object"] enum MyEnum { A, } @@ -37,6 +38,7 @@ mod ffi { type MyOtherEnum; } #[repr(i32)] + #[namespace = "cxx_qt::my_object"] enum MyNamespacedEnum { A, B, @@ -57,6 +59,7 @@ mod ffi { type MyOtherNamespacedEnum; } #[repr(i32)] + #[namespace = "cxx_qt::my_object"] enum MyRenamedEnum { A, B, diff --git a/crates/cxx-qt-gen/test_outputs/signals.rs b/crates/cxx-qt-gen/test_outputs/signals.rs index 327a67962..88007d63d 100644 --- a/crates/cxx-qt-gen/test_outputs/signals.rs +++ b/crates/cxx-qt-gen/test_outputs/signals.rs @@ -45,6 +45,7 @@ mod ffi { } unsafe extern "C++" { #[cxx_name = "ready"] + #[namespace = "cxx_qt::my_object"] fn ready(self: Pin<&mut MyObject>); } unsafe extern "C++" { @@ -73,6 +74,7 @@ mod ffi { } unsafe extern "C++" { #[cxx_name = "dataChanged"] + #[namespace = "cxx_qt::my_object"] fn data_changed( self: Pin<&mut MyObject>, first: i32, @@ -111,6 +113,7 @@ mod ffi { } unsafe extern "C++" { #[cxx_name = "newData"] + #[namespace = "cxx_qt::my_object"] fn base_class_new_data( self: Pin<&mut MyObject>, first: i32, @@ -168,6 +171,7 @@ mod ffi { } unsafe extern "C++" { include ! (< QtCore / QTimer >); + #[namespace = "cxx_qt::my_object"] #[doc = " QTimer"] type QTimer; }