Skip to main content

xso_proc/
enums.rs

1// Copyright (c) 2024 Jonas Schäfer <jonas@zombofant.net>
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7//! Handling of enums
8
9use std::collections::HashMap;
10
11use proc_macro2::{Span, TokenStream};
12use quote::{quote, ToTokens};
13use syn::*;
14
15use crate::common::{AsXmlParts, FromXmlParts, GenericsInfo, ItemDef, XmlNameMatcher};
16use crate::compound::Compound;
17use crate::error_message::ParentRef;
18use crate::meta::{reject_key, Flag, NameRef, NamespaceRef, QNameRef, XmlCompoundMeta};
19use crate::state::{AsItemsStateMachine, FromEventsMatchMode, FromEventsStateMachine};
20use crate::structs::StructInner;
21use crate::types::ref_ty;
22
23/// The definition of an enum variant, switched on the XML element's name,
24/// inside a [`NameSwitchedEnum`].
25struct NameVariant {
26    /// The XML name of the element to map the enum variant to.
27    name: NameRef,
28
29    /// The name of the variant
30    ident: Ident,
31
32    /// The field(s) of this struct.
33    inner: Compound,
34}
35
36impl NameVariant {
37    /// Construct a new name-selected variant from its declaration.
38    fn new(decl: &Variant, enum_namespace: &NamespaceRef) -> Result<Self> {
39        // We destructure here so that we get informed when new fields are
40        // added and can handle them, either by processing them or raising
41        // an error if they are present.
42        let XmlCompoundMeta {
43            span: meta_span,
44            qname: QNameRef { namespace, name },
45            exhaustive,
46            debug,
47            builder,
48            iterator,
49            on_unknown_attribute,
50            on_unknown_child,
51            transparent,
52            discard,
53            deserialize_callback,
54            attribute,
55            value,
56        } = XmlCompoundMeta::parse_from_attributes(&decl.attrs)?;
57
58        reject_key!(debug flag not on "enum variants" only on "enums and structs");
59        reject_key!(exhaustive flag not on "enum variants" only on "enums");
60        reject_key!(namespace not on "enum variants" only on "enums and structs");
61        reject_key!(builder not on "enum variants" only on "enums and structs");
62        reject_key!(iterator not on "enum variants" only on "enums and structs");
63        reject_key!(transparent flag not on "named enum variants" only on "structs");
64        reject_key!(deserialize_callback not on "enum variants" only on "enums and structs");
65        reject_key!(attribute not on "enum variants" only on "attribute-switched enums");
66        reject_key!(value not on "name-switched enum variants" only on "attribute-switched enum variants");
67
68        let Some(name) = name else {
69            return Err(Error::new(
70                meta_span,
71                "`name` is required on name-switched enum variants",
72            ));
73        };
74
75        Ok(Self {
76            name,
77            ident: decl.ident.clone(),
78            inner: Compound::from_fields(
79                &decl.fields,
80                enum_namespace,
81                on_unknown_attribute,
82                on_unknown_child,
83                discard,
84            )?,
85        })
86    }
87
88    fn make_from_events_statemachine(
89        &self,
90        generics: &mut GenericsInfo,
91        enum_ident: &Ident,
92        state_ty_ident: &Ident,
93    ) -> Result<FromEventsStateMachine> {
94        let xml_name = &self.name;
95
96        let mut inner_generics = generics.scoped_for(&self.inner.to_tuple_ty().into())?;
97
98        let result = self
99            .inner
100            .make_from_events_statemachine(
101                &mut inner_generics,
102                state_ty_ident,
103                &ParentRef::Named(Path {
104                    leading_colon: None,
105                    segments: [
106                        PathSegment::from(enum_ident.clone()),
107                        self.ident.clone().into(),
108                    ]
109                    .into_iter()
110                    .collect(),
111                }),
112                &self.ident.to_string(),
113            )?
114            .with_augmented_init(|init| {
115                quote! {
116                    if name.1 != #xml_name {
117                        ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch {
118                            name,
119                            attrs,
120                        })
121                    } else {
122                        #init
123                    }
124                }
125            })
126            .compile();
127
128        generics.extend_bounds(inner_generics.into_new_bounds());
129
130        Ok(result)
131    }
132
133    fn make_as_item_iter_statemachine(
134        &self,
135        generics: &mut GenericsInfo,
136        xml_namespace: &NamespaceRef,
137        enum_ident: &Ident,
138        state_ty_ident: &Ident,
139        item_iter_ty_lifetime: &Lifetime,
140    ) -> Result<AsItemsStateMachine> {
141        let xml_name = &self.name;
142
143        Ok(self
144            .inner
145            .make_as_item_iter_statemachine(
146                generics,
147                &ParentRef::Named(Path {
148                    leading_colon: None,
149                    segments: [
150                        PathSegment::from(enum_ident.clone()),
151                        self.ident.clone().into(),
152                    ]
153                    .into_iter()
154                    .collect(),
155                }),
156                state_ty_ident,
157                &self.ident.to_string(),
158                item_iter_ty_lifetime,
159            )?
160            .with_augmented_init(|init| {
161                quote! {
162                    let name = (
163                        ::xso::exports::rxml::Namespace::from(#xml_namespace),
164                        ::xso::exports::alloc::borrow::Cow::Borrowed(#xml_name),
165                    );
166                    #init
167                }
168            })
169            .compile())
170    }
171}
172
173/// The definition of a enum which switches based on the XML element name,
174/// with the XML namespace fixed.
175struct NameSwitchedEnum {
176    /// The XML namespace of the element to map the enum to.
177    namespace: NamespaceRef,
178
179    /// The variants of the enum.
180    variants: Vec<NameVariant>,
181
182    /// Flag indicating whether the enum is exhaustive.
183    exhaustive: bool,
184}
185
186impl NameSwitchedEnum {
187    fn new<'x, I: IntoIterator<Item = &'x Variant>>(
188        namespace: NamespaceRef,
189        exhaustive: Flag,
190        variant_iter: I,
191    ) -> Result<Self> {
192        let mut variants = Vec::new();
193        let mut seen_names = HashMap::new();
194        for variant in variant_iter {
195            let variant = NameVariant::new(variant, &namespace)?;
196            if let Some(other) = seen_names.get(&variant.name) {
197                return Err(Error::new_spanned(
198                    variant.name,
199                    format!(
200                        "duplicate `name` in enum: variants {} and {} have the same XML name",
201                        other, variant.ident
202                    ),
203                ));
204            }
205            seen_names.insert(variant.name.clone(), variant.ident.clone());
206            variants.push(variant);
207        }
208
209        Ok(Self {
210            namespace,
211            variants,
212            exhaustive: exhaustive.is_set(),
213        })
214    }
215
216    /// Provide the `XmlNameMatcher` template for this enum.
217    ///
218    /// Name-switched enums always return a matcher in the namespace of the
219    /// elements they match against.
220    fn xml_name_matcher(&self) -> Result<XmlNameMatcher> {
221        Ok(XmlNameMatcher::InNamespace(
222            self.namespace.to_token_stream(),
223        ))
224    }
225
226    /// Build the deserialisation statemachine for the name-switched enum.
227    fn make_from_events_statemachine(
228        &self,
229        generics: &mut GenericsInfo,
230        target_ty_ident: &Ident,
231        state_ty_ident: &Ident,
232    ) -> Result<FromEventsStateMachine> {
233        let xml_namespace = &self.namespace;
234
235        let mut statemachine = FromEventsStateMachine::new();
236        for variant in self.variants.iter() {
237            statemachine.merge(variant.make_from_events_statemachine(
238                generics,
239                target_ty_ident,
240                state_ty_ident,
241            )?);
242        }
243
244        statemachine.set_pre_init(quote! {
245            if name.0 != #xml_namespace {
246                return ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch {
247                    name,
248                    attrs,
249                })
250            }
251        });
252
253        if self.exhaustive {
254            let mismatch_err = format!("This is not a {} element.", target_ty_ident);
255            statemachine.set_fallback(quote! {
256                ::core::result::Result::Err(::xso::error::FromEventsError::Invalid(
257                    ::xso::error::Error::Other(#mismatch_err),
258                ))
259            })
260        }
261
262        Ok(statemachine)
263    }
264
265    /// Build the serialisation statemachine for the name-switched enum.
266    fn make_as_item_iter_statemachine(
267        &self,
268        generics: &mut GenericsInfo,
269        target_ty_ident: &Ident,
270        state_ty_ident: &Ident,
271        item_iter_ty_lifetime: &Lifetime,
272    ) -> Result<AsItemsStateMachine> {
273        let mut statemachine = AsItemsStateMachine::new();
274        for variant in self.variants.iter() {
275            statemachine.merge(variant.make_as_item_iter_statemachine(
276                generics,
277                &self.namespace,
278                target_ty_ident,
279                state_ty_ident,
280                item_iter_ty_lifetime,
281            )?);
282        }
283
284        Ok(statemachine)
285    }
286}
287
288/// The definition of an enum variant, switched on the XML element's
289/// attribute's value, inside a [`AttributeSwitchedEnum`].
290struct ValueVariant {
291    /// The verbatim value to match.
292    value: String,
293
294    /// The identifier of the variant
295    ident: Ident,
296
297    /// The field(s) of the variant.
298    inner: Compound,
299}
300
301impl ValueVariant {
302    /// Construct a new value-selected variant from its declaration.
303    fn new(decl: &Variant, enum_namespace: &NamespaceRef) -> Result<Self> {
304        // We destructure here so that we get informed when new fields are
305        // added and can handle them, either by processing them or raising
306        // an error if they are present.
307        let XmlCompoundMeta {
308            span: meta_span,
309            qname: QNameRef { namespace, name },
310            exhaustive,
311            debug,
312            builder,
313            iterator,
314            on_unknown_attribute,
315            on_unknown_child,
316            transparent,
317            discard,
318            deserialize_callback,
319            attribute,
320            value,
321        } = XmlCompoundMeta::parse_from_attributes(&decl.attrs)?;
322
323        reject_key!(debug flag not on "enum variants" only on "enums and structs");
324        reject_key!(exhaustive flag not on "enum variants" only on "enums");
325        reject_key!(namespace not on "enum variants" only on "enums and structs");
326        reject_key!(name not on "attribute-switched enum variants" only on "enums, structs and name-switched enum variants");
327        reject_key!(builder not on "enum variants" only on "enums and structs");
328        reject_key!(iterator not on "enum variants" only on "enums and structs");
329        reject_key!(transparent flag not on "named enum variants" only on "structs");
330        reject_key!(deserialize_callback not on "enum variants" only on "enums and structs");
331        reject_key!(attribute not on "enum variants" only on "attribute-switched enums");
332
333        let Some(value) = value else {
334            return Err(Error::new(
335                meta_span,
336                "`value` is required on attribute-switched enum variants",
337            ));
338        };
339
340        Ok(Self {
341            value: value.value(),
342            ident: decl.ident.clone(),
343            inner: Compound::from_fields(
344                &decl.fields,
345                enum_namespace,
346                on_unknown_attribute,
347                on_unknown_child,
348                discard,
349            )?,
350        })
351    }
352
353    fn make_from_events_statemachine(
354        &self,
355        generics: &mut GenericsInfo,
356        enum_ident: &Ident,
357        state_ty_ident: &Ident,
358    ) -> Result<FromEventsStateMachine> {
359        let value = &self.value;
360
361        let mut inner_generics = generics.scoped_for(&self.inner.to_tuple_ty().into())?;
362
363        let result = self
364            .inner
365            .make_from_events_statemachine(
366                &mut inner_generics,
367                state_ty_ident,
368                &ParentRef::Named(Path {
369                    leading_colon: None,
370                    segments: [
371                        PathSegment::from(enum_ident.clone()),
372                        self.ident.clone().into(),
373                    ]
374                    .into_iter()
375                    .collect(),
376                }),
377                &self.ident.to_string(),
378            )?
379            .with_augmented_init(|init| {
380                quote! {
381                    #value => { #init },
382                }
383            })
384            .compile();
385
386        generics.extend_bounds(inner_generics.into_new_bounds());
387
388        Ok(result)
389    }
390
391    fn make_as_item_iter_statemachine(
392        &self,
393        generics: &mut GenericsInfo,
394        elem_namespace: &NamespaceRef,
395        elem_name: &NameRef,
396        attr_namespace: &TokenStream,
397        attr_name: &NameRef,
398        enum_ident: &Ident,
399        state_ty_ident: &Ident,
400        item_iter_ty_lifetime: &Lifetime,
401    ) -> Result<AsItemsStateMachine> {
402        let attr_value = &self.value;
403
404        Ok(self
405            .inner
406            .make_as_item_iter_statemachine(
407                generics,
408                &ParentRef::Named(Path {
409                    leading_colon: None,
410                    segments: [
411                        PathSegment::from(enum_ident.clone()),
412                        self.ident.clone().into(),
413                    ]
414                    .into_iter()
415                    .collect(),
416                }),
417                state_ty_ident,
418                &self.ident.to_string(),
419                item_iter_ty_lifetime,
420            )?
421            .with_augmented_init(|init| {
422                quote! {
423                    let name = (
424                        ::xso::exports::rxml::Namespace::from(#elem_namespace),
425                        ::xso::exports::alloc::borrow::Cow::Borrowed(#elem_name),
426                    );
427                    #init
428                }
429            })
430            .with_extra_header_state(
431                // Note: we convert the identifier to a string here to prevent
432                // its Span from leaking into the new identifier.
433                quote::format_ident!("{}Discriminator", &self.ident.to_string()),
434                quote! {
435                    ::xso::Item::Attribute(
436                        #attr_namespace,
437                        ::xso::exports::alloc::borrow::Cow::Borrowed(#attr_name),
438                        ::xso::exports::alloc::borrow::Cow::Borrowed(#attr_value),
439                    )
440                },
441            )
442            .compile())
443    }
444}
445
446/// The definition of an enum where each variant represents a different value
447/// of a fixed attribute.
448struct AttributeSwitchedEnum {
449    /// The XML namespace of the element.
450    elem_namespace: NamespaceRef,
451
452    /// The XML name of the element.
453    elem_name: NameRef,
454
455    /// The XML namespace of the attribute, or None if the attribute isn't
456    /// namespaced.'
457    attr_namespace: Option<NamespaceRef>,
458
459    /// The XML name of the attribute.
460    attr_name: NameRef,
461
462    /// Enum variant definitions
463    variants: Vec<ValueVariant>,
464}
465
466impl AttributeSwitchedEnum {
467    fn new<'x, I: IntoIterator<Item = &'x Variant>>(
468        elem_namespace: NamespaceRef,
469        elem_name: NameRef,
470        attr_namespace: Option<NamespaceRef>,
471        attr_name: NameRef,
472        variant_iter: I,
473    ) -> Result<Self> {
474        let mut variants = Vec::new();
475        let mut seen_values = HashMap::new();
476        for variant in variant_iter {
477            let variant = ValueVariant::new(variant, &elem_namespace)?;
478            if let Some(other) = seen_values.get(&variant.value) {
479                return Err(Error::new_spanned(
480                    variant.value,
481                    format!(
482                        "duplicate `value` in enum: variants {} and {} have the same attribute value",
483                        other, variant.ident
484                    ),
485                ));
486            }
487            seen_values.insert(variant.value.clone(), variant.ident.clone());
488            variants.push(variant);
489        }
490
491        Ok(Self {
492            elem_namespace,
493            elem_name,
494            attr_namespace,
495            attr_name,
496            variants,
497        })
498    }
499
500    /// Provide the `XmlNameMatcher` template for this enum.
501    ///
502    /// Attribute-switched enums always return a matcher specific to the
503    /// element they operate on.
504    fn xml_name_matcher(&self) -> Result<XmlNameMatcher> {
505        Ok(XmlNameMatcher::Specific(
506            self.elem_namespace.to_token_stream(),
507            self.elem_name.to_token_stream(),
508        ))
509    }
510
511    /// Build the deserialisation statemachine for the attribute-switched enum.
512    fn make_from_events_statemachine(
513        &self,
514        generics: &mut GenericsInfo,
515        target_ty_ident: &Ident,
516        state_ty_ident: &Ident,
517    ) -> Result<FromEventsStateMachine> {
518        let elem_namespace = &self.elem_namespace;
519        let elem_name = &self.elem_name;
520        let attr_namespace = match self.attr_namespace.as_ref() {
521            Some(v) => v.to_token_stream(),
522            None => quote! {
523                ::xso::exports::rxml::Namespace::none()
524            },
525        };
526        let attr_name = &self.attr_name;
527
528        let mut statemachine = FromEventsStateMachine::new();
529        for variant in self.variants.iter() {
530            statemachine.merge(variant.make_from_events_statemachine(
531                generics,
532                target_ty_ident,
533                state_ty_ident,
534            )?);
535        }
536
537        statemachine.set_pre_init(quote! {
538            let attr = {
539                if name.0 != #elem_namespace || name.1 != #elem_name {
540                    return ::core::result::Result::Err(
541                        ::xso::error::FromEventsError::Mismatch {
542                            name,
543                            attrs,
544                        },
545                    );
546                }
547
548                let ::core::option::Option::Some(attr) = attrs.remove(#attr_namespace, #attr_name) else {
549                    return ::core::result::Result::Err(
550                        ::xso::error::FromEventsError::Invalid(
551                            ::xso::error::Error::Other("Missing discriminator attribute.")
552                        ),
553                    );
554                };
555
556                attr
557            };
558        });
559        statemachine.set_fallback(quote! {
560            ::core::result::Result::Err(
561                ::xso::error::FromEventsError::Invalid(
562                    ::xso::error::Error::Other("Unknown value for discriminator attribute.")
563                ),
564            )
565        });
566        statemachine.set_match_mode(FromEventsMatchMode::Matched {
567            expr: quote! { attr.as_str() },
568        });
569
570        Ok(statemachine)
571    }
572
573    /// Build the serialisation statemachine for the attribute-switched enum.
574    fn make_as_item_iter_statemachine(
575        &self,
576        generics: &mut GenericsInfo,
577        target_ty_ident: &Ident,
578        state_ty_ident: &Ident,
579        item_iter_ty_lifetime: &Lifetime,
580    ) -> Result<AsItemsStateMachine> {
581        let attr_namespace = match self.attr_namespace.as_ref() {
582            Some(v) => v.to_token_stream(),
583            None => quote! {
584                ::xso::exports::rxml::Namespace::NONE
585            },
586        };
587
588        let mut statemachine = AsItemsStateMachine::new();
589        for variant in self.variants.iter() {
590            statemachine.merge(variant.make_as_item_iter_statemachine(
591                generics,
592                &self.elem_namespace,
593                &self.elem_name,
594                &attr_namespace,
595                &self.attr_name,
596                target_ty_ident,
597                state_ty_ident,
598                item_iter_ty_lifetime,
599            )?);
600        }
601
602        Ok(statemachine)
603    }
604}
605
606/// The definition of an enum variant in a [`DynamicEnum`].
607struct DynamicVariant {
608    /// The identifier of the enum variant.
609    ident: Ident,
610
611    /// The definition of the struct-like which resembles the enum variant.
612    inner: StructInner,
613}
614
615impl DynamicVariant {
616    fn new(variant: &Variant) -> Result<Self> {
617        let ident = variant.ident.clone();
618        let meta = XmlCompoundMeta::parse_from_attributes(&variant.attrs)?;
619
620        // We destructure here so that we get informed when new fields are
621        // added and can handle them, either by processing them or raising
622        // an error if they are present.
623        let XmlCompoundMeta {
624            span: _,
625            qname: _, // used by StructInner
626            ref exhaustive,
627            ref debug,
628            ref builder,
629            ref iterator,
630            on_unknown_attribute: _, // used by StructInner
631            on_unknown_child: _,     // used by StructInner
632            transparent: _,          // used by StructInner
633            discard: _,              // used by StructInner
634            ref deserialize_callback,
635            ref attribute,
636            ref value,
637        } = meta;
638
639        reject_key!(debug flag not on "enum variants" only on "enums and structs");
640        reject_key!(exhaustive flag not on "enum variants" only on "enums");
641        reject_key!(builder not on "enum variants" only on "enums and structs");
642        reject_key!(iterator not on "enum variants" only on "enums and structs");
643        reject_key!(deserialize_callback not on "enum variants" only on "enums and structs");
644        reject_key!(attribute not on "enum variants" only on "attribute-switched enums");
645        reject_key!(value not on "dynamic enum variants" only on "attribute-switched enum variants");
646
647        let inner = StructInner::new(meta, &variant.fields)?;
648        Ok(Self { ident, inner })
649    }
650}
651
652/// The definition of an enum where each variant is a completely unrelated
653/// possible XML subtree.
654struct DynamicEnum {
655    /// The enum variants.
656    variants: Vec<DynamicVariant>,
657}
658
659impl DynamicEnum {
660    fn new<'x, I: IntoIterator<Item = &'x Variant>>(variant_iter: I) -> Result<Self> {
661        let mut variants = Vec::new();
662        for variant in variant_iter {
663            variants.push(DynamicVariant::new(variant)?);
664        }
665
666        Ok(Self { variants })
667    }
668
669    /// Provide the `XmlNameMatcher` template for this dynamic enum.
670    ///
671    /// Dynamic enums return the superset of the matchers of their variants,
672    /// which in many cases will be XmlNameMatcher::Any.
673    fn xml_name_matcher(&self) -> Result<XmlNameMatcher> {
674        let mut iter = self.variants.iter();
675        let Some(first) = iter.next() else {
676            // Since the enum has no variants, it cannot match anything. We
677            // use an empty namespace and an empty name, which will never
678            // match.
679            return Ok(XmlNameMatcher::Custom(quote! {
680                ::xso::fromxml::XmlNameMatcher::<'static>::Specific("", "")
681            }));
682        };
683
684        let first_matcher = first.inner.xml_name_matcher()?;
685        let mut composition = quote! { #first_matcher };
686        for variant in iter {
687            let next_matcher = variant.inner.xml_name_matcher()?;
688            composition.extend(quote! { .superset(#next_matcher) });
689        }
690
691        Ok(XmlNameMatcher::Custom(composition))
692    }
693
694    /// Build the deserialisation statemachine for the dynamic enum.
695    fn make_from_events_statemachine(
696        &self,
697        generics: &mut GenericsInfo,
698        target_ty_ident: &Ident,
699        state_ty_ident: &Ident,
700    ) -> Result<FromEventsStateMachine> {
701        let mut statemachine = FromEventsStateMachine::new();
702        for variant in self.variants.iter() {
703            let mut inner_generics = match variant.inner {
704                StructInner::Transparent { ref ty, .. } => generics.scoped_for(ty)?,
705                StructInner::Compound { ref inner, .. } => {
706                    generics.scoped_for(&inner.to_tuple_ty().into())?
707                }
708            };
709            let submachine = variant.inner.make_from_events_statemachine(
710                &mut inner_generics,
711                state_ty_ident,
712                &ParentRef::Named(Path {
713                    leading_colon: None,
714                    segments: [
715                        PathSegment::from(target_ty_ident.clone()),
716                        variant.ident.clone().into(),
717                    ]
718                    .into_iter()
719                    .collect(),
720                }),
721                &variant.ident.to_string(),
722            )?;
723
724            generics.extend_bounds(inner_generics.into_new_bounds());
725
726            statemachine.merge(submachine.compile());
727        }
728
729        Ok(statemachine)
730    }
731
732    /// Build the serialisation statemachine for the dynamic enum.
733    fn make_as_item_iter_statemachine(
734        &self,
735        generics: &mut GenericsInfo,
736        target_ty_ident: &Ident,
737        state_ty_ident: &Ident,
738        item_iter_ty_lifetime: &Lifetime,
739    ) -> Result<AsItemsStateMachine> {
740        let mut statemachine = AsItemsStateMachine::new();
741        for variant in self.variants.iter() {
742            let submachine = variant.inner.make_as_item_iter_statemachine(
743                generics,
744                &ParentRef::Named(Path {
745                    leading_colon: None,
746                    segments: [
747                        PathSegment::from(target_ty_ident.clone()),
748                        variant.ident.clone().into(),
749                    ]
750                    .into_iter()
751                    .collect(),
752                }),
753                state_ty_ident,
754                &variant.ident.to_string(),
755                item_iter_ty_lifetime,
756            )?;
757
758            statemachine.merge(submachine.compile());
759        }
760
761        Ok(statemachine)
762    }
763}
764
765/// The definition of an enum.
766enum EnumInner {
767    /// The enum switches based on the XML name of the element, with the XML
768    /// namespace fixed.
769    NameSwitched(NameSwitchedEnum),
770
771    /// The enum switches based on the value of an attribute of the XML
772    /// element.
773    AttributeSwitched(AttributeSwitchedEnum),
774
775    /// The enum consists of variants with entirely unrelated XML structures.
776    Dynamic(DynamicEnum),
777}
778
779impl EnumInner {
780    fn new<'x, I: IntoIterator<Item = &'x Variant>>(
781        meta: XmlCompoundMeta,
782        variant_iter: I,
783    ) -> Result<Self> {
784        // We destructure here so that we get informed when new fields are
785        // added and can handle them, either by processing them or raising
786        // an error if they are present.
787        let XmlCompoundMeta {
788            span,
789            qname: QNameRef { namespace, name },
790            exhaustive,
791            debug,
792            builder,
793            iterator,
794            on_unknown_attribute,
795            on_unknown_child,
796            transparent,
797            discard,
798            deserialize_callback,
799            attribute,
800            value,
801        } = meta;
802
803        // These must've been cleared by the caller. Because these being set
804        // is a programming error (in xso-proc) and not a usage error, we
805        // assert here instead of using reject_key!.
806        assert!(builder.is_none());
807        assert!(iterator.is_none());
808        assert!(!debug.is_set());
809        assert!(deserialize_callback.is_none());
810
811        reject_key!(transparent flag not on "enums" only on "structs");
812        reject_key!(on_unknown_attribute not on "enums" only on "enum variants and structs");
813        reject_key!(on_unknown_child not on "enums" only on "enum variants and structs");
814        reject_key!(discard vec not on "enums" only on "enum variants and structs");
815        reject_key!(value not on "enums" only on "attribute-switched enum variants");
816
817        if let Some(attribute) = attribute {
818            let Some(attr_name) = attribute.qname.name else {
819                return Err(Error::new(
820                    attribute.span,
821                    "missing `name` for `attribute` key",
822                ));
823            };
824
825            let attr_namespace = attribute.qname.namespace;
826
827            let Some(elem_namespace) = namespace else {
828                let mut error =
829                    Error::new(span, "missing `namespace` key on attribute-switched enum");
830                error.combine(Error::new(
831                    attribute.span,
832                    "enum is attribute-switched because of the `attribute` key here",
833                ));
834                return Err(error);
835            };
836
837            let Some(elem_name) = name else {
838                let mut error = Error::new(span, "missing `name` key on attribute-switched enum");
839                error.combine(Error::new(
840                    attribute.span,
841                    "enum is attribute-switched because of the `attribute` key here",
842                ));
843                return Err(error);
844            };
845
846            if !exhaustive.is_set() {
847                return Err(Error::new(
848                    span,
849                    "attribute-switched enums must be marked as `exhaustive`. non-exhaustive attribute-switched enums are not supported."
850                ));
851            }
852
853            Ok(Self::AttributeSwitched(AttributeSwitchedEnum::new(
854                elem_namespace,
855                elem_name,
856                attr_namespace,
857                attr_name,
858                variant_iter,
859            )?))
860        } else if let Some(namespace) = namespace {
861            reject_key!(name not on "name-switched enums" only on "their variants");
862            Ok(Self::NameSwitched(NameSwitchedEnum::new(
863                namespace,
864                exhaustive,
865                variant_iter,
866            )?))
867        } else {
868            reject_key!(name not on "dynamic enums" only on "their variants");
869            reject_key!(exhaustive flag not on "dynamic enums" only on "name-switched enums");
870            Ok(Self::Dynamic(DynamicEnum::new(variant_iter)?))
871        }
872    }
873
874    /// Provide the `XmlNameMatcher` template for this enum.
875    fn xml_name_matcher(&self) -> Result<XmlNameMatcher> {
876        match self {
877            Self::NameSwitched(ref inner) => inner.xml_name_matcher(),
878            Self::AttributeSwitched(ref inner) => inner.xml_name_matcher(),
879            Self::Dynamic(ref inner) => inner.xml_name_matcher(),
880        }
881    }
882
883    /// Build the deserialisation statemachine for the enum.
884    fn make_from_events_statemachine(
885        &self,
886        generics: &mut GenericsInfo,
887        target_ty_ident: &Ident,
888        state_ty_ident: &Ident,
889    ) -> Result<FromEventsStateMachine> {
890        match self {
891            Self::NameSwitched(ref inner) => {
892                inner.make_from_events_statemachine(generics, target_ty_ident, state_ty_ident)
893            }
894            Self::AttributeSwitched(ref inner) => {
895                inner.make_from_events_statemachine(generics, target_ty_ident, state_ty_ident)
896            }
897            Self::Dynamic(ref inner) => {
898                inner.make_from_events_statemachine(generics, target_ty_ident, state_ty_ident)
899            }
900        }
901    }
902
903    /// Build the serialisation statemachine for the enum.
904    fn make_as_item_iter_statemachine(
905        &self,
906        generics: &mut GenericsInfo,
907        target_ty_ident: &Ident,
908        state_ty_ident: &Ident,
909        item_iter_ty_lifetime: &Lifetime,
910    ) -> Result<AsItemsStateMachine> {
911        match self {
912            Self::NameSwitched(ref inner) => inner.make_as_item_iter_statemachine(
913                generics,
914                target_ty_ident,
915                state_ty_ident,
916                item_iter_ty_lifetime,
917            ),
918            Self::AttributeSwitched(ref inner) => inner.make_as_item_iter_statemachine(
919                generics,
920                target_ty_ident,
921                state_ty_ident,
922                item_iter_ty_lifetime,
923            ),
924            Self::Dynamic(ref inner) => inner.make_as_item_iter_statemachine(
925                generics,
926                target_ty_ident,
927                state_ty_ident,
928                item_iter_ty_lifetime,
929            ),
930        }
931    }
932}
933
934/// Definition of an enum and how to parse it.
935pub(crate) struct EnumDef {
936    /// Implementation of the enum itself
937    inner: EnumInner,
938
939    /// Name of the target type.
940    target_ty_ident: Ident,
941
942    /// Name of the builder type.
943    builder_ty_ident: Ident,
944
945    /// Name of the iterator type.
946    item_iter_ty_ident: Ident,
947
948    /// Flag whether debug mode is enabled.
949    debug: bool,
950
951    /// Optional validator function to call.
952    deserialize_callback: Option<Path>,
953}
954
955impl EnumDef {
956    /// Create a new enum from its name, meta, and variants.
957    pub(crate) fn new<'x, I: IntoIterator<Item = &'x Variant>>(
958        ident: &Ident,
959        mut meta: XmlCompoundMeta,
960        variant_iter: I,
961    ) -> Result<Self> {
962        let builder_ty_ident = match meta.builder.take() {
963            Some(v) => v,
964            None => quote::format_ident!("{}FromXmlBuilder", ident.to_string()),
965        };
966
967        let item_iter_ty_ident = match meta.iterator.take() {
968            Some(v) => v,
969            None => quote::format_ident!("{}AsXmlIterator", ident.to_string()),
970        };
971
972        let debug = meta.debug.take().is_set();
973        let deserialize_callback = meta.deserialize_callback.take();
974
975        Ok(Self {
976            inner: EnumInner::new(meta, variant_iter)?,
977            target_ty_ident: ident.clone(),
978            builder_ty_ident,
979            item_iter_ty_ident,
980            debug,
981            deserialize_callback,
982        })
983    }
984}
985
986impl ItemDef for EnumDef {
987    fn make_from_events_builder(
988        &self,
989        vis: &Visibility,
990        generics: &mut GenericsInfo,
991        name_ident: &Ident,
992        attrs_ident: &Ident,
993    ) -> Result<FromXmlParts> {
994        let target_ty_ident = &self.target_ty_ident;
995        let builder_ty_ident = &self.builder_ty_ident;
996        let state_ty_ident = quote::format_ident!("{}State", builder_ty_ident);
997
998        let defs = self
999            .inner
1000            .make_from_events_statemachine(generics, target_ty_ident, &state_ty_ident)?
1001            .render(
1002                vis,
1003                generics,
1004                builder_ty_ident,
1005                &state_ty_ident,
1006                &generics.ty_with_arguments(target_ty_ident.clone()),
1007                self.deserialize_callback.as_ref(),
1008            )?;
1009
1010        Ok(FromXmlParts {
1011            defs,
1012            from_events_body: quote! {
1013                #builder_ty_ident::new(#name_ident, #attrs_ident, ctx)
1014            },
1015            builder_ty: generics.ty_with_arguments(builder_ty_ident.clone()),
1016            name_matcher: self.inner.xml_name_matcher()?,
1017        })
1018    }
1019
1020    fn make_as_xml_iter(
1021        &self,
1022        vis: &Visibility,
1023        generics: &mut GenericsInfo,
1024    ) -> Result<AsXmlParts> {
1025        let target_ty_ident = &self.target_ty_ident;
1026        let target_ty = generics.ty_with_arguments(target_ty_ident.clone());
1027        let item_iter_ty_ident = &self.item_iter_ty_ident;
1028        let item_iter_ty_lifetime = Lifetime {
1029            apostrophe: Span::call_site(),
1030            ident: Ident::new("xso_proc_as_xml_iter_lifetime", Span::call_site()),
1031        };
1032        let mut inner_generics = generics.subscope();
1033        inner_generics.insert_lifetime(&item_iter_ty_lifetime);
1034        let state_ty_ident = quote::format_ident!("{}State", item_iter_ty_ident);
1035
1036        let defs = self
1037            .inner
1038            .make_as_item_iter_statemachine(
1039                &mut inner_generics,
1040                target_ty_ident,
1041                &state_ty_ident,
1042                &item_iter_ty_lifetime,
1043            )?
1044            .render(
1045                vis,
1046                &inner_generics,
1047                &ref_ty(target_ty, item_iter_ty_lifetime.clone()),
1048                &state_ty_ident,
1049                &item_iter_ty_lifetime,
1050                item_iter_ty_ident,
1051            )?;
1052
1053        // XXX: this is a bit of a hack, but I don't see a good way around it
1054        // for now: on the one hand, we need the where_clause as updated by
1055        // the impls in order to collect the bounds needed for the traits to
1056        // work. On the other hand, we mustn't propagate the addition of the
1057        // item_iter_ty_lifetime to the caller.
1058        let item_iter_ty = inner_generics.ty_with_arguments(item_iter_ty_ident.clone());
1059        generics.extend_bounds(inner_generics.into_new_bounds());
1060
1061        Ok(AsXmlParts {
1062            defs,
1063            as_xml_iter_body: quote! {
1064                #item_iter_ty_ident::new(self)
1065            },
1066            item_iter_ty,
1067            item_iter_ty_lifetime,
1068        })
1069    }
1070
1071    fn debug(&self) -> bool {
1072        self.debug
1073    }
1074}