Skip to main content

derive_where/
item.rs

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