Skip to main content

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::common::GenericsInfo;
16use crate::error_message::{FieldName, ParentRef};
17use crate::field::{FieldBuilderPart, FieldDef, FieldIteratorPart, FieldTempInit, NestedMatcher};
18use crate::meta::{DiscardSpec, Flag, NameRef, NamespaceRef, QNameRef};
19use crate::scope::{mangle_member, AsItemsScope, FromEventsScope};
20use crate::state::{AsItemsSubmachine, FromEventsSubmachine, State};
21use crate::types::{
22    default_fn, discard_builder_ty, feed_fn, namespace_ty, ncnamestr_cow_ty, phantom_lifetime_ty,
23    ref_ty, unknown_attribute_policy_path, unknown_child_policy_path,
24};
25
26fn resolve_policy(policy: Option<Ident>, mut enum_ref: Path) -> Expr {
27    match policy {
28        Some(ident) => {
29            enum_ref.segments.push(ident.into());
30            Expr::Path(ExprPath {
31                attrs: Vec::new(),
32                qself: None,
33                path: enum_ref,
34            })
35        }
36        None => {
37            let default_fn = default_fn(Type::Path(TypePath {
38                qself: None,
39                path: enum_ref,
40            }));
41            Expr::Call(ExprCall {
42                attrs: Vec::new(),
43                func: Box::new(default_fn),
44                paren_token: token::Paren::default(),
45                args: punctuated::Punctuated::new(),
46            })
47        }
48    }
49}
50
51/// A struct or enum variant's contents.
52pub(crate) struct Compound {
53    /// The fields of this compound.
54    fields: Vec<FieldDef>,
55
56    /// Policy defining how to handle unknown attributes.
57    unknown_attribute_policy: Expr,
58
59    /// Policy defining how to handle unknown children.
60    unknown_child_policy: Expr,
61
62    /// Attributes to discard.
63    discard_attr: Vec<(Option<NamespaceRef>, NameRef)>,
64
65    /// Text to discard.
66    discard_text: Flag,
67
68    /// Attribute qualified names which are selected by fields.
69    ///
70    /// This is used to generate code which asserts, at compile time, that no
71    /// two fields select the same XML attribute.
72    selected_attributes: Vec<(QNameRef, Member)>,
73}
74
75impl Compound {
76    /// Construct a compound from processed field definitions.
77    pub(crate) fn from_field_defs<I: IntoIterator<Item = Result<FieldDef>>>(
78        compound_fields: I,
79        unknown_attribute_policy: Option<Ident>,
80        unknown_child_policy: Option<Ident>,
81        discard: Vec<DiscardSpec>,
82    ) -> Result<Self> {
83        let unknown_attribute_policy = resolve_policy(
84            unknown_attribute_policy,
85            unknown_attribute_policy_path(Span::call_site()),
86        );
87        let unknown_child_policy = resolve_policy(
88            unknown_child_policy,
89            unknown_child_policy_path(Span::call_site()),
90        );
91        let compound_fields = compound_fields.into_iter();
92        let size_hint = compound_fields.size_hint();
93        let mut fields = Vec::with_capacity(size_hint.1.unwrap_or(size_hint.0));
94        let mut text_field = None;
95        let mut selected_attributes: HashMap<QNameRef, Member> = HashMap::new();
96        for field in compound_fields {
97            let field = field?;
98
99            if field.is_text_field() {
100                if let Some(other_field) = text_field.as_ref() {
101                    let mut err = Error::new_spanned(
102                        field.member(),
103                        "only one `#[xml(text)]` field allowed per compound",
104                    );
105                    err.combine(Error::new(
106                        *other_field,
107                        "the other `#[xml(text)]` field is here",
108                    ));
109                    return Err(err);
110                }
111                text_field = Some(field.member().span())
112            }
113
114            if let Some(qname) = field.captures_attribute() {
115                let span = field.span();
116                match selected_attributes.entry(qname) {
117                    Entry::Occupied(o) => {
118                        let mut err = Error::new(
119                            span,
120                            "this field XML field matches the same attribute as another field",
121                        );
122                        err.combine(Error::new(
123                            o.get().span(),
124                            "the other field matching the same attribute is here",
125                        ));
126                        return Err(err);
127                    }
128                    Entry::Vacant(v) => {
129                        v.insert(field.member().clone());
130                    }
131                }
132            }
133
134            fields.push(field);
135        }
136
137        let mut discard_text = Flag::Absent;
138        let mut discard_attr = Vec::new();
139        for spec in discard {
140            match spec {
141                DiscardSpec::Text { span } => {
142                    if let Some(field) = text_field.as_ref() {
143                        let mut err = Error::new(
144                            *field,
145                            "cannot combine `#[xml(text)]` field with `discard(text)`",
146                        );
147                        err.combine(Error::new(
148                            spec.span(),
149                            "the discard(text) attribute is here",
150                        ));
151                        return Err(err);
152                    }
153                    if let Flag::Present(other) = discard_text {
154                        let mut err = Error::new(
155                            span,
156                            "only one `discard(text)` meta is allowed per compound",
157                        );
158                        err.combine(Error::new(other, "the discard(text) meta is here"));
159                        return Err(err);
160                    }
161
162                    discard_text = Flag::Present(span);
163                }
164
165                DiscardSpec::Attribute {
166                    qname: QNameRef { namespace, name },
167                    span,
168                } => {
169                    let xml_namespace = namespace;
170                    let xml_name = match name {
171                        Some(v) => v,
172                        None => {
173                            return Err(Error::new(
174                                span,
175                                "discard(attribute) must specify a name, e.g. via discard(attribute = \"some-name\")",
176                            ));
177                        }
178                    };
179                    discard_attr.push((xml_namespace, xml_name));
180                }
181            }
182        }
183
184        Ok(Self {
185            fields,
186            unknown_attribute_policy,
187            unknown_child_policy,
188            discard_attr,
189            discard_text,
190            selected_attributes: selected_attributes.into_iter().collect(),
191        })
192    }
193
194    /// Construct a compound from fields.
195    pub(crate) fn from_fields(
196        compound_fields: &Fields,
197        container_namespace: &NamespaceRef,
198        unknown_attribute_policy: Option<Ident>,
199        unknown_child_policy: Option<Ident>,
200        discard: Vec<DiscardSpec>,
201    ) -> Result<Self> {
202        Self::from_field_defs(
203            compound_fields.iter().enumerate().map(|(i, field)| {
204                let index = match i.try_into() {
205                    Ok(v) => v,
206                    // we are converting to u32, are you crazy?!
207                    // (u32, because syn::Member::Index needs that.)
208                    Err(_) => {
209                        return Err(Error::new_spanned(
210                            field,
211                            "okay, mate, that are way too many fields. get your life together.",
212                        ))
213                    }
214                };
215                FieldDef::from_field(field, index, container_namespace)
216            }),
217            unknown_attribute_policy,
218            unknown_child_policy,
219            discard,
220        )
221    }
222
223    /// Generate code which, at compile time, asserts that all attributes
224    /// which are selected by this compound are disjunct.
225    ///
226    /// NOTE: this needs rustc 1.83 or newer for `const_refs_to_static`.
227    fn assert_disjunct_attributes(&self) -> TokenStream {
228        let mut checks = TokenStream::default();
229
230        // Comparison is commutative, so we *could* reduce this to n^2/2
231        // comparisons instead of n*(n-1). However, by comparing every field
232        // with every other field and emitting check code for that, we can
233        // point at both fields in the error messages.
234        for (i, (qname_a, member_a)) in self.selected_attributes.iter().enumerate() {
235            for (j, (qname_b, member_b)) in self.selected_attributes.iter().enumerate() {
236                if i == j {
237                    continue;
238                }
239                // Flip a and b around if a is later than b.
240                // This way, the error message is the same for both
241                // conflicting fields. Note that we always take the span of
242                // `a` though, so that the two errors point at different
243                // fields.
244                let span = member_a.span();
245                let (member_a, member_b) = if i > j {
246                    (member_b, member_a)
247                } else {
248                    (member_a, member_b)
249                };
250                if qname_a.namespace.is_some() != qname_b.namespace.is_some() {
251                    // cannot ever match.
252                    continue;
253                }
254                let Some((name_a, name_b)) = qname_a.name.as_ref().zip(qname_b.name.as_ref())
255                else {
256                    panic!("selected attribute has no XML local name");
257                };
258
259                let mut check = quote! {
260                    ::xso::exports::const_str_eq(#name_a.as_str(), #name_b.as_str())
261                };
262
263                let namespaces = qname_a.namespace.as_ref().zip(qname_b.namespace.as_ref());
264                if let Some((ns_a, ns_b)) = namespaces {
265                    check.extend(quote! {
266                        && ::xso::exports::const_str_eq(#ns_a, #ns_b)
267                    });
268                };
269
270                let attr_a = if let Some(namespace_a) = qname_a.namespace.as_ref() {
271                    format!("{{{}}}{}", namespace_a, name_a)
272                } else {
273                    format!("{}", name_a)
274                };
275
276                let attr_b = if let Some(namespace_b) = qname_b.namespace.as_ref() {
277                    format!("{{{}}}{}", namespace_b, name_b)
278                } else {
279                    format!("{}", name_b)
280                };
281
282                // We need to escape `{` and `}` because we feed it into
283                // a generated `panic!()` as message template below. We cannot
284                // do something like `panic!("member {} […]", #field_a, […])`,
285                // because rustc does not allow that in a const context.
286                // (It *did* briefly (and unintentionally, see
287                // https://github.com/rust-lang/rust/issues/140585 ) allow
288                // that in version 1.86, but that was rolled back.
289                let attr_a = attr_a.replace('{', "{{").replace('}', "}}");
290                let attr_b = attr_b.replace('{', "{{").replace('}', "}}");
291                let field_a = FieldName(member_a)
292                    .to_string()
293                    .replace('{', "{{")
294                    .replace('}', "}}");
295                let field_b = FieldName(member_b)
296                    .to_string()
297                    .replace('{', "{{")
298                    .replace('}', "}}");
299
300                // By assigning the checks to a `const`, we ensure that they
301                // are in fact evaluated at compile time, even if that constant
302                // is never used.
303                checks.extend(quote_spanned! {span=>
304                    const _: () = { if #check {
305                        panic!(concat!("member ", #field_a, " and member ", #field_b, " match the same XML attribute: ", #attr_a, " == ", #attr_b));
306                    } };
307                })
308            }
309        }
310
311        checks
312    }
313
314    /// Make and return a set of states which is used to construct the target
315    /// type from XML events.
316    ///
317    /// The states are returned as partial state machine. See the return
318    /// type's documentation for details.
319    pub(crate) fn make_from_events_statemachine(
320        &self,
321        generics: &mut GenericsInfo,
322        state_ty_ident: &Ident,
323        output_name: &ParentRef,
324        state_prefix: &str,
325    ) -> Result<FromEventsSubmachine> {
326        let scope = FromEventsScope::new(state_ty_ident.clone());
327        let FromEventsScope {
328            ref attrs,
329            ref builder_data_ident,
330            ref text,
331            ref substate_data,
332            ref substate_result,
333            ..
334        } = scope;
335
336        let default_state_ident = quote::format_ident!("{}Default", state_prefix);
337        let discard_state_ident = quote::format_ident!("{}Discard", state_prefix);
338        let builder_data_ty_ident = quote::format_ident!("{}Data{}", state_ty_ident, state_prefix);
339        let builder_data_ty = generics.ty_with_arguments(builder_data_ty_ident.clone());
340        let mut states = Vec::new();
341
342        let mut builder_data_def = TokenStream::default();
343        let mut builder_data_init = TokenStream::default();
344        let mut output_cons = TokenStream::default();
345        let mut child_matchers = TokenStream::default();
346        let mut fallback_child_matcher = None;
347        let mut text_handler = if self.discard_text.is_set() {
348            Some(quote! {
349                ::core::result::Result::Ok(::core::ops::ControlFlow::Break(
350                    Self::#default_state_ident { #builder_data_ident }
351                ))
352            })
353        } else {
354            None
355        };
356        let mut extra_defs = TokenStream::default();
357        let is_tuple = !output_name.is_path();
358
359        for (i, field) in self.fields.iter().enumerate() {
360            let member = field.member();
361            let builder_field_name = mangle_member(member);
362            let part = field.make_builder_part(&scope, generics, output_name)?;
363            let state_name = quote::format_ident!("{}Field{}", state_prefix, i);
364
365            match part {
366                FieldBuilderPart::Init {
367                    value: FieldTempInit { ty, init },
368                } => {
369                    builder_data_def.extend(quote! {
370                        #builder_field_name: #ty,
371                    });
372
373                    builder_data_init.extend(quote! {
374                        #builder_field_name: #init,
375                    });
376
377                    if is_tuple {
378                        output_cons.extend(quote! {
379                            #builder_data_ident.#builder_field_name,
380                        });
381                    } else {
382                        output_cons.extend(quote! {
383                            #member: #builder_data_ident.#builder_field_name,
384                        });
385                    }
386                }
387
388                FieldBuilderPart::Text {
389                    value: FieldTempInit { ty, init },
390                    collect,
391                    finalize,
392                } => {
393                    if text_handler.is_some() {
394                        // the existence of only one text handler is enforced
395                        // by Compound's constructor(s).
396                        panic!("more than one field attempts to collect text data");
397                    }
398
399                    builder_data_def.extend(quote! {
400                        #builder_field_name: #ty,
401                    });
402                    builder_data_init.extend(quote! {
403                        #builder_field_name: #init,
404                    });
405                    text_handler = Some(quote! {
406                        #collect
407                        ::core::result::Result::Ok(::core::ops::ControlFlow::Break(
408                            Self::#default_state_ident { #builder_data_ident }
409                        ))
410                    });
411
412                    if is_tuple {
413                        output_cons.extend(quote! {
414                            #finalize,
415                        });
416                    } else {
417                        output_cons.extend(quote! {
418                            #member: #finalize,
419                        });
420                    }
421                }
422
423                FieldBuilderPart::Nested {
424                    extra_defs: field_extra_defs,
425                    value: FieldTempInit { ty, init },
426                    matcher,
427                    builder,
428                    collect,
429                    finalize,
430                } => {
431                    let feed = feed_fn(builder.clone());
432
433                    let mut substate_data_ident = substate_data.clone();
434                    substate_data_ident.set_span(ty.span());
435                    states.push(State::new_with_builder(
436                        state_name.clone(),
437                        builder_data_ident,
438                        &builder_data_ty,
439                    ).with_field(
440                        &substate_data_ident,
441                        &builder,
442                    ).with_mut(substate_data).with_impl(quote! {
443                        match #feed(&mut #substate_data, ev, ctx)? {
444                            ::core::option::Option::Some(#substate_result) => {
445                                #collect
446                                ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#default_state_ident {
447                                    #builder_data_ident,
448                                }))
449                            }
450                            ::core::option::Option::None => {
451                                ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#state_name {
452                                    #builder_data_ident,
453                                    #substate_data,
454                                }))
455                            }
456                        }
457                    }));
458
459                    builder_data_def.extend(quote! {
460                        #builder_field_name: #ty,
461                    });
462
463                    builder_data_init.extend(quote! {
464                        #builder_field_name: #init,
465                    });
466
467                    match matcher {
468                        NestedMatcher::Selective(matcher) => {
469                            child_matchers.extend(quote! {
470                                let (name, attrs) = match #matcher {
471                                    ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch { name, attrs }) => (name, attrs),
472                                    ::core::result::Result::Err(::xso::error::FromEventsError::Invalid(e)) => return ::core::result::Result::Err(e),
473                                    ::core::result::Result::Ok(#substate_data) => {
474                                        return ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#state_name {
475                                            #builder_data_ident,
476                                            #substate_data,
477                                        }))
478                                    }
479                                };
480                            });
481                        }
482                        NestedMatcher::Fallback(matcher) => {
483                            if let Some((span, _)) = fallback_child_matcher.as_ref() {
484                                let mut err = Error::new(
485                                    field.span(),
486                                    "more than one field is attempting to consume all unmatched child elements"
487                                );
488                                err.combine(Error::new(
489                                    *span,
490                                    "the previous field collecting all unmatched child elements is here"
491                                ));
492                                return Err(err);
493                            }
494
495                            let matcher = quote! {
496                                ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#state_name {
497                                    #builder_data_ident,
498                                    #substate_data: { #matcher },
499                                }))
500                            };
501
502                            fallback_child_matcher = Some((field.span(), matcher));
503                        }
504                    }
505
506                    if is_tuple {
507                        output_cons.extend(quote! {
508                            #finalize,
509                        });
510                    } else {
511                        output_cons.extend(quote! {
512                            #member: #finalize,
513                        });
514                    }
515
516                    extra_defs.extend(field_extra_defs);
517                }
518            }
519        }
520
521        // We always implicitly discard the `xml:lang` attribute. Its
522        // processing is handled using the `#[xml(language)]` meta.
523        let mut discard_attr = quote! {
524            let _ = #attrs.remove(::xso::exports::rxml::Namespace::xml(), "lang");
525        };
526        for (xml_namespace, xml_name) in self.discard_attr.iter() {
527            let xml_namespace = match xml_namespace {
528                Some(v) => v.to_token_stream(),
529                None => quote! {
530                    ::xso::exports::rxml::Namespace::none()
531                },
532            };
533            discard_attr.extend(quote! {
534                let _ = #attrs.remove(#xml_namespace, #xml_name);
535            });
536        }
537
538        let text_handler = match text_handler {
539            Some(v) => v,
540            None => quote! {
541                // note: u8::is_ascii_whitespace includes U+000C, which is not
542                // part of XML's white space definition.'
543                if !::xso::is_xml_whitespace(#text.as_bytes()) {
544                    ::core::result::Result::Err(::xso::error::Error::Other("Unexpected text content".into()))
545                } else {
546                    ::core::result::Result::Ok(::core::ops::ControlFlow::Break(
547                        Self::#default_state_ident { #builder_data_ident }
548                    ))
549                }
550            },
551        };
552
553        let unknown_attr_err = format!("Unknown attribute in {}.", output_name);
554        let unknown_child_err = format!("Unknown child in {}.", output_name);
555        let unknown_child_policy = &self.unknown_child_policy;
556
557        let output_cons = match output_name {
558            ParentRef::Named(ref path) => {
559                quote! {
560                    #path { #output_cons }
561                }
562            }
563            ParentRef::Unnamed { .. } => {
564                quote! {
565                    ( #output_cons )
566                }
567            }
568        };
569
570        let discard_builder_ty = discard_builder_ty(Span::call_site());
571        let discard_feed = feed_fn(discard_builder_ty.clone());
572        let child_fallback = match fallback_child_matcher {
573            Some((_, matcher)) => matcher,
574            None => quote! {
575                let _ = (name, attrs);
576                let _: () = #unknown_child_policy.apply_policy(#unknown_child_err)?;
577                ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#discard_state_ident {
578                    #builder_data_ident,
579                    #substate_data: #discard_builder_ty::new(),
580                }))
581            },
582        };
583
584        states.push(State::new_with_builder(
585            discard_state_ident.clone(),
586            builder_data_ident,
587            &builder_data_ty,
588        ).with_field(
589            substate_data,
590            &discard_builder_ty,
591        ).with_mut(substate_data).with_impl(quote! {
592            match #discard_feed(&mut #substate_data, ev, ctx)? {
593                ::core::option::Option::Some(#substate_result) => {
594                    ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#default_state_ident {
595                        #builder_data_ident,
596                    }))
597                }
598                ::core::option::Option::None => {
599                    ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#discard_state_ident {
600                        #builder_data_ident,
601                        #substate_data,
602                    }))
603                }
604            }
605        }));
606
607        states.push(State::new_with_builder(
608            default_state_ident.clone(),
609            builder_data_ident,
610            &builder_data_ty,
611        ).with_impl(quote! {
612            match ev {
613                // EndElement in Default state -> done parsing.
614                ::xso::exports::rxml::Event::EndElement(_) => {
615                    ::core::result::Result::Ok(::core::ops::ControlFlow::Continue(
616                        #output_cons
617                    ))
618                }
619                ::xso::exports::rxml::Event::StartElement(_, name, attrs) => {
620                    #child_matchers
621                    #child_fallback
622                }
623                ::xso::exports::rxml::Event::Text(_, #text) => {
624                    #text_handler
625                }
626                // we ignore these: a correct parser only generates
627                // them at document start, and there we want to indeed
628                // not worry about them being in front of the first
629                // element.
630                ::xso::exports::rxml::Event::XmlDeclaration(_, ::xso::exports::rxml::XmlVersion::V1_0) => ::core::result::Result::Ok(::core::ops::ControlFlow::Break(
631                    Self::#default_state_ident { #builder_data_ident }
632                ))
633            }
634        }));
635
636        let unknown_attribute_policy = &self.unknown_attribute_policy;
637
638        let checks = self.assert_disjunct_attributes();
639
640        Ok(FromEventsSubmachine {
641            defs: quote! {
642                #extra_defs
643                #checks
644
645                struct #builder_data_ty {
646                    #builder_data_def
647                }
648            },
649            states,
650            init: quote! {
651                let #builder_data_ident = #builder_data_ty_ident {
652                    #builder_data_init
653                };
654                #discard_attr
655                if #attrs.len() > 0 {
656                    let _: () = #unknown_attribute_policy.apply_policy(#unknown_attr_err)?;
657                }
658                ::core::result::Result::Ok(#state_ty_ident::#default_state_ident { #builder_data_ident })
659            },
660        })
661    }
662
663    /// Make and return a set of states which is used to destructure the
664    /// target type into XML events.
665    ///
666    /// The states are returned as partial state machine. See the return
667    /// type's documentation for details.
668    ///
669    /// **Important:** The returned submachine is not in functional state!
670    /// It's `init` must be modified so that a variable called `name` of type
671    /// `rxml::QName` is in scope.
672    pub(crate) fn make_as_item_iter_statemachine(
673        &self,
674        generics: &mut GenericsInfo,
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, generics, 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}