diff --git a/.config/topic.dic b/.config/topic.dic index e6947e15..33f1a002 100644 --- a/.config/topic.dic +++ b/.config/topic.dic @@ -1,4 +1,4 @@ -19 +21 ≥1 APIs Changelog @@ -11,6 +11,8 @@ FQS invariants io MSRV +Serde +Serde's std's struct/S TODO diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 307df28a..6429ef42 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -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 @@ -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 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d831618a..7ea9f784 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,10 +28,14 @@ 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 @@ -39,6 +43,8 @@ jobs: features: --features nightly - rust: nightly features: --features safe,nightly + - rust: nightly + features: --features nightly,serde - rust: nightly features: --features nightly,zeroize - rust: nightly @@ -83,10 +89,14 @@ 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 @@ -94,6 +104,8 @@ jobs: features: --features nightly - rust: nightly features: --features safe,nightly + - rust: nightly + features: --features nightly,serde - rust: nightly features: --features nightly,zeroize - rust: nightly @@ -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 diff --git a/CHANGELOG.md b/CHANGELOG.md index b115f626..ea4ccf12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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`. diff --git a/Cargo.toml b/Cargo.toml index 2e68198b..a7ad76b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ proc-macro = true [features] nightly = [] safe = [] +serde = [] zeroize = [] zeroize-on-drop = ["zeroize"] @@ -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 diff --git a/README.md b/README.md index 2cad2678..8f0dfcae 100644 --- a/README.md +++ b/README.md @@ -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: @@ -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`], @@ -352,6 +361,7 @@ 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 @@ -359,5 +369,6 @@ conditions. [`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 diff --git a/src/attr.rs b/src/attr.rs index 41cc5d95..9dae999d 100644 --- a/src/attr.rs +++ b/src/attr.rs @@ -1,5 +1,6 @@ //! [`Attribute`](syn::Attribute) parsing for items, variants and fields. +mod crate_; mod default; mod field; mod incomparable; @@ -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, diff --git a/src/attr/crate_.rs b/src/attr/crate_.rs new file mode 100644 index 00000000..77a60965 --- /dev/null +++ b/src/attr/crate_.rs @@ -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::() { + 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())) + } +} diff --git a/src/attr/item.rs b/src/attr/item.rs index e6ba68a6..e304d506 100644 --- a/src/attr/item.rs +++ b/src/attr/item.rs @@ -7,8 +7,8 @@ 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}; @@ -16,6 +16,8 @@ use crate::{trait_::DeriveTrait, Error, Incomparable, Item, Skip, SkipGroup, Tra /// Attributes on item. #[derive(Default)] pub struct ItemAttr { + /// Path to `derive_where` if set by `#[derive_where(crate = ...)]`. + pub crate_: Option, /// [`Trait`]s to skip all fields for. pub skip_inner: Skip, /// Comparing item will yield `false` for [`PartialEq`] and [`None`] for @@ -59,20 +61,21 @@ 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 @@ -80,7 +83,7 @@ impl ItemAttr { 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())); @@ -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 @@ -152,7 +167,7 @@ pub struct DeriveWhere { impl DeriveWhere { /// Create [`DeriveWhere`] from [`Attribute`]. - fn from_attr(span: Span, data: &Data, attr: &Attribute) -> Result { + fn from_attr(attrs: &[Attribute], span: Span, data: &Data, attr: &Attribute) -> Result { attr.parse_args_with(|input: ParseStream| { // Parse the attribute input, this should either be: // - Comma separated traits. @@ -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_); diff --git a/src/attr/skip.rs b/src/attr/skip.rs index a29af549..d1bc1064 100644 --- a/src/attr/skip.rs +++ b/src/attr/skip.rs @@ -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, } diff --git a/src/error.rs b/src/error.rs index 1b93e409..d531f0cc 100644 --- a/src/error.rs +++ b/src/error.rs @@ -167,7 +167,8 @@ impl Error { ) } - /// Invalid value for the `derive_where` or `Zeroize` `crate` option. + /// Invalid value for the `derive_where`, `serde` or `Zeroize` `crate` + /// option. pub fn path(span: Span, parse_error: syn::Error) -> syn::Error { syn::Error::new(span, format!("expected path, {}", parse_error)) } @@ -191,6 +192,24 @@ impl Error { ) } + /// Requires crate feature `serde`. + #[cfg(not(feature = "serde"))] + pub fn serde_feature(span: Span) -> syn::Error { + syn::Error::new(span, "requires crate feature `serde`") + } + + /// Requires crate feature `zeroize`. + #[cfg(not(feature = "zeroize"))] + pub fn zeroize_feature(span: Span) -> syn::Error { + syn::Error::new(span, "requires crate feature `zeroize`") + } + + /// Requires crate feature `zeroize-on-drop`. + #[cfg(all(feature = "zeroize", not(feature = "zeroize-on-drop")))] + pub fn zeroize_on_drop_feature(span: Span) -> syn::Error { + syn::Error::new(span, "requires crate feature `zeroize-on-drop`") + } + /// Invalid delimiter in `derive_where` attribute for /// [`Trait`](crate::Trait)s. pub fn derive_where_delimiter(span: Span) -> syn::Error { @@ -282,6 +301,18 @@ impl Error { syn::Error::new(skip_clone, "Cannot skip `Clone` while deriving `Copy`") } + /// Unsupported `serde(...)` without deriving `De/Serialize`. + #[cfg(feature = "serde")] + pub fn serde_without_serde(serde: Span) -> syn::Error { + syn::Error::new(serde, "Found unused `#[serde(...)]`") + } + + /// Conflicting `serde(bound ...)` when deriving `De/Serialize`. + #[cfg(feature = "serde")] + pub fn serde_bound(serde: Span) -> syn::Error { + syn::Error::new(serde, "Found conflicting `#[serde(bound ...)]`") + } + /// List of available [`Trait`](crate::Trait)s. fn trait_list() -> String { [ @@ -289,14 +320,14 @@ impl Error { "Copy", "Debug", "Default", + "Deserialize", "Eq", "Hash", "Ord", "PartialEq", "PartialOrd", - #[cfg(feature = "zeroize")] + "Serialize", "Zeroize", - #[cfg(feature = "zeroize")] "ZeroizeOnDrop", ] .join(", ") @@ -304,15 +335,7 @@ impl Error { /// List of available [`SkipGroup`](crate::SkipGroup)s. fn skip_group_list() -> String { - [ - "Clone", - "Debug", - "EqHashOrd", - "Hash", - #[cfg(feature = "zeroize")] - "Zeroize", - ] - .join(", ") + ["Clone", "Debug", "EqHashOrd", "Hash", "Zeroize"].join(", ") } /// Unsupported `Zeroize` option if [`Zeroize`](https://docs.rs/zeroize/latest/zeroize/trait.Zeroize.html) isn't implemented. diff --git a/src/input.rs b/src/input.rs index 506d7893..a1f70876 100644 --- a/src/input.rs +++ b/src/input.rs @@ -1,7 +1,9 @@ //! Parses [`DeriveInput`] into something more useful. use proc_macro2::Span; -use syn::{DeriveInput, GenericParam, Generics, ImplGenerics, Result, TypeGenerics, WhereClause}; +use syn::{ + DeriveInput, GenericParam, Generics, ImplGenerics, Path, Result, TypeGenerics, WhereClause, +}; #[cfg(not(feature = "nightly"))] use crate::Discriminant; @@ -14,6 +16,8 @@ use crate::{Data, DeriveWhere, Either, Error, Item, ItemAttr, Trait}; /// Parsed input. pub struct Input<'a> { + /// Path to `derive_where` if set by `#[derive_where(crate = ...)]`. + pub crate_: Option, /// `derive_where` attributes on the item. pub derive_wheres: Vec, /// Generics necessary to define for an `impl`. @@ -36,6 +40,7 @@ impl<'a> Input<'a> { ) -> Result { // Parse `Attribute`s on item. let ItemAttr { + crate_, skip_inner, derive_wheres, incomparable, @@ -208,6 +213,7 @@ impl<'a> Input<'a> { let generics = SplitGenerics::new(generics); Ok(Self { + crate_, derive_wheres, generics, item, diff --git a/src/lib.rs b/src/lib.rs index a62b3087..a95fe5b6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -228,6 +228,13 @@ //! 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: @@ -298,11 +305,13 @@ //! - [`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`], @@ -379,11 +388,13 @@ //! [LICENSE-APACHE]: https://github.com/ModProg/derive-where/blob/main/LICENSE-APACHE //! [`Debug`]: core::fmt::Debug //! [`Default`]: core::default::Default +//! [`Deserialize`]: https://docs.rs/serde/latest/serde/derive.Deserialize.html //! [`Eq`]: core::cmp::Eq //! [`Hash`]: core::hash::Hash //! [`Ord`]: core::cmp::Ord //! [`PartialEq`]: core::cmp::PartialEq //! [`PartialOrd`]: core::cmp::PartialOrd +//! [`Serialize`]: https://docs.rs/serde/latest/serde/derive.Serialize.html //! [`zeroize`]: https://docs.rs/zeroize //! [`Zeroize`]: https://docs.rs/zeroize/latest/zeroize/trait.Zeroize.html //! [`ZeroizeOnDrop`]: https://docs.rs/zeroize/latest/zeroize/trait.ZeroizeOnDrop.html @@ -405,8 +416,8 @@ use input::SplitGenerics; use proc_macro2::TokenStream; use quote::{quote, quote_spanned, ToTokens}; use syn::{ - spanned::Spanned, Attribute, DataEnum, DataStruct, DataUnion, DeriveInput, Expr, ExprLit, - ExprPath, Fields, FieldsNamed, FieldsUnnamed, Lit, Meta, Path, Result, Variant, + spanned::Spanned, Attribute, DataEnum, DataStruct, DataUnion, DeriveInput, Fields, FieldsNamed, + FieldsUnnamed, Meta, Path, Result, Variant, }; use util::MetaListExt; @@ -500,39 +511,11 @@ fn derive_where_internal(mut item: DeriveInput) -> Result { let meta = nested.into_iter().next().expect("unexpected empty list"); if meta.path().is_ident("crate") { - 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::() { - 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]) { - return Err(Error::path_unnecessary( - path.span(), - &format!("::{}", DERIVE_WHERE), - )); - } - - match crate_ { - Some(_) => { - return Err(Error::option_duplicate( - name_value.span(), - "crate", - )) - } - None => crate_ = Some(path), - } - } else { - return Err(Error::option_syntax(meta.span())); + let (path, span) = attr::parse_crate(meta)?; + + match crate_ { + Some(_) => return Err(Error::option_duplicate(span, "crate")), + None => crate_ = Some(path), } } } @@ -569,11 +552,18 @@ fn derive_where_internal(mut item: DeriveInput) -> Result { } #[doc(hidden)] -#[proc_macro_derive(DeriveWhere, attributes(derive_where))] +#[cfg_attr( + not(feature = "serde"), + proc_macro_derive(DeriveWhere, attributes(derive_where)) +)] +#[cfg_attr( + feature = "serde", + proc_macro_derive(DeriveWhere, attributes(derive_where, serde)) +)] #[cfg_attr(feature = "nightly", allow_internal_unstable(core_intrinsics))] pub fn derive_where_actual(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = TokenStream::from(input); - let item = match syn::parse2::(input) { + let full_item = match syn::parse2::(input) { Ok(item) => item, Err(error) => { return error.into_compile_error().into(); @@ -583,24 +573,34 @@ pub fn derive_where_actual(input: proc_macro::TokenStream) -> proc_macro::TokenS let span = { let clean_item = DeriveInput { attrs: Vec::new(), - vis: item.vis.clone(), - ident: item.ident.clone(), - generics: item.generics.clone(), - data: item.data.clone(), + vis: full_item.vis.clone(), + ident: full_item.ident.clone(), + generics: full_item.generics.clone(), + data: full_item.data.clone(), }; clean_item.span() }; - match Input::from_input(span, &item) { + match Input::from_input(span, &full_item) { Ok(Input { + crate_, derive_wheres, generics, item, }) => derive_wheres .iter() .flat_map(|derive_where| iter::repeat(derive_where).zip(&derive_where.traits)) - .map(|(derive_where, trait_)| generate_impl(derive_where, trait_, &item, &generics)) + .map(|(derive_where, trait_)| { + generate_impl( + crate_.as_ref(), + &full_item, + derive_where, + trait_, + &item, + &generics, + ) + }) .collect::() .into(), Err(error) => error.into_compile_error().into(), @@ -627,6 +627,8 @@ pub fn derive_where_visited( /// Generate implementation for a [`Trait`]. fn generate_impl( + crate_: Option<&Path>, + full_item: &DeriveInput, derive_where: &DeriveWhere, trait_: &DeriveTrait, item: &Item, @@ -643,7 +645,7 @@ fn generate_impl( let body = generate_body(derive_where, trait_, item, generics); let ident = item.ident(); - let mut output = trait_.impl_item(imp, ident, ty, &where_clause, body); + let mut output = trait_.impl_item(crate_, full_item, imp, ident, ty, &where_clause, body); if let Some((path, body)) = trait_.additional_impl() { output.extend(quote! { @@ -734,3 +736,14 @@ fn input_without_derive_where_attributes(mut input: DeriveInput) -> DeriveInput input } + +/// This is used by the Serde implementation to remove the duplicate item. +#[cfg(feature = "serde")] +#[doc(hidden)] +#[proc_macro_attribute] +pub fn derive_where_serde( + _: proc_macro::TokenStream, + _: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + proc_macro::TokenStream::new() +} diff --git a/src/test/mod.rs b/src/test/mod.rs index 763425a7..741ca541 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -7,6 +7,8 @@ mod enum_; mod incomparable; mod misc; mod partial_ord; +#[cfg(feature = "serde")] +mod serde; mod skip; mod use_case; #[cfg(feature = "zeroize")] @@ -37,18 +39,28 @@ fn compiles(input: TokenStream) -> Result<()> { fn derive_where_internal(input: TokenStream) -> Result { // Save `Span` before we consume `input` when parsing it. let span = input.span(); - let item = syn::parse2::(input).expect("derive on unparsable item"); + let full_item = syn::parse2::(input).expect("derive on unparsable item"); let Input { + crate_, derive_wheres, generics, item, .. - } = Input::from_input(span, &item)?; + } = Input::from_input(span, &full_item)?; Ok(derive_wheres .iter() .flat_map(|derive_where| iter::repeat(derive_where).zip(&derive_where.traits)) - .map(|(derive_where, trait_)| generate_impl(derive_where, trait_, &item, &generics)) + .map(|(derive_where, trait_)| { + generate_impl( + crate_.as_ref(), + &full_item, + derive_where, + trait_, + &item, + &generics, + ) + }) .collect()) } diff --git a/src/test/serde.rs b/src/test/serde.rs new file mode 100644 index 00000000..981df869 --- /dev/null +++ b/src/test/serde.rs @@ -0,0 +1,125 @@ +use quote::quote; +use syn::Result; + +use super::test_derive; + +#[test] +fn basic() -> Result<()> { + test_derive( + quote! { + #[derive_where(Deserialize, Serialize)] + struct Test(std::marker::PhantomData); + }, + quote! { + #[::core::prelude::v1::derive(::serde::Deserialize)] + #[serde(bound(deserialize = ""))] + #[::derive_where::derive_where_serde] + #[derive_where(Deserialize, Serialize)] + struct Test(std::marker::PhantomData); + + #[::core::prelude::v1::derive(::serde::Serialize)] + #[serde(bound(serialize = ""))] + #[::derive_where::derive_where_serde] + #[derive_where(Deserialize, Serialize)] + struct Test(std::marker::PhantomData); + }, + ) +} + +#[test] +fn bound() -> Result<()> { + test_derive( + quote! { + #[derive_where(Deserialize, Serialize; T)] + struct Test(T, std::marker::PhantomData); + }, + quote! { + #[::core::prelude::v1::derive(::serde::Deserialize)] + #[serde(bound(deserialize = "T : :: serde :: Deserialize < 'de >"))] + #[::derive_where::derive_where_serde] + #[derive_where(Deserialize, Serialize; T)] + struct Test(T, std::marker::PhantomData); + + #[::core::prelude::v1::derive(::serde::Serialize)] + #[serde(bound(serialize = "T : :: serde :: Serialize"))] + #[::derive_where::derive_where_serde] + #[derive_where(Deserialize, Serialize; T)] + struct Test(T, std::marker::PhantomData); + }, + ) +} + +#[test] +fn bound_two() -> Result<()> { + test_derive( + quote! { + #[derive_where(Deserialize, Serialize; T, U)] + struct Test(T, U, std::marker::PhantomData); + }, + quote! { + #[::core::prelude::v1::derive(::serde::Deserialize)] + #[serde(bound(deserialize = "T : :: serde :: Deserialize < 'de > , U : :: serde :: Deserialize < 'de >"))] + #[::derive_where::derive_where_serde] + #[derive_where(Deserialize, Serialize; T, U)] + struct Test(T, U, std::marker::PhantomData); + + #[::core::prelude::v1::derive(::serde::Serialize)] + #[serde(bound(serialize = "T : :: serde :: Serialize , U : :: serde :: Serialize"))] + #[::derive_where::derive_where_serde] + #[derive_where(Deserialize, Serialize; T, U)] + struct Test(T, U, std::marker::PhantomData); + }, + ) +} + +#[test] +fn attribute() -> Result<()> { + test_derive( + quote! { + #[derive_where(Deserialize, Serialize)] + #[serde(test_attribute)] + struct Test(std::marker::PhantomData); + }, + quote! { + #[::core::prelude::v1::derive(::serde::Deserialize)] + #[serde(bound(deserialize = ""))] + #[::derive_where::derive_where_serde] + #[derive_where(Deserialize, Serialize)] + #[serde(test_attribute)] + struct Test(std::marker::PhantomData); + + #[::core::prelude::v1::derive(::serde::Serialize)] + #[serde(bound(serialize = ""))] + #[::derive_where::derive_where_serde] + #[derive_where(Deserialize, Serialize)] + #[serde(test_attribute)] + struct Test(std::marker::PhantomData); + }, + ) +} + +#[test] +fn crate_() -> Result<()> { + test_derive( + quote! { + #[derive_where(Deserialize, Serialize)] + #[serde(crate = "serde_")] + struct Test(std::marker::PhantomData); + }, + quote! { + #[::core::prelude::v1::derive(serde_::Deserialize)] + #[serde(bound(deserialize = ""))] + #[::derive_where::derive_where_serde] + #[derive_where(Deserialize, Serialize)] + #[serde(crate = "serde_")] + struct Test(std::marker::PhantomData); + + #[::core::prelude::v1::derive(serde_::Serialize)] + #[serde(bound(serialize = ""))] + #[::derive_where::derive_where_serde] + #[derive_where(Deserialize, Serialize)] + #[serde(crate = "serde_")] + struct Test(std::marker::PhantomData); + }, + ) +} diff --git a/src/trait_.rs b/src/trait_.rs index 0147d481..5c3fe6eb 100644 --- a/src/trait_.rs +++ b/src/trait_.rs @@ -5,11 +5,17 @@ mod common_ord; pub mod copy; pub mod debug; pub mod default; +#[cfg(feature = "serde")] +pub mod deserialize; pub mod eq; pub mod hash; pub mod ord; pub mod partial_eq; pub mod partial_ord; +#[cfg(feature = "serde")] +mod serde; +#[cfg(feature = "serde")] +pub mod serialize; #[cfg(feature = "zeroize")] pub mod zeroize; #[cfg(feature = "zeroize")] @@ -23,8 +29,8 @@ use syn::{ parse::{Parse, ParseStream}, punctuated::Punctuated, spanned::Spanned, - Ident, ImplGenerics, Meta, Path, Result, Token, TraitBound, TraitBoundModifier, TypeGenerics, - TypeParamBound, WhereClause, + Attribute, DeriveInput, Ident, ImplGenerics, Meta, Path, Result, Token, TraitBound, + TraitBoundModifier, TypeGenerics, TypeParamBound, WhereClause, }; use crate::{util::MetaListExt, Data, DeriveWhere, Error, Item, SplitGenerics}; @@ -41,6 +47,9 @@ pub enum Trait { Debug, /// [`Default`]. Default, + /// [`Deserialize`](https://docs.rs/serde/latest/serde/derive.Deserialize.html). + #[cfg(feature = "serde")] + Deserialize, /// [`Eq`]. Eq, /// [`Hash`](std::hash::Hash). @@ -51,6 +60,9 @@ pub enum Trait { PartialEq, /// [`PartialOrd`]. PartialOrd, + /// [`Serialize`](https://docs.rs/serde/latest/serde/derive.Serialize.html). + #[cfg(feature = "serde")] + Serialize, /// [`Zeroize`](https://docs.rs/zeroize/latest/zeroize/trait.Zeroize.html). #[cfg(feature = "zeroize")] Zeroize, @@ -66,11 +78,15 @@ macro_rules! trait_dispatch { Trait::Copy => copy::Copy::$method($($par),*), Trait::Debug => debug::Debug::$method($($par),*), Trait::Default => default::Default::$method($($par),*), + #[cfg(feature = "serde")] + Trait::Deserialize => deserialize::Deserialize::$method($($par),*), Trait::Eq => eq::Eq::$method($($par),*), Trait::Hash => hash::Hash::$method($($par),*), Trait::Ord => ord::Ord::$method($($par),*), Trait::PartialEq => partial_eq::PartialEq::$method($($par),*), Trait::PartialOrd => partial_ord::PartialOrd::$method($($par),*), + #[cfg(feature = "serde")] + Trait::Serialize => serialize::Serialize::$method($($par),*), #[cfg(feature = "zeroize")] Trait::Zeroize => zeroize::Zeroize::$method($($par),*), #[cfg(feature = "zeroize")] @@ -90,15 +106,27 @@ impl Trait { "Copy" => Ok(Copy), "Debug" => Ok(Debug), "Default" => Ok(Default), + #[cfg(feature = "serde")] + "Deserialize" => Ok(Deserialize), + #[cfg(not(feature = "serde"))] + "Deserialize" => Err(Error::serde_feature(path.span())), "Eq" => Ok(Eq), "Hash" => Ok(Hash), "Ord" => Ok(Ord), "PartialEq" => Ok(PartialEq), "PartialOrd" => Ok(PartialOrd), + #[cfg(feature = "serde")] + "Serialize" => Ok(Serialize), + #[cfg(not(feature = "serde"))] + "Serialize" => Err(Error::serde_feature(path.span())), #[cfg(feature = "zeroize")] "Zeroize" => Ok(Zeroize), + #[cfg(not(feature = "zeroize"))] + "Zeroize" => Err(Error::zeroize_feature(path.span())), #[cfg(feature = "zeroize")] "ZeroizeOnDrop" => Ok(ZeroizeOnDrop), + #[cfg(not(feature = "zeroize"))] + "ZeroizeOnDrop" => Err(Error::zeroize_feature(path.span())), "crate" => Err(Error::crate_(path.span())), _ => Err(Error::trait_(path.span())), } @@ -120,10 +148,11 @@ impl Trait { /// Re-direct to [`TraitImpl::parse_derive_trait()`]. pub fn parse_derive_trait( &self, + attrs: &[Attribute], span: Span, - list: Punctuated, + list: Option>, ) -> Result { - trait_dispatch!(self, parse_derive_trait(span, list)) + trait_dispatch!(self, parse_derive_trait(attrs, span, list)) } /// Re-direct to [`TraitImpl::supports_union()`]. @@ -148,6 +177,9 @@ pub enum DeriveTrait { Debug, /// [`Default`]. Default, + /// [`Deserialize`](https://docs.rs/serde/latest/serde/derive.Deserialize.html). + #[cfg(feature = "serde")] + Deserialize(deserialize::Deserialize), /// [`Eq`]. Eq, /// [`Hash`](std::hash::Hash). @@ -158,6 +190,9 @@ pub enum DeriveTrait { PartialEq, /// [`PartialOrd`]. PartialOrd, + /// [`Serialize`](https://docs.rs/serde/latest/serde/derive.Serialize.html). + #[cfg(feature = "serde")] + Serialize(serialize::Serialize), /// [`Zeroize`](https://docs.rs/zeroize/latest/zeroize/trait.Zeroize.html). #[cfg(feature = "zeroize")] Zeroize(zeroize::Zeroize), @@ -177,11 +212,15 @@ impl Deref for DeriveTrait { Copy => ©::Copy, Debug => &debug::Debug, Default => &default::Default, + #[cfg(feature = "serde")] + Deserialize(trait_) => trait_, Eq => &eq::Eq, Hash => &hash::Hash, Ord => &ord::Ord, PartialEq => &partial_eq::PartialEq, PartialOrd => &partial_ord::PartialOrd, + #[cfg(feature = "serde")] + Serialize(trait_) => trait_, #[cfg(feature = "zeroize")] Zeroize(trait_) => trait_, #[cfg(feature = "zeroize")] @@ -218,7 +257,12 @@ impl DeriveTrait { } /// Create [`DeriveTrait`] from [`ParseStream`]. - pub fn from_stream(span: Span, data: &syn::Data, input: ParseStream) -> Result<(Span, Self)> { + pub fn from_stream( + attrs: &[Attribute], + span: Span, + data: &syn::Data, + input: ParseStream, + ) -> Result<(Span, Self)> { match Meta::parse(input) { Ok(meta) => { let trait_ = Trait::from_path(meta.path())?; @@ -231,12 +275,18 @@ impl DeriveTrait { } match &meta { - Meta::Path(path) => Ok((path.span(), trait_.default_derive_trait())), + Meta::Path(path) => Ok(( + path.span(), + trait_.parse_derive_trait(attrs, meta.span(), None)?, + )), Meta::List(list) => { let nested = list.parse_non_empty_nested_metas()?; // This will return an error if no options are supported. - Ok((list.span(), trait_.parse_derive_trait(meta.span(), nested)?)) + Ok(( + list.span(), + trait_.parse_derive_trait(attrs, meta.span(), Some(nested))?, + )) } Meta::NameValue(name_value) => Err(Error::option_syntax(name_value.span())), } @@ -261,11 +311,19 @@ pub trait TraitImpl: Deref { Self: Sized; /// Parse a `derive_where` trait with it's options. - fn parse_derive_trait(span: Span, _list: Punctuated) -> Result + fn parse_derive_trait( + _attrs: &[Attribute], + span: Span, + list: Option>, + ) -> Result where Self: Sized, { - Err(Error::options(span, Self::as_str())) + if list.is_some() { + Err(Error::options(span, Self::as_str())) + } else { + Ok(Self::default_derive_trait()) + } } /// Returns `true` if [`Trait`] supports unions. @@ -294,8 +352,11 @@ pub trait TraitImpl: Deref { /// Trait to implement. Only used by [`Eq`] and /// [`ZeroizeOnDrop`](https://docs.rs/zeroize/latest/zeroize/trait.ZeroizeOnDrop.html). + #[allow(clippy::too_many_arguments)] fn impl_item( &self, + _crate_: Option<&Path>, + _full_item: &DeriveInput, imp: &ImplGenerics<'_>, ident: &Ident, ty: &TypeGenerics<'_>, diff --git a/src/trait_/deserialize.rs b/src/trait_/deserialize.rs new file mode 100644 index 00000000..cf8d0ff9 --- /dev/null +++ b/src/trait_/deserialize.rs @@ -0,0 +1,93 @@ +//! [`Deserialize`](https://docs.rs/serde/latest/serde/derive.Deserialize.html) implementation. + +use std::{borrow::Cow, ops::Deref}; + +use proc_macro2::{Span, TokenStream}; +use quote::{format_ident, quote}; +use syn::{ + punctuated::Punctuated, Attribute, DeriveInput, Ident, ImplGenerics, Meta, Path, Result, + TypeGenerics, WhereClause, +}; + +use super::serde; +use crate::{util, DeriveTrait, Trait, TraitImpl}; + +/// [`TraitImpl`] for [`Deserialize`](https://docs.rs/serde/latest/serde/derive.Deserialize.html). +#[derive(Eq, PartialEq)] +pub struct Deserialize { + /// [`Deserialize`](https://docs.rs/serde/latest/serde/derive.Deserialize.html) path. + pub crate_: Option, +} + +impl TraitImpl for Deserialize { + fn as_str() -> &'static str + where + Self: Sized, + { + "Deserialize" + } + + fn default_derive_trait() -> super::DeriveTrait + where + Self: Sized, + { + DeriveTrait::Deserialize(Self { crate_: None }) + } + + fn parse_derive_trait( + attrs: &[Attribute], + span: Span, + list: Option>, + ) -> Result + where + Self: Sized, + { + Ok(DeriveTrait::Deserialize(Self { + crate_: serde::parse_derive_trait(Trait::Deserialize, attrs, span, list)?, + })) + } + + fn path(&self) -> Path { + let crate_ = self.crate_(); + syn::parse2::(quote! { #crate_::Deserialize<'de> }).unwrap() + } + + fn impl_item( + &self, + crate_: Option<&Path>, + full_item: &DeriveInput, + _: &ImplGenerics<'_>, + _: &Ident, + _: &TypeGenerics<'_>, + where_clause: &Option>, + _: TokenStream, + ) -> TokenStream { + serde::impl_item( + crate_, + &self.crate_(), + format_ident!("Deserialize"), + format_ident!("deserialize"), + full_item, + where_clause, + ) + } +} + +impl Deserialize { + /// Returns the path to the root crate for this trait. + fn crate_(&self) -> Path { + if let Some(crate_) = &self.crate_ { + crate_.clone() + } else { + util::path_from_strs(&["serde"]) + } + } +} + +impl Deref for Deserialize { + type Target = Trait; + + fn deref(&self) -> &Self::Target { + &Trait::Deserialize + } +} diff --git a/src/trait_/eq.rs b/src/trait_/eq.rs index f45aed9b..378b947f 100644 --- a/src/trait_/eq.rs +++ b/src/trait_/eq.rs @@ -4,7 +4,7 @@ use std::{borrow::Cow, ops::Deref}; use proc_macro2::TokenStream; use quote::quote; -use syn::{Ident, ImplGenerics, Path, TypeGenerics, WhereClause}; +use syn::{DeriveInput, Ident, ImplGenerics, Path, TypeGenerics, WhereClause}; use crate::{util, Data, DeriveTrait, DeriveWhere, Item, SplitGenerics, Trait, TraitImpl}; @@ -30,6 +30,8 @@ impl TraitImpl for Eq { fn impl_item( &self, + _: Option<&Path>, + _: &DeriveInput, imp: &ImplGenerics<'_>, ident: &Ident, ty: &TypeGenerics<'_>, diff --git a/src/trait_/serde.rs b/src/trait_/serde.rs new file mode 100644 index 00000000..5b8c6f3d --- /dev/null +++ b/src/trait_/serde.rs @@ -0,0 +1,106 @@ +//! Common functionality between +//! [`Deserialize`](https://docs.rs/serde/latest/serde/derive.Deserialize.html) and +//! [`Serialize`](https://docs.rs/serde/latest/serde/derive.Serialize.html). + +use std::borrow::Cow; + +use proc_macro2::{Span, TokenStream}; +use quote::{quote, ToTokens}; +use syn::{ + punctuated::Punctuated, spanned::Spanned, Attribute, DeriveInput, Expr, ExprLit, Ident, Lit, + Meta, Path, Token, WhereClause, +}; + +use crate::{util, Error, Result, Trait, DERIVE_WHERE}; + +/// Parse `#[serde(crate = "...")]`. +pub fn parse_derive_trait( + trait_: Trait, + attrs: &[Attribute], + span: Span, + list: Option>, +) -> Result> { + if list.is_some() { + return Err(Error::options(span, trait_.as_str())); + } + + let mut crate_ = None; + + for attr in attrs { + if !attr.path().is_ident("serde") { + continue; + } + + if let Meta::List(list) = &attr.meta { + if let Ok(nested) = + list.parse_args_with(Punctuated::::parse_terminated) + { + for meta in nested { + if meta.path().is_ident("bound") { + return Err(Error::serde_bound(meta.span())); + } + + if !meta.path().is_ident("crate") { + continue; + } + + match &meta { + Meta::NameValue(name_value) => { + // Check for duplicate `crate` option. + if crate_.is_none() { + let path = match &name_value.value { + Expr::Lit(ExprLit { + lit: Lit::Str(lit_str), + .. + }) => match lit_str.parse::() { + Ok(path) => path, + Err(error) => { + return Err(Error::path(lit_str.span(), error)) + } + }, + _ => return Err(Error::option_syntax(name_value.value.span())), + }; + + crate_ = Some(path); + } else { + return Err(Error::option_duplicate(name_value.span(), "crate")); + } + } + _ => { + return Err(Error::option_syntax(meta.span())); + } + } + } + } + } + } + + Ok(crate_) +} + +/// Implement Serde trait. +pub fn impl_item( + derive_where: Option<&Path>, + serde: &Path, + trait_: Ident, + bound: Ident, + full_item: &DeriveInput, + where_clause: &Option>, +) -> TokenStream { + let derive_where = derive_where + .map(Cow::Borrowed) + .unwrap_or_else(|| Cow::Owned(util::path_from_strs(&[DERIVE_WHERE]))); + + let bounds = if let Some(where_clause) = where_clause { + where_clause.predicates.to_token_stream().to_string() + } else { + String::new() + }; + + quote! { + #[::core::prelude::v1::derive(#serde::#trait_)] + #[serde(bound(#bound = #bounds))] + #[#derive_where::derive_where_serde] + #full_item + } +} diff --git a/src/trait_/serialize.rs b/src/trait_/serialize.rs new file mode 100644 index 00000000..2470ed16 --- /dev/null +++ b/src/trait_/serialize.rs @@ -0,0 +1,92 @@ +//! [`Serialize`](https://docs.rs/serde/latest/serde/derive.Serialize.html) implementation. + +use std::{borrow::Cow, ops::Deref}; + +use proc_macro2::{Span, TokenStream}; +use quote::format_ident; +use syn::{ + punctuated::Punctuated, Attribute, DeriveInput, Ident, ImplGenerics, Meta, Path, Result, + TypeGenerics, WhereClause, +}; + +use super::serde; +use crate::{util, DeriveTrait, Trait, TraitImpl}; + +/// [`TraitImpl`] for [`Serialize`](https://docs.rs/serde/latest/serde/derive.Serialize.html). +#[derive(Eq, PartialEq)] +pub struct Serialize { + /// [`Serialize`](https://docs.rs/serde/latest/serde/derive.Serialize.html) path. + pub crate_: Option, +} + +impl TraitImpl for Serialize { + fn as_str() -> &'static str + where + Self: Sized, + { + "Serialize" + } + + fn default_derive_trait() -> super::DeriveTrait + where + Self: Sized, + { + DeriveTrait::Serialize(Self { crate_: None }) + } + + fn parse_derive_trait( + attrs: &[Attribute], + span: Span, + list: Option>, + ) -> Result + where + Self: Sized, + { + Ok(DeriveTrait::Serialize(Self { + crate_: serde::parse_derive_trait(Trait::Serialize, attrs, span, list)?, + })) + } + + fn path(&self) -> Path { + util::path_from_root_and_strs(self.crate_(), &["Serialize"]) + } + + fn impl_item( + &self, + crate_: Option<&Path>, + full_item: &DeriveInput, + _: &ImplGenerics<'_>, + _: &Ident, + _: &TypeGenerics<'_>, + where_clause: &Option>, + _: TokenStream, + ) -> TokenStream { + serde::impl_item( + crate_, + &self.crate_(), + format_ident!("Serialize"), + format_ident!("serialize"), + full_item, + where_clause, + ) + } +} + +impl Serialize { + /// Returns the path to the root crate for this trait. + fn crate_(&self) -> Path { + if let Some(crate_) = &self.crate_ { + crate_.clone() + } else { + util::path_from_strs(&["serde"]) + } + } +} + +impl Deref for Serialize { + type Target = Trait; + + fn deref(&self) -> &Self::Target { + &Trait::Serialize + } +} diff --git a/src/trait_/zeroize.rs b/src/trait_/zeroize.rs index 1812be4c..89ae7286 100644 --- a/src/trait_/zeroize.rs +++ b/src/trait_/zeroize.rs @@ -5,8 +5,8 @@ use std::ops::Deref; use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::{ - punctuated::Punctuated, spanned::Spanned, Expr, ExprLit, ExprPath, Lit, Meta, Path, Result, - Token, + punctuated::Punctuated, spanned::Spanned, Attribute, Expr, ExprLit, ExprPath, Lit, Meta, Path, + Result, Token, }; use crate::{ @@ -29,9 +29,16 @@ impl TraitImpl for Zeroize { DeriveTrait::Zeroize(Self { crate_: None }) } - fn parse_derive_trait(_span: Span, list: Punctuated) -> Result { - // This is already checked in `DeriveTrait::from_stream`. - debug_assert!(!list.is_empty()); + fn parse_derive_trait( + _: &[Attribute], + _span: Span, + list: Option>, + ) -> Result { + let list = if let Some(list) = list { + list + } else { + return Ok(Self::default_derive_trait()); + }; let mut crate_ = None; diff --git a/src/trait_/zeroize_on_drop.rs b/src/trait_/zeroize_on_drop.rs index 95491dea..a6e2a339 100644 --- a/src/trait_/zeroize_on_drop.rs +++ b/src/trait_/zeroize_on_drop.rs @@ -5,8 +5,8 @@ use std::{borrow::Cow, iter, ops::Deref}; use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::{ - punctuated::Punctuated, spanned::Spanned, Expr, ExprLit, ExprPath, Ident, ImplGenerics, Lit, - Meta, Path, Result, Token, TypeGenerics, WhereClause, + punctuated::Punctuated, spanned::Spanned, Attribute, DeriveInput, Expr, ExprLit, ExprPath, + Ident, ImplGenerics, Lit, Meta, Path, Result, Token, TypeGenerics, WhereClause, }; use crate::{util, DeriveTrait, DeriveWhere, Error, Item, SplitGenerics, Trait, TraitImpl}; @@ -34,9 +34,16 @@ impl TraitImpl for ZeroizeOnDrop { }) } - fn parse_derive_trait(_span: Span, list: Punctuated) -> Result { - // This is already checked in `DeriveTrait::from_stream`. - debug_assert!(!list.is_empty()); + fn parse_derive_trait( + _: &[Attribute], + _: Span, + list: Option>, + ) -> Result { + let list = if let Some(list) = list { + list + } else { + return Ok(Self::default_derive_trait()); + }; let mut crate_ = None; #[cfg_attr(not(feature = "zeroize-on-drop"), allow(unused_mut))] @@ -45,16 +52,18 @@ impl TraitImpl for ZeroizeOnDrop { for meta in list { match &meta { Meta::Path(path) => { - #[cfg(feature = "zeroize-on-drop")] if path.is_ident("no_drop") { // Check for duplicate `no_drop` option. + #[cfg(feature = "zeroize-on-drop")] if !no_drop { no_drop = true; + continue; } else { return Err(Error::option_duplicate(path.span(), "no_drop")); } - continue; + #[cfg(not(feature = "zeroize-on-drop"))] + return Err(Error::zeroize_on_drop_feature(path.span())); } return Err(Error::option_trait(path.span(), Self::as_str())); @@ -109,6 +118,8 @@ impl TraitImpl for ZeroizeOnDrop { fn impl_item( &self, + _: Option<&Path>, + _: &DeriveInput, imp: &ImplGenerics<'_>, ident: &Ident, ty: &TypeGenerics<'_>, diff --git a/test-crates/crate_/Cargo.toml b/test-crates/crate_/Cargo.toml index 7a230bda..defaf486 100644 --- a/test-crates/crate_/Cargo.toml +++ b/test-crates/crate_/Cargo.toml @@ -7,12 +7,16 @@ version = "0.0.0" [features] nightly = ["derive-where_/nightly"] safe = ["derive-where_/safe"] +serde = ["derive-where_/serde", "serde_"] zeroize = ["derive-where_/zeroize", "zeroize_"] zeroize-on-drop = ["derive-where_/zeroize-on-drop", "zeroize"] [dependencies] derive-where_ = { path = "../..", package = "derive-where" } -zeroize_ = { version = "1.5", package = "zeroize", default-features = false, optional = true } +serde_ = { package = "serde", version = "1", default-features = false, features = [ + "derive", +], optional = true } +zeroize_ = { package = "zeroize", version = "1.5", default-features = false, optional = true } [lib] doctest = false diff --git a/test-crates/crate_/src/lib.rs b/test-crates/crate_/src/lib.rs index 96bba365..a8c049ba 100644 --- a/test-crates/crate_/src/lib.rs +++ b/test-crates/crate_/src/lib.rs @@ -5,11 +5,15 @@ use core::marker::PhantomData; use derive_where_::derive_where; #[derive_where(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serde", derive_where(Deserialize, Serialize))] +#[cfg_attr(feature = "serde", serde(crate = "serde_"))] #[cfg_attr(feature = "zeroize", derive_where(Zeroize(crate = zeroize_)))] #[derive_where(crate = derive_where_)] pub struct Test(PhantomData); #[derive_where(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serde", derive_where(Deserialize, Serialize))] +#[cfg_attr(feature = "serde", serde(crate = "serde_"))] #[cfg_attr(feature = "zeroize", derive_where(Zeroize(crate = "zeroize_")))] #[derive_where(crate = "derive_where_")] pub struct TestDeprecated(PhantomData); diff --git a/test-crates/ensure-no-std/Cargo.toml b/test-crates/ensure-no-std/Cargo.toml index 95a7e7f1..4ee4d591 100644 --- a/test-crates/ensure-no-std/Cargo.toml +++ b/test-crates/ensure-no-std/Cargo.toml @@ -7,12 +7,16 @@ version = "0.0.0" [features] nightly = ["derive-where/nightly"] safe = ["derive-where/safe"] +serde = ["derive-where/serde", "serde_"] zeroize = ["derive-where/zeroize", "zeroize_"] zeroize-on-drop = ["derive-where/zeroize-on-drop", "zeroize"] [dependencies] derive-where = { path = "../.." } -zeroize_ = { version = "1.5", package = "zeroize", default-features = false, optional = true } +serde_ = { package = "serde", version = "1", default-features = false, features = [ + "derive", +], optional = true } +zeroize_ = { package = "zeroize", version = "1.5", default-features = false, optional = true } [lib] doctest = false diff --git a/test-crates/ensure-no-std/src/lib.rs b/test-crates/ensure-no-std/src/lib.rs index 3cf76309..8ff34f7e 100644 --- a/test-crates/ensure-no-std/src/lib.rs +++ b/test-crates/ensure-no-std/src/lib.rs @@ -8,5 +8,7 @@ use core::marker::PhantomData; use derive_where::derive_where; #[derive_where(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serde", derive_where(Deserialize, Serialize))] +#[cfg_attr(feature = "serde", serde(crate = "serde_"))] #[cfg_attr(feature = "zeroize", derive_where(Zeroize))] pub struct Test(PhantomData); diff --git a/test-crates/minimal-versions/Cargo.toml b/test-crates/minimal-versions/Cargo.toml index f4fa79cf..dcc77b1f 100644 --- a/test-crates/minimal-versions/Cargo.toml +++ b/test-crates/minimal-versions/Cargo.toml @@ -11,9 +11,15 @@ version = "0.0.0" [features] nightly = ["derive-where/nightly"] safe = ["derive-where/safe"] +serde_ = ["derive-where/serde", "serde", "serde_derive", "syn"] zeroize = ["derive-where/zeroize", "zeroize_"] zeroize-on-drop = ["derive-where/zeroize-on-drop", "zeroize"] [dependencies] derive-where = { path = "../.." } -zeroize_ = { version = "1.5", package = "zeroize", default-features = false, optional = true } +serde = { version = "1", default-features = false, features = ["derive"], optional = true } +# Fails to parse item in earlier versions. +serde_derive = { version = "1.0.28", optional = true } +# `serde_derive` v1.0.28 doesn't compile with earlier versions. +syn = { version = "0.12.3", default-features = false, optional = true } +zeroize_ = { package = "zeroize", version = "1.5", default-features = false, optional = true } diff --git a/test-crates/minimal-versions/src/lib.rs b/test-crates/minimal-versions/src/lib.rs index fa4baf99..1f5f4671 100644 --- a/test-crates/minimal-versions/src/lib.rs +++ b/test-crates/minimal-versions/src/lib.rs @@ -6,5 +6,6 @@ use core::marker::PhantomData; use derive_where::derive_where; #[derive_where(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serde", derive_where(Deserialize, Serialize))] #[cfg_attr(feature = "zeroize", derive_where(Zeroize))] pub struct Test(PhantomData); diff --git a/tests/bound.rs b/tests/bound.rs index fccbab61..7b20ce8c 100644 --- a/tests/bound.rs +++ b/tests/bound.rs @@ -10,7 +10,7 @@ use self::util::{AssertClone, AssertCopy}; #[test] fn bound() { #[derive_where(Clone, Copy; T)] - struct Test(T, std::marker::PhantomData); + struct Test(T, PhantomData); let test_1 = Test(42, PhantomData::<()>); diff --git a/tests/serde.rs b/tests/serde.rs new file mode 100644 index 00000000..bcb558ec --- /dev/null +++ b/tests/serde.rs @@ -0,0 +1,72 @@ +mod util; + +use std::marker::PhantomData; + +use derive_where::derive_where; +#[cfg(not(feature = "serde"))] +use serde_::{Deserialize, Serialize}; +use serde_test::Token; +use util::Wrapper; + +#[test] +fn basic() { + #[derive_where(Debug, PartialEq; T)] + #[cfg_attr( + feature = "serde", + derive_where(Deserialize, Serialize; T) + )] + #[cfg_attr(not(feature = "serde"), derive(Deserialize, Serialize))] + #[cfg_attr( + not(feature = "serde"), + serde(bound(deserialize = "T: Deserialize<'de>", serialize = "T: Serialize")) + )] + #[serde(crate = "serde_")] + struct Test(T, PhantomData); + + let test = Test(42, PhantomData::); + + serde_test::assert_tokens( + &test, + &[ + Token::TupleStruct { + name: "Test", + len: 2, + }, + Token::I32(42), + Token::UnitStruct { + name: "PhantomData", + }, + Token::TupleStructEnd, + ], + ); +} + +#[test] +fn attribute() { + #[derive_where(Debug, PartialEq)] + #[cfg_attr(feature = "serde", derive_where(Deserialize, Serialize))] + #[cfg_attr(not(feature = "serde"), derive(Deserialize, Serialize))] + #[cfg_attr(not(feature = "serde"), serde(bound = ""))] + #[serde(crate = "serde_")] + #[serde(transparent)] + struct Test(Wrapper); + + let test = Test(42.into()); + + serde_test::assert_tokens( + &test, + &[ + Token::Struct { + name: "Wrapper", + len: 2, + }, + Token::Str("data"), + Token::I32(42), + Token::Str("hack"), + Token::UnitStruct { + name: "PhantomData", + }, + Token::StructEnd, + ], + ); +} diff --git a/tests/ui.rs b/tests/ui.rs index 6467f8e3..cb153902 100644 --- a/tests/ui.rs +++ b/tests/ui.rs @@ -6,6 +6,10 @@ fn ui() { use trybuild::TestCases; TestCases::new().compile_fail("tests/ui/*.rs"); + #[cfg(feature = "serde")] + TestCases::new().compile_fail("tests/ui/serde/*.rs"); + #[cfg(not(feature = "serde"))] + TestCases::new().compile_fail("tests/ui/not-serde/*.rs"); #[cfg(not(feature = "zeroize"))] TestCases::new().compile_fail("tests/ui/not-zeroize/*.rs"); #[cfg(feature = "zeroize")] diff --git a/tests/ui/item.rs b/tests/ui/item.rs index b8d42b47..1f458a1c 100644 --- a/tests/ui/item.rs +++ b/tests/ui/item.rs @@ -10,12 +10,21 @@ struct NoOption(PhantomData); #[derive_where()] struct EmptyAttribute(PhantomData); +#[derive_where(,)] +struct OnlyComma(PhantomData); + #[derive_where(crate(derive_where_))] struct WrongCrateSyntax(PhantomData); #[derive_where(crate = "struct Test")] struct InvalidPathDeprecated(PhantomData); +#[derive_where(crate = struct Test)] +struct InvalidPath(PhantomData); + +#[derive_where(skip_inner, Clone)] +struct SkipInnerWithTrait(PhantomData); + // The error message here shows that `crate = ..` should be in it's own // attribute instead of an error pointing out this is duplicate. This is not // ideal but much less complicated to implement. @@ -32,6 +41,18 @@ struct OnlyCrate(PhantomData); #[derive_where(crate = ::derive_where)] struct DefaultCrate(PhantomData); +#[derive_where(,Clone)] +struct StartWithComma(PhantomData); + +#[derive_where(Clone,,)] +struct DuplicateCommaAtEnd(PhantomData); + +#[derive_where("Clone")] +struct InvalidTrait(PhantomData); + +#[derive_where(T)] +struct GenericInsteadTrait(T, PhantomData); + #[derive_where(Debug = invalid; T)] struct WrongOptionSyntax(T, PhantomData); diff --git a/tests/ui/item.stderr b/tests/ui/item.stderr index 5a14c662..ed8936f2 100644 --- a/tests/ui/item.stderr +++ b/tests/ui/item.stderr @@ -14,94 +14,136 @@ error: empty `derive_where` found | = note: this error originates in the attribute macro `derive_where` (in Nightly builds, run with -Z macro-backtrace for more info) -error: unexpected option syntax +error: unsupported trait syntax, expected one of Clone, Copy, Debug, Default, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Zeroize, ZeroizeOnDrop --> tests/ui/item.rs:13:16 | -13 | #[derive_where(crate(derive_where_))] +13 | #[derive_where(,)] + | ^ + +error: unexpected option syntax + --> tests/ui/item.rs:16:16 + | +16 | #[derive_where(crate(derive_where_))] | ^^^^^^^^^^^^^^^^^^^^ error: expected path, expected identifier, found keyword `struct` - --> tests/ui/item.rs:16:24 + --> tests/ui/item.rs:19:24 | -16 | #[derive_where(crate = "struct Test")] +19 | #[derive_where(crate = "struct Test")] | ^^^^^^^^^^^^^ +error: unsupported trait syntax, expected one of Clone, Copy, Debug, Default, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Zeroize, ZeroizeOnDrop + --> tests/ui/item.rs:22:24 + | +22 | #[derive_where(crate = struct Test)] + | ^^^^^^ + +error: unsupported trait, expected one of Clone, Copy, Debug, Default, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Zeroize, ZeroizeOnDrop + --> tests/ui/item.rs:25:16 + | +25 | #[derive_where(skip_inner, Clone)] + | ^^^^^^^^^^ + error: the `crate` option has to be defined in it's own `#[derive_where(..)` attribute - --> tests/ui/item.rs:22:16 + --> tests/ui/item.rs:31:16 | -22 | #[derive_where(crate = derive_where_, crate = derive_where_)] +31 | #[derive_where(crate = derive_where_, crate = derive_where_)] | ^^^^^ error: duplicate `crate` option - --> tests/ui/item.rs:26:16 + --> tests/ui/item.rs:35:16 | -26 | #[derive_where(crate = derive_where_)] +35 | #[derive_where(crate = derive_where_)] | ^^^^^^^^^^^^^^^^^^^^^ error: no traits found to implement, use `#[derive_where(..)` to specify some - --> tests/ui/item.rs:30:1 + --> tests/ui/item.rs:39:1 | -30 | struct OnlyCrate(PhantomData); +39 | struct OnlyCrate(PhantomData); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: unnecessary path qualification, `::derive_where` is used by default - --> tests/ui/item.rs:32:24 + --> tests/ui/item.rs:41:24 | -32 | #[derive_where(crate = ::derive_where)] +41 | #[derive_where(crate = ::derive_where)] | ^^^^^^^^^^^^^^ +error: unsupported trait syntax, expected one of Clone, Copy, Debug, Default, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Zeroize, ZeroizeOnDrop + --> tests/ui/item.rs:44:16 + | +44 | #[derive_where(,Clone)] + | ^ + +error: unsupported trait syntax, expected one of Clone, Copy, Debug, Default, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Zeroize, ZeroizeOnDrop + --> tests/ui/item.rs:47:22 + | +47 | #[derive_where(Clone,,)] + | ^ + +error: unsupported trait syntax, expected one of Clone, Copy, Debug, Default, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Zeroize, ZeroizeOnDrop + --> tests/ui/item.rs:50:16 + | +50 | #[derive_where("Clone")] + | ^^^^^^^ + +error: unsupported trait, expected one of Clone, Copy, Debug, Default, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Zeroize, ZeroizeOnDrop + --> tests/ui/item.rs:53:16 + | +53 | #[derive_where(T)] + | ^ + error: unexpected option syntax - --> tests/ui/item.rs:35:16 + --> tests/ui/item.rs:56:16 | -35 | #[derive_where(Debug = invalid; T)] +56 | #[derive_where(Debug = invalid; T)] | ^^^^^^^^^^^^^^^ error: expected `,` - --> tests/ui/item.rs:38:24 + --> tests/ui/item.rs:59:24 | -38 | #[derive_where(Clone; T;)] +59 | #[derive_where(Clone; T;)] | ^ error: expected type to bind to, expected one of: `for`, parentheses, `fn`, `unsafe`, `extern`, identifier, `::`, `<`, `dyn`, square brackets, `*`, `&`, `!`, `impl`, `_`, lifetime - --> tests/ui/item.rs:41:25 + --> tests/ui/item.rs:62:25 | -41 | #[derive_where(Clone; T,,)] +62 | #[derive_where(Clone; T,,)] | ^ error: expected type to bind to, expected one of: `for`, parentheses, `fn`, `unsafe`, `extern`, identifier, `::`, `<`, `dyn`, square brackets, `*`, `&`, `!`, `impl`, `_`, lifetime - --> tests/ui/item.rs:44:23 + --> tests/ui/item.rs:65:23 | -44 | #[derive_where(Clone; where)] +65 | #[derive_where(Clone; where)] | ^^^^^ error: expected `;` or `, - --> tests/ui/item.rs:47:22 + --> tests/ui/item.rs:68:22 | -47 | #[derive_where(Clone Debug)] +68 | #[derive_where(Clone Debug)] | ^^^^^ error: expected `,` - --> tests/ui/item.rs:50:25 + --> tests/ui/item.rs:71:25 | -50 | #[derive_where(Clone; T U)] +71 | #[derive_where(Clone; T U)] | ^ error: `#[derive_where(..)` was already applied to this item before, this occurs when using a qualified path for any `#[derive_where(..)`s except the first - --> tests/ui/item.rs:53:1 + --> tests/ui/item.rs:74:1 | -53 | #[derive_where(Clone)] +74 | #[derive_where(Clone)] | ^^^^^^^^^^^^^^^^^^^^^^ | = note: this error originates in the attribute macro `derive_where` (in Nightly builds, run with -Z macro-backtrace for more info) error: duplicate trait with the same bound - --> tests/ui/item.rs:57:23 + --> tests/ui/item.rs:78:23 | -57 | #[derive_where(Clone, Clone)] +78 | #[derive_where(Clone, Clone)] | ^^^^^ error: duplicate trait with the same bound - --> tests/ui/item.rs:61:16 + --> tests/ui/item.rs:82:16 | -61 | #[derive_where(Clone)] +82 | #[derive_where(Clone)] | ^^^^^ diff --git a/tests/ui/item_option_syntax.rs b/tests/ui/item_option_syntax.rs index bd727262..61c52755 100644 --- a/tests/ui/item_option_syntax.rs +++ b/tests/ui/item_option_syntax.rs @@ -5,6 +5,9 @@ use derive_where::derive_where; #[derive_where()] struct EmptyAttribute(PhantomData); +#[derive_where = "invalid"] +struct WrongAttributeSyntax(PhantomData); + #[derive_where(Debug = "option")] struct WrongOptionSyntax(PhantomData); diff --git a/tests/ui/item_option_syntax.stderr b/tests/ui/item_option_syntax.stderr index c58f2cbe..f96261e1 100644 --- a/tests/ui/item_option_syntax.stderr +++ b/tests/ui/item_option_syntax.stderr @@ -6,20 +6,32 @@ error: empty `derive_where` found | = note: this error originates in the attribute macro `derive_where` (in Nightly builds, run with -Z macro-backtrace for more info) -error: unexpected option syntax - --> tests/ui/item_option_syntax.rs:8:16 +error: key-value macro attributes are not supported + --> tests/ui/item_option_syntax.rs:8:1 | -8 | #[derive_where(Debug = "option")] - | ^^^^^^^^^^^^^^^^ +8 | #[derive_where = "invalid"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: empty attribute option found +error: unsupported trait syntax, expected one of Clone, Copy, Debug, Default, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Zeroize, ZeroizeOnDrop + --> tests/ui/item_option_syntax.rs:8:18 + | +8 | #[derive_where = "invalid"] + | ^^^^^^^^^ + +error: unexpected option syntax --> tests/ui/item_option_syntax.rs:11:16 | -11 | #[derive_where(Debug())] +11 | #[derive_where(Debug = "option")] + | ^^^^^^^^^^^^^^^^ + +error: empty attribute option found + --> tests/ui/item_option_syntax.rs:14:16 + | +14 | #[derive_where(Debug())] | ^^^^^^^ error: `Debug` doesn't support any options - --> tests/ui/item_option_syntax.rs:14:16 + --> tests/ui/item_option_syntax.rs:17:16 | -14 | #[derive_where(Debug(option))] +17 | #[derive_where(Debug(option))] | ^^^^^^^^^^^^^ diff --git a/tests/ui/item_skip.rs b/tests/ui/item_skip.rs index 3605981f..ff5b281a 100644 --- a/tests/ui/item_skip.rs +++ b/tests/ui/item_skip.rs @@ -31,6 +31,10 @@ struct UnderridingSkipInner(PhantomData); #[derive_where(skip_inner)] struct NoSupportedTrait(PhantomData); +#[derive_where(Copy; T)] +#[derive_where(skip_inner(Copy))] +struct UnsupportedTrait(PhantomData); + #[derive_where(Debug)] #[derive_where(skip_inner(Debug, Debug))] struct DuplicateTraitSame(PhantomData); diff --git a/tests/ui/item_skip.stderr b/tests/ui/item_skip.stderr index 2d587332..529a8993 100644 --- a/tests/ui/item_skip.stderr +++ b/tests/ui/item_skip.stderr @@ -34,20 +34,26 @@ error: no trait that can be skipped is being implemented 31 | #[derive_where(skip_inner)] | ^^^^^^^^^^ +error: unsupported skip group, expected one of Clone, Debug, EqHashOrd, Hash, Zeroize + --> tests/ui/item_skip.rs:35:27 + | +35 | #[derive_where(skip_inner(Copy))] + | ^^^^ + error: duplicate `Debug` constraint on `skip` - --> tests/ui/item_skip.rs:35:34 + --> tests/ui/item_skip.rs:39:34 | -35 | #[derive_where(skip_inner(Debug, Debug))] +39 | #[derive_where(skip_inner(Debug, Debug))] | ^^^^^ error: duplicate `Debug` constraint on `skip` - --> tests/ui/item_skip.rs:40:27 + --> tests/ui/item_skip.rs:44:27 | -40 | #[derive_where(skip_inner(Debug))] +44 | #[derive_where(skip_inner(Debug))] | ^^^^^ error: trait to be skipped isn't being implemented - --> tests/ui/item_skip.rs:44:27 + --> tests/ui/item_skip.rs:48:27 | -44 | #[derive_where(skip_inner(Debug))] +48 | #[derive_where(skip_inner(Debug))] | ^^^^^ diff --git a/tests/ui/not-serde/serde.rs b/tests/ui/not-serde/serde.rs new file mode 100644 index 00000000..43db877e --- /dev/null +++ b/tests/ui/not-serde/serde.rs @@ -0,0 +1,9 @@ +use std::marker::PhantomData; + +use derive_where::derive_where; + +#[derive_where(Clone)] +#[serde(crate = "serde_")] +struct InvalidAttribute(PhantomData); + +fn main() {} diff --git a/tests/ui/not-serde/serde.stderr b/tests/ui/not-serde/serde.stderr new file mode 100644 index 00000000..bfc85c4a --- /dev/null +++ b/tests/ui/not-serde/serde.stderr @@ -0,0 +1,5 @@ +error: cannot find attribute `serde` in this scope + --> tests/ui/not-serde/serde.rs:6:3 + | +6 | #[serde(crate = "serde_")] + | ^^^^^ diff --git a/tests/ui/not-serde/support.rs b/tests/ui/not-serde/support.rs new file mode 100644 index 00000000..2b93185d --- /dev/null +++ b/tests/ui/not-serde/support.rs @@ -0,0 +1,11 @@ +use std::marker::PhantomData; + +use derive_where::derive_where; + +#[derive_where(Deserialize)] +struct UnsupportedDeserialize(PhantomData); + +#[derive_where(Serialize)] +struct UnsupportedSerialize(PhantomData); + +fn main() {} diff --git a/tests/ui/not-serde/support.stderr b/tests/ui/not-serde/support.stderr new file mode 100644 index 00000000..598f3b5d --- /dev/null +++ b/tests/ui/not-serde/support.stderr @@ -0,0 +1,11 @@ +error: requires crate feature `serde` + --> tests/ui/not-serde/support.rs:5:16 + | +5 | #[derive_where(Deserialize)] + | ^^^^^^^^^^^ + +error: requires crate feature `serde` + --> tests/ui/not-serde/support.rs:8:16 + | +8 | #[derive_where(Serialize)] + | ^^^^^^^^^ diff --git a/tests/ui/not-zeroize-on-drop/zeroize-on-drop.rs b/tests/ui/not-zeroize-on-drop/zeroize-on-drop.rs index b803b57c..bdd7ebb9 100644 --- a/tests/ui/not-zeroize-on-drop/zeroize-on-drop.rs +++ b/tests/ui/not-zeroize-on-drop/zeroize-on-drop.rs @@ -5,6 +5,6 @@ use std::marker::PhantomData; use derive_where::derive_where; #[derive_where(ZeroizeOnDrop(no_drop))] -struct InvalidOption(PhantomData); +struct UnsupportedOption(PhantomData); fn main() {} diff --git a/tests/ui/not-zeroize-on-drop/zeroize-on-drop.stderr b/tests/ui/not-zeroize-on-drop/zeroize-on-drop.stderr index d4dbae0f..1c5e0c97 100644 --- a/tests/ui/not-zeroize-on-drop/zeroize-on-drop.stderr +++ b/tests/ui/not-zeroize-on-drop/zeroize-on-drop.stderr @@ -1,4 +1,4 @@ -error: `ZeroizeOnDrop` doesn't support this option +error: requires crate feature `zeroize-on-drop` --> tests/ui/not-zeroize-on-drop/zeroize-on-drop.rs:7:30 | 7 | #[derive_where(ZeroizeOnDrop(no_drop))] diff --git a/tests/ui/not-zeroize/item.rs b/tests/ui/not-zeroize/item.rs deleted file mode 100644 index 959d90ae..00000000 --- a/tests/ui/not-zeroize/item.rs +++ /dev/null @@ -1,26 +0,0 @@ -use std::marker::PhantomData; - -use derive_where::derive_where; - -#[derive_where(crate = struct Test)] -struct InvalidPath(PhantomData); - -#[derive_where(skip_inner, Clone)] -struct SkipInnerWithTrait(PhantomData); - -#[derive_where(,)] -struct OnlyComma(PhantomData); - -#[derive_where(,Clone)] -struct StartWithComma(PhantomData); - -#[derive_where(Clone,,)] -struct DuplicateCommaAtEnd(PhantomData); - -#[derive_where("Clone")] -struct InvalidTrait(PhantomData); - -#[derive_where(T)] -struct GenericInsteadTrait(T, PhantomData); - -fn main() {} diff --git a/tests/ui/not-zeroize/item.stderr b/tests/ui/not-zeroize/item.stderr deleted file mode 100644 index b0bd2345..00000000 --- a/tests/ui/not-zeroize/item.stderr +++ /dev/null @@ -1,41 +0,0 @@ -error: unsupported trait syntax, expected one of Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd - --> tests/ui/not-zeroize/item.rs:5:24 - | -5 | #[derive_where(crate = struct Test)] - | ^^^^^^ - -error: unsupported trait, expected one of Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd - --> tests/ui/not-zeroize/item.rs:8:16 - | -8 | #[derive_where(skip_inner, Clone)] - | ^^^^^^^^^^ - -error: unsupported trait syntax, expected one of Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd - --> tests/ui/not-zeroize/item.rs:11:16 - | -11 | #[derive_where(,)] - | ^ - -error: unsupported trait syntax, expected one of Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd - --> tests/ui/not-zeroize/item.rs:14:16 - | -14 | #[derive_where(,Clone)] - | ^ - -error: unsupported trait syntax, expected one of Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd - --> tests/ui/not-zeroize/item.rs:17:22 - | -17 | #[derive_where(Clone,,)] - | ^ - -error: unsupported trait syntax, expected one of Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd - --> tests/ui/not-zeroize/item.rs:20:16 - | -20 | #[derive_where("Clone")] - | ^^^^^^^ - -error: unsupported trait, expected one of Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd - --> tests/ui/not-zeroize/item.rs:23:16 - | -23 | #[derive_where(T)] - | ^ diff --git a/tests/ui/not-zeroize/item_option_syntax.stderr b/tests/ui/not-zeroize/item_option_syntax.stderr deleted file mode 100644 index b1a1f92a..00000000 --- a/tests/ui/not-zeroize/item_option_syntax.stderr +++ /dev/null @@ -1,11 +0,0 @@ -error: key-value macro attributes are not supported - --> tests/ui/not-zeroize/item_option_syntax.rs:5:1 - | -5 | #[derive_where = "invalid"] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: unsupported trait syntax, expected one of Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd - --> tests/ui/not-zeroize/item_option_syntax.rs:5:18 - | -5 | #[derive_where = "invalid"] - | ^^^^^^^^^ diff --git a/tests/ui/not-zeroize/item_skip.rs b/tests/ui/not-zeroize/item_skip.rs deleted file mode 100644 index 3a9464cb..00000000 --- a/tests/ui/not-zeroize/item_skip.rs +++ /dev/null @@ -1,9 +0,0 @@ -use std::marker::PhantomData; - -use derive_where::derive_where; - -#[derive_where(Copy; T)] -#[derive_where(skip_inner(Copy))] -struct UnsupportedTrait(PhantomData); - -fn main() {} diff --git a/tests/ui/not-zeroize/item_skip.stderr b/tests/ui/not-zeroize/item_skip.stderr deleted file mode 100644 index de66a5e7..00000000 --- a/tests/ui/not-zeroize/item_skip.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: unsupported skip group, expected one of Clone, Debug, EqHashOrd, Hash - --> tests/ui/not-zeroize/item_skip.rs:6:27 - | -6 | #[derive_where(skip_inner(Copy))] - | ^^^^ diff --git a/tests/ui/not-zeroize/skip.rs b/tests/ui/not-zeroize/skip.rs deleted file mode 100644 index f8f861df..00000000 --- a/tests/ui/not-zeroize/skip.rs +++ /dev/null @@ -1,8 +0,0 @@ -use std::marker::PhantomData; - -use derive_where::derive_where; - -#[derive_where(Copy; T)] -struct UnsupportedTrait(#[derive_where(skip(Copy))] PhantomData); - -fn main() {} diff --git a/tests/ui/not-zeroize/skip.stderr b/tests/ui/not-zeroize/skip.stderr deleted file mode 100644 index 44e02135..00000000 --- a/tests/ui/not-zeroize/skip.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: unsupported skip group, expected one of Clone, Debug, EqHashOrd, Hash - --> tests/ui/not-zeroize/skip.rs:6:48 - | -6 | struct UnsupportedTrait(#[derive_where(skip(Copy))] PhantomData); - | ^^^^ diff --git a/tests/ui/not-zeroize/item_option_syntax.rs b/tests/ui/not-zeroize/support.rs similarity index 50% rename from tests/ui/not-zeroize/item_option_syntax.rs rename to tests/ui/not-zeroize/support.rs index a54c4c53..8cadf9d4 100644 --- a/tests/ui/not-zeroize/item_option_syntax.rs +++ b/tests/ui/not-zeroize/support.rs @@ -2,7 +2,7 @@ use std::marker::PhantomData; use derive_where::derive_where; -#[derive_where = "invalid"] -struct WrongAttributeSyntax(PhantomData); +#[derive_where(Zeroize)] +struct Unsupported(PhantomData); fn main() {} diff --git a/tests/ui/not-zeroize/support.stderr b/tests/ui/not-zeroize/support.stderr new file mode 100644 index 00000000..c6211518 --- /dev/null +++ b/tests/ui/not-zeroize/support.stderr @@ -0,0 +1,5 @@ +error: requires crate feature `zeroize` + --> tests/ui/not-zeroize/support.rs:5:16 + | +5 | #[derive_where(Zeroize)] + | ^^^^^^^ diff --git a/tests/ui/serde/crate_syntax.rs b/tests/ui/serde/crate_syntax.rs new file mode 100644 index 00000000..96c6c1e5 --- /dev/null +++ b/tests/ui/serde/crate_syntax.rs @@ -0,0 +1,26 @@ +use std::marker::PhantomData; + +use derive_where::derive_where; + +#[derive_where(Deserialize)] +#[serde(crate)] +struct WrongCrateSyntax1(PhantomData); + +#[derive_where(Deserialize)] +#[serde(crate())] +struct WrongCrateSyntax2(PhantomData); + +#[derive_where(Deserialize)] +#[serde(crate = ())] +struct NotString(PhantomData); + +#[derive_where(Deserialize)] +#[serde(crate = "1")] +struct NotPath(PhantomData); + +#[derive_where(Deserialize)] +#[serde(crate = "serde_")] +#[serde(crate = "serde_")] +struct Duplicate(PhantomData); + +fn main() {} diff --git a/tests/ui/serde/crate_syntax.stderr b/tests/ui/serde/crate_syntax.stderr new file mode 100644 index 00000000..db01e7de --- /dev/null +++ b/tests/ui/serde/crate_syntax.stderr @@ -0,0 +1,29 @@ +error: unexpected option syntax + --> tests/ui/serde/crate_syntax.rs:6:9 + | +6 | #[serde(crate)] + | ^^^^^ + +error: unexpected option syntax + --> tests/ui/serde/crate_syntax.rs:10:9 + | +10 | #[serde(crate())] + | ^^^^^^^ + +error: unexpected option syntax + --> tests/ui/serde/crate_syntax.rs:14:17 + | +14 | #[serde(crate = ())] + | ^^ + +error: expected path, expected identifier + --> tests/ui/serde/crate_syntax.rs:18:17 + | +18 | #[serde(crate = "1")] + | ^^^ + +error: duplicate `crate` option + --> tests/ui/serde/crate_syntax.rs:23:9 + | +23 | #[serde(crate = "serde_")] + | ^^^^^^^^^^^^^^^^ diff --git a/tests/ui/serde/serde.rs b/tests/ui/serde/serde.rs new file mode 100644 index 00000000..0548985f --- /dev/null +++ b/tests/ui/serde/serde.rs @@ -0,0 +1,13 @@ +use std::marker::PhantomData; + +use derive_where::derive_where; + +#[derive_where(Clone)] +#[serde(crate = "serde_")] +struct InvalidSerde(PhantomData); + +#[derive_where(Deserialize)] +#[serde(bound = "")] +struct ConflictingBound(PhantomData); + +fn main() {} diff --git a/tests/ui/serde/serde.stderr b/tests/ui/serde/serde.stderr new file mode 100644 index 00000000..70e8be4d --- /dev/null +++ b/tests/ui/serde/serde.stderr @@ -0,0 +1,11 @@ +error: Found unused `#[serde(...)]` + --> tests/ui/serde/serde.rs:6:1 + | +6 | #[serde(crate = "serde_")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: Found conflicting `#[serde(bound ...)]` + --> tests/ui/serde/serde.rs:10:9 + | +10 | #[serde(bound = "")] + | ^^^^^^^^^^ diff --git a/tests/ui/skip.rs b/tests/ui/skip.rs index bd367bb4..d1598d28 100644 --- a/tests/ui/skip.rs +++ b/tests/ui/skip.rs @@ -32,6 +32,9 @@ struct UnderridingSkip( #[derive_where(Clone; T)] struct NoSupportedTrait(#[derive_where(skip)] PhantomData); +#[derive_where(Copy; T)] +struct UnsupportedTrait(#[derive_where(skip(Copy))] PhantomData); + #[derive_where(Debug)] struct DuplicateTraitSame(#[derive_where(skip(Debug, Debug))] PhantomData); diff --git a/tests/ui/skip.stderr b/tests/ui/skip.stderr index a22e7651..9097a5b1 100644 --- a/tests/ui/skip.stderr +++ b/tests/ui/skip.stderr @@ -34,39 +34,45 @@ error: no trait that can be skipped is being implemented 33 | struct NoSupportedTrait(#[derive_where(skip)] PhantomData); | ^^^^ +error: unsupported skip group, expected one of Clone, Debug, EqHashOrd, Hash, Zeroize + --> tests/ui/skip.rs:36:48 + | +36 | struct UnsupportedTrait(#[derive_where(skip(Copy))] PhantomData); + | ^^^^ + error: duplicate `Debug` constraint on `skip` - --> tests/ui/skip.rs:36:57 + --> tests/ui/skip.rs:39:57 | -36 | struct DuplicateTraitSame(#[derive_where(skip(Debug, Debug))] PhantomData); +39 | struct DuplicateTraitSame(#[derive_where(skip(Debug, Debug))] PhantomData); | ^^^^^ error: duplicate `Debug` constraint on `skip` - --> tests/ui/skip.rs:41:22 + --> tests/ui/skip.rs:44:22 | -41 | #[derive_where(skip(Debug))] +44 | #[derive_where(skip(Debug))] | ^^^^^ error: trait to be skipped isn't being implemented - --> tests/ui/skip.rs:46:50 + --> tests/ui/skip.rs:49:50 | -46 | struct MissingDeriveTrait(#[derive_where(skip(Debug))] PhantomData); +49 | struct MissingDeriveTrait(#[derive_where(skip(Debug))] PhantomData); | ^^^^^ error: Cannot skip `Clone` while deriving `Copy` - --> tests/ui/skip.rs:49:50 + --> tests/ui/skip.rs:52:50 | -49 | struct SkipCloneWhileCopy(#[derive_where(skip(Clone))] PhantomData); +52 | struct SkipCloneWhileCopy(#[derive_where(skip(Clone))] PhantomData); | ^^^^^ error[E0277]: the trait bound `NonDefault: Default` is not satisfied - --> tests/ui/skip.rs:53:1 + --> tests/ui/skip.rs:56:1 | -53 | #[derive_where(Clone)] +56 | #[derive_where(Clone)] | ^^^^^^^^^^^^^^^^^^^^^^ the trait `Default` is not implemented for `NonDefault` | = note: this error originates in the derive macro `::derive_where::DeriveWhere` (in Nightly builds, run with -Z macro-backtrace for more info) help: consider annotating `NonDefault` with `#[derive(Default)]` | -51 + #[derive(Default)] -52 | struct NonDefault(PhantomData); +54 + #[derive(Default)] +55 | struct NonDefault(PhantomData); | diff --git a/tests/ui/zeroize/item.rs b/tests/ui/zeroize/item.rs deleted file mode 100644 index 959d90ae..00000000 --- a/tests/ui/zeroize/item.rs +++ /dev/null @@ -1,26 +0,0 @@ -use std::marker::PhantomData; - -use derive_where::derive_where; - -#[derive_where(crate = struct Test)] -struct InvalidPath(PhantomData); - -#[derive_where(skip_inner, Clone)] -struct SkipInnerWithTrait(PhantomData); - -#[derive_where(,)] -struct OnlyComma(PhantomData); - -#[derive_where(,Clone)] -struct StartWithComma(PhantomData); - -#[derive_where(Clone,,)] -struct DuplicateCommaAtEnd(PhantomData); - -#[derive_where("Clone")] -struct InvalidTrait(PhantomData); - -#[derive_where(T)] -struct GenericInsteadTrait(T, PhantomData); - -fn main() {} diff --git a/tests/ui/zeroize/item.stderr b/tests/ui/zeroize/item.stderr deleted file mode 100644 index f284bd79..00000000 --- a/tests/ui/zeroize/item.stderr +++ /dev/null @@ -1,41 +0,0 @@ -error: unsupported trait syntax, expected one of Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize, ZeroizeOnDrop - --> tests/ui/zeroize/item.rs:5:24 - | -5 | #[derive_where(crate = struct Test)] - | ^^^^^^ - -error: unsupported trait, expected one of Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize, ZeroizeOnDrop - --> tests/ui/zeroize/item.rs:8:16 - | -8 | #[derive_where(skip_inner, Clone)] - | ^^^^^^^^^^ - -error: unsupported trait syntax, expected one of Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize, ZeroizeOnDrop - --> tests/ui/zeroize/item.rs:11:16 - | -11 | #[derive_where(,)] - | ^ - -error: unsupported trait syntax, expected one of Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize, ZeroizeOnDrop - --> tests/ui/zeroize/item.rs:14:16 - | -14 | #[derive_where(,Clone)] - | ^ - -error: unsupported trait syntax, expected one of Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize, ZeroizeOnDrop - --> tests/ui/zeroize/item.rs:17:22 - | -17 | #[derive_where(Clone,,)] - | ^ - -error: unsupported trait syntax, expected one of Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize, ZeroizeOnDrop - --> tests/ui/zeroize/item.rs:20:16 - | -20 | #[derive_where("Clone")] - | ^^^^^^^ - -error: unsupported trait, expected one of Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize, ZeroizeOnDrop - --> tests/ui/zeroize/item.rs:23:16 - | -23 | #[derive_where(T)] - | ^ diff --git a/tests/ui/zeroize/item_option_syntax.rs b/tests/ui/zeroize/item_option_syntax.rs deleted file mode 100644 index a54c4c53..00000000 --- a/tests/ui/zeroize/item_option_syntax.rs +++ /dev/null @@ -1,8 +0,0 @@ -use std::marker::PhantomData; - -use derive_where::derive_where; - -#[derive_where = "invalid"] -struct WrongAttributeSyntax(PhantomData); - -fn main() {} diff --git a/tests/ui/zeroize/item_option_syntax.stderr b/tests/ui/zeroize/item_option_syntax.stderr deleted file mode 100644 index 6a44c3f7..00000000 --- a/tests/ui/zeroize/item_option_syntax.stderr +++ /dev/null @@ -1,11 +0,0 @@ -error: key-value macro attributes are not supported - --> tests/ui/zeroize/item_option_syntax.rs:5:1 - | -5 | #[derive_where = "invalid"] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: unsupported trait syntax, expected one of Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize, ZeroizeOnDrop - --> tests/ui/zeroize/item_option_syntax.rs:5:18 - | -5 | #[derive_where = "invalid"] - | ^^^^^^^^^ diff --git a/tests/ui/zeroize/item_skip.rs b/tests/ui/zeroize/item_skip.rs deleted file mode 100644 index 3a9464cb..00000000 --- a/tests/ui/zeroize/item_skip.rs +++ /dev/null @@ -1,9 +0,0 @@ -use std::marker::PhantomData; - -use derive_where::derive_where; - -#[derive_where(Copy; T)] -#[derive_where(skip_inner(Copy))] -struct UnsupportedTrait(PhantomData); - -fn main() {} diff --git a/tests/ui/zeroize/item_skip.stderr b/tests/ui/zeroize/item_skip.stderr deleted file mode 100644 index 9f8ec6f8..00000000 --- a/tests/ui/zeroize/item_skip.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: unsupported skip group, expected one of Clone, Debug, EqHashOrd, Hash, Zeroize - --> tests/ui/zeroize/item_skip.rs:6:27 - | -6 | #[derive_where(skip_inner(Copy))] - | ^^^^ diff --git a/tests/ui/zeroize/skip.rs b/tests/ui/zeroize/skip.rs deleted file mode 100644 index 6f866f0d..00000000 --- a/tests/ui/zeroize/skip.rs +++ /dev/null @@ -1,8 +0,0 @@ -use std::marker::PhantomData; - -use derive_where::derive_where; - -#[derive_where(Skip; T)] -struct UnsupportedTrait(#[derive_where(skip(Skip))] PhantomData); - -fn main() {} diff --git a/tests/ui/zeroize/skip.stderr b/tests/ui/zeroize/skip.stderr deleted file mode 100644 index acc20bab..00000000 --- a/tests/ui/zeroize/skip.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: unsupported trait, expected one of Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize, ZeroizeOnDrop - --> tests/ui/zeroize/skip.rs:5:16 - | -5 | #[derive_where(Skip; T)] - | ^^^^ diff --git a/tests/util/mod.rs b/tests/util/mod.rs index d2f1eb7f..89cf5e3b 100644 --- a/tests/util/mod.rs +++ b/tests/util/mod.rs @@ -8,11 +8,15 @@ use std::{ ptr, }; +use serde_::{Deserialize, Serialize}; #[cfg(feature = "zeroize")] use zeroize_::Zeroize; #[cfg(feature = "zeroize-on-drop")] use zeroize_::ZeroizeOnDrop; +#[derive(Deserialize, Serialize)] +#[serde(crate = "serde_")] +#[serde(bound = "")] pub struct Wrapper { data: i32, hack: PhantomData,