From a332032ab5b04bbedb1f4c5461358c3830345f07 Mon Sep 17 00:00:00 2001 From: Ben Ford Date: Wed, 21 May 2025 17:27:15 +0100 Subject: [PATCH 1/3] Place all objects and passthrough items in the same block, to pass through module docs and cfg (C++Qt only) --- .../src/generator/rust/externcxxqt.rs | 134 ++++++++++-------- .../cxx-qt-gen/src/generator/rust/qobject.rs | 14 +- crates/cxx-qt-gen/src/parser/externcxxqt.rs | 11 +- crates/cxx-qt-gen/src/parser/qobject.rs | 8 +- .../test_inputs/passthrough_and_naming.rs | 3 + crates/cxx-qt-gen/test_outputs/inheritance.rs | 2 - .../test_outputs/passthrough_and_naming.rs | 12 +- crates/cxx-qt-gen/test_outputs/signals.rs | 2 - 8 files changed, 111 insertions(+), 75 deletions(-) diff --git a/crates/cxx-qt-gen/src/generator/rust/externcxxqt.rs b/crates/cxx-qt-gen/src/generator/rust/externcxxqt.rs index 7e4ce6be9..688232e04 100644 --- a/crates/cxx-qt-gen/src/generator/rust/externcxxqt.rs +++ b/crates/cxx-qt-gen/src/generator/rust/externcxxqt.rs @@ -10,7 +10,7 @@ use crate::{ syntax::path::path_compare_str, }; use quote::quote; -use syn::{parse_quote, Attribute, Result}; +use syn::{parse_quote, Attribute, Item, Result}; impl GeneratedRustFragment { pub fn from_extern_cxx_qt( @@ -22,76 +22,88 @@ impl GeneratedRustFragment { } else { quote! {} }; + let extern_block_docs = &extern_cxxqt_block.docs; // Add the pass through blocks let unsafety = &extern_cxxqt_block.unsafety; let items = &extern_cxxqt_block.passthrough_items; - let mut generated = extern_cxxqt_block - .qobjects - .iter() - .map(|ty| -> Result { - let mut generated = vec![]; - let qobject_names = QObjectNames::from_extern_qobject(ty, type_names)?; - generated.push(GeneratedRustFragment::generate_casting_impl( - &qobject_names, - type_names, - &ty.name, - &ty.base_class, - )?); + let mut qobject_items: Vec = vec![]; + let mut cxx_qt_mod = vec![]; + let mut cxx_mod = vec![]; + for obj in &extern_cxxqt_block.qobjects { + let qobject_names = QObjectNames::from_extern_qobject(obj, type_names)?; - 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 cfgs: Vec<&Attribute> = ty - .declaration - .attrs - .iter() - .filter(|attr| path_compare_str(attr.meta.path(), &["cfg"])) - .collect(); - let docs: Vec<&Attribute> = ty - .declaration - .attrs - .iter() - .filter(|attr| path_compare_str(attr.meta.path(), &["doc"])) - .collect(); - generated.push(GeneratedRustFragment::from_cxx_item(parse_quote! { - #extern_block_namespace - #unsafety extern "C++" { - #namespace - #cxx_name - #(#cfgs)* - #(#docs)* - #vis type #ident; - } - })); - Ok(GeneratedRustFragment::flatten(generated)) - }) - .collect::>>()?; + let casting = GeneratedRustFragment::generate_casting_impl( + &qobject_names, + type_names, + &obj.name, + &obj.base_class, + )?; + cxx_mod.extend(casting.cxx_mod_contents); + cxx_qt_mod.extend(casting.cxx_qt_mod_contents); - if !items.is_empty() { - generated.push(GeneratedRustFragment::from_cxx_item(parse_quote! { - #extern_block_namespace - #unsafety extern "C++" { - #(#items)* + let namespace = if let Some(namespace) = &obj.name.namespace() { + quote! { #[namespace = #namespace ] } + } else { + quote! {} + }; + let cpp_name = &obj.name.cxx_unqualified(); + let rust_name = &obj.name.rust_unqualified(); + let vis = &obj.declaration.vis; + let ident = &obj.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 cfgs: Vec<&Attribute> = obj + .declaration + .attrs + .iter() + .filter(|attr| path_compare_str(attr.meta.path(), &["cfg"])) + .collect(); + let docs: Vec<&Attribute> = obj + .declaration + .attrs + .iter() + .filter(|attr| path_compare_str(attr.meta.path(), &["doc"])) + .collect(); + qobject_items.push(parse_quote! { + #namespace + #cxx_name + #(#cfgs)* + #(#docs)* + #vis type #ident; + }); } + let passthrough_items = if !items.is_empty() { + quote! { + #(#items)* + } + } else { + quote! {} + }; + + cxx_mod.push(parse_quote! { + #extern_block_namespace + #(#extern_block_docs)* + #unsafety extern "C++" { + #(#qobject_items)* + + #passthrough_items + } + }); + + let mut generated = vec![GeneratedRustFragment { + cxx_mod_contents: cxx_mod, + cxx_qt_mod_contents: cxx_qt_mod, + }]; + // Build the signals for signal in &extern_cxxqt_block.signals { let qobject_name = type_names.lookup(&signal.qobject_ident)?; diff --git a/crates/cxx-qt-gen/src/generator/rust/qobject.rs b/crates/cxx-qt-gen/src/generator/rust/qobject.rs index aa9b9bd2c..171cfcc57 100644 --- a/crates/cxx-qt-gen/src/generator/rust/qobject.rs +++ b/crates/cxx-qt-gen/src/generator/rust/qobject.rs @@ -28,7 +28,7 @@ impl GeneratedRustFragment { let namespace_idents = NamespaceName::from(qobject); let mut generated = vec![ - generate_qobject_definitions(&qobject_names, &qobject.cfgs)?, + generate_qobject_definitions(&qobject_names, &qobject.cfgs, &qobject.docs)?, generate_rust_properties( &qobject.properties, &qobject_names, @@ -88,6 +88,7 @@ impl GeneratedRustFragment { fn generate_qobject_definitions( qobject_idents: &QObjectNames, cfgs: &[Attribute], + docs: &[Attribute], ) -> Result { let cpp_class_name_rust = &qobject_idents.name.rust_unqualified(); let cpp_class_name_cpp = &qobject_idents.name.cxx_unqualified(); @@ -106,6 +107,15 @@ fn generate_qobject_definitions( } }; + let maybe_docs = if docs.is_empty() { + quote! {} + } else { + quote! { + #[doc = "\n"] + #(#docs)* + } + }; + Ok(GeneratedRustFragment { cxx_mod_contents: vec![ parse_quote! { @@ -116,6 +126,7 @@ fn generate_qobject_definitions( #[doc = "Use this type when referring to the QObject as a pointer"] #[doc = "\n"] #[doc = "See the book for more information: "] + #maybe_docs #namespace #cxx_name #(#cfgs)* @@ -130,6 +141,7 @@ fn generate_qobject_definitions( // but to apply it to only certain types, it is needed here too #namespace #(#cfgs)* + #(#docs)* type #rust_struct_name_rust; } }, diff --git a/crates/cxx-qt-gen/src/parser/externcxxqt.rs b/crates/cxx-qt-gen/src/parser/externcxxqt.rs index 4364c9027..812aacec9 100644 --- a/crates/cxx-qt-gen/src/parser/externcxxqt.rs +++ b/crates/cxx-qt-gen/src/parser/externcxxqt.rs @@ -3,6 +3,7 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 +use crate::parser::extract_docs; use crate::{ parser::{ externqobject::ParsedExternQObject, require_attributes, signals::ParsedSignal, @@ -11,7 +12,8 @@ use crate::{ syntax::{attribute::attribute_get_path, expr::expr_to_string}, }; use syn::{ - spanned::Spanned, Error, ForeignItem, ForeignItemFn, Ident, ItemForeignMod, Result, Token, + spanned::Spanned, Attribute, Error, ForeignItem, ForeignItemFn, Ident, ItemForeignMod, Result, + Token, }; /// Representation of an extern "C++Qt" block @@ -19,6 +21,8 @@ use syn::{ pub struct ParsedExternCxxQt { /// The namespace of the type in C++. pub namespace: Option, + /// The Top level docs on the module + pub docs: Vec, /// Whether this block has an unsafe token pub unsafety: Option, /// Items which can be passed into the extern "C++Qt" block @@ -38,9 +42,11 @@ impl ParsedExternCxxQt { // TODO: support cfg on foreign mod blocks let attrs = require_attributes( &foreign_mod.attrs, - &["namespace", "auto_cxx_name", "auto_rust_name"], + &["namespace", "doc", "auto_cxx_name", "auto_rust_name"], )?; + let docs = extract_docs(&foreign_mod.attrs); + let auto_case = CaseConversion::from_attrs(&attrs)?; let namespace = attrs @@ -52,6 +58,7 @@ impl ParsedExternCxxQt { let mut extern_cxx_block = ParsedExternCxxQt { namespace, + docs, unsafety: foreign_mod.unsafety, ..Default::default() }; diff --git a/crates/cxx-qt-gen/src/parser/qobject.rs b/crates/cxx-qt-gen/src/parser/qobject.rs index 30e1ed051..f7dfd4968 100644 --- a/crates/cxx-qt-gen/src/parser/qobject.rs +++ b/crates/cxx-qt-gen/src/parser/qobject.rs @@ -11,7 +11,7 @@ use crate::{ #[cfg(test)] use quote::format_ident; -use crate::parser::{parse_base_type, CaseConversion}; +use crate::parser::{extract_docs, parse_base_type, CaseConversion}; use syn::{Attribute, Error, Ident, Meta, Result}; /// Metadata for registering QML element @@ -44,6 +44,8 @@ pub struct ParsedQObject { pub declaration: ForeignTypeIdentAlias, /// Cfgs for the object pub cfgs: Vec, + /// Docs for the object + pub docs: Vec, } impl ParsedQObject { @@ -75,6 +77,7 @@ impl ParsedQObject { ident_right: format_ident!("MyObjectRust"), }, cfgs: vec![], + docs: vec![], } } @@ -86,8 +89,8 @@ impl ParsedQObject { auto_case: CaseConversion, ) -> Result { let attributes = require_attributes(&declaration.attrs, &Self::ALLOWED_ATTRS)?; - // TODO: handle docs through to generation let cfgs = extract_cfgs(&declaration.attrs); + let docs = extract_docs(&declaration.attrs); let has_qobject_macro = attributes.contains_key("qobject"); @@ -126,6 +129,7 @@ impl ParsedQObject { qml_metadata, has_qobject_macro, cfgs, + docs, }) } 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 bb4fa2c2e..01dca4a00 100644 --- a/crates/cxx-qt-gen/test_inputs/passthrough_and_naming.rs +++ b/crates/cxx-qt-gen/test_inputs/passthrough_and_naming.rs @@ -92,6 +92,7 @@ pub mod ffi { } #[namespace = ""] + /// Top level docs for a module unsafe extern "C++Qt" { #[qobject] type QPushButton; @@ -102,6 +103,7 @@ pub mod ffi { #[namespace = "mynamespace"] #[cxx_name = "ExternObjectCpp"] #[qobject] + /// An external object with some docs on it type ExternObject; #[qsignal] @@ -130,6 +132,7 @@ pub mod ffi { #[qobject] #[namespace = "second_object"] #[qproperty(i32, property_name, cxx_name = "propertyName")] + /// The second QObject with some different docs on it type SecondObject = super::SecondObjectRust; } diff --git a/crates/cxx-qt-gen/test_outputs/inheritance.rs b/crates/cxx-qt-gen/test_outputs/inheritance.rs index a44c252d0..6189f7af1 100644 --- a/crates/cxx-qt-gen/test_outputs/inheritance.rs +++ b/crates/cxx-qt-gen/test_outputs/inheritance.rs @@ -103,8 +103,6 @@ mod inheritance { } extern "C++" { type QPushButton; - } - extern "C++" { include ! (< QtWidgets / QPushButton >); } extern "C++" { 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 5e0f5b3a7..d74076746 100644 --- a/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.rs +++ b/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.rs @@ -201,11 +201,14 @@ pub mod ffi { #[doc = "Use this type when referring to the QObject as a pointer"] #[doc = "\n"] #[doc = "See the book for more information: "] + #[doc = "\n"] + #[doc = " The second QObject with some different docs on it"] #[namespace = "second_object"] type SecondObject; } extern "Rust" { #[namespace = "second_object"] + #[doc = " The second QObject with some different docs on it"] type SecondObjectRust; } extern "Rust" { @@ -381,11 +384,6 @@ pub mod ffi { #[namespace = "rust::cxxqt1"] unsafe fn cxx_qt_ffi_QPushButton_downcastPtr(base: *const QObject) -> *const QPushButton; } - #[namespace = ""] - unsafe extern "C++" { - #[namespace = "cxx_qt::multi_object"] - type QPushButton; - } extern "C++" { #[doc(hidden)] #[cxx_name = "upcastPtr"] @@ -400,9 +398,13 @@ pub mod ffi { ) -> *const ExternObject; } #[namespace = ""] + #[doc = " Top level docs for a module"] unsafe extern "C++" { + #[namespace = "cxx_qt::multi_object"] + type QPushButton; #[namespace = "mynamespace"] #[cxx_name = "ExternObjectCpp"] + #[doc = " An external object with some docs on it"] type ExternObject; } unsafe extern "C++" { diff --git a/crates/cxx-qt-gen/test_outputs/signals.rs b/crates/cxx-qt-gen/test_outputs/signals.rs index 9c30d80ea..56c6fc613 100644 --- a/crates/cxx-qt-gen/test_outputs/signals.rs +++ b/crates/cxx-qt-gen/test_outputs/signals.rs @@ -196,8 +196,6 @@ mod ffi { #[namespace = "cxx_qt::my_object"] #[doc = " QTimer"] type QTimer; - } - unsafe extern "C++" { include ! (< QtCore / QTimer >); } unsafe extern "C++" { From c361163bcbec2e29eaca54b7ef2c79a7b1d1d5d9 Mon Sep 17 00:00:00 2001 From: Ben Ford Date: Wed, 18 Jun 2025 13:08:55 +0100 Subject: [PATCH 2/3] Return docs and cfgs from require_attributes, and make common struct --- crates/cxx-qt-gen/bat | 1 + crates/cxx-qt-gen/src/parser/externcxxqt.rs | 7 ++----- crates/cxx-qt-gen/src/parser/externqobject.rs | 2 +- crates/cxx-qt-gen/src/parser/externrustqt.rs | 2 +- crates/cxx-qt-gen/src/parser/inherit.rs | 12 ++++------- crates/cxx-qt-gen/src/parser/method.rs | 7 +++---- crates/cxx-qt-gen/src/parser/mod.rs | 21 +++++++++++++++---- crates/cxx-qt-gen/src/parser/qenum.rs | 10 ++++----- crates/cxx-qt-gen/src/parser/qnamespace.rs | 2 +- crates/cxx-qt-gen/src/parser/qobject.rs | 14 +++++-------- crates/cxx-qt-gen/src/parser/signals.rs | 11 ++++------ 11 files changed, 43 insertions(+), 46 deletions(-) create mode 100644 crates/cxx-qt-gen/bat diff --git a/crates/cxx-qt-gen/bat b/crates/cxx-qt-gen/bat new file mode 100644 index 000000000..62b5c4b53 --- /dev/null +++ b/crates/cxx-qt-gen/bat @@ -0,0 +1 @@ +/home/ben/cxx-qt/crates/cxx-qt-gen/src/generator/naming/property.rs diff --git a/crates/cxx-qt-gen/src/parser/externcxxqt.rs b/crates/cxx-qt-gen/src/parser/externcxxqt.rs index 812aacec9..a733e5ed7 100644 --- a/crates/cxx-qt-gen/src/parser/externcxxqt.rs +++ b/crates/cxx-qt-gen/src/parser/externcxxqt.rs @@ -3,7 +3,6 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 -use crate::parser::extract_docs; use crate::{ parser::{ externqobject::ParsedExternQObject, require_attributes, signals::ParsedSignal, @@ -40,13 +39,11 @@ impl ParsedExternCxxQt { parent_namespace: Option<&str>, ) -> Result { // TODO: support cfg on foreign mod blocks - let attrs = require_attributes( + let (attrs, common_attrs) = require_attributes( &foreign_mod.attrs, &["namespace", "doc", "auto_cxx_name", "auto_rust_name"], )?; - let docs = extract_docs(&foreign_mod.attrs); - let auto_case = CaseConversion::from_attrs(&attrs)?; let namespace = attrs @@ -58,7 +55,7 @@ impl ParsedExternCxxQt { let mut extern_cxx_block = ParsedExternCxxQt { namespace, - docs, + docs: common_attrs.docs, unsafety: foreign_mod.unsafety, ..Default::default() }; diff --git a/crates/cxx-qt-gen/src/parser/externqobject.rs b/crates/cxx-qt-gen/src/parser/externqobject.rs index b7ea93faa..d95eda2b7 100644 --- a/crates/cxx-qt-gen/src/parser/externqobject.rs +++ b/crates/cxx-qt-gen/src/parser/externqobject.rs @@ -32,7 +32,7 @@ impl ParsedExternQObject { module_ident: &Ident, parent_namespace: Option<&str>, ) -> Result { - let attributes = require_attributes(&ty.attrs, &Self::ALLOWED_ATTRS)?; + let (attributes, _common_attrs) = require_attributes(&ty.attrs, &Self::ALLOWED_ATTRS)?; let base_class = parse_base_type(&attributes)?; diff --git a/crates/cxx-qt-gen/src/parser/externrustqt.rs b/crates/cxx-qt-gen/src/parser/externrustqt.rs index 6da520bdf..dadbe949e 100644 --- a/crates/cxx-qt-gen/src/parser/externrustqt.rs +++ b/crates/cxx-qt-gen/src/parser/externrustqt.rs @@ -38,7 +38,7 @@ impl ParsedExternRustQt { parent_namespace: Option<&str>, ) -> Result { // TODO: support cfg on foreign mod blocks - let attrs = require_attributes( + let (attrs, _common_attrs) = require_attributes( &foreign_mod.attrs, &["namespace", "auto_cxx_name", "auto_rust_name"], )?; diff --git a/crates/cxx-qt-gen/src/parser/inherit.rs b/crates/cxx-qt-gen/src/parser/inherit.rs index 9ebb5af17..910f547bf 100644 --- a/crates/cxx-qt-gen/src/parser/inherit.rs +++ b/crates/cxx-qt-gen/src/parser/inherit.rs @@ -3,9 +3,7 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 -use crate::parser::{ - extract_cfgs, extract_docs, method::MethodFields, require_attributes, CaseConversion, -}; +use crate::parser::{method::MethodFields, require_attributes, CaseConversion}; use core::ops::Deref; use quote::format_ident; use std::ops::DerefMut; @@ -32,14 +30,12 @@ impl ParsedInheritedMethod { ]; pub fn parse(method: ForeignItemFn, auto_case: CaseConversion) -> Result { - require_attributes(&method.attrs, &Self::ALLOWED_ATTRS)?; - let docs = extract_docs(&method.attrs); - let cfgs = extract_cfgs(&method.attrs); + let (_attrs, common_attrs) = require_attributes(&method.attrs, &Self::ALLOWED_ATTRS)?; Ok(Self { method_fields: MethodFields::parse(method, auto_case)?, - docs, - cfgs, + docs: common_attrs.docs, + cfgs: common_attrs.cfgs, }) } diff --git a/crates/cxx-qt-gen/src/parser/method.rs b/crates/cxx-qt-gen/src/parser/method.rs index 89c708da5..24230bbef 100644 --- a/crates/cxx-qt-gen/src/parser/method.rs +++ b/crates/cxx-qt-gen/src/parser/method.rs @@ -5,7 +5,7 @@ use crate::parser::CaseConversion; use crate::{ naming::Name, - parser::{extract_cfgs, parameter::ParsedFunctionParameter, require_attributes}, + parser::{parameter::ParsedFunctionParameter, require_attributes}, syntax::{foreignmod, types}, }; use core::ops::Deref; @@ -121,8 +121,7 @@ impl ParsedMethod { unsafe_block: bool, ) -> Result { let fields = MethodFields::parse(method, auto_case)?; - let attrs = require_attributes(&fields.method.attrs, &Self::ALLOWED_ATTRS)?; - let cfgs = extract_cfgs(&fields.method.attrs); + let (attrs, common_attrs) = require_attributes(&fields.method.attrs, &Self::ALLOWED_ATTRS)?; // Determine if the method is invokable let is_qinvokable = attrs.contains_key("qinvokable"); @@ -134,7 +133,7 @@ impl ParsedMethod { specifiers, is_qinvokable, is_pure, - cfgs, + cfgs: common_attrs.cfgs, unsafe_block, }) } diff --git a/crates/cxx-qt-gen/src/parser/mod.rs b/crates/cxx-qt-gen/src/parser/mod.rs index 97b11ec1f..2587ad7f2 100644 --- a/crates/cxx-qt-gen/src/parser/mod.rs +++ b/crates/cxx-qt-gen/src/parser/mod.rs @@ -115,12 +115,20 @@ fn split_path(path_str: &str) -> Vec<&str> { path } +/// Attributes which should be passed through, and are available on most things +pub struct CommonAttrs { + docs: Vec, + cfgs: Vec, +} + /// 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 +/// +/// Has the option to allow a set of common attributes such as doc, cfg, etc... pub fn require_attributes<'a>( attrs: &'a [Attribute], allowed: &'a [&str], -) -> Result> { +) -> Result<(BTreeMap<&'a str, &'a Attribute>, CommonAttrs)> { let mut output = BTreeMap::default(); for attr in attrs { let index = allowed @@ -128,7 +136,8 @@ pub fn require_attributes<'a>( .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 { + } + else { return Err(Error::new( attr.span(), format!( @@ -138,7 +147,11 @@ pub fn require_attributes<'a>( )); } } - Ok(output) + let common = CommonAttrs { + docs: extract_docs(attrs), + cfgs: extract_cfgs(attrs), + }; + Ok((output, common)) } // Extract base identifier from attribute @@ -196,7 +209,7 @@ pub struct Parser { impl Parser { fn parse_mod_attributes(module: &mut ItemMod) -> Result> { - let attrs = require_attributes(&module.attrs, &["doc", "cxx_qt::bridge"])?; + let (attrs, _common_attrs) = require_attributes(&module.attrs, &["doc", "cxx_qt::bridge"])?; let mut namespace = None; // Check for the cxx_qt::bridge attribute diff --git a/crates/cxx-qt-gen/src/parser/qenum.rs b/crates/cxx-qt-gen/src/parser/qenum.rs index 069831a1b..8ae595d80 100644 --- a/crates/cxx-qt-gen/src/parser/qenum.rs +++ b/crates/cxx-qt-gen/src/parser/qenum.rs @@ -3,7 +3,7 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 -use crate::parser::{extract_cfgs, extract_docs, CaseConversion}; +use crate::parser::CaseConversion; use crate::{naming::Name, parser::require_attributes, syntax::path::path_compare_str}; use quote::ToTokens; use syn::{Attribute, Ident, ItemEnum, Result, Variant}; @@ -60,9 +60,7 @@ impl ParsedQEnum { parent_namespace: Option<&str>, module: &Ident, ) -> Result { - require_attributes(&qenum.attrs, &Self::ALLOWED_ATTRS)?; - let cfgs = extract_cfgs(&qenum.attrs); - let docs = extract_docs(&qenum.attrs); + let (_attrs, common_attrs) = require_attributes(&qenum.attrs, &Self::ALLOWED_ATTRS)?; if qenum.variants.is_empty() { return Err(syn::Error::new_spanned( @@ -96,8 +94,8 @@ impl ParsedQEnum { name, qobject, variants, - docs, - cfgs, + docs: common_attrs.docs, + cfgs: common_attrs.cfgs, item: qenum, }) } diff --git a/crates/cxx-qt-gen/src/parser/qnamespace.rs b/crates/cxx-qt-gen/src/parser/qnamespace.rs index ab483ec20..e4e89db33 100644 --- a/crates/cxx-qt-gen/src/parser/qnamespace.rs +++ b/crates/cxx-qt-gen/src/parser/qnamespace.rs @@ -15,7 +15,7 @@ pub struct ParsedQNamespace { impl ParsedQNamespace { pub fn parse(mac: ItemMacro) -> Result { - let attrs = require_attributes(&mac.attrs, &["qml_element"])?; + let (attrs, _common_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) { diff --git a/crates/cxx-qt-gen/src/parser/qobject.rs b/crates/cxx-qt-gen/src/parser/qobject.rs index f7dfd4968..608f0f972 100644 --- a/crates/cxx-qt-gen/src/parser/qobject.rs +++ b/crates/cxx-qt-gen/src/parser/qobject.rs @@ -5,13 +5,11 @@ use crate::{ naming::Name, - parser::{extract_cfgs, property::ParsedQProperty, require_attributes}, + parser::{property::ParsedQProperty, require_attributes, parse_base_type, CaseConversion}, syntax::{expr::expr_to_string, foreignmod::ForeignTypeIdentAlias, path::path_compare_str}, }; #[cfg(test)] use quote::format_ident; - -use crate::parser::{extract_docs, parse_base_type, CaseConversion}; use syn::{Attribute, Error, Ident, Meta, Result}; /// Metadata for registering QML element @@ -88,9 +86,7 @@ impl ParsedQObject { module: &Ident, auto_case: CaseConversion, ) -> Result { - let attributes = require_attributes(&declaration.attrs, &Self::ALLOWED_ATTRS)?; - let cfgs = extract_cfgs(&declaration.attrs); - let docs = extract_docs(&declaration.attrs); + let (attributes, common_attrs) = require_attributes(&declaration.attrs, &Self::ALLOWED_ATTRS)?; let has_qobject_macro = attributes.contains_key("qobject"); @@ -128,13 +124,13 @@ impl ParsedQObject { properties, qml_metadata, has_qobject_macro, - cfgs, - docs, + cfgs: common_attrs.cfgs, + docs: common_attrs.docs, }) } fn parse_qml_metadata(name: &Name, attrs: &[Attribute]) -> Result> { - let attributes = require_attributes(attrs, &Self::ALLOWED_ATTRS)?; + let (attributes, _common_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 diff --git a/crates/cxx-qt-gen/src/parser/signals.rs b/crates/cxx-qt-gen/src/parser/signals.rs index 81e264ebb..2ab85ba54 100644 --- a/crates/cxx-qt-gen/src/parser/signals.rs +++ b/crates/cxx-qt-gen/src/parser/signals.rs @@ -2,9 +2,8 @@ // SPDX-FileContributor: Andrew Hayzen // // SPDX-License-Identifier: MIT OR Apache-2.0 -use crate::parser::CaseConversion; use crate::{ - parser::{extract_cfgs, extract_docs, method::MethodFields, require_attributes}, + parser::{method::MethodFields, require_attributes, CaseConversion}, syntax::path::path_compare_str, }; use core::ops::Deref; @@ -37,10 +36,8 @@ impl ParsedSignal { } pub fn parse(method: ForeignItemFn, auto_case: CaseConversion) -> Result { - let docs = extract_docs(&method.attrs); - let cfgs = extract_cfgs(&method.attrs); let fields = MethodFields::parse(method, auto_case)?; - let attrs = require_attributes(&fields.method.attrs, &Self::ALLOWED_ATTRS)?; + let (attrs, common_attrs) = require_attributes(&fields.method.attrs, &Self::ALLOWED_ATTRS)?; if !fields.mutable { return Err(Error::new( @@ -61,8 +58,8 @@ impl ParsedSignal { method_fields: fields, inherit, private, - docs, - cfgs, + docs: common_attrs.docs, + cfgs: common_attrs.cfgs, }) } } From fac4c0d9c197b17c6d00f56bd7402f76418351f0 Mon Sep 17 00:00:00 2001 From: Ben Ford Date: Wed, 18 Jun 2025 13:26:37 +0100 Subject: [PATCH 3/3] Switch structs to storing CommonAttrs instead of just attrs --- .../cxx-qt-gen/src/generator/cpp/inherit.rs | 2 +- crates/cxx-qt-gen/src/generator/cpp/mod.rs | 2 +- crates/cxx-qt-gen/src/generator/cpp/qenum.rs | 4 ++-- crates/cxx-qt-gen/src/generator/cpp/signal.rs | 2 +- .../src/generator/rust/externcxxqt.rs | 3 ++- .../cxx-qt-gen/src/generator/rust/inherit.rs | 4 ++-- crates/cxx-qt-gen/src/generator/rust/qenum.rs | 4 ++-- .../cxx-qt-gen/src/generator/rust/qobject.rs | 8 +++---- .../cxx-qt-gen/src/generator/rust/signals.rs | 4 ++-- crates/cxx-qt-gen/src/parser/externcxxqt.rs | 9 ++++---- crates/cxx-qt-gen/src/parser/inherit.rs | 13 ++++------- crates/cxx-qt-gen/src/parser/mod.rs | 23 +++++++++++-------- crates/cxx-qt-gen/src/parser/qenum.rs | 13 ++++------- crates/cxx-qt-gen/src/parser/qobject.rs | 16 ++++++------- crates/cxx-qt-gen/src/parser/signals.rs | 12 ++++------ 15 files changed, 58 insertions(+), 61 deletions(-) diff --git a/crates/cxx-qt-gen/src/generator/cpp/inherit.rs b/crates/cxx-qt-gen/src/generator/cpp/inherit.rs index bb2e05b40..21faf90be 100644 --- a/crates/cxx-qt-gen/src/generator/cpp/inherit.rs +++ b/crates/cxx-qt-gen/src/generator/cpp/inherit.rs @@ -27,7 +27,7 @@ pub fn generate( for &method in inherited_methods { // Skip if the cfg attributes are not resolved to true - if !try_eval_attributes(opt.cfg_evaluator.as_ref(), &method.cfgs)? { + if !try_eval_attributes(opt.cfg_evaluator.as_ref(), &method.common_attrs.cfgs)? { continue; } diff --git a/crates/cxx-qt-gen/src/generator/cpp/mod.rs b/crates/cxx-qt-gen/src/generator/cpp/mod.rs index f5c74ce6b..3ac0c2fc5 100644 --- a/crates/cxx-qt-gen/src/generator/cpp/mod.rs +++ b/crates/cxx-qt-gen/src/generator/cpp/mod.rs @@ -72,7 +72,7 @@ impl GeneratedCppBlocks { .iter() .filter_map(|qobject| { // Skip if the cfg attributes are not resolved to true - match try_eval_attributes(opt.cfg_evaluator.as_ref(), &qobject.declaration.cfgs) + match try_eval_attributes(opt.cfg_evaluator.as_ref(), &qobject.declaration.common_attrs.cfgs) { Ok(true) => { Some(GeneratedCppQObject::from(qobject, &parser.type_names, opt)) diff --git a/crates/cxx-qt-gen/src/generator/cpp/qenum.rs b/crates/cxx-qt-gen/src/generator/cpp/qenum.rs index 9e75e6e4b..15e87c182 100644 --- a/crates/cxx-qt-gen/src/generator/cpp/qenum.rs +++ b/crates/cxx-qt-gen/src/generator/cpp/qenum.rs @@ -39,7 +39,7 @@ pub fn generate_declaration( opt: &GeneratedOpt, ) -> Result { // Skip if the cfg attributes are not resolved to true - if !try_eval_attributes(opt.cfg_evaluator.as_ref(), &qenum.cfgs)? { + if !try_eval_attributes(opt.cfg_evaluator.as_ref(), &qenum.common_attrs.cfgs)? { return Ok(String::new()); } @@ -75,7 +75,7 @@ pub fn generate_on_qobject<'a>( for qenum in qenums { // Skip if the cfg attributes are not resolved to true - if !try_eval_attributes(opt.cfg_evaluator.as_ref(), &qenum.cfgs)? { + if !try_eval_attributes(opt.cfg_evaluator.as_ref(), &qenum.common_attrs.cfgs)? { continue; } diff --git a/crates/cxx-qt-gen/src/generator/cpp/signal.rs b/crates/cxx-qt-gen/src/generator/cpp/signal.rs index 1dc4808da..4e5d26b1a 100644 --- a/crates/cxx-qt-gen/src/generator/cpp/signal.rs +++ b/crates/cxx-qt-gen/src/generator/cpp/signal.rs @@ -89,7 +89,7 @@ pub fn generate_cpp_signal( let mut generated = CppSignalFragment::default(); // Skip if the cfg attributes are not resolved to true - if !try_eval_attributes(opt.cfg_evaluator.as_ref(), &signal.cfgs)? { + if !try_eval_attributes(opt.cfg_evaluator.as_ref(), &signal.common_attrs.cfgs)? { return Ok(generated); } diff --git a/crates/cxx-qt-gen/src/generator/rust/externcxxqt.rs b/crates/cxx-qt-gen/src/generator/rust/externcxxqt.rs index 688232e04..a1d3617e7 100644 --- a/crates/cxx-qt-gen/src/generator/rust/externcxxqt.rs +++ b/crates/cxx-qt-gen/src/generator/rust/externcxxqt.rs @@ -22,7 +22,7 @@ impl GeneratedRustFragment { } else { quote! {} }; - let extern_block_docs = &extern_cxxqt_block.docs; + let extern_block_docs = &extern_cxxqt_block.common_attrs.docs; // Add the pass through blocks let unsafety = &extern_cxxqt_block.unsafety; @@ -60,6 +60,7 @@ impl GeneratedRustFragment { #[cxx_name = #cxx_name] } }; + // TODO! Can we make extract_docs return references, and then use here? let cfgs: Vec<&Attribute> = obj .declaration .attrs diff --git a/crates/cxx-qt-gen/src/generator/rust/inherit.rs b/crates/cxx-qt-gen/src/generator/rust/inherit.rs index b61786949..6e2504bd0 100644 --- a/crates/cxx-qt-gen/src/generator/rust/inherit.rs +++ b/crates/cxx-qt-gen/src/generator/rust/inherit.rs @@ -48,8 +48,8 @@ pub fn generate( if method.safe { std::mem::swap(&mut unsafe_call, &mut unsafe_block); } - let doc_comments = &method.docs; - let cfgs = &method.cfgs; + let doc_comments = &method.common_attrs.docs; + let cfgs = &method.common_attrs.cfgs; let namespace = qobject_names.namespace_tokens(); syn::parse2(quote_spanned! { diff --git a/crates/cxx-qt-gen/src/generator/rust/qenum.rs b/crates/cxx-qt-gen/src/generator/rust/qenum.rs index d791aaf52..ba4158883 100644 --- a/crates/cxx-qt-gen/src/generator/rust/qenum.rs +++ b/crates/cxx-qt-gen/src/generator/rust/qenum.rs @@ -16,8 +16,8 @@ pub fn generate_cxx_mod_contents(qenums: &[ParsedQEnum]) -> Vec { let item = &qenum.item; let vis = &item.vis; let variants = &item.variants; - let docs = &qenum.docs; - let cfgs = &qenum.cfgs; + let docs = &qenum.common_attrs.docs; + let cfgs = &qenum.common_attrs.cfgs; let cxx_namespace = if namespace.is_none() { quote! {} diff --git a/crates/cxx-qt-gen/src/generator/rust/qobject.rs b/crates/cxx-qt-gen/src/generator/rust/qobject.rs index 171cfcc57..d548d4fc6 100644 --- a/crates/cxx-qt-gen/src/generator/rust/qobject.rs +++ b/crates/cxx-qt-gen/src/generator/rust/qobject.rs @@ -28,7 +28,7 @@ impl GeneratedRustFragment { let namespace_idents = NamespaceName::from(qobject); let mut generated = vec![ - generate_qobject_definitions(&qobject_names, &qobject.cfgs, &qobject.docs)?, + generate_qobject_definitions(&qobject_names, &qobject.common_attrs.cfgs, &qobject.common_attrs.docs)?, generate_rust_properties( &qobject.properties, &qobject_names, @@ -57,7 +57,7 @@ impl GeneratedRustFragment { &qobject_names, &namespace_idents, type_names, - &qobject.cfgs, + &qobject.common_attrs.cfgs, )?); } @@ -75,9 +75,9 @@ impl GeneratedRustFragment { &qobject_names, &namespace_idents, type_names, - &qobject.cfgs, + &qobject.common_attrs.cfgs, )?, - cxxqttype::generate(&qobject_names, type_names, &qobject.cfgs)?, + cxxqttype::generate(&qobject_names, type_names, &qobject.common_attrs.cfgs)?, ]); Ok(GeneratedRustFragment::flatten(generated)) diff --git a/crates/cxx-qt-gen/src/generator/rust/signals.rs b/crates/cxx-qt-gen/src/generator/rust/signals.rs index 2b407522c..fbe21a386 100644 --- a/crates/cxx-qt-gen/src/generator/rust/signals.rs +++ b/crates/cxx-qt-gen/src/generator/rust/signals.rs @@ -100,8 +100,8 @@ pub fn generate_rust_signal( } else { Some(quote! { unsafe }) }; - let doc_comments = &signal.docs; - let cfgs = &signal.cfgs; + let doc_comments = &signal.common_attrs.docs; + let cfgs = &signal.common_attrs.cfgs; let namespace = if let Some(namespace) = qobject_name.namespace() { quote_spanned! { span=> #[namespace = #namespace ] } } else { diff --git a/crates/cxx-qt-gen/src/parser/externcxxqt.rs b/crates/cxx-qt-gen/src/parser/externcxxqt.rs index a733e5ed7..8fa316024 100644 --- a/crates/cxx-qt-gen/src/parser/externcxxqt.rs +++ b/crates/cxx-qt-gen/src/parser/externcxxqt.rs @@ -11,17 +11,18 @@ use crate::{ syntax::{attribute::attribute_get_path, expr::expr_to_string}, }; use syn::{ - spanned::Spanned, Attribute, Error, ForeignItem, ForeignItemFn, Ident, ItemForeignMod, Result, + spanned::Spanned, Error, ForeignItem, ForeignItemFn, Ident, ItemForeignMod, Result, Token, }; +use crate::parser::CommonAttrs; /// Representation of an extern "C++Qt" block #[derive(Default)] pub struct ParsedExternCxxQt { /// The namespace of the type in C++. pub namespace: Option, - /// The Top level docs on the module - pub docs: Vec, + /// All the universal top level attributes for the block + pub common_attrs: CommonAttrs, /// Whether this block has an unsafe token pub unsafety: Option, /// Items which can be passed into the extern "C++Qt" block @@ -55,7 +56,7 @@ impl ParsedExternCxxQt { let mut extern_cxx_block = ParsedExternCxxQt { namespace, - docs: common_attrs.docs, + common_attrs, unsafety: foreign_mod.unsafety, ..Default::default() }; diff --git a/crates/cxx-qt-gen/src/parser/inherit.rs b/crates/cxx-qt-gen/src/parser/inherit.rs index 910f547bf..f141f116a 100644 --- a/crates/cxx-qt-gen/src/parser/inherit.rs +++ b/crates/cxx-qt-gen/src/parser/inherit.rs @@ -3,20 +3,18 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 -use crate::parser::{method::MethodFields, require_attributes, CaseConversion}; +use crate::parser::{method::MethodFields, require_attributes, CaseConversion, CommonAttrs}; use core::ops::Deref; use quote::format_ident; use std::ops::DerefMut; -use syn::{Attribute, ForeignItemFn, Ident, Result}; +use syn::{ForeignItemFn, Ident, Result}; /// Describes a method found in an extern "RustQt" with #[inherit] pub struct ParsedInheritedMethod { /// The common fields which are available on all callable types pub method_fields: MethodFields, - /// All the docs (each line) of the inherited method - pub docs: Vec, - /// Cfgs for the inherited method - pub cfgs: Vec, + /// All the universal attributes for the inherited method + pub common_attrs: CommonAttrs, } impl ParsedInheritedMethod { @@ -34,8 +32,7 @@ impl ParsedInheritedMethod { Ok(Self { method_fields: MethodFields::parse(method, auto_case)?, - docs: common_attrs.docs, - cfgs: common_attrs.cfgs, + common_attrs }) } diff --git a/crates/cxx-qt-gen/src/parser/mod.rs b/crates/cxx-qt-gen/src/parser/mod.rs index 2587ad7f2..f5af3108b 100644 --- a/crates/cxx-qt-gen/src/parser/mod.rs +++ b/crates/cxx-qt-gen/src/parser/mod.rs @@ -87,22 +87,24 @@ impl CaseConversion { } } -/// Iterate the attributes of the method to extract cfg attributes -pub fn extract_cfgs(attrs: &[Attribute]) -> Vec { +/// Helper function to extract all of a particular attribute from the slice +fn extract_attr(attrs: &[Attribute], target: &str) -> Vec { attrs .iter() - .filter(|attr| path_compare_str(attr.meta.path(), &["cfg"])) + .filter(|attr| path_compare_str(attr.meta.path(), &[target])) .cloned() .collect() } +/// Iterate the attributes of the method to extract cfg attributes +pub fn extract_cfgs(attrs: &[Attribute]) -> Vec { + extract_attr(attrs, "cfg") +} + /// Iterate the attributes of the method to extract Doc attributes (doc comments are parsed as this) pub fn extract_docs(attrs: &[Attribute]) -> Vec { - attrs - .iter() - .filter(|attr| path_compare_str(attr.meta.path(), &["doc"])) - .cloned() - .collect() + extract_attr(attrs, "doc") + } /// Splits a path by :: separators e.g. "cxx_qt::bridge" becomes ["cxx_qt", "bridge"] @@ -116,9 +118,10 @@ fn split_path(path_str: &str) -> Vec<&str> { } /// Attributes which should be passed through, and are available on most things +#[derive(Clone, Debug, Default)] pub struct CommonAttrs { - docs: Vec, - cfgs: Vec, + pub docs: Vec, + pub cfgs: Vec, } /// Collects a Map of all attributes found from the allowed list diff --git a/crates/cxx-qt-gen/src/parser/qenum.rs b/crates/cxx-qt-gen/src/parser/qenum.rs index 8ae595d80..9357e22d6 100644 --- a/crates/cxx-qt-gen/src/parser/qenum.rs +++ b/crates/cxx-qt-gen/src/parser/qenum.rs @@ -3,10 +3,10 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 -use crate::parser::CaseConversion; +use crate::parser::{CaseConversion, CommonAttrs}; use crate::{naming::Name, parser::require_attributes, syntax::path::path_compare_str}; use quote::ToTokens; -use syn::{Attribute, Ident, ItemEnum, Result, Variant}; +use syn::{Ident, ItemEnum, Result, Variant}; pub struct ParsedQEnum { /// The name of the QObject @@ -17,10 +17,8 @@ pub struct ParsedQEnum { pub qobject: Option, /// The original enum item pub item: ItemEnum, - /// Docs from the qenum - pub docs: Vec, - /// Cfgs from the qenum - pub cfgs: Vec, + /// All the universal attributes for the enum + pub common_attrs: CommonAttrs, } impl ParsedQEnum { @@ -94,8 +92,7 @@ impl ParsedQEnum { name, qobject, variants, - docs: common_attrs.docs, - cfgs: common_attrs.cfgs, + common_attrs, item: qenum, }) } diff --git a/crates/cxx-qt-gen/src/parser/qobject.rs b/crates/cxx-qt-gen/src/parser/qobject.rs index 608f0f972..735c502a8 100644 --- a/crates/cxx-qt-gen/src/parser/qobject.rs +++ b/crates/cxx-qt-gen/src/parser/qobject.rs @@ -11,6 +11,7 @@ use crate::{ #[cfg(test)] use quote::format_ident; use syn::{Attribute, Error, Ident, Meta, Result}; +use crate::parser::CommonAttrs; /// Metadata for registering QML element #[derive(Clone, Debug, Default, Eq, PartialEq)] @@ -40,10 +41,8 @@ pub struct ParsedQObject { pub has_qobject_macro: bool, /// The original declaration entered by the user, i.e. a type alias with a list of attributes pub declaration: ForeignTypeIdentAlias, - /// Cfgs for the object - pub cfgs: Vec, - /// Docs for the object - pub docs: Vec, + /// All the universal attributes for the object + pub common_attrs: CommonAttrs, } impl ParsedQObject { @@ -74,8 +73,10 @@ impl ParsedQObject { ident_left: format_ident!("MyObject"), ident_right: format_ident!("MyObjectRust"), }, - cfgs: vec![], - docs: vec![], + common_attrs: CommonAttrs { + docs: vec![], + cfgs: vec![], + } } } @@ -124,8 +125,7 @@ impl ParsedQObject { properties, qml_metadata, has_qobject_macro, - cfgs: common_attrs.cfgs, - docs: common_attrs.docs, + common_attrs }) } diff --git a/crates/cxx-qt-gen/src/parser/signals.rs b/crates/cxx-qt-gen/src/parser/signals.rs index 2ab85ba54..e58be2721 100644 --- a/crates/cxx-qt-gen/src/parser/signals.rs +++ b/crates/cxx-qt-gen/src/parser/signals.rs @@ -8,7 +8,8 @@ use crate::{ }; use core::ops::Deref; use std::ops::DerefMut; -use syn::{spanned::Spanned, Attribute, Error, ForeignItemFn, Result, Visibility}; +use syn::{spanned::Spanned, Error, ForeignItemFn, Result, Visibility}; +use crate::parser::CommonAttrs; #[derive(Clone)] /// Describes an individual Signal @@ -19,10 +20,8 @@ pub struct ParsedSignal { pub inherit: bool, /// Whether the signal is private pub private: bool, - /// All the doc attributes (each line) of the signal - pub docs: Vec, - /// Cfgs for signal - pub cfgs: Vec, + /// All the universal attributes for the signal + pub common_attrs: CommonAttrs, } impl ParsedSignal { @@ -58,8 +57,7 @@ impl ParsedSignal { method_fields: fields, inherit, private, - docs: common_attrs.docs, - cfgs: common_attrs.cfgs, + common_attrs, }) } }