diff --git a/CHANGELOG.md b/CHANGELOG.md index 83e224b6..6d4f1c8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Added +- `no_drop` item-level option to `ZeroizeOnDrop` which does not implement + `Drop` but instead only asserts that every field implements `ZeroizeOnDrop`. + ## [1.4.0] - 2025-05-01 ### Added diff --git a/README.md b/README.md index 9c522140..2cad2678 100644 --- a/README.md +++ b/README.md @@ -238,9 +238,12 @@ If the `zeroize-on-drop` feature is enabled, it implements [`ZeroizeOnDrop`] and can be implemented without [`Zeroize`], otherwise it only implements [`Drop`] and requires [`Zeroize`] to be implemented. -[`ZeroizeOnDrop`] has one option: +[`ZeroizeOnDrop`] has two options: - `crate`: an item-level option which specifies a path to the [`zeroize`] crate in case of a re-export or rename. +- `no_drop`: an item-level option which will not implement [`Drop`] but instead + only assert that every field implements [`ZeroizeOnDrop`]. Requires the + `zeroize-on-drop` feature. ```rust #[derive_where(ZeroizeOnDrop(crate = zeroize_))] diff --git a/src/attr.rs b/src/attr.rs index 9b8419d0..41cc5d95 100644 --- a/src/attr.rs +++ b/src/attr.rs @@ -15,7 +15,7 @@ pub use self::{ default::Default, field::FieldAttr, incomparable::Incomparable, - item::{DeriveTrait, DeriveWhere, ItemAttr}, + item::{DeriveWhere, ItemAttr}, skip::{Skip, SkipGroup}, variant::VariantAttr, }; diff --git a/src/attr/field.rs b/src/attr/field.rs index d687827f..fc7c7233 100644 --- a/src/attr/field.rs +++ b/src/attr/field.rs @@ -4,7 +4,7 @@ use syn::{spanned::Spanned, Attribute, Meta, Result}; use crate::{util::MetaListExt, DeriveWhere, Error, Skip, DERIVE_WHERE}; #[cfg(feature = "zeroize")] -use crate::{Trait, TraitImpl, ZeroizeFqs}; +use crate::{Trait, ZeroizeFqs}; /// Attributes on field. #[derive(Default)] diff --git a/src/attr/incomparable.rs b/src/attr/incomparable.rs index b6d337ff..94d6f070 100644 --- a/src/attr/incomparable.rs +++ b/src/attr/incomparable.rs @@ -3,7 +3,7 @@ use proc_macro2::Span; use syn::{spanned::Spanned, Meta, Result}; -use crate::{attr::DeriveTrait, DeriveWhere, Error}; +use crate::{trait_::DeriveTrait, DeriveWhere, Error}; /// Stores if this variant should be incomparable when implementing /// [`PartialEq`] or [`PartialOrd`]. diff --git a/src/attr/item.rs b/src/attr/item.rs index 215b23a3..e6ba68a6 100644 --- a/src/attr/item.rs +++ b/src/attr/item.rs @@ -1,20 +1,17 @@ //! [`Attribute`] parsing for items. -use std::{borrow::Cow, ops::Deref}; +use std::borrow::Cow; use proc_macro2::Span; use syn::{ parse::{discouraged::Speculative, Parse, ParseStream}, punctuated::Punctuated, spanned::Spanned, - Attribute, BoundLifetimes, Data, Ident, Meta, Path, PredicateType, Result, Token, TraitBound, - TraitBoundModifier, Type, TypeParamBound, TypePath, WhereClause, WherePredicate, + Attribute, BoundLifetimes, Data, Ident, Meta, PredicateType, Result, Token, Type, TypePath, + WhereClause, WherePredicate, }; -use crate::{ - util::{self, MetaListExt}, - Error, Incomparable, Item, Skip, SkipGroup, Trait, TraitImpl, DERIVE_WHERE, -}; +use crate::{trait_::DeriveTrait, Error, Incomparable, Item, Skip, SkipGroup, Trait, DERIVE_WHERE}; /// Attributes on item. #[derive(Default)] @@ -259,7 +256,7 @@ impl DeriveWhere { pub fn any_skip(&self) -> bool { self.traits .iter() - .any(|trait_| SkipGroup::trait_supported_by_skip_all(**trait_)) + .any(|trait_| SkipGroup::trait_supported_by_skip_all(***trait_)) } /// Create [`WhereClause`] for the given parameters. @@ -353,172 +350,3 @@ impl Parse for Generic { } } } - -/// Trait to implement. -#[derive(Eq, PartialEq)] -pub enum DeriveTrait { - /// [`Clone`]. - Clone, - /// [`Copy`]. - Copy, - /// [`Debug`](std::fmt::Debug). - Debug, - /// [`Default`]. - Default, - /// [`Eq`]. - Eq, - /// [`Hash`](std::hash::Hash). - Hash, - /// [`Ord`]. - Ord, - /// [`PartialEq`]. - PartialEq, - /// [`PartialOrd`]. - PartialOrd, - /// [`Zeroize`](https://docs.rs/zeroize/latest/zeroize/trait.Zeroize.html). - #[cfg(feature = "zeroize")] - Zeroize { - /// [`Zeroize`](https://docs.rs/zeroize/latest/zeroize/trait.Zeroize.html) path. - crate_: Option, - }, - /// [`ZeroizeOnDrop`](https://docs.rs/zeroize/latest/zeroize/trait.ZeroizeOnDrop.html). - #[cfg(feature = "zeroize")] - ZeroizeOnDrop { - /// [`ZeroizeOnDrop`](https://docs.rs/zeroize/latest/zeroize/trait.ZeroizeOnDrop.html) path. - crate_: Option, - }, -} - -impl Deref for DeriveTrait { - type Target = Trait; - - fn deref(&self) -> &Self::Target { - use DeriveTrait::*; - - match self { - Clone => &Trait::Clone, - Copy => &Trait::Copy, - Debug => &Trait::Debug, - Default => &Trait::Default, - Eq => &Trait::Eq, - Hash => &Trait::Hash, - Ord => &Trait::Ord, - PartialEq => &Trait::PartialEq, - PartialOrd => &Trait::PartialOrd, - #[cfg(feature = "zeroize")] - Zeroize { .. } => &Trait::Zeroize, - #[cfg(feature = "zeroize")] - ZeroizeOnDrop { .. } => &Trait::ZeroizeOnDrop, - } - } -} - -impl PartialEq for &DeriveTrait { - fn eq(&self, other: &Trait) -> bool { - let trait_: &Trait = self; - trait_ == other - } -} - -impl DeriveTrait { - /// Returns fully qualified [`Path`] for this trait. - pub fn path(&self) -> Path { - use DeriveTrait::*; - - match self { - Clone => util::path_from_root_and_strs(self.crate_(), &["clone", "Clone"]), - Copy => util::path_from_root_and_strs(self.crate_(), &["marker", "Copy"]), - Debug => util::path_from_root_and_strs(self.crate_(), &["fmt", "Debug"]), - Default => util::path_from_root_and_strs(self.crate_(), &["default", "Default"]), - Eq => util::path_from_root_and_strs(self.crate_(), &["cmp", "Eq"]), - Hash => util::path_from_root_and_strs(self.crate_(), &["hash", "Hash"]), - Ord => util::path_from_root_and_strs(self.crate_(), &["cmp", "Ord"]), - PartialEq => util::path_from_root_and_strs(self.crate_(), &["cmp", "PartialEq"]), - PartialOrd => util::path_from_root_and_strs(self.crate_(), &["cmp", "PartialOrd"]), - #[cfg(feature = "zeroize")] - Zeroize { .. } => util::path_from_root_and_strs(self.crate_(), &["Zeroize"]), - #[cfg(feature = "zeroize")] - ZeroizeOnDrop { .. } => util::path_from_root_and_strs(self.crate_(), &["ZeroizeOnDrop"]), - } - } - - /// Returns the path to the root crate for this trait. - pub fn crate_(&self) -> Path { - use DeriveTrait::*; - - match self { - Clone => util::path_from_strs(&["core"]), - Copy => util::path_from_strs(&["core"]), - Debug => util::path_from_strs(&["core"]), - Default => util::path_from_strs(&["core"]), - Eq => util::path_from_strs(&["core"]), - Hash => util::path_from_strs(&["core"]), - Ord => util::path_from_strs(&["core"]), - PartialEq => util::path_from_strs(&["core"]), - PartialOrd => util::path_from_strs(&["core"]), - #[cfg(feature = "zeroize")] - Zeroize { crate_, .. } => { - if let Some(crate_) = crate_ { - crate_.clone() - } else { - util::path_from_strs(&["zeroize"]) - } - } - #[cfg(feature = "zeroize")] - ZeroizeOnDrop { crate_, .. } => { - if let Some(crate_) = crate_ { - crate_.clone() - } else { - util::path_from_strs(&["zeroize"]) - } - } - } - } - - /// Returns where-clause bounds for the trait in respect of the item type. - fn where_bounds(&self, data: &Item) -> Punctuated { - let mut list = Punctuated::new(); - - list.push(TypeParamBound::Trait(TraitBound { - paren_token: None, - modifier: TraitBoundModifier::None, - lifetimes: None, - path: self.path(), - })); - - // Add bounds specific to the trait. - if let Some(bound) = self.additional_where_bounds(data) { - list.push(bound) - } - - list - } - - /// Create [`DeriveTrait`] from [`ParseStream`]. - fn from_stream(span: Span, data: &Data, input: ParseStream) -> Result<(Span, Self)> { - match Meta::parse(input) { - Ok(meta) => { - let trait_ = Trait::from_path(meta.path())?; - - if let Data::Union(_) = data { - // Make sure this `Trait` supports unions. - if !trait_.supports_union() { - return Err(Error::union(span)); - } - } - - match &meta { - Meta::Path(path) => Ok((path.span(), trait_.default_derive_trait())), - 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)?)) - } - Meta::NameValue(name_value) => Err(Error::option_syntax(name_value.span())), - } - } - Err(error) => Err(Error::trait_syntax(error.span())), - } - } -} diff --git a/src/attr/skip.rs b/src/attr/skip.rs index 1a56168a..a29af549 100644 --- a/src/attr/skip.rs +++ b/src/attr/skip.rs @@ -4,7 +4,7 @@ use std::default::Default; use syn::{spanned::Spanned, Meta, Path, Result}; -use crate::{attr::DeriveTrait, util::MetaListExt, DeriveWhere, Error, Trait}; +use crate::{trait_::DeriveTrait, util::MetaListExt, DeriveWhere, Error, Trait}; /// Stores what [`Trait`]s to skip this field or variant for. #[cfg_attr(test, derive(Debug))] diff --git a/src/attr/zeroize_fqs.rs b/src/attr/zeroize_fqs.rs index cedaa909..958aa344 100644 --- a/src/attr/zeroize_fqs.rs +++ b/src/attr/zeroize_fqs.rs @@ -2,7 +2,7 @@ use syn::{spanned::Spanned, Meta, Result}; -use crate::{util::MetaListExt, DeriveWhere, Error, Trait, TraitImpl}; +use crate::{util::MetaListExt, DeriveWhere, Error, Trait}; /// Stores if this field should use FQS to call [`Zeroize::zeroize`](https://docs.rs/zeroize/latest/zeroize/trait.Zeroize.html#tymethod.zeroize). #[derive(Default)] diff --git a/src/input.rs b/src/input.rs index 55a52fed..506d7893 100644 --- a/src/input.rs +++ b/src/input.rs @@ -3,10 +3,13 @@ use proc_macro2::Span; use syn::{DeriveInput, GenericParam, Generics, ImplGenerics, Result, TypeGenerics, WhereClause}; -#[cfg(feature = "zeroize")] -use crate::DeriveTrait; #[cfg(not(feature = "nightly"))] use crate::Discriminant; +#[cfg(feature = "zeroize")] +use crate::{ + trait_::{zeroize::Zeroize, zeroize_on_drop::ZeroizeOnDrop}, + DeriveTrait, +}; use crate::{Data, DeriveWhere, Either, Error, Item, ItemAttr, Trait}; /// Parsed input. @@ -170,7 +173,7 @@ impl<'a> Input<'a> { } // Any field is skipped with a corresponding `Trait`. - if item.any_skip_trait(**trait_) { + if item.any_skip_trait(***trait_) { continue; } @@ -181,9 +184,13 @@ impl<'a> Input<'a> { #[cfg(feature = "zeroize")] { - // `Zeroize(crate = ..)` or `ZeroizeOnDrop(crate = ..)` is used. - if let DeriveTrait::Zeroize { crate_: Some(_) } - | DeriveTrait::ZeroizeOnDrop { crate_: Some(_) } = *trait_ + // `Zeroize(crate = ..)`, `ZeroizeOnDrop(crate = ..)` or + // `ZeroizeOnDrop(no_drop)` is used. + if let DeriveTrait::Zeroize(Zeroize { crate_: Some(_) }) + | DeriveTrait::ZeroizeOnDrop(ZeroizeOnDrop { + crate_: Some(_), .. + }) + | DeriveTrait::ZeroizeOnDrop(ZeroizeOnDrop { no_drop: true, .. }) = *trait_ { continue; } diff --git a/src/lib.rs b/src/lib.rs index c5df73e2..a62b3087 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -272,9 +272,12 @@ //! and can be implemented without [`Zeroize`], otherwise it only implements //! [`Drop`] and requires [`Zeroize`] to be implemented. //! -//! [`ZeroizeOnDrop`] has one option: +//! [`ZeroizeOnDrop`] has two options: //! - `crate`: an item-level option which specifies a path to the [`zeroize`] //! crate in case of a re-export or rename. +//! - `no_drop`: an item-level option which will not implement [`Drop`] but +//! instead only assert that every field implements [`ZeroizeOnDrop`]. +//! Requires the `zeroize-on-drop` feature. //! //! ``` //! # #[cfg(feature = "zeroize-on-drop")] @@ -412,15 +415,12 @@ use self::attr::ZeroizeFqs; #[cfg(not(feature = "nightly"))] use self::item::Discriminant; use self::{ - attr::{ - Default, DeriveTrait, DeriveWhere, FieldAttr, Incomparable, ItemAttr, Skip, SkipGroup, - VariantAttr, - }, + attr::{Default, DeriveWhere, FieldAttr, Incomparable, ItemAttr, Skip, SkipGroup, VariantAttr}, data::{Data, DataType, Field, SimpleType}, error::Error, input::Input, item::Item, - trait_::{Trait, TraitImpl}, + trait_::{DeriveTrait, Trait, TraitImpl}, util::Either, }; @@ -439,6 +439,7 @@ const DERIVE_WHERE_VISITED: &str = "derive_where_visited"; /// trait. /// - `#[derive_where(ZeroizeOnDrop(crate = path))]`: Specify path to /// [`ZeroizeOnDrop`] trait. +/// - `#[derive_where(ZeroizeOnDrop(no_drop))]`: no [`Drop`] implementation. /// - `#[derive_where(skip_inner(EqHashOrd, ..))]`: Skip all fields in the item. /// Optionally specify trait groups to constrain skipping fields. Only works /// for structs, for enums use this on the variant-level. @@ -642,17 +643,9 @@ fn generate_impl( let body = generate_body(derive_where, trait_, item, generics); let ident = item.ident(); - let path = trait_.impl_path(trait_); - let mut output = quote! { - #[automatically_derived] - impl #imp #path for #ident #ty - #where_clause - { - #body - } - }; + let mut output = trait_.impl_item(imp, ident, ty, &where_clause, body); - if let Some((path, body)) = trait_.additional_impl(trait_) { + if let Some((path, body)) = trait_.additional_impl() { output.extend(quote! { #[automatically_derived] impl #imp #path for #ident #ty @@ -675,16 +668,16 @@ fn generate_body( ) -> TokenStream { match &item { Item::Item(data) => { - let body = trait_.build_body(derive_where, trait_, data); - trait_.build_signature(derive_where, item, generics, trait_, &body) + let body = trait_.build_body(derive_where, data); + trait_.build_signature(derive_where, item, generics, &body) } Item::Enum { variants, .. } => { let body: TokenStream = variants .iter() - .map(|data| trait_.build_body(derive_where, trait_, data)) + .map(|data| trait_.build_body(derive_where, data)) .collect(); - trait_.build_signature(derive_where, item, generics, trait_, &body) + trait_.build_signature(derive_where, item, generics, &body) } } } diff --git a/src/test/zeroize.rs b/src/test/zeroize.rs index 8ba1afd2..c3c1a725 100644 --- a/src/test/zeroize.rs +++ b/src/test/zeroize.rs @@ -302,3 +302,47 @@ fn enum_skip_drop() -> Result<()> { }, ) } + +#[test] +#[cfg(feature = "zeroize-on-drop")] +fn no_drop() -> Result<()> { + test_derive( + quote! { + #[derive_where(ZeroizeOnDrop(no_drop); T)] + struct Test(T, std::marker::PhantomData); + }, + quote! { + const _: () = { + trait DeriveWhereAssertZeroizeOnDrop { + fn assert(&mut self); + } + + impl DeriveWhereAssertZeroizeOnDrop for Test + where T: ::zeroize::ZeroizeOnDrop + { + fn assert(&mut self) { + trait AssertZeroizeOnDrop { + fn __derive_where_zeroize_on_drop(&mut self); + } + + impl AssertZeroizeOnDrop for T { + fn __derive_where_zeroize_on_drop(&mut self) {} + } + + match self { + Test (ref mut __field_0, ref mut __field_1) => { + __field_0.__derive_where_zeroize_on_drop(); + __field_1.__derive_where_zeroize_on_drop(); + } + } + } + } + }; + + #[automatically_derived] + impl ::zeroize::ZeroizeOnDrop for Test + where T: ::zeroize::ZeroizeOnDrop + { } + }, + ) +} diff --git a/src/trait_.rs b/src/trait_.rs index 912953ea..e6ebf7f6 100644 --- a/src/trait_.rs +++ b/src/trait_.rs @@ -1,24 +1,33 @@ //! Individual implementation for all traits. -mod clone; +pub mod clone; mod common_ord; -mod copy; -mod debug; -mod default; -mod eq; -mod hash; -mod ord; -mod partial_eq; -mod partial_ord; +pub mod copy; +pub mod debug; +pub mod default; +pub mod eq; +pub mod hash; +pub mod ord; +pub mod partial_eq; +pub mod partial_ord; #[cfg(feature = "zeroize")] -mod zeroize; +pub mod zeroize; #[cfg(feature = "zeroize")] -mod zeroize_on_drop; +pub mod zeroize_on_drop; + +use std::{borrow::Cow, ops::Deref}; use proc_macro2::{Span, TokenStream}; -use syn::{punctuated::Punctuated, spanned::Spanned, Meta, Path, Result, Token, TypeParamBound}; +use quote::quote; +use syn::{ + parse::{Parse, ParseStream}, + punctuated::Punctuated, + spanned::Spanned, + Ident, ImplGenerics, Meta, Path, Result, Token, TraitBound, TraitBoundModifier, TypeGenerics, + TypeParamBound, WhereClause, +}; -use crate::{Data, DeriveTrait, DeriveWhere, Error, Item, SplitGenerics}; +use crate::{util::MetaListExt, Data, DeriveWhere, Error, Item, SplitGenerics}; /// Type implementing [`TraitImpl`] for every trait. #[derive(Clone, Copy, Eq, PartialEq)] @@ -50,26 +59,27 @@ pub enum Trait { ZeroizeOnDrop, } -impl Trait { - /// Return dummy-struct for the internal implementation. - fn implementation(&self) -> &dyn TraitImpl { - match self { - Trait::Clone => &clone::Clone, - Trait::Copy => ©::Copy, - Trait::Debug => &debug::Debug, - Trait::Default => &default::Default, - Trait::Eq => &eq::Eq, - Trait::Hash => &hash::Hash, - Trait::Ord => &ord::Ord, - Trait::PartialEq => &partial_eq::PartialEq, - Trait::PartialOrd => &partial_ord::PartialOrd, +macro_rules! trait_dispatch { + ($self:expr, $method:ident($($par:expr),*)) => { + match $self { + Trait::Clone => clone::Clone::$method($($par),*), + Trait::Copy => copy::Copy::$method($($par),*), + Trait::Debug => debug::Debug::$method($($par),*), + Trait::Default => default::Default::$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 = "zeroize")] - Trait::Zeroize => &zeroize::Zeroize, + Trait::Zeroize => zeroize::Zeroize::$method($($par),*), #[cfg(feature = "zeroize")] - Trait::ZeroizeOnDrop => &zeroize_on_drop::ZeroizeOnDrop, + Trait::ZeroizeOnDrop => zeroize_on_drop::ZeroizeOnDrop::$method($($par),*), } - } + }; +} +impl Trait { /// Create [`Trait`] from [`Path`]. pub fn from_path(path: &Path) -> Result { if let Some(ident) = path.get_ident() { @@ -96,101 +106,212 @@ impl Trait { Err(Error::trait_(path.span())) } } -} -impl TraitImpl for Trait { - fn as_str(&self) -> &'static str { - self.implementation().as_str() + /// Re-direct to [`TraitImpl::as_str()`]. + pub fn as_str(&self) -> &'static str { + trait_dispatch!(self, as_str()) } - fn default_derive_trait(&self) -> DeriveTrait { - self.implementation().default_derive_trait() + /// Re-direct to [`TraitImpl::default_derive_trait()`]. + pub fn default_derive_trait(&self) -> DeriveTrait { + trait_dispatch!(self, default_derive_trait()) } - fn parse_derive_trait( + /// Re-direct to [`TraitImpl::parse_derive_trait()`]. + pub fn parse_derive_trait( &self, span: Span, list: Punctuated, ) -> Result { - self.implementation().parse_derive_trait(span, list) + trait_dispatch!(self, parse_derive_trait(span, list)) } - fn supports_union(&self) -> bool { - self.implementation().supports_union() + /// Re-direct to [`TraitImpl::supports_union()`]. + pub fn supports_union(&self) -> bool { + trait_dispatch!(self, supports_union()) } - fn additional_where_bounds(&self, data: &Item) -> Option { - self.implementation().additional_where_bounds(data) + /// Re-direct to [`TraitImpl::additional_where_bounds()`]. + pub fn additional_where_bounds(&self, data: &Item) -> Option { + trait_dispatch!(self, additional_where_bounds(data)) } +} + +/// Trait to implement. +#[derive(Eq, PartialEq)] +pub enum DeriveTrait { + /// [`Clone`]. + Clone, + /// [`Copy`]. + Copy, + /// [`Debug`](std::fmt::Debug). + Debug, + /// [`Default`]. + Default, + /// [`Eq`]. + Eq, + /// [`Hash`](std::hash::Hash). + Hash, + /// [`Ord`]. + Ord, + /// [`PartialEq`]. + PartialEq, + /// [`PartialOrd`]. + PartialOrd, + /// [`Zeroize`](https://docs.rs/zeroize/latest/zeroize/trait.Zeroize.html). + #[cfg(feature = "zeroize")] + Zeroize(zeroize::Zeroize), + /// [`ZeroizeOnDrop`](https://docs.rs/zeroize/latest/zeroize/trait.ZeroizeOnDrop.html). + #[cfg(feature = "zeroize")] + ZeroizeOnDrop(zeroize_on_drop::ZeroizeOnDrop), +} + +impl Deref for DeriveTrait { + type Target = dyn TraitImpl; - fn additional_impl(&self, trait_: &DeriveTrait) -> Option<(Path, TokenStream)> { - self.implementation().additional_impl(trait_) + fn deref(&self) -> &Self::Target { + use DeriveTrait::*; + + match self { + Clone => &clone::Clone, + Copy => ©::Copy, + Debug => &debug::Debug, + Default => &default::Default, + Eq => &eq::Eq, + Hash => &hash::Hash, + Ord => &ord::Ord, + PartialEq => &partial_eq::PartialEq, + PartialOrd => &partial_ord::PartialOrd, + #[cfg(feature = "zeroize")] + Zeroize(trait_) => trait_, + #[cfg(feature = "zeroize")] + ZeroizeOnDrop(trait_) => trait_, + } } +} - fn impl_path(&self, trait_: &DeriveTrait) -> Path { - self.implementation().impl_path(trait_) +impl PartialEq for &DeriveTrait { + fn eq(&self, other: &Trait) -> bool { + let trait_: &Trait = self; + trait_ == other } +} - fn build_signature( - &self, - derive_where: &DeriveWhere, - item: &Item, - generics: &SplitGenerics<'_>, - trait_: &DeriveTrait, - body: &TokenStream, - ) -> TokenStream { - self.implementation() - .build_signature(derive_where, item, generics, trait_, body) +impl DeriveTrait { + /// Returns where-clause bounds for the trait in respect of the item type. + pub fn where_bounds(&self, data: &Item) -> Punctuated { + let mut list = Punctuated::new(); + + list.push(TypeParamBound::Trait(TraitBound { + paren_token: None, + modifier: TraitBoundModifier::None, + lifetimes: None, + path: self.path(), + })); + + // Add bounds specific to the trait. + if let Some(bound) = self.additional_where_bounds(data) { + list.push(bound) + } + + list } - fn build_body( - &self, - derive_where: &DeriveWhere, - trait_: &DeriveTrait, - data: &Data, - ) -> TokenStream { - self.implementation().build_body(derive_where, trait_, data) + /// Create [`DeriveTrait`] from [`ParseStream`]. + pub fn from_stream(span: Span, data: &syn::Data, input: ParseStream) -> Result<(Span, Self)> { + match Meta::parse(input) { + Ok(meta) => { + let trait_ = Trait::from_path(meta.path())?; + + if let syn::Data::Union(_) = data { + // Make sure this `Trait` supports unions. + if !trait_.supports_union() { + return Err(Error::union(span)); + } + } + + match &meta { + Meta::Path(path) => Ok((path.span(), trait_.default_derive_trait())), + 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)?)) + } + Meta::NameValue(name_value) => Err(Error::option_syntax(name_value.span())), + } + } + Err(error) => Err(Error::trait_syntax(error.span())), + } } } /// Single trait implementation. Parses attributes and constructs `impl`s. -pub trait TraitImpl { +pub trait TraitImpl: Deref { /// [`str`] representation of this [`Trait`]. /// Used to compare against [`Ident`](struct@syn::Ident)s and create error /// messages. - fn as_str(&self) -> &'static str; + fn as_str() -> &'static str + where + Self: Sized; /// Associated [`DeriveTrait`]. - fn default_derive_trait(&self) -> DeriveTrait; + fn default_derive_trait() -> DeriveTrait + where + Self: Sized; /// Parse a `derive_where` trait with it's options. - fn parse_derive_trait( - &self, - span: Span, - _list: Punctuated, - ) -> Result { - Err(Error::options(span, self.as_str())) + fn parse_derive_trait(span: Span, _list: Punctuated) -> Result + where + Self: Sized, + { + Err(Error::options(span, Self::as_str())) } /// Returns `true` if [`Trait`] supports unions. - fn supports_union(&self) -> bool { + fn supports_union() -> bool + where + Self: Sized, + { false } - /// Additional bounds to add to [`WhereClause`](syn::WhereClause). - fn additional_where_bounds(&self, _data: &Item) -> Option { + /// Additional bounds to add to [`WhereClause`]. + fn additional_where_bounds(_data: &Item) -> Option + where + Self: Sized, + { None } + /// Returns fully qualified [`Path`] for this trait. + fn path(&self) -> Path; + /// Additional implementation to add for this [`Trait`]. - fn additional_impl(&self, _trait_: &DeriveTrait) -> Option<(Path, TokenStream)> { + fn additional_impl(&self) -> Option<(Path, TokenStream)> { None } /// Trait to implement. Only used for [`ZeroizeOnDrop`](https://docs.rs/zeroize/latest/zeroize/trait.ZeroizeOnDrop.html) /// because it implements [`Drop`] and not itself. - fn impl_path(&self, trait_: &DeriveTrait) -> Path { - trait_.path() + fn impl_item( + &self, + imp: &ImplGenerics<'_>, + ident: &Ident, + ty: &TypeGenerics<'_>, + where_clause: &Option>, + body: TokenStream, + ) -> TokenStream { + let path = self.path(); + + quote! { + #[automatically_derived] + impl #imp #path for #ident #ty + #where_clause + { + #body + } + } } /// Build method signature for this [`Trait`]. @@ -199,19 +320,13 @@ pub trait TraitImpl { _derive_where: &DeriveWhere, _item: &Item, _generics: &SplitGenerics<'_>, - _trait_: &DeriveTrait, _body: &TokenStream, ) -> TokenStream { TokenStream::new() } /// Build method body for this [`Trait`]. - fn build_body( - &self, - _derive_where: &DeriveWhere, - _trait_: &DeriveTrait, - _data: &Data, - ) -> TokenStream { + fn build_body(&self, _derive_where: &DeriveWhere, _data: &Data) -> TokenStream { TokenStream::new() } } diff --git a/src/trait_/clone.rs b/src/trait_/clone.rs index 94592b33..9b15f4c8 100644 --- a/src/trait_/clone.rs +++ b/src/trait_/clone.rs @@ -1,31 +1,33 @@ //! [`Clone`](trait@std::clone::Clone) implementation. +use std::ops::Deref; + use proc_macro2::TokenStream; use quote::quote; use syn::{TraitBound, TraitBoundModifier, TypeParamBound}; use crate::{ - data::Field, Data, DataType, DeriveTrait, DeriveWhere, Item, SimpleType, SplitGenerics, Trait, - TraitImpl, + data::Field, util, Data, DataType, DeriveTrait, DeriveWhere, Item, SimpleType, SplitGenerics, + Trait, TraitImpl, }; -/// Dummy-struct implement [`Trait`] for [`Clone`](trait@std::clone::Clone). +/// [`TraitImpl`] for [`Clone`](trait@std::clone::Clone). pub struct Clone; impl TraitImpl for Clone { - fn as_str(&self) -> &'static str { + fn as_str() -> &'static str { "Clone" } - fn default_derive_trait(&self) -> DeriveTrait { + fn default_derive_trait() -> DeriveTrait { DeriveTrait::Clone } - fn supports_union(&self) -> bool { + fn supports_union() -> bool { true } - fn additional_where_bounds(&self, data: &Item) -> Option { + fn additional_where_bounds(data: &Item) -> Option { // `Clone` for unions requires the `Copy` bound. if let Item::Item(Data { type_: DataType::Union(..), @@ -43,12 +45,15 @@ impl TraitImpl for Clone { } } + fn path(&self) -> syn::Path { + util::path_from_strs(&["core", "clone", "Clone"]) + } + fn build_signature( &self, derive_where: &DeriveWhere, item: &Item, _generics: &SplitGenerics<'_>, - _trait_: &DeriveTrait, body: &TokenStream, ) -> TokenStream { // Special implementation for items also implementing `Copy`. @@ -87,12 +92,7 @@ impl TraitImpl for Clone { } } - fn build_body( - &self, - derive_where: &DeriveWhere, - trait_: &DeriveTrait, - data: &Data, - ) -> TokenStream { + fn build_body(&self, derive_where: &DeriveWhere, data: &Data) -> TokenStream { if (derive_where.generics.is_empty() || derive_where.any_custom_bound()) && derive_where.contains(Trait::Copy) { @@ -103,7 +103,7 @@ impl TraitImpl for Clone { SimpleType::Struct(fields) | SimpleType::Tuple(fields) => { let self_pattern = &fields.self_pattern; let item_path = &data.path; - let trait_path = trait_.path(); + let trait_path = self.path(); let default_path = DeriveTrait::Default.path(); let fields = fields.fields.iter().map( @@ -129,3 +129,11 @@ impl TraitImpl for Clone { } } } + +impl Deref for Clone { + type Target = Trait; + + fn deref(&self) -> &Self::Target { + &Trait::Clone + } +} diff --git a/src/trait_/common_ord.rs b/src/trait_/common_ord.rs index 88ab11d9..9efaf209 100644 --- a/src/trait_/common_ord.rs +++ b/src/trait_/common_ord.rs @@ -13,21 +13,21 @@ use quote::quote; use syn::{parse_quote, Expr, ExprLit, LitInt, Path}; #[cfg(not(feature = "nightly"))] -use crate::{item::Representation, Discriminant, Trait}; -use crate::{Data, DeriveTrait, DeriveWhere, Item, SimpleType, SplitGenerics}; +use crate::{item::Representation, DeriveTrait, Discriminant}; +use crate::{Data, DeriveWhere, Item, SimpleType, SplitGenerics, Trait, TraitImpl}; /// Build signature for [`PartialOrd`] and [`Ord`]. pub fn build_ord_signature( item: &Item, #[cfg_attr(feature = "nightly", allow(unused_variables))] generics: &SplitGenerics<'_>, #[cfg_attr(feature = "nightly", allow(unused_variables))] derive_where: &DeriveWhere, - trait_: &DeriveTrait, + trait_: &dyn TraitImpl, body: &TokenStream, ) -> TokenStream { let mut equal = quote! { ::core::cmp::Ordering::Equal }; // Add `Option` to `Ordering` if we are implementing `PartialOrd`. - if let DeriveTrait::PartialOrd = trait_ { + if let Trait::PartialOrd = **trait_ { equal = quote! { ::core::option::Option::Some(#equal) }; } @@ -108,9 +108,9 @@ pub fn build_ord_signature( }; let path = trait_.path(); - let method = match trait_ { - DeriveTrait::PartialOrd => quote! { partial_cmp }, - DeriveTrait::Ord => quote! { cmp }, + let method = match **trait_ { + Trait::PartialOrd => quote! { partial_cmp }, + Trait::Ord => quote! { cmp }, _ => unreachable!("unsupported trait in `prepare_ord`"), }; @@ -416,17 +416,17 @@ fn build_discriminant_comparison( } /// Build `match` arms for [`PartialOrd`] and [`Ord`]. -pub fn build_ord_body(trait_: &DeriveTrait, data: &Data) -> TokenStream { +pub fn build_ord_body(trait_: &dyn TraitImpl, data: &Data) -> TokenStream { let path = trait_.path(); let mut equal = quote! { ::core::cmp::Ordering::Equal }; // Add `Option` to `Ordering` if we are implementing `PartialOrd`. - let method = match trait_ { - DeriveTrait::PartialOrd => { + let method = match **trait_ { + Trait::PartialOrd => { equal = quote! { ::core::option::Option::Some(#equal) }; quote! { partial_cmp } } - DeriveTrait::Ord => quote! { cmp }, + Trait::Ord => quote! { cmp }, _ => unreachable!("unsupported trait in `build_ord`"), }; diff --git a/src/trait_/copy.rs b/src/trait_/copy.rs index c3958733..1c25b73f 100644 --- a/src/trait_/copy.rs +++ b/src/trait_/copy.rs @@ -1,21 +1,34 @@ //! [`Copy`](trait@std::marker::Copy) implementation. -use crate::{DeriveTrait, TraitImpl}; +use std::ops::Deref; -/// Dummy-struct implement [`Trait`](crate::Trait) for -/// [`Copy`](trait@std::marker::Copy). +use crate::{util, DeriveTrait, Trait, TraitImpl}; + +/// [`TraitImpl`] for [`Copy`](trait@std::marker::Copy). pub struct Copy; impl TraitImpl for Copy { - fn as_str(&self) -> &'static str { + fn as_str() -> &'static str { "Copy" } - fn default_derive_trait(&self) -> DeriveTrait { + fn default_derive_trait() -> DeriveTrait { DeriveTrait::Copy } - fn supports_union(&self) -> bool { + fn supports_union() -> bool { true } + + fn path(&self) -> syn::Path { + util::path_from_strs(&["core", "marker", "Copy"]) + } +} + +impl Deref for Copy { + type Target = Trait; + + fn deref(&self) -> &Self::Target { + &Trait::Copy + } } diff --git a/src/trait_/debug.rs b/src/trait_/debug.rs index 084637ad..67d3feb7 100644 --- a/src/trait_/debug.rs +++ b/src/trait_/debug.rs @@ -1,29 +1,35 @@ //! [`Debug`](trait@std::fmt::Debug) implementation. +use std::ops::Deref; + use proc_macro2::TokenStream; use quote::quote; -use crate::{Data, DeriveTrait, DeriveWhere, Item, SimpleType, SplitGenerics, TraitImpl}; +use crate::{ + util, Data, DeriveTrait, DeriveWhere, Item, SimpleType, SplitGenerics, Trait, TraitImpl, +}; -/// Dummy-struct implement [`Trait`](crate::Trait) for -/// [`Debug`](trait@std::fmt::Debug). +/// [`TraitImpl`] for [`Debug`](trait@std::fmt::Debug). pub struct Debug; impl TraitImpl for Debug { - fn as_str(&self) -> &'static str { + fn as_str() -> &'static str { "Debug" } - fn default_derive_trait(&self) -> DeriveTrait { + fn default_derive_trait() -> DeriveTrait { DeriveTrait::Debug } + fn path(&self) -> syn::Path { + util::path_from_strs(&["core", "fmt", "Debug"]) + } + fn build_signature( &self, _derive_where: &DeriveWhere, _item: &Item, _generics: &SplitGenerics<'_>, - _trait_: &DeriveTrait, body: &TokenStream, ) -> TokenStream { quote! { @@ -35,23 +41,16 @@ impl TraitImpl for Debug { } } - fn build_body( - &self, - _derive_where: &DeriveWhere, - trait_: &DeriveTrait, - data: &Data, - ) -> TokenStream { + fn build_body(&self, _derive_where: &DeriveWhere, data: &Data) -> TokenStream { let self_pattern = &data.self_pattern(); let debug_name = data.ident.to_string(); match data.simple_type() { SimpleType::Struct(_) => { - let self_ident = data.iter_self_ident(**trait_); - let debug_fields = data - .iter_field_ident(**trait_) - .map(|field| field.to_string()); + let self_ident = data.iter_self_ident(**self); + let debug_fields = data.iter_field_ident(**self).map(|field| field.to_string()); - let finish = if data.any_skip_trait(**trait_) { + let finish = if data.any_skip_trait(**self) { quote! { finish_non_exhaustive } } else { quote! { finish } @@ -66,7 +65,7 @@ impl TraitImpl for Debug { } } SimpleType::Tuple(_) => { - let self_ident = data.iter_self_ident(**trait_); + let self_ident = data.iter_self_ident(**self); quote! { #self_pattern => { @@ -83,3 +82,11 @@ impl TraitImpl for Debug { } } } + +impl Deref for Debug { + type Target = Trait; + + fn deref(&self) -> &Self::Target { + &Trait::Debug + } +} diff --git a/src/trait_/default.rs b/src/trait_/default.rs index af256173..93f1ef68 100644 --- a/src/trait_/default.rs +++ b/src/trait_/default.rs @@ -1,29 +1,35 @@ //! [`Default`](trait@std::default::Default) implementation. +use std::ops::Deref; + use proc_macro2::TokenStream; use quote::quote; -use crate::{Data, DeriveTrait, DeriveWhere, Item, SimpleType, SplitGenerics, TraitImpl}; +use crate::{ + util, Data, DeriveTrait, DeriveWhere, Item, SimpleType, SplitGenerics, Trait, TraitImpl, +}; -/// Dummy-struct implement [`Trait`](crate::Trait) for -/// [`Default`](trait@std::default::Default). +/// [`TraitImpl`] for [`Default`](trait@std::default::Default). pub struct Default; impl TraitImpl for Default { - fn as_str(&self) -> &'static str { + fn as_str() -> &'static str { "Default" } - fn default_derive_trait(&self) -> DeriveTrait { + fn default_derive_trait() -> DeriveTrait { DeriveTrait::Default } + fn path(&self) -> syn::Path { + util::path_from_strs(&["core", "default", "Default"]) + } + fn build_signature( &self, _derive_where: &DeriveWhere, _item: &Item, _generics: &SplitGenerics<'_>, - _trait_: &DeriveTrait, body: &TokenStream, ) -> TokenStream { quote! { @@ -33,26 +39,21 @@ impl TraitImpl for Default { } } - fn build_body( - &self, - _derive_where: &DeriveWhere, - trait_: &DeriveTrait, - data: &Data, - ) -> TokenStream { + fn build_body(&self, _derive_where: &DeriveWhere, data: &Data) -> TokenStream { if data.is_default() { let path = &data.path; match data.simple_type() { SimpleType::Struct(_) => { - let fields = data.iter_field_ident(**trait_); - let trait_path = trait_.path(); + let fields = data.iter_field_ident(**self); + let trait_path = self.path(); quote! { #path { #(#fields: #trait_path::default()),* } } } SimpleType::Tuple(_) => { - let trait_path = trait_.path(); + let trait_path = self.path(); let fields = data - .iter_fields(**trait_) + .iter_fields(**self) .map(|_| quote! { #trait_path::default() }); quote! { #path(#(#fields),*) } @@ -69,3 +70,11 @@ impl TraitImpl for Default { } } } + +impl Deref for Default { + type Target = Trait; + + fn deref(&self) -> &Self::Target { + &Trait::Default + } +} diff --git a/src/trait_/eq.rs b/src/trait_/eq.rs index bd17e4dc..ecd25433 100644 --- a/src/trait_/eq.rs +++ b/src/trait_/eq.rs @@ -1,29 +1,33 @@ //! [`Eq`](trait@std::cmp::Eq) implementation. +use std::ops::Deref; + use proc_macro2::TokenStream; use quote::quote; -use crate::{Data, DeriveTrait, DeriveWhere, Item, SplitGenerics, TraitImpl}; +use crate::{util, Data, DeriveTrait, DeriveWhere, Item, SplitGenerics, Trait, TraitImpl}; -/// Dummy-struct implement [`Trait`](crate::Trait) for -/// [`Eq`](trait@std::cmp::Eq). +/// [`TraitImpl`] for [`Eq`](trait@std::cmp::Eq). pub struct Eq; impl TraitImpl for Eq { - fn as_str(&self) -> &'static str { + fn as_str() -> &'static str { "Eq" } - fn default_derive_trait(&self) -> DeriveTrait { + fn default_derive_trait() -> DeriveTrait { DeriveTrait::Eq } + fn path(&self) -> syn::Path { + util::path_from_strs(&["core", "cmp", "Eq"]) + } + fn build_signature( &self, _derive_where: &DeriveWhere, _item: &Item, _generics: &SplitGenerics<'_>, - _trait_: &DeriveTrait, body: &TokenStream, ) -> TokenStream { quote! { @@ -36,16 +40,19 @@ impl TraitImpl for Eq { } } - fn build_body( - &self, - _derive_where: &DeriveWhere, - trait_: &DeriveTrait, - data: &Data, - ) -> TokenStream { - let types = data.iter_fields(**trait_).map(|field| field.type_); + fn build_body(&self, _derive_where: &DeriveWhere, data: &Data) -> TokenStream { + let types = data.iter_fields(**self).map(|field| field.type_); quote! { #(let _: __AssertEq<#types>;)* } } } + +impl Deref for Eq { + type Target = Trait; + + fn deref(&self) -> &Self::Target { + &Trait::Eq + } +} diff --git a/src/trait_/hash.rs b/src/trait_/hash.rs index 5c130cfb..7a6002c5 100644 --- a/src/trait_/hash.rs +++ b/src/trait_/hash.rs @@ -1,29 +1,36 @@ //! [`Hash`](trait@std::hash::Hash) implementation. +use std::ops::Deref; + use proc_macro2::TokenStream; use quote::quote; -use crate::{Data, DataType, DeriveTrait, DeriveWhere, Item, SimpleType, SplitGenerics, TraitImpl}; +use crate::{ + util, Data, DataType, DeriveTrait, DeriveWhere, Item, SimpleType, SplitGenerics, Trait, + TraitImpl, +}; -/// Dummy-struct implement [`Trait`](crate::Trait) for -/// [`Hash`](trait@std::hash::Hash). +/// [`TraitImpl`] for [`Hash`](trait@std::hash::Hash). pub struct Hash; impl TraitImpl for Hash { - fn as_str(&self) -> &'static str { + fn as_str() -> &'static str { "Hash" } - fn default_derive_trait(&self) -> DeriveTrait { + fn default_derive_trait() -> DeriveTrait { DeriveTrait::Hash } + fn path(&self) -> syn::Path { + util::path_from_strs(&["core", "hash", "Hash"]) + } + fn build_signature( &self, _derive_where: &DeriveWhere, _item: &Item, _generics: &SplitGenerics<'_>, - _trait_: &DeriveTrait, body: &TokenStream, ) -> TokenStream { quote! { @@ -35,14 +42,9 @@ impl TraitImpl for Hash { } } - fn build_body( - &self, - _derive_where: &DeriveWhere, - trait_: &DeriveTrait, - data: &Data, - ) -> TokenStream { + fn build_body(&self, _derive_where: &DeriveWhere, data: &Data) -> TokenStream { let self_pattern = data.self_pattern(); - let trait_path = trait_.path(); + let trait_path = self.path(); // Add hashing the variant if this is an enum. let discriminant = if let DataType::Variant { .. } = data.type_ { @@ -53,7 +55,7 @@ impl TraitImpl for Hash { match data.simple_type() { SimpleType::Struct(_) | SimpleType::Tuple(_) => { - let self_ident = data.iter_self_ident(**trait_); + let self_ident = data.iter_self_ident(**self); quote! { #self_pattern => { @@ -73,3 +75,11 @@ impl TraitImpl for Hash { } } } + +impl Deref for Hash { + type Target = Trait; + + fn deref(&self) -> &Self::Target { + &Trait::Hash + } +} diff --git a/src/trait_/ord.rs b/src/trait_/ord.rs index 17ae516c..3ebb6111 100644 --- a/src/trait_/ord.rs +++ b/src/trait_/ord.rs @@ -1,33 +1,39 @@ //! [`Ord`](trait@std::cmp::Ord) implementation. +use std::ops::Deref; + use proc_macro2::TokenStream; use quote::quote; use super::common_ord; -use crate::{Data, DeriveTrait, DeriveWhere, Item, SimpleType, SplitGenerics, TraitImpl}; +use crate::{ + util, Data, DeriveTrait, DeriveWhere, Item, SimpleType, SplitGenerics, Trait, TraitImpl, +}; -/// Dummy-struct implement [`Trait`](crate::Trait) for -/// [`Ord`](trait@std::cmp::Ord). +/// [`TraitImpl`] for [`Ord`](trait@std::cmp::Ord). pub struct Ord; impl TraitImpl for Ord { - fn as_str(&self) -> &'static str { + fn as_str() -> &'static str { "Ord" } - fn default_derive_trait(&self) -> DeriveTrait { + fn default_derive_trait() -> DeriveTrait { DeriveTrait::Ord } + fn path(&self) -> syn::Path { + util::path_from_strs(&["core", "cmp", "Ord"]) + } + fn build_signature( &self, derive_where: &DeriveWhere, item: &Item, generics: &SplitGenerics<'_>, - trait_: &DeriveTrait, body: &TokenStream, ) -> TokenStream { - let body = common_ord::build_ord_signature(item, generics, derive_where, trait_, body); + let body = common_ord::build_ord_signature(item, generics, derive_where, self, body); quote! { #[inline] @@ -37,20 +43,15 @@ impl TraitImpl for Ord { } } - fn build_body( - &self, - _derive_where: &DeriveWhere, - trait_: &DeriveTrait, - data: &Data, - ) -> TokenStream { - if data.is_empty(**trait_) { + fn build_body(&self, _derive_where: &DeriveWhere, data: &Data) -> TokenStream { + if data.is_empty(**self) { TokenStream::new() } else { match data.simple_type() { SimpleType::Struct(fields) | SimpleType::Tuple(fields) => { let self_pattern = &fields.self_pattern; let other_pattern = &fields.other_pattern; - let body = common_ord::build_ord_body(trait_, data); + let body = common_ord::build_ord_body(self, data); quote! { (#self_pattern, #other_pattern) => #body, @@ -62,3 +63,11 @@ impl TraitImpl for Ord { } } } + +impl Deref for Ord { + type Target = Trait; + + fn deref(&self) -> &Self::Target { + &Trait::Ord + } +} diff --git a/src/trait_/partial_eq.rs b/src/trait_/partial_eq.rs index b8917ad2..241d2303 100644 --- a/src/trait_/partial_eq.rs +++ b/src/trait_/partial_eq.rs @@ -1,30 +1,36 @@ //! [`PartialEq`](trait@std::cmp::PartialEq) implementation. +use std::ops::Deref; + use proc_macro2::TokenStream; use quote::quote; use super::common_ord::build_incomparable_pattern; -use crate::{Data, DeriveTrait, DeriveWhere, Item, SimpleType, SplitGenerics, TraitImpl}; +use crate::{ + util, Data, DeriveTrait, DeriveWhere, Item, SimpleType, SplitGenerics, Trait, TraitImpl, +}; -/// Dummy-struct implement [`Trait`](crate::Trait) for -/// [`PartialEq`](trait@std::cmp::PartialEq). +/// [`TraitImpl`] for [`PartialEq`](trait@std::cmp::PartialEq). pub struct PartialEq; impl TraitImpl for PartialEq { - fn as_str(&self) -> &'static str { + fn as_str() -> &'static str { "PartialEq" } - fn default_derive_trait(&self) -> DeriveTrait { + fn default_derive_trait() -> DeriveTrait { DeriveTrait::PartialEq } + fn path(&self) -> syn::Path { + util::path_from_strs(&["core", "cmp", "PartialEq"]) + } + fn build_signature( &self, _derive_where: &DeriveWhere, item: &Item, _generics: &SplitGenerics<'_>, - trait_: &DeriveTrait, body: &TokenStream, ) -> TokenStream { let body = { @@ -35,12 +41,12 @@ impl TraitImpl for PartialEq { } // If there is more than one variant and not all variants are empty, check for // discriminant and match on variant data. - Item::Enum { variants, .. } if variants.len() > 1 && !item.is_empty(**trait_) => { + Item::Enum { variants, .. } if variants.len() > 1 && !item.is_empty(**self) => { // Return `true` in the rest pattern if there are any empty variants // that are not incomparable. let rest = if variants .iter() - .any(|variant| variant.is_empty(**trait_) && !variant.is_incomparable()) + .any(|variant| variant.is_empty(**self) && !variant.is_incomparable()) { quote! { true } } else { @@ -68,7 +74,7 @@ impl TraitImpl for PartialEq { } // If there is more than one variant and all are empty, check for // discriminant and simply return `true` if it is not incomparable. - Item::Enum { variants, .. } if variants.len() > 1 && item.is_empty(**trait_) => { + Item::Enum { variants, .. } if variants.len() > 1 && item.is_empty(**self) => { let incomparable = build_incomparable_pattern(variants).into_iter(); quote! { if ::core::mem::discriminant(self) == ::core::mem::discriminant(__other) { @@ -83,7 +89,7 @@ impl TraitImpl for PartialEq { } // If there is only one variant and it's empty or if the struct is empty, simply // return `true`. - item if item.is_empty(**trait_) => { + item if item.is_empty(**self) => { quote! { true } } _ => { @@ -104,22 +110,17 @@ impl TraitImpl for PartialEq { } } - fn build_body( - &self, - _derive_where: &DeriveWhere, - trait_: &DeriveTrait, - data: &Data, - ) -> TokenStream { - if data.is_empty(**trait_) || data.is_incomparable() { + fn build_body(&self, _derive_where: &DeriveWhere, data: &Data) -> TokenStream { + if data.is_empty(**self) || data.is_incomparable() { TokenStream::new() } else { match data.simple_type() { SimpleType::Struct(fields) | SimpleType::Tuple(fields) => { let self_pattern = &fields.self_pattern; let other_pattern = &fields.other_pattern; - let trait_path = trait_.path(); - let self_ident = data.iter_self_ident(**trait_); - let other_ident = data.iter_other_ident(**trait_); + let trait_path = self.path(); + let self_ident = data.iter_self_ident(**self); + let other_ident = data.iter_other_ident(**self); quote! { (#self_pattern, #other_pattern) => @@ -132,3 +133,11 @@ impl TraitImpl for PartialEq { } } } + +impl Deref for PartialEq { + type Target = Trait; + + fn deref(&self) -> &Self::Target { + &Trait::PartialEq + } +} diff --git a/src/trait_/partial_ord.rs b/src/trait_/partial_ord.rs index 81e48492..19f4f15f 100644 --- a/src/trait_/partial_ord.rs +++ b/src/trait_/partial_ord.rs @@ -1,30 +1,36 @@ //! [`PartialOrd`](trait@std::cmp::PartialOrd) implementation. +use std::ops::Deref; + use proc_macro2::TokenStream; use quote::quote; use super::common_ord; -use crate::{Data, DeriveTrait, DeriveWhere, Item, SimpleType, SplitGenerics, Trait, TraitImpl}; +use crate::{ + util, Data, DeriveTrait, DeriveWhere, Item, SimpleType, SplitGenerics, Trait, TraitImpl, +}; -/// Dummy-struct implement [`Trait`] for -/// [`PartialOrd`](trait@std::cmp::PartialOrd). +/// [`TraitImpl`] for [`PartialOrd`](trait@std::cmp::PartialOrd). pub struct PartialOrd; impl TraitImpl for PartialOrd { - fn as_str(&self) -> &'static str { + fn as_str() -> &'static str { "PartialOrd" } - fn default_derive_trait(&self) -> DeriveTrait { + fn default_derive_trait() -> DeriveTrait { DeriveTrait::PartialOrd } + fn path(&self) -> syn::Path { + util::path_from_strs(&["core", "cmp", "PartialOrd"]) + } + fn build_signature( &self, derive_where: &DeriveWhere, item: &Item, generics: &SplitGenerics<'_>, - trait_: &DeriveTrait, body: &TokenStream, ) -> TokenStream { let body = if (derive_where.generics.is_empty() || derive_where.any_custom_bound()) @@ -34,7 +40,7 @@ impl TraitImpl for PartialOrd { ::core::option::Option::Some(::core::cmp::Ord::cmp(self, __other)) } } else { - common_ord::build_ord_signature(item, generics, derive_where, trait_, body) + common_ord::build_ord_signature(item, generics, derive_where, self, body) }; quote! { @@ -45,13 +51,8 @@ impl TraitImpl for PartialOrd { } } - fn build_body( - &self, - derive_where: &DeriveWhere, - trait_: &DeriveTrait, - data: &Data, - ) -> TokenStream { - if data.is_empty(**trait_) + fn build_body(&self, derive_where: &DeriveWhere, data: &Data) -> TokenStream { + if data.is_empty(**self) || data.is_incomparable() || ((derive_where.generics.is_empty() || derive_where.any_custom_bound()) && derive_where.contains(Trait::Ord)) @@ -62,7 +63,7 @@ impl TraitImpl for PartialOrd { SimpleType::Struct(fields) | SimpleType::Tuple(fields) => { let self_pattern = &fields.self_pattern; let other_pattern = &fields.other_pattern; - let body = common_ord::build_ord_body(trait_, data); + let body = common_ord::build_ord_body(self, data); quote! { (#self_pattern, #other_pattern) => #body, @@ -74,3 +75,11 @@ impl TraitImpl for PartialOrd { } } } + +impl Deref for PartialOrd { + type Target = Trait; + + fn deref(&self) -> &Self::Target { + &Trait::PartialOrd + } +} diff --git a/src/trait_/zeroize.rs b/src/trait_/zeroize.rs index eaa63eee..1812be4c 100644 --- a/src/trait_/zeroize.rs +++ b/src/trait_/zeroize.rs @@ -1,5 +1,7 @@ //! [`Zeroize`](https://docs.rs/zeroize/latest/zeroize/trait.Zeroize.html) implementation. +use std::ops::Deref; + use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::{ @@ -8,26 +10,26 @@ use syn::{ }; use crate::{ - util, Data, DeriveTrait, DeriveWhere, Error, Item, SimpleType, SplitGenerics, TraitImpl, + util, Data, DeriveTrait, DeriveWhere, Error, Item, SimpleType, SplitGenerics, Trait, TraitImpl, }; -/// Dummy-struct implement [`Trait`](crate::Trait) for [`Zeroize`](https://docs.rs/zeroize/latest/zeroize/trait.Zeroize.html) . -pub struct Zeroize; +/// [`TraitImpl`] for [`Zeroize`](https://docs.rs/zeroize/latest/zeroize/trait.Zeroize.html). +#[derive(Eq, PartialEq)] +pub struct Zeroize { + /// [`Zeroize`](https://docs.rs/zeroize/latest/zeroize/trait.Zeroize.html) path. + pub crate_: Option, +} impl TraitImpl for Zeroize { - fn as_str(&self) -> &'static str { + fn as_str() -> &'static str { "Zeroize" } - fn default_derive_trait(&self) -> DeriveTrait { - DeriveTrait::Zeroize { crate_: None } + fn default_derive_trait() -> DeriveTrait { + DeriveTrait::Zeroize(Self { crate_: None }) } - fn parse_derive_trait( - &self, - _span: Span, - list: Punctuated, - ) -> Result { + fn parse_derive_trait(_span: Span, list: Punctuated) -> Result { // This is already checked in `DeriveTrait::from_stream`. debug_assert!(!list.is_empty()); @@ -39,7 +41,7 @@ impl TraitImpl for Zeroize { if path.is_ident("drop") { return Err(Error::deprecated_zeroize_drop(path.span())); } else { - return Err(Error::option_trait(path.span(), self.as_str())); + return Err(Error::option_trait(path.span(), Self::as_str())); } } Meta::NameValue(name_value) => { @@ -67,7 +69,7 @@ impl TraitImpl for Zeroize { return Err(Error::option_duplicate(name_value.span(), "crate")); } } else { - return Err(Error::option_trait(name_value.path.span(), self.as_str())); + return Err(Error::option_trait(name_value.path.span(), Self::as_str())); } } _ => { @@ -76,7 +78,11 @@ impl TraitImpl for Zeroize { } } - Ok(DeriveTrait::Zeroize { crate_ }) + Ok(DeriveTrait::Zeroize(Self { crate_ })) + } + + fn path(&self) -> syn::Path { + util::path_from_root_and_strs(self.crate_(), &["Zeroize"]) } fn build_signature( @@ -84,15 +90,14 @@ impl TraitImpl for Zeroize { _derive_where: &DeriveWhere, item: &Item, _generics: &SplitGenerics<'_>, - trait_: &DeriveTrait, body: &TokenStream, ) -> TokenStream { match item { - Item::Item(data) if data.is_empty(**trait_) => quote! { + Item::Item(data) if data.is_empty(**self) => quote! { fn zeroize(&mut self) { } }, _ => { - let trait_path = trait_.path(); + let trait_path = self.path(); quote! { fn zeroize(&mut self) { use #trait_path; @@ -106,20 +111,15 @@ impl TraitImpl for Zeroize { } } - fn build_body( - &self, - _derive_where: &DeriveWhere, - trait_: &DeriveTrait, - data: &Data, - ) -> TokenStream { + fn build_body(&self, _derive_where: &DeriveWhere, data: &Data) -> TokenStream { match data.simple_type() { SimpleType::Struct(fields) | SimpleType::Tuple(fields) => { - let trait_path = trait_.path(); + let trait_path = self.path(); let self_pattern = fields.self_pattern_mut(); let body = data - .iter_fields(**trait_) - .zip(data.iter_self_ident(**trait_)) + .iter_fields(**self) + .zip(data.iter_self_ident(**self)) .map(|(field, self_ident)| { if field.attr.zeroize_fqs.0 { quote! { #trait_path::zeroize(#self_ident); } @@ -139,3 +139,22 @@ impl TraitImpl for Zeroize { } } } + +impl Zeroize { + /// 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(&["zeroize"]) + } + } +} + +impl Deref for Zeroize { + type Target = Trait; + + fn deref(&self) -> &Self::Target { + &Trait::Zeroize + } +} diff --git a/src/trait_/zeroize_on_drop.rs b/src/trait_/zeroize_on_drop.rs index 64650f2f..cfb56351 100644 --- a/src/trait_/zeroize_on_drop.rs +++ b/src/trait_/zeroize_on_drop.rs @@ -1,41 +1,64 @@ //! [`ZeroizeOnDrop`](https://docs.rs/zeroize/latest/zeroize/trait.ZeroizeOnDrop.html) implementation. +use std::{borrow::Cow, iter, 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, Expr, ExprLit, ExprPath, Ident, ImplGenerics, Lit, + Meta, Path, Result, Token, TypeGenerics, WhereClause, }; -use crate::{util, DeriveTrait, DeriveWhere, Error, Item, SplitGenerics, TraitImpl}; +use crate::{util, DeriveTrait, DeriveWhere, Error, Item, SplitGenerics, Trait, TraitImpl}; #[cfg(feature = "zeroize-on-drop")] use crate::{Data, SimpleType}; -/// Dummy-struct implement [`Trait`](crate::Trait) for [`ZeroizeOnDrop`](https://docs.rs/zeroize/latest/zeroize/trait.ZeroizeOnDrop.html) . -pub struct ZeroizeOnDrop; +/// [`TraitImpl`] for [`ZeroizeOnDrop`](https://docs.rs/zeroize/latest/zeroize/trait.ZeroizeOnDrop.html). +#[derive(Eq, PartialEq)] +pub struct ZeroizeOnDrop { + /// [`ZeroizeOnDrop`](https://docs.rs/zeroize/latest/zeroize/trait.ZeroizeOnDrop.html) path. + pub crate_: Option, + /// If `Drop` should be implemented. + pub no_drop: bool, +} impl TraitImpl for ZeroizeOnDrop { - fn as_str(&self) -> &'static str { + fn as_str() -> &'static str { "ZeroizeOnDrop" } - fn default_derive_trait(&self) -> DeriveTrait { - DeriveTrait::ZeroizeOnDrop { crate_: None } + fn default_derive_trait() -> DeriveTrait { + DeriveTrait::ZeroizeOnDrop(Self { + crate_: None, + no_drop: false, + }) } - fn parse_derive_trait( - &self, - _span: Span, - list: Punctuated, - ) -> Result { + fn parse_derive_trait(_span: Span, list: Punctuated) -> Result { // This is already checked in `DeriveTrait::from_stream`. debug_assert!(!list.is_empty()); let mut crate_ = None; + #[cfg_attr(not(feature = "zeroize-on-drop"), allow(unused_mut))] + let mut no_drop = false; for meta in list { match &meta { - Meta::Path(path) => return Err(Error::option_trait(path.span(), self.as_str())), + Meta::Path(path) => { + #[cfg(feature = "zeroize-on-drop")] + if path.is_ident("no_drop") { + // Check for duplicate `no_drop` option. + if !no_drop { + no_drop = true; + } else { + return Err(Error::option_duplicate(path.span(), "no_drop")); + } + + continue; + } + + return Err(Error::option_trait(path.span(), Self::as_str())); + } Meta::NameValue(name_value) => { if name_value.path.is_ident("crate") { // Check for duplicate `crate` option. @@ -61,7 +84,7 @@ impl TraitImpl for ZeroizeOnDrop { return Err(Error::option_duplicate(name_value.span(), "crate")); } } else { - return Err(Error::option_trait(name_value.path.span(), self.as_str())); + return Err(Error::option_trait(name_value.path.span(), Self::as_str())); } } _ => { @@ -70,19 +93,63 @@ impl TraitImpl for ZeroizeOnDrop { } } - Ok(DeriveTrait::ZeroizeOnDrop { crate_ }) + Ok(DeriveTrait::ZeroizeOnDrop(Self { crate_, no_drop })) + } + + fn path(&self) -> syn::Path { + util::path_from_root_and_strs(self.crate_(), &["ZeroizeOnDrop"]) } - #[allow(unused_variables)] - fn additional_impl(&self, trait_: &DeriveTrait) -> Option<(Path, TokenStream)> { + fn additional_impl(&self) -> Option<(Path, TokenStream)> { #[cfg(feature = "zeroize-on-drop")] - return Some((trait_.path(), quote! {})); + return Some((self.path(), quote! {})); #[cfg(not(feature = "zeroize-on-drop"))] None } - fn impl_path(&self, _trait_: &DeriveTrait) -> Path { - util::path_from_strs(&["core", "ops", "Drop"]) + fn impl_item( + &self, + imp: &ImplGenerics<'_>, + ident: &Ident, + ty: &TypeGenerics<'_>, + where_clause: &Option>, + body: TokenStream, + ) -> TokenStream { + let path = if self.no_drop { + Path { + leading_colon: None, + segments: Punctuated::from_iter(iter::once(util::path_segment( + "DeriveWhereAssertZeroizeOnDrop", + ))), + } + } else { + util::path_from_strs(&["core", "ops", "Drop"]) + }; + + let imp = quote! { + impl #imp #path for #ident #ty + #where_clause + { + #body + } + }; + + if self.no_drop { + quote! { + const _: () = { + trait DeriveWhereAssertZeroizeOnDrop { + fn assert(&mut self); + } + + #imp + }; + } + } else { + quote! { + #[automatically_derived] + #imp + } + } } fn build_signature( @@ -90,35 +157,54 @@ impl TraitImpl for ZeroizeOnDrop { _derive_where: &DeriveWhere, item: &Item, _generics: &SplitGenerics<'_>, - trait_: &DeriveTrait, body: &TokenStream, ) -> TokenStream { match item { - Item::Item(data) if data.is_empty(**trait_) => quote! { + Item::Item(data) if data.is_empty(**self) => quote! { fn drop(&mut self) { } }, #[cfg(feature = "zeroize-on-drop")] _ => { - let crate_ = trait_.crate_(); - let internal = util::path_segment("__internal"); + if self.no_drop { + let zeroize_on_drop = self.path(); - let mut assert_zeroize = crate_.clone(); - assert_zeroize - .segments - .extend([internal.clone(), util::path_segment("AssertZeroize")]); + quote! { + fn assert(&mut self) { + trait AssertZeroizeOnDrop { + fn __derive_where_zeroize_on_drop(&mut self); + } - let mut assert_zeroize_on_drop = crate_; - assert_zeroize_on_drop - .segments - .extend([internal, util::path_segment("AssertZeroizeOnDrop")]); + impl AssertZeroizeOnDrop for T { + fn __derive_where_zeroize_on_drop(&mut self) {} + } - quote! { - fn drop(&mut self) { - use #assert_zeroize; - use #assert_zeroize_on_drop; + match self { + #body + } + } + } + } else { + let crate_ = self.crate_(); + let internal = util::path_segment("__internal"); + + let mut assert_zeroize = crate_.clone(); + assert_zeroize + .segments + .extend([internal.clone(), util::path_segment("AssertZeroize")]); - match self { - #body + let mut assert_zeroize_on_drop = crate_; + assert_zeroize_on_drop + .segments + .extend([internal, util::path_segment("AssertZeroizeOnDrop")]); + + quote! { + fn drop(&mut self) { + use #assert_zeroize; + use #assert_zeroize_on_drop; + + match self { + #body + } } } } @@ -128,7 +214,7 @@ impl TraitImpl for ZeroizeOnDrop { // Use unused variables. let _ = body; - let path = util::path_from_root_and_strs(trait_.crate_(), &["Zeroize"]); + let path = util::path_from_root_and_strs(self.crate_(), &["Zeroize"]); quote! { fn drop(&mut self) { @@ -140,35 +226,36 @@ impl TraitImpl for ZeroizeOnDrop { } #[cfg(feature = "zeroize-on-drop")] - fn build_body( - &self, - _derive_where: &DeriveWhere, - trait_: &DeriveTrait, - data: &Data, - ) -> TokenStream { + fn build_body(&self, _derive_where: &DeriveWhere, data: &Data) -> TokenStream { match data.simple_type() { + #[cfg(feature = "zeroize-on-drop")] SimpleType::Struct(fields) | SimpleType::Tuple(fields) => { - #[cfg(feature = "zeroize-on-drop")] - { - let self_pattern = fields.self_pattern_mut(); - let self_ident = data.iter_self_ident(**trait_); + let self_pattern = fields.self_pattern_mut(); + let self_ident = data.iter_self_ident(**self); + if self.no_drop { + quote! { + #self_pattern => { + #(#self_ident.__derive_where_zeroize_on_drop();)* + } + } + } else { quote! { #self_pattern => { #(#self_ident.zeroize_or_on_drop();)* } } } - #[cfg(not(feature = "zeroize-on-drop"))] - { - // Use unused variables. - let _ = fields; + } + #[cfg(not(feature = "zeroize-on-drop"))] + SimpleType::Struct(fields) | SimpleType::Tuple(fields) => { + // Use unused variables. + let _ = fields; - let path = util::path_from_root_and_strs(trait_.crate_(), &["Zeroize"]); + let path = util::path_from_root_and_strs(trait_.crate_(), &["Zeroize"]); - quote! { - #path::zeroize(self); - } + quote! { + #path::zeroize(self); } } SimpleType::Unit(_) => TokenStream::new(), @@ -176,3 +263,22 @@ impl TraitImpl for ZeroizeOnDrop { } } } + +impl ZeroizeOnDrop { + /// 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(&["zeroize"]) + } + } +} + +impl Deref for ZeroizeOnDrop { + type Target = Trait; + + fn deref(&self) -> &Self::Target { + &Trait::ZeroizeOnDrop + } +} diff --git a/tests/ui.rs b/tests/ui.rs index 6211c954..6467f8e3 100644 --- a/tests/ui.rs +++ b/tests/ui.rs @@ -6,8 +6,12 @@ fn ui() { use trybuild::TestCases; TestCases::new().compile_fail("tests/ui/*.rs"); - #[cfg(feature = "zeroize")] - TestCases::new().compile_fail("tests/ui/zeroize/*.rs"); #[cfg(not(feature = "zeroize"))] TestCases::new().compile_fail("tests/ui/not-zeroize/*.rs"); + #[cfg(feature = "zeroize")] + TestCases::new().compile_fail("tests/ui/zeroize/*.rs"); + #[cfg(all(feature = "zeroize", not(feature = "zeroize-on-drop")))] + TestCases::new().compile_fail("tests/ui/not-zeroize-on-drop/*.rs"); + #[cfg(feature = "zeroize-on-drop")] + TestCases::new().compile_fail("tests/ui/zeroize-on-drop/*.rs"); } diff --git a/tests/ui/not-zeroize-on-drop/zeroize-on-drop.rs b/tests/ui/not-zeroize-on-drop/zeroize-on-drop.rs new file mode 100644 index 00000000..b803b57c --- /dev/null +++ b/tests/ui/not-zeroize-on-drop/zeroize-on-drop.rs @@ -0,0 +1,10 @@ +extern crate zeroize_ as zeroize; + +use std::marker::PhantomData; + +use derive_where::derive_where; + +#[derive_where(ZeroizeOnDrop(no_drop))] +struct InvalidOption(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 new file mode 100644 index 00000000..d4dbae0f --- /dev/null +++ b/tests/ui/not-zeroize-on-drop/zeroize-on-drop.stderr @@ -0,0 +1,5 @@ +error: `ZeroizeOnDrop` doesn't support this option + --> tests/ui/not-zeroize-on-drop/zeroize-on-drop.rs:7:30 + | +7 | #[derive_where(ZeroizeOnDrop(no_drop))] + | ^^^^^^^ diff --git a/tests/ui/zeroize-on-drop/zeroize-on-drop.rs b/tests/ui/zeroize-on-drop/zeroize-on-drop.rs new file mode 100644 index 00000000..6da1154e --- /dev/null +++ b/tests/ui/zeroize-on-drop/zeroize-on-drop.rs @@ -0,0 +1,13 @@ +extern crate zeroize_ as zeroize; + +use std::marker::PhantomData; + +use derive_where::derive_where; + +#[derive_where(ZeroizeOnDrop(no_drop, no_drop))] +struct DuplicateNoDrop(PhantomData); + +#[derive_where(ZeroizeOnDrop(no_drop))] +struct NoDropNoZeroizeOnDrop(T); + +fn main() {} diff --git a/tests/ui/zeroize-on-drop/zeroize-on-drop.stderr b/tests/ui/zeroize-on-drop/zeroize-on-drop.stderr new file mode 100644 index 00000000..989b5a3a --- /dev/null +++ b/tests/ui/zeroize-on-drop/zeroize-on-drop.stderr @@ -0,0 +1,25 @@ +error: duplicate `no_drop` option + --> tests/ui/zeroize-on-drop/zeroize-on-drop.rs:7:39 + | +7 | #[derive_where(ZeroizeOnDrop(no_drop, no_drop))] + | ^^^^^^^ + +error[E0599]: the method `__derive_where_zeroize_on_drop` exists for mutable reference `&mut T`, but its trait bounds were not satisfied + --> tests/ui/zeroize-on-drop/zeroize-on-drop.rs:10:1 + | +10 | #[derive_where(ZeroizeOnDrop(no_drop))] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ method cannot be called on `&mut T` due to unsatisfied trait bounds + | +note: the following trait bounds were not satisfied: + `&mut T: ZeroizeOnDrop` + `T: ZeroizeOnDrop` + --> tests/ui/zeroize-on-drop/zeroize-on-drop.rs:10:1 + | +10 | #[derive_where(ZeroizeOnDrop(no_drop))] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unsatisfied trait bound introduced in this `derive` macro + = help: items from traits can only be used if the type parameter is bounded by the trait + = note: this error originates in the derive macro `::derive_where::DeriveWhere` which comes from the expansion of the attribute macro `derive_where` (in Nightly builds, run with -Z macro-backtrace for more info) +help: the following trait defines an item `__derive_where_zeroize_on_drop`, perhaps you need to restrict type parameter `T` with it: + | +11 | struct NoDropNoZeroizeOnDrop as DeriveWhereAssertZeroizeOnDrop>::assert::AssertZeroizeOnDrop>(T); + | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ diff --git a/tests/zeroize.rs b/tests/zeroize.rs index 55bde0e8..16882dd6 100644 --- a/tests/zeroize.rs +++ b/tests/zeroize.rs @@ -140,6 +140,26 @@ fn deref() { }) } +#[test] +#[cfg(feature = "zeroize-on-drop")] +fn no_drop() { + use zeroize::Zeroizing; + + #[derive_where(ZeroizeOnDrop(no_drop))] + struct Test(Zeroizing); + + // Test that `Drop` isn't implemented by `derive_where`. + impl Drop for Test { + fn drop(&mut self) {} + } + + let mut test = Test(42.into()); + + let _ = AssertZeroizeOnDrop(&test); + + util::test_drop(Test(42.into()), |test| assert_eq!(*test.0, 0)) +} + mod hygiene { use derive_where::derive_where;