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