1use std::{
2 collections::{BTreeMap, HashSet},
3 str::FromStr,
4 sync::Arc,
5};
6
7use log::{error, warn};
8use toml::Value;
9
10use super::{
11 child_properties::ChildProperties,
12 constants::Constants,
13 derives::Derives,
14 functions::Functions,
15 ident::Ident,
16 members::Members,
17 properties::Properties,
18 signals::{Signal, Signals},
19 virtual_methods::VirtualMethods,
20};
21use crate::{
22 analysis::{conversion_type::ConversionType, ref_mode},
23 codegen::Visibility,
24 config::{
25 error::TomlHelper,
26 parsable::{Parsable, Parse},
27 },
28 library::{self, Library, TypeId, MAIN_NAMESPACE},
29 version::Version,
30};
31
32#[derive(Default, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
33pub enum GStatus {
34 Manual,
35 Generate,
36 #[default]
37 Ignore,
38}
39
40impl GStatus {
41 pub fn ignored(self) -> bool {
42 self == Self::Ignore
43 }
44 pub fn manual(self) -> bool {
45 self == Self::Manual
46 }
47 pub fn need_generate(self) -> bool {
48 self == Self::Generate
49 }
50}
51
52impl FromStr for GStatus {
53 type Err = String;
54 fn from_str(s: &str) -> Result<Self, Self::Err> {
55 match s {
56 "manual" => Ok(Self::Manual),
57 "generate" => Ok(Self::Generate),
58 "ignore" => Ok(Self::Ignore),
59 e => Err(format!("Wrong object status: \"{e}\"")),
60 }
61 }
62}
63
64#[derive(Clone, Debug)]
66pub struct GObject {
67 pub name: String,
68 pub functions: Functions,
69 pub virtual_methods: VirtualMethods,
70 pub constants: Constants,
71 pub signals: Signals,
72 pub members: Members,
73 pub properties: Properties,
74 pub derives: Option<Derives>,
75 pub status: GStatus,
76 pub module_name: Option<String>,
77 pub version: Option<Version>,
78 pub cfg_condition: Option<String>,
79 pub type_id: Option<TypeId>,
80 pub final_type: Option<bool>,
81 pub fundamental_type: Option<bool>,
82 pub exhaustive: bool,
83 pub trait_name: Option<String>,
84 pub child_properties: Option<ChildProperties>,
85 pub concurrency: library::Concurrency,
86 pub ref_mode: Option<ref_mode::RefMode>,
87 pub must_use: bool,
88 pub conversion_type: Option<ConversionType>,
89 pub trust_return_value_nullability: bool,
90 pub manual_traits: Vec<String>,
91 pub align: Option<u32>,
92 pub generate_builder: bool,
93 pub builder_postprocess: Option<String>,
94 pub boxed_inline: bool,
95 pub init_function_expression: Option<String>,
96 pub copy_into_function_expression: Option<String>,
97 pub clear_function_expression: Option<String>,
98 pub visibility: Visibility,
99 pub default_value: Option<String>,
100 pub generate_doc: bool,
101}
102
103impl Default for GObject {
104 fn default() -> GObject {
105 GObject {
106 name: "Default".into(),
107 functions: Functions::new(),
108 virtual_methods: VirtualMethods::new(),
109 constants: Constants::new(),
110 signals: Signals::new(),
111 members: Members::new(),
112 properties: Properties::new(),
113 derives: None,
114 status: Default::default(),
115 module_name: None,
116 version: None,
117 cfg_condition: None,
118 type_id: None,
119 final_type: None,
120 fundamental_type: None,
121 exhaustive: false,
122 trait_name: None,
123 child_properties: None,
124 concurrency: Default::default(),
125 ref_mode: None,
126 must_use: false,
127 conversion_type: None,
128 trust_return_value_nullability: false,
129 manual_traits: Vec::default(),
130 align: None,
131 generate_builder: false,
132 builder_postprocess: None,
133 boxed_inline: false,
134 init_function_expression: None,
135 copy_into_function_expression: None,
136 clear_function_expression: None,
137 visibility: Default::default(),
138 default_value: None,
139 generate_doc: true,
140 }
141 }
142}
143
144pub type GObjects = BTreeMap<String, GObject>;
146
147pub fn parse_toml(
148 toml_objects: &Value,
149 concurrency: library::Concurrency,
150 generate_builder: bool,
151 trust_return_value_nullability: bool,
152) -> GObjects {
153 let mut objects = GObjects::new();
154 for toml_object in toml_objects.as_array().unwrap() {
155 let gobject = parse_object(
156 toml_object,
157 concurrency,
158 generate_builder,
159 trust_return_value_nullability,
160 );
161 objects.insert(gobject.name.clone(), gobject);
162 }
163 objects
164}
165
166pub fn parse_conversion_type(toml: Option<&Value>, object_name: &str) -> Option<ConversionType> {
167 use crate::analysis::conversion_type::ConversionType::*;
168
169 let v = toml?;
170 v.check_unwanted(&["variant", "ok_type", "err_type"], "conversion_type");
171
172 let (conversion_type, ok_type, err_type) = match &v {
173 Value::Table(table) => {
174 let conversion_type = table.get("variant").and_then(Value::as_str);
175 if conversion_type.is_none() {
176 error!("Missing `variant` for {}.conversion_type", object_name);
177 return None;
178 }
179
180 let ok_type = Some(Arc::from(
181 table
182 .get("ok_type")
183 .and_then(Value::as_str)
184 .unwrap_or(object_name),
185 ));
186 let err_type = table.get("err_type").and_then(Value::as_str);
187
188 (conversion_type.unwrap(), ok_type, err_type)
189 }
190 Value::String(conversion_type) => (conversion_type.as_str(), None, None),
191 _ => {
192 error!("Unexpected toml item for {}.conversion_type", object_name);
193 return None;
194 }
195 };
196
197 let get_err_type = || -> Arc<str> {
198 err_type.map_or_else(
199 || {
200 error!("Missing `err_type` for {}.conversion_type", object_name);
201 Arc::from("MissingErrorType")
202 },
203 Arc::from,
204 )
205 };
206
207 match conversion_type {
208 "direct" => Some(Direct),
209 "scalar" => Some(Scalar),
210 "Option" => Some(Option),
211 "Result" => Some(Result {
212 ok_type: ok_type.expect("Missing `ok_type`"),
213 err_type: get_err_type(),
214 }),
215 "pointer" => Some(Pointer),
216 "borrow" => Some(Borrow),
217 "unknown" => Some(Unknown),
218 unexpected => {
219 error!(
220 "Unexpected {} for {}.conversion_type",
221 unexpected, object_name
222 );
223 None
224 }
225 }
226}
227
228fn parse_object(
229 toml_object: &Value,
230 concurrency: library::Concurrency,
231 generate_builder: bool,
232 trust_return_value_nullability: bool,
233) -> GObject {
234 let name: String = toml_object
235 .lookup("name")
236 .expect("Object name not defined")
237 .as_str()
238 .unwrap()
239 .into();
240 toml_object.check_unwanted(
242 &[
243 "name",
244 "status",
245 "function",
246 "constant",
247 "signal",
248 "member",
249 "property",
250 "derive",
251 "module_name",
252 "version",
253 "concurrency",
254 "ref_mode",
255 "conversion_type",
256 "child_prop",
257 "child_name",
258 "child_type",
259 "final_type",
260 "fundamental_type",
261 "exhaustive",
262 "trait",
263 "trait_name",
264 "cfg_condition",
265 "must_use",
266 "trust_return_value_nullability",
267 "manual_traits",
268 "align",
269 "generate_builder",
270 "builder_postprocess",
271 "boxed_inline",
272 "init_function_expression",
273 "copy_into_function_expression",
274 "clear_function_expression",
275 "visibility",
276 "default_value",
277 "generate_doc",
278 ],
279 &format!("object {name}"),
280 );
281
282 let status = match toml_object.lookup("status") {
283 Some(value) => {
284 GStatus::from_str(value.as_str().unwrap()).unwrap_or_else(|_| Default::default())
285 }
286 None => Default::default(),
287 };
288
289 let constants = Constants::parse(toml_object.lookup("constant"), &name);
290 let functions = Functions::parse(toml_object.lookup("function"), &name);
291 let mut function_names = HashSet::new();
292 for f in &functions {
293 if let Ident::Name(name) = &f.ident {
294 assert!(function_names.insert(name), "{name} already defined!");
295 }
296 }
297 let virtual_methods = VirtualMethods::parse(toml_object.lookup("virtual_method"), &name);
298 let mut virtual_methods_names = HashSet::new();
299 for f in &virtual_methods {
300 if let Ident::Name(name) = &f.ident {
301 assert!(
302 virtual_methods_names.insert(name),
303 "{name} already defined!"
304 );
305 }
306 }
307
308 let signals = {
309 let mut v = Vec::new();
310 if let Some(configs) = toml_object.lookup("signal").and_then(Value::as_array) {
311 for config in configs {
312 if let Some(item) = Signal::parse(config, &name, concurrency) {
313 v.push(item);
314 }
315 }
316 }
317
318 v
319 };
320 let members = Members::parse(toml_object.lookup("member"), &name);
321 let properties = Properties::parse(toml_object.lookup("property"), &name);
322 let derives = toml_object
323 .lookup("derive")
324 .map(|derives| Derives::parse(Some(derives), &name));
325 let module_name = toml_object
326 .lookup("module_name")
327 .and_then(Value::as_str)
328 .map(ToOwned::to_owned);
329 let version = toml_object
330 .lookup("version")
331 .and_then(Value::as_str)
332 .and_then(|s| s.parse().ok());
333 let cfg_condition = toml_object
334 .lookup("cfg_condition")
335 .and_then(Value::as_str)
336 .map(ToOwned::to_owned);
337 let generate_trait = toml_object.lookup("trait").and_then(Value::as_bool);
338 let final_type = toml_object
339 .lookup("final_type")
340 .and_then(Value::as_bool)
341 .or_else(|| generate_trait.map(|t| !t));
342 let fundamental_type = toml_object
343 .lookup("fundamental_type")
344 .and_then(Value::as_bool);
345 let exhaustive = toml_object
346 .lookup("exhaustive")
347 .and_then(Value::as_bool)
348 .unwrap_or(false);
349 let trait_name = toml_object
350 .lookup("trait_name")
351 .and_then(Value::as_str)
352 .map(ToOwned::to_owned);
353 let concurrency = toml_object
354 .lookup("concurrency")
355 .and_then(Value::as_str)
356 .and_then(|v| v.parse().ok())
357 .unwrap_or(concurrency);
358 let ref_mode = toml_object
359 .lookup("ref_mode")
360 .and_then(Value::as_str)
361 .and_then(|v| v.parse().ok());
362 let conversion_type = parse_conversion_type(toml_object.lookup("conversion_type"), &name);
363 let child_properties = ChildProperties::parse(toml_object, &name);
364 let must_use = toml_object
365 .lookup("must_use")
366 .and_then(Value::as_bool)
367 .unwrap_or(false);
368 let trust_return_value_nullability = toml_object
369 .lookup("trust_return_value_nullability")
370 .and_then(Value::as_bool)
371 .unwrap_or(trust_return_value_nullability);
372 let manual_traits = toml_object
373 .lookup_vec("manual_traits", "IGNORED ERROR")
374 .into_iter()
375 .flatten()
376 .filter_map(|v| v.as_str().map(String::from))
377 .collect();
378 let align = toml_object
379 .lookup("align")
380 .and_then(Value::as_integer)
381 .and_then(|v| {
382 if v.count_ones() != 1 || v > i64::from(u32::MAX) || v < 0 {
383 warn!(
384 "`align` configuration must be a power of two of type u32, found {}",
385 v
386 );
387 None
388 } else {
389 Some(v as u32)
390 }
391 });
392 let generate_builder = toml_object
393 .lookup("generate_builder")
394 .and_then(Value::as_bool)
395 .unwrap_or(generate_builder);
396
397 let boxed_inline = toml_object
398 .lookup("boxed_inline")
399 .and_then(Value::as_bool)
400 .unwrap_or(false);
401
402 let builder_postprocess = toml_object
403 .lookup("builder_postprocess")
404 .and_then(Value::as_str)
405 .map(String::from);
406 let init_function_expression = toml_object
407 .lookup("init_function_expression")
408 .and_then(Value::as_str)
409 .map(ToOwned::to_owned);
410 let copy_into_function_expression = toml_object
411 .lookup("copy_into_function_expression")
412 .and_then(Value::as_str)
413 .map(ToOwned::to_owned);
414 let clear_function_expression = toml_object
415 .lookup("clear_function_expression")
416 .and_then(Value::as_str)
417 .map(ToOwned::to_owned);
418 let default_value = toml_object
419 .lookup("default_value")
420 .and_then(Value::as_str)
421 .map(ToOwned::to_owned);
422
423 let visibility = toml_object
424 .lookup("visibility")
425 .and_then(Value::as_str)
426 .map(|v| v.parse())
427 .transpose();
428 if let Err(ref err) = visibility {
429 error!("{}", err);
430 }
431 let visibility = visibility.ok().flatten().unwrap_or_default();
432 if boxed_inline
433 && !((init_function_expression.is_none()
434 && copy_into_function_expression.is_none()
435 && clear_function_expression.is_none())
436 || (init_function_expression.is_some()
437 && copy_into_function_expression.is_some()
438 && clear_function_expression.is_some()))
439 {
440 panic!(
441 "`init_function_expression`, `copy_into_function_expression` and `clear_function_expression` all have to be provided or neither"
442 );
443 }
444
445 if !boxed_inline
446 && (init_function_expression.is_some()
447 || copy_into_function_expression.is_some()
448 || clear_function_expression.is_some())
449 {
450 panic!(
451 "`init_function_expression`, `copy_into_function_expression` and `clear_function_expression` can only be provided for BoxedInline types"
452 );
453 }
454
455 if status != GStatus::Manual && ref_mode.is_some() {
456 warn!("ref_mode configuration used for non-manual object {}", name);
457 }
458
459 if status != GStatus::Manual
460 && !conversion_type
461 .as_ref()
462 .is_none_or(ConversionType::can_use_to_generate)
463 {
464 warn!(
465 "unexpected conversion_type {:?} configuration used for non-manual object {}",
466 conversion_type, name
467 );
468 }
469
470 let generate_doc = toml_object
471 .lookup("generate_doc")
472 .and_then(Value::as_bool)
473 .unwrap_or(true);
474
475 if generate_trait.is_some() {
476 warn!(
477 "`trait` configuration is deprecated and replaced by `final_type` for object {}",
478 name
479 );
480 }
481
482 GObject {
483 name,
484 functions,
485 virtual_methods,
486 constants,
487 signals,
488 members,
489 properties,
490 derives,
491 status,
492 module_name,
493 version,
494 cfg_condition,
495 type_id: None,
496 final_type,
497 fundamental_type,
498 exhaustive,
499 trait_name,
500 child_properties,
501 concurrency,
502 ref_mode,
503 must_use,
504 conversion_type,
505 trust_return_value_nullability,
506 manual_traits,
507 align,
508 generate_builder,
509 builder_postprocess,
510 boxed_inline,
511 init_function_expression,
512 copy_into_function_expression,
513 clear_function_expression,
514 visibility,
515 default_value,
516 generate_doc,
517 }
518}
519
520pub fn parse_status_shorthands(
521 objects: &mut GObjects,
522 toml: &Value,
523 concurrency: library::Concurrency,
524 generate_builder: bool,
525 trust_return_value_nullability: bool,
526) {
527 use self::GStatus::*;
528 for &status in &[Manual, Generate, Ignore] {
529 parse_status_shorthand(
530 objects,
531 status,
532 toml,
533 concurrency,
534 generate_builder,
535 trust_return_value_nullability,
536 );
537 }
538}
539
540fn parse_status_shorthand(
541 objects: &mut GObjects,
542 status: GStatus,
543 toml: &Value,
544 concurrency: library::Concurrency,
545 generate_builder: bool,
546 trust_return_value_nullability: bool,
547) {
548 let option_name = format!("options.{status:?}").to_ascii_lowercase();
549 if let Some(a) = toml.lookup(&option_name).map(|a| a.as_array().unwrap()) {
550 for name in a.iter().map(|s| s.as_str().unwrap()) {
551 match objects.get(name) {
552 None => {
553 objects.insert(
554 name.into(),
555 GObject {
556 name: name.into(),
557 status,
558 concurrency,
559 trust_return_value_nullability,
560 generate_builder,
561 ..Default::default()
562 },
563 );
564 }
565 Some(_) => panic!("Bad name in {option_name}: {name} already defined"),
566 }
567 }
568 }
569}
570
571pub fn resolve_type_ids(objects: &mut GObjects, library: &Library) {
572 let ns = library.namespace(MAIN_NAMESPACE);
573 let global_functions_name = format!("{}.*", ns.name);
574
575 for (name, object) in objects.iter_mut() {
576 let type_id = library.find_type(0, name);
577 if type_id.is_none() && name != &global_functions_name && object.status != GStatus::Ignore {
578 warn!("Configured object `{}` missing from the library", name);
579 } else if object.generate_builder {
580 if let Some(type_id) = type_id {
581 if library.type_(type_id).is_abstract() {
582 warn!(
583 "Cannot generate builder for `{}` because it's a base class",
584 name
585 );
586 object.generate_builder = false;
589 }
590 }
591 }
592 object.type_id = type_id;
593 }
594}
595
596#[cfg(test)]
597mod tests {
598 use super::*;
599 use crate::{analysis::conversion_type::ConversionType, library::Concurrency};
600
601 fn toml(input: &str) -> ::toml::Value {
602 let value = ::toml::from_str(input);
603 assert!(value.is_ok());
604 value.unwrap()
605 }
606
607 #[test]
608 fn conversion_type_default() {
609 let toml = &toml(
610 r#"
611name = "Test"
612status = "generate"
613"#,
614 );
615
616 let object = parse_object(toml, Concurrency::default(), false, false);
617 assert_eq!(object.conversion_type, None);
618 }
619
620 #[test]
621 fn conversion_type_option_str() {
622 let toml = toml(
623 r#"
624name = "Test"
625status = "generate"
626conversion_type = "Option"
627"#,
628 );
629
630 let object = parse_object(&toml, Concurrency::default(), false, false);
631 assert_eq!(object.conversion_type, Some(ConversionType::Option));
632 }
633
634 #[test]
635 fn conversion_type_option_table() {
636 let toml = &toml(
637 r#"
638name = "Test"
639status = "generate"
640 [conversion_type]
641 variant = "Option"
642"#,
643 );
644
645 let object = parse_object(toml, Concurrency::default(), false, false);
646 assert_eq!(object.conversion_type, Some(ConversionType::Option));
647 }
648
649 #[test]
650 fn conversion_type_result_table_missing_err() {
651 let toml = &toml(
652 r#"
653name = "Test"
654status = "generate"
655 [conversion_type]
656 variant = "Result"
657"#,
658 );
659
660 let object = parse_object(toml, Concurrency::default(), false, false);
661 assert_eq!(
662 object.conversion_type,
663 Some(ConversionType::Result {
664 ok_type: Arc::from("Test"),
665 err_type: Arc::from("MissingErrorType"),
666 }),
667 );
668 }
669
670 #[test]
671 fn conversion_type_result_table_with_err() {
672 let toml = &toml(
673 r#"
674name = "Test"
675status = "generate"
676 [conversion_type]
677 variant = "Result"
678 err_type = "TryFromIntError"
679"#,
680 );
681
682 let object = parse_object(toml, Concurrency::default(), false, false);
683 assert_eq!(
684 object.conversion_type,
685 Some(ConversionType::Result {
686 ok_type: Arc::from("Test"),
687 err_type: Arc::from("TryFromIntError"),
688 }),
689 );
690 }
691
692 #[test]
693 fn conversion_type_result_table_with_ok_err() {
694 let toml = &toml(
695 r#"
696name = "Test"
697status = "generate"
698 [conversion_type]
699 variant = "Result"
700 ok_type = "TestSuccess"
701 err_type = "TryFromIntError"
702"#,
703 );
704
705 let object = parse_object(toml, Concurrency::default(), false, false);
706 assert_eq!(
707 object.conversion_type,
708 Some(ConversionType::Result {
709 ok_type: Arc::from("TestSuccess"),
710 err_type: Arc::from("TryFromIntError"),
711 }),
712 );
713 }
714
715 #[test]
716 fn conversion_type_fields() {
717 let toml = &toml(
718 r#"
719[[object]]
720name = "Test"
721status = "generate"
722 [[object.constant]]
723 name = "Const"
724 [[object.function]]
725 name = "Func"
726 manual = true
727
728"#,
729 );
730
731 let object = toml
732 .lookup("object")
733 .map(|t| parse_toml(t, Concurrency::default(), false, false))
734 .expect("parsing failed");
735 assert_eq!(
736 object["Test"].constants,
737 vec![crate::config::constants::Constant {
738 ident: Ident::Name("Const".to_owned()),
739 status: GStatus::Generate,
740 version: None,
741 cfg_condition: None,
742 generate_doc: true,
743 }],
744 );
745 assert_eq!(object["Test"].functions.len(), 1);
746 assert_eq!(
747 object["Test"].functions[0].ident,
748 Ident::Name("Func".to_owned()),
749 );
750 }
751
752 #[test]
753 fn conversion_type_generate_doc() {
754 let r = &toml(
755 r#"
756name = "Test"
757status = "generate"
758generate_doc = false
759"#,
760 );
761
762 let object = parse_object(r, Concurrency::default(), false, false);
763 assert!(!object.generate_doc);
764
765 let r = &toml(
767 r#"
768name = "Test"
769status = "generate"
770"#,
771 );
772 let object = parse_object(r, Concurrency::default(), false, false);
773 assert!(object.generate_doc);
774 }
775}