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, ToTokens};
11use syn::{spanned::Spanned, *};
12
13use crate::error_message::ParentRef;
14use crate::field::{FieldBuilderPart, FieldDef, FieldIteratorPart, FieldTempInit, NestedMatcher};
15use crate::meta::{DiscardSpec, Flag, NameRef, NamespaceRef, QNameRef};
16use crate::scope::{mangle_member, AsItemsScope, FromEventsScope};
17use crate::state::{AsItemsSubmachine, FromEventsSubmachine, State};
18use crate::types::{
19    default_fn, discard_builder_ty, feed_fn, namespace_ty, ncnamestr_cow_ty, phantom_lifetime_ty,
20    ref_ty, unknown_attribute_policy_path, unknown_child_policy_path,
21};
22
23fn resolve_policy(policy: Option<Ident>, mut enum_ref: Path) -> Expr {
24    match policy {
25        Some(ident) => {
26            enum_ref.segments.push(ident.into());
27            Expr::Path(ExprPath {
28                attrs: Vec::new(),
29                qself: None,
30                path: enum_ref,
31            })
32        }
33        None => {
34            let default_fn = default_fn(Type::Path(TypePath {
35                qself: None,
36                path: enum_ref,
37            }));
38            Expr::Call(ExprCall {
39                attrs: Vec::new(),
40                func: Box::new(default_fn),
41                paren_token: token::Paren::default(),
42                args: punctuated::Punctuated::new(),
43            })
44        }
45    }
46}
47
48/// A struct or enum variant's contents.
49pub(crate) struct Compound {
50    /// The fields of this compound.
51    fields: Vec<FieldDef>,
52
53    /// Policy defining how to handle unknown attributes.
54    unknown_attribute_policy: Expr,
55
56    /// Policy defining how to handle unknown children.
57    unknown_child_policy: Expr,
58
59    /// Attributes to discard.
60    discard_attr: Vec<(Option<NamespaceRef>, NameRef)>,
61
62    /// Text to discard.
63    discard_text: Flag,
64}
65
66impl Compound {
67    /// Construct a compound from processed field definitions.
68    pub(crate) fn from_field_defs<I: IntoIterator<Item = Result<FieldDef>>>(
69        compound_fields: I,
70        unknown_attribute_policy: Option<Ident>,
71        unknown_child_policy: Option<Ident>,
72        discard: Vec<DiscardSpec>,
73    ) -> Result<Self> {
74        let unknown_attribute_policy = resolve_policy(
75            unknown_attribute_policy,
76            unknown_attribute_policy_path(Span::call_site()),
77        );
78        let unknown_child_policy = resolve_policy(
79            unknown_child_policy,
80            unknown_child_policy_path(Span::call_site()),
81        );
82        let compound_fields = compound_fields.into_iter();
83        let size_hint = compound_fields.size_hint();
84        let mut fields = Vec::with_capacity(size_hint.1.unwrap_or(size_hint.0));
85        let mut text_field = None;
86        for field in compound_fields {
87            let field = field?;
88
89            if field.is_text_field() {
90                if let Some(other_field) = text_field.as_ref() {
91                    let mut err = Error::new_spanned(
92                        field.member(),
93                        "only one `#[xml(text)]` field allowed per compound",
94                    );
95                    err.combine(Error::new(
96                        *other_field,
97                        "the other `#[xml(text)]` field is here",
98                    ));
99                    return Err(err);
100                }
101                text_field = Some(field.member().span())
102            }
103
104            fields.push(field);
105        }
106
107        let mut discard_text = Flag::Absent;
108        let mut discard_attr = Vec::new();
109        for spec in discard {
110            match spec {
111                DiscardSpec::Text { span } => {
112                    if let Some(field) = text_field.as_ref() {
113                        let mut err = Error::new(
114                            *field,
115                            "cannot combine `#[xml(text)]` field with `discard(text)`",
116                        );
117                        err.combine(Error::new(
118                            spec.span(),
119                            "the discard(text) attribute is here",
120                        ));
121                        return Err(err);
122                    }
123                    if let Flag::Present(other) = discard_text {
124                        let mut err = Error::new(
125                            span,
126                            "only one `discard(text)` meta is allowed per compound",
127                        );
128                        err.combine(Error::new(other, "the discard(text) meta is here"));
129                        return Err(err);
130                    }
131
132                    discard_text = Flag::Present(span);
133                }
134
135                DiscardSpec::Attribute {
136                    qname: QNameRef { namespace, name },
137                    span,
138                } => {
139                    let xml_namespace = namespace;
140                    let xml_name = match name {
141                        Some(v) => v,
142                        None => {
143                            return Err(Error::new(
144                                span,
145                                "discard(attribute) must specify a name, e.g. via discard(attribute = \"some-name\")",
146                            ));
147                        }
148                    };
149                    discard_attr.push((xml_namespace, xml_name));
150                }
151            }
152        }
153
154        Ok(Self {
155            fields,
156            unknown_attribute_policy,
157            unknown_child_policy,
158            discard_attr,
159            discard_text,
160        })
161    }
162
163    /// Construct a compound from fields.
164    pub(crate) fn from_fields(
165        compound_fields: &Fields,
166        container_namespace: &NamespaceRef,
167        unknown_attribute_policy: Option<Ident>,
168        unknown_child_policy: Option<Ident>,
169        discard: Vec<DiscardSpec>,
170    ) -> Result<Self> {
171        Self::from_field_defs(
172            compound_fields.iter().enumerate().map(|(i, field)| {
173                let index = match i.try_into() {
174                    Ok(v) => v,
175                    // we are converting to u32, are you crazy?!
176                    // (u32, because syn::Member::Index needs that.)
177                    Err(_) => {
178                        return Err(Error::new_spanned(
179                            field,
180                            "okay, mate, that are way too many fields. get your life together.",
181                        ))
182                    }
183                };
184                FieldDef::from_field(field, index, container_namespace)
185            }),
186            unknown_attribute_policy,
187            unknown_child_policy,
188            discard,
189        )
190    }
191
192    /// Make and return a set of states which is used to construct the target
193    /// type from XML events.
194    ///
195    /// The states are returned as partial state machine. See the return
196    /// type's documentation for details.
197    pub(crate) fn make_from_events_statemachine(
198        &self,
199        state_ty_ident: &Ident,
200        output_name: &ParentRef,
201        state_prefix: &str,
202    ) -> Result<FromEventsSubmachine> {
203        let scope = FromEventsScope::new(state_ty_ident.clone());
204        let FromEventsScope {
205            ref attrs,
206            ref builder_data_ident,
207            ref text,
208            ref substate_data,
209            ref substate_result,
210            ..
211        } = scope;
212
213        let default_state_ident = quote::format_ident!("{}Default", state_prefix);
214        let discard_state_ident = quote::format_ident!("{}Discard", state_prefix);
215        let builder_data_ty: Type = TypePath {
216            qself: None,
217            path: quote::format_ident!("{}Data{}", state_ty_ident, state_prefix).into(),
218        }
219        .into();
220        let mut states = Vec::new();
221
222        let mut builder_data_def = TokenStream::default();
223        let mut builder_data_init = TokenStream::default();
224        let mut output_cons = TokenStream::default();
225        let mut child_matchers = TokenStream::default();
226        let mut fallback_child_matcher = None;
227        let mut text_handler = if self.discard_text.is_set() {
228            Some(quote! {
229                ::core::result::Result::Ok(::core::ops::ControlFlow::Break(
230                    Self::#default_state_ident { #builder_data_ident }
231                ))
232            })
233        } else {
234            None
235        };
236        let mut extra_defs = TokenStream::default();
237        let is_tuple = !output_name.is_path();
238
239        for (i, field) in self.fields.iter().enumerate() {
240            let member = field.member();
241            let builder_field_name = mangle_member(member);
242            let part = field.make_builder_part(&scope, output_name)?;
243            let state_name = quote::format_ident!("{}Field{}", state_prefix, i);
244
245            match part {
246                FieldBuilderPart::Init {
247                    value: FieldTempInit { ty, init },
248                } => {
249                    builder_data_def.extend(quote! {
250                        #builder_field_name: #ty,
251                    });
252
253                    builder_data_init.extend(quote! {
254                        #builder_field_name: #init,
255                    });
256
257                    if is_tuple {
258                        output_cons.extend(quote! {
259                            #builder_data_ident.#builder_field_name,
260                        });
261                    } else {
262                        output_cons.extend(quote! {
263                            #member: #builder_data_ident.#builder_field_name,
264                        });
265                    }
266                }
267
268                FieldBuilderPart::Text {
269                    value: FieldTempInit { ty, init },
270                    collect,
271                    finalize,
272                } => {
273                    if text_handler.is_some() {
274                        // the existence of only one text handler is enforced
275                        // by Compound's constructor(s).
276                        panic!("more than one field attempts to collect text data");
277                    }
278
279                    builder_data_def.extend(quote! {
280                        #builder_field_name: #ty,
281                    });
282                    builder_data_init.extend(quote! {
283                        #builder_field_name: #init,
284                    });
285                    text_handler = Some(quote! {
286                        #collect
287                        ::core::result::Result::Ok(::core::ops::ControlFlow::Break(
288                            Self::#default_state_ident { #builder_data_ident }
289                        ))
290                    });
291
292                    if is_tuple {
293                        output_cons.extend(quote! {
294                            #finalize,
295                        });
296                    } else {
297                        output_cons.extend(quote! {
298                            #member: #finalize,
299                        });
300                    }
301                }
302
303                FieldBuilderPart::Nested {
304                    extra_defs: field_extra_defs,
305                    value: FieldTempInit { ty, init },
306                    matcher,
307                    builder,
308                    collect,
309                    finalize,
310                } => {
311                    let feed = feed_fn(builder.clone());
312
313                    states.push(State::new_with_builder(
314                        state_name.clone(),
315                        &builder_data_ident,
316                        &builder_data_ty,
317                    ).with_field(
318                        substate_data,
319                        &builder,
320                    ).with_mut(substate_data).with_impl(quote! {
321                        match #feed(&mut #substate_data, ev)? {
322                            ::core::option::Option::Some(#substate_result) => {
323                                #collect
324                                ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#default_state_ident {
325                                    #builder_data_ident,
326                                }))
327                            }
328                            ::core::option::Option::None => {
329                                ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#state_name {
330                                    #builder_data_ident,
331                                    #substate_data,
332                                }))
333                            }
334                        }
335                    }));
336
337                    builder_data_def.extend(quote! {
338                        #builder_field_name: #ty,
339                    });
340
341                    builder_data_init.extend(quote! {
342                        #builder_field_name: #init,
343                    });
344
345                    match matcher {
346                        NestedMatcher::Selective(matcher) => {
347                            child_matchers.extend(quote! {
348                                let (name, attrs) = match #matcher {
349                                    ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch { name, attrs }) => (name, attrs),
350                                    ::core::result::Result::Err(::xso::error::FromEventsError::Invalid(e)) => return ::core::result::Result::Err(e),
351                                    ::core::result::Result::Ok(#substate_data) => {
352                                        return ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#state_name {
353                                            #builder_data_ident,
354                                            #substate_data,
355                                        }))
356                                    }
357                                };
358                            });
359                        }
360                        NestedMatcher::Fallback(matcher) => {
361                            if let Some((span, _)) = fallback_child_matcher.as_ref() {
362                                let mut err = Error::new(
363                                    field.span(),
364                                    "more than one field is attempting to consume all unmatched child elements"
365                                );
366                                err.combine(Error::new(
367                                    *span,
368                                    "the previous field collecting all unmatched child elements is here"
369                                ));
370                                return Err(err);
371                            }
372
373                            let matcher = quote! {
374                                ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#state_name {
375                                    #builder_data_ident,
376                                    #substate_data: { #matcher },
377                                }))
378                            };
379
380                            fallback_child_matcher = Some((field.span(), matcher));
381                        }
382                    }
383
384                    if is_tuple {
385                        output_cons.extend(quote! {
386                            #finalize,
387                        });
388                    } else {
389                        output_cons.extend(quote! {
390                            #member: #finalize,
391                        });
392                    }
393
394                    extra_defs.extend(field_extra_defs);
395                }
396            }
397        }
398
399        let mut discard_attr = TokenStream::default();
400        for (xml_namespace, xml_name) in self.discard_attr.iter() {
401            let xml_namespace = match xml_namespace {
402                Some(v) => v.to_token_stream(),
403                None => quote! {
404                    ::xso::exports::rxml::Namespace::none()
405                },
406            };
407            discard_attr.extend(quote! {
408                let _ = #attrs.remove(#xml_namespace, #xml_name);
409            });
410        }
411
412        let text_handler = match text_handler {
413            Some(v) => v,
414            None => quote! {
415                // note: u8::is_ascii_whitespace includes U+000C, which is not
416                // part of XML's white space definition.'
417                if !::xso::is_xml_whitespace(#text.as_bytes()) {
418                    ::core::result::Result::Err(::xso::error::Error::Other("Unexpected text content".into()))
419                } else {
420                    ::core::result::Result::Ok(::core::ops::ControlFlow::Break(
421                        Self::#default_state_ident { #builder_data_ident }
422                    ))
423                }
424            },
425        };
426
427        let unknown_attr_err = format!("Unknown attribute in {}.", output_name);
428        let unknown_child_err = format!("Unknown child in {}.", output_name);
429        let unknown_child_policy = &self.unknown_child_policy;
430
431        let output_cons = match output_name {
432            ParentRef::Named(ref path) => {
433                quote! {
434                    #path { #output_cons }
435                }
436            }
437            ParentRef::Unnamed { .. } => {
438                quote! {
439                    ( #output_cons )
440                }
441            }
442        };
443
444        let discard_builder_ty = discard_builder_ty(Span::call_site());
445        let discard_feed = feed_fn(discard_builder_ty.clone());
446        let child_fallback = match fallback_child_matcher {
447            Some((_, matcher)) => matcher,
448            None => quote! {
449                let _ = (name, attrs);
450                let _: () = #unknown_child_policy.apply_policy(#unknown_child_err)?;
451                ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#discard_state_ident {
452                    #builder_data_ident,
453                    #substate_data: #discard_builder_ty::new(),
454                }))
455            },
456        };
457
458        states.push(State::new_with_builder(
459            discard_state_ident.clone(),
460            &builder_data_ident,
461            &builder_data_ty,
462        ).with_field(
463            substate_data,
464            &discard_builder_ty,
465        ).with_mut(substate_data).with_impl(quote! {
466            match #discard_feed(&mut #substate_data, ev)? {
467                ::core::option::Option::Some(#substate_result) => {
468                    ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#default_state_ident {
469                        #builder_data_ident,
470                    }))
471                }
472                ::core::option::Option::None => {
473                    ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#discard_state_ident {
474                        #builder_data_ident,
475                        #substate_data,
476                    }))
477                }
478            }
479        }));
480
481        states.push(State::new_with_builder(
482            default_state_ident.clone(),
483            builder_data_ident,
484            &builder_data_ty,
485        ).with_impl(quote! {
486            match ev {
487                // EndElement in Default state -> done parsing.
488                ::xso::exports::rxml::Event::EndElement(_) => {
489                    ::core::result::Result::Ok(::core::ops::ControlFlow::Continue(
490                        #output_cons
491                    ))
492                }
493                ::xso::exports::rxml::Event::StartElement(_, name, attrs) => {
494                    #child_matchers
495                    #child_fallback
496                }
497                ::xso::exports::rxml::Event::Text(_, #text) => {
498                    #text_handler
499                }
500                // we ignore these: a correct parser only generates
501                // them at document start, and there we want to indeed
502                // not worry about them being in front of the first
503                // element.
504                ::xso::exports::rxml::Event::XmlDeclaration(_, ::xso::exports::rxml::XmlVersion::V1_0) => ::core::result::Result::Ok(::core::ops::ControlFlow::Break(
505                    Self::#default_state_ident { #builder_data_ident }
506                ))
507            }
508        }));
509
510        let unknown_attribute_policy = &self.unknown_attribute_policy;
511
512        Ok(FromEventsSubmachine {
513            defs: quote! {
514                #extra_defs
515
516                struct #builder_data_ty {
517                    #builder_data_def
518                }
519            },
520            states,
521            init: quote! {
522                let #builder_data_ident = #builder_data_ty {
523                    #builder_data_init
524                };
525                #discard_attr
526                if #attrs.len() > 0 {
527                    let _: () = #unknown_attribute_policy.apply_policy(#unknown_attr_err)?;
528                }
529                ::core::result::Result::Ok(#state_ty_ident::#default_state_ident { #builder_data_ident })
530            },
531        })
532    }
533
534    /// Make and return a set of states which is used to destructure the
535    /// target type into XML events.
536    ///
537    /// The states are returned as partial state machine. See the return
538    /// type's documentation for details.
539    ///
540    /// **Important:** The returned submachine is not in functional state!
541    /// It's `init` must be modified so that a variable called `name` of type
542    /// `rxml::QName` is in scope.
543    pub(crate) fn make_as_item_iter_statemachine(
544        &self,
545        input_name: &ParentRef,
546        state_ty_ident: &Ident,
547        state_prefix: &str,
548        lifetime: &Lifetime,
549    ) -> Result<AsItemsSubmachine> {
550        let scope = AsItemsScope::new(lifetime, state_ty_ident.clone());
551
552        let element_head_start_state_ident =
553            quote::format_ident!("{}ElementHeadStart", state_prefix);
554        let element_head_end_state_ident = quote::format_ident!("{}ElementHeadEnd", state_prefix);
555        let element_foot_state_ident = quote::format_ident!("{}ElementFoot", state_prefix);
556        let name_ident = quote::format_ident!("name");
557        let ns_ident = quote::format_ident!("ns");
558        let dummy_ident = quote::format_ident!("dummy");
559        let mut states = Vec::new();
560
561        let is_tuple = !input_name.is_path();
562        let mut destructure = TokenStream::default();
563        let mut start_init = TokenStream::default();
564        let mut extra_defs = TokenStream::default();
565
566        states.push(
567            State::new(element_head_start_state_ident.clone())
568                .with_field(&dummy_ident, &phantom_lifetime_ty(lifetime.clone()))
569                .with_field(&ns_ident, &namespace_ty(Span::call_site()))
570                .with_field(
571                    &name_ident,
572                    &ncnamestr_cow_ty(Span::call_site(), lifetime.clone()),
573                ),
574        );
575
576        let mut element_head_end_idx = states.len();
577        states.push(
578            State::new(element_head_end_state_ident.clone()).with_impl(quote! {
579                ::core::option::Option::Some(::xso::Item::ElementHeadEnd)
580            }),
581        );
582
583        for (i, field) in self.fields.iter().enumerate() {
584            let member = field.member();
585            let bound_name = mangle_member(member);
586            let part = field.make_iterator_part(&scope, input_name, &bound_name)?;
587            let state_name = quote::format_ident!("{}Field{}", state_prefix, i);
588            let ty = scope.borrow(field.ty().clone());
589
590            match part {
591                FieldIteratorPart::Header { generator } => {
592                    // we have to make sure that we carry our data around in
593                    // all the previous states.
594                    for state in &mut states[..element_head_end_idx] {
595                        state.add_field(&bound_name, &ty);
596                    }
597                    states.insert(
598                        element_head_end_idx,
599                        State::new(state_name)
600                            .with_field(&bound_name, &ty)
601                            .with_impl(quote! {
602                                #generator
603                            }),
604                    );
605                    element_head_end_idx += 1;
606
607                    if is_tuple {
608                        destructure.extend(quote! {
609                            ref #bound_name,
610                        });
611                    } else {
612                        destructure.extend(quote! {
613                            #member: ref #bound_name,
614                        });
615                    }
616                    start_init.extend(quote! {
617                        #bound_name,
618                    });
619                }
620
621                FieldIteratorPart::Text { generator } => {
622                    // we have to make sure that we carry our data around in
623                    // all the previous states.
624                    for state in states.iter_mut() {
625                        state.add_field(&bound_name, &ty);
626                    }
627                    states.push(
628                        State::new(state_name)
629                            .with_field(&bound_name, &ty)
630                            .with_impl(quote! {
631                                #generator.map(|value| ::xso::Item::Text(
632                                    value,
633                                ))
634                            }),
635                    );
636                    if is_tuple {
637                        destructure.extend(quote! {
638                            #bound_name,
639                        });
640                    } else {
641                        destructure.extend(quote! {
642                            #member: #bound_name,
643                        });
644                    }
645                    start_init.extend(quote! {
646                        #bound_name,
647                    });
648                }
649
650                FieldIteratorPart::Content {
651                    extra_defs: field_extra_defs,
652                    value: FieldTempInit { ty, init },
653                    generator,
654                } => {
655                    // we have to make sure that we carry our data around in
656                    // all the previous states.
657                    for state in states.iter_mut() {
658                        state.add_field(&bound_name, &ty);
659                    }
660
661                    states.push(
662                        State::new(state_name.clone())
663                            .with_field(&bound_name, &ty)
664                            .with_mut(&bound_name)
665                            .with_impl(quote! {
666                                #generator?
667                            }),
668                    );
669                    if is_tuple {
670                        destructure.extend(quote! {
671                            #bound_name,
672                        });
673                    } else {
674                        destructure.extend(quote! {
675                            #member: #bound_name,
676                        });
677                    }
678                    start_init.extend(quote! {
679                        #bound_name: #init,
680                    });
681
682                    extra_defs.extend(field_extra_defs);
683                }
684            }
685        }
686
687        states[0].set_impl(quote! {
688            {
689                ::core::option::Option::Some(::xso::Item::ElementHeadStart(
690                    #ns_ident,
691                    #name_ident,
692                ))
693            }
694        });
695
696        states.push(
697            State::new(element_foot_state_ident.clone()).with_impl(quote! {
698                ::core::option::Option::Some(::xso::Item::ElementFoot)
699            }),
700        );
701
702        let destructure = match input_name {
703            ParentRef::Named(ref input_path) => quote! {
704                #input_path { #destructure }
705            },
706            ParentRef::Unnamed { .. } => quote! {
707                ( #destructure )
708            },
709        };
710
711        Ok(AsItemsSubmachine {
712            defs: extra_defs,
713            states,
714            destructure,
715            init: quote! {
716                Self::#element_head_start_state_ident { #dummy_ident: ::core::marker::PhantomData, #name_ident: name.1, #ns_ident: name.0, #start_init }
717            },
718        })
719    }
720
721    /// Return a reference to this compound's only field's type.
722    ///
723    /// If the compound does not have exactly one field, this function returns
724    /// None.
725    pub(crate) fn single_ty(&self) -> Option<&Type> {
726        if self.fields.len() > 1 {
727            return None;
728        }
729        self.fields.get(0).map(|x| x.ty())
730    }
731
732    /// Construct a tuple type with this compound's field's types in the same
733    /// order as they appear in the compound.
734    pub(crate) fn to_tuple_ty(&self) -> TypeTuple {
735        TypeTuple {
736            paren_token: token::Paren::default(),
737            elems: self.fields.iter().map(|x| x.ty().clone()).collect(),
738        }
739    }
740
741    /// Construct a tuple type with this compound's field's types in the same
742    /// order as they appear in the compound.
743    pub(crate) fn to_single_or_tuple_ty(&self) -> Type {
744        match self.single_ty() {
745            None => self.to_tuple_ty().into(),
746            Some(v) => v.clone(),
747        }
748    }
749
750    /// Construct a tuple type with references to this compound's field's
751    /// types in the same order as they appear in the compound, with the given
752    /// lifetime.
753    pub(crate) fn to_ref_tuple_ty(&self, lifetime: &Lifetime) -> TypeTuple {
754        TypeTuple {
755            paren_token: token::Paren::default(),
756            elems: self
757                .fields
758                .iter()
759                .map(|x| ref_ty(x.ty().clone(), lifetime.clone()))
760                .collect(),
761        }
762    }
763
764    /// Return the number of fields in this compound.
765    pub(crate) fn field_count(&self) -> usize {
766        self.fields.len()
767    }
768}