Skip to content

Commit 5105a9e

Browse files
committed
Incomparable variant attribute
1 parent d2c1b38 commit 5105a9e

24 files changed

+1014
-134
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## [Unreleased]
8+
### Added
9+
- `incomparable` variant and item attribute for `PartialEq` and `PartialOrd`
10+
derives, yielding false on all comparisons but `!=`.
11+
712
## [1.0.0] - 2022-07-16
813
- No changes.
914

@@ -31,6 +36,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3136
### Added
3237
- Initial release.
3338

39+
[unreleased]: https://github.com/ModProg/derive-where/compare/v1.0.0...HEAD
3440
[1.0.0]: https://github.com/ModProg/derive-where/compare/v1.0.0-rc.3...v1.0.0
3541
[1.0.0-rc.3]: https://github.com/ModProg/derive-where/compare/v1.0.0-rc.2...v1.0.0-rc.3
3642
[1.0.0-rc.2]: https://github.com/ModProg/derive-where/compare/v1.0.0-rc.1...v1.0.0-rc.2

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ syn = { version = "1.0.56", default-features = false, features = [
3939
] }
4040

4141
[dev-dependencies]
42+
pretty_assertions = "1"
4243
trybuild = { version = "1.0.18", default-features = false }
4344
zeroize_ = { version = "1.5", package = "zeroize", default-features = false }
4445

README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,43 @@ assert_ne!(
162162
);
163163
```
164164

165+
### Incomparable variants/items
166+
167+
Similar to the `skip` attribute, `incomparable` can be used to skip variants
168+
or items in [`PartialEq`] and [`PartialOrd`] trait implementations, meaning
169+
they will always yield `false` for `eq` and `None` for `partial_cmp`. This
170+
results in all comparisons but `!=`, i.e. `==`, `<`, `<=`, `>=` and `>`,
171+
with the marked variant or struct evaluating to `false`.
172+
173+
```rust
174+
# use derive_where::derive_where;
175+
#[derive(Debug)]
176+
#[derive_where(PartialEq, PartialOrd)]
177+
enum EnumExample {
178+
#[derive_where(incomparable)]
179+
Incomparable,
180+
Comparable,
181+
}
182+
assert_eq!(EnumExample::Comparable, EnumExample::Comparable);
183+
assert_ne!(EnumExample::Incomparable, EnumExample::Incomparable);
184+
assert!(!(EnumExample::Comparable >= EnumExample::Incomparable));
185+
assert!(!(EnumExample::Comparable <= EnumExample::Incomparable));
186+
assert!(!(EnumExample::Incomparable >= EnumExample::Incomparable));
187+
assert!(!(EnumExample::Incomparable <= EnumExample::Incomparable));
188+
189+
#[derive(Debug)]
190+
#[derive_where(PartialEq, PartialOrd)]
191+
#[derive_where(incomparable)]
192+
struct StructExample;
193+
194+
assert_ne!(StructExample, StructExample);
195+
assert!(!(StructExample >= StructExample));
196+
assert!(!(StructExample <= StructExample));
197+
```
198+
199+
Note that it is not possible to use `incomparable` with [`Eq`] or [`Ord`] as
200+
that would break their invariants.
201+
165202
### `Zeroize` options
166203

167204
`Zeroize` has two options:

src/attr.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
33
mod default;
44
mod field;
5+
mod incomparable;
56
mod item;
67
mod skip;
78
mod variant;
@@ -13,6 +14,7 @@ pub use self::zeroize_fqs::ZeroizeFqs;
1314
pub use self::{
1415
default::Default,
1516
field::FieldAttr,
17+
incomparable::Incomparable,
1618
item::{DeriveTrait, DeriveWhere, Generic, ItemAttr},
1719
skip::{Skip, SkipGroup},
1820
variant::VariantAttr,

src/attr/default.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ impl Default {
1515
/// Token used for the `default` option.
1616
pub const DEFAULT: &'static str = "default";
1717

18-
/// Adds a [`Meta`] to this [`Default`](self).
18+
/// Adds a [`Meta`] to this [`Default`](Self).
1919
pub fn add_attribute(&mut self, meta: &Meta, derive_wheres: &[DeriveWhere]) -> Result<()> {
2020
debug_assert!(meta.path().is_ident(Self::DEFAULT));
2121

src/attr/incomparable.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
//! Attribute parsing for the `incomparable` option.
2+
3+
use proc_macro2::Span;
4+
use syn::{spanned::Spanned, Meta, Result};
5+
6+
use crate::{attr::DeriveTrait, DeriveWhere, Error};
7+
8+
/// Stores if this variant should be incomparable when implementing
9+
/// [`PartialEq`] or [`PartialOrd`].
10+
#[derive(Clone, Copy, Default)]
11+
#[cfg_attr(test, derive(Debug))]
12+
pub struct Incomparable(pub Option<Span>);
13+
14+
impl Incomparable {
15+
/// Token used for the `incomparable` option.
16+
pub const INCOMPARABLE: &'static str = "incomparable";
17+
18+
/// Adds a [`Meta`] to this [`Incomparable`].
19+
pub fn add_attribute(&mut self, meta: &Meta, derive_wheres: &[DeriveWhere]) -> Result<()> {
20+
debug_assert!(meta.path().is_ident(Self::INCOMPARABLE));
21+
22+
if let Meta::Path(path) = meta {
23+
if self.0.is_some() {
24+
Err(Error::option_duplicate(path.span(), Self::INCOMPARABLE))
25+
} else {
26+
let mut impl_cmp = false;
27+
28+
for trait_ in derive_wheres
29+
.iter()
30+
.flat_map(|derive_where| derive_where.traits.iter())
31+
{
32+
match trait_ {
33+
DeriveTrait::Eq | DeriveTrait::Ord => {
34+
return Err(Error::non_partial_incomparable(path.span()));
35+
}
36+
DeriveTrait::PartialEq | DeriveTrait::PartialOrd => impl_cmp = true,
37+
_ => {}
38+
}
39+
}
40+
41+
if impl_cmp {
42+
self.0 = Some(path.span());
43+
Ok(())
44+
} else {
45+
Err(Error::incomparable(path.span()))
46+
}
47+
}
48+
} else {
49+
Err(Error::option_syntax(meta.span()))
50+
}
51+
}
52+
}

src/attr/item.rs

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

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

1616
/// Attributes on item.
1717
#[derive(Default)]
1818
pub struct ItemAttr {
1919
/// [`Trait`]s to skip all fields for.
2020
pub skip_inner: Skip,
21+
/// Comparing item will yield `false` for [`PartialEq`] and [`None`] for
22+
/// [`PartialOrd`].
23+
pub incomparable: Incomparable,
2124
/// [`DeriveWhere`]s on this item.
2225
pub derive_wheres: Vec<DeriveWhere>,
2326
}
@@ -27,6 +30,7 @@ impl ItemAttr {
2730
pub fn from_attrs(span: Span, data: &Data, attrs: &[Attribute]) -> Result<Self> {
2831
let mut self_ = ItemAttr::default();
2932
let mut skip_inners = Vec::new();
33+
let mut incomparables = Vec::new();
3034

3135
for attr in attrs {
3236
if attr.path.is_ident(DERIVE_WHERE) {
@@ -52,6 +56,9 @@ impl ItemAttr {
5256
// Don't parse `Skip` yet, because it needs access to all
5357
// `DeriveWhere`s.
5458
skip_inners.push(meta);
59+
} else if meta.path().is_ident(Incomparable::INCOMPARABLE) {
60+
// Needs to be parsed after all traits are known.
61+
incomparables.push(meta)
5562
} else if meta.path().is_ident("crate") {
5663
// Do nothing, we checked this before
5764
// already.
@@ -119,14 +126,20 @@ impl ItemAttr {
119126
}
120127
}
121128

122-
// Delayed parsing of `skip_inner` to get access to all traits to be
123-
// implemented.
129+
// Delayed parsing of `skip_inner` and `incomparable` to get access to all
130+
// traits to be implemented.
124131
for meta in skip_inners {
125132
self_
126133
.skip_inner
127134
.add_attribute(&self_.derive_wheres, None, &meta)?;
128135
}
129136

137+
for meta in incomparables {
138+
self_
139+
.incomparable
140+
.add_attribute(&meta, &self_.derive_wheres)?;
141+
}
142+
130143
Ok(self_)
131144
}
132145
}

src/attr/variant.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
use syn::{spanned::Spanned, Attribute, Fields, Meta, NestedMeta, Result, Variant};
44

5-
use crate::{Default, DeriveWhere, Error, Skip, DERIVE_WHERE};
5+
use crate::{Default, DeriveWhere, Error, Incomparable, Skip, DERIVE_WHERE};
66

77
/// Attributes on variant.
88
#[derive(Default)]
@@ -11,6 +11,9 @@ pub struct VariantAttr {
1111
pub default: Default,
1212
/// [`Trait`](crate::Trait)s to skip all fields for.
1313
pub skip_inner: Skip,
14+
/// Comparing variant will yield `false` for [`PartialEq`] and [`None`] for
15+
/// [`PartialOrd`].
16+
pub incomparable: Incomparable,
1417
}
1518

1619
impl VariantAttr {
@@ -67,6 +70,8 @@ impl VariantAttr {
6770
}
6871
} else if meta.path().is_ident(Default::DEFAULT) {
6972
self.default.add_attribute(meta, derive_wheres)?;
73+
} else if meta.path().is_ident(Incomparable::INCOMPARABLE) {
74+
self.incomparable.add_attribute(meta, derive_wheres)?;
7075
} else {
7176
return Err(Error::option(meta.path().span()));
7277
}

0 commit comments

Comments
 (0)