1pub mod constructor;
7pub mod cxxqtdata;
8pub mod externcxxqt;
9pub mod externqobject;
10pub mod inherit;
11pub mod method;
12pub mod parameter;
13pub mod property;
14pub mod qenum;
15pub mod qnamespace;
16pub mod qobject;
17pub mod signals;
18pub mod trait_impl;
19
20use crate::{
21 naming::TypeNames,
22 syntax::{expr::expr_to_string, path::path_compare_str},
23};
24use convert_case::Case;
25use cxxqtdata::ParsedCxxQtData;
26use std::collections::BTreeMap;
27use syn::{
28 punctuated::Punctuated,
29 spanned::Spanned,
30 token::{Brace, Semi},
31 Attribute, Error, Expr, Ident, Item, ItemMod, Meta, Result, Token, Visibility,
32};
33
34#[derive(Copy, Clone)]
35pub struct CaseConversion {
36 pub cxx: Option<Case>,
37 pub rust: Option<Case>,
38}
39
40fn meta_to_case(attr: &Attribute, default: Case) -> Result<Case> {
42 match &attr.meta {
43 Meta::Path(_) => Ok(default),
44 Meta::NameValue(case) => match &case.value {
45 Expr::Path(expr_path) => match expr_path.path.require_ident()?.to_string().as_str() {
46 "Camel" => Ok(Case::Camel),
47 "Snake" => Ok(Case::Snake),
48 _ => Err(Error::new(
49 attr.span(),
50 "Invalid case! You can use either `Camel` or `Snake`",
51 )),
52 },
53 _ => Err(Error::new(
54 attr.span(),
55 "Case should be specified as an identifier! Like `#[auto_cxx_name = Camel]`",
56 )),
57 },
58 _ => Err(Error::new(
59 attr.span(),
60 "Invalid attribute format! Use like `auto_cxx_name` or `auto_cxx_name = Camel`",
61 )),
62 }
63}
64
65impl CaseConversion {
66 pub fn none() -> Self {
67 Self {
68 cxx: None,
69 rust: None,
70 }
71 }
72
73 pub fn from_attrs(attrs: &BTreeMap<&str, &Attribute>) -> Result<Self> {
76 let rust = attrs
77 .get("auto_rust_name")
78 .map(|attr| meta_to_case(attr, Case::Snake))
79 .transpose()?;
80 let cxx = attrs
81 .get("auto_cxx_name")
82 .map(|attr| meta_to_case(attr, Case::Camel))
83 .transpose()?;
84
85 Ok(Self { rust, cxx })
86 }
87}
88
89pub fn extract_cfgs(attrs: &[Attribute]) -> Vec<Attribute> {
91 attrs
92 .iter()
93 .filter(|attr| path_compare_str(attr.meta.path(), &["cfg"]))
94 .cloned()
95 .collect()
96}
97
98pub fn extract_docs(attrs: &[Attribute]) -> Vec<Attribute> {
100 attrs
101 .iter()
102 .filter(|attr| path_compare_str(attr.meta.path(), &["doc"]))
103 .cloned()
104 .collect()
105}
106
107fn split_path(path_str: &str) -> Vec<&str> {
109 let path = if path_str.contains("::") {
110 path_str.split("::").collect::<Vec<_>>()
111 } else {
112 vec![path_str]
113 };
114 path
115}
116
117pub fn require_attributes<'a>(
120 attrs: &'a [Attribute],
121 allowed: &'a [&str],
122) -> Result<BTreeMap<&'a str, &'a Attribute>> {
123 let mut output = BTreeMap::default();
124 for attr in attrs {
125 let index = allowed
126 .iter()
127 .position(|string| path_compare_str(attr.meta.path(), &split_path(string)));
128 if let Some(index) = index {
129 output.insert(allowed[index], attr); } else {
131 return Err(Error::new(
132 attr.span(),
133 format!(
134 "Unsupported attribute! The only attributes allowed on this item are\n{}",
135 allowed.join(", ")
136 ),
137 ));
138 }
139 }
140 Ok(output)
141}
142
143pub struct PassthroughMod {
145 pub(crate) items: Option<Vec<Item>>,
146 pub(crate) docs: Vec<Attribute>,
147 pub(crate) module_ident: Ident,
148 pub(crate) vis: Visibility,
149}
150
151impl PassthroughMod {
152 pub fn parse(module: ItemMod) -> Self {
154 let items = module.content.map(|(_, items)| items);
155
156 Self {
157 items,
158 docs: extract_docs(&module.attrs),
159 module_ident: module.ident,
160 vis: module.vis,
161 }
162 }
163}
164
165pub struct Parser {
170 pub(crate) passthrough_module: PassthroughMod,
172 pub(crate) cxx_qt_data: ParsedCxxQtData,
174 pub(crate) type_names: TypeNames,
176}
177
178impl Parser {
179 fn parse_mod_attributes(module: &mut ItemMod) -> Result<Option<String>> {
180 let attrs = require_attributes(&module.attrs, &["doc", "cxx_qt::bridge"])?;
181 let mut namespace = None;
182
183 if let Some(attr) = attrs.get("cxx_qt::bridge") {
185 if !matches!(attr.meta, Meta::Path(_)) {
187 let nested =
188 attr.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
189 for meta in nested {
190 match meta {
191 Meta::NameValue(ref name_value) => {
192 if name_value.path.is_ident("namespace") {
194 namespace = Some(expr_to_string(&name_value.value)?);
195 } else if name_value.path.is_ident("cxx_file_stem") {
197 return Err(Error::new(
198 meta.span(),
199 "cxx_file_stem is unsupported, instead the input file name will be used",
200 ));
201 }
202 }
203 _others => {}
204 }
205 }
206 }
207 } else {
208 return Err(Error::new(
209 module.span(),
210 "Tried to parse a module which doesn't have a cxx_qt::bridge attribute!",
211 ));
212 }
213
214 Ok(namespace)
215 }
216
217 fn parse_module_contents(
218 mut module: ItemMod,
219 namespace: Option<String>,
220 ) -> Result<(ParsedCxxQtData, ItemMod)> {
221 let mut others = vec![];
222
223 let mut cxx_qt_data = ParsedCxxQtData::new(module.ident.clone(), namespace);
224
225 if let Some((_, items)) = module.content {
227 for item in items.into_iter() {
229 if let Some(other) = cxx_qt_data.parse_cxx_qt_item(item)? {
232 others.push(other);
234 }
235 }
236 }
237
238 if !others.is_empty() {
240 module.content = Some((Brace::default(), others));
241 module.semi = None;
242 } else {
243 module.content = None;
244 module.semi = Some(Semi::default());
245 }
246 Ok((cxx_qt_data, module))
247 }
248
249 fn naming_phase(
251 cxx_qt_data: &mut ParsedCxxQtData,
252 cxx_items: &[Item],
253 module_ident: &Ident,
254 ) -> Result<TypeNames> {
255 TypeNames::from_parsed_data(
256 cxx_qt_data,
257 cxx_items,
258 cxx_qt_data.namespace.as_deref(),
259 module_ident,
260 )
261 }
262
263 pub fn from(mut module: ItemMod) -> Result<Self> {
265 let namespace = Self::parse_mod_attributes(&mut module)?;
266 let (mut cxx_qt_data, module) = Self::parse_module_contents(module, namespace)?;
267 let type_names = Self::naming_phase(
268 &mut cxx_qt_data,
269 module
270 .content
271 .as_ref()
272 .map(|brace_and_items| &brace_and_items.1)
273 .unwrap_or(&vec![]),
274 &module.ident,
275 )?;
276
277 Ok(Self {
279 passthrough_module: PassthroughMod::parse(module),
280 type_names,
281 cxx_qt_data,
282 })
283 }
284}
285
286#[cfg(test)]
287mod tests {
288 use super::*;
289
290 use crate::tests::assert_parse_errors;
291 use pretty_assertions::assert_eq;
292 use quote::format_ident;
293 use syn::{parse_quote, ItemMod, Type};
294
295 pub fn f64_type() -> Type {
297 parse_quote! { f64 }
298 }
299 #[test]
300 fn test_parser_from_empty_module() {
301 let module: ItemMod = parse_quote! {
302 #[cxx_qt::bridge]
303 mod ffi {}
304 };
305 let parser = Parser::from(module).unwrap();
306
307 assert!(parser.passthrough_module.items.is_none());
308 assert!(parser.passthrough_module.docs.is_empty());
309 assert_eq!(parser.passthrough_module.module_ident, "ffi");
310 assert_eq!(parser.passthrough_module.vis, Visibility::Inherited);
311 assert_eq!(parser.cxx_qt_data.namespace, None);
312 assert_eq!(parser.cxx_qt_data.qobjects.len(), 0);
313 }
314
315 #[test]
316 fn test_incorrect_bridge_args() {
317 let module: ItemMod = parse_quote! {
318 #[cxx_qt::bridge(a, b, c)]
319 mod ffi {
320 extern "Rust" {
321 fn test();
322 }
323 }
324 };
325 assert!(Parser::from(module).is_ok()); let module: ItemMod = parse_quote! {
328 #[cxx_qt::bridge(a = b)]
329 mod ffi {
330 extern "Rust" {
331 fn test();
332 }
333 }
334 };
335 assert!(Parser::from(module).is_ok()); }
337
338 #[test]
339 fn test_parser_from_cxx_items() {
340 let module: ItemMod = parse_quote! {
341 #[cxx_qt::bridge]
342 mod ffi {
343 extern "Rust" {
344 fn test();
345 }
346 }
347 };
348 let parser = Parser::from(module).unwrap();
349 assert_eq!(parser.passthrough_module.items.unwrap().len(), 1);
350 assert!(parser.passthrough_module.docs.is_empty());
351 assert_eq!(parser.passthrough_module.module_ident, "ffi");
352 assert_eq!(parser.passthrough_module.vis, Visibility::Inherited);
353 assert_eq!(parser.cxx_qt_data.namespace, None);
354 assert_eq!(parser.cxx_qt_data.qobjects.len(), 0);
355 }
356
357 #[test]
358 fn test_parser_from_cxx_qt_items() {
359 let module: ItemMod = parse_quote! {
360 #[cxx_qt::bridge(namespace = "cxx_qt")]
361 mod ffi {
362 extern "RustQt" {
363 #[qobject]
364 type MyObject = super::MyObjectRust;
365 }
366
367 unsafe extern "RustQt" {
368 #[qsignal]
369 fn ready(self: Pin<&mut MyObject>);
370 }
371 }
372 };
373 let parser = Parser::from(module.clone()).unwrap();
374
375 assert!(parser.passthrough_module.items.is_none());
376 assert!(parser.passthrough_module.docs.is_empty());
377 assert_eq!(parser.passthrough_module.module_ident, "ffi");
378 assert_eq!(parser.passthrough_module.vis, Visibility::Inherited);
379 assert_eq!(parser.cxx_qt_data.namespace, Some("cxx_qt".to_owned()));
380 assert_eq!(parser.cxx_qt_data.qobjects.len(), 1);
381 assert_eq!(parser.type_names.num_types(), 18);
382 assert_eq!(
383 parser
384 .type_names
385 .rust_qualified(&format_ident!("MyObject"))
386 .unwrap(),
387 parse_quote! { ffi::MyObject }
388 );
389 assert_eq!(
390 parser
391 .type_names
392 .rust_qualified(&format_ident!("MyObjectRust"))
393 .unwrap(),
394 parse_quote! { ffi::MyObjectRust }
395 );
396 }
397
398 #[test]
399 fn test_parser_from_cxx_and_cxx_qt_items() {
400 let module: ItemMod = parse_quote! {
401 #[cxx_qt::bridge]
402 mod ffi {
404 extern "RustQt" {
405 #[qobject]
406 type MyObject = super::MyObjectRust;
407 }
408
409 unsafe extern "RustQt" {
410 #[qsignal]
411 fn ready(self: Pin<&mut MyObject>);
412 }
413
414 extern "Rust" {
415 fn test();
416 }
417 }
418 };
419 let parser = Parser::from(module.clone()).unwrap();
420
421 assert_eq!(parser.passthrough_module.items.unwrap().len(), 1);
422 assert_eq!(parser.passthrough_module.docs.len(), 1);
423 assert_eq!(parser.passthrough_module.module_ident, "ffi");
424 assert_eq!(parser.passthrough_module.vis, Visibility::Inherited);
425 assert_eq!(parser.cxx_qt_data.namespace, None);
426 assert_eq!(parser.cxx_qt_data.qobjects.len(), 1);
427 }
428
429 #[test]
430 fn test_parser_invalid() {
431 assert_parse_errors! {
432 Parser::from =>
433
434 {
435 #[cxx_qt::bridge]
437 mod ffi {
438 extern "Rust" {
439 #[namespace = 1]
440 type MyObject = super::MyObjectRust;
441 }
442 }
443 }
444 {
445 mod ffi {
447 extern "Rust" {
448 fn test();
449 }
450 }
451 }
452 {
453 #[cxx_qt::bridge(cxx_file_stem = "stem")]
455 mod ffi {
456 extern "Rust" {
457 fn test();
458 }
459 }
460 }
461 }
462 }
463
464 #[test]
465 fn test_cxx_qobject_namespace() {
466 let module: ItemMod = parse_quote! {
467 #[cxx_qt::bridge(namespace = "bridge_namespace")]
468 mod ffi {
469 extern "RustQt" {
470 #[qobject]
471 type MyObjectA = super::MyObjectARust;
472
473 #[qobject]
474 #[namespace = "type_namespace"]
475 type MyObjectB = super::MyObjectBRust;
476 }
477
478 #[namespace = "extern_namespace"]
479 extern "RustQt" {
480 #[qobject]
481 type MyObjectC = super::MyObjectCRust;
482 }
483 }
484 };
485 let parser = Parser::from(module).unwrap();
486 assert_eq!(parser.type_names.num_types(), 22);
487 assert_eq!(
488 parser
489 .type_names
490 .namespace(&format_ident!("MyObjectA"))
491 .unwrap()
492 .unwrap(),
493 "bridge_namespace"
494 );
495 assert_eq!(
496 parser
497 .type_names
498 .namespace(&format_ident!("MyObjectB"))
499 .unwrap()
500 .unwrap(),
501 "type_namespace"
502 );
503 assert_eq!(
504 parser
505 .type_names
506 .namespace(&format_ident!("MyObjectC"))
507 .unwrap()
508 .unwrap(),
509 "extern_namespace"
510 );
511
512 assert_eq!(
513 parser
514 .type_names
515 .namespace(&format_ident!("MyObjectARust"))
516 .unwrap(),
517 None
518 );
519 assert_eq!(
520 parser
521 .type_names
522 .namespace(&format_ident!("MyObjectBRust"))
523 .unwrap(),
524 None
525 );
526 assert_eq!(
527 parser
528 .type_names
529 .namespace(&format_ident!("MyObjectCRust"))
530 .unwrap(),
531 None
532 );
533 }
534}