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;
12use quote::quote;
13use syn::*;
14
15use crate::common::{AsXmlParts, FromXmlParts, ItemDef};
16use crate::compound::Compound;
17use crate::error_message::ParentRef;
18use crate::meta::{reject_key, Flag, NameRef, NamespaceRef, QNameRef, XmlCompoundMeta};
19use crate::state::{AsItemsStateMachine, FromEventsStateMachine};
20use crate::structs::StructInner;
21use crate::types::{ref_ty, ty_from_ident};
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        } = XmlCompoundMeta::parse_from_attributes(&decl.attrs)?;
53
54        reject_key!(debug flag not on "enum variants" only on "enums and structs");
55        reject_key!(exhaustive flag not on "enum variants" only on "enums");
56        reject_key!(namespace not on "enum variants" only on "enums and structs");
57        reject_key!(builder not on "enum variants" only on "enums and structs");
58        reject_key!(iterator not on "enum variants" only on "enums and structs");
59        reject_key!(transparent flag not on "named enum variants" only on "structs");
60
61        let Some(name) = name else {
62            return Err(Error::new(meta_span, "`name` is required on enum variants"));
63        };
64
65        Ok(Self {
66            name,
67            ident: decl.ident.clone(),
68            inner: Compound::from_fields(
69                &decl.fields,
70                enum_namespace,
71                on_unknown_attribute,
72                on_unknown_child,
73            )?,
74        })
75    }
76
77    fn make_from_events_statemachine(
78        &self,
79        enum_ident: &Ident,
80        state_ty_ident: &Ident,
81    ) -> Result<FromEventsStateMachine> {
82        let xml_name = &self.name;
83
84        Ok(self
85            .inner
86            .make_from_events_statemachine(
87                state_ty_ident,
88                &ParentRef::Named(Path {
89                    leading_colon: None,
90                    segments: [
91                        PathSegment::from(enum_ident.clone()),
92                        self.ident.clone().into(),
93                    ]
94                    .into_iter()
95                    .collect(),
96                }),
97                &self.ident.to_string(),
98            )?
99            .with_augmented_init(|init| {
100                quote! {
101                    if name.1 != #xml_name {
102                        ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch {
103                            name,
104                            attrs,
105                        })
106                    } else {
107                        #init
108                    }
109                }
110            })
111            .compile())
112    }
113
114    fn make_as_item_iter_statemachine(
115        &self,
116        xml_namespace: &NamespaceRef,
117        enum_ident: &Ident,
118        state_ty_ident: &Ident,
119        item_iter_ty_lifetime: &Lifetime,
120    ) -> Result<AsItemsStateMachine> {
121        let xml_name = &self.name;
122
123        Ok(self
124            .inner
125            .make_as_item_iter_statemachine(
126                &ParentRef::Named(Path {
127                    leading_colon: None,
128                    segments: [
129                        PathSegment::from(enum_ident.clone()),
130                        self.ident.clone().into(),
131                    ]
132                    .into_iter()
133                    .collect(),
134                }),
135                state_ty_ident,
136                &self.ident.to_string(),
137                &item_iter_ty_lifetime,
138            )?
139            .with_augmented_init(|init| {
140                quote! {
141                    let name = (
142                        ::xso::exports::rxml::Namespace::from(#xml_namespace),
143                        ::xso::exports::alloc::borrow::Cow::Borrowed(#xml_name),
144                    );
145                    #init
146                }
147            })
148            .compile())
149    }
150}
151
152/// The definition of a enum which switches based on the XML element name,
153/// with the XML namespace fixed.
154struct NameSwitchedEnum {
155    /// The XML namespace of the element to map the enum to.
156    namespace: NamespaceRef,
157
158    /// The variants of the enum.
159    variants: Vec<NameVariant>,
160
161    /// Flag indicating whether the enum is exhaustive.
162    exhaustive: bool,
163}
164
165impl NameSwitchedEnum {
166    fn new<'x, I: IntoIterator<Item = &'x Variant>>(
167        namespace: NamespaceRef,
168        exhaustive: Flag,
169        variant_iter: I,
170    ) -> Result<Self> {
171        let mut variants = Vec::new();
172        let mut seen_names = HashMap::new();
173        for variant in variant_iter {
174            let variant = NameVariant::new(variant, &namespace)?;
175            if let Some(other) = seen_names.get(&variant.name) {
176                return Err(Error::new_spanned(
177                    variant.name,
178                    format!(
179                        "duplicate `name` in enum: variants {} and {} have the same XML name",
180                        other, variant.ident
181                    ),
182                ));
183            }
184            seen_names.insert(variant.name.clone(), variant.ident.clone());
185            variants.push(variant);
186        }
187
188        Ok(Self {
189            namespace,
190            variants,
191            exhaustive: exhaustive.is_set(),
192        })
193    }
194
195    /// Build the deserialisation statemachine for the name-switched enum.
196    fn make_from_events_statemachine(
197        &self,
198        target_ty_ident: &Ident,
199        state_ty_ident: &Ident,
200    ) -> Result<FromEventsStateMachine> {
201        let xml_namespace = &self.namespace;
202
203        let mut statemachine = FromEventsStateMachine::new();
204        for variant in self.variants.iter() {
205            statemachine
206                .merge(variant.make_from_events_statemachine(target_ty_ident, state_ty_ident)?);
207        }
208
209        statemachine.set_pre_init(quote! {
210            if name.0 != #xml_namespace {
211                return ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch {
212                    name,
213                    attrs,
214                })
215            }
216        });
217
218        if self.exhaustive {
219            let mismatch_err = format!("This is not a {} element.", target_ty_ident);
220            statemachine.set_fallback(quote! {
221                ::core::result::Result::Err(::xso::error::FromEventsError::Invalid(
222                    ::xso::error::Error::Other(#mismatch_err),
223                ))
224            })
225        }
226
227        Ok(statemachine)
228    }
229
230    /// Build the serialisation statemachine for the name-switched enum.
231    fn make_as_item_iter_statemachine(
232        &self,
233        target_ty_ident: &Ident,
234        state_ty_ident: &Ident,
235        item_iter_ty_lifetime: &Lifetime,
236    ) -> Result<AsItemsStateMachine> {
237        let mut statemachine = AsItemsStateMachine::new();
238        for variant in self.variants.iter() {
239            statemachine.merge(variant.make_as_item_iter_statemachine(
240                &self.namespace,
241                target_ty_ident,
242                state_ty_ident,
243                item_iter_ty_lifetime,
244            )?);
245        }
246
247        Ok(statemachine)
248    }
249}
250
251/// The definition of an enum variant in a [`DynamicEnum`].
252struct DynamicVariant {
253    /// The identifier of the enum variant.
254    ident: Ident,
255
256    /// The definition of the struct-like which resembles the enum variant.
257    inner: StructInner,
258}
259
260impl DynamicVariant {
261    fn new(variant: &Variant) -> Result<Self> {
262        let ident = variant.ident.clone();
263        let meta = XmlCompoundMeta::parse_from_attributes(&variant.attrs)?;
264
265        // We destructure here so that we get informed when new fields are
266        // added and can handle them, either by processing them or raising
267        // an error if they are present.
268        let XmlCompoundMeta {
269            span: _,
270            qname: _, // used by StructInner
271            ref exhaustive,
272            ref debug,
273            ref builder,
274            ref iterator,
275            on_unknown_attribute: _, // used by StructInner
276            on_unknown_child: _,     // used by StructInner
277            transparent: _,          // used by StructInner
278        } = meta;
279
280        reject_key!(debug flag not on "enum variants" only on "enums and structs");
281        reject_key!(exhaustive flag not on "enum variants" only on "enums");
282        reject_key!(builder not on "enum variants" only on "enums and structs");
283        reject_key!(iterator not on "enum variants" only on "enums and structs");
284
285        let inner = StructInner::new(meta, &variant.fields)?;
286        Ok(Self { ident, inner })
287    }
288}
289
290/// The definition of an enum where each variant is a completely unrelated
291/// possible XML subtree.
292struct DynamicEnum {
293    /// The enum variants.
294    variants: Vec<DynamicVariant>,
295}
296
297impl DynamicEnum {
298    fn new<'x, I: IntoIterator<Item = &'x Variant>>(variant_iter: I) -> Result<Self> {
299        let mut variants = Vec::new();
300        for variant in variant_iter {
301            variants.push(DynamicVariant::new(variant)?);
302        }
303
304        Ok(Self { variants })
305    }
306
307    /// Build the deserialisation statemachine for the dynamic enum.
308    fn make_from_events_statemachine(
309        &self,
310        target_ty_ident: &Ident,
311        state_ty_ident: &Ident,
312    ) -> Result<FromEventsStateMachine> {
313        let mut statemachine = FromEventsStateMachine::new();
314        for variant in self.variants.iter() {
315            let submachine = variant.inner.make_from_events_statemachine(
316                state_ty_ident,
317                &ParentRef::Named(Path {
318                    leading_colon: None,
319                    segments: [
320                        PathSegment::from(target_ty_ident.clone()),
321                        variant.ident.clone().into(),
322                    ]
323                    .into_iter()
324                    .collect(),
325                }),
326                &variant.ident.to_string(),
327            )?;
328
329            statemachine.merge(submachine.compile());
330        }
331
332        Ok(statemachine)
333    }
334
335    /// Build the serialisation statemachine for the dynamic enum.
336    fn make_as_item_iter_statemachine(
337        &self,
338        target_ty_ident: &Ident,
339        state_ty_ident: &Ident,
340        item_iter_ty_lifetime: &Lifetime,
341    ) -> Result<AsItemsStateMachine> {
342        let mut statemachine = AsItemsStateMachine::new();
343        for variant in self.variants.iter() {
344            let submachine = variant.inner.make_as_item_iter_statemachine(
345                &ParentRef::Named(Path {
346                    leading_colon: None,
347                    segments: [
348                        PathSegment::from(target_ty_ident.clone()),
349                        variant.ident.clone().into(),
350                    ]
351                    .into_iter()
352                    .collect(),
353                }),
354                state_ty_ident,
355                &variant.ident.to_string(),
356                item_iter_ty_lifetime,
357            )?;
358
359            statemachine.merge(submachine.compile());
360        }
361
362        Ok(statemachine)
363    }
364}
365
366/// The definition of an enum.
367enum EnumInner {
368    /// The enum switches based on the XML name of the element, with the XML
369    /// namespace fixed.
370    NameSwitched(NameSwitchedEnum),
371
372    /// The enum consists of variants with entirely unrelated XML structures.
373    Dynamic(DynamicEnum),
374}
375
376impl EnumInner {
377    fn new<'x, I: IntoIterator<Item = &'x Variant>>(
378        meta: XmlCompoundMeta,
379        variant_iter: I,
380    ) -> Result<Self> {
381        // We destructure here so that we get informed when new fields are
382        // added and can handle them, either by processing them or raising
383        // an error if they are present.
384        let XmlCompoundMeta {
385            span: _,
386            qname: QNameRef { namespace, name },
387            exhaustive,
388            debug,
389            builder,
390            iterator,
391            on_unknown_attribute,
392            on_unknown_child,
393            transparent,
394        } = meta;
395
396        // These must've been cleared by the caller. Because these being set
397        // is a programming error (in xso-proc) and not a usage error, we
398        // assert here instead of using reject_key!.
399        assert!(builder.is_none());
400        assert!(iterator.is_none());
401        assert!(!debug.is_set());
402
403        reject_key!(name not on "enums" only on "their variants");
404        reject_key!(transparent flag not on "enums" only on "structs");
405        reject_key!(on_unknown_attribute not on "enums" only on "enum variants and structs");
406        reject_key!(on_unknown_child not on "enums" only on "enum variants and structs");
407
408        if let Some(namespace) = namespace {
409            Ok(Self::NameSwitched(NameSwitchedEnum::new(
410                namespace,
411                exhaustive,
412                variant_iter,
413            )?))
414        } else {
415            reject_key!(exhaustive flag not on "dynamic enums" only on "name-switched enums");
416            Ok(Self::Dynamic(DynamicEnum::new(variant_iter)?))
417        }
418    }
419
420    /// Build the deserialisation statemachine for the enum.
421    fn make_from_events_statemachine(
422        &self,
423        target_ty_ident: &Ident,
424        state_ty_ident: &Ident,
425    ) -> Result<FromEventsStateMachine> {
426        match self {
427            Self::NameSwitched(ref inner) => {
428                inner.make_from_events_statemachine(target_ty_ident, state_ty_ident)
429            }
430            Self::Dynamic(ref inner) => {
431                inner.make_from_events_statemachine(target_ty_ident, state_ty_ident)
432            }
433        }
434    }
435
436    /// Build the serialisation statemachine for the enum.
437    fn make_as_item_iter_statemachine(
438        &self,
439        target_ty_ident: &Ident,
440        state_ty_ident: &Ident,
441        item_iter_ty_lifetime: &Lifetime,
442    ) -> Result<AsItemsStateMachine> {
443        match self {
444            Self::NameSwitched(ref inner) => inner.make_as_item_iter_statemachine(
445                target_ty_ident,
446                state_ty_ident,
447                item_iter_ty_lifetime,
448            ),
449            Self::Dynamic(ref inner) => inner.make_as_item_iter_statemachine(
450                target_ty_ident,
451                state_ty_ident,
452                item_iter_ty_lifetime,
453            ),
454        }
455    }
456}
457
458/// Definition of an enum and how to parse it.
459pub(crate) struct EnumDef {
460    /// Implementation of the enum itself
461    inner: EnumInner,
462
463    /// Name of the target type.
464    target_ty_ident: Ident,
465
466    /// Name of the builder type.
467    builder_ty_ident: Ident,
468
469    /// Name of the iterator type.
470    item_iter_ty_ident: Ident,
471
472    /// Flag whether debug mode is enabled.
473    debug: bool,
474}
475
476impl EnumDef {
477    /// Create a new enum from its name, meta, and variants.
478    pub(crate) fn new<'x, I: IntoIterator<Item = &'x Variant>>(
479        ident: &Ident,
480        mut meta: XmlCompoundMeta,
481        variant_iter: I,
482    ) -> Result<Self> {
483        let builder_ty_ident = match meta.builder.take() {
484            Some(v) => v,
485            None => quote::format_ident!("{}FromXmlBuilder", ident.to_string()),
486        };
487
488        let item_iter_ty_ident = match meta.iterator.take() {
489            Some(v) => v,
490            None => quote::format_ident!("{}AsXmlIterator", ident.to_string()),
491        };
492
493        let debug = meta.debug.take().is_set();
494
495        Ok(Self {
496            inner: EnumInner::new(meta, variant_iter)?,
497            target_ty_ident: ident.clone(),
498            builder_ty_ident,
499            item_iter_ty_ident,
500            debug,
501        })
502    }
503}
504
505impl ItemDef for EnumDef {
506    fn make_from_events_builder(
507        &self,
508        vis: &Visibility,
509        name_ident: &Ident,
510        attrs_ident: &Ident,
511    ) -> Result<FromXmlParts> {
512        let target_ty_ident = &self.target_ty_ident;
513        let builder_ty_ident = &self.builder_ty_ident;
514        let state_ty_ident = quote::format_ident!("{}State", builder_ty_ident);
515
516        let defs = self
517            .inner
518            .make_from_events_statemachine(target_ty_ident, &state_ty_ident)?
519            .render(
520                vis,
521                builder_ty_ident,
522                &state_ty_ident,
523                &TypePath {
524                    qself: None,
525                    path: target_ty_ident.clone().into(),
526                }
527                .into(),
528            )?;
529
530        Ok(FromXmlParts {
531            defs,
532            from_events_body: quote! {
533                #builder_ty_ident::new(#name_ident, #attrs_ident)
534            },
535            builder_ty_ident: builder_ty_ident.clone(),
536        })
537    }
538
539    fn make_as_xml_iter(&self, vis: &Visibility) -> Result<AsXmlParts> {
540        let target_ty_ident = &self.target_ty_ident;
541        let item_iter_ty_ident = &self.item_iter_ty_ident;
542        let item_iter_ty_lifetime = Lifetime {
543            apostrophe: Span::call_site(),
544            ident: Ident::new("xso_proc_as_xml_iter_lifetime", Span::call_site()),
545        };
546        let item_iter_ty = Type::Path(TypePath {
547            qself: None,
548            path: Path {
549                leading_colon: None,
550                segments: [PathSegment {
551                    ident: item_iter_ty_ident.clone(),
552                    arguments: PathArguments::AngleBracketed(AngleBracketedGenericArguments {
553                        colon2_token: None,
554                        lt_token: token::Lt {
555                            spans: [Span::call_site()],
556                        },
557                        args: [GenericArgument::Lifetime(item_iter_ty_lifetime.clone())]
558                            .into_iter()
559                            .collect(),
560                        gt_token: token::Gt {
561                            spans: [Span::call_site()],
562                        },
563                    }),
564                }]
565                .into_iter()
566                .collect(),
567            },
568        });
569        let state_ty_ident = quote::format_ident!("{}State", item_iter_ty_ident);
570
571        let defs = self
572            .inner
573            .make_as_item_iter_statemachine(
574                target_ty_ident,
575                &state_ty_ident,
576                &item_iter_ty_lifetime,
577            )?
578            .render(
579                vis,
580                &ref_ty(
581                    ty_from_ident(target_ty_ident.clone()).into(),
582                    item_iter_ty_lifetime.clone(),
583                ),
584                &state_ty_ident,
585                &item_iter_ty_lifetime,
586                &item_iter_ty,
587            )?;
588
589        Ok(AsXmlParts {
590            defs,
591            as_xml_iter_body: quote! {
592                #item_iter_ty_ident::new(self)
593            },
594            item_iter_ty,
595            item_iter_ty_lifetime,
596        })
597    }
598
599    fn debug(&self) -> bool {
600        self.debug
601    }
602}