blob: e8587cab157b06effde59e461c5670eb7550b718 [file] [log] [blame] [edit]
//! Intermediate representation of item data.
use proc_macro2::{Ident, Span, TokenStream, TokenTree};
use quote::ToTokens;
use syn::{punctuated::Punctuated, spanned::Spanned, Attribute, Meta, Result, Token, Variant};
use crate::{Data, Error, Incomparable, Trait};
/// Fields or variants of an item.
#[cfg_attr(test, derive(Debug))]
#[allow(clippy::large_enum_variant)]
pub enum Item<'a> {
/// Enum.
Enum {
/// Type of discriminant used.
discriminant: Discriminant,
/// [`struct@Ident`] of this enum.
ident: &'a Ident,
/// [`Incomparable`] attribute of this enum.
incomparable: Incomparable,
/// Variants of this enum.
variants: Vec<Data<'a>>,
},
/// Struct, tuple struct or union.
Item(Data<'a>),
}
impl Item<'_> {
/// Returns [`struct@Ident`] of this [`Item`].
pub fn ident(&self) -> &Ident {
match self {
Item::Item(data) => data.ident,
Item::Enum { ident, .. } => ident,
}
}
/// Returns `true` if this [`Item`] if an enum.
pub fn is_enum(&self) -> bool {
match self {
Item::Enum { .. } => true,
Item::Item(_) => false,
}
}
/// Returns `true` if any field is skipped with that [`Trait`].
pub fn any_skip_trait(&self, trait_: Trait) -> bool {
match self {
Item::Item(data) => data.any_skip_trait(trait_),
Item::Enum { variants, .. } => variants.iter().any(|data| data.any_skip_trait(trait_)),
}
}
/// Returns `true` if any field uses `Zeroize(fqs)`.
#[cfg(feature = "zeroize")]
pub fn any_fqs(&self) -> bool {
use crate::Either;
match self {
Item::Item(data) => match data.fields() {
Either::Left(fields) => fields.fields.iter().any(|field| field.attr.zeroize_fqs.0),
Either::Right(_) => false,
},
Item::Enum { variants, .. } => variants.iter().any(|data| match data.fields() {
Either::Left(fields) => fields.fields.iter().any(|field| field.attr.zeroize_fqs.0),
Either::Right(_) => false,
}),
}
}
/// Returns `true` if all [`Fields`](crate::data::Fields) are empty for this
/// [`Trait`].
pub fn is_empty(&self, trait_: Trait) -> bool {
match self {
Item::Enum { variants, .. } => variants.iter().all(|data| data.is_empty(trait_)),
Item::Item(data) => data.is_empty(trait_),
}
}
/// Returns `true` if the item is incomparable or all (≥1) variants are
/// incomparable.
pub fn is_incomparable(&self) -> bool {
match self {
Item::Enum {
variants,
incomparable,
..
} => {
incomparable.0.is_some()
|| !variants.is_empty() && variants.iter().all(Data::is_incomparable)
}
Item::Item(data) => data.is_incomparable(),
}
}
}
/// Type of discriminant used.
#[derive(Clone, Copy)]
#[cfg_attr(test, derive(Debug))]
pub enum Discriminant {
/// The enum has only a single variant.
Single,
/// The enum has only unit variants.
Unit,
/// The enum has a non-unit variant.
Data,
/// The enum has only unit variants.
UnitRepr(Representation),
/// The enum has a non-unit variant.
DataRepr(Representation),
}
impl Discriminant {
/// Parse the representation of an item.
pub fn parse(attrs: &[Attribute], variants: &Punctuated<Variant, Token![,]>) -> Result<Self> {
if variants.len() == 1 {
return Ok(Self::Single);
}
let mut has_repr = None;
for attr in attrs {
if attr.path().is_ident("repr") {
if let Meta::List(list) = &attr.meta {
let list =
list.parse_args_with(Punctuated::<Ident, Token![,]>::parse_terminated)?;
for ident in list {
if let Some(repr) = Representation::parse(&ident) {
has_repr = Some(repr);
break;
} else if ident != "C" && ident != "Rust" && ident != "align" {
return Err(Error::repr_unknown(ident.span()));
}
}
} else {
unreachable!("found invalid `repr` attribute")
}
}
}
let is_unit = variants.iter().all(|variant| variant.fields.is_empty());
Ok(if let Some(repr) = has_repr {
if is_unit {
Self::UnitRepr(repr)
} else {
Self::DataRepr(repr)
}
} else if is_unit {
Self::Unit
} else {
let discriminant = variants
.iter()
.find_map(|variant| variant.discriminant.as_ref());
if let Some(discriminant) = discriminant {
return Err(Error::repr_discriminant_invalid(discriminant.1.span()));
}
Self::Data
})
}
}
/// The type used to represent an enum.
#[derive(Clone, Copy)]
#[cfg_attr(test, derive(Debug))]
pub enum Representation {
/// [`u8`].
U8,
/// [`u16`].
U16,
/// [`u32`].
U32,
/// [`u64`].
U64,
/// [`u128`].
U128,
/// [`usize`].
USize,
/// [`i8`].
I8,
/// [`i16`].
I16,
/// [`i32`].
I32,
/// [`i64`].
I64,
/// [`i128`].
I128,
/// [`isize`].
ISize,
}
impl Representation {
/// Parse an [`struct@Ident`] to a valid representation if it is.
fn parse(ident: &Ident) -> Option<Self> {
Some(if ident == "u8" {
Self::U8
} else if ident == "u16" {
Self::U16
} else if ident == "u32" {
Self::U32
} else if ident == "u64" {
Self::U64
} else if ident == "u128" {
Self::U128
} else if ident == "usize" {
Self::USize
} else if ident == "i8" {
Self::I8
} else if ident == "i16" {
Self::I16
} else if ident == "i32" {
Self::I32
} else if ident == "i64" {
Self::I64
} else if ident == "i128" {
Self::I128
} else if ident == "isize" {
Self::ISize
} else {
return None;
})
}
/// Convert this [`Representation`] to a [`TokenStream`].
pub fn to_token(self) -> TokenStream {
let ident = match self {
Representation::U8 => "u8",
Representation::U16 => "u16",
Representation::U32 => "u32",
Representation::U64 => "u64",
Representation::U128 => "u128",
Representation::USize => "usize",
Representation::I8 => "i8",
Representation::I16 => "i16",
Representation::I32 => "i32",
Representation::I64 => "i64",
Representation::I128 => "i128",
Representation::ISize => "isize",
};
TokenTree::from(Ident::new(ident, Span::call_site())).into()
}
}
impl ToTokens for Representation {
fn to_tokens(&self, tokens: &mut TokenStream) {
tokens.extend(self.to_token());
}
}