Skip to content

Commit

Permalink
cxx-qt-gen: Improve attribute parsing
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
BenFordTytherington authored Sep 20, 2024
1 parent 4b23a7b commit 8ac1f79
Show file tree
Hide file tree
Showing 35 changed files with 670 additions and 501 deletions.
49 changes: 29 additions & 20 deletions crates/cxx-qt-gen/src/generator/cpp/externcxxqt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"));
Expand Down
2 changes: 1 addition & 1 deletion crates/cxx-qt-gen/src/generator/cpp/inherit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ mod tests {
method: ForeignItemFn,
base_class: Option<&str>,
) -> Result<GeneratedCppQObjectBlocks> {
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())
Expand Down
25 changes: 11 additions & 14 deletions crates/cxx-qt-gen/src/generator/cpp/property/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -95,7 +95,7 @@ pub mod tests {
}

fn setup_generated(input: &mut ItemStruct) -> Result<GeneratedCppQObjectBlocks> {
let property = ParsedQProperty::parse(input.attrs.remove(0)).unwrap();
let property = ParsedQProperty::parse(&input.attrs.remove(0))?;

let properties = vec![property];

Expand Down Expand Up @@ -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];

Expand Down Expand Up @@ -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];

Expand Down Expand Up @@ -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];

Expand Down Expand Up @@ -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<QColor>, 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();

Expand Down
51 changes: 47 additions & 4 deletions crates/cxx-qt-gen/src/generator/rust/externcxxqt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@
// SPDX-FileContributor: Andrew Hayzen <[email protected]>
//
// SPDX-License-Identifier: MIT OR Apache-2.0

use crate::{
generator::rust::{
fragment::{GeneratedRustFragment, RustFragmentPair},
signals::generate_rust_signal,
},
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(
Expand All @@ -21,15 +21,58 @@ impl GeneratedRustFragment {
) -> Result<Self> {
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::<Vec<_>>();

let fragment = RustFragmentPair {
cxx_bridge: vec![quote! {
#(#attrs)*
#extern_block_namespace
#unsafety extern "C++" {
#(#items)*

#(#types)*
}
}],
implementation: vec![],
Expand Down
7 changes: 3 additions & 4 deletions crates/cxx-qt-gen/src/generator/rust/inherit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -78,7 +77,7 @@ mod tests {
method: ForeignItemFn,
safety: Safety,
) -> Result<GeneratedRustFragment> {
let method = ParsedInheritedMethod::parse(method, safety).unwrap();
let method = ParsedInheritedMethod::parse(method, safety)?;
let inherited_methods = vec![&method];
generate(&create_qobjectname(), &inherited_methods)
}
Expand Down
60 changes: 51 additions & 9 deletions crates/cxx-qt-gen/src/generator/rust/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -59,10 +57,35 @@ impl GeneratedRustBlocks {
.collect::<Result<Vec<GeneratedRustFragment>>>()?,
);

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,
})
}
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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);
}
}
Loading

0 comments on commit 8ac1f79

Please sign in to comment.