xso-proc: merge extract and normal child code

Jonas Schäfer created

Change summary

xso-proc/src/field.rs | 463 +++++++++++++++++++++-----------------------
1 file changed, 226 insertions(+), 237 deletions(-)

Detailed changes

xso-proc/src/field.rs 🔗

@@ -140,6 +140,26 @@ pub(crate) enum FieldIteratorPart {
     },
 }
 
+/// Definition of what to extract from a child element.
+struct ExtractDef {
+    /// The XML namespace of the child to extract data from.
+    xml_namespace: NamespaceRef,
+
+    /// The XML name of the child to extract data from.
+    xml_name: NameRef,
+
+    /// Compound which contains the arguments of the `extract(..)` meta
+    /// (except the `from`), transformed into a struct with unnamed
+    /// fields.
+    ///
+    /// This is used to generate the parsing/serialisation code, by
+    /// essentially "declaring" a shim struct, as if it were a real Rust
+    /// struct, and using the result of the parsing process directly for
+    /// the field on which the `extract(..)` option was used, instead of
+    /// putting it into a Rust struct.
+    parts: Compound,
+}
+
 /// Specify how the field is mapped to XML.
 enum FieldKind {
     /// The field maps to an attribute.
@@ -169,26 +189,10 @@ enum FieldKind {
 
         /// Number of child elements allowed.
         amount: AmountConstraint,
-    },
-
-    /// Extract contents from a child element.
-    Extract {
-        /// The XML namespace of the child to extract data from.
-        xml_namespace: NamespaceRef,
 
-        /// The XML name of the child to extract data from.
-        xml_name: NameRef,
-
-        /// Compound which contains the arguments of the `extract(..)` meta
-        /// (except the `from`), transformed into a struct with unnamed
-        /// fields.
-        ///
-        /// This is used to generate the parsing/serialisation code, by
-        /// essentially "declaring" a shim struct, as if it were a real Rust
-        /// struct, and using the result of the parsing process directly for
-        /// the field on which the `extract(..)` option was used, instead of
-        /// putting it into a Rust struct.
-        parts: Compound,
+        /// If set, the child element is not parsed as a field implementing
+        /// `FromXml` / `AsXml`, but instead its contents are extracted.
+        extract: Option<ExtractDef>,
     },
 }
 
@@ -269,6 +273,7 @@ impl FieldKind {
                 Ok(Self::Child {
                     default_,
                     amount: amount.unwrap_or(AmountConstraint::FixedSingle(Span::call_site())),
+                    extract: None,
                 })
             }
 
@@ -303,10 +308,14 @@ impl FieldKind {
                     [FieldDef::from_extract(field, 0, field_ty, &xml_namespace)].into_iter(),
                 )?;
 
-                Ok(Self::Extract {
-                    xml_namespace,
-                    xml_name,
-                    parts,
+                Ok(Self::Child {
+                    default_: Flag::Absent,
+                    amount: AmountConstraint::FixedSingle(Span::call_site()),
+                    extract: Some(ExtractDef {
+                        xml_namespace,
+                        xml_name,
+                        parts,
+                    }),
                 })
             }
         }
@@ -475,6 +484,7 @@ impl FieldDef {
             FieldKind::Child {
                 ref default_,
                 ref amount,
+                ref extract,
             } => {
                 let FromEventsScope {
                     ref substate_result,
@@ -487,11 +497,65 @@ impl FieldDef {
                     AmountConstraint::Any(_) => into_iterator_item_ty(self.ty.clone()),
                 };
 
-                let from_events = from_events_fn(element_ty.clone());
-                let from_xml_builder = from_xml_builder_ty(element_ty.clone());
+                let (extra_defs, matcher, fetch, builder) = match extract {
+                    Some(ExtractDef {
+                        ref xml_namespace,
+                        ref xml_name,
+                        ref parts,
+                    }) => {
+                        let from_xml_builder_ty_ident =
+                            scope.make_member_type_name(&self.member, "FromXmlBuilder");
+                        let state_ty_ident =
+                            quote::format_ident!("{}State", from_xml_builder_ty_ident,);
+
+                        let extra_defs = parts.make_from_events_statemachine(
+                            &state_ty_ident,
+                            &container_name.child(self.member.clone()),
+                            "",
+                        )?.with_augmented_init(|init| quote! {
+                            if name.0 == #xml_namespace && name.1 == #xml_name {
+                                #init
+                            } else {
+                                ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch { name, attrs })
+                            }
+                        }).compile().render(
+                            &Visibility::Inherited,
+                            &from_xml_builder_ty_ident,
+                            &state_ty_ident,
+                            &Type::Tuple(TypeTuple {
+                                paren_token: token::Paren::default(),
+                                elems: [
+                                    element_ty.clone(),
+                                ].into_iter().collect(),
+                            })
+                        )?;
+                        let from_xml_builder_ty =
+                            ty_from_ident(from_xml_builder_ty_ident.clone()).into();
+
+                        let matcher = quote! { #state_ty_ident::new(name, attrs).map(|x| #from_xml_builder_ty_ident(::core::option::Option::Some(x))) };
+
+                        (
+                            extra_defs,
+                            matcher,
+                            quote! { #substate_result.0 },
+                            from_xml_builder_ty,
+                        )
+                    }
+                    None => {
+                        let from_events = from_events_fn(element_ty.clone());
+                        let from_xml_builder = from_xml_builder_ty(element_ty.clone());
+
+                        let matcher = quote! { #from_events(name, attrs) };
+                        let builder = from_xml_builder;
 
-                let matcher = quote! { #from_events(name, attrs) };
-                let builder = from_xml_builder;
+                        (
+                            TokenStream::default(),
+                            matcher,
+                            quote! { #substate_result },
+                            builder,
+                        )
+                    }
+                };
 
                 match amount {
                     AmountConstraint::FixedSingle(_) => {
@@ -505,7 +569,7 @@ impl FieldDef {
                                 return ::core::result::Result::Err(::xso::error::Error::Other(#missing_msg).into())
                             },
                             Flag::Present(_) => {
-                                let default_ = default_fn(self.ty.clone());
+                                let default_ = default_fn(element_ty.clone());
                                 quote! {
                                     #default_()
                                 }
@@ -513,7 +577,7 @@ impl FieldDef {
                         };
 
                         Ok(FieldBuilderPart::Nested {
-                            extra_defs: TokenStream::default(),
+                            extra_defs,
                             value: FieldTempInit {
                                 init: quote! { ::std::option::Option::None },
                                 ty: option_ty(self.ty.clone()),
@@ -530,7 +594,7 @@ impl FieldDef {
                             },
                             builder,
                             collect: quote! {
-                                #field_access = ::std::option::Option::Some(#substate_result);
+                                #field_access = ::std::option::Option::Some(#fetch);
                             },
                             finalize: quote! {
                                 match #field_access {
@@ -544,7 +608,7 @@ impl FieldDef {
                         let ty_extend = extend_fn(self.ty.clone(), element_ty.clone());
                         let ty_default = default_fn(self.ty.clone());
                         Ok(FieldBuilderPart::Nested {
-                            extra_defs: TokenStream::default(),
+                            extra_defs,
                             value: FieldTempInit {
                                 init: quote! { #ty_default() },
                                 ty: self.ty.clone(),
@@ -552,87 +616,13 @@ impl FieldDef {
                             matcher,
                             builder,
                             collect: quote! {
-                                #ty_extend(&mut #field_access, [#substate_result]);
+                                #ty_extend(&mut #field_access, [#fetch]);
                             },
                             finalize: quote! { #field_access },
                         })
                     }
                 }
             }
-
-            FieldKind::Extract {
-                ref xml_namespace,
-                ref xml_name,
-                ref parts,
-            } => {
-                let FromEventsScope {
-                    ref substate_result,
-                    ..
-                } = scope;
-                let field_access = scope.access_field(&self.member);
-
-                let missing_msg = error_message::on_missing_child(container_name, &self.member);
-                let duplicate_msg = error_message::on_duplicate_child(container_name, &self.member);
-
-                let on_absent = quote! {
-                    return ::core::result::Result::Err(::xso::error::Error::Other(#missing_msg).into())
-                };
-
-                let from_xml_builder_ty_ident =
-                    scope.make_member_type_name(&self.member, "FromXmlBuilder");
-                let state_ty_ident = quote::format_ident!("{}State", from_xml_builder_ty_ident,);
-
-                let extra_defs = parts.make_from_events_statemachine(
-                    &state_ty_ident,
-                    &container_name.child(self.member.clone()),
-                    "",
-                )?.with_augmented_init(|init| quote! {
-                    if name.0 == #xml_namespace && name.1 == #xml_name {
-                        #init
-                    } else {
-                        ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch { name, attrs })
-                    }
-                }).compile().render(
-                    &Visibility::Inherited,
-                    &from_xml_builder_ty_ident,
-                    &state_ty_ident,
-                    &Type::Tuple(TypeTuple {
-                        paren_token: token::Paren::default(),
-                        elems: [
-                            self.ty.clone(),
-                        ].into_iter().collect(),
-                    })
-                )?;
-                let from_xml_builder_ty = ty_from_ident(from_xml_builder_ty_ident.clone()).into();
-
-                Ok(FieldBuilderPart::Nested {
-                    extra_defs,
-                    value: FieldTempInit {
-                        init: quote! { ::std::option::Option::None },
-                        ty: option_ty(self.ty.clone()),
-                    },
-                    matcher: quote! {
-                        match #state_ty_ident::new(name, attrs) {
-                            ::core::result::Result::Ok(v) => if #field_access.is_some() {
-                                ::core::result::Result::Err(::xso::error::FromEventsError::Invalid(::xso::error::Error::Other(#duplicate_msg)))
-                            } else {
-                                ::core::result::Result::Ok(#from_xml_builder_ty_ident(::core::option::Option::Some(v)))
-                            },
-                            ::core::result::Result::Err(e) => ::core::result::Result::Err(e),
-                        }
-                    },
-                    builder: from_xml_builder_ty,
-                    collect: quote! {
-                        #field_access = ::std::option::Option::Some(#substate_result.0);
-                    },
-                    finalize: quote! {
-                        match #field_access {
-                            ::std::option::Option::Some(value) => value,
-                            ::std::option::Option::None => #on_absent,
-                        }
-                    },
-                })
-            }
         }
     }
 
@@ -689,147 +679,146 @@ impl FieldDef {
 
             FieldKind::Child {
                 default_: _,
-                amount: AmountConstraint::FixedSingle(_),
-            } => {
-                let AsItemsScope { ref lifetime, .. } = scope;
-
-                let as_xml_iter = as_xml_iter_fn(self.ty.clone());
-                let item_iter = item_iter_ty(self.ty.clone(), lifetime.clone());
-
-                Ok(FieldIteratorPart::Content {
-                    extra_defs: TokenStream::default(),
-                    value: FieldTempInit {
-                        init: quote! {
-                            #as_xml_iter(#bound_name)?
-                        },
-                        ty: item_iter,
-                    },
-                    generator: quote! {
-                        #bound_name.next().transpose()
-                    },
-                })
-            }
-
-            FieldKind::Child {
-                default_: _,
-                amount: AmountConstraint::Any(_),
+                ref amount,
+                ref extract,
             } => {
                 let AsItemsScope { ref lifetime, .. } = scope;
 
-                // This should give us the type of element stored in the
-                // collection.
-                let element_ty = into_iterator_item_ty(self.ty.clone());
-
-                // And this is the collection type we actually work with --
-                // as_xml_iter uses references after all.
-                let ty = ref_ty(self.ty.clone(), lifetime.clone());
+                let item_ty = match amount {
+                    AmountConstraint::FixedSingle(_) => self.ty.clone(),
+                    AmountConstraint::Any(_) => {
+                        // This should give us the type of element stored in the
+                        // collection.
+                        into_iterator_item_ty(self.ty.clone())
+                    }
+                };
 
-                // as_xml_iter is called on the bare type (not the ref type)
-                let as_xml_iter = as_xml_iter_fn(element_ty.clone());
+                let (extra_defs, fetch, as_xml_iter, iter_ty) = match extract {
+                    Some(ExtractDef {
+                        ref xml_namespace,
+                        ref xml_name,
+                        ref parts,
+                    }) => {
+                        let item_iter_ty_ident =
+                            scope.make_member_type_name(&self.member, "AsXmlIterator");
+                        let state_ty_ident = quote::format_ident!("{}State", item_iter_ty_ident,);
+                        let mut item_iter_ty = ty_from_ident(item_iter_ty_ident.clone());
+                        item_iter_ty.path.segments[0].arguments =
+                            PathArguments::AngleBracketed(AngleBracketedGenericArguments {
+                                colon2_token: None,
+                                lt_token: token::Lt::default(),
+                                args: [GenericArgument::Lifetime(lifetime.clone())]
+                                    .into_iter()
+                                    .collect(),
+                                gt_token: token::Gt::default(),
+                            });
+                        let item_iter_ty = item_iter_ty.into();
+
+                        let extra_defs = parts
+                            .make_as_item_iter_statemachine(
+                                &container_name.child(self.member.clone()),
+                                &state_ty_ident,
+                                "",
+                                lifetime,
+                            )?
+                            .with_augmented_init(|init| {
+                                quote! {
+                                    let name = (
+                                        ::xso::exports::rxml::Namespace::from(#xml_namespace),
+                                        ::std::borrow::Cow::Borrowed(#xml_name),
+                                    );
+                                    #init
+                                }
+                            })
+                            .compile()
+                            .render(
+                                &Visibility::Inherited,
+                                &Type::Tuple(TypeTuple {
+                                    paren_token: token::Paren::default(),
+                                    elems: [ref_ty(item_ty.clone(), lifetime.clone())]
+                                        .into_iter()
+                                        .collect(),
+                                }),
+                                &state_ty_ident,
+                                lifetime,
+                                &item_iter_ty,
+                            )?;
+
+                        (
+                            extra_defs,
+                            quote! { (&#bound_name,) },
+                            quote! { #item_iter_ty_ident::new },
+                            item_iter_ty,
+                        )
+                    }
+                    None => {
+                        let as_xml_iter = as_xml_iter_fn(item_ty.clone());
+                        let item_iter = item_iter_ty(item_ty.clone(), lifetime.clone());
+
+                        (
+                            TokenStream::default(),
+                            quote! { #bound_name },
+                            quote! { #as_xml_iter },
+                            item_iter,
+                        )
+                    }
+                };
 
-                // And thus the iterator associated with AsXml is also derived
-                // from the bare type.
-                let item_iter = item_iter_ty(element_ty.clone(), lifetime.clone());
+                match amount {
+                    AmountConstraint::FixedSingle(_) => Ok(FieldIteratorPart::Content {
+                        extra_defs,
+                        value: FieldTempInit {
+                            init: quote! {
+                                #as_xml_iter(#fetch)?
+                            },
+                            ty: iter_ty,
+                        },
+                        generator: quote! {
+                            #bound_name.next().transpose()
+                        },
+                    }),
+                    AmountConstraint::Any(_) => {
+                        // This is the collection type we actually work
+                        // with -- as_xml_iter uses references after all.
+                        let ty = ref_ty(self.ty.clone(), lifetime.clone());
 
-                // But the iterator for iterating over the elements inside the
-                // collection must use the ref type.
-                let element_iter = into_iterator_iter_ty(ty.clone());
+                        // But the iterator for iterating over the elements
+                        // inside the collection must use the ref type.
+                        let element_iter = into_iterator_iter_ty(ty.clone());
 
-                // And likewise the into_iter impl.
-                let into_iter = into_iterator_into_iter_fn(ty.clone());
+                        // And likewise the into_iter impl.
+                        let into_iter = into_iterator_into_iter_fn(ty.clone());
 
-                let state_ty = Type::Tuple(TypeTuple {
-                    paren_token: token::Paren::default(),
-                    elems: [element_iter, option_ty(item_iter)].into_iter().collect(),
-                });
+                        let state_ty = Type::Tuple(TypeTuple {
+                            paren_token: token::Paren::default(),
+                            elems: [element_iter, option_ty(iter_ty)].into_iter().collect(),
+                        });
 
-                Ok(FieldIteratorPart::Content {
-                    extra_defs: TokenStream::default(),
-                    value: FieldTempInit {
-                        init: quote! {
-                            (#into_iter(#bound_name), ::core::option::Option::None)
-                        },
-                        ty: state_ty,
-                    },
-                    generator: quote! {
-                        loop {
-                            if let ::core::option::Option::Some(current) = #bound_name.1.as_mut() {
-                                if let ::core::option::Option::Some(item) = current.next() {
-                                    break ::core::option::Option::Some(item).transpose();
+                        Ok(FieldIteratorPart::Content {
+                            extra_defs,
+                            value: FieldTempInit {
+                                init: quote! {
+                                    (#into_iter(#bound_name), ::core::option::Option::None)
+                                },
+                                ty: state_ty,
+                            },
+                            generator: quote! {
+                                loop {
+                                    if let ::core::option::Option::Some(current) = #bound_name.1.as_mut() {
+                                        if let ::core::option::Option::Some(item) = current.next() {
+                                            break ::core::option::Option::Some(item).transpose();
+                                        }
+                                    }
+                                    if let ::core::option::Option::Some(item) = #bound_name.0.next() {
+                                        #bound_name.1 = ::core::option::Option::Some(#as_xml_iter({ let #bound_name = item; #fetch })?)
+                                    } else {
+                                        break ::core::result::Result::Ok(::core::option::Option::None)
+                                    }
                                 }
-                            }
-                            if let ::core::option::Option::Some(item) = #bound_name.0.next() {
-                                #bound_name.1 = ::core::option::Option::Some(#as_xml_iter(item)?)
-                            } else {
-                                break ::core::result::Result::Ok(::core::option::Option::None)
-                            }
-                        }
-                    },
-                })
-            }
-
-            FieldKind::Extract {
-                ref xml_namespace,
-                ref xml_name,
-                ref parts,
-            } => {
-                let AsItemsScope { ref lifetime, .. } = scope;
-                let item_iter_ty_ident = scope.make_member_type_name(&self.member, "AsXmlIterator");
-                let state_ty_ident = quote::format_ident!("{}State", item_iter_ty_ident,);
-                let mut item_iter_ty = ty_from_ident(item_iter_ty_ident.clone());
-                item_iter_ty.path.segments[0].arguments =
-                    PathArguments::AngleBracketed(AngleBracketedGenericArguments {
-                        colon2_token: None,
-                        lt_token: token::Lt::default(),
-                        args: [GenericArgument::Lifetime(lifetime.clone())]
-                            .into_iter()
-                            .collect(),
-                        gt_token: token::Gt::default(),
-                    });
-                let item_iter_ty = item_iter_ty.into();
-
-                let extra_defs = parts
-                    .make_as_item_iter_statemachine(
-                        &container_name.child(self.member.clone()),
-                        &state_ty_ident,
-                        "",
-                        lifetime,
-                    )?
-                    .with_augmented_init(|init| {
-                        quote! {
-                            let name = (
-                                ::xso::exports::rxml::Namespace::from(#xml_namespace),
-                                ::std::borrow::Cow::Borrowed(#xml_name),
-                            );
-                            #init
-                        }
-                    })
-                    .compile()
-                    .render(
-                        &Visibility::Inherited,
-                        &Type::Tuple(TypeTuple {
-                            paren_token: token::Paren::default(),
-                            elems: [ref_ty(self.ty.clone(), lifetime.clone())]
-                                .into_iter()
-                                .collect(),
-                        }),
-                        &state_ty_ident,
-                        lifetime,
-                        &item_iter_ty,
-                    )?;
-
-                Ok(FieldIteratorPart::Content {
-                    extra_defs,
-                    value: FieldTempInit {
-                        init: quote! {
-                            #item_iter_ty_ident::new((&#bound_name,))?
-                        },
-                        ty: item_iter_ty,
-                    },
-                    generator: quote! {
-                        #bound_name.next().transpose()
-                    },
-                })
+                            },
+                        })
+                    }
+                }
             }
         }
     }