libgir/config/
gobjects.rs

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/// Info about `GObject` descendant
65#[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
144// TODO: ?change to HashMap<String, GStatus>
145pub 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    // Also checks for ChildProperties
241    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                    // We set this to `false` to avoid having the "not_bound" mode saying that this
587                    // builder should be generated.
588                    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        // Ensure that the default value is "true".
766        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}