xso: implement support for extracting data from child elements

This commit is contained in:
Jonas Schäfer 2024-08-01 17:16:55 +02:00
commit 2c5f1f096b
12 changed files with 728 additions and 52 deletions

View file

@ -83,7 +83,7 @@ impl Compound {
output_name: &ParentRef,
state_prefix: &str,
) -> Result<FromEventsSubmachine> {
let scope = FromEventsScope::new();
let scope = FromEventsScope::new(state_ty_ident.clone());
let FromEventsScope {
ref attrs,
ref builder_data_ident,
@ -106,6 +106,8 @@ impl Compound {
let mut output_cons = TokenStream::default();
let mut child_matchers = TokenStream::default();
let mut text_handler = None;
let mut extra_defs = TokenStream::default();
let is_tuple = !output_name.is_path();
for (i, field) in self.fields.iter().enumerate() {
let member = field.member();
@ -125,9 +127,15 @@ impl Compound {
#builder_field_name: #init,
});
output_cons.extend(quote! {
#member: #builder_data_ident.#builder_field_name,
});
if is_tuple {
output_cons.extend(quote! {
#builder_data_ident.#builder_field_name,
});
} else {
output_cons.extend(quote! {
#member: #builder_data_ident.#builder_field_name,
});
}
}
FieldBuilderPart::Text {
@ -153,12 +161,20 @@ impl Compound {
Self::#default_state_ident { #builder_data_ident }
))
});
output_cons.extend(quote! {
#member: #finalize,
});
if is_tuple {
output_cons.extend(quote! {
#finalize,
});
} else {
output_cons.extend(quote! {
#member: #finalize,
});
}
}
FieldBuilderPart::Nested {
extra_defs: field_extra_defs,
value: FieldTempInit { ty, init },
matcher,
builder,
@ -212,9 +228,17 @@ impl Compound {
};
});
output_cons.extend(quote! {
#member: #finalize,
});
if is_tuple {
output_cons.extend(quote! {
#finalize,
});
} else {
output_cons.extend(quote! {
#member: #finalize,
});
}
extra_defs.extend(field_extra_defs);
}
}
}
@ -243,6 +267,11 @@ impl Compound {
#path { #output_cons }
}
}
ParentRef::Unnamed { .. } => {
quote! {
( #output_cons )
}
}
};
states.push(State::new_with_builder(
@ -277,6 +306,8 @@ impl Compound {
Ok(FromEventsSubmachine {
defs: quote! {
#extra_defs
struct #builder_data_ty {
#builder_data_def
}
@ -308,10 +339,11 @@ impl Compound {
pub(crate) fn make_as_item_iter_statemachine(
&self,
input_name: &ParentRef,
state_ty_ident: &Ident,
state_prefix: &str,
lifetime: &Lifetime,
) -> Result<AsItemsSubmachine> {
let scope = AsItemsScope::new(lifetime);
let scope = AsItemsScope::new(lifetime, state_ty_ident.clone());
let element_head_start_state_ident =
quote::format_ident!("{}ElementHeadStart", state_prefix);
@ -322,8 +354,10 @@ impl Compound {
let dummy_ident = quote::format_ident!("dummy");
let mut states = Vec::new();
let is_tuple = !input_name.is_path();
let mut destructure = TokenStream::default();
let mut start_init = TokenStream::default();
let mut extra_defs = TokenStream::default();
states.push(
State::new(element_head_start_state_ident.clone())
@ -345,7 +379,7 @@ impl Compound {
for (i, field) in self.fields.iter().enumerate() {
let member = field.member();
let bound_name = mangle_member(member);
let part = field.make_iterator_part(&scope, &bound_name)?;
let part = field.make_iterator_part(&scope, input_name, &bound_name)?;
let state_name = quote::format_ident!("{}Field{}", state_prefix, i);
let ty = scope.borrow(field.ty().clone());
@ -366,9 +400,15 @@ impl Compound {
);
element_head_end_idx += 1;
destructure.extend(quote! {
#member: ref #bound_name,
});
if is_tuple {
destructure.extend(quote! {
ref #bound_name,
});
} else {
destructure.extend(quote! {
#member: ref #bound_name,
});
}
start_init.extend(quote! {
#bound_name,
});
@ -389,15 +429,22 @@ impl Compound {
))
}),
);
destructure.extend(quote! {
#member: #bound_name,
});
if is_tuple {
destructure.extend(quote! {
#bound_name,
});
} else {
destructure.extend(quote! {
#member: #bound_name,
});
}
start_init.extend(quote! {
#bound_name,
});
}
FieldIteratorPart::Content {
extra_defs: field_extra_defs,
value: FieldTempInit { ty, init },
generator,
} => {
@ -415,12 +462,20 @@ impl Compound {
#generator?
}),
);
destructure.extend(quote! {
#member: #bound_name,
});
if is_tuple {
destructure.extend(quote! {
#bound_name,
});
} else {
destructure.extend(quote! {
#member: #bound_name,
});
}
start_init.extend(quote! {
#bound_name: #init,
});
extra_defs.extend(field_extra_defs);
}
}
}
@ -440,14 +495,19 @@ impl Compound {
}),
);
let ParentRef::Named(input_path) = input_name;
Ok(AsItemsSubmachine {
defs: TokenStream::default(),
states,
destructure: quote! {
let destructure = match input_name {
ParentRef::Named(ref input_path) => quote! {
#input_path { #destructure }
},
ParentRef::Unnamed { .. } => quote! {
( #destructure )
},
};
Ok(AsItemsSubmachine {
defs: extra_defs,
states,
destructure,
init: quote! {
Self::#element_head_start_state_ident { #dummy_ident: ::std::marker::PhantomData, #name_ident: name.1, #ns_ident: name.0, #start_init }
},

View file

@ -17,6 +17,7 @@ use crate::compound::Compound;
use crate::error_message::ParentRef;
use crate::meta::{reject_key, Flag, NameRef, NamespaceRef, QNameRef, XmlCompoundMeta};
use crate::state::{AsItemsStateMachine, FromEventsStateMachine};
use crate::types::{ref_ty, ty_from_ident};
/// The definition of an enum variant, switched on the XML element's name.
struct NameVariant {
@ -103,6 +104,7 @@ impl NameVariant {
&self,
xml_namespace: &NamespaceRef,
enum_ident: &Ident,
state_ty_ident: &Ident,
item_iter_ty_lifetime: &Lifetime,
) -> Result<AsItemsStateMachine> {
let xml_name = &self.name;
@ -119,6 +121,7 @@ impl NameVariant {
.into_iter()
.collect(),
}),
state_ty_ident,
&self.ident.to_string(),
&item_iter_ty_lifetime,
)?
@ -316,17 +319,17 @@ impl ItemDef for EnumDef {
statemachine.merge(variant.make_as_item_iter_statemachine(
&self.namespace,
target_ty_ident,
&state_ty_ident,
&item_iter_ty_lifetime,
)?);
}
let defs = statemachine.render(
vis,
&TypePath {
qself: None,
path: target_ty_ident.clone().into(),
}
.into(),
&ref_ty(
ty_from_ident(target_ty_ident.clone()).into(),
item_iter_ty_lifetime.clone(),
),
&state_ty_ident,
&item_iter_ty_lifetime,
&item_iter_ty,

View file

@ -19,6 +19,25 @@ pub(super) enum ParentRef {
/// The parent is addressable by a path, e.g. a struct type or enum
/// variant.
Named(Path),
/// The parent is not addressable by a path, because it is in fact an
/// ephemeral structure.
///
/// Used to reference the ephemeral structures used by fields declared
/// with `#[xml(extract(..))]`.
Unnamed {
/// The parent's ref.
///
/// For extracts, this refers to the compound where the field with
/// the extract is declared.
parent: Box<ParentRef>,
/// The field inside that parent.
///
/// For extracts, this refers to the compound field where the extract
/// is declared.
field: Member,
},
}
impl From<Path> for ParentRef {
@ -47,6 +66,32 @@ impl fmt::Display for ParentRef {
}
write!(f, " element")
}
Self::Unnamed { parent, field } => {
write!(f, "extraction for {} in {}", FieldName(field), parent)
}
}
}
}
impl ParentRef {
/// Create a new `ParentRef` for a member inside this one.
///
/// Returns a [`Self::Unnamed`] with `self` as parent and `member` as
/// field.
pub(crate) fn child(&self, member: Member) -> Self {
match self {
Self::Named { .. } | Self::Unnamed { .. } => Self::Unnamed {
parent: Box::new(self.clone()),
field: member,
},
}
}
/// Return true if and only if this ParentRef can be addressed as a path.
pub(crate) fn is_path(&self) -> bool {
match self {
Self::Named { .. } => true,
Self::Unnamed { .. } => false,
}
}
}

View file

@ -12,6 +12,7 @@ use syn::{spanned::Spanned, *};
use rxml_validation::NcName;
use crate::compound::Compound;
use crate::error_message::{self, ParentRef};
use crate::meta::{AmountConstraint, Flag, NameRef, NamespaceRef, QNameRef, XmlFieldMeta};
use crate::scope::{AsItemsScope, FromEventsScope};
@ -19,7 +20,7 @@ use crate::types::{
as_optional_xml_text_fn, as_xml_iter_fn, as_xml_text_fn, default_fn, extend_fn, from_events_fn,
from_xml_builder_ty, from_xml_text_fn, into_iterator_into_iter_fn, into_iterator_item_ty,
into_iterator_iter_ty, item_iter_ty, option_ty, ref_ty, string_ty, text_codec_decode_fn,
text_codec_encode_fn,
text_codec_encode_fn, ty_from_ident,
};
/// Code slices necessary for declaring and initializing a temporary variable
@ -63,6 +64,10 @@ pub(crate) enum FieldBuilderPart {
/// Parse a field from child element events.
Nested {
/// Additional definition items which need to be inserted at module
/// level for the rest of the implementation to work.
extra_defs: TokenStream,
/// Expression and type which initializes a buffer to use during
/// parsing.
value: FieldTempInit,
@ -117,6 +122,10 @@ pub(crate) enum FieldIteratorPart {
/// The field is emitted as series of items which form a child element.
Content {
/// Additional definition items which need to be inserted at module
/// level for the rest of the implementation to work.
extra_defs: TokenStream,
/// Expression and type which initializes the nested iterator.
///
/// Note that this is evaluated at construction time of the iterator.
@ -161,6 +170,26 @@ 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,
},
}
impl FieldKind {
@ -168,7 +197,9 @@ impl FieldKind {
///
/// `field_ident` is, for some field types, used to infer an XML name if
/// it is not specified explicitly.
fn from_meta(meta: XmlFieldMeta, field_ident: Option<&Ident>) -> Result<Self> {
///
/// `field_ty` is needed for type inferrence on extracted fields.
fn from_meta(meta: XmlFieldMeta, field_ident: Option<&Ident>, field_ty: &Type) -> Result<Self> {
match meta {
XmlFieldMeta::Attribute {
span,
@ -204,9 +235,13 @@ impl FieldKind {
})
}
XmlFieldMeta::Text { codec } => Ok(Self::Text { codec }),
XmlFieldMeta::Text { span: _, codec } => Ok(Self::Text { codec }),
XmlFieldMeta::Child { default_, amount } => {
XmlFieldMeta::Child {
span: _,
default_,
amount,
} => {
if let Some(AmountConstraint::Any(ref amount_span)) = amount {
if let Flag::Present(ref flag_span) = default_ {
let mut err = Error::new(
@ -226,6 +261,55 @@ impl FieldKind {
amount: amount.unwrap_or(AmountConstraint::FixedSingle(Span::call_site())),
})
}
XmlFieldMeta::Extract {
span,
qname: QNameRef { namespace, name },
fields,
} => {
let Some(xml_namespace) = namespace else {
return Err(Error::new(
span,
"`#[xml(extract(..))]` must contain a `namespace` key.",
));
};
let Some(xml_name) = name else {
return Err(Error::new(
span,
"`#[xml(extract(..))]` must contain a `name` key.",
));
};
let field = {
let mut fields = fields.into_iter();
let Some(field) = fields.next() else {
return Err(Error::new(
span,
"`#[xml(extract(..))]` must contain one `fields(..)` nested meta which contains at least one field meta."
));
};
if let Some(field) = fields.next() {
return Err(Error::new(
field.span(),
"more than one extracted piece of data is currently not supported",
));
}
field
};
let parts = Compound::from_field_defs(
[FieldDef::from_extract(field, 0, field_ty)].into_iter(),
)?;
Ok(Self::Extract {
xml_namespace,
xml_name,
parts,
})
}
}
}
}
@ -268,9 +352,22 @@ impl FieldDef {
let ty = field.ty.clone();
Ok(Self {
kind: FieldKind::from_meta(meta, ident, &ty)?,
member,
ty,
kind: FieldKind::from_meta(meta, ident)?,
})
}
/// Create a new field definition from its declaration.
///
/// The `index` must be the zero-based index of the field even for named
/// fields.
pub(crate) fn from_extract(meta: XmlFieldMeta, index: u32, ty: &Type) -> Result<Self> {
let span = meta.span();
Ok(Self {
member: Member::Unnamed(Index { index, span }),
ty: ty.clone(),
kind: FieldKind::from_meta(meta, None, ty)?,
})
}
@ -408,6 +505,7 @@ impl FieldDef {
};
Ok(FieldBuilderPart::Nested {
extra_defs: TokenStream::default(),
value: FieldTempInit {
init: quote! { ::std::option::Option::None },
ty: option_ty(self.ty.clone()),
@ -438,6 +536,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(),
value: FieldTempInit {
init: quote! { #ty_default() },
ty: self.ty.clone(),
@ -452,6 +551,80 @@ impl FieldDef {
}
}
}
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,
}
},
})
}
}
}
@ -462,6 +635,7 @@ impl FieldDef {
pub(crate) fn make_iterator_part(
&self,
scope: &AsItemsScope,
container_name: &ParentRef,
bound_name: &Ident,
) -> Result<FieldIteratorPart> {
match self.kind {
@ -515,6 +689,7 @@ impl FieldDef {
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)?
@ -561,6 +736,7 @@ impl FieldDef {
});
Ok(FieldIteratorPart::Content {
extra_defs: TokenStream::default(),
value: FieldTempInit {
init: quote! {
(#into_iter(#bound_name), ::core::option::Option::None)
@ -583,6 +759,70 @@ impl FieldDef {
},
})
}
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()
},
})
}
}
}

View file

@ -638,18 +638,42 @@ pub(crate) enum XmlFieldMeta {
/// `#[xml(text)]`
Text {
/// The span of the `#[xml(text)]` meta from which this was parsed.
///
/// This is useful for error messages.
span: Span,
/// The path to the optional codec type.
codec: Option<Expr>,
},
/// `#[xml(child)`
Child {
/// The span of the `#[xml(child)]` meta from which this was parsed.
///
/// This is useful for error messages.
span: Span,
/// The `default` flag.
default_: Flag,
/// The `n` flag.
amount: Option<AmountConstraint>,
},
/// `#[xml(extract)]
Extract {
/// The span of the `#[xml(extract)]` meta from which this was parsed.
///
/// This is useful for error messages.
span: Span,
/// The namespace/name keys.
qname: QNameRef,
/// The `fields` nested meta.
fields: Vec<XmlFieldMeta>,
},
}
impl XmlFieldMeta {
@ -725,7 +749,10 @@ impl XmlFieldMeta {
return Err(e);
}
}
Ok(Self::Text { codec: Some(codec) })
Ok(Self::Text {
span: meta.path.span(),
codec: Some(codec),
})
} else if meta.input.peek(syn::token::Paren) {
let mut codec: Option<Expr> = None;
meta.parse_nested_meta(|meta| {
@ -750,9 +777,15 @@ impl XmlFieldMeta {
Err(Error::new_spanned(meta.path, "unsupported key"))
}
})?;
Ok(Self::Text { codec })
Ok(Self::Text {
span: meta.path.span(),
codec,
})
} else {
Ok(Self::Text { codec: None })
Ok(Self::Text {
span: meta.path.span(),
codec: None,
})
}
}
@ -778,15 +811,53 @@ impl XmlFieldMeta {
Err(Error::new_spanned(meta.path, "unsupported key"))
}
})?;
Ok(Self::Child { default_, amount })
Ok(Self::Child {
span: meta.path.span(),
default_,
amount,
})
} else {
Ok(Self::Child {
span: meta.path.span(),
default_: Flag::Absent,
amount: None,
})
}
}
/// Parse a `#[xml(extract)]` meta.
fn extract_from_meta(meta: ParseNestedMeta<'_>) -> Result<Self> {
let mut qname = QNameRef::default();
let mut fields = None;
meta.parse_nested_meta(|meta| {
if meta.path.is_ident("fields") {
if let Some((fields_span, _)) = fields.as_ref() {
let mut error = Error::new_spanned(meta.path, "duplicate `fields` meta");
error.combine(Error::new(*fields_span, "previous `fields` meta was here"));
return Err(error);
}
let mut new_fields = Vec::new();
meta.parse_nested_meta(|meta| {
new_fields.push(XmlFieldMeta::parse_from_meta(meta)?);
Ok(())
})?;
fields = Some((meta.path.span(), new_fields));
Ok(())
} else {
match qname.parse_incremental_from_meta(meta)? {
None => Ok(()),
Some(meta) => Err(Error::new_spanned(meta.path, "unsupported key")),
}
}
})?;
let fields = fields.map(|(_, x)| x).unwrap_or_else(Vec::new);
Ok(Self::Extract {
span: meta.path.span(),
qname,
fields,
})
}
/// Parse [`Self`] from a nestd meta, switching on the identifier
/// of that nested meta.
fn parse_from_meta(meta: ParseNestedMeta<'_>) -> Result<Self> {
@ -796,6 +867,8 @@ impl XmlFieldMeta {
Self::text_from_meta(meta)
} else if meta.path.is_ident("child") {
Self::child_from_meta(meta)
} else if meta.path.is_ident("extract") {
Self::extract_from_meta(meta)
} else {
Err(Error::new_spanned(meta.path, "unsupported field meta"))
}
@ -868,4 +941,15 @@ impl XmlFieldMeta {
Err(Error::new(*err_span, "missing #[xml(..)] meta on field"))
}
}
/// Return a span which points at the meta which constructed this
/// XmlFieldMeta.
pub(crate) fn span(&self) -> Span {
match self {
Self::Attribute { ref span, .. } => *span,
Self::Child { ref span, .. } => *span,
Self::Text { ref span, .. } => *span,
Self::Extract { ref span, .. } => *span,
}
}
}

View file

@ -52,11 +52,15 @@ pub(crate) struct FromEventsScope {
///
/// See [`crate::field::FieldBuilderPart::Nested`].
pub(crate) substate_result: Ident,
/// Prefix which should be used for any types which are declared, to
/// ensure they don't collide with other names.
pub(crate) type_prefix: Ident,
}
impl FromEventsScope {
/// Create a fresh scope with all necessary identifiers.
pub(crate) fn new() -> Self {
pub(crate) fn new(type_prefix: Ident) -> Self {
// Sadly, `Ident::new` is not `const`, so we have to create even the
// well-known identifiers from scratch all the time.
Self {
@ -65,6 +69,7 @@ impl FromEventsScope {
builder_data_ident: Ident::new("__xso_proc_macro_builder_data", Span::call_site()),
substate_data: Ident::new("__xso_proc_macro_substate_data", Span::call_site()),
substate_result: Ident::new("__xso_proc_macro_substate_result", Span::call_site()),
type_prefix,
}
}
@ -84,6 +89,24 @@ impl FromEventsScope {
member: Member::Named(mangle_member(member)),
})
}
/// Generate an ident with proper scope and span from the type prefix and
/// the given member and actual type name.
///
/// Due to being merged with the type prefix of this scope and the given
/// member, this type name is guaranteed to be unique for unique values of
/// `name`.
pub(crate) fn make_member_type_name(&self, member: &Member, name: &str) -> Ident {
quote::format_ident!(
"{}Member{}{}",
self.type_prefix,
match member {
Member::Named(ref ident) => ident.to_string(),
Member::Unnamed(Index { index, .. }) => index.to_string(),
},
name,
)
}
}
/// Container struct for various identifiers used throughout the generator
@ -97,13 +120,18 @@ impl FromEventsScope {
pub(crate) struct AsItemsScope {
/// Lifetime for data borrowed by the implementation.
pub(crate) lifetime: Lifetime,
/// Prefix which should be used for any types which are declared, to
/// ensure they don't collide with other names.
pub(crate) type_prefix: Ident,
}
impl AsItemsScope {
/// Create a fresh scope with all necessary identifiers.
pub(crate) fn new(lifetime: &Lifetime) -> Self {
pub(crate) fn new(lifetime: &Lifetime, type_prefix: Ident) -> Self {
Self {
lifetime: lifetime.clone(),
type_prefix,
}
}
@ -112,6 +140,24 @@ impl AsItemsScope {
pub(crate) fn borrow(&self, ty: Type) -> Type {
ref_ty(ty, self.lifetime.clone())
}
/// Generate an ident with proper scope and span from the type prefix and
/// the given member and actual type name.
///
/// Due to being merged with the type prefix of this scope and the given
/// member, this type name is guaranteed to be unique for unique values of
/// `name`.
pub(crate) fn make_member_type_name(&self, member: &Member, name: &str) -> Ident {
quote::format_ident!(
"{}Member{}{}",
self.type_prefix,
match member {
Member::Named(ref ident) => ident.to_string(),
Member::Unnamed(Index { index, .. }) => index.to_string(),
},
name,
)
}
}
pub(crate) fn mangle_member(member: &Member) -> Ident {

View file

@ -638,7 +638,7 @@ impl AsItemsStateMachine {
pub(crate) fn render(
self,
vis: &Visibility,
input_ty: &Type,
input_ty_ref: &Type,
state_ty_ident: &Ident,
item_iter_ty_lifetime: &Lifetime,
item_iter_ty: &Type,
@ -650,8 +650,8 @@ impl AsItemsStateMachine {
mut variants,
} = self;
let input_ty_ref = make_ty_ref(input_ty);
let docstr = format!("Convert a {0} into XML events.\n\nThis type is generated using the [`macro@xso::AsXml`] derive macro and implements [`std::iter:Iterator`] for {0}.", input_ty_ref);
let input_ty_ref_text = make_ty_ref(input_ty_ref);
let docstr = format!("Convert a {0} into XML events.\n\nThis type is generated using the [`macro@xso::AsXml`] derive macro and implements [`std::iter:Iterator`] for {0}.", input_ty_ref_text);
let init_body = if variants.len() == 1 {
let AsItemsEntryPoint { destructure, init } = variants.remove(0);
@ -691,7 +691,7 @@ impl AsItemsStateMachine {
}
fn new(
value: &#item_iter_ty_lifetime #input_ty,
value: #input_ty_ref,
) -> ::core::result::Result<Self, ::xso::error::Error> {
::core::result::Result::Ok(#init_body)
}
@ -729,7 +729,7 @@ impl AsItemsStateMachine {
}
impl<#item_iter_ty_lifetime> #item_iter_ty {
fn new(value: &#item_iter_ty_lifetime #input_ty) -> ::core::result::Result<Self, ::xso::error::Error> {
fn new(value: #input_ty_ref) -> ::core::result::Result<Self, ::xso::error::Error> {
#state_ty_ident::new(value).map(|ok| Self(::core::option::Option::Some(ok)))
}
}
@ -765,6 +765,7 @@ fn doc_link_path(ty: &Type) -> Option<String> {
}
Some(buf)
}
Type::Reference(TypeReference { ref elem, .. }) => doc_link_path(elem),
_ => None,
}
}

View file

@ -13,6 +13,7 @@ use syn::*;
use crate::common::{AsXmlParts, FromXmlParts, ItemDef};
use crate::compound::Compound;
use crate::meta::{reject_key, Flag, NameRef, NamespaceRef, QNameRef, XmlCompoundMeta};
use crate::types::{ref_ty, ty_from_ident};
/// Definition of a struct and how to parse it.
pub(crate) struct StructDef {
@ -178,6 +179,7 @@ impl ItemDef for StructDef {
.inner
.make_as_item_iter_statemachine(
&Path::from(target_ty_ident.clone()).into(),
&state_ty_ident,
"Struct",
&item_iter_ty_lifetime,
)?
@ -193,11 +195,10 @@ impl ItemDef for StructDef {
.compile()
.render(
vis,
&TypePath {
qself: None,
path: target_ty_ident.clone().into(),
}
.into(),
&ref_ty(
ty_from_ident(target_ty_ident.clone()).into(),
item_iter_ty_lifetime.clone(),
),
&state_ty_ident,
&item_iter_ty_lifetime,
&item_iter_ty,

View file

@ -745,3 +745,11 @@ pub(crate) fn extend_fn(of_ty: Type, item_ty: Type) -> Expr {
},
})
}
/// Construct a [`syn::TypePath`] which references the given type name.
pub(crate) fn ty_from_ident(ident: Ident) -> TypePath {
TypePath {
qself: None,
path: ident.into(),
}
}