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                let field_a = FieldName(&member_a).to_string();
282                let field_b = FieldName(&member_b).to_string();
283
284                // By assigning the checks to a `const`, we ensure that they
285                // are in fact evaluated at compile time, even if that constant
286                // is never used.
287                checks.extend(quote_spanned! {span=>
288                    const _: () = { if #check {
289                        panic!("member {} and member {} match the same XML attribute: {} == {}", #field_a, #field_b, #attr_a, #attr_b);
290                    } };
291                })
292            }
293        }
294
295        checks
296    }
297
298    /// Make and return a set of states which is used to construct the target
299    /// type from XML events.
300    ///
301    /// The states are returned as partial state machine. See the return
302    /// type's documentation for details.
303    pub(crate) fn make_from_events_statemachine(
304        &self,
305        state_ty_ident: &Ident,
306        output_name: &ParentRef,
307        state_prefix: &str,
308    ) -> Result<FromEventsSubmachine> {
309        let scope = FromEventsScope::new(state_ty_ident.clone());
310        let FromEventsScope {
311            ref attrs,
312            ref builder_data_ident,
313            ref text,
314            ref substate_data,
315            ref substate_result,
316            ..
317        } = scope;
318
319        let default_state_ident = quote::format_ident!("{}Default", state_prefix);
320        let discard_state_ident = quote::format_ident!("{}Discard", state_prefix);
321        let builder_data_ty: Type = TypePath {
322            qself: None,
323            path: quote::format_ident!("{}Data{}", state_ty_ident, state_prefix).into(),
324        }
325        .into();
326        let mut states = Vec::new();
327
328        let mut builder_data_def = TokenStream::default();
329        let mut builder_data_init = TokenStream::default();
330        let mut output_cons = TokenStream::default();
331        let mut child_matchers = TokenStream::default();
332        let mut fallback_child_matcher = None;
333        let mut text_handler = if self.discard_text.is_set() {
334            Some(quote! {
335                ::core::result::Result::Ok(::core::ops::ControlFlow::Break(
336                    Self::#default_state_ident { #builder_data_ident }
337                ))
338            })
339        } else {
340            None
341        };
342        let mut extra_defs = TokenStream::default();
343        let is_tuple = !output_name.is_path();
344
345        for (i, field) in self.fields.iter().enumerate() {
346            let member = field.member();
347            let builder_field_name = mangle_member(member);
348            let part = field.make_builder_part(&scope, output_name)?;
349            let state_name = quote::format_ident!("{}Field{}", state_prefix, i);
350
351            match part {
352                FieldBuilderPart::Init {
353                    value: FieldTempInit { ty, init },
354                } => {
355                    builder_data_def.extend(quote! {
356                        #builder_field_name: #ty,
357                    });
358
359                    builder_data_init.extend(quote! {
360                        #builder_field_name: #init,
361                    });
362
363                    if is_tuple {
364                        output_cons.extend(quote! {
365                            #builder_data_ident.#builder_field_name,
366                        });
367                    } else {
368                        output_cons.extend(quote! {
369                            #member: #builder_data_ident.#builder_field_name,
370                        });
371                    }
372                }
373
374                FieldBuilderPart::Text {
375                    value: FieldTempInit { ty, init },
376                    collect,
377                    finalize,
378                } => {
379                    if text_handler.is_some() {
380                        // the existence of only one text handler is enforced
381                        // by Compound's constructor(s).
382                        panic!("more than one field attempts to collect text data");
383                    }
384
385                    builder_data_def.extend(quote! {
386                        #builder_field_name: #ty,
387                    });
388                    builder_data_init.extend(quote! {
389                        #builder_field_name: #init,
390                    });
391                    text_handler = Some(quote! {
392                        #collect
393                        ::core::result::Result::Ok(::core::ops::ControlFlow::Break(
394                            Self::#default_state_ident { #builder_data_ident }
395                        ))
396                    });
397
398                    if is_tuple {
399                        output_cons.extend(quote! {
400                            #finalize,
401                        });
402                    } else {
403                        output_cons.extend(quote! {
404                            #member: #finalize,
405                        });
406                    }
407                }
408
409                FieldBuilderPart::Nested {
410                    extra_defs: field_extra_defs,
411                    value: FieldTempInit { ty, init },
412                    matcher,
413                    builder,
414                    collect,
415                    finalize,
416                } => {
417                    let feed = feed_fn(builder.clone());
418
419                    states.push(State::new_with_builder(
420                        state_name.clone(),
421                        builder_data_ident,
422                        &builder_data_ty,
423                    ).with_field(
424                        substate_data,
425                        &builder,
426                    ).with_mut(substate_data).with_impl(quote! {
427                        match #feed(&mut #substate_data, ev, ctx)? {
428                            ::core::option::Option::Some(#substate_result) => {
429                                #collect
430                                ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#default_state_ident {
431                                    #builder_data_ident,
432                                }))
433                            }
434                            ::core::option::Option::None => {
435                                ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#state_name {
436                                    #builder_data_ident,
437                                    #substate_data,
438                                }))
439                            }
440                        }
441                    }));
442
443                    builder_data_def.extend(quote! {
444                        #builder_field_name: #ty,
445                    });
446
447                    builder_data_init.extend(quote! {
448                        #builder_field_name: #init,
449                    });
450
451                    match matcher {
452                        NestedMatcher::Selective(matcher) => {
453                            child_matchers.extend(quote! {
454                                let (name, attrs) = match #matcher {
455                                    ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch { name, attrs }) => (name, attrs),
456                                    ::core::result::Result::Err(::xso::error::FromEventsError::Invalid(e)) => return ::core::result::Result::Err(e),
457                                    ::core::result::Result::Ok(#substate_data) => {
458                                        return ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#state_name {
459                                            #builder_data_ident,
460                                            #substate_data,
461                                        }))
462                                    }
463                                };
464                            });
465                        }
466                        NestedMatcher::Fallback(matcher) => {
467                            if let Some((span, _)) = fallback_child_matcher.as_ref() {
468                                let mut err = Error::new(
469                                    field.span(),
470                                    "more than one field is attempting to consume all unmatched child elements"
471                                );
472                                err.combine(Error::new(
473                                    *span,
474                                    "the previous field collecting all unmatched child elements is here"
475                                ));
476                                return Err(err);
477                            }
478
479                            let matcher = quote! {
480                                ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#state_name {
481                                    #builder_data_ident,
482                                    #substate_data: { #matcher },
483                                }))
484                            };
485
486                            fallback_child_matcher = Some((field.span(), matcher));
487                        }
488                    }
489
490                    if is_tuple {
491                        output_cons.extend(quote! {
492                            #finalize,
493                        });
494                    } else {
495                        output_cons.extend(quote! {
496                            #member: #finalize,
497                        });
498                    }
499
500                    extra_defs.extend(field_extra_defs);
501                }
502            }
503        }
504
505        // We always implicitly discard the `xml:lang` attribute. Its
506        // processing is handled using the `#[xml(language)]` meta.
507        let mut discard_attr = quote! {
508            let _ = #attrs.remove(::xso::exports::rxml::Namespace::xml(), "lang");
509        };
510        for (xml_namespace, xml_name) in self.discard_attr.iter() {
511            let xml_namespace = match xml_namespace {
512                Some(v) => v.to_token_stream(),
513                None => quote! {
514                    ::xso::exports::rxml::Namespace::none()
515                },
516            };
517            discard_attr.extend(quote! {
518                let _ = #attrs.remove(#xml_namespace, #xml_name);
519            });
520        }
521
522        let text_handler = match text_handler {
523            Some(v) => v,
524            None => quote! {
525                // note: u8::is_ascii_whitespace includes U+000C, which is not
526                // part of XML's white space definition.'
527                if !::xso::is_xml_whitespace(#text.as_bytes()) {
528                    ::core::result::Result::Err(::xso::error::Error::Other("Unexpected text content".into()))
529                } else {
530                    ::core::result::Result::Ok(::core::ops::ControlFlow::Break(
531                        Self::#default_state_ident { #builder_data_ident }
532                    ))
533                }
534            },
535        };
536
537        let unknown_attr_err = format!("Unknown attribute in {}.", output_name);
538        let unknown_child_err = format!("Unknown child in {}.", output_name);
539        let unknown_child_policy = &self.unknown_child_policy;
540
541        let output_cons = match output_name {
542            ParentRef::Named(ref path) => {
543                quote! {
544                    #path { #output_cons }
545                }
546            }
547            ParentRef::Unnamed { .. } => {
548                quote! {
549                    ( #output_cons )
550                }
551            }
552        };
553
554        let discard_builder_ty = discard_builder_ty(Span::call_site());
555        let discard_feed = feed_fn(discard_builder_ty.clone());
556        let child_fallback = match fallback_child_matcher {
557            Some((_, matcher)) => matcher,
558            None => quote! {
559                let _ = (name, attrs);
560                let _: () = #unknown_child_policy.apply_policy(#unknown_child_err)?;
561                ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#discard_state_ident {
562                    #builder_data_ident,
563                    #substate_data: #discard_builder_ty::new(),
564                }))
565            },
566        };
567
568        states.push(State::new_with_builder(
569            discard_state_ident.clone(),
570            builder_data_ident,
571            &builder_data_ty,
572        ).with_field(
573            substate_data,
574            &discard_builder_ty,
575        ).with_mut(substate_data).with_impl(quote! {
576            match #discard_feed(&mut #substate_data, ev, ctx)? {
577                ::core::option::Option::Some(#substate_result) => {
578                    ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#default_state_ident {
579                        #builder_data_ident,
580                    }))
581                }
582                ::core::option::Option::None => {
583                    ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#discard_state_ident {
584                        #builder_data_ident,
585                        #substate_data,
586                    }))
587                }
588            }
589        }));
590
591        states.push(State::new_with_builder(
592            default_state_ident.clone(),
593            builder_data_ident,
594            &builder_data_ty,
595        ).with_impl(quote! {
596            match ev {
597                // EndElement in Default state -> done parsing.
598                ::xso::exports::rxml::Event::EndElement(_) => {
599                    ::core::result::Result::Ok(::core::ops::ControlFlow::Continue(
600                        #output_cons
601                    ))
602                }
603                ::xso::exports::rxml::Event::StartElement(_, name, attrs) => {
604                    #child_matchers
605                    #child_fallback
606                }
607                ::xso::exports::rxml::Event::Text(_, #text) => {
608                    #text_handler
609                }
610                // we ignore these: a correct parser only generates
611                // them at document start, and there we want to indeed
612                // not worry about them being in front of the first
613                // element.
614                ::xso::exports::rxml::Event::XmlDeclaration(_, ::xso::exports::rxml::XmlVersion::V1_0) => ::core::result::Result::Ok(::core::ops::ControlFlow::Break(
615                    Self::#default_state_ident { #builder_data_ident }
616                ))
617            }
618        }));
619
620        let unknown_attribute_policy = &self.unknown_attribute_policy;
621
622        let checks = self.assert_disjunct_attributes();
623
624        Ok(FromEventsSubmachine {
625            defs: quote! {
626                #extra_defs
627                #checks
628
629                struct #builder_data_ty {
630                    #builder_data_def
631                }
632            },
633            states,
634            init: quote! {
635                let #builder_data_ident = #builder_data_ty {
636                    #builder_data_init
637                };
638                #discard_attr
639                if #attrs.len() > 0 {
640                    let _: () = #unknown_attribute_policy.apply_policy(#unknown_attr_err)?;
641                }
642                ::core::result::Result::Ok(#state_ty_ident::#default_state_ident { #builder_data_ident })
643            },
644        })
645    }
646
647    /// Make and return a set of states which is used to destructure the
648    /// target type into XML events.
649    ///
650    /// The states are returned as partial state machine. See the return
651    /// type's documentation for details.
652    ///
653    /// **Important:** The returned submachine is not in functional state!
654    /// It's `init` must be modified so that a variable called `name` of type
655    /// `rxml::QName` is in scope.
656    pub(crate) fn make_as_item_iter_statemachine(
657        &self,
658        input_name: &ParentRef,
659        state_ty_ident: &Ident,
660        state_prefix: &str,
661        lifetime: &Lifetime,
662    ) -> Result<AsItemsSubmachine> {
663        let scope = AsItemsScope::new(lifetime, state_ty_ident.clone());
664
665        let element_head_start_state_ident =
666            quote::format_ident!("{}ElementHeadStart", state_prefix);
667        let element_head_end_state_ident = quote::format_ident!("{}ElementHeadEnd", state_prefix);
668        let element_foot_state_ident = quote::format_ident!("{}ElementFoot", state_prefix);
669        let name_ident = quote::format_ident!("name");
670        let ns_ident = quote::format_ident!("ns");
671        let dummy_ident = quote::format_ident!("dummy");
672        let mut header_states = Vec::new();
673        let mut body_states = Vec::new();
674
675        let is_tuple = !input_name.is_path();
676        let mut destructure = TokenStream::default();
677        let mut start_init = TokenStream::default();
678        let mut extra_defs = TokenStream::default();
679
680        header_states.push(
681            State::new(element_head_start_state_ident.clone())
682                .with_field(&dummy_ident, &phantom_lifetime_ty(lifetime.clone()))
683                .with_field(&ns_ident, &namespace_ty(Span::call_site()))
684                .with_field(
685                    &name_ident,
686                    &ncnamestr_cow_ty(Span::call_site(), lifetime.clone()),
687                ),
688        );
689
690        body_states.push((
691            None,
692            State::new(element_head_end_state_ident.clone()).with_impl(quote! {
693                ::core::option::Option::Some(::xso::Item::ElementHeadEnd)
694            }),
695        ));
696
697        for (i, field) in self.fields.iter().enumerate() {
698            let member = field.member();
699            let bound_name = mangle_member(member);
700            let part = field.make_iterator_part(&scope, input_name, &bound_name)?;
701            let state_name = quote::format_ident!("{}Field{}", state_prefix, i);
702            let ty = scope.borrow(field.ty().clone());
703
704            match part {
705                FieldIteratorPart::Header { generator } => {
706                    // We have to make sure that we carry our data around in
707                    // all the previous states.
708                    // For header states, it is sufficient to do it here.
709                    // For body states, we have to do it in a separate loop
710                    // below to correctly handle the case when a field with a
711                    // body state is placed before a field with a header
712                    // state.
713                    for state in header_states.iter_mut() {
714                        state.add_field(&bound_name, &ty);
715                    }
716                    header_states.push(
717                        State::new(state_name)
718                            .with_field(&bound_name, &ty)
719                            .with_impl(quote! {
720                                #generator
721                            }),
722                    );
723
724                    if is_tuple {
725                        destructure.extend(quote! {
726                            ref #bound_name,
727                        });
728                    } else {
729                        destructure.extend(quote! {
730                            #member: ref #bound_name,
731                        });
732                    }
733                    start_init.extend(quote! {
734                        #bound_name,
735                    });
736                }
737
738                FieldIteratorPart::Text { generator } => {
739                    // We have to make sure that we carry our data around in
740                    // all the previous body states.
741                    // We also have to make sure that our data is carried
742                    // by all *header* states, but we can only do that once
743                    // we have visited them all, so that happens at the bottom
744                    // of the loop.
745                    for (_, state) in body_states.iter_mut() {
746                        state.add_field(&bound_name, &ty);
747                    }
748                    let state = State::new(state_name)
749                        .with_field(&bound_name, &ty)
750                        .with_impl(quote! {
751                            #generator.map(|value| ::xso::Item::Text(
752                                value,
753                            ))
754                        });
755                    if is_tuple {
756                        destructure.extend(quote! {
757                            #bound_name,
758                        });
759                    } else {
760                        destructure.extend(quote! {
761                            #member: #bound_name,
762                        });
763                    }
764                    start_init.extend(quote! {
765                        #bound_name,
766                    });
767                    body_states.push((Some((bound_name, ty)), state));
768                }
769
770                FieldIteratorPart::Content {
771                    extra_defs: field_extra_defs,
772                    value: FieldTempInit { ty, init },
773                    generator,
774                } => {
775                    // We have to make sure that we carry our data around in
776                    // all the previous body states.
777                    // We also have to make sure that our data is carried
778                    // by all *header* states, but we can only do that once
779                    // we have visited them all, so that happens at the bottom
780                    // of the loop.
781                    for (_, state) in body_states.iter_mut() {
782                        state.add_field(&bound_name, &ty);
783                    }
784
785                    let state = State::new(state_name.clone())
786                        .with_field(&bound_name, &ty)
787                        .with_mut(&bound_name)
788                        .with_impl(quote! {
789                            #generator?
790                        });
791                    if is_tuple {
792                        destructure.extend(quote! {
793                            #bound_name,
794                        });
795                    } else {
796                        destructure.extend(quote! {
797                            #member: #bound_name,
798                        });
799                    }
800                    start_init.extend(quote! {
801                        #bound_name: #init,
802                    });
803
804                    extra_defs.extend(field_extra_defs);
805                    body_states.push((Some((bound_name, ty)), state));
806                }
807            }
808        }
809
810        header_states[0].set_impl(quote! {
811            {
812                ::core::option::Option::Some(::xso::Item::ElementHeadStart(
813                    #ns_ident,
814                    #name_ident,
815                ))
816            }
817        });
818
819        for (data, _) in body_states.iter() {
820            if let Some((bound_name, ty)) = data.as_ref() {
821                for state in header_states.iter_mut() {
822                    state.add_field(bound_name, ty);
823                }
824            }
825        }
826
827        header_states.extend(body_states.into_iter().map(|(_, state)| state));
828        let mut states = header_states;
829
830        states.push(
831            State::new(element_foot_state_ident.clone()).with_impl(quote! {
832                ::core::option::Option::Some(::xso::Item::ElementFoot)
833            }),
834        );
835
836        let destructure = match input_name {
837            ParentRef::Named(ref input_path) => quote! {
838                #input_path { #destructure }
839            },
840            ParentRef::Unnamed { .. } => quote! {
841                ( #destructure )
842            },
843        };
844
845        let checks = self.assert_disjunct_attributes();
846        extra_defs.extend(checks);
847
848        Ok(AsItemsSubmachine {
849            defs: extra_defs,
850            states,
851            destructure,
852            init: quote! {
853                Self::#element_head_start_state_ident { #dummy_ident: ::core::marker::PhantomData, #name_ident: name.1, #ns_ident: name.0, #start_init }
854            },
855        })
856    }
857
858    /// Return a reference to this compound's only field's type.
859    ///
860    /// If the compound does not have exactly one field, this function returns
861    /// None.
862    pub(crate) fn single_ty(&self) -> Option<&Type> {
863        if self.fields.len() > 1 {
864            return None;
865        }
866        self.fields.first().map(|x| x.ty())
867    }
868
869    /// Construct a tuple type with this compound's field's types in the same
870    /// order as they appear in the compound.
871    pub(crate) fn to_tuple_ty(&self) -> TypeTuple {
872        TypeTuple {
873            paren_token: token::Paren::default(),
874            elems: self.fields.iter().map(|x| x.ty().clone()).collect(),
875        }
876    }
877
878    /// Construct a tuple type with this compound's field's types in the same
879    /// order as they appear in the compound.
880    pub(crate) fn to_single_or_tuple_ty(&self) -> Type {
881        match self.single_ty() {
882            None => self.to_tuple_ty().into(),
883            Some(v) => v.clone(),
884        }
885    }
886
887    /// Construct a tuple type with references to this compound's field's
888    /// types in the same order as they appear in the compound, with the given
889    /// lifetime.
890    pub(crate) fn to_ref_tuple_ty(&self, lifetime: &Lifetime) -> TypeTuple {
891        TypeTuple {
892            paren_token: token::Paren::default(),
893            elems: self
894                .fields
895                .iter()
896                .map(|x| ref_ty(x.ty().clone(), lifetime.clone()))
897                .collect(),
898        }
899    }
900
901    /// Return the number of fields in this compound.
902    pub(crate) fn field_count(&self) -> usize {
903        self.fields.len()
904    }
905}