Skip to main content

xsd_parser/pipeline/optimizer/
remove_duplicates.rs

1use std::collections::HashMap;
2use std::fmt::Debug;
3use std::hash::{Hash, Hasher};
4
5use crate::models::meta::{MetaType, MetaTypeVariant, MetaTypes, ReferenceMeta, TypeEq};
6
7use super::Optimizer;
8
9impl Optimizer {
10    /// If two types are completely equal this optimization will generate the
11    /// first type complete and just a type definition for the second one.
12    ///
13    /// <div class="warning">
14    /// *Caution*
15    ///
16    /// Be careful with this optimization. This will compare each known
17    /// type with each other type and check if the types are identical or not.
18    /// This would result in a type reference for two types, even if the types
19    /// itself are not logically related to each other.
20    ///
21    /// Furthermore this may result in typedef loops. The code generator should
22    /// be able to deal with them (using a Box), but it is still risky to use it.
23    /// </div>
24    ///
25    /// # Examples
26    ///
27    /// Consider the following XML schema.
28    /// ```xml
29    #[doc = include_str!("../../../tests/optimizer/duplicate.xsd")]
30    /// ```
31    ///
32    /// Without this optimization this will result in the following code:
33    /// ```rust
34    #[doc = include_str!("../../../tests/optimizer/expected0/remove_duplicates.rs")]
35    /// ```
36    ///
37    /// With this optimization the following code is generated:
38    /// ```rust
39    #[doc = include_str!("../../../tests/optimizer/expected1/remove_duplicates.rs")]
40    /// ```
41    pub fn remove_duplicates(mut self) -> Self {
42        use std::collections::hash_map::Entry;
43
44        struct Value<'a> {
45            type_: &'a MetaType,
46            types: &'a MetaTypes,
47        }
48        impl Debug for Value<'_> {
49            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50                f.debug_struct("Value").field("type_", &self.type_).finish()
51            }
52        }
53
54        impl PartialEq for Value<'_> {
55            fn eq(&self, other: &Self) -> bool {
56                self.type_.type_eq(other.type_, self.types)
57            }
58        }
59
60        impl Eq for Value<'_> {}
61
62        impl Hash for Value<'_> {
63            fn hash<H: Hasher>(&self, state: &mut H) {
64                self.type_.type_hash(state, self.types);
65            }
66        }
67
68        tracing::debug!("remove_duplicates");
69
70        let mut changed = true;
71
72        while changed {
73            changed = false;
74
75            tracing::trace!("remove_duplicates new iteration");
76
77            let types = &self.types;
78
79            #[allow(clippy::mutable_key_type)]
80            let mut map = HashMap::new();
81            let mut idents = HashMap::new();
82
83            for (ident, type_) in &self.types.items {
84                match map.entry(Value { type_, types }) {
85                    Entry::Vacant(e) => {
86                        if let Some(ident) = types.get_resolved_ident(ident) {
87                            e.insert(ident.clone());
88                        }
89                    }
90                    Entry::Occupied(e) => {
91                        let reference_ident = e.get();
92                        if !matches!(&type_.variant, MetaTypeVariant::Reference(ti) if ti.type_.eq(reference_ident))
93                        {
94                            idents.insert(ident.clone(), reference_ident.clone());
95                        }
96                    }
97                }
98            }
99
100            if !idents.is_empty() {
101                changed = true;
102                self.typedefs = None;
103            }
104
105            for (ident, referenced_type) in idents {
106                tracing::trace!(
107                    "Create reference for duplicate type: {ident} => {referenced_type}"
108                );
109
110                let ty = self.types.items.get_mut(&ident).unwrap();
111                ty.variant = MetaTypeVariant::Reference(ReferenceMeta::new(referenced_type));
112            }
113        }
114
115        self
116    }
117}