xso-proc: pull out the extract code into a separate impl
This should make everything a little easier to read, because it reduces the level of indentation involved.
This commit is contained in:
parent
f466b18622
commit
b0996e3a35
1 changed files with 158 additions and 128 deletions
|
@ -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());
|
||||
|
|
Loading…
Reference in a new issue