xso_proc/
compound.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 the insides of compound structures (structs and enum variants)
8
9use proc_macro2::{Span, TokenStream};
10use quote::{quote, quote_spanned, ToTokens};
11use syn::{spanned::Spanned, *};
12
13use std::collections::{hash_map::Entry, HashMap};
14
15use crate::error_message::{FieldName, ParentRef};
16use crate::field::{FieldBuilderPart, FieldDef, FieldIteratorPart, FieldTempInit, NestedMatcher};
17use crate::meta::{DiscardSpec, Flag, NameRef, NamespaceRef, QNameRef};
18use crate::scope::{mangle_member, AsItemsScope, FromEventsScope};
19use crate::state::{AsItemsSubmachine, FromEventsSubmachine, State};
20use crate::types::{
21    default_fn, discard_builder_ty, feed_fn, namespace_ty, ncnamestr_cow_ty, phantom_lifetime_ty,
22    ref_ty, unknown_attribute_policy_path, unknown_child_policy_path,
23};
24
25fn resolve_policy(policy: Option<Ident>, mut enum_ref: Path) -> Expr {
26    match policy {
27        Some(ident) => {
28            enum_ref.segments.push(ident.into());
29            Expr::Path(ExprPath {
30                attrs: Vec::new(),
31                qself: None,
32                path: enum_ref,
33            })
34        }
35        None => {
36            let default_fn = default_fn(Type::Path(TypePath {
37                qself: None,
38                path: enum_ref,
39            }));
40            Expr::Call(ExprCall {
41                attrs: Vec::new(),
42                func: Box::new(default_fn),
43                paren_token: token::Paren::default(),
44                args: punctuated::Punctuated::new(),
45            })
46        }
47    }
48}
49
50/// A struct or enum variant's contents.
51pub(crate) struct Compound {
52    /// The fields of this compound.
53    fields: Vec<FieldDef>,
54
55    /// Policy defining how to handle unknown attributes.
56    unknown_attribute_policy: Expr,
57
58    /// Policy defining how to handle unknown children.
59    unknown_child_policy: Expr,
60
61    /// Attributes to discard.
62    discard_attr: Vec<(Option<NamespaceRef>, NameRef)>,
63
64    /// Text to discard.
65    discard_text: Flag,
66
67    /// Attribute qualified names which are selected by fields.
68    ///
69    /// This is used to generate code which asserts, at compile time, that no
70    /// two fields select the same XML attribute.
71    selected_attributes: Vec<(QNameRef, Member)>,
72}
73
74impl Compound {
75    /// Construct a compound from processed field definitions.
76    pub(crate) fn from_field_defs<I: IntoIterator<Item = Result<FieldDef>>>(
77        compound_fields: I,
78        unknown_attribute_policy: Option<Ident>,
79        unknown_child_policy: Option<Ident>,
80        discard: Vec<DiscardSpec>,
81    ) -> Result<Self> {
82        let unknown_attribute_policy = resolve_policy(
83            unknown_attribute_policy,
84            unknown_attribute_policy_path(Span::call_site()),
85        );
86        let unknown_child_policy = resolve_policy(
87            unknown_child_policy,
88            unknown_child_policy_path(Span::call_site()),
89        );
90        let compound_fields = compound_fields.into_iter();
91        let size_hint = compound_fields.size_hint();
92        let mut fields = Vec::with_capacity(size_hint.1.unwrap_or(size_hint.0));
93        let mut text_field = None;
94        let mut selected_attributes: HashMap<QNameRef, Member> = HashMap::new();
95        for field in compound_fields {
96            let field = field?;
97
98            if field.is_text_field() {
99                if let Some(other_field) = text_field.as_ref() {
100                    let mut err = Error::new_spanned(
101                        field.member(),
102                        "only one `#[xml(text)]` field allowed per compound",
103                    );
104                    err.combine(Error::new(
105                        *other_field,
106                        "the other `#[xml(text)]` field is here",
107                    ));
108                    return Err(err);
109                }
110                text_field = Some(field.member().span())
111            }
112
113            if let Some(qname) = field.captures_attribute() {
114                let span = field.span();
115                match selected_attributes.entry(qname) {
116                    Entry::Occupied(o) => {
117                        let mut err = Error::new(
118                            span,
119                            "this field XML field matches the same attribute as another field",
120                        );
121                        err.combine(Error::new(
122                            o.get().span(),
123                            "the other field matching the same attribute is here",
124                        ));
125                        return Err(err);
126                    }
127                    Entry::Vacant(v) => {
128                        v.insert(field.member().clone());
129                    }
130                }
131            }
132
133            fields.push(field);
134        }
135
136        let mut discard_text = Flag::Absent;
137        let mut discard_attr = Vec::new();
138        for spec in discard {
139            match spec {
140                DiscardSpec::Text { span } => {
141                    if let Some(field) = text_field.as_ref() {
142                        let mut err = Error::new(
143                            *field,
144                            "cannot combine `#[xml(text)]` field with `discard(text)`",
145                        );
146                        err.combine(Error::new(
147                            spec.span(),
148                            "the discard(text) attribute is here",
149                        ));
150                        return Err(err);
151                    }
152                    if let Flag::Present(other) = discard_text {
153                        let mut err = Error::new(
154                            span,
155                            "only one `discard(text)` meta is allowed per compound",
156                        );
157                        err.combine(Error::new(other, "the discard(text) meta is here"));
158                        return Err(err);
159                    }
160
161                    discard_text = Flag::Present(span);
162                }
163
164                DiscardSpec::Attribute {
165                    qname: QNameRef { namespace, name },
166                    span,
167                } => {
168                    let xml_namespace = namespace;
169                    let xml_name = match name {
170                        Some(v) => v,
171                        None => {
172                            return Err(Error::new(
173                                span,
174                                "discard(attribute) must specify a name, e.g. via discard(attribute = \"some-name\")",
175                            ));
176                        }
177                    };
178                    discard_attr.push((xml_namespace, xml_name));
179                }
180            }
181        }
182
183        Ok(Self {
184            fields,
185            unknown_attribute_policy,
186            unknown_child_policy,
187            discard_attr,
188            discard_text,
189            selected_attributes: selected_attributes.into_iter().collect(),
190        })
191    }
192
193    /// Construct a compound from fields.
194    pub(crate) fn from_fields(
195        compound_fields: &Fields,
196        container_namespace: &NamespaceRef,
197        unknown_attribute_policy: Option<Ident>,
198        unknown_child_policy: Option<Ident>,
199        discard: Vec<DiscardSpec>,
200    ) -> Result<Self> {
201        Self::from_field_defs(
202            compound_fields.iter().enumerate().map(|(i, field)| {
203                let index = match i.try_into() {
204                    Ok(v) => v,
205                    // we are converting to u32, are you crazy?!
206                    // (u32, because syn::Member::Index needs that.)
207                    Err(_) => {
208                        return Err(Error::new_spanned(
209                            field,
210                            "okay, mate, that are way too many fields. get your life together.",
211                        ))
212                    }
213                };
214                FieldDef::from_field(field, index, container_namespace)
215            }),
216            unknown_attribute_policy,
217            unknown_child_policy,
218            discard,
219        )
220    }
221
222    /// Generate code which, at compile time, asserts that all attributes
223    /// which are selected by this compound are disjunct.
224    ///
225    /// NOTE: this needs rustc 1.83 or newer for `const_refs_to_static`.
226    fn assert_disjunct_attributes(&self) -> TokenStream {
227        let mut checks = TokenStream::default();
228
229        // Comparison is commutative, so we *could* reduce this to n^2/2
230        // comparisons instead of n*(n-1). However, by comparing every field
231        // with every other field and emitting check code for that, we can
232        // point at both fields in the error messages.
233        for (i, (qname_a, member_a)) in self.selected_attributes.iter().enumerate() {
234            for (j, (qname_b, member_b)) in self.selected_attributes.iter().enumerate() {
235                if i == j {
236                    continue;
237                }
238                // Flip a and b around if a is later than b.
239                // This way, the error message is the same for both
240                // conflicting fields. Note that we always take the span of
241                // `a` though, so that the two errors point at different
242                // fields.
243                let span = member_a.span();
244                let (member_a, member_b) = if i > j {
245                    (member_b, member_a)
246                } else {
247                    (member_a, member_b)
248                };
249                if qname_a.namespace.is_some() != qname_b.namespace.is_some() {
250                    // cannot ever match.
251                    continue;
252                }
253                let Some((name_a, name_b)) = qname_a.name.as_ref().zip(qname_b.name.as_ref())
254                else {
255                    panic!("selected attribute has no XML local name");
256                };
257
258                let mut check = quote! {
259                    ::xso::exports::const_str_eq(#name_a.as_str(), #name_b.as_str())
260                };
261
262                let namespaces = qname_a.namespace.as_ref().zip(qname_b.namespace.as_ref());
263                if let Some((ns_a, ns_b)) = namespaces {
264                    check.extend(quote! {
265                        && ::xso::exports::const_str_eq(#ns_a, #ns_b)
266                    });
267                };
268
269                let attr_a = if let Some(namespace_a) = qname_a.namespace.as_ref() {
270                    format!("{{{}}}{}", namespace_a, name_a)
271                } else {
272                    format!("{}", name_a)
273                };
274
275                let attr_b = if let Some(namespace_b) = qname_b.namespace.as_ref() {
276                    format!("{{{}}}{}", namespace_b, name_b)
277                } else {
278                    format!("{}", name_b)
279                };
280
281                // We need to escape `{` and `}` because we feed it into
282                // a generated `panic!()` as message template below. We cannot
283                // do something like `panic!("member {} […]", #field_a, […])`,
284                // because rustc does not allow that in a const context.
285                // (It *did* briefly (and unintentionally, see
286                // https://github.com/rust-lang/rust/issues/140585 ) allow
287                // that in version 1.86, but that was rolled back.
288                let attr_a = attr_a.replace('{', "{{").replace('}', "}}");
289                let attr_b = attr_b.replace('{', "{{").replace('}', "}}");
290                let field_a = FieldName(&member_a)
291                    .to_string()
292                    .replace('{', "{{")
293                    .replace('}', "}}");
294                let field_b = FieldName(&member_b)
295                    .to_string()
296                    .replace('{', "{{")
297                    .replace('}', "}}");
298
299                // By assigning the checks to a `const`, we ensure that they
300                // are in fact evaluated at compile time, even if that constant
301                // is never used.
302                checks.extend(quote_spanned! {span=>
303                    const _: () = { if #check {
304                        panic!(concat!("member ", #field_a, " and member ", #field_b, " match the same XML attribute: ", #attr_a, " == ", #attr_b));
305                    } };
306                })
307            }
308        }
309
310        checks
311    }
312
313    /// Make and return a set of states which is used to construct the target
314    /// type from XML events.
315    ///
316    /// The states are returned as partial state machine. See the return
317    /// type's documentation for details.
318    pub(crate) fn make_from_events_statemachine(
319        &self,
320        state_ty_ident: &Ident,
321        output_name: &ParentRef,
322        state_prefix: &str,
323    ) -> Result<FromEventsSubmachine> {
324        let scope = FromEventsScope::new(state_ty_ident.clone());
325        let FromEventsScope {
326            ref attrs,
327            ref builder_data_ident,
328            ref text,
329            ref substate_data,
330            ref substate_result,
331            ..
332        } = scope;
333
334        let default_state_ident = quote::format_ident!("{}Default", state_prefix);
335        let discard_state_ident = quote::format_ident!("{}Discard", state_prefix);
336        let builder_data_ty: Type = TypePath {
337            qself: None,
338            path: quote::format_ident!("{}Data{}", state_ty_ident, state_prefix).into(),
339        }
340        .into();
341        let mut states = Vec::new();
342
343        let mut builder_data_def = TokenStream::default();
344        let mut builder_data_init = TokenStream::default();
345        let mut output_cons = TokenStream::default();
346        let mut child_matchers = TokenStream::default();
347        let mut fallback_child_matcher = None;
348        let mut text_handler = if self.discard_text.is_set() {
349            Some(quote! {
350                ::core::result::Result::Ok(::core::ops::ControlFlow::Break(
351                    Self::#default_state_ident { #builder_data_ident }
352                ))
353            })
354        } else {
355            None
356        };
357        let mut extra_defs = TokenStream::default();
358        let is_tuple = !output_name.is_path();
359
360        for (i, field) in self.fields.iter().enumerate() {
361            let member = field.member();
362            let builder_field_name = mangle_member(member);
363            let part = field.make_builder_part(&scope, output_name)?;
364            let state_name = quote::format_ident!("{}Field{}", state_prefix, i);
365
366            match part {
367                FieldBuilderPart::Init {
368                    value: FieldTempInit { ty, init },
369                } => {
370                    builder_data_def.extend(quote! {
371                        #builder_field_name: #ty,
372                    });
373
374                    builder_data_init.extend(quote! {
375                        #builder_field_name: #init,
376                    });
377
378                    if is_tuple {
379                        output_cons.extend(quote! {
380                            #builder_data_ident.#builder_field_name,
381                        });
382                    } else {
383                        output_cons.extend(quote! {
384                            #member: #builder_data_ident.#builder_field_name,
385                        });
386                    }
387                }
388
389                FieldBuilderPart::Text {
390                    value: FieldTempInit { ty, init },
391                    collect,
392                    finalize,
393                } => {
394                    if text_handler.is_some() {
395                        // the existence of only one text handler is enforced
396                        // by Compound's constructor(s).
397                        panic!("more than one field attempts to collect text data");
398                    }
399
400                    builder_data_def.extend(quote! {
401                        #builder_field_name: #ty,
402                    });
403                    builder_data_init.extend(quote! {
404                        #builder_field_name: #init,
405                    });
406                    text_handler = Some(quote! {
407                        #collect
408                        ::core::result::Result::Ok(::core::ops::ControlFlow::Break(
409                            Self::#default_state_ident { #builder_data_ident }
410                        ))
411                    });
412
413                    if is_tuple {
414                        output_cons.extend(quote! {
415                            #finalize,
416                        });
417                    } else {
418                        output_cons.extend(quote! {
419                            #member: #finalize,
420                        });
421                    }
422                }
423
424                FieldBuilderPart::Nested {
425                    extra_defs: field_extra_defs,
426                    value: FieldTempInit { ty, init },
427                    matcher,
428                    builder,
429                    collect,
430                    finalize,
431                } => {
432                    let feed = feed_fn(builder.clone());
433
434                    let mut substate_data_ident = substate_data.clone();
435                    substate_data_ident.set_span(ty.span());
436                    states.push(State::new_with_builder(
437                        state_name.clone(),
438                        builder_data_ident,
439                        &builder_data_ty,
440                    ).with_field(
441                        &substate_data_ident,
442                        &builder,
443                    ).with_mut(substate_data).with_impl(quote! {
444                        match #feed(&mut #substate_data, ev, ctx)? {
445                            ::core::option::Option::Some(#substate_result) => {
446                                #collect
447                                ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#default_state_ident {
448                                    #builder_data_ident,
449                                }))
450                            }
451                            ::core::option::Option::None => {
452                                ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#state_name {
453                                    #builder_data_ident,
454                                    #substate_data,
455                                }))
456                            }
457                        }
458                    }));
459
460                    builder_data_def.extend(quote! {
461                        #builder_field_name: #ty,
462                    });
463
464                    builder_data_init.extend(quote! {
465                        #builder_field_name: #init,
466                    });
467
468                    match matcher {
469                        NestedMatcher::Selective(matcher) => {
470                            child_matchers.extend(quote! {
471                                let (name, attrs) = match #matcher {
472                                    ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch { name, attrs }) => (name, attrs),
473                                    ::core::result::Result::Err(::xso::error::FromEventsError::Invalid(e)) => return ::core::result::Result::Err(e),
474                                    ::core::result::Result::Ok(#substate_data) => {
475                                        return ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#state_name {
476                                            #builder_data_ident,
477                                            #substate_data,
478                                        }))
479                                    }
480                                };
481                            });
482                        }
483                        NestedMatcher::Fallback(matcher) => {
484                            if let Some((span, _)) = fallback_child_matcher.as_ref() {
485                                let mut err = Error::new(
486                                    field.span(),
487                                    "more than one field is attempting to consume all unmatched child elements"
488                                );
489                                err.combine(Error::new(
490                                    *span,
491                                    "the previous field collecting all unmatched child elements is here"
492                                ));
493                                return Err(err);
494                            }
495
496                            let matcher = quote! {
497                                ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#state_name {
498                                    #builder_data_ident,
499                                    #substate_data: { #matcher },
500                                }))
501                            };
502
503                            fallback_child_matcher = Some((field.span(), matcher));
504                        }
505                    }
506
507                    if is_tuple {
508                        output_cons.extend(quote! {
509                            #finalize,
510                        });
511                    } else {
512                        output_cons.extend(quote! {
513                            #member: #finalize,
514                        });
515                    }
516
517                    extra_defs.extend(field_extra_defs);
518                }
519            }
520        }
521
522        // We always implicitly discard the `xml:lang` attribute. Its
523        // processing is handled using the `#[xml(language)]` meta.
524        let mut discard_attr = quote! {
525            let _ = #attrs.remove(::xso::exports::rxml::Namespace::xml(), "lang");
526        };
527        for (xml_namespace, xml_name) in self.discard_attr.iter() {
528            let xml_namespace = match xml_namespace {
529                Some(v) => v.to_token_stream(),
530                None => quote! {
531                    ::xso::exports::rxml::Namespace::none()
532                },
533            };
534            discard_attr.extend(quote! {
535                let _ = #attrs.remove(#xml_namespace, #xml_name);
536            });
537        }
538
539        let text_handler = match text_handler {
540            Some(v) => v,
541            None => quote! {
542                // note: u8::is_ascii_whitespace includes U+000C, which is not
543                // part of XML's white space definition.'
544                if !::xso::is_xml_whitespace(#text.as_bytes()) {
545                    ::core::result::Result::Err(::xso::error::Error::Other("Unexpected text content".into()))
546                } else {
547                    ::core::result::Result::Ok(::core::ops::ControlFlow::Break(
548                        Self::#default_state_ident { #builder_data_ident }
549                    ))
550                }
551            },
552        };
553
554        let unknown_attr_err = format!("Unknown attribute in {}.", output_name);
555        let unknown_child_err = format!("Unknown child in {}.", output_name);
556        let unknown_child_policy = &self.unknown_child_policy;
557
558        let output_cons = match output_name {
559            ParentRef::Named(ref path) => {
560                quote! {
561                    #path { #output_cons }
562                }
563            }
564            ParentRef::Unnamed { .. } => {
565                quote! {
566                    ( #output_cons )
567                }
568            }
569        };
570
571        let discard_builder_ty = discard_builder_ty(Span::call_site());
572        let discard_feed = feed_fn(discard_builder_ty.clone());
573        let child_fallback = match fallback_child_matcher {
574            Some((_, matcher)) => matcher,
575            None => quote! {
576                let _ = (name, attrs);
577                let _: () = #unknown_child_policy.apply_policy(#unknown_child_err)?;
578                ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#discard_state_ident {
579                    #builder_data_ident,
580                    #substate_data: #discard_builder_ty::new(),
581                }))
582            },
583        };
584
585        states.push(State::new_with_builder(
586            discard_state_ident.clone(),
587            builder_data_ident,
588            &builder_data_ty,
589        ).with_field(
590            substate_data,
591            &discard_builder_ty,
592        ).with_mut(substate_data).with_impl(quote! {
593            match #discard_feed(&mut #substate_data, ev, ctx)? {
594                ::core::option::Option::Some(#substate_result) => {
595                    ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#default_state_ident {
596                        #builder_data_ident,
597                    }))
598                }
599                ::core::option::Option::None => {
600                    ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#discard_state_ident {
601                        #builder_data_ident,
602                        #substate_data,
603                    }))
604                }
605            }
606        }));
607
608        states.push(State::new_with_builder(
609            default_state_ident.clone(),
610            builder_data_ident,
611            &builder_data_ty,
612        ).with_impl(quote! {
613            match ev {
614                // EndElement in Default state -> done parsing.
615                ::xso::exports::rxml::Event::EndElement(_) => {
616                    ::core::result::Result::Ok(::core::ops::ControlFlow::Continue(
617                        #output_cons
618                    ))
619                }
620                ::xso::exports::rxml::Event::StartElement(_, name, attrs) => {
621                    #child_matchers
622                    #child_fallback
623                }
624                ::xso::exports::rxml::Event::Text(_, #text) => {
625                    #text_handler
626                }
627                // we ignore these: a correct parser only generates
628                // them at document start, and there we want to indeed
629                // not worry about them being in front of the first
630                // element.
631                ::xso::exports::rxml::Event::XmlDeclaration(_, ::xso::exports::rxml::XmlVersion::V1_0) => ::core::result::Result::Ok(::core::ops::ControlFlow::Break(
632                    Self::#default_state_ident { #builder_data_ident }
633                ))
634            }
635        }));
636
637        let unknown_attribute_policy = &self.unknown_attribute_policy;
638
639        let checks = self.assert_disjunct_attributes();
640
641        Ok(FromEventsSubmachine {
642            defs: quote! {
643                #extra_defs
644                #checks
645
646                struct #builder_data_ty {
647                    #builder_data_def
648                }
649            },
650            states,
651            init: quote! {
652                let #builder_data_ident = #builder_data_ty {
653                    #builder_data_init
654                };
655                #discard_attr
656                if #attrs.len() > 0 {
657                    let _: () = #unknown_attribute_policy.apply_policy(#unknown_attr_err)?;
658                }
659                ::core::result::Result::Ok(#state_ty_ident::#default_state_ident { #builder_data_ident })
660            },
661        })
662    }
663
664    /// Make and return a set of states which is used to destructure the
665    /// target type into XML events.
666    ///
667    /// The states are returned as partial state machine. See the return
668    /// type's documentation for details.
669    ///
670    /// **Important:** The returned submachine is not in functional state!
671    /// It's `init` must be modified so that a variable called `name` of type
672    /// `rxml::QName` is in scope.
673    pub(crate) fn make_as_item_iter_statemachine(
674        &self,
675        input_name: &ParentRef,
676        state_ty_ident: &Ident,
677        state_prefix: &str,
678        lifetime: &Lifetime,
679    ) -> Result<AsItemsSubmachine> {
680        let scope = AsItemsScope::new(lifetime, state_ty_ident.clone());
681
682        let element_head_start_state_ident =
683            quote::format_ident!("{}ElementHeadStart", state_prefix);
684        let element_head_end_state_ident = quote::format_ident!("{}ElementHeadEnd", state_prefix);
685        let element_foot_state_ident = quote::format_ident!("{}ElementFoot", state_prefix);
686        let name_ident = quote::format_ident!("name");
687        let ns_ident = quote::format_ident!("ns");
688        let dummy_ident = quote::format_ident!("dummy");
689        let mut header_states = Vec::new();
690        let mut body_states = Vec::new();
691
692        let is_tuple = !input_name.is_path();
693        let mut destructure = TokenStream::default();
694        let mut start_init = TokenStream::default();
695        let mut extra_defs = TokenStream::default();
696
697        header_states.push(
698            State::new(element_head_start_state_ident.clone())
699                .with_field(&dummy_ident, &phantom_lifetime_ty(lifetime.clone()))
700                .with_field(&ns_ident, &namespace_ty(Span::call_site()))
701                .with_field(
702                    &name_ident,
703                    &ncnamestr_cow_ty(Span::call_site(), lifetime.clone()),
704                ),
705        );
706
707        body_states.push((
708            None,
709            State::new(element_head_end_state_ident.clone()).with_impl(quote! {
710                ::core::option::Option::Some(::xso::Item::ElementHeadEnd)
711            }),
712        ));
713
714        for (i, field) in self.fields.iter().enumerate() {
715            let member = field.member();
716            let bound_name = mangle_member(member);
717            let part = field.make_iterator_part(&scope, input_name, &bound_name)?;
718            let state_name = quote::format_ident!("{}Field{}", state_prefix, i);
719            let ty = scope.borrow(field.ty().clone());
720
721            match part {
722                FieldIteratorPart::Header { generator } => {
723                    // We have to make sure that we carry our data around in
724                    // all the previous states.
725                    // For header states, it is sufficient to do it here.
726                    // For body states, we have to do it in a separate loop
727                    // below to correctly handle the case when a field with a
728                    // body state is placed before a field with a header
729                    // state.
730                    for state in header_states.iter_mut() {
731                        state.add_field(&bound_name, &ty);
732                    }
733                    header_states.push(
734                        State::new(state_name)
735                            .with_field(&bound_name, &ty)
736                            .with_impl(quote! {
737                                #generator
738                            }),
739                    );
740
741                    if is_tuple {
742                        destructure.extend(quote! {
743                            ref #bound_name,
744                        });
745                    } else {
746                        destructure.extend(quote! {
747                            #member: ref #bound_name,
748                        });
749                    }
750                    start_init.extend(quote! {
751                        #bound_name,
752                    });
753                }
754
755                FieldIteratorPart::Text { generator } => {
756                    // We have to make sure that we carry our data around in
757                    // all the previous body states.
758                    // We also have to make sure that our data is carried
759                    // by all *header* states, but we can only do that once
760                    // we have visited them all, so that happens at the bottom
761                    // of the loop.
762                    for (_, state) in body_states.iter_mut() {
763                        state.add_field(&bound_name, &ty);
764                    }
765                    let state = State::new(state_name)
766                        .with_field(&bound_name, &ty)
767                        .with_impl(quote! {
768                            #generator.map(|value| ::xso::Item::Text(
769                                value,
770                            ))
771                        });
772                    if is_tuple {
773                        destructure.extend(quote! {
774                            #bound_name,
775                        });
776                    } else {
777                        destructure.extend(quote! {
778                            #member: #bound_name,
779                        });
780                    }
781                    start_init.extend(quote! {
782                        #bound_name,
783                    });
784                    body_states.push((Some((bound_name, ty)), state));
785                }
786
787                FieldIteratorPart::Content {
788                    extra_defs: field_extra_defs,
789                    value: FieldTempInit { ty, init },
790                    generator,
791                } => {
792                    // We have to make sure that we carry our data around in
793                    // all the previous body states.
794                    // We also have to make sure that our data is carried
795                    // by all *header* states, but we can only do that once
796                    // we have visited them all, so that happens at the bottom
797                    // of the loop.
798                    for (_, state) in body_states.iter_mut() {
799                        state.add_field(&bound_name, &ty);
800                    }
801
802                    let state = State::new(state_name.clone())
803                        .with_field(&bound_name, &ty)
804                        .with_mut(&bound_name)
805                        .with_impl(quote! {
806                            #generator?
807                        });
808                    if is_tuple {
809                        destructure.extend(quote! {
810                            #bound_name,
811                        });
812                    } else {
813                        destructure.extend(quote! {
814                            #member: #bound_name,
815                        });
816                    }
817                    start_init.extend(quote! {
818                        #bound_name: #init,
819                    });
820
821                    extra_defs.extend(field_extra_defs);
822                    body_states.push((Some((bound_name, ty)), state));
823                }
824            }
825        }
826
827        header_states[0].set_impl(quote! {
828            {
829                ::core::option::Option::Some(::xso::Item::ElementHeadStart(
830                    #ns_ident,
831                    #name_ident,
832                ))
833            }
834        });
835
836        for (data, _) in body_states.iter() {
837            if let Some((bound_name, ty)) = data.as_ref() {
838                for state in header_states.iter_mut() {
839                    state.add_field(bound_name, ty);
840                }
841            }
842        }
843
844        header_states.extend(body_states.into_iter().map(|(_, state)| state));
845        let mut states = header_states;
846
847        states.push(
848            State::new(element_foot_state_ident.clone()).with_impl(quote! {
849                ::core::option::Option::Some(::xso::Item::ElementFoot)
850            }),
851        );
852
853        let destructure = match input_name {
854            ParentRef::Named(ref input_path) => quote! {
855                #input_path { #destructure }
856            },
857            ParentRef::Unnamed { .. } => quote! {
858                ( #destructure )
859            },
860        };
861
862        let checks = self.assert_disjunct_attributes();
863        extra_defs.extend(checks);
864
865        Ok(AsItemsSubmachine {
866            defs: extra_defs,
867            states,
868            destructure,
869            init: quote! {
870                Self::#element_head_start_state_ident { #dummy_ident: ::core::marker::PhantomData, #name_ident: name.1, #ns_ident: name.0, #start_init }
871            },
872        })
873    }
874
875    /// Return a reference to this compound's only field's type.
876    ///
877    /// If the compound does not have exactly one field, this function returns
878    /// None.
879    pub(crate) fn single_ty(&self) -> Option<&Type> {
880        if self.fields.len() > 1 {
881            return None;
882        }
883        self.fields.first().map(|x| x.ty())
884    }
885
886    /// Construct a tuple type with this compound's field's types in the same
887    /// order as they appear in the compound.
888    pub(crate) fn to_tuple_ty(&self) -> TypeTuple {
889        TypeTuple {
890            paren_token: token::Paren::default(),
891            elems: self.fields.iter().map(|x| x.ty().clone()).collect(),
892        }
893    }
894
895    /// Construct a tuple type with this compound's field's types in the same
896    /// order as they appear in the compound.
897    pub(crate) fn to_single_or_tuple_ty(&self) -> Type {
898        match self.single_ty() {
899            None => self.to_tuple_ty().into(),
900            Some(v) => v.clone(),
901        }
902    }
903
904    /// Construct a tuple type with references to this compound's field's
905    /// types in the same order as they appear in the compound, with the given
906    /// lifetime.
907    pub(crate) fn to_ref_tuple_ty(&self, lifetime: &Lifetime) -> TypeTuple {
908        TypeTuple {
909            paren_token: token::Paren::default(),
910            elems: self
911                .fields
912                .iter()
913                .map(|x| ref_ty(x.ty().clone(), lifetime.clone()))
914                .collect(),
915        }
916    }
917
918    /// Return the number of fields in this compound.
919    pub(crate) fn field_count(&self) -> usize {
920        self.fields.len()
921    }
922}