1use 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#[cfg_attr(test, derive(Debug))]
11#[allow(clippy::large_enum_variant)]
12pub enum Item<'a> {
13 Enum {
15 discriminant: Discriminant,
17 ident: &'a Ident,
19 incomparable: Incomparable,
21 variants: Vec<Data<'a>>,
23 },
24 Item(Data<'a>),
26}
27
28impl Item<'_> {
29 pub fn ident(&self) -> &Ident {
31 match self {
32 Item::Item(data) => data.ident,
33 Item::Enum { ident, .. } => ident,
34 }
35 }
36
37 pub fn is_enum(&self) -> bool {
39 match self {
40 Item::Enum { .. } => true,
41 Item::Item(_) => false,
42 }
43 }
44
45 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 #[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 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 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#[derive(Clone, Copy)]
98#[cfg_attr(test, derive(Debug))]
99pub enum Discriminant {
100 Single,
102 Unit,
104 Data,
106 UnitRepr(Representation),
108 DataRepr(Representation),
110}
111
112impl Discriminant {
113 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#[derive(Clone, Copy)]
167#[cfg_attr(test, derive(Debug))]
168pub enum Representation {
169 U8,
171 U16,
173 U32,
175 U64,
177 U128,
179 USize,
181 I8,
183 I16,
185 I32,
187 I64,
189 I128,
191 ISize,
193}
194
195impl Representation {
196 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 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}