xso-proc: pull out the extract code into a separate impl

Jonas Schäfer created

This should make everything a little easier to read, because it reduces
the level of indentation involved.

Change summary

xso-proc/src/field.rs | 286 ++++++++++++++++++++++++--------------------
1 file changed, 158 insertions(+), 128 deletions(-)

Detailed changes

xso-proc/src/field.rs 🔗

@@ -160,6 +160,150 @@ struct ExtractDef {
     parts: Compound,
 }
 
+impl ExtractDef {
+    /// Construct
+    /// [`FieldBuilderPart::Nested::extra_defs`],
+    /// [`FieldBuilderPart::Nested::matcher`],
+    /// an expression which pulls the extraction result from
+    /// `substate_result`,
+    /// and the [`FieldBuilderPart::Nested::builder`] type.
+    fn make_from_xml_builder_parts(
+        &self,
+        scope: &FromEventsScope,
+        container_name: &ParentRef,
+        member: &Member,
+    ) -> Result<(TokenStream, TokenStream, TokenStream, Type)> {
+        let FromEventsScope {
+            ref substate_result,
+            ..
+        } = scope;
+
+        let xml_namespace = &self.xml_namespace;
+        let xml_name = &self.xml_name;
+
+        let from_xml_builder_ty_ident = scope.make_member_type_name(member, "FromXmlBuilder");
+        let state_ty_ident = quote::format_ident!("{}State", from_xml_builder_ty_ident,);
+
+        let extra_defs = self.parts.make_from_events_statemachine(
+            &state_ty_ident,
+            &container_name.child(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,
+            &self.parts.to_tuple_ty().into(),
+        )?;
+        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))) };
+
+        Ok((
+            extra_defs,
+            matcher,
+            // This little ".into()" here goes a long way. It relies on one of
+            // the most underrated trait implementations in the standard
+            // library: `impl From<T> for Option<T>`, which creates a
+            // `Some(_)` from a `T`. Why is it so great? Because there is also
+            // `impl From<Option<T>> for Option<T>` (obviously), which is just
+            // a move. So even without knowing the exact type of the substate
+            // result and the field, we can make an "downcast" to `Option<T>`
+            // if the field is of type `Option<T>`, and it does the right
+            // thing no matter whether the extracted field is of type
+            // `Option<T>` or `T`.
+            //
+            // And then, type inferrence does the rest: There is ambiguity
+            // there, of course, if we call `.into()` on a value of type
+            // `Option<T>`: Should Rust wrap it into another layer of
+            // `Option`, or should it just move the value? The answer lies in
+            // the type constraint imposed by the place the value is *used*,
+            // which is strictly bound by the field's type (so there is, in
+            // fact, no ambiguity). So this works all kinds of magic.
+            quote! { #substate_result.0.into() },
+            from_xml_builder_ty,
+        ))
+    }
+
+    /// Construct
+    /// [`FieldIteratorPart::Content::extra_defs`],
+    /// the [`FieldIteratorPart::Content::value`] init,
+    /// and the iterator type.
+    fn make_as_item_iter_parts(
+        &self,
+        scope: &AsItemsScope,
+        container_name: &ParentRef,
+        bound_name: &Ident,
+        member: &Member,
+    ) -> Result<(TokenStream, TokenStream, Type)> {
+        let AsItemsScope { ref lifetime, .. } = scope;
+
+        let xml_namespace = &self.xml_namespace;
+        let xml_name = &self.xml_name;
+
+        let item_iter_ty_ident = scope.make_member_type_name(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 = self
+            .parts
+            .make_as_item_iter_statemachine(
+                &container_name.child(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,
+                &self.parts.to_ref_tuple_ty(lifetime).into(),
+                &state_ty_ident,
+                lifetime,
+                &item_iter_ty,
+            )?;
+
+        let item_iter_ty = option_as_xml_ty(item_iter_ty);
+        Ok((
+            extra_defs,
+            // Again we exploit the extreme usefulness of the
+            // `impl From<T> for Option<T>`. We already wrote extensively
+            // about that in [`make_from_xml_builder_parts`] implementation
+            // corresponding to this code above, and we will not repeat it
+            // here.
+            quote! {
+                ::xso::OptionAsXml::new(::core::option::Option::from(#bound_name).map(|#bound_name| {
+                    #item_iter_ty_ident::new((#bound_name,))
+                }).transpose()?)
+            },
+            item_iter_ty,
+        ))
+    }
+}
+
 /// Specify how the field is mapped to XML.
 enum FieldKind {
     /// The field maps to an attribute.
@@ -530,81 +674,21 @@ impl FieldDef {
                 ref amount,
                 ref extract,
             } => {
-                let FromEventsScope {
-                    ref substate_result,
-                    ..
-                } = scope;
-                let field_access = scope.access_field(&self.member);
-
                 let element_ty = match amount {
                     AmountConstraint::FixedSingle(_) => self.ty.clone(),
                     AmountConstraint::Any(_) => into_iterator_item_ty(self.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,
-                            &parts.to_tuple_ty().into(),
-                        )?;
-                        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,
-                            // This little ".into()" here goes a long way. It
-                            // relies on one of the most underrated trait
-                            // implementations in the standard library:
-                            // `impl From<T> for Option<T>`, which creates a
-                            // `Some(_)` from a `T`. Why is it so great?
-                            // Because there is also `impl From<Option<T>> for
-                            // Option<T>` (obviously), which is just a move.
-                            // So even without knowing the exact type of the
-                            // substate result and the field, we can make an
-                            // "downcast" to `Option<T>` if the field is of
-                            // type `Option<T>`, and it does the right thing
-                            // no matter whether the extracted field is of
-                            // type `Option<T>` or `T`.
-                            //
-                            // And then, type inferrence does the rest: There
-                            // is ambiguity there, of course, if we call
-                            // `.into()` on a value of type `Option<T>`:
-                            // Should Rust wrap it into another layer of
-                            // `Option`, or should it just move the value? The
-                            // answer lies in the type constraint imposed by
-                            // the place the value is *used*, which is
-                            // strictly bound by the field's type (so there
-                            // is, in fact, no ambiguity). So this works all
-                            // kinds of magic.
-                            quote! { #substate_result.0.into() },
-                            from_xml_builder_ty,
-                        )
+                    Some(extract) => {
+                        extract.make_from_xml_builder_parts(scope, container_name, &self.member)?
                     }
                     None => {
+                        let FromEventsScope {
+                            ref substate_result,
+                            ..
+                        } = scope;
+
                         let from_events = from_events_fn(element_ty.clone());
                         let from_xml_builder = from_xml_builder_ty(element_ty.clone());
 
@@ -620,6 +704,7 @@ impl FieldDef {
                     }
                 };
 
+                let field_access = scope.access_field(&self.member);
                 match amount {
                     AmountConstraint::FixedSingle(_) => {
                         let missing_msg =
@@ -757,67 +842,12 @@ impl FieldDef {
                 };
 
                 let (extra_defs, init, 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,
-                                &parts.to_ref_tuple_ty(lifetime).into(),
-                                &state_ty_ident,
-                                lifetime,
-                                &item_iter_ty,
-                            )?;
-
-                        let item_iter_ty = option_as_xml_ty(item_iter_ty);
-                        (
-                            extra_defs,
-                            // Again we exploit the extreme usefulness of the
-                            // `impl From<T> for Option<T>`. We already wrote
-                            // extensively about that in the FromXml
-                            // implementation corresponding to this code
-                            // above, and we will not repeat it here.
-                            quote! {
-                                ::xso::OptionAsXml::new(::core::option::Option::from(#bound_name).map(|#bound_name| {
-                                    #item_iter_ty_ident::new((#bound_name,))
-                                }).transpose()?)
-                            },
-                            item_iter_ty,
-                        )
-                    }
+                    Some(extract) => extract.make_as_item_iter_parts(
+                        scope,
+                        container_name,
+                        bound_name,
+                        &self.member,
+                    )?,
                     None => {
                         let as_xml_iter = as_xml_iter_fn(item_ty.clone());
                         let item_iter = item_iter_ty(item_ty.clone(), lifetime.clone());