derive_where/attr/
skip.rs1use std::default::Default;
4
5use syn::{spanned::Spanned, Meta, Path, Result};
6
7use crate::{trait_::DeriveTrait, util::MetaListExt, DeriveWhere, Error, Trait};
8
9#[cfg_attr(test, derive(Debug))]
11pub enum Skip {
12 None,
14 All,
16 Traits(Vec<SkipGroup>),
18}
19
20impl Default for Skip {
21 fn default() -> Self {
22 Skip::None
23 }
24}
25
26impl Skip {
27 pub const SKIP: &'static str = "skip";
29 pub const SKIP_INNER: &'static str = "skip_inner";
31
32 pub fn is_none(&self) -> bool {
34 matches!(self, Skip::None)
35 }
36
37 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 if self.is_none() {
50 match skip_inner {
52 Some(Skip::None) | Some(Skip::Traits(..)) | None => {
54 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 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 let traits = match self {
85 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 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 if traits.contains(&skip_group) {
118 return Err(Error::option_skip_duplicate(
119 path.span(),
120 skip_group.as_str(),
121 ));
122 } else {
123 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 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 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 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#[derive(Clone, Copy, Eq, PartialEq)]
179#[cfg_attr(test, derive(Debug))]
180pub enum SkipGroup {
181 Clone,
183 Debug,
185 EqHashOrd,
187 Hash,
189 #[cfg(feature = "zeroize")]
192 Zeroize,
193}
194
195impl SkipGroup {
196 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 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 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 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}