Skip to content

Commit b89c321

Browse files
committed
Implement skip groups
1 parent 230e44e commit b89c321

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+325
-275
lines changed

.config/topic.dic

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
destructure/G
55
enum/S
66
FQS
7+
invariants
78
io
89
MSRV
910
std's

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,14 @@ assert_eq!(format!("{:?}", EnumExample::A(42)), "A");
138138
```
139139

140140
Selective skipping of fields for certain traits is also an option, both in
141-
`skip` and `skip_inner`:
141+
`skip` and `skip_inner`. To prevent breaking invariants defined for these
142+
traits, some of them can only be skipped in groups. The following groups are
143+
available:
144+
- [`Debug`]
145+
- `EqHashOrd`: Skips [`Eq`], [`Hash`], [`Ord`], [`PartialOrd`] and
146+
[`PartialEq`].
147+
- [`Hash`]
148+
- `Zeroize`: Skips [`Zeroize`] and [`ZeroizeOnDrop`].
142149

143150
```rust
144151
#[derive_where(Debug, PartialEq)]

src/attr.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ pub use self::{
1414
default::Default,
1515
field::FieldAttr,
1616
item::{DeriveTrait, DeriveWhere, Generic, ItemAttr},
17-
skip::Skip,
17+
skip::{Skip, SkipGroup},
1818
variant::VariantAttr,
1919
};

src/attr/default.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ impl Default {
2626
let mut impl_default = false;
2727

2828
for derive_where in derive_wheres {
29-
if derive_where.trait_(&Trait::Default).is_some() {
29+
if derive_where.contains(Trait::Default) {
3030
impl_default = true;
3131
break;
3232
}

src/attr/item.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use syn::{
1111
TraitBoundModifier, Type, TypeParamBound, TypePath, WhereClause, WherePredicate,
1212
};
1313

14-
use crate::{util, Error, Item, Skip, Trait, TraitImpl, DERIVE_WHERE};
14+
use crate::{util, Error, Item, Skip, SkipGroup, Trait, TraitImpl, DERIVE_WHERE};
1515

1616
/// Attributes on item.
1717
#[derive(Default)]
@@ -209,11 +209,11 @@ impl DeriveWhere {
209209
})
210210
}
211211

212-
/// Returns selected [`DeriveTrait`] if present.
213-
pub fn trait_(&self, trait_: &Trait) -> Option<&DeriveTrait> {
212+
/// Returns `true` if [`Trait`] is present.
213+
pub fn contains(&self, trait_: Trait) -> bool {
214214
self.traits
215215
.iter()
216-
.find(|derive_trait| derive_trait == trait_)
216+
.any(|derive_trait| derive_trait == trait_)
217217
}
218218

219219
/// Returns `true` if any [`CustomBound`](Generic::CustomBound) is present.
@@ -240,7 +240,9 @@ impl DeriveWhere {
240240

241241
/// Returns `true` if any [`Trait`] supports skipping.
242242
pub fn any_skip(&self) -> bool {
243-
self.traits.iter().any(|trait_| trait_.supports_skip())
243+
self.traits
244+
.iter()
245+
.any(|trait_| SkipGroup::trait_supported(**trait_))
244246
}
245247

246248
/// Create [`WhereClause`] for the given parameters.

src/attr/skip.rs

Lines changed: 137 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
33
use std::default::Default;
44

5-
use syn::{spanned::Spanned, Meta, NestedMeta, Result};
5+
use syn::{spanned::Spanned, Meta, NestedMeta, Path, Result};
66

7-
use crate::{DeriveWhere, Error, Trait, TraitImpl};
7+
use crate::{DeriveWhere, Error, Trait};
88

99
/// Stores what [`Trait`]s to skip this field or variant for.
1010
#[cfg_attr(test, derive(Debug))]
@@ -14,7 +14,7 @@ pub enum Skip {
1414
/// Field skipped for all [`Trait`]s that support it.
1515
All,
1616
/// Field skipped for the [`Trait`]s listed.
17-
Traits(Vec<Trait>),
17+
Traits(Vec<SkipGroup>),
1818
}
1919

2020
impl Default for Skip {
@@ -102,37 +102,34 @@ impl Skip {
102102

103103
for nested_meta in &list.nested {
104104
if let NestedMeta::Meta(Meta::Path(path)) = nested_meta {
105-
let trait_ = Trait::from_path(path)?;
106-
107-
// Don't allow unsupported traits to be skipped.
108-
if trait_.supports_skip() {
109-
// Don't allow to skip the same trait twice.
110-
if traits.contains(&trait_) {
111-
return Err(Error::option_skip_duplicate(
112-
path.span(),
113-
trait_.as_str(),
114-
));
115-
} else {
116-
// Don't allow to skip a trait already set to be skipped in the
117-
// parent.
118-
match skip_inner {
119-
Some(skip_inner) if skip_inner.skip(&trait_) => {
120-
return Err(Error::option_skip_inner(path.span()))
121-
}
122-
_ => {
123-
// Don't allow to skip trait that isn't being implemented.
124-
if derive_wheres.iter().any(|derive_where| {
125-
derive_where.trait_(&trait_).is_some()
126-
}) {
127-
traits.push(trait_)
128-
} else {
129-
return Err(Error::option_skip_trait(path.span()));
130-
}
105+
let skip_group = SkipGroup::from_path(path)?;
106+
107+
// Don't allow to skip the same trait twice.
108+
if traits.contains(&skip_group) {
109+
return Err(Error::option_skip_duplicate(
110+
path.span(),
111+
skip_group.as_str(),
112+
));
113+
} else {
114+
// Don't allow to skip a trait already set to be skipped in the
115+
// parent.
116+
match skip_inner {
117+
Some(skip_inner) if skip_inner.group_skipped(skip_group) => {
118+
return Err(Error::option_skip_inner(path.span()))
119+
}
120+
_ => {
121+
// Don't allow to skip trait that isn't being implemented.
122+
if derive_wheres.iter().any(|derive_where| {
123+
skip_group
124+
.traits()
125+
.any(|trait_| derive_where.contains(trait_))
126+
}) {
127+
traits.push(skip_group)
128+
} else {
129+
return Err(Error::option_skip_trait(path.span()));
131130
}
132131
}
133132
}
134-
} else {
135-
return Err(Error::option_skip_support(path.span(), trait_.as_str()));
136133
}
137134
} else {
138135
return Err(Error::option_syntax(nested_meta.span()));
@@ -147,15 +144,118 @@ impl Skip {
147144

148145
/// Returns `true` if this item, variant or field is skipped with the given
149146
/// [`Trait`].
150-
pub fn skip(&self, trait_: &Trait) -> bool {
147+
pub fn trait_skipped(&self, trait_: Trait) -> bool {
148+
match self {
149+
Skip::None => false,
150+
Skip::All => SkipGroup::trait_supported(trait_),
151+
Skip::Traits(skip_groups) => skip_groups
152+
.iter()
153+
.any(|skip_group| skip_group.traits().any(|this_trait| this_trait == trait_)),
154+
}
155+
}
156+
157+
/// Returns `true` if this item, variant or field is skipped with the given
158+
/// [`SkipGroup`].
159+
pub fn group_skipped(&self, group: SkipGroup) -> bool {
151160
match self {
152161
Skip::None => false,
153-
Skip::All => trait_.supports_skip(),
154-
Skip::Traits(traits) => {
155-
let skip = traits.contains(trait_);
156-
debug_assert!(!skip || trait_.supports_skip());
157-
skip
162+
Skip::All => true,
163+
Skip::Traits(groups) => groups.iter().any(|this_group| *this_group == group),
164+
}
165+
}
166+
}
167+
168+
/// Available groups of [`Trait`]s to skip.
169+
#[derive(Clone, Copy, Eq, PartialEq)]
170+
#[cfg_attr(test, derive(Debug))]
171+
pub enum SkipGroup {
172+
/// [`Debug`].
173+
Debug,
174+
/// [`Eq`], [`Hash`], [`Ord`], [`PartialEq`] and [`PartialOrd`].
175+
EqHashOrd,
176+
/// [`Hash`].
177+
Hash,
178+
/// [`Zeroize`](https://docs.rs/zeroize/latest/zeroize/trait.Zeroize.html) and
179+
/// [`ZeroizeOnDrop`](https://docs.rs/zeroize/latest/zeroize/trait.ZeroizeOnDrop.html).
180+
#[cfg(feature = "zeroize")]
181+
Zeroize,
182+
}
183+
184+
impl SkipGroup {
185+
/// Create [`SkipGroup`] from [`Path`].
186+
fn from_path(path: &Path) -> Result<Self> {
187+
if let Some(ident) = path.get_ident() {
188+
use SkipGroup::*;
189+
190+
match ident.to_string().as_str() {
191+
"Debug" => Ok(Debug),
192+
"EqHashOrd" => Ok(EqHashOrd),
193+
"Hash" => Ok(Hash),
194+
#[cfg(feature = "zeroize")]
195+
"Zeroize" => Ok(Zeroize),
196+
_ => Err(Error::skip_group(path.span())),
158197
}
198+
} else {
199+
Err(Error::skip_group(path.span()))
200+
}
201+
}
202+
203+
/// [`str`] representation of this [`Trait`].
204+
/// Used to compare against [`Ident`](struct@syn::Ident)s and create error
205+
/// messages.
206+
const fn as_str(self) -> &'static str {
207+
match self {
208+
Self::Debug => "Debug",
209+
Self::EqHashOrd => "EqHashOrd",
210+
Self::Hash => "Hash",
211+
#[cfg(feature = "zeroize")]
212+
Self::Zeroize => "Zeroize",
213+
}
214+
}
215+
216+
/// [`Trait`]s supported by this group.
217+
fn traits(self) -> impl Iterator<Item = Trait> {
218+
match self {
219+
Self::Debug => [Some(Trait::Debug), None, None, None, None]
220+
.into_iter()
221+
.flatten(),
222+
Self::EqHashOrd => [
223+
Some(Trait::Eq),
224+
Some(Trait::Hash),
225+
Some(Trait::Ord),
226+
Some(Trait::PartialEq),
227+
Some(Trait::PartialOrd),
228+
]
229+
.into_iter()
230+
.flatten(),
231+
Self::Hash => [Some(Trait::Hash), None, None, None, None]
232+
.into_iter()
233+
.flatten(),
234+
#[cfg(feature = "zeroize")]
235+
Self::Zeroize => [
236+
Some(Trait::Zeroize),
237+
Some(Trait::ZeroizeOnDrop),
238+
None,
239+
None,
240+
None,
241+
]
242+
.into_iter()
243+
.flatten(),
244+
}
245+
}
246+
247+
/// Returns `true` if [`Trait`] is supported by any group.
248+
pub fn trait_supported(trait_: Trait) -> bool {
249+
match trait_ {
250+
Trait::Clone | Trait::Copy | Trait::Default => false,
251+
Trait::Debug
252+
| Trait::Eq
253+
| Trait::Hash
254+
| Trait::Ord
255+
| Trait::PartialEq
256+
| Trait::PartialOrd => true,
257+
#[cfg(feature = "zeroize")]
258+
Trait::Zeroize | Trait::ZeroizeOnDrop => true,
159259
}
160260
}
161261
}

src/attr/zeroize_fqs.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ impl ZeroizeFqs {
1919

2020
if !derive_wheres
2121
.iter()
22-
.any(|derive_where| derive_where.trait_(&Trait::Zeroize).is_some())
22+
.any(|derive_where| derive_where.contains(Trait::Zeroize))
2323
{
2424
return Err(Error::zeroize(meta.span()));
2525
}

src/data.rs

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -257,22 +257,22 @@ impl<'a> Data<'a> {
257257
}
258258

259259
/// Returns `true` if this [`Data`] has no [`Fields`].
260-
pub fn is_empty(&self, trait_: &Trait) -> bool {
260+
pub fn is_empty(&self, trait_: Trait) -> bool {
261261
self.iter_fields(trait_).count() == 0
262262
}
263263

264264
/// Returns `true` if a field is skipped with that [`Trait`].
265-
pub fn any_skip_trait(&self, trait_: &Trait) -> bool {
266-
self.skip_inner.skip(trait_)
265+
pub fn any_skip_trait(&self, trait_: Trait) -> bool {
266+
self.skip_inner.trait_skipped(trait_)
267267
|| match self.fields() {
268268
Either::Left(fields) => fields.any_skip_trait(trait_),
269269
Either::Right(_) => false,
270270
}
271271
}
272272

273273
/// Returns `true` if all fields are skipped with that [`Trait`].
274-
fn skip(&self, trait_: &Trait) -> bool {
275-
self.skip_inner.skip(trait_)
274+
fn skip(&self, trait_: Trait) -> bool {
275+
self.skip_inner.trait_skipped(trait_)
276276
|| match self.fields() {
277277
Either::Left(fields) => fields.skip(trait_),
278278
Either::Right(_) => false,
@@ -302,9 +302,9 @@ impl<'a> Data<'a> {
302302

303303
/// Returns an [`Iterator`] over [`Field`]s.
304304
pub fn iter_fields(
305-
&'a self,
306-
trait_: &'a Trait,
307-
) -> impl 'a + Iterator<Item = &'a Field> + DoubleEndedIterator {
305+
&self,
306+
trait_: Trait,
307+
) -> impl '_ + Iterator<Item = &'_ Field> + DoubleEndedIterator {
308308
if self.skip(trait_) {
309309
[].iter()
310310
} else {
@@ -317,25 +317,25 @@ impl<'a> Data<'a> {
317317
}
318318

319319
/// Returns an [`Iterator`] over [`Member`]s.
320-
pub fn iter_field_ident(&'a self, trait_: &'a Trait) -> impl 'a + Iterator<Item = &'a Member> {
320+
pub fn iter_field_ident(&self, trait_: Trait) -> impl '_ + Iterator<Item = &'_ Member> {
321321
self.iter_fields(trait_).map(|field| &field.member)
322322
}
323323

324324
/// Returns an [`Iterator`] over [`struct@Ident`]s used as temporary
325325
/// variables for destructuring `self`.
326326
pub fn iter_self_ident(
327-
&'a self,
328-
trait_: &'a Trait,
329-
) -> impl Iterator<Item = &'a Ident> + DoubleEndedIterator {
327+
&self,
328+
trait_: Trait,
329+
) -> impl Iterator<Item = &'_ Ident> + DoubleEndedIterator {
330330
self.iter_fields(trait_).map(|field| &field.self_ident)
331331
}
332332

333333
/// Returns an [`Iterator`] over [`struct@Ident`]s used as temporary
334334
/// variables for destructuring `other`.
335335
pub fn iter_other_ident(
336-
&'a self,
337-
trait_: &'a Trait,
338-
) -> impl Iterator<Item = &'a Ident> + DoubleEndedIterator {
336+
&self,
337+
trait_: Trait,
338+
) -> impl Iterator<Item = &'_ Ident> + DoubleEndedIterator {
339339
self.iter_fields(trait_).map(|field| &field.other_ident)
340340
}
341341
}

src/data/field.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ impl<'a> Field<'a> {
131131
}
132132

133133
/// Returns `true` if this field is skipped with the given [`Trait`].
134-
pub fn skip(&self, trait_: &Trait) -> bool {
135-
self.attr.skip.skip(trait_)
134+
pub fn skip(&self, trait_: Trait) -> bool {
135+
self.attr.skip.trait_skipped(trait_)
136136
}
137137
}

src/data/fields.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,12 +179,12 @@ impl<'a> Fields<'a> {
179179
}
180180

181181
/// Returns `true` if any field is skipped with that [`Trait`].
182-
pub fn any_skip_trait(&self, trait_: &Trait) -> bool {
182+
pub fn any_skip_trait(&self, trait_: Trait) -> bool {
183183
self.fields.iter().any(|field| field.skip(trait_))
184184
}
185185

186186
/// Returns `true` if all fields are skipped with that [`Trait`].
187-
pub fn skip(&self, trait_: &Trait) -> bool {
187+
pub fn skip(&self, trait_: Trait) -> bool {
188188
self.fields.iter().all(|field| field.skip(trait_))
189189
}
190190
}

0 commit comments

Comments
 (0)