derive_where/attr/
skip.rs

1//! Attribute parsing for the `skip` and `skip_inner` options.
2
3use std::default::Default;
4
5use syn::{spanned::Spanned, Meta, Path, Result};
6
7use crate::{trait_::DeriveTrait, util::MetaListExt, DeriveWhere, Error, Trait};
8
9/// Stores what [`Trait`]s to skip this field or variant for.
10#[cfg_attr(test, derive(Debug))]
11pub enum Skip {
12	/// Field skipped for no [`Trait`].
13	None,
14	/// Field skipped for all [`Trait`]s that support it.
15	All,
16	/// Field skipped for the [`Trait`]s listed.
17	Traits(Vec<SkipGroup>),
18}
19
20impl Default for Skip {
21	fn default() -> Self {
22		Skip::None
23	}
24}
25
26impl Skip {
27	/// Token used for the `skip` option.
28	pub const SKIP: &'static str = "skip";
29	/// Token used for the `skip_inner` option.
30	pub const SKIP_INNER: &'static str = "skip_inner";
31
32	/// Returns `true` if variant is [`Skip::None`].
33	pub fn is_none(&self) -> bool {
34		matches!(self, Skip::None)
35	}
36
37	/// Adds a [`Meta`] to this [`Skip`].
38	pub fn add_attribute(
39		&mut self,
40		derive_wheres: &[DeriveWhere],
41		skip_inner: Option<&Skip>,
42		meta: &Meta,
43	) -> Result<()> {
44		debug_assert!(meta.path().is_ident(Self::SKIP) || meta.path().is_ident(Self::SKIP_INNER));
45
46		match meta {
47			Meta::Path(path) => {
48				// Check for duplicates.
49				if self.is_none() {
50					// Check against parent `skip_inner`.
51					match skip_inner {
52						// Allow `Skip::All` on field if parent has a tighter constraint.
53						Some(Skip::None) | Some(Skip::Traits(..)) | None => {
54							// Don't allow to skip all traits if no trait to be implemented supports
55							// skipping.
56							if derive_wheres
57								.iter()
58								.any(|derive_where| derive_where.any_skip())
59							{
60								*self = Skip::All;
61								Ok(())
62							} else {
63								Err(Error::option_skip_no_trait(path.span()))
64							}
65						}
66						// Don't allow `Skip::All` on field if parent already covers it.
67						Some(Skip::All) => Err(Error::option_skip_inner(path.span())),
68					}
69				} else {
70					Err(Error::option_duplicate(
71						path.span(),
72						&meta
73							.path()
74							.get_ident()
75							.expect("unexpected skip syntax")
76							.to_string(),
77					))
78				}
79			}
80			Meta::List(list) => {
81				let nested = list.parse_non_empty_nested_metas()?;
82
83				// Get traits already set to be skipped.
84				let traits = match self {
85					// If no traits are set, change to empty `Skip::Traits` and return that.
86					Skip::None => {
87						*self = Skip::Traits(Vec::new());
88
89						if let Skip::Traits(traits) = self {
90							traits
91						} else {
92							unreachable!("unexpected variant")
93						}
94					}
95					// If we are already skipping all traits, we can't skip again with constraints.
96					Skip::All => return Err(Error::option_skip_all(list.span())),
97					Skip::Traits(traits) => traits,
98				};
99
100				for nested_meta in &nested {
101					if let Meta::Path(path) = nested_meta {
102						let skip_group = SkipGroup::from_path(path)?;
103
104						if skip_group == SkipGroup::Clone
105							&& derive_wheres.iter().any(|derive_where| {
106								derive_where
107									.traits
108									.iter()
109									.any(|trait_| trait_ == &DeriveTrait::Copy)
110							}) {
111							return Err(Error::unable_to_skip_clone_while_deriving_copy(
112								path.span(),
113							));
114						}
115
116						// Don't allow to skip the same trait twice.
117						if traits.contains(&skip_group) {
118							return Err(Error::option_skip_duplicate(
119								path.span(),
120								skip_group.as_str(),
121							));
122						} else {
123							// Don't allow to skip a trait already set to be skipped in the
124							// parent.
125							match skip_inner {
126								Some(skip_inner) if skip_inner.group_skipped(skip_group) => {
127									return Err(Error::option_skip_inner(path.span()))
128								}
129								_ => {
130									// Don't allow to skip trait that isn't being implemented.
131									if derive_wheres.iter().any(|derive_where| {
132										skip_group
133											.traits()
134											.any(|trait_| derive_where.contains(trait_))
135									}) {
136										traits.push(skip_group)
137									} else {
138										return Err(Error::option_skip_trait(path.span()));
139									}
140								}
141							}
142						}
143					} else {
144						return Err(Error::option_syntax(nested_meta.span()));
145					}
146				}
147
148				Ok(())
149			}
150			_ => Err(Error::option_syntax(meta.span())),
151		}
152	}
153
154	/// Returns `true` if this item, variant or field is skipped with the given
155	/// [`Trait`].
156	pub fn trait_skipped(&self, trait_: Trait) -> bool {
157		match self {
158			Skip::None => false,
159			Skip::All => SkipGroup::trait_supported_by_skip_all(trait_),
160			Skip::Traits(skip_groups) => skip_groups
161				.iter()
162				.any(|skip_group| skip_group.traits().any(|this_trait| this_trait == trait_)),
163		}
164	}
165
166	/// Returns `true` if this item, variant or field is skipped with the given
167	/// [`SkipGroup`].
168	pub fn group_skipped(&self, group: SkipGroup) -> bool {
169		match self {
170			Skip::None => false,
171			Skip::All => true,
172			Skip::Traits(groups) => groups.contains(&group),
173		}
174	}
175}
176
177/// Available groups of [`Trait`]s to skip.
178#[derive(Clone, Copy, Eq, PartialEq)]
179#[cfg_attr(test, derive(Debug))]
180pub enum SkipGroup {
181	/// [`Clone`].
182	Clone,
183	/// [`Debug`].
184	Debug,
185	/// [`Eq`], [`Hash`], [`Ord`], [`PartialEq`] and [`PartialOrd`].
186	EqHashOrd,
187	/// [`Hash`].
188	Hash,
189	/// [`Zeroize`](https://docs.rs/zeroize/latest/zeroize/trait.Zeroize.html) and
190	/// [`ZeroizeOnDrop`](https://docs.rs/zeroize/latest/zeroize/trait.ZeroizeOnDrop.html).
191	#[cfg(feature = "zeroize")]
192	Zeroize,
193}
194
195impl SkipGroup {
196	/// Create [`SkipGroup`] from [`Path`].
197	fn from_path(path: &Path) -> Result<Self> {
198		if let Some(ident) = path.get_ident() {
199			use SkipGroup::*;
200
201			match ident.to_string().as_str() {
202				"Clone" => Ok(Clone),
203				"Debug" => Ok(Debug),
204				"EqHashOrd" => Ok(EqHashOrd),
205				"Hash" => Ok(Hash),
206				#[cfg(feature = "zeroize")]
207				"Zeroize" => Ok(Zeroize),
208				_ => Err(Error::skip_group(path.span())),
209			}
210		} else {
211			Err(Error::skip_group(path.span()))
212		}
213	}
214
215	/// [`str`] representation of this [`Trait`].
216	/// Used to compare against [`Ident`](struct@syn::Ident)s and create error
217	/// messages.
218	const fn as_str(self) -> &'static str {
219		match self {
220			Self::Clone => "Clone",
221			Self::Debug => "Debug",
222			Self::EqHashOrd => "EqHashOrd",
223			Self::Hash => "Hash",
224			#[cfg(feature = "zeroize")]
225			Self::Zeroize => "Zeroize",
226		}
227	}
228
229	/// [`Trait`]s supported by this group.
230	fn traits(self) -> impl Iterator<Item = Trait> {
231		match self {
232			Self::Clone => [Some(Trait::Clone), None, None, None, None]
233				.into_iter()
234				.flatten(),
235			Self::Debug => [Some(Trait::Debug), None, None, None, None]
236				.into_iter()
237				.flatten(),
238			Self::EqHashOrd => [
239				Some(Trait::Eq),
240				Some(Trait::Hash),
241				Some(Trait::Ord),
242				Some(Trait::PartialEq),
243				Some(Trait::PartialOrd),
244			]
245			.into_iter()
246			.flatten(),
247			Self::Hash => [Some(Trait::Hash), None, None, None, None]
248				.into_iter()
249				.flatten(),
250			#[cfg(feature = "zeroize")]
251			Self::Zeroize => [
252				Some(Trait::Zeroize),
253				Some(Trait::ZeroizeOnDrop),
254				None,
255				None,
256				None,
257			]
258			.into_iter()
259			.flatten(),
260		}
261	}
262
263	/// Returns `true` if [`Trait`] is supported by any group.
264	pub fn trait_supported_by_skip_all(trait_: Trait) -> bool {
265		match trait_ {
266			Trait::Clone | Trait::Copy | Trait::Default => false,
267			Trait::Debug
268			| Trait::Eq
269			| Trait::Hash
270			| Trait::Ord
271			| Trait::PartialEq
272			| Trait::PartialOrd => true,
273			#[cfg(feature = "serde")]
274			Trait::Deserialize | Trait::Serialize => false,
275			#[cfg(feature = "zeroize")]
276			Trait::Zeroize | Trait::ZeroizeOnDrop => true,
277		}
278	}
279}