xsd_parser/pipeline/optimizer/
remove_duplicates.rs

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