1 | //! Attribute parsing for the `skip` and `skip_inner` options. |
2 | |
3 | use std::default::Default; |
4 | |
5 | use syn::{spanned::Spanned, Meta, Path, Result}; |
6 | |
7 | use 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))] |
11 | pub 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 | |
20 | impl Default for Skip { |
21 | fn default() -> Self { |
22 | Skip::None |
23 | } |
24 | } |
25 | |
26 | impl 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))] |
168 | pub 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 | |
181 | impl 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 | |