1 | //! Intermediate representation of item data. |
2 | |
3 | use proc_macro2::{Ident, Span, TokenStream, TokenTree}; |
4 | use quote::ToTokens; |
5 | use syn::{punctuated::Punctuated, spanned::Spanned, Attribute, Meta, Result, Token, Variant}; |
6 | |
7 | use 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)] |
12 | pub 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 | |
28 | impl 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))] |
99 | pub 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 | |
112 | impl 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))] |
168 | pub 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 | |
195 | impl 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 | |
248 | impl ToTokens for Representation { |
249 | fn to_tokens(&self, tokens: &mut TokenStream) { |
250 | tokens.extend(self.to_token()); |
251 | } |
252 | } |
253 | |