diff --git a/xso-proc/src/field.rs b/xso-proc/src/field.rs index 083d0ef0..0fe088e2 100644 --- a/xso-proc/src/field.rs +++ b/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 for Option`, which creates a + // `Some(_)` from a `T`. Why is it so great? Because there is also + // `impl From> for Option` (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` + // if the field is of type `Option`, and it does the right + // thing no matter whether the extracted field is of type + // `Option` 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`: 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 for Option`. 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 for Option`, which creates a - // `Some(_)` from a `T`. Why is it so great? - // Because there is also `impl From> for - // Option` (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` if the field is of - // type `Option`, and it does the right thing - // no matter whether the extracted field is of - // type `Option` 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`: - // 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 for Option`. 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());