2024-06-22 15:35:56 +02:00
|
|
|
|
// Copyright (c) 2024 Jonas Schäfer <jonas@zombofant.net>
|
|
|
|
|
|
//
|
|
|
|
|
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
|
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
|
|
|
|
|
|
|
|
//! Handling of the insides of compound structures (structs and enum variants)
|
|
|
|
|
|
|
|
|
|
|
|
use proc_macro2::{Span, TokenStream};
|
xso: reject attempts to match the same XML attribute in different fields
This was a bit tricky to build, because it is possible to have an
indirection through a `static` there. Thanks to Rust's extensive
const-fn capabilities, though, it's in fact possible to cover all cases.
We still do two different checks to improve user experience. If we can,
from within the proc macro, determine that two fields refer to the same
XML attribute (because their namespace/name values use the same Rust
tokens), then we reject the fields with a clear error message pointing
at both fields.
In the other case, when there's e.g. `#[xml(lang)]` and
`#[xml(attribute(namespace = rxml::XMLNS_XML, name = "lang"))]`, the
macro cannot be sure that XMLNS_XML is in fact the XML namespace. For
that case, we generate code which is evaluated at compile time (and
has no runtime impact) which panics if the namespace and name of two
attribute-matching fields is the same.
The error message will be less clear (because it contains extra,
unchangeable wording like "evaluation of constant value failed" and "the
evaluated program panicked at", which may be a bit confusing) than the
message generated by the macros themselves, but it's a price we have to
pay unfortunately.
Note that this check may seem cosmetic and purely for better user
experience, but it is in fact needed to avoid generating not-well-formed
and/or not-namespace-well-formed XML: As `AsXml` generates `xso::Item`,
where each attribute is emitted separated (and not aggregated in a
map structure), a naive (and efficient) implementation of a writer might
not double-check that no duplicate attributes are generated.
2025-04-27 09:26:56 +02:00
|
|
|
|
use quote::{quote, quote_spanned, ToTokens};
|
2024-07-01 07:46:02 +02:00
|
|
|
|
use syn::{spanned::Spanned, *};
|
2024-06-22 15:35:56 +02:00
|
|
|
|
|
xso: reject attempts to match the same XML attribute in different fields
This was a bit tricky to build, because it is possible to have an
indirection through a `static` there. Thanks to Rust's extensive
const-fn capabilities, though, it's in fact possible to cover all cases.
We still do two different checks to improve user experience. If we can,
from within the proc macro, determine that two fields refer to the same
XML attribute (because their namespace/name values use the same Rust
tokens), then we reject the fields with a clear error message pointing
at both fields.
In the other case, when there's e.g. `#[xml(lang)]` and
`#[xml(attribute(namespace = rxml::XMLNS_XML, name = "lang"))]`, the
macro cannot be sure that XMLNS_XML is in fact the XML namespace. For
that case, we generate code which is evaluated at compile time (and
has no runtime impact) which panics if the namespace and name of two
attribute-matching fields is the same.
The error message will be less clear (because it contains extra,
unchangeable wording like "evaluation of constant value failed" and "the
evaluated program panicked at", which may be a bit confusing) than the
message generated by the macros themselves, but it's a price we have to
pay unfortunately.
Note that this check may seem cosmetic and purely for better user
experience, but it is in fact needed to avoid generating not-well-formed
and/or not-namespace-well-formed XML: As `AsXml` generates `xso::Item`,
where each attribute is emitted separated (and not aggregated in a
map structure), a naive (and efficient) implementation of a writer might
not double-check that no duplicate attributes are generated.
2025-04-27 09:26:56 +02:00
|
|
|
|
use std::collections::{hash_map::Entry, HashMap};
|
|
|
|
|
|
|
|
|
|
|
|
use crate::error_message::{FieldName, ParentRef};
|
2024-08-05 08:20:25 +02:00
|
|
|
|
use crate::field::{FieldBuilderPart, FieldDef, FieldIteratorPart, FieldTempInit, NestedMatcher};
|
2025-04-05 13:06:18 +02:00
|
|
|
|
use crate::meta::{DiscardSpec, Flag, NameRef, NamespaceRef, QNameRef};
|
2024-07-09 17:01:42 +02:00
|
|
|
|
use crate::scope::{mangle_member, AsItemsScope, FromEventsScope};
|
|
|
|
|
|
use crate::state::{AsItemsSubmachine, FromEventsSubmachine, State};
|
2024-10-03 12:29:10 +02:00
|
|
|
|
use crate::types::{
|
2024-10-03 12:51:57 +02:00
|
|
|
|
default_fn, discard_builder_ty, feed_fn, namespace_ty, ncnamestr_cow_ty, phantom_lifetime_ty,
|
|
|
|
|
|
ref_ty, unknown_attribute_policy_path, unknown_child_policy_path,
|
2024-10-03 12:29:10 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
fn resolve_policy(policy: Option<Ident>, mut enum_ref: Path) -> Expr {
|
|
|
|
|
|
match policy {
|
|
|
|
|
|
Some(ident) => {
|
|
|
|
|
|
enum_ref.segments.push(ident.into());
|
|
|
|
|
|
Expr::Path(ExprPath {
|
|
|
|
|
|
attrs: Vec::new(),
|
|
|
|
|
|
qself: None,
|
|
|
|
|
|
path: enum_ref,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
None => {
|
|
|
|
|
|
let default_fn = default_fn(Type::Path(TypePath {
|
|
|
|
|
|
qself: None,
|
|
|
|
|
|
path: enum_ref,
|
|
|
|
|
|
}));
|
|
|
|
|
|
Expr::Call(ExprCall {
|
|
|
|
|
|
attrs: Vec::new(),
|
|
|
|
|
|
func: Box::new(default_fn),
|
|
|
|
|
|
paren_token: token::Paren::default(),
|
|
|
|
|
|
args: punctuated::Punctuated::new(),
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-06-22 15:35:56 +02:00
|
|
|
|
|
|
|
|
|
|
/// A struct or enum variant's contents.
|
2024-06-23 09:06:32 +02:00
|
|
|
|
pub(crate) struct Compound {
|
|
|
|
|
|
/// The fields of this compound.
|
|
|
|
|
|
fields: Vec<FieldDef>,
|
2024-10-03 12:29:10 +02:00
|
|
|
|
|
|
|
|
|
|
/// Policy defining how to handle unknown attributes.
|
|
|
|
|
|
unknown_attribute_policy: Expr,
|
2024-10-03 12:51:57 +02:00
|
|
|
|
|
|
|
|
|
|
/// Policy defining how to handle unknown children.
|
|
|
|
|
|
unknown_child_policy: Expr,
|
2025-04-05 13:06:18 +02:00
|
|
|
|
|
|
|
|
|
|
/// Attributes to discard.
|
|
|
|
|
|
discard_attr: Vec<(Option<NamespaceRef>, NameRef)>,
|
|
|
|
|
|
|
|
|
|
|
|
/// Text to discard.
|
|
|
|
|
|
discard_text: Flag,
|
xso: reject attempts to match the same XML attribute in different fields
This was a bit tricky to build, because it is possible to have an
indirection through a `static` there. Thanks to Rust's extensive
const-fn capabilities, though, it's in fact possible to cover all cases.
We still do two different checks to improve user experience. If we can,
from within the proc macro, determine that two fields refer to the same
XML attribute (because their namespace/name values use the same Rust
tokens), then we reject the fields with a clear error message pointing
at both fields.
In the other case, when there's e.g. `#[xml(lang)]` and
`#[xml(attribute(namespace = rxml::XMLNS_XML, name = "lang"))]`, the
macro cannot be sure that XMLNS_XML is in fact the XML namespace. For
that case, we generate code which is evaluated at compile time (and
has no runtime impact) which panics if the namespace and name of two
attribute-matching fields is the same.
The error message will be less clear (because it contains extra,
unchangeable wording like "evaluation of constant value failed" and "the
evaluated program panicked at", which may be a bit confusing) than the
message generated by the macros themselves, but it's a price we have to
pay unfortunately.
Note that this check may seem cosmetic and purely for better user
experience, but it is in fact needed to avoid generating not-well-formed
and/or not-namespace-well-formed XML: As `AsXml` generates `xso::Item`,
where each attribute is emitted separated (and not aggregated in a
map structure), a naive (and efficient) implementation of a writer might
not double-check that no duplicate attributes are generated.
2025-04-27 09:26:56 +02:00
|
|
|
|
|
|
|
|
|
|
/// Attribute qualified names which are selected by fields.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// This is used to generate code which asserts, at compile time, that no
|
|
|
|
|
|
/// two fields select the same XML attribute.
|
|
|
|
|
|
selected_attributes: Vec<(QNameRef, Member)>,
|
2024-06-23 09:06:32 +02:00
|
|
|
|
}
|
2024-06-22 15:35:56 +02:00
|
|
|
|
|
|
|
|
|
|
impl Compound {
|
2024-08-01 17:15:36 +02:00
|
|
|
|
/// Construct a compound from processed field definitions.
|
|
|
|
|
|
pub(crate) fn from_field_defs<I: IntoIterator<Item = Result<FieldDef>>>(
|
|
|
|
|
|
compound_fields: I,
|
2024-10-03 12:29:10 +02:00
|
|
|
|
unknown_attribute_policy: Option<Ident>,
|
2024-10-03 12:51:57 +02:00
|
|
|
|
unknown_child_policy: Option<Ident>,
|
2025-04-05 13:06:18 +02:00
|
|
|
|
discard: Vec<DiscardSpec>,
|
2024-08-01 17:15:36 +02:00
|
|
|
|
) -> Result<Self> {
|
2024-10-03 12:29:10 +02:00
|
|
|
|
let unknown_attribute_policy = resolve_policy(
|
|
|
|
|
|
unknown_attribute_policy,
|
|
|
|
|
|
unknown_attribute_policy_path(Span::call_site()),
|
|
|
|
|
|
);
|
2024-10-03 12:51:57 +02:00
|
|
|
|
let unknown_child_policy = resolve_policy(
|
|
|
|
|
|
unknown_child_policy,
|
|
|
|
|
|
unknown_child_policy_path(Span::call_site()),
|
|
|
|
|
|
);
|
2024-08-01 17:15:36 +02:00
|
|
|
|
let compound_fields = compound_fields.into_iter();
|
|
|
|
|
|
let size_hint = compound_fields.size_hint();
|
|
|
|
|
|
let mut fields = Vec::with_capacity(size_hint.1.unwrap_or(size_hint.0));
|
2024-07-01 07:46:02 +02:00
|
|
|
|
let mut text_field = None;
|
xso: reject attempts to match the same XML attribute in different fields
This was a bit tricky to build, because it is possible to have an
indirection through a `static` there. Thanks to Rust's extensive
const-fn capabilities, though, it's in fact possible to cover all cases.
We still do two different checks to improve user experience. If we can,
from within the proc macro, determine that two fields refer to the same
XML attribute (because their namespace/name values use the same Rust
tokens), then we reject the fields with a clear error message pointing
at both fields.
In the other case, when there's e.g. `#[xml(lang)]` and
`#[xml(attribute(namespace = rxml::XMLNS_XML, name = "lang"))]`, the
macro cannot be sure that XMLNS_XML is in fact the XML namespace. For
that case, we generate code which is evaluated at compile time (and
has no runtime impact) which panics if the namespace and name of two
attribute-matching fields is the same.
The error message will be less clear (because it contains extra,
unchangeable wording like "evaluation of constant value failed" and "the
evaluated program panicked at", which may be a bit confusing) than the
message generated by the macros themselves, but it's a price we have to
pay unfortunately.
Note that this check may seem cosmetic and purely for better user
experience, but it is in fact needed to avoid generating not-well-formed
and/or not-namespace-well-formed XML: As `AsXml` generates `xso::Item`,
where each attribute is emitted separated (and not aggregated in a
map structure), a naive (and efficient) implementation of a writer might
not double-check that no duplicate attributes are generated.
2025-04-27 09:26:56 +02:00
|
|
|
|
let mut selected_attributes: HashMap<QNameRef, Member> = HashMap::new();
|
2024-08-01 17:15:36 +02:00
|
|
|
|
for field in compound_fields {
|
|
|
|
|
|
let field = field?;
|
2024-07-01 07:46:02 +02:00
|
|
|
|
|
|
|
|
|
|
if field.is_text_field() {
|
|
|
|
|
|
if let Some(other_field) = text_field.as_ref() {
|
|
|
|
|
|
let mut err = Error::new_spanned(
|
|
|
|
|
|
field.member(),
|
|
|
|
|
|
"only one `#[xml(text)]` field allowed per compound",
|
|
|
|
|
|
);
|
|
|
|
|
|
err.combine(Error::new(
|
|
|
|
|
|
*other_field,
|
|
|
|
|
|
"the other `#[xml(text)]` field is here",
|
|
|
|
|
|
));
|
|
|
|
|
|
return Err(err);
|
|
|
|
|
|
}
|
|
|
|
|
|
text_field = Some(field.member().span())
|
|
|
|
|
|
}
|
|
|
|
|
|
|
xso: reject attempts to match the same XML attribute in different fields
This was a bit tricky to build, because it is possible to have an
indirection through a `static` there. Thanks to Rust's extensive
const-fn capabilities, though, it's in fact possible to cover all cases.
We still do two different checks to improve user experience. If we can,
from within the proc macro, determine that two fields refer to the same
XML attribute (because their namespace/name values use the same Rust
tokens), then we reject the fields with a clear error message pointing
at both fields.
In the other case, when there's e.g. `#[xml(lang)]` and
`#[xml(attribute(namespace = rxml::XMLNS_XML, name = "lang"))]`, the
macro cannot be sure that XMLNS_XML is in fact the XML namespace. For
that case, we generate code which is evaluated at compile time (and
has no runtime impact) which panics if the namespace and name of two
attribute-matching fields is the same.
The error message will be less clear (because it contains extra,
unchangeable wording like "evaluation of constant value failed" and "the
evaluated program panicked at", which may be a bit confusing) than the
message generated by the macros themselves, but it's a price we have to
pay unfortunately.
Note that this check may seem cosmetic and purely for better user
experience, but it is in fact needed to avoid generating not-well-formed
and/or not-namespace-well-formed XML: As `AsXml` generates `xso::Item`,
where each attribute is emitted separated (and not aggregated in a
map structure), a naive (and efficient) implementation of a writer might
not double-check that no duplicate attributes are generated.
2025-04-27 09:26:56 +02:00
|
|
|
|
if let Some(qname) = field.captures_attribute() {
|
|
|
|
|
|
let span = field.span();
|
|
|
|
|
|
match selected_attributes.entry(qname) {
|
|
|
|
|
|
Entry::Occupied(o) => {
|
|
|
|
|
|
let mut err = Error::new(
|
|
|
|
|
|
span,
|
|
|
|
|
|
"this field XML field matches the same attribute as another field",
|
|
|
|
|
|
);
|
|
|
|
|
|
err.combine(Error::new(
|
|
|
|
|
|
o.get().span(),
|
|
|
|
|
|
"the other field matching the same attribute is here",
|
|
|
|
|
|
));
|
|
|
|
|
|
return Err(err);
|
|
|
|
|
|
}
|
|
|
|
|
|
Entry::Vacant(v) => {
|
|
|
|
|
|
v.insert(field.member().clone());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-07-01 07:46:02 +02:00
|
|
|
|
fields.push(field);
|
2024-06-22 15:35:56 +02:00
|
|
|
|
}
|
2025-04-05 13:06:18 +02:00
|
|
|
|
|
|
|
|
|
|
let mut discard_text = Flag::Absent;
|
|
|
|
|
|
let mut discard_attr = Vec::new();
|
|
|
|
|
|
for spec in discard {
|
|
|
|
|
|
match spec {
|
|
|
|
|
|
DiscardSpec::Text { span } => {
|
|
|
|
|
|
if let Some(field) = text_field.as_ref() {
|
|
|
|
|
|
let mut err = Error::new(
|
|
|
|
|
|
*field,
|
|
|
|
|
|
"cannot combine `#[xml(text)]` field with `discard(text)`",
|
|
|
|
|
|
);
|
|
|
|
|
|
err.combine(Error::new(
|
|
|
|
|
|
spec.span(),
|
|
|
|
|
|
"the discard(text) attribute is here",
|
|
|
|
|
|
));
|
|
|
|
|
|
return Err(err);
|
|
|
|
|
|
}
|
|
|
|
|
|
if let Flag::Present(other) = discard_text {
|
|
|
|
|
|
let mut err = Error::new(
|
|
|
|
|
|
span,
|
|
|
|
|
|
"only one `discard(text)` meta is allowed per compound",
|
|
|
|
|
|
);
|
|
|
|
|
|
err.combine(Error::new(other, "the discard(text) meta is here"));
|
|
|
|
|
|
return Err(err);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
discard_text = Flag::Present(span);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
DiscardSpec::Attribute {
|
|
|
|
|
|
qname: QNameRef { namespace, name },
|
|
|
|
|
|
span,
|
|
|
|
|
|
} => {
|
|
|
|
|
|
let xml_namespace = namespace;
|
|
|
|
|
|
let xml_name = match name {
|
|
|
|
|
|
Some(v) => v,
|
|
|
|
|
|
None => {
|
|
|
|
|
|
return Err(Error::new(
|
|
|
|
|
|
span,
|
|
|
|
|
|
"discard(attribute) must specify a name, e.g. via discard(attribute = \"some-name\")",
|
|
|
|
|
|
));
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
discard_attr.push((xml_namespace, xml_name));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-10-03 12:29:10 +02:00
|
|
|
|
Ok(Self {
|
|
|
|
|
|
fields,
|
|
|
|
|
|
unknown_attribute_policy,
|
2024-10-03 12:51:57 +02:00
|
|
|
|
unknown_child_policy,
|
2025-04-05 13:06:18 +02:00
|
|
|
|
discard_attr,
|
|
|
|
|
|
discard_text,
|
xso: reject attempts to match the same XML attribute in different fields
This was a bit tricky to build, because it is possible to have an
indirection through a `static` there. Thanks to Rust's extensive
const-fn capabilities, though, it's in fact possible to cover all cases.
We still do two different checks to improve user experience. If we can,
from within the proc macro, determine that two fields refer to the same
XML attribute (because their namespace/name values use the same Rust
tokens), then we reject the fields with a clear error message pointing
at both fields.
In the other case, when there's e.g. `#[xml(lang)]` and
`#[xml(attribute(namespace = rxml::XMLNS_XML, name = "lang"))]`, the
macro cannot be sure that XMLNS_XML is in fact the XML namespace. For
that case, we generate code which is evaluated at compile time (and
has no runtime impact) which panics if the namespace and name of two
attribute-matching fields is the same.
The error message will be less clear (because it contains extra,
unchangeable wording like "evaluation of constant value failed" and "the
evaluated program panicked at", which may be a bit confusing) than the
message generated by the macros themselves, but it's a price we have to
pay unfortunately.
Note that this check may seem cosmetic and purely for better user
experience, but it is in fact needed to avoid generating not-well-formed
and/or not-namespace-well-formed XML: As `AsXml` generates `xso::Item`,
where each attribute is emitted separated (and not aggregated in a
map structure), a naive (and efficient) implementation of a writer might
not double-check that no duplicate attributes are generated.
2025-04-27 09:26:56 +02:00
|
|
|
|
selected_attributes: selected_attributes.into_iter().collect(),
|
2024-10-03 12:29:10 +02:00
|
|
|
|
})
|
2024-06-22 15:35:56 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-08-01 17:15:36 +02:00
|
|
|
|
/// Construct a compound from fields.
|
2024-08-03 14:06:41 +02:00
|
|
|
|
pub(crate) fn from_fields(
|
|
|
|
|
|
compound_fields: &Fields,
|
|
|
|
|
|
container_namespace: &NamespaceRef,
|
2024-10-03 12:29:10 +02:00
|
|
|
|
unknown_attribute_policy: Option<Ident>,
|
2024-10-03 12:51:57 +02:00
|
|
|
|
unknown_child_policy: Option<Ident>,
|
2025-04-05 13:06:18 +02:00
|
|
|
|
discard: Vec<DiscardSpec>,
|
2024-08-03 14:06:41 +02:00
|
|
|
|
) -> Result<Self> {
|
2024-10-03 12:29:10 +02:00
|
|
|
|
Self::from_field_defs(
|
|
|
|
|
|
compound_fields.iter().enumerate().map(|(i, field)| {
|
|
|
|
|
|
let index = match i.try_into() {
|
|
|
|
|
|
Ok(v) => v,
|
|
|
|
|
|
// we are converting to u32, are you crazy?!
|
|
|
|
|
|
// (u32, because syn::Member::Index needs that.)
|
|
|
|
|
|
Err(_) => {
|
|
|
|
|
|
return Err(Error::new_spanned(
|
|
|
|
|
|
field,
|
|
|
|
|
|
"okay, mate, that are way too many fields. get your life together.",
|
|
|
|
|
|
))
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
FieldDef::from_field(field, index, container_namespace)
|
|
|
|
|
|
}),
|
|
|
|
|
|
unknown_attribute_policy,
|
2024-10-03 12:51:57 +02:00
|
|
|
|
unknown_child_policy,
|
2025-04-05 13:06:18 +02:00
|
|
|
|
discard,
|
2024-10-03 12:29:10 +02:00
|
|
|
|
)
|
2024-08-01 17:15:36 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
xso: reject attempts to match the same XML attribute in different fields
This was a bit tricky to build, because it is possible to have an
indirection through a `static` there. Thanks to Rust's extensive
const-fn capabilities, though, it's in fact possible to cover all cases.
We still do two different checks to improve user experience. If we can,
from within the proc macro, determine that two fields refer to the same
XML attribute (because their namespace/name values use the same Rust
tokens), then we reject the fields with a clear error message pointing
at both fields.
In the other case, when there's e.g. `#[xml(lang)]` and
`#[xml(attribute(namespace = rxml::XMLNS_XML, name = "lang"))]`, the
macro cannot be sure that XMLNS_XML is in fact the XML namespace. For
that case, we generate code which is evaluated at compile time (and
has no runtime impact) which panics if the namespace and name of two
attribute-matching fields is the same.
The error message will be less clear (because it contains extra,
unchangeable wording like "evaluation of constant value failed" and "the
evaluated program panicked at", which may be a bit confusing) than the
message generated by the macros themselves, but it's a price we have to
pay unfortunately.
Note that this check may seem cosmetic and purely for better user
experience, but it is in fact needed to avoid generating not-well-formed
and/or not-namespace-well-formed XML: As `AsXml` generates `xso::Item`,
where each attribute is emitted separated (and not aggregated in a
map structure), a naive (and efficient) implementation of a writer might
not double-check that no duplicate attributes are generated.
2025-04-27 09:26:56 +02:00
|
|
|
|
/// Generate code which, at compile time, asserts that all attributes
|
|
|
|
|
|
/// which are selected by this compound are disjunct.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// NOTE: this needs rustc 1.83 or newer for `const_refs_to_static`.
|
|
|
|
|
|
fn assert_disjunct_attributes(&self) -> TokenStream {
|
|
|
|
|
|
let mut checks = TokenStream::default();
|
|
|
|
|
|
|
|
|
|
|
|
// Comparison is commutative, so we *could* reduce this to n^2/2
|
|
|
|
|
|
// comparisons instead of n*(n-1). However, by comparing every field
|
|
|
|
|
|
// with every other field and emitting check code for that, we can
|
|
|
|
|
|
// point at both fields in the error messages.
|
|
|
|
|
|
for (i, (qname_a, member_a)) in self.selected_attributes.iter().enumerate() {
|
|
|
|
|
|
for (j, (qname_b, member_b)) in self.selected_attributes.iter().enumerate() {
|
|
|
|
|
|
if i == j {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
// Flip a and b around if a is later than b.
|
|
|
|
|
|
// This way, the error message is the same for both
|
|
|
|
|
|
// conflicting fields. Note that we always take the span of
|
|
|
|
|
|
// `a` though, so that the two errors point at different
|
|
|
|
|
|
// fields.
|
|
|
|
|
|
let span = member_a.span();
|
|
|
|
|
|
let (member_a, member_b) = if i > j {
|
|
|
|
|
|
(member_b, member_a)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
(member_a, member_b)
|
|
|
|
|
|
};
|
|
|
|
|
|
if qname_a.namespace.is_some() != qname_b.namespace.is_some() {
|
|
|
|
|
|
// cannot ever match.
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
let Some((name_a, name_b)) = qname_a.name.as_ref().zip(qname_b.name.as_ref())
|
|
|
|
|
|
else {
|
|
|
|
|
|
panic!("selected attribute has no XML local name");
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
let mut check = quote! {
|
|
|
|
|
|
::xso::exports::const_str_eq(#name_a.as_str(), #name_b.as_str())
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
let namespaces = qname_a.namespace.as_ref().zip(qname_b.namespace.as_ref());
|
|
|
|
|
|
if let Some((ns_a, ns_b)) = namespaces {
|
|
|
|
|
|
check.extend(quote! {
|
|
|
|
|
|
&& ::xso::exports::const_str_eq(#ns_a, #ns_b)
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
let attr_a = if let Some(namespace_a) = qname_a.namespace.as_ref() {
|
|
|
|
|
|
format!("{{{}}}{}", namespace_a, name_a)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
format!("{}", name_a)
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
let attr_b = if let Some(namespace_b) = qname_b.namespace.as_ref() {
|
|
|
|
|
|
format!("{{{}}}{}", namespace_b, name_b)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
format!("{}", name_b)
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-05-02 16:28:47 +02:00
|
|
|
|
// See TODO below for why we do the extra replace calls.
|
|
|
|
|
|
let attr_a = attr_a.replace('{', "{{").replace('}', "}}");
|
|
|
|
|
|
let attr_b = attr_b.replace('{', "{{").replace('}', "}}");
|
|
|
|
|
|
let field_a = FieldName(&member_a)
|
|
|
|
|
|
.to_string()
|
|
|
|
|
|
.replace('{', "{{")
|
|
|
|
|
|
.replace('}', "}}");
|
|
|
|
|
|
let field_b = FieldName(&member_b)
|
|
|
|
|
|
.to_string()
|
|
|
|
|
|
.replace('{', "{{")
|
|
|
|
|
|
.replace('}', "}}");
|
xso: reject attempts to match the same XML attribute in different fields
This was a bit tricky to build, because it is possible to have an
indirection through a `static` there. Thanks to Rust's extensive
const-fn capabilities, though, it's in fact possible to cover all cases.
We still do two different checks to improve user experience. If we can,
from within the proc macro, determine that two fields refer to the same
XML attribute (because their namespace/name values use the same Rust
tokens), then we reject the fields with a clear error message pointing
at both fields.
In the other case, when there's e.g. `#[xml(lang)]` and
`#[xml(attribute(namespace = rxml::XMLNS_XML, name = "lang"))]`, the
macro cannot be sure that XMLNS_XML is in fact the XML namespace. For
that case, we generate code which is evaluated at compile time (and
has no runtime impact) which panics if the namespace and name of two
attribute-matching fields is the same.
The error message will be less clear (because it contains extra,
unchangeable wording like "evaluation of constant value failed" and "the
evaluated program panicked at", which may be a bit confusing) than the
message generated by the macros themselves, but it's a price we have to
pay unfortunately.
Note that this check may seem cosmetic and purely for better user
experience, but it is in fact needed to avoid generating not-well-formed
and/or not-namespace-well-formed XML: As `AsXml` generates `xso::Item`,
where each attribute is emitted separated (and not aggregated in a
map structure), a naive (and efficient) implementation of a writer might
not double-check that no duplicate attributes are generated.
2025-04-27 09:26:56 +02:00
|
|
|
|
|
|
|
|
|
|
// By assigning the checks to a `const`, we ensure that they
|
|
|
|
|
|
// are in fact evaluated at compile time, even if that constant
|
|
|
|
|
|
// is never used.
|
|
|
|
|
|
checks.extend(quote_spanned! {span=>
|
|
|
|
|
|
const _: () = { if #check {
|
2025-05-02 16:28:47 +02:00
|
|
|
|
// TODO: Rust nightly from 2025-05-02 (or around that date) didn't like our fancy panic, so we use something simpler.
|
|
|
|
|
|
// Filed bug upstream: https://github.com/rust-lang/rust/issues/140585
|
|
|
|
|
|
// For now, we work around this because it breaks our docs builds...
|
|
|
|
|
|
//panic!("member {} and member {} match the same XML attribute: {} == {}", #field_a, #field_b, #attr_a, #attr_b);
|
|
|
|
|
|
// Note that we cannot replace it with concat!(..), because we may have `{` and `}` in the strings...
|
|
|
|
|
|
panic!(concat!("member ", #field_a, " and member ", #field_b, " match the same XML attribute: ", #attr_a, " == ", #attr_b));
|
xso: reject attempts to match the same XML attribute in different fields
This was a bit tricky to build, because it is possible to have an
indirection through a `static` there. Thanks to Rust's extensive
const-fn capabilities, though, it's in fact possible to cover all cases.
We still do two different checks to improve user experience. If we can,
from within the proc macro, determine that two fields refer to the same
XML attribute (because their namespace/name values use the same Rust
tokens), then we reject the fields with a clear error message pointing
at both fields.
In the other case, when there's e.g. `#[xml(lang)]` and
`#[xml(attribute(namespace = rxml::XMLNS_XML, name = "lang"))]`, the
macro cannot be sure that XMLNS_XML is in fact the XML namespace. For
that case, we generate code which is evaluated at compile time (and
has no runtime impact) which panics if the namespace and name of two
attribute-matching fields is the same.
The error message will be less clear (because it contains extra,
unchangeable wording like "evaluation of constant value failed" and "the
evaluated program panicked at", which may be a bit confusing) than the
message generated by the macros themselves, but it's a price we have to
pay unfortunately.
Note that this check may seem cosmetic and purely for better user
experience, but it is in fact needed to avoid generating not-well-formed
and/or not-namespace-well-formed XML: As `AsXml` generates `xso::Item`,
where each attribute is emitted separated (and not aggregated in a
map structure), a naive (and efficient) implementation of a writer might
not double-check that no duplicate attributes are generated.
2025-04-27 09:26:56 +02:00
|
|
|
|
} };
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
checks
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-06-22 15:35:56 +02:00
|
|
|
|
/// Make and return a set of states which is used to construct the target
|
|
|
|
|
|
/// type from XML events.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// The states are returned as partial state machine. See the return
|
|
|
|
|
|
/// type's documentation for details.
|
|
|
|
|
|
pub(crate) fn make_from_events_statemachine(
|
|
|
|
|
|
&self,
|
|
|
|
|
|
state_ty_ident: &Ident,
|
2024-06-23 09:06:32 +02:00
|
|
|
|
output_name: &ParentRef,
|
2024-06-22 15:35:56 +02:00
|
|
|
|
state_prefix: &str,
|
|
|
|
|
|
) -> Result<FromEventsSubmachine> {
|
2024-08-01 17:16:55 +02:00
|
|
|
|
let scope = FromEventsScope::new(state_ty_ident.clone());
|
2024-06-26 17:54:36 +02:00
|
|
|
|
let FromEventsScope {
|
|
|
|
|
|
ref attrs,
|
|
|
|
|
|
ref builder_data_ident,
|
|
|
|
|
|
ref text,
|
2024-06-29 15:22:52 +02:00
|
|
|
|
ref substate_data,
|
|
|
|
|
|
ref substate_result,
|
2024-06-26 17:54:36 +02:00
|
|
|
|
..
|
|
|
|
|
|
} = scope;
|
2024-06-23 09:06:32 +02:00
|
|
|
|
|
2024-06-22 15:35:56 +02:00
|
|
|
|
let default_state_ident = quote::format_ident!("{}Default", state_prefix);
|
2024-10-03 12:51:57 +02:00
|
|
|
|
let discard_state_ident = quote::format_ident!("{}Discard", state_prefix);
|
2024-06-22 15:35:56 +02:00
|
|
|
|
let builder_data_ty: Type = TypePath {
|
|
|
|
|
|
qself: None,
|
|
|
|
|
|
path: quote::format_ident!("{}Data{}", state_ty_ident, state_prefix).into(),
|
|
|
|
|
|
}
|
|
|
|
|
|
.into();
|
|
|
|
|
|
let mut states = Vec::new();
|
|
|
|
|
|
|
2024-06-23 09:06:32 +02:00
|
|
|
|
let mut builder_data_def = TokenStream::default();
|
|
|
|
|
|
let mut builder_data_init = TokenStream::default();
|
|
|
|
|
|
let mut output_cons = TokenStream::default();
|
2024-06-29 15:22:52 +02:00
|
|
|
|
let mut child_matchers = TokenStream::default();
|
2024-08-05 08:20:25 +02:00
|
|
|
|
let mut fallback_child_matcher = None;
|
2025-04-05 13:06:18 +02:00
|
|
|
|
let mut text_handler = if self.discard_text.is_set() {
|
|
|
|
|
|
Some(quote! {
|
|
|
|
|
|
::core::result::Result::Ok(::core::ops::ControlFlow::Break(
|
|
|
|
|
|
Self::#default_state_ident { #builder_data_ident }
|
|
|
|
|
|
))
|
|
|
|
|
|
})
|
|
|
|
|
|
} else {
|
|
|
|
|
|
None
|
|
|
|
|
|
};
|
2024-08-01 17:16:55 +02:00
|
|
|
|
let mut extra_defs = TokenStream::default();
|
|
|
|
|
|
let is_tuple = !output_name.is_path();
|
2024-06-23 09:06:32 +02:00
|
|
|
|
|
2024-06-29 15:22:52 +02:00
|
|
|
|
for (i, field) in self.fields.iter().enumerate() {
|
2024-06-23 09:06:32 +02:00
|
|
|
|
let member = field.member();
|
|
|
|
|
|
let builder_field_name = mangle_member(member);
|
2024-07-03 11:15:33 +02:00
|
|
|
|
let part = field.make_builder_part(&scope, output_name)?;
|
2024-06-29 15:22:52 +02:00
|
|
|
|
let state_name = quote::format_ident!("{}Field{}", state_prefix, i);
|
2024-06-23 09:06:32 +02:00
|
|
|
|
|
|
|
|
|
|
match part {
|
|
|
|
|
|
FieldBuilderPart::Init {
|
|
|
|
|
|
value: FieldTempInit { ty, init },
|
|
|
|
|
|
} => {
|
|
|
|
|
|
builder_data_def.extend(quote! {
|
|
|
|
|
|
#builder_field_name: #ty,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
builder_data_init.extend(quote! {
|
|
|
|
|
|
#builder_field_name: #init,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2024-08-01 17:16:55 +02:00
|
|
|
|
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,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2024-06-23 09:06:32 +02:00
|
|
|
|
}
|
2024-06-26 17:54:36 +02:00
|
|
|
|
|
|
|
|
|
|
FieldBuilderPart::Text {
|
|
|
|
|
|
value: FieldTempInit { ty, init },
|
|
|
|
|
|
collect,
|
|
|
|
|
|
finalize,
|
|
|
|
|
|
} => {
|
|
|
|
|
|
if text_handler.is_some() {
|
2024-07-01 07:46:02 +02:00
|
|
|
|
// the existence of only one text handler is enforced
|
|
|
|
|
|
// by Compound's constructor(s).
|
|
|
|
|
|
panic!("more than one field attempts to collect text data");
|
2024-06-26 17:54:36 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
builder_data_def.extend(quote! {
|
|
|
|
|
|
#builder_field_name: #ty,
|
|
|
|
|
|
});
|
|
|
|
|
|
builder_data_init.extend(quote! {
|
|
|
|
|
|
#builder_field_name: #init,
|
|
|
|
|
|
});
|
|
|
|
|
|
text_handler = Some(quote! {
|
|
|
|
|
|
#collect
|
2024-08-03 17:23:14 +02:00
|
|
|
|
::core::result::Result::Ok(::core::ops::ControlFlow::Break(
|
2024-06-26 17:54:36 +02:00
|
|
|
|
Self::#default_state_ident { #builder_data_ident }
|
|
|
|
|
|
))
|
|
|
|
|
|
});
|
2024-08-01 17:16:55 +02:00
|
|
|
|
|
|
|
|
|
|
if is_tuple {
|
|
|
|
|
|
output_cons.extend(quote! {
|
|
|
|
|
|
#finalize,
|
|
|
|
|
|
});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
output_cons.extend(quote! {
|
|
|
|
|
|
#member: #finalize,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2024-06-26 17:54:36 +02:00
|
|
|
|
}
|
2024-06-29 15:22:52 +02:00
|
|
|
|
|
|
|
|
|
|
FieldBuilderPart::Nested {
|
2024-08-01 17:16:55 +02:00
|
|
|
|
extra_defs: field_extra_defs,
|
2024-06-29 15:22:52 +02:00
|
|
|
|
value: FieldTempInit { ty, init },
|
|
|
|
|
|
matcher,
|
|
|
|
|
|
builder,
|
|
|
|
|
|
collect,
|
|
|
|
|
|
finalize,
|
|
|
|
|
|
} => {
|
|
|
|
|
|
let feed = feed_fn(builder.clone());
|
|
|
|
|
|
|
2025-05-02 16:56:22 +02:00
|
|
|
|
let mut substate_data_ident = substate_data.clone();
|
|
|
|
|
|
substate_data_ident.set_span(ty.span());
|
2024-06-29 15:22:52 +02:00
|
|
|
|
states.push(State::new_with_builder(
|
|
|
|
|
|
state_name.clone(),
|
2025-04-07 21:04:16 +02:00
|
|
|
|
builder_data_ident,
|
2024-06-29 15:22:52 +02:00
|
|
|
|
&builder_data_ty,
|
|
|
|
|
|
).with_field(
|
2025-05-02 16:56:22 +02:00
|
|
|
|
&substate_data_ident,
|
2024-06-29 15:22:52 +02:00
|
|
|
|
&builder,
|
|
|
|
|
|
).with_mut(substate_data).with_impl(quote! {
|
2025-04-18 17:20:06 +02:00
|
|
|
|
match #feed(&mut #substate_data, ev, ctx)? {
|
2024-08-03 17:23:14 +02:00
|
|
|
|
::core::option::Option::Some(#substate_result) => {
|
2024-06-29 15:22:52 +02:00
|
|
|
|
#collect
|
2024-08-03 17:23:14 +02:00
|
|
|
|
::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#default_state_ident {
|
2024-06-29 15:22:52 +02:00
|
|
|
|
#builder_data_ident,
|
|
|
|
|
|
}))
|
|
|
|
|
|
}
|
2024-08-03 17:23:14 +02:00
|
|
|
|
::core::option::Option::None => {
|
|
|
|
|
|
::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#state_name {
|
2024-06-29 15:22:52 +02:00
|
|
|
|
#builder_data_ident,
|
|
|
|
|
|
#substate_data,
|
|
|
|
|
|
}))
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
builder_data_def.extend(quote! {
|
|
|
|
|
|
#builder_field_name: #ty,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
builder_data_init.extend(quote! {
|
|
|
|
|
|
#builder_field_name: #init,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2024-08-05 08:20:25 +02:00
|
|
|
|
match matcher {
|
|
|
|
|
|
NestedMatcher::Selective(matcher) => {
|
|
|
|
|
|
child_matchers.extend(quote! {
|
|
|
|
|
|
let (name, attrs) = match #matcher {
|
|
|
|
|
|
::core::result::Result::Err(::xso::error::FromEventsError::Mismatch { name, attrs }) => (name, attrs),
|
|
|
|
|
|
::core::result::Result::Err(::xso::error::FromEventsError::Invalid(e)) => return ::core::result::Result::Err(e),
|
|
|
|
|
|
::core::result::Result::Ok(#substate_data) => {
|
|
|
|
|
|
return ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#state_name {
|
|
|
|
|
|
#builder_data_ident,
|
|
|
|
|
|
#substate_data,
|
|
|
|
|
|
}))
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
NestedMatcher::Fallback(matcher) => {
|
|
|
|
|
|
if let Some((span, _)) = fallback_child_matcher.as_ref() {
|
|
|
|
|
|
let mut err = Error::new(
|
|
|
|
|
|
field.span(),
|
|
|
|
|
|
"more than one field is attempting to consume all unmatched child elements"
|
|
|
|
|
|
);
|
|
|
|
|
|
err.combine(Error::new(
|
|
|
|
|
|
*span,
|
|
|
|
|
|
"the previous field collecting all unmatched child elements is here"
|
|
|
|
|
|
));
|
|
|
|
|
|
return Err(err);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let matcher = quote! {
|
|
|
|
|
|
::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#state_name {
|
2024-06-29 15:22:52 +02:00
|
|
|
|
#builder_data_ident,
|
2024-08-05 08:20:25 +02:00
|
|
|
|
#substate_data: { #matcher },
|
2024-06-29 15:22:52 +02:00
|
|
|
|
}))
|
2024-08-05 08:20:25 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
fallback_child_matcher = Some((field.span(), matcher));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-06-29 15:22:52 +02:00
|
|
|
|
|
2024-08-01 17:16:55 +02:00
|
|
|
|
if is_tuple {
|
|
|
|
|
|
output_cons.extend(quote! {
|
|
|
|
|
|
#finalize,
|
|
|
|
|
|
});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
output_cons.extend(quote! {
|
|
|
|
|
|
#member: #finalize,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
extra_defs.extend(field_extra_defs);
|
2024-06-29 15:22:52 +02:00
|
|
|
|
}
|
2024-06-23 09:06:32 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-18 19:42:42 +02:00
|
|
|
|
// We always implicitly discard the `xml:lang` attribute. Its
|
|
|
|
|
|
// processing is handled using the `#[xml(language)]` meta.
|
|
|
|
|
|
let mut discard_attr = quote! {
|
|
|
|
|
|
let _ = #attrs.remove(::xso::exports::rxml::Namespace::xml(), "lang");
|
|
|
|
|
|
};
|
2025-04-05 13:06:18 +02:00
|
|
|
|
for (xml_namespace, xml_name) in self.discard_attr.iter() {
|
|
|
|
|
|
let xml_namespace = match xml_namespace {
|
|
|
|
|
|
Some(v) => v.to_token_stream(),
|
|
|
|
|
|
None => quote! {
|
|
|
|
|
|
::xso::exports::rxml::Namespace::none()
|
|
|
|
|
|
},
|
|
|
|
|
|
};
|
|
|
|
|
|
discard_attr.extend(quote! {
|
|
|
|
|
|
let _ = #attrs.remove(#xml_namespace, #xml_name);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-06-26 17:54:36 +02:00
|
|
|
|
let text_handler = match text_handler {
|
|
|
|
|
|
Some(v) => v,
|
|
|
|
|
|
None => quote! {
|
2024-07-01 07:40:28 +02:00
|
|
|
|
// note: u8::is_ascii_whitespace includes U+000C, which is not
|
|
|
|
|
|
// part of XML's white space definition.'
|
2024-08-09 14:48:58 +02:00
|
|
|
|
if !::xso::is_xml_whitespace(#text.as_bytes()) {
|
2024-07-01 07:40:28 +02:00
|
|
|
|
::core::result::Result::Err(::xso::error::Error::Other("Unexpected text content".into()))
|
|
|
|
|
|
} else {
|
2024-08-03 17:23:14 +02:00
|
|
|
|
::core::result::Result::Ok(::core::ops::ControlFlow::Break(
|
2024-07-01 07:40:28 +02:00
|
|
|
|
Self::#default_state_ident { #builder_data_ident }
|
|
|
|
|
|
))
|
|
|
|
|
|
}
|
2024-06-26 17:54:36 +02:00
|
|
|
|
},
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2024-06-23 09:06:32 +02:00
|
|
|
|
let unknown_attr_err = format!("Unknown attribute in {}.", output_name);
|
|
|
|
|
|
let unknown_child_err = format!("Unknown child in {}.", output_name);
|
2024-10-03 12:51:57 +02:00
|
|
|
|
let unknown_child_policy = &self.unknown_child_policy;
|
2024-06-23 09:06:32 +02:00
|
|
|
|
|
|
|
|
|
|
let output_cons = match output_name {
|
|
|
|
|
|
ParentRef::Named(ref path) => {
|
|
|
|
|
|
quote! {
|
|
|
|
|
|
#path { #output_cons }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-08-01 17:16:55 +02:00
|
|
|
|
ParentRef::Unnamed { .. } => {
|
|
|
|
|
|
quote! {
|
|
|
|
|
|
( #output_cons )
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-06-23 09:06:32 +02:00
|
|
|
|
};
|
2024-06-22 15:35:56 +02:00
|
|
|
|
|
2024-10-03 12:51:57 +02:00
|
|
|
|
let discard_builder_ty = discard_builder_ty(Span::call_site());
|
|
|
|
|
|
let discard_feed = feed_fn(discard_builder_ty.clone());
|
2024-08-05 08:20:25 +02:00
|
|
|
|
let child_fallback = match fallback_child_matcher {
|
|
|
|
|
|
Some((_, matcher)) => matcher,
|
|
|
|
|
|
None => quote! {
|
|
|
|
|
|
let _ = (name, attrs);
|
2024-10-03 12:51:57 +02:00
|
|
|
|
let _: () = #unknown_child_policy.apply_policy(#unknown_child_err)?;
|
|
|
|
|
|
::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#discard_state_ident {
|
|
|
|
|
|
#builder_data_ident,
|
|
|
|
|
|
#substate_data: #discard_builder_ty::new(),
|
|
|
|
|
|
}))
|
2024-08-05 08:20:25 +02:00
|
|
|
|
},
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2024-10-03 12:51:57 +02:00
|
|
|
|
states.push(State::new_with_builder(
|
|
|
|
|
|
discard_state_ident.clone(),
|
2025-04-07 21:04:16 +02:00
|
|
|
|
builder_data_ident,
|
2024-10-03 12:51:57 +02:00
|
|
|
|
&builder_data_ty,
|
|
|
|
|
|
).with_field(
|
|
|
|
|
|
substate_data,
|
|
|
|
|
|
&discard_builder_ty,
|
|
|
|
|
|
).with_mut(substate_data).with_impl(quote! {
|
2025-04-18 17:20:06 +02:00
|
|
|
|
match #discard_feed(&mut #substate_data, ev, ctx)? {
|
2024-10-03 12:51:57 +02:00
|
|
|
|
::core::option::Option::Some(#substate_result) => {
|
|
|
|
|
|
::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#default_state_ident {
|
|
|
|
|
|
#builder_data_ident,
|
|
|
|
|
|
}))
|
|
|
|
|
|
}
|
|
|
|
|
|
::core::option::Option::None => {
|
|
|
|
|
|
::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#discard_state_ident {
|
|
|
|
|
|
#builder_data_ident,
|
|
|
|
|
|
#substate_data,
|
|
|
|
|
|
}))
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
2024-06-22 15:35:56 +02:00
|
|
|
|
states.push(State::new_with_builder(
|
|
|
|
|
|
default_state_ident.clone(),
|
2024-07-03 11:15:33 +02:00
|
|
|
|
builder_data_ident,
|
2024-06-22 15:35:56 +02:00
|
|
|
|
&builder_data_ty,
|
|
|
|
|
|
).with_impl(quote! {
|
|
|
|
|
|
match ev {
|
|
|
|
|
|
// EndElement in Default state -> done parsing.
|
|
|
|
|
|
::xso::exports::rxml::Event::EndElement(_) => {
|
2024-08-03 17:23:14 +02:00
|
|
|
|
::core::result::Result::Ok(::core::ops::ControlFlow::Continue(
|
2024-06-22 15:35:56 +02:00
|
|
|
|
#output_cons
|
|
|
|
|
|
))
|
|
|
|
|
|
}
|
2024-06-29 15:22:52 +02:00
|
|
|
|
::xso::exports::rxml::Event::StartElement(_, name, attrs) => {
|
|
|
|
|
|
#child_matchers
|
2024-08-05 08:20:25 +02:00
|
|
|
|
#child_fallback
|
2024-06-22 15:35:56 +02:00
|
|
|
|
}
|
2024-06-26 17:54:36 +02:00
|
|
|
|
::xso::exports::rxml::Event::Text(_, #text) => {
|
|
|
|
|
|
#text_handler
|
2024-06-22 15:35:56 +02:00
|
|
|
|
}
|
|
|
|
|
|
// we ignore these: a correct parser only generates
|
|
|
|
|
|
// them at document start, and there we want to indeed
|
|
|
|
|
|
// not worry about them being in front of the first
|
|
|
|
|
|
// element.
|
2024-08-03 17:23:14 +02:00
|
|
|
|
::xso::exports::rxml::Event::XmlDeclaration(_, ::xso::exports::rxml::XmlVersion::V1_0) => ::core::result::Result::Ok(::core::ops::ControlFlow::Break(
|
2024-06-22 15:35:56 +02:00
|
|
|
|
Self::#default_state_ident { #builder_data_ident }
|
|
|
|
|
|
))
|
|
|
|
|
|
}
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
2024-10-03 12:29:10 +02:00
|
|
|
|
let unknown_attribute_policy = &self.unknown_attribute_policy;
|
|
|
|
|
|
|
xso: reject attempts to match the same XML attribute in different fields
This was a bit tricky to build, because it is possible to have an
indirection through a `static` there. Thanks to Rust's extensive
const-fn capabilities, though, it's in fact possible to cover all cases.
We still do two different checks to improve user experience. If we can,
from within the proc macro, determine that two fields refer to the same
XML attribute (because their namespace/name values use the same Rust
tokens), then we reject the fields with a clear error message pointing
at both fields.
In the other case, when there's e.g. `#[xml(lang)]` and
`#[xml(attribute(namespace = rxml::XMLNS_XML, name = "lang"))]`, the
macro cannot be sure that XMLNS_XML is in fact the XML namespace. For
that case, we generate code which is evaluated at compile time (and
has no runtime impact) which panics if the namespace and name of two
attribute-matching fields is the same.
The error message will be less clear (because it contains extra,
unchangeable wording like "evaluation of constant value failed" and "the
evaluated program panicked at", which may be a bit confusing) than the
message generated by the macros themselves, but it's a price we have to
pay unfortunately.
Note that this check may seem cosmetic and purely for better user
experience, but it is in fact needed to avoid generating not-well-formed
and/or not-namespace-well-formed XML: As `AsXml` generates `xso::Item`,
where each attribute is emitted separated (and not aggregated in a
map structure), a naive (and efficient) implementation of a writer might
not double-check that no duplicate attributes are generated.
2025-04-27 09:26:56 +02:00
|
|
|
|
let checks = self.assert_disjunct_attributes();
|
|
|
|
|
|
|
2024-06-22 15:35:56 +02:00
|
|
|
|
Ok(FromEventsSubmachine {
|
|
|
|
|
|
defs: quote! {
|
2024-08-01 17:16:55 +02:00
|
|
|
|
#extra_defs
|
xso: reject attempts to match the same XML attribute in different fields
This was a bit tricky to build, because it is possible to have an
indirection through a `static` there. Thanks to Rust's extensive
const-fn capabilities, though, it's in fact possible to cover all cases.
We still do two different checks to improve user experience. If we can,
from within the proc macro, determine that two fields refer to the same
XML attribute (because their namespace/name values use the same Rust
tokens), then we reject the fields with a clear error message pointing
at both fields.
In the other case, when there's e.g. `#[xml(lang)]` and
`#[xml(attribute(namespace = rxml::XMLNS_XML, name = "lang"))]`, the
macro cannot be sure that XMLNS_XML is in fact the XML namespace. For
that case, we generate code which is evaluated at compile time (and
has no runtime impact) which panics if the namespace and name of two
attribute-matching fields is the same.
The error message will be less clear (because it contains extra,
unchangeable wording like "evaluation of constant value failed" and "the
evaluated program panicked at", which may be a bit confusing) than the
message generated by the macros themselves, but it's a price we have to
pay unfortunately.
Note that this check may seem cosmetic and purely for better user
experience, but it is in fact needed to avoid generating not-well-formed
and/or not-namespace-well-formed XML: As `AsXml` generates `xso::Item`,
where each attribute is emitted separated (and not aggregated in a
map structure), a naive (and efficient) implementation of a writer might
not double-check that no duplicate attributes are generated.
2025-04-27 09:26:56 +02:00
|
|
|
|
#checks
|
2024-08-01 17:16:55 +02:00
|
|
|
|
|
2024-06-23 09:06:32 +02:00
|
|
|
|
struct #builder_data_ty {
|
|
|
|
|
|
#builder_data_def
|
|
|
|
|
|
}
|
2024-06-22 15:35:56 +02:00
|
|
|
|
},
|
|
|
|
|
|
states,
|
|
|
|
|
|
init: quote! {
|
2024-06-23 09:06:32 +02:00
|
|
|
|
let #builder_data_ident = #builder_data_ty {
|
|
|
|
|
|
#builder_data_init
|
|
|
|
|
|
};
|
2025-04-05 13:06:18 +02:00
|
|
|
|
#discard_attr
|
2024-06-23 09:06:32 +02:00
|
|
|
|
if #attrs.len() > 0 {
|
2024-10-03 12:29:10 +02:00
|
|
|
|
let _: () = #unknown_attribute_policy.apply_policy(#unknown_attr_err)?;
|
2024-06-22 15:35:56 +02:00
|
|
|
|
}
|
2024-06-23 09:06:32 +02:00
|
|
|
|
::core::result::Result::Ok(#state_ty_ident::#default_state_ident { #builder_data_ident })
|
2024-06-22 15:35:56 +02:00
|
|
|
|
},
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Make and return a set of states which is used to destructure the
|
|
|
|
|
|
/// target type into XML events.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// The states are returned as partial state machine. See the return
|
|
|
|
|
|
/// type's documentation for details.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// **Important:** The returned submachine is not in functional state!
|
|
|
|
|
|
/// It's `init` must be modified so that a variable called `name` of type
|
|
|
|
|
|
/// `rxml::QName` is in scope.
|
2024-07-09 17:01:42 +02:00
|
|
|
|
pub(crate) fn make_as_item_iter_statemachine(
|
2024-06-22 15:35:56 +02:00
|
|
|
|
&self,
|
2024-06-30 09:06:10 +02:00
|
|
|
|
input_name: &ParentRef,
|
2024-08-01 17:16:55 +02:00
|
|
|
|
state_ty_ident: &Ident,
|
2024-06-22 15:35:56 +02:00
|
|
|
|
state_prefix: &str,
|
2024-07-09 17:01:42 +02:00
|
|
|
|
lifetime: &Lifetime,
|
|
|
|
|
|
) -> Result<AsItemsSubmachine> {
|
2024-08-01 17:16:55 +02:00
|
|
|
|
let scope = AsItemsScope::new(lifetime, state_ty_ident.clone());
|
2024-07-09 17:01:42 +02:00
|
|
|
|
|
|
|
|
|
|
let element_head_start_state_ident =
|
|
|
|
|
|
quote::format_ident!("{}ElementHeadStart", state_prefix);
|
|
|
|
|
|
let element_head_end_state_ident = quote::format_ident!("{}ElementHeadEnd", state_prefix);
|
|
|
|
|
|
let element_foot_state_ident = quote::format_ident!("{}ElementFoot", state_prefix);
|
2024-06-22 15:35:56 +02:00
|
|
|
|
let name_ident = quote::format_ident!("name");
|
2024-07-09 17:01:42 +02:00
|
|
|
|
let ns_ident = quote::format_ident!("ns");
|
|
|
|
|
|
let dummy_ident = quote::format_ident!("dummy");
|
2025-04-18 10:46:09 +02:00
|
|
|
|
let mut header_states = Vec::new();
|
|
|
|
|
|
let mut body_states = Vec::new();
|
2024-06-22 15:35:56 +02:00
|
|
|
|
|
2024-08-01 17:16:55 +02:00
|
|
|
|
let is_tuple = !input_name.is_path();
|
2024-06-23 09:06:32 +02:00
|
|
|
|
let mut destructure = TokenStream::default();
|
|
|
|
|
|
let mut start_init = TokenStream::default();
|
2024-08-01 17:16:55 +02:00
|
|
|
|
let mut extra_defs = TokenStream::default();
|
2024-06-23 09:06:32 +02:00
|
|
|
|
|
2025-04-18 10:46:09 +02:00
|
|
|
|
header_states.push(
|
2024-07-09 17:01:42 +02:00
|
|
|
|
State::new(element_head_start_state_ident.clone())
|
|
|
|
|
|
.with_field(&dummy_ident, &phantom_lifetime_ty(lifetime.clone()))
|
|
|
|
|
|
.with_field(&ns_ident, &namespace_ty(Span::call_site()))
|
|
|
|
|
|
.with_field(
|
|
|
|
|
|
&name_ident,
|
|
|
|
|
|
&ncnamestr_cow_ty(Span::call_site(), lifetime.clone()),
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2025-04-18 10:46:09 +02:00
|
|
|
|
body_states.push((
|
|
|
|
|
|
None,
|
2024-07-09 17:01:42 +02:00
|
|
|
|
State::new(element_head_end_state_ident.clone()).with_impl(quote! {
|
|
|
|
|
|
::core::option::Option::Some(::xso::Item::ElementHeadEnd)
|
|
|
|
|
|
}),
|
2025-04-18 10:46:09 +02:00
|
|
|
|
));
|
2024-06-22 15:35:56 +02:00
|
|
|
|
|
2024-06-26 17:54:36 +02:00
|
|
|
|
for (i, field) in self.fields.iter().enumerate() {
|
2024-06-23 09:06:32 +02:00
|
|
|
|
let member = field.member();
|
|
|
|
|
|
let bound_name = mangle_member(member);
|
2024-08-01 17:16:55 +02:00
|
|
|
|
let part = field.make_iterator_part(&scope, input_name, &bound_name)?;
|
2024-06-26 17:54:36 +02:00
|
|
|
|
let state_name = quote::format_ident!("{}Field{}", state_prefix, i);
|
2024-07-09 17:01:42 +02:00
|
|
|
|
let ty = scope.borrow(field.ty().clone());
|
2024-06-23 09:06:32 +02:00
|
|
|
|
|
|
|
|
|
|
match part {
|
2024-07-09 17:01:42 +02:00
|
|
|
|
FieldIteratorPart::Header { generator } => {
|
2025-04-18 10:46:09 +02:00
|
|
|
|
// We have to make sure that we carry our data around in
|
2024-07-09 17:01:42 +02:00
|
|
|
|
// all the previous states.
|
2025-04-18 10:46:09 +02:00
|
|
|
|
// For header states, it is sufficient to do it here.
|
|
|
|
|
|
// For body states, we have to do it in a separate loop
|
|
|
|
|
|
// below to correctly handle the case when a field with a
|
|
|
|
|
|
// body state is placed before a field with a header
|
|
|
|
|
|
// state.
|
|
|
|
|
|
for state in header_states.iter_mut() {
|
2024-07-09 17:01:42 +02:00
|
|
|
|
state.add_field(&bound_name, &ty);
|
|
|
|
|
|
}
|
2025-04-18 10:46:09 +02:00
|
|
|
|
header_states.push(
|
2024-07-09 17:01:42 +02:00
|
|
|
|
State::new(state_name)
|
|
|
|
|
|
.with_field(&bound_name, &ty)
|
|
|
|
|
|
.with_impl(quote! {
|
|
|
|
|
|
#generator
|
|
|
|
|
|
}),
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2024-08-01 17:16:55 +02:00
|
|
|
|
if is_tuple {
|
|
|
|
|
|
destructure.extend(quote! {
|
|
|
|
|
|
ref #bound_name,
|
|
|
|
|
|
});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
destructure.extend(quote! {
|
|
|
|
|
|
#member: ref #bound_name,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2024-06-23 09:06:32 +02:00
|
|
|
|
start_init.extend(quote! {
|
|
|
|
|
|
#bound_name,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2024-06-26 17:54:36 +02:00
|
|
|
|
|
|
|
|
|
|
FieldIteratorPart::Text { generator } => {
|
2025-04-18 10:46:09 +02:00
|
|
|
|
// We have to make sure that we carry our data around in
|
|
|
|
|
|
// all the previous body states.
|
|
|
|
|
|
// We also have to make sure that our data is carried
|
|
|
|
|
|
// by all *header* states, but we can only do that once
|
|
|
|
|
|
// we have visited them all, so that happens at the bottom
|
|
|
|
|
|
// of the loop.
|
|
|
|
|
|
for (_, state) in body_states.iter_mut() {
|
2024-07-09 17:01:42 +02:00
|
|
|
|
state.add_field(&bound_name, &ty);
|
2024-06-26 17:54:36 +02:00
|
|
|
|
}
|
2025-04-18 10:46:09 +02:00
|
|
|
|
let state = State::new(state_name)
|
|
|
|
|
|
.with_field(&bound_name, &ty)
|
|
|
|
|
|
.with_impl(quote! {
|
|
|
|
|
|
#generator.map(|value| ::xso::Item::Text(
|
|
|
|
|
|
value,
|
|
|
|
|
|
))
|
|
|
|
|
|
});
|
2024-08-01 17:16:55 +02:00
|
|
|
|
if is_tuple {
|
|
|
|
|
|
destructure.extend(quote! {
|
|
|
|
|
|
#bound_name,
|
|
|
|
|
|
});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
destructure.extend(quote! {
|
|
|
|
|
|
#member: #bound_name,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2024-06-26 17:54:36 +02:00
|
|
|
|
start_init.extend(quote! {
|
|
|
|
|
|
#bound_name,
|
|
|
|
|
|
});
|
2025-04-18 10:46:09 +02:00
|
|
|
|
body_states.push((Some((bound_name, ty)), state));
|
2024-06-26 17:54:36 +02:00
|
|
|
|
}
|
2024-06-29 15:22:52 +02:00
|
|
|
|
|
|
|
|
|
|
FieldIteratorPart::Content {
|
2024-08-01 17:16:55 +02:00
|
|
|
|
extra_defs: field_extra_defs,
|
2024-06-29 15:22:52 +02:00
|
|
|
|
value: FieldTempInit { ty, init },
|
|
|
|
|
|
generator,
|
|
|
|
|
|
} => {
|
2025-04-18 10:46:09 +02:00
|
|
|
|
// We have to make sure that we carry our data around in
|
|
|
|
|
|
// all the previous body states.
|
|
|
|
|
|
// We also have to make sure that our data is carried
|
|
|
|
|
|
// by all *header* states, but we can only do that once
|
|
|
|
|
|
// we have visited them all, so that happens at the bottom
|
|
|
|
|
|
// of the loop.
|
|
|
|
|
|
for (_, state) in body_states.iter_mut() {
|
2024-06-29 15:22:52 +02:00
|
|
|
|
state.add_field(&bound_name, &ty);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-18 10:46:09 +02:00
|
|
|
|
let state = State::new(state_name.clone())
|
|
|
|
|
|
.with_field(&bound_name, &ty)
|
|
|
|
|
|
.with_mut(&bound_name)
|
|
|
|
|
|
.with_impl(quote! {
|
|
|
|
|
|
#generator?
|
|
|
|
|
|
});
|
2024-08-01 17:16:55 +02:00
|
|
|
|
if is_tuple {
|
|
|
|
|
|
destructure.extend(quote! {
|
|
|
|
|
|
#bound_name,
|
|
|
|
|
|
});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
destructure.extend(quote! {
|
|
|
|
|
|
#member: #bound_name,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2024-06-29 15:22:52 +02:00
|
|
|
|
start_init.extend(quote! {
|
|
|
|
|
|
#bound_name: #init,
|
|
|
|
|
|
});
|
2024-08-01 17:16:55 +02:00
|
|
|
|
|
|
|
|
|
|
extra_defs.extend(field_extra_defs);
|
2025-04-18 10:46:09 +02:00
|
|
|
|
body_states.push((Some((bound_name, ty)), state));
|
2024-06-29 15:22:52 +02:00
|
|
|
|
}
|
2024-06-23 09:06:32 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-18 10:46:09 +02:00
|
|
|
|
header_states[0].set_impl(quote! {
|
2024-06-23 09:06:32 +02:00
|
|
|
|
{
|
2024-07-09 17:01:42 +02:00
|
|
|
|
::core::option::Option::Some(::xso::Item::ElementHeadStart(
|
|
|
|
|
|
#ns_ident,
|
2024-06-23 09:06:32 +02:00
|
|
|
|
#name_ident,
|
|
|
|
|
|
))
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-04-18 10:46:09 +02:00
|
|
|
|
for (data, _) in body_states.iter() {
|
|
|
|
|
|
if let Some((bound_name, ty)) = data.as_ref() {
|
|
|
|
|
|
for state in header_states.iter_mut() {
|
|
|
|
|
|
state.add_field(bound_name, ty);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
header_states.extend(body_states.into_iter().map(|(_, state)| state));
|
|
|
|
|
|
let mut states = header_states;
|
|
|
|
|
|
|
2024-06-22 15:35:56 +02:00
|
|
|
|
states.push(
|
2024-07-09 17:01:42 +02:00
|
|
|
|
State::new(element_foot_state_ident.clone()).with_impl(quote! {
|
|
|
|
|
|
::core::option::Option::Some(::xso::Item::ElementFoot)
|
2024-06-22 15:35:56 +02:00
|
|
|
|
}),
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2024-08-01 17:16:55 +02:00
|
|
|
|
let destructure = match input_name {
|
|
|
|
|
|
ParentRef::Named(ref input_path) => quote! {
|
|
|
|
|
|
#input_path { #destructure }
|
|
|
|
|
|
},
|
|
|
|
|
|
ParentRef::Unnamed { .. } => quote! {
|
|
|
|
|
|
( #destructure )
|
|
|
|
|
|
},
|
|
|
|
|
|
};
|
2024-06-30 09:06:10 +02:00
|
|
|
|
|
xso: reject attempts to match the same XML attribute in different fields
This was a bit tricky to build, because it is possible to have an
indirection through a `static` there. Thanks to Rust's extensive
const-fn capabilities, though, it's in fact possible to cover all cases.
We still do two different checks to improve user experience. If we can,
from within the proc macro, determine that two fields refer to the same
XML attribute (because their namespace/name values use the same Rust
tokens), then we reject the fields with a clear error message pointing
at both fields.
In the other case, when there's e.g. `#[xml(lang)]` and
`#[xml(attribute(namespace = rxml::XMLNS_XML, name = "lang"))]`, the
macro cannot be sure that XMLNS_XML is in fact the XML namespace. For
that case, we generate code which is evaluated at compile time (and
has no runtime impact) which panics if the namespace and name of two
attribute-matching fields is the same.
The error message will be less clear (because it contains extra,
unchangeable wording like "evaluation of constant value failed" and "the
evaluated program panicked at", which may be a bit confusing) than the
message generated by the macros themselves, but it's a price we have to
pay unfortunately.
Note that this check may seem cosmetic and purely for better user
experience, but it is in fact needed to avoid generating not-well-formed
and/or not-namespace-well-formed XML: As `AsXml` generates `xso::Item`,
where each attribute is emitted separated (and not aggregated in a
map structure), a naive (and efficient) implementation of a writer might
not double-check that no duplicate attributes are generated.
2025-04-27 09:26:56 +02:00
|
|
|
|
let checks = self.assert_disjunct_attributes();
|
|
|
|
|
|
extra_defs.extend(checks);
|
|
|
|
|
|
|
2024-07-09 17:01:42 +02:00
|
|
|
|
Ok(AsItemsSubmachine {
|
2024-08-01 17:16:55 +02:00
|
|
|
|
defs: extra_defs,
|
2024-06-22 15:35:56 +02:00
|
|
|
|
states,
|
2024-08-01 17:16:55 +02:00
|
|
|
|
destructure,
|
2024-06-22 15:35:56 +02:00
|
|
|
|
init: quote! {
|
2024-08-03 17:23:14 +02:00
|
|
|
|
Self::#element_head_start_state_ident { #dummy_ident: ::core::marker::PhantomData, #name_ident: name.1, #ns_ident: name.0, #start_init }
|
2024-06-22 15:35:56 +02:00
|
|
|
|
},
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
2024-08-04 15:11:13 +02:00
|
|
|
|
|
2024-08-09 16:41:21 +02:00
|
|
|
|
/// Return a reference to this compound's only field's type.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// If the compound does not have exactly one field, this function returns
|
|
|
|
|
|
/// None.
|
|
|
|
|
|
pub(crate) fn single_ty(&self) -> Option<&Type> {
|
|
|
|
|
|
if self.fields.len() > 1 {
|
|
|
|
|
|
return None;
|
|
|
|
|
|
}
|
2025-04-07 21:04:16 +02:00
|
|
|
|
self.fields.first().map(|x| x.ty())
|
2024-08-09 16:41:21 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-08-04 15:11:13 +02:00
|
|
|
|
/// Construct a tuple type with this compound's field's types in the same
|
|
|
|
|
|
/// order as they appear in the compound.
|
|
|
|
|
|
pub(crate) fn to_tuple_ty(&self) -> TypeTuple {
|
|
|
|
|
|
TypeTuple {
|
|
|
|
|
|
paren_token: token::Paren::default(),
|
|
|
|
|
|
elems: self.fields.iter().map(|x| x.ty().clone()).collect(),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-08-18 11:50:41 +02:00
|
|
|
|
/// Construct a tuple type with this compound's field's types in the same
|
|
|
|
|
|
/// order as they appear in the compound.
|
|
|
|
|
|
pub(crate) fn to_single_or_tuple_ty(&self) -> Type {
|
|
|
|
|
|
match self.single_ty() {
|
|
|
|
|
|
None => self.to_tuple_ty().into(),
|
|
|
|
|
|
Some(v) => v.clone(),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-08-04 15:11:13 +02:00
|
|
|
|
/// Construct a tuple type with references to this compound's field's
|
|
|
|
|
|
/// types in the same order as they appear in the compound, with the given
|
|
|
|
|
|
/// lifetime.
|
|
|
|
|
|
pub(crate) fn to_ref_tuple_ty(&self, lifetime: &Lifetime) -> TypeTuple {
|
|
|
|
|
|
TypeTuple {
|
|
|
|
|
|
paren_token: token::Paren::default(),
|
|
|
|
|
|
elems: self
|
|
|
|
|
|
.fields
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.map(|x| ref_ty(x.ty().clone(), lifetime.clone()))
|
|
|
|
|
|
.collect(),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-08-09 16:41:21 +02:00
|
|
|
|
|
|
|
|
|
|
/// Return the number of fields in this compound.
|
|
|
|
|
|
pub(crate) fn field_count(&self) -> usize {
|
|
|
|
|
|
self.fields.len()
|
|
|
|
|
|
}
|
2024-06-22 15:35:56 +02:00
|
|
|
|
}
|