xsd_parser/optimizer/
merge_enum_unions.rs

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