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::{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 // Don't allow to skip the same trait twice.
105 if traits.contains(&skip_group) {
106 return Err(Error::option_skip_duplicate(
107 path.span(),
108 skip_group.as_str(),
109 ));
110 } else {
111 // Don't allow to skip a trait already set to be skipped in the
112 // parent.
113 match skip_inner {
114 Some(skip_inner) if skip_inner.group_skipped(skip_group) => {
115 return Err(Error::option_skip_inner(path.span()))
116 }
117 _ => {
118 // Don't allow to skip trait that isn't being implemented.
119 if derive_wheres.iter().any(|derive_where| {
120 skip_group
121 .traits()
122 .any(|trait_| derive_where.contains(trait_))
123 }) {
124 traits.push(skip_group)
125 } else {
126 return Err(Error::option_skip_trait(path.span()));
127 }
128 }
129 }
130 }
131 } else {
132 return Err(Error::option_syntax(nested_meta.span()));
133 }
134 }
135
136 Ok(())
137 }
138 _ => Err(Error::option_syntax(meta.span())),
139 }
140 }
141
142 /// Returns `true` if this item, variant or field is skipped with the given
143 /// [`Trait`].
144 pub fn trait_skipped(&self, trait_: Trait) -> bool {
145 match self {
146 Skip::None => false,
147 Skip::All => SkipGroup::trait_supported(trait_),
148 Skip::Traits(skip_groups) => skip_groups
149 .iter()
150 .any(|skip_group| skip_group.traits().any(|this_trait| this_trait == trait_)),
151 }
152 }
153
154 /// Returns `true` if this item, variant or field is skipped with the given
155 /// [`SkipGroup`].
156 pub fn group_skipped(&self, group: SkipGroup) -> bool {
157 match self {
158 Skip::None => false,
159 Skip::All => true,
160 Skip::Traits(groups) => groups.iter().any(|this_group| *this_group == group),
161 }
162 }
163}
164
165/// Available groups of [`Trait`]s to skip.
166#[derive(Clone, Copy, Eq, PartialEq)]
167#[cfg_attr(test, derive(Debug))]
168pub enum SkipGroup {
169 /// [`Debug`].
170 Debug,
171 /// [`Eq`], [`Hash`], [`Ord`], [`PartialEq`] and [`PartialOrd`].
172 EqHashOrd,
173 /// [`Hash`].
174 Hash,
175 /// [`Zeroize`](https://docs.rs/zeroize/latest/zeroize/trait.Zeroize.html) and
176 /// [`ZeroizeOnDrop`](https://docs.rs/zeroize/latest/zeroize/trait.ZeroizeOnDrop.html).
177 #[cfg(feature = "zeroize")]
178 Zeroize,
179}
180
181impl SkipGroup {
182 /// Create [`SkipGroup`] from [`Path`].
183 fn from_path(path: &Path) -> Result<Self> {
184 if let Some(ident) = path.get_ident() {
185 use SkipGroup::*;
186
187 match ident.to_string().as_str() {
188 "Debug" => Ok(Debug),
189 "EqHashOrd" => Ok(EqHashOrd),
190 "Hash" => Ok(Hash),
191 #[cfg(feature = "zeroize")]
192 "Zeroize" => Ok(Zeroize),
193 _ => Err(Error::skip_group(path.span())),
194 }
195 } else {
196 Err(Error::skip_group(path.span()))
197 }
198 }
199
200 /// [`str`] representation of this [`Trait`].
201 /// Used to compare against [`Ident`](struct@syn::Ident)s and create error
202 /// messages.
203 const fn as_str(self) -> &'static str {
204 match self {
205 Self::Debug => "Debug",
206 Self::EqHashOrd => "EqHashOrd",
207 Self::Hash => "Hash",
208 #[cfg(feature = "zeroize")]
209 Self::Zeroize => "Zeroize",
210 }
211 }
212
213 /// [`Trait`]s supported by this group.
214 fn traits(self) -> impl Iterator<Item = Trait> {
215 match self {
216 Self::Debug => [Some(Trait::Debug), None, None, None, None]
217 .into_iter()
218 .flatten(),
219 Self::EqHashOrd => [
220 Some(Trait::Eq),
221 Some(Trait::Hash),
222 Some(Trait::Ord),
223 Some(Trait::PartialEq),
224 Some(Trait::PartialOrd),
225 ]
226 .into_iter()
227 .flatten(),
228 Self::Hash => [Some(Trait::Hash), None, None, None, None]
229 .into_iter()
230 .flatten(),
231 #[cfg(feature = "zeroize")]
232 Self::Zeroize => [
233 Some(Trait::Zeroize),
234 Some(Trait::ZeroizeOnDrop),
235 None,
236 None,
237 None,
238 ]
239 .into_iter()
240 .flatten(),
241 }
242 }
243
244 /// Returns `true` if [`Trait`] is supported by any group.
245 pub fn trait_supported(trait_: Trait) -> bool {
246 match trait_ {
247 Trait::Clone | Trait::Copy | Trait::Default => false,
248 Trait::Debug
249 | Trait::Eq
250 | Trait::Hash
251 | Trait::Ord
252 | Trait::PartialEq
253 | Trait::PartialOrd => true,
254 #[cfg(feature = "zeroize")]
255 Trait::Zeroize | Trait::ZeroizeOnDrop => true,
256 }
257 }
258}
259