xmpp-rs/xso-proc/src/compound.rs
Jonas Schäfer de39f410d5 xso-proc: improve spans for error messages
For codec-based error messages, this reduces the amount of errors per
violation to one. For all others, it improves the placement of the error
slightly, but we still get duplicates.

I couldn't figure out the remaining discrepancies in the spans ...
2025-05-02 17:16:59 +02:00

921 lines
36 KiB
Rust
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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};
use quote::{quote, quote_spanned, ToTokens};
use syn::{spanned::Spanned, *};
use std::collections::{hash_map::Entry, HashMap};
use crate::error_message::{FieldName, ParentRef};
use crate::field::{FieldBuilderPart, FieldDef, FieldIteratorPart, FieldTempInit, NestedMatcher};
use crate::meta::{DiscardSpec, Flag, NameRef, NamespaceRef, QNameRef};
use crate::scope::{mangle_member, AsItemsScope, FromEventsScope};
use crate::state::{AsItemsSubmachine, FromEventsSubmachine, State};
use crate::types::{
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,
};
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(),
})
}
}
}
/// A struct or enum variant's contents.
pub(crate) struct Compound {
/// The fields of this compound.
fields: Vec<FieldDef>,
/// Policy defining how to handle unknown attributes.
unknown_attribute_policy: Expr,
/// Policy defining how to handle unknown children.
unknown_child_policy: Expr,
/// Attributes to discard.
discard_attr: Vec<(Option<NamespaceRef>, NameRef)>,
/// Text to discard.
discard_text: Flag,
/// 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)>,
}
impl Compound {
/// Construct a compound from processed field definitions.
pub(crate) fn from_field_defs<I: IntoIterator<Item = Result<FieldDef>>>(
compound_fields: I,
unknown_attribute_policy: Option<Ident>,
unknown_child_policy: Option<Ident>,
discard: Vec<DiscardSpec>,
) -> Result<Self> {
let unknown_attribute_policy = resolve_policy(
unknown_attribute_policy,
unknown_attribute_policy_path(Span::call_site()),
);
let unknown_child_policy = resolve_policy(
unknown_child_policy,
unknown_child_policy_path(Span::call_site()),
);
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));
let mut text_field = None;
let mut selected_attributes: HashMap<QNameRef, Member> = HashMap::new();
for field in compound_fields {
let field = field?;
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())
}
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());
}
}
}
fields.push(field);
}
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));
}
}
}
Ok(Self {
fields,
unknown_attribute_policy,
unknown_child_policy,
discard_attr,
discard_text,
selected_attributes: selected_attributes.into_iter().collect(),
})
}
/// Construct a compound from fields.
pub(crate) fn from_fields(
compound_fields: &Fields,
container_namespace: &NamespaceRef,
unknown_attribute_policy: Option<Ident>,
unknown_child_policy: Option<Ident>,
discard: Vec<DiscardSpec>,
) -> Result<Self> {
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,
unknown_child_policy,
discard,
)
}
/// 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)
};
// 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('}', "}}");
// 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 {
// 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));
} };
})
}
}
checks
}
/// 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,
output_name: &ParentRef,
state_prefix: &str,
) -> Result<FromEventsSubmachine> {
let scope = FromEventsScope::new(state_ty_ident.clone());
let FromEventsScope {
ref attrs,
ref builder_data_ident,
ref text,
ref substate_data,
ref substate_result,
..
} = scope;
let default_state_ident = quote::format_ident!("{}Default", state_prefix);
let discard_state_ident = quote::format_ident!("{}Discard", state_prefix);
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();
let mut builder_data_def = TokenStream::default();
let mut builder_data_init = TokenStream::default();
let mut output_cons = TokenStream::default();
let mut child_matchers = TokenStream::default();
let mut fallback_child_matcher = None;
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
};
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();
let builder_field_name = mangle_member(member);
let part = field.make_builder_part(&scope, output_name)?;
let state_name = quote::format_ident!("{}Field{}", state_prefix, i);
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,
});
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 {
value: FieldTempInit { ty, init },
collect,
finalize,
} => {
if text_handler.is_some() {
// the existence of only one text handler is enforced
// by Compound's constructor(s).
panic!("more than one field attempts to collect text data");
}
builder_data_def.extend(quote! {
#builder_field_name: #ty,
});
builder_data_init.extend(quote! {
#builder_field_name: #init,
});
text_handler = Some(quote! {
#collect
::core::result::Result::Ok(::core::ops::ControlFlow::Break(
Self::#default_state_ident { #builder_data_ident }
))
});
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,
collect,
finalize,
} => {
let feed = feed_fn(builder.clone());
let mut substate_data_ident = substate_data.clone();
substate_data_ident.set_span(ty.span());
states.push(State::new_with_builder(
state_name.clone(),
builder_data_ident,
&builder_data_ty,
).with_field(
&substate_data_ident,
&builder,
).with_mut(substate_data).with_impl(quote! {
match #feed(&mut #substate_data, ev, ctx)? {
::core::option::Option::Some(#substate_result) => {
#collect
::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::#state_name {
#builder_data_ident,
#substate_data,
}))
}
}
}));
builder_data_def.extend(quote! {
#builder_field_name: #ty,
});
builder_data_init.extend(quote! {
#builder_field_name: #init,
});
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 {
#builder_data_ident,
#substate_data: { #matcher },
}))
};
fallback_child_matcher = Some((field.span(), matcher));
}
}
if is_tuple {
output_cons.extend(quote! {
#finalize,
});
} else {
output_cons.extend(quote! {
#member: #finalize,
});
}
extra_defs.extend(field_extra_defs);
}
}
}
// 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");
};
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);
});
}
let text_handler = match text_handler {
Some(v) => v,
None => quote! {
// note: u8::is_ascii_whitespace includes U+000C, which is not
// part of XML's white space definition.'
if !::xso::is_xml_whitespace(#text.as_bytes()) {
::core::result::Result::Err(::xso::error::Error::Other("Unexpected text content".into()))
} else {
::core::result::Result::Ok(::core::ops::ControlFlow::Break(
Self::#default_state_ident { #builder_data_ident }
))
}
},
};
let unknown_attr_err = format!("Unknown attribute in {}.", output_name);
let unknown_child_err = format!("Unknown child in {}.", output_name);
let unknown_child_policy = &self.unknown_child_policy;
let output_cons = match output_name {
ParentRef::Named(ref path) => {
quote! {
#path { #output_cons }
}
}
ParentRef::Unnamed { .. } => {
quote! {
( #output_cons )
}
}
};
let discard_builder_ty = discard_builder_ty(Span::call_site());
let discard_feed = feed_fn(discard_builder_ty.clone());
let child_fallback = match fallback_child_matcher {
Some((_, matcher)) => matcher,
None => quote! {
let _ = (name, attrs);
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(),
}))
},
};
states.push(State::new_with_builder(
discard_state_ident.clone(),
builder_data_ident,
&builder_data_ty,
).with_field(
substate_data,
&discard_builder_ty,
).with_mut(substate_data).with_impl(quote! {
match #discard_feed(&mut #substate_data, ev, ctx)? {
::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,
}))
}
}
}));
states.push(State::new_with_builder(
default_state_ident.clone(),
builder_data_ident,
&builder_data_ty,
).with_impl(quote! {
match ev {
// EndElement in Default state -> done parsing.
::xso::exports::rxml::Event::EndElement(_) => {
::core::result::Result::Ok(::core::ops::ControlFlow::Continue(
#output_cons
))
}
::xso::exports::rxml::Event::StartElement(_, name, attrs) => {
#child_matchers
#child_fallback
}
::xso::exports::rxml::Event::Text(_, #text) => {
#text_handler
}
// 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.
::xso::exports::rxml::Event::XmlDeclaration(_, ::xso::exports::rxml::XmlVersion::V1_0) => ::core::result::Result::Ok(::core::ops::ControlFlow::Break(
Self::#default_state_ident { #builder_data_ident }
))
}
}));
let unknown_attribute_policy = &self.unknown_attribute_policy;
let checks = self.assert_disjunct_attributes();
Ok(FromEventsSubmachine {
defs: quote! {
#extra_defs
#checks
struct #builder_data_ty {
#builder_data_def
}
},
states,
init: quote! {
let #builder_data_ident = #builder_data_ty {
#builder_data_init
};
#discard_attr
if #attrs.len() > 0 {
let _: () = #unknown_attribute_policy.apply_policy(#unknown_attr_err)?;
}
::core::result::Result::Ok(#state_ty_ident::#default_state_ident { #builder_data_ident })
},
})
}
/// 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.
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, state_ty_ident.clone());
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);
let name_ident = quote::format_ident!("name");
let ns_ident = quote::format_ident!("ns");
let dummy_ident = quote::format_ident!("dummy");
let mut header_states = Vec::new();
let mut body_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();
header_states.push(
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()),
),
);
body_states.push((
None,
State::new(element_head_end_state_ident.clone()).with_impl(quote! {
::core::option::Option::Some(::xso::Item::ElementHeadEnd)
}),
));
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, input_name, &bound_name)?;
let state_name = quote::format_ident!("{}Field{}", state_prefix, i);
let ty = scope.borrow(field.ty().clone());
match part {
FieldIteratorPart::Header { generator } => {
// We have to make sure that we carry our data around in
// all the previous states.
// 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() {
state.add_field(&bound_name, &ty);
}
header_states.push(
State::new(state_name)
.with_field(&bound_name, &ty)
.with_impl(quote! {
#generator
}),
);
if is_tuple {
destructure.extend(quote! {
ref #bound_name,
});
} else {
destructure.extend(quote! {
#member: ref #bound_name,
});
}
start_init.extend(quote! {
#bound_name,
});
}
FieldIteratorPart::Text { generator } => {
// 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() {
state.add_field(&bound_name, &ty);
}
let state = State::new(state_name)
.with_field(&bound_name, &ty)
.with_impl(quote! {
#generator.map(|value| ::xso::Item::Text(
value,
))
});
if is_tuple {
destructure.extend(quote! {
#bound_name,
});
} else {
destructure.extend(quote! {
#member: #bound_name,
});
}
start_init.extend(quote! {
#bound_name,
});
body_states.push((Some((bound_name, ty)), state));
}
FieldIteratorPart::Content {
extra_defs: field_extra_defs,
value: FieldTempInit { ty, init },
generator,
} => {
// 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() {
state.add_field(&bound_name, &ty);
}
let state = State::new(state_name.clone())
.with_field(&bound_name, &ty)
.with_mut(&bound_name)
.with_impl(quote! {
#generator?
});
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);
body_states.push((Some((bound_name, ty)), state));
}
}
}
header_states[0].set_impl(quote! {
{
::core::option::Option::Some(::xso::Item::ElementHeadStart(
#ns_ident,
#name_ident,
))
}
});
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;
states.push(
State::new(element_foot_state_ident.clone()).with_impl(quote! {
::core::option::Option::Some(::xso::Item::ElementFoot)
}),
);
let destructure = match input_name {
ParentRef::Named(ref input_path) => quote! {
#input_path { #destructure }
},
ParentRef::Unnamed { .. } => quote! {
( #destructure )
},
};
let checks = self.assert_disjunct_attributes();
extra_defs.extend(checks);
Ok(AsItemsSubmachine {
defs: extra_defs,
states,
destructure,
init: quote! {
Self::#element_head_start_state_ident { #dummy_ident: ::core::marker::PhantomData, #name_ident: name.1, #ns_ident: name.0, #start_init }
},
})
}
/// 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;
}
self.fields.first().map(|x| x.ty())
}
/// 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(),
}
}
/// 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(),
}
}
/// 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(),
}
}
/// Return the number of fields in this compound.
pub(crate) fn field_count(&self) -> usize {
self.fields.len()
}
}