Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .config/topic.dic
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
19
21
≥1
APIs
Changelog
Expand All @@ -11,6 +11,8 @@ FQS
invariants
io
MSRV
Serde
Serde's
std's
struct/S
TODO
Expand Down
10 changes: 10 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,16 @@ jobs:
features: --features safe
- channel: nightly
features: --features nightly
- channel: stable
features: --features serde
- channel: stable
features: --features zeroize
- channel: stable
features: --features zeroize-on-drop
- channel: nightly
features: --features safe,nightly
- channel: stable
features: --features safe,serde
- channel: stable
features: --features safe,zeroize
- channel: stable
Expand All @@ -42,6 +46,12 @@ jobs:
features: --features nightly,zeroize
- channel: nightly
features: --features nightly,zeroize-on-drop
- channel: nightly
features: --features nightly,serde
- channel: stable
features: --features serde,zeroize
- channel: stable
features: --features serde,zeroize-on-drop
- channel: nightly
features: --all-features

Expand Down
16 changes: 14 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,23 @@ jobs:
features:
- ""
- --features safe
- --features serde
- --features zeroize
- --features zeroize-on-drop
- --features safe,serde
- --features safe,zeroize
- --features safe,zeroize-on-drop
- --features serde,zeroize
- --features serde,zeroize-on-drop
include:
- rust: 1.57.0
msrv: true
- rust: nightly
features: --features nightly
- rust: nightly
features: --features safe,nightly
- rust: nightly
features: --features nightly,serde
- rust: nightly
features: --features nightly,zeroize
- rust: nightly
Expand Down Expand Up @@ -83,17 +89,23 @@ jobs:
features:
- ""
- --features safe
- --features serde
- --features zeroize
- --features zeroize-on-drop
- --features safe,serde
- --features safe,zeroize
- --features safe,zeroize-on-drop
- --features serde,zeroize
- --features serde,zeroize-on-drop
include:
- rust: 1.57.0
msrv: true
- rust: nightly
features: --features nightly
- rust: nightly
features: --features safe,nightly
- rust: nightly
features: --features nightly,serde
- rust: nightly
features: --features nightly,zeroize
- rust: nightly
Expand Down Expand Up @@ -127,9 +139,9 @@ jobs:
matrix:
rust:
- version: 1.57.0
features: safe,zeroize-on-drop
features: safe,serde_,zeroize-on-drop
- version: stable
features: safe,zeroize-on-drop
features: safe,serde_,zeroize-on-drop

steps:
- name: Checkout
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- `no_drop` item-level option to `ZeroizeOnDrop` which does not implement
`Drop` but instead only asserts that every field implements `ZeroizeOnDrop`.
- Support deriving Serde's `Deserialize` and `Serialize` trait via Serde's own
proc-macro.

### Changed
- Error messages now point to crate features instead of reporting traits as
unsupported.

### Fixed
- Stop depending on unstable APIs for `Eq` for `ZeroizeOnDrop`.
Expand Down
5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ proc-macro = true
[features]
nightly = []
safe = []
serde = []
zeroize = []
zeroize-on-drop = ["zeroize"]

Expand All @@ -41,8 +42,10 @@ syn = { version = "2", default-features = false, features = [
[dev-dependencies]
pretty_assertions = "1"
rustversion = "1"
serde_ = { package = "serde", version = "1", default-features = false, features = ["derive"] }
serde_test = "1"
trybuild = { version = "1.0.18", default-features = false }
zeroize_ = { version = "1.5", package = "zeroize", default-features = false }
zeroize_ = { package = "zeroize", version = "1.5", default-features = false }

[package.metadata.docs.rs]
all-features = true
Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,13 @@ assert!(!(StructExample <= StructExample));
Note that it is not possible to use `incomparable` with [`Eq`] or [`Ord`] as
that would break their invariants.

### Serde `Deserialize` and `Serialize`

Deriving [`Deserialize`] and [`Serialize`] works as expected. While
derive-where does not offer any attribute options, regular `serde` attributes
can be used. Derive-where will respect
[`#[serde(crate = "...")]`](https://serde.rs/container-attrs.html#crate).

### `Zeroize` options

`Zeroize` has two options:
Expand Down Expand Up @@ -259,11 +266,13 @@ The following traits can be derived with derive-where:
- [`Copy`]
- [`Debug`]
- [`Default`]
- [`Deserialize`]: Only available with the `serde` crate feature.
- [`Eq`]
- [`Hash`]
- [`Ord`]
- [`PartialEq`]
- [`PartialOrd`]
- [`Serialize`]: Only available with the `serde` crate feature.
- [`Zeroize`]: Only available with the `zeroize` crate feature.
- [`ZeroizeOnDrop`]: Only available with the `zeroize` crate feature. If the
`zeroize-on-drop` feature is enabled, it implements [`ZeroizeOnDrop`],
Expand Down Expand Up @@ -352,12 +361,14 @@ conditions.
[`core::intrinsics::discriminant_value`]: https://doc.rust-lang.org/core/intrinsics/fn.discriminant_value.html
[`derive_where`]: https://docs.rs/derive-where/latest/derive_where/attr.derive_where.html
[`Discriminant`]: https://doc.rust-lang.org/core/mem/struct.Discriminant.html
[`Deserialize`]: https://docs.rs/serde/latest/serde/derive.Deserialize.html
[`Drop`]: https://doc.rust-lang.org/core/ops/trait.Drop.html
[`Eq`]: https://doc.rust-lang.org/core/cmp/trait.Eq.html
[`i32`]: https://doc.rust-lang.org/core/primitive.i32.html
[`isize`]: https://doc.rust-lang.org/core/primitive.isize.html
[`Ord`]: https://doc.rust-lang.org/core/cmp/trait.Ord.html
[`PartialEq`]: https://doc.rust-lang.org/core/cmp/trait.PartialEq.html
[`PartialOrd`]: https://doc.rust-lang.org/core/cmp/trait.PartialOrd.html
[`Serialize`]: https://docs.rs/serde/latest/serde/derive.Serialize.html
[`transmute`]: https://doc.rust-lang.org/core/mem/fn.transmute.html
[`unreachable`]: https://doc.rust-lang.org/core/macro.unreachable.html
2 changes: 2 additions & 0 deletions src/attr.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! [`Attribute`](syn::Attribute) parsing for items, variants and fields.

mod crate_;
mod default;
mod field;
mod incomparable;
Expand All @@ -12,6 +13,7 @@ mod zeroize_fqs;
#[cfg(feature = "zeroize")]
pub use self::zeroize_fqs::ZeroizeFqs;
pub use self::{
crate_::parse_crate,
default::Default,
field::FieldAttr,
incomparable::Incomparable,
Expand Down
34 changes: 34 additions & 0 deletions src/attr/crate_.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//! Parsing implementation for `#[derive_where(crate = ...)]`.

use proc_macro2::Span;
use syn::{spanned::Spanned, Expr, ExprLit, ExprPath, Lit, Meta, Path, Result};

use crate::{util, Error, DERIVE_WHERE};

/// Parses `#[derive_where(crate = ...)]`.
pub fn parse_crate(meta: Meta) -> Result<(Path, Span)> {
if let Meta::NameValue(name_value) = meta {
let path = match &name_value.value {
Expr::Lit(ExprLit {
lit: Lit::Str(lit_str),
..
}) => match lit_str.parse::<Path>() {
Ok(path) => path,
Err(error) => return Err(Error::path(lit_str.span(), error)),
},
Expr::Path(ExprPath { path, .. }) => path.clone(),
_ => return Err(Error::option_syntax(name_value.value.span())),
};

if path == util::path_from_strs(&[DERIVE_WHERE]) {
Err(Error::path_unnecessary(
path.span(),
&format!("::{}", DERIVE_WHERE),
))
} else {
Ok((path, name_value.span()))
}
} else {
Err(Error::option_syntax(meta.span()))
}
}
33 changes: 24 additions & 9 deletions src/attr/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,17 @@ use syn::{
parse::{discouraged::Speculative, Parse, ParseStream},
punctuated::Punctuated,
spanned::Spanned,
Attribute, BoundLifetimes, Data, Ident, Meta, PredicateType, Result, Token, Type, TypePath,
WhereClause, WherePredicate,
Attribute, BoundLifetimes, Data, Ident, Meta, Path, PredicateType, Result, Token, Type,
TypePath, WhereClause, WherePredicate,
};

use crate::{trait_::DeriveTrait, Error, Incomparable, Item, Skip, SkipGroup, Trait, DERIVE_WHERE};

/// Attributes on item.
#[derive(Default)]
pub struct ItemAttr {
/// Path to `derive_where` if set by `#[derive_where(crate = ...)]`.
pub crate_: Option<Path>,
/// [`Trait`]s to skip all fields for.
pub skip_inner: Skip,
/// Comparing item will yield `false` for [`PartialEq`] and [`None`] for
Expand Down Expand Up @@ -59,28 +61,29 @@ impl ItemAttr {
// Needs to be parsed after all traits are known.
incomparables.push(meta)
} else if meta.path().is_ident("crate") {
// Do nothing, we checked this before
// already.
let (path, _) = super::parse_crate(meta)
.expect("failed to parse previously parsed attribute");
self_.crate_ = Some(path);
}
// The list can have one item but still not be the `skip_inner`
// attribute, continue with parsing `DeriveWhere`.
else {
self_
.derive_wheres
.push(DeriveWhere::from_attr(span, data, attr)?);
.push(DeriveWhere::from_attr(attrs, span, data, attr)?);
}
}
_ => self_
.derive_wheres
.push(DeriveWhere::from_attr(span, data, attr)?),
.push(DeriveWhere::from_attr(attrs, span, data, attr)?),
}
}
// Anything list that isn't using `,` as separator, is because we expect
// `A, B; C`.
else {
self_
.derive_wheres
.push(DeriveWhere::from_attr(span, data, attr)?)
.push(DeriveWhere::from_attr(attrs, span, data, attr)?)
}
} else {
return Err(Error::option_syntax(attr.meta.span()));
Expand All @@ -93,6 +96,18 @@ impl ItemAttr {
return Err(Error::none(span));
}

// Check for `#[serde(...)]` attributes without `De/Serialize`.
#[cfg(feature = "serde")]
if !self_.derive_wheres.iter().any(|derive_where| {
derive_where.contains(Trait::Deserialize) | derive_where.contains(Trait::Serialize)
}) {
for attr in attrs {
if attr.path().is_ident("serde") {
return Err(Error::serde_without_serde(attr.span()));
}
}
}

// Merge `DeriveWhere`s with the same bounds.
self_
.derive_wheres
Expand Down Expand Up @@ -152,7 +167,7 @@ pub struct DeriveWhere {

impl DeriveWhere {
/// Create [`DeriveWhere`] from [`Attribute`].
fn from_attr(span: Span, data: &Data, attr: &Attribute) -> Result<Self> {
fn from_attr(attrs: &[Attribute], span: Span, data: &Data, attr: &Attribute) -> Result<Self> {
attr.parse_args_with(|input: ParseStream| {
// Parse the attribute input, this should either be:
// - Comma separated traits.
Expand All @@ -169,7 +184,7 @@ impl DeriveWhere {
// Start with parsing a trait.
// Not checking for duplicates here, we do that after merging `derive_where`s
// with the same bounds.
let (span, trait_) = DeriveTrait::from_stream(span, data, input)?;
let (span, trait_) = DeriveTrait::from_stream(attrs, span, data, input)?;
spans.push(span);
traits.push(trait_);

Expand Down
2 changes: 2 additions & 0 deletions src/attr/skip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,8 @@ impl SkipGroup {
| Trait::Ord
| Trait::PartialEq
| Trait::PartialOrd => true,
#[cfg(feature = "serde")]
Trait::Deserialize | Trait::Serialize => false,
#[cfg(feature = "zeroize")]
Trait::Zeroize | Trait::ZeroizeOnDrop => true,
}
Expand Down
Loading