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,
|
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.
|
/// Specify how the field is mapped to XML.
|
||||||
enum FieldKind {
|
enum FieldKind {
|
||||||
/// The field maps to an attribute.
|
/// The field maps to an attribute.
|
||||||
|
|
@ -530,81 +674,21 @@ impl FieldDef {
|
||||||
ref amount,
|
ref amount,
|
||||||
ref extract,
|
ref extract,
|
||||||
} => {
|
} => {
|
||||||
let FromEventsScope {
|
|
||||||
ref substate_result,
|
|
||||||
..
|
|
||||||
} = scope;
|
|
||||||
let field_access = scope.access_field(&self.member);
|
|
||||||
|
|
||||||
let element_ty = match amount {
|
let element_ty = match amount {
|
||||||
AmountConstraint::FixedSingle(_) => self.ty.clone(),
|
AmountConstraint::FixedSingle(_) => self.ty.clone(),
|
||||||
AmountConstraint::Any(_) => into_iterator_item_ty(self.ty.clone()),
|
AmountConstraint::Any(_) => into_iterator_item_ty(self.ty.clone()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let (extra_defs, matcher, fetch, builder) = match extract {
|
let (extra_defs, matcher, fetch, builder) = match extract {
|
||||||
Some(ExtractDef {
|
Some(extract) => {
|
||||||
ref xml_namespace,
|
extract.make_from_xml_builder_parts(scope, container_name, &self.member)?
|
||||||
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,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
|
let FromEventsScope {
|
||||||
|
ref substate_result,
|
||||||
|
..
|
||||||
|
} = scope;
|
||||||
|
|
||||||
let from_events = from_events_fn(element_ty.clone());
|
let from_events = from_events_fn(element_ty.clone());
|
||||||
let from_xml_builder = from_xml_builder_ty(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 {
|
match amount {
|
||||||
AmountConstraint::FixedSingle(_) => {
|
AmountConstraint::FixedSingle(_) => {
|
||||||
let missing_msg =
|
let missing_msg =
|
||||||
|
|
@ -757,67 +842,12 @@ impl FieldDef {
|
||||||
};
|
};
|
||||||
|
|
||||||
let (extra_defs, init, iter_ty) = match extract {
|
let (extra_defs, init, iter_ty) = match extract {
|
||||||
Some(ExtractDef {
|
Some(extract) => extract.make_as_item_iter_parts(
|
||||||
ref xml_namespace,
|
scope,
|
||||||
ref xml_name,
|
container_name,
|
||||||
ref parts,
|
bound_name,
|
||||||
}) => {
|
&self.member,
|
||||||
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,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
None => {
|
None => {
|
||||||
let as_xml_iter = as_xml_iter_fn(item_ty.clone());
|
let as_xml_iter = as_xml_iter_fn(item_ty.clone());
|
||||||
let item_iter = item_iter_ty(item_ty.clone(), lifetime.clone());
|
let item_iter = item_iter_ty(item_ty.clone(), lifetime.clone());
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue