Skip to content

Commit aaa1547

Browse files
WIP add attribute parsing enum (check coverage)
1 parent b5e2b50 commit aaa1547

File tree

4 files changed

+196
-30
lines changed

4 files changed

+196
-30
lines changed

crates/cxx-qt-gen/src/parser/attribute.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,20 @@ pub struct ParsedAttributes {
1414
pub passthrough_attrs: Vec<Attribute>,
1515
}
1616

17+
// TODO: ATTR could this instead be used as Result<ParsedAttribute> to encapsulate error states
18+
pub enum ParsedAttribute<'a> {
19+
/// A single attribute was found
20+
Single(&'a Attribute),
21+
/// An attribute was not found, but this is ok
22+
Absent,
23+
/// An attribute was not found, and this is an error
24+
AbsentRequired,
25+
/// Multiple attributes were found, but this is ok
26+
Multiple(Vec<&'a Attribute>),
27+
/// Multiple attributes were found, but this is an error
28+
MultipleDisallowed(Vec<&'a Attribute>),
29+
}
30+
1731
/// Iterate the attributes of the method to extract cfg attributes
1832
pub fn extract_cfgs(attrs: &[Attribute]) -> Vec<Attribute> {
1933
attrs
@@ -108,6 +122,18 @@ impl<'a> ParsedAttributes {
108122
self.cxx_qt_attrs.get(key)?.first()
109123
}
110124

125+
pub fn require_one(&self, key: &str) -> ParsedAttribute {
126+
if let Some(attrs) = self.cxx_qt_attrs.get(key) {
127+
if attrs.len() != 1 {
128+
ParsedAttribute::MultipleDisallowed(attrs.iter().by_ref().collect())
129+
} else {
130+
ParsedAttribute::Single(attrs.first().expect("Expected at least one attribute"))
131+
}
132+
} else {
133+
ParsedAttribute::Absent
134+
}
135+
}
136+
111137
/// Check if CXX-Qt or passthrough attributes contains a particular key
112138
pub fn contains_key(&self, key: &str) -> bool {
113139
self.cxx_qt_attrs.contains_key(key) // TODO: Check in passthrough too

crates/cxx-qt-gen/src/parser/externcxxqt.rs

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33
//
44
// SPDX-License-Identifier: MIT OR Apache-2.0
55

6-
use crate::parser::attribute::ParsedAttributes;
6+
use crate::parser::attribute::{ParsedAttribute, ParsedAttributes};
77
use crate::{
88
parser::{externqobject::ParsedExternQObject, signals::ParsedSignal, CaseConversion},
99
syntax::{attribute::attribute_get_path, expr::expr_to_string},
1010
};
11+
use proc_macro2::Span;
1112
use syn::{
1213
spanned::Spanned, Error, ForeignItem, ForeignItemFn, Ident, ItemForeignMod, Result, Token,
1314
};
@@ -41,12 +42,25 @@ impl ParsedExternCxxQt {
4142

4243
let auto_case = CaseConversion::from_attrs(&attrs)?;
4344

44-
let namespace = attrs
45-
.get_one("namespace")
46-
.map(|attr| -> Result<String> {
47-
expr_to_string(&attr.meta.require_name_value()?.value)
48-
})
49-
.transpose()?;
45+
let namespace = match attrs.require_one("namespace") {
46+
ParsedAttribute::Single(attr) => {
47+
expr_to_string(&attr.meta.require_name_value()?.value).ok()
48+
}
49+
ParsedAttribute::Absent => None,
50+
ParsedAttribute::MultipleDisallowed(_) => {
51+
Err(Error::new(
52+
Span::call_site(),
53+
"There must be at most one namespace attribute",
54+
))? // TODO: ATTR use real span
55+
}
56+
_ => {
57+
// CODECOV_EXCLUDE_START
58+
unreachable!(
59+
"Namepsace is not an allowed duplicate, nor required so this block should be unreachable"
60+
)
61+
// CODECOV_EXCLUDE_STOP
62+
}
63+
};
5064

5165
let mut extern_cxx_block = ParsedExternCxxQt {
5266
namespace,
@@ -219,6 +233,26 @@ mod tests {
219233
type QPushButton;
220234
}
221235
}
236+
237+
// Duplicate base attr is an error
238+
{
239+
extern "C++Qt" {
240+
#[base = QPushButton]
241+
#[base = QPushButton]
242+
#[qobject]
243+
type QPushButtonChild;
244+
}
245+
}
246+
247+
// All types in "C++Qt" blocks must be marked as QObjects
248+
{
249+
#[namespace = "My namespace"]
250+
#[namespace = "My other namespace"]
251+
unsafe extern "C++Qt" {
252+
#[qobject]
253+
type QPushButton;
254+
}
255+
}
222256
);
223257
}
224258
}

crates/cxx-qt-gen/src/parser/externrustqt.rs

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
// SPDX-License-Identifier: MIT OR Apache-2.0
55

66
use crate::naming::cpp::err_unsupported_item;
7-
use crate::parser::attribute::ParsedAttributes;
7+
use crate::parser::attribute::{ParsedAttribute, ParsedAttributes};
88
use crate::parser::inherit::ParsedInheritedMethod;
99
use crate::parser::method::ParsedMethod;
1010
use crate::parser::qobject::ParsedQObject;
@@ -13,7 +13,7 @@ use crate::parser::CaseConversion;
1313
use crate::syntax::attribute::attribute_get_path;
1414
use crate::syntax::expr::expr_to_string;
1515
use crate::syntax::foreignmod::ForeignTypeIdentAlias;
16-
use proc_macro2::Ident;
16+
use proc_macro2::{Ident, Span};
1717
use syn::spanned::Spanned;
1818
use syn::{Error, ForeignItem, ForeignItemFn, ItemForeignMod, Result, Token};
1919

@@ -51,11 +51,31 @@ impl ParsedExternRustQt {
5151
..Default::default()
5252
};
5353

54-
let namespace = attrs
55-
.get_one("namespace")
56-
.map(|attr| expr_to_string(&attr.meta.require_name_value()?.value))
57-
.transpose()?
58-
.or_else(|| parent_namespace.map(String::from));
54+
// let namespace = attrs
55+
// .get_one("namespace")
56+
// .map(|attr| expr_to_string(&attr.meta.require_name_value()?.value))
57+
// .transpose()?
58+
// .or_else(|| parent_namespace.map(String::from));
59+
60+
let namespace = match attrs.require_one("namespace") {
61+
ParsedAttribute::Single(attr) => {
62+
expr_to_string(&attr.meta.require_name_value()?.value).ok()
63+
}
64+
ParsedAttribute::Absent => None,
65+
ParsedAttribute::MultipleDisallowed(_) => {
66+
Err(Error::new(
67+
Span::call_site(),
68+
"There must be at most one namespace attribute",
69+
))? // TODO: ATTR use real span
70+
}
71+
_ => {
72+
// CODECOV_EXCLUDE_START
73+
unreachable!(
74+
"Namepsace is not an allowed duplicate, nor required so this block should be unreachable"
75+
)
76+
// CODECOV_EXCLUDE_STOP
77+
}
78+
}.or_else(|| parent_namespace.map(String::from));
5979

6080
for item in foreign_mod.items.drain(..) {
6181
match item {
@@ -224,6 +244,36 @@ mod tests {
224244
}
225245
}
226246

247+
// Duplicate namespace not allowed
248+
{
249+
#[namespace = "My Namespace"]
250+
#[namespace = "My Other Namespace"]
251+
unsafe extern "RustQt" {
252+
#[qinvokable]
253+
fn invokable(self: &MyObject);
254+
}
255+
}
256+
257+
// Duplicate auto_cxx_name not allowed
258+
{
259+
unsafe extern "RustQt" {
260+
#[qobject]
261+
#[auto_cxx_name]
262+
#[auto_cxx_name]
263+
type MyObject = super::MyObjectRust;
264+
}
265+
}
266+
267+
// Duplicate auto_rust_name not allowed
268+
{
269+
unsafe extern "RustQt" {
270+
#[qobject]
271+
#[auto_rust_name]
272+
#[auto_rust_name]
273+
type MyObject = super::MyObjectRust;
274+
}
275+
}
276+
227277
// Namespaces aren't allowed on qinvokables
228278
{
229279
unsafe extern "RustQt" {

crates/cxx-qt-gen/src/parser/mod.rs

Lines changed: 72 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@ pub mod qobject;
1919
pub mod signals;
2020
pub mod trait_impl;
2121

22-
use crate::parser::attribute::ParsedAttributes;
22+
use crate::parser::attribute::{ParsedAttribute, ParsedAttributes};
2323
use crate::{naming::TypeNames, syntax::expr::expr_to_string};
2424
use convert_case::Case;
2525
use cxxqtdata::ParsedCxxQtData;
26+
use proc_macro2::Span;
2627
use syn::{
2728
punctuated::Punctuated,
2829
spanned::Spanned,
@@ -73,14 +74,54 @@ impl CaseConversion {
7374
/// Parses both `auto_cxx_name` and `auto_cxx_name = Camel`
7475
pub fn from_attrs(attrs: &ParsedAttributes) -> Result<Self> {
7576
// TODO: ATTR Won't error on duplicates, and needs error handling
76-
let rust = attrs
77-
.get_one("auto_rust_name")
78-
.map(|attr| meta_to_case(attr, Case::Snake))
79-
.transpose()?;
80-
let cxx = attrs
81-
.get_one("auto_cxx_name")
82-
.map(|attr| meta_to_case(attr, Case::Camel))
83-
.transpose()?;
77+
// let rust = attrs
78+
// .get_one("auto_rust_name")
79+
// .map(|attr| meta_to_case(attr, Case::Snake))
80+
// .transpose()?;
81+
// let cxx = attrs
82+
// .get_one("auto_cxx_name")
83+
// .map(|attr| meta_to_case(attr, Case::Camel))
84+
// .transpose()?;
85+
86+
let rust = match attrs.require_one("auto_rust_name") {
87+
ParsedAttribute::Single(attr) => {
88+
Some(meta_to_case(attr, Case::Snake)?)
89+
}
90+
ParsedAttribute::Absent => None,
91+
ParsedAttribute::MultipleDisallowed(_) => {
92+
Err(Error::new(
93+
Span::call_site(),
94+
"There must be at most one auto_rust_name attribute",
95+
))? // TODO: ATTR use real span
96+
}
97+
_ => {
98+
// CODECOV_EXCLUDE_START
99+
unreachable!(
100+
"Auto_rust_name is not an allowed duplicate, nor required so this block should be unreachable"
101+
)
102+
// CODECOV_EXCLUDE_STOP
103+
}
104+
};
105+
106+
let cxx = match attrs.require_one("auto_cxx_name") {
107+
ParsedAttribute::Single(attr) => {
108+
Some(meta_to_case(attr, Case::Camel)?)
109+
}
110+
ParsedAttribute::Absent => None,
111+
ParsedAttribute::MultipleDisallowed(_) => {
112+
Err(Error::new(
113+
Span::call_site(),
114+
"There must be at most one auto_cxx_name attribute",
115+
))? // TODO: ATTR use real span
116+
}
117+
_ => {
118+
// CODECOV_EXCLUDE_START
119+
unreachable!(
120+
"Auto_cxx_name is not an allowed duplicate, nor required so this block should be unreachable"
121+
)
122+
// CODECOV_EXCLUDE_STOP
123+
}
124+
};
84125

85126
Ok(Self { rust, cxx })
86127
}
@@ -98,20 +139,35 @@ fn split_path(path_str: &str) -> Vec<&str> {
98139

99140
// Extract base identifier from attribute
100141
pub fn parse_base_type(attributes: &ParsedAttributes) -> Result<Option<Ident>> {
101-
attributes
102-
.get_one("base") // TODO: ATTR Check for duplicates
103-
.map(|attr| -> Result<Ident> {
142+
match attributes.require_one("base") {
143+
ParsedAttribute::Single(attr) => {
104144
let expr = &attr.meta.require_name_value()?.value;
105145
if let Expr::Path(path_expr) = expr {
106-
Ok(path_expr.path.require_ident()?.clone())
146+
Ok(Some(path_expr.path.require_ident()?.clone()))
107147
} else {
108148
Err(Error::new_spanned(
109149
expr,
110-
"Base must be a identifier and cannot be empty!",
150+
"There must be a single base identifier, not string, and cannot be empty!",
111151
))
112152
}
113-
})
114-
.transpose()
153+
}
154+
ParsedAttribute::Absent => Ok(None),
155+
ParsedAttribute::MultipleDisallowed(attrs) => {
156+
let attr = attrs.first().expect("Expected at least one attribute");
157+
let expr = &attr.meta.require_name_value()?.value;
158+
Err(Error::new_spanned(
159+
expr,
160+
"There must be a single base identifier, not string, and cannot be empty!",
161+
))
162+
}
163+
_ => {
164+
// CODECOV_EXCLUDE_START
165+
unreachable!(
166+
"base is not an allowed duplicate, nor required so this block should be unreachable"
167+
)
168+
// CODECOV_EXCLUDE_STOP
169+
}
170+
}
115171
}
116172

117173
/// Struct representing the necessary components of a cxx mod to be passed through to generation

0 commit comments

Comments
 (0)