derive_where/
item.rs

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}