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}