xsd_parser/pipeline/optimizer/
merge_enum_unions.rs

1use crate::models::{
2    meta::{EnumerationMeta, EnumerationMetaVariant, MetaTypeVariant, UnionMeta, UnionMetaType},
3    Ident,
4};
5use crate::traits::VecHelper;
6
7use super::{Error, Optimizer};
8
9impl Optimizer {
10    /// This will flatten the union identified by `ident` to one single union.
11    ///
12    /// This will merge the nested union and enum types of the union identified
13    /// by `ident` to one single enum type.
14    ///
15    /// # Errors
16    ///
17    /// Returns an error if the passed `ident` could not be found,
18    /// or is not an union.
19    ///
20    /// # Examples
21    ///
22    /// Consider the following XML schema.
23    /// ```xml
24    #[doc = include_str!("../../../tests/optimizer/union_flatten.xsd")]
25    /// ```
26    ///
27    /// Without this optimization this will result in the following code:
28    /// ```rust
29    #[doc = include_str!("../../../tests/optimizer/expected0/merge_enum_unions.rs")]
30    /// ```
31    ///
32    /// With this optimization the following code is generated:
33    /// ```rust
34    #[doc = include_str!("../../../tests/optimizer/expected1/merge_enum_unions.rs")]
35    /// ```
36    pub fn merge_enum_union(mut self, ident: Ident) -> Result<Self, Error> {
37        tracing::debug!("merge_enum_union(ident={ident:?})");
38
39        let Some(variant) = self.types.get_variant(&ident) else {
40            return Err(Error::UnknownType(ident));
41        };
42
43        let MetaTypeVariant::Union(_) = variant else {
44            return Err(Error::ExpectedUnion(ident));
45        };
46
47        let mut next = None;
48
49        self.merge_enum_union_impl(&ident, None, &mut next);
50
51        if let Some(next) = next {
52            let ty = self.types.items.get_mut(&ident).unwrap();
53            ty.variant = next;
54        }
55
56        Ok(self)
57    }
58
59    /// This will flatten all enumeration and union types.
60    ///
61    /// For details see [`merge_enum_union`](Self::merge_enum_union).
62    pub fn merge_enum_unions(mut self) -> Self {
63        tracing::debug!("merge_enum_unions");
64
65        let idents = self
66            .types
67            .items
68            .iter()
69            .filter_map(|(ident, type_)| {
70                if matches!(&type_.variant, MetaTypeVariant::Union(_)) {
71                    Some(ident)
72                } else {
73                    None
74                }
75            })
76            .cloned()
77            .collect::<Vec<_>>();
78
79        for ident in idents {
80            self = self.merge_enum_union(ident).unwrap();
81        }
82
83        self
84    }
85
86    fn merge_enum_union_impl(
87        &self,
88        ident: &Ident,
89        display_name: Option<&str>,
90        next: &mut Option<MetaTypeVariant>,
91    ) {
92        let Some(type_) = self.types.get_variant(ident) else {
93            return;
94        };
95
96        match type_ {
97            MetaTypeVariant::Union(x) => {
98                for t in &*x.types {
99                    self.merge_enum_union_impl(&t.type_, t.display_name.as_deref(), next);
100                }
101            }
102            MetaTypeVariant::Enumeration(x) => {
103                *next = match next.take() {
104                    None => Some(MetaTypeVariant::Enumeration(EnumerationMeta::default())),
105                    Some(MetaTypeVariant::Enumeration(ei)) => {
106                        Some(MetaTypeVariant::Enumeration(ei))
107                    }
108                    Some(MetaTypeVariant::Union(ui)) => {
109                        let mut ei = EnumerationMeta::default();
110
111                        for t in ui.types.0 {
112                            let var = ei.variants.find_or_insert(t.type_.clone(), |ident| {
113                                EnumerationMetaVariant::new(ident).with_type(Some(t.type_.clone()))
114                            });
115                            var.display_name = t.display_name;
116                        }
117
118                        Some(MetaTypeVariant::Enumeration(ei))
119                    }
120                    _ => crate::unreachable!(),
121                };
122
123                let Some(MetaTypeVariant::Enumeration(ei)) = next else {
124                    crate::unreachable!();
125                };
126
127                for var in &*x.variants {
128                    let new_var = ei.variants.find_or_insert(var.ident.clone(), |ident| {
129                        EnumerationMetaVariant::new(ident).with_type(var.type_.clone())
130                    });
131                    new_var.display_name.clone_from(&var.display_name);
132                }
133            }
134            MetaTypeVariant::Reference(x) if x.is_single() => {
135                self.merge_enum_union_impl(&x.type_, display_name, next);
136            }
137            _ => {
138                if next.is_none() {
139                    *next = Some(MetaTypeVariant::Union(UnionMeta::default()));
140                }
141
142                match next {
143                    Some(MetaTypeVariant::Union(ui)) => {
144                        let mut ti = UnionMetaType::new(ident.clone());
145                        ti.display_name = display_name.map(ToOwned::to_owned);
146
147                        ui.types.push(ti);
148                    }
149                    Some(MetaTypeVariant::Enumeration(ei)) => {
150                        let var = ei.variants.find_or_insert(ident.clone(), |x| {
151                            EnumerationMetaVariant::new(x).with_type(Some(ident.clone()))
152                        });
153                        var.display_name = display_name.map(ToOwned::to_owned);
154                    }
155                    _ => crate::unreachable!(),
156                }
157            }
158        }
159    }
160}