1//! Intermediate representation of item data.
2
3use proc_macro2::{Ident, Span, TokenStream, TokenTree};
4use quote::ToTokens;
5use syn::{punctuated::Punctuated, spanned::Spanned, Attribute, Meta, Result, Token, Variant};
6
7use crate::{Data, Error, Incomparable, Trait};
8
9/// Fields or variants of an item.
10#[cfg_attr(test, derive(Debug))]
11#[allow(clippy::large_enum_variant)]
12pub enum Item<'a> {
13 /// Enum.
14 Enum {
15 /// Type of discriminant used.
16 discriminant: Discriminant,
17 /// [`struct@Ident`] of this enum.
18 ident: &'a Ident,
19 /// [`Incomparable`] attribute of this enum.
20 incomparable: Incomparable,
21 /// Variants of this enum.
22 variants: Vec<Data<'a>>,
23 },
24 /// Struct, tuple struct or union.
25 Item(Data<'a>),
26}
27
28impl Item<'_> {
29 /// Returns [`struct@Ident`] of this [`Item`].
30 pub fn ident(&self) -> &Ident {
31 match self {
32 Item::Item(data) => data.ident,
33 Item::Enum { ident, .. } => ident,
34 }
35 }
36
37 /// Returns `true` if this [`Item`] if an enum.
38 pub fn is_enum(&self) -> bool {
39 match self {
40 Item::Enum { .. } => true,
41 Item::Item(_) => false,
42 }
43 }
44
45 /// Returns `true` if any field is skipped with that [`Trait`].
46 pub fn any_skip_trait(&self, trait_: Trait) -> bool {
47 match self {
48 Item::Item(data) => data.any_skip_trait(trait_),
49 Item::Enum { variants, .. } => variants.iter().any(|data| data.any_skip_trait(trait_)),
50 }
51 }
52
53 /// Returns `true` if any field uses `Zeroize(fqs)`.
54 #[cfg(feature = "zeroize")]
55 pub fn any_fqs(&self) -> bool {
56 use crate::Either;
57
58 match self {
59 Item::Item(data) => match data.fields() {
60 Either::Left(fields) => fields.fields.iter().any(|field| field.attr.zeroize_fqs.0),
61 Either::Right(_) => false,
62 },
63 Item::Enum { variants, .. } => variants.iter().any(|data| match data.fields() {
64 Either::Left(fields) => fields.fields.iter().any(|field| field.attr.zeroize_fqs.0),
65 Either::Right(_) => false,
66 }),
67 }
68 }
69
70 /// Returns `true` if all [`Fields`](crate::data::Fields) are empty for this
71 /// [`Trait`].
72 pub fn is_empty(&self, trait_: Trait) -> bool {
73 match self {
74 Item::Enum { variants, .. } => variants.iter().all(|data| data.is_empty(trait_)),
75 Item::Item(data) => data.is_empty(trait_),
76 }
77 }
78
79 /// Returns `true` if the item is incomparable or all (≥1) variants are
80 /// incomparable.
81 pub fn is_incomparable(&self) -> bool {
82 match self {
83 Item::Enum {
84 variants,
85 incomparable,
86 ..
87 } => {
88 incomparable.0.is_some()
89 || !variants.is_empty() && variants.iter().all(Data::is_incomparable)
90 }
91 Item::Item(data) => data.is_incomparable(),
92 }
93 }
94}
95
96/// Type of discriminant used.
97#[derive(Clone, Copy)]
98#[cfg_attr(test, derive(Debug))]
99pub enum Discriminant {
100 /// The enum has only a single variant.
101 Single,
102 /// The enum has only unit variants.
103 Unit,
104 /// The enum has a non-unit variant.
105 Data,
106 /// The enum has only unit variants.
107 UnitRepr(Representation),
108 /// The enum has a non-unit variant.
109 DataRepr(Representation),
110}
111
112impl Discriminant {
113 /// Parse the representation of an item.
114 pub fn parse(attrs: &[Attribute], variants: &Punctuated<Variant, Token![,]>) -> Result<Self> {
115 if variants.len() == 1 {
116 return Ok(Self::Single);
117 }
118
119 let mut has_repr = None;
120
121 for attr in attrs {
122 if attr.path().is_ident("repr") {
123 if let Meta::List(list) = &attr.meta {
124 let list =
125 list.parse_args_with(Punctuated::<Ident, Token![,]>::parse_terminated)?;
126
127 for ident in list {
128 if let Some(repr) = Representation::parse(&ident) {
129 has_repr = Some(repr);
130 break;
131 } else if ident != "C" && ident != "Rust" && ident != "align" {
132 return Err(Error::repr_unknown(ident.span()));
133 }
134 }
135 } else {
136 unreachable!("found invalid `repr` attribute")
137 }
138 }
139 }
140
141 let is_unit = variants.iter().all(|variant| variant.fields.is_empty());
142
143 Ok(if let Some(repr) = has_repr {
144 if is_unit {
145 Self::UnitRepr(repr)
146 } else {
147 Self::DataRepr(repr)
148 }
149 } else if is_unit {
150 Self::Unit
151 } else {
152 let discriminant = variants
153 .iter()
154 .find_map(|variant| variant.discriminant.as_ref());
155
156 if let Some(discriminant) = discriminant {
157 return Err(Error::repr_discriminant_invalid(discriminant.1.span()));
158 }
159
160 Self::Data
161 })
162 }
163}
164
165/// The type used to represent an enum.
166#[derive(Clone, Copy)]
167#[cfg_attr(test, derive(Debug))]
168pub enum Representation {
169 /// [`u8`].
170 U8,
171 /// [`u16`].
172 U16,
173 /// [`u32`].
174 U32,
175 /// [`u64`].
176 U64,
177 /// [`u128`].
178 U128,
179 /// [`usize`].
180 USize,
181 /// [`i8`].
182 I8,
183 /// [`i16`].
184 I16,
185 /// [`i32`].
186 I32,
187 /// [`i64`].
188 I64,
189 /// [`i128`].
190 I128,
191 /// [`isize`].
192 ISize,
193}
194
195impl Representation {
196 /// Parse an [`struct@Ident`] to a valid representation if it is.
197 fn parse(ident: &Ident) -> Option<Self> {
198 Some(if ident == "u8" {
199 Self::U8
200 } else if ident == "u16" {
201 Self::U16
202 } else if ident == "u32" {
203 Self::U32
204 } else if ident == "u64" {
205 Self::U64
206 } else if ident == "u128" {
207 Self::U128
208 } else if ident == "usize" {
209 Self::USize
210 } else if ident == "i8" {
211 Self::I8
212 } else if ident == "i16" {
213 Self::I16
214 } else if ident == "i32" {
215 Self::I32
216 } else if ident == "i64" {
217 Self::I64
218 } else if ident == "i128" {
219 Self::I128
220 } else if ident == "isize" {
221 Self::ISize
222 } else {
223 return None;
224 })
225 }
226
227 /// Convert this [`Representation`] to a [`TokenStream`].
228 pub fn to_token(self) -> TokenStream {
229 let ident = match self {
230 Representation::U8 => "u8",
231 Representation::U16 => "u16",
232 Representation::U32 => "u32",
233 Representation::U64 => "u64",
234 Representation::U128 => "u128",
235 Representation::USize => "usize",
236 Representation::I8 => "i8",
237 Representation::I16 => "i16",
238 Representation::I32 => "i32",
239 Representation::I64 => "i64",
240 Representation::I128 => "i128",
241 Representation::ISize => "isize",
242 };
243
244 TokenTree::from(Ident::new(ident, Span::call_site())).into()
245 }
246}
247
248impl ToTokens for Representation {
249 fn to_tokens(&self, tokens: &mut TokenStream) {
250 tokens.extend(self.to_token());
251 }
252}
253