From a1163af236f4137d838ddfcc4d46c882d3a259dc Mon Sep 17 00:00:00 2001 From: Roland Fredenhagen Date: Thu, 30 Jan 2025 22:53:49 +0100 Subject: [PATCH 1/4] skip(Clone) --- CHANGELOG.md | 6 +++ src/attr/item.rs | 2 +- src/attr/skip.rs | 25 +++++++++++-- src/data.rs | 2 +- src/error.rs | 6 +++ src/test/bound.rs | 16 ++++---- src/test/clone.rs | 54 +++++++++++++++++++++++++-- src/trait_/clone.rs | 30 ++++++++------- tests/skip/field_trait.rs | 15 +++++++- tests/skip/struct_trait.rs | 23 ++++++++++-- tests/skip/variant_trait.rs | 27 ++++++++++++-- tests/ui/not-zeroize/item_skip.rs | 4 +- tests/ui/not-zeroize/item_skip.stderr | 6 +-- tests/ui/not-zeroize/skip.rs | 4 +- tests/ui/not-zeroize/skip.stderr | 6 +-- tests/ui/skip.rs | 3 ++ tests/ui/skip.stderr | 6 +++ tests/ui/zeroize/item_skip.rs | 4 +- tests/ui/zeroize/item_skip.stderr | 6 +-- tests/ui/zeroize/skip.rs | 4 +- tests/ui/zeroize/skip.stderr | 8 ++-- 21 files changed, 199 insertions(+), 58 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87ac7e67..1075c81b 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 +- Allow skipping fields for `Clone`, calling `Default::default()` instead. + **Note:** `Clone` is excluded from `#[derive_where(skip)]` to avoid this being a breaking change. + ## [1.3.0] - 2025-04-21 ### Added diff --git a/src/attr/item.rs b/src/attr/item.rs index c021be09..215b23a3 100644 --- a/src/attr/item.rs +++ b/src/attr/item.rs @@ -259,7 +259,7 @@ impl DeriveWhere { pub fn any_skip(&self) -> bool { self.traits .iter() - .any(|trait_| SkipGroup::trait_supported(**trait_)) + .any(|trait_| SkipGroup::trait_supported_by_skip_all(**trait_)) } /// Create [`WhereClause`] for the given parameters. diff --git a/src/attr/skip.rs b/src/attr/skip.rs index cd8fc9e3..1a56168a 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::{util::MetaListExt, DeriveWhere, Error, Trait}; +use crate::{attr::DeriveTrait, util::MetaListExt, DeriveWhere, Error, Trait}; /// Stores what [`Trait`]s to skip this field or variant for. #[cfg_attr(test, derive(Debug))] @@ -101,6 +101,18 @@ impl Skip { if let Meta::Path(path) = nested_meta { let skip_group = SkipGroup::from_path(path)?; + if skip_group == SkipGroup::Clone + && derive_wheres.iter().any(|derive_where| { + derive_where + .traits + .iter() + .any(|trait_| trait_ == &DeriveTrait::Copy) + }) { + return Err(Error::unable_to_skip_clone_while_deriving_copy( + path.span(), + )); + } + // Don't allow to skip the same trait twice. if traits.contains(&skip_group) { return Err(Error::option_skip_duplicate( @@ -144,7 +156,7 @@ impl Skip { pub fn trait_skipped(&self, trait_: Trait) -> bool { match self { Skip::None => false, - Skip::All => SkipGroup::trait_supported(trait_), + Skip::All => SkipGroup::trait_supported_by_skip_all(trait_), Skip::Traits(skip_groups) => skip_groups .iter() .any(|skip_group| skip_group.traits().any(|this_trait| this_trait == trait_)), @@ -166,6 +178,8 @@ impl Skip { #[derive(Clone, Copy, Eq, PartialEq)] #[cfg_attr(test, derive(Debug))] pub enum SkipGroup { + /// [`Clone`]. + Clone, /// [`Debug`]. Debug, /// [`Eq`], [`Hash`], [`Ord`], [`PartialEq`] and [`PartialOrd`]. @@ -185,6 +199,7 @@ impl SkipGroup { use SkipGroup::*; match ident.to_string().as_str() { + "Clone" => Ok(Clone), "Debug" => Ok(Debug), "EqHashOrd" => Ok(EqHashOrd), "Hash" => Ok(Hash), @@ -202,6 +217,7 @@ impl SkipGroup { /// messages. const fn as_str(self) -> &'static str { match self { + Self::Clone => "Clone", Self::Debug => "Debug", Self::EqHashOrd => "EqHashOrd", Self::Hash => "Hash", @@ -213,6 +229,9 @@ impl SkipGroup { /// [`Trait`]s supported by this group. fn traits(self) -> impl Iterator { match self { + Self::Clone => [Some(Trait::Clone), None, None, None, None] + .into_iter() + .flatten(), Self::Debug => [Some(Trait::Debug), None, None, None, None] .into_iter() .flatten(), @@ -242,7 +261,7 @@ impl SkipGroup { } /// Returns `true` if [`Trait`] is supported by any group. - pub fn trait_supported(trait_: Trait) -> bool { + pub fn trait_supported_by_skip_all(trait_: Trait) -> bool { match trait_ { Trait::Clone | Trait::Copy | Trait::Default => false, Trait::Debug diff --git a/src/data.rs b/src/data.rs index e2f35536..4b54e187 100644 --- a/src/data.rs +++ b/src/data.rs @@ -311,7 +311,7 @@ impl<'a> Data<'a> { } /// Returns `true` if all fields are skipped with that [`Trait`]. - fn skip(&self, trait_: Trait) -> bool { + pub fn skip(&self, trait_: Trait) -> bool { self.skip_inner.trait_skipped(trait_) || match self.fields() { Either::Left(fields) => fields.skip(trait_), diff --git a/src/error.rs b/src/error.rs index 9557ac58..1b93e409 100644 --- a/src/error.rs +++ b/src/error.rs @@ -277,6 +277,11 @@ impl Error { ) } + /// Unsupported `skip(Clone)` while deriving copy. + pub fn unable_to_skip_clone_while_deriving_copy(skip_clone: Span) -> syn::Error { + syn::Error::new(skip_clone, "Cannot skip `Clone` while deriving `Copy`") + } + /// List of available [`Trait`](crate::Trait)s. fn trait_list() -> String { [ @@ -300,6 +305,7 @@ impl Error { /// List of available [`SkipGroup`](crate::SkipGroup)s. fn skip_group_list() -> String { [ + "Clone", "Debug", "EqHashOrd", "Hash", diff --git a/src/test/bound.rs b/src/test/bound.rs index a84d0c27..c440fdac 100644 --- a/src/test/bound.rs +++ b/src/test/bound.rs @@ -18,7 +18,7 @@ fn bound() -> Result<()> { #[inline] fn clone(&self) -> Self { match self { - Test(ref __field_0, ref __field_1) => Test(::core::clone::Clone::clone(__field_0), ::core::clone::Clone::clone(__field_1)), + Test(ref __field_0, ref __field_1) => Test { 0: ::core::clone::Clone::clone(__field_0), 1: ::core::clone::Clone::clone(__field_1) }, } } } @@ -48,7 +48,7 @@ fn bound_multiple() -> Result<()> { #[inline] fn clone(&self) -> Self { match self { - Test(ref __field_0, ref __field_1) => Test(::core::clone::Clone::clone(__field_0), ::core::clone::Clone::clone(__field_1)), + Test(ref __field_0, ref __field_1) => Test{ 0: ::core::clone::Clone::clone(__field_0), 1: ::core::clone::Clone::clone(__field_1) }, } } } @@ -78,7 +78,7 @@ fn custom_bound() -> Result<()> { #[inline] fn clone(&self) -> Self { match self { - Test(ref __field_0) => Test(::core::clone::Clone::clone(__field_0)), + Test(ref __field_0) => Test { 0: ::core::clone::Clone::clone(__field_0) }, } } } @@ -103,7 +103,7 @@ fn where_() -> Result<()> { #[inline] fn clone(&self) -> Self { match self { - Test(ref __field_0, ref __field_1) => Test(::core::clone::Clone::clone(__field_0), ::core::clone::Clone::clone(__field_1)), + Test(ref __field_0, ref __field_1) => Test { 0: ::core::clone::Clone::clone(__field_0), 1: ::core::clone::Clone::clone(__field_1) }, } } } @@ -151,7 +151,7 @@ fn associated_type() -> Result<()> { #[inline] fn clone(&self) -> Self { match self { - Test(ref __field_0) => Test(::core::clone::Clone::clone(__field_0)), + Test(ref __field_0) => Test { 0: ::core::clone::Clone::clone(__field_0) }, } } } @@ -174,7 +174,7 @@ fn associated_type_custom_bound() -> Result<()> { #[inline] fn clone(&self) -> Self { match self { - Test(ref __field_0) => Test(::core::clone::Clone::clone(__field_0)), + Test(ref __field_0) => Test { 0: ::core::clone::Clone::clone(__field_0) }, } } } @@ -197,7 +197,7 @@ fn check_trait_bounds() -> Result<()> { #[inline] fn clone(&self) -> Self { match self { - Test(ref __field_0, ref __field_1) => Test(::core::clone::Clone::clone(__field_0), ::core::clone::Clone::clone(__field_1)), + Test(ref __field_0, ref __field_1) => Test { 0: ::core::clone::Clone::clone(__field_0), 1: ::core::clone::Clone::clone(__field_1) }, } } } @@ -333,7 +333,7 @@ fn check_multiple_trait_bounds() -> Result<()> { #[inline] fn clone(&self) -> Self { match self { - Test(ref __field_0, ref __field_1) => Test(::core::clone::Clone::clone(__field_0), ::core::clone::Clone::clone(__field_1)), + Test(ref __field_0, ref __field_1) => Test { 0: ::core::clone::Clone::clone(__field_0), 1: ::core::clone::Clone::clone(__field_1) }, } } } diff --git a/src/test/clone.rs b/src/test/clone.rs index fdaafdd4..4a8bb0b2 100644 --- a/src/test/clone.rs +++ b/src/test/clone.rs @@ -26,6 +26,54 @@ fn struct_() -> Result<()> { ) } +#[test] +fn skip_inner() -> Result<()> { + test_derive( + quote! { + #[derive_where(Clone)] + #[derive_where(skip_inner(Clone))] + struct Test { + field: std::marker::PhantomData, + } + }, + quote! { + #[automatically_derived] + impl ::core::clone::Clone for Test { + #[inline] + fn clone(&self) -> Self { + match self { + Test { field: ref __field_field } => Test { field: ::core::default::Default::default() }, + } + } + } + }, + ) +} + +#[test] +fn skip_field() -> Result<()> { + test_derive( + quote! { + #[derive_where(Clone)] + struct Test { + #[derive_where(skip(Clone))] + field: std::marker::PhantomData, + } + }, + quote! { + #[automatically_derived] + impl ::core::clone::Clone for Test { + #[inline] + fn clone(&self) -> Self { + match self { + Test { field: ref __field_field } => Test { field: ::core::default::Default::default() }, + } + } + } + }, + ) +} + #[test] fn tuple() -> Result<()> { test_derive( @@ -39,7 +87,7 @@ fn tuple() -> Result<()> { #[inline] fn clone(&self) -> Self { match self { - Test(ref __field_0) => Test(::core::clone::Clone::clone(__field_0)), + Test(ref __field_0) => Test { 0: ::core::clone::Clone::clone(__field_0) }, } } } @@ -68,8 +116,8 @@ fn enum_() -> Result<()> { match self { Test::A { field: ref __field_field } => Test::A { field: ::core::clone::Clone::clone(__field_field) }, Test::B { } => Test::B { }, - Test::C(ref __field_0) => Test::C(::core::clone::Clone::clone(__field_0)), - Test::D() => Test::D(), + Test::C(ref __field_0) => Test::C { 0: ::core::clone::Clone::clone(__field_0) }, + Test::D() => Test::D { }, Test::E => Test::E, } } diff --git a/src/trait_/clone.rs b/src/trait_/clone.rs index dd08a33b..94592b33 100644 --- a/src/trait_/clone.rs +++ b/src/trait_/clone.rs @@ -5,7 +5,8 @@ use quote::quote; use syn::{TraitBound, TraitBoundModifier, TypeParamBound}; use crate::{ - Data, DataType, DeriveTrait, DeriveWhere, Item, SimpleType, SplitGenerics, Trait, TraitImpl, + data::Field, Data, DataType, DeriveTrait, DeriveWhere, Item, SimpleType, SplitGenerics, Trait, + TraitImpl, }; /// Dummy-struct implement [`Trait`] for [`Clone`](trait@std::clone::Clone). @@ -99,25 +100,26 @@ impl TraitImpl for Clone { } match data.simple_type() { - SimpleType::Struct(fields) => { + SimpleType::Struct(fields) | SimpleType::Tuple(fields) => { let self_pattern = &fields.self_pattern; let item_path = &data.path; - let self_ident = data.iter_self_ident(**trait_); - let fields = data.iter_field_ident(**trait_); let trait_path = trait_.path(); + let default_path = DeriveTrait::Default.path(); - quote! { - #self_pattern => #item_path { #(#fields: #trait_path::clone(#self_ident)),* }, - } - } - SimpleType::Tuple(fields) => { - let self_pattern = &fields.self_pattern; - let item_path = &data.path; - let self_ident = data.iter_self_ident(**trait_); - let trait_path = trait_.path(); + let fields = fields.fields.iter().map( + |field @ Field { + self_ident, member, .. + }| { + if field.skip(Trait::Clone) || data.skip(Trait::Clone) { + quote!(#member: #default_path::default()) + } else { + quote!(#member: #trait_path::clone(#self_ident)) + } + }, + ); quote! { - #self_pattern => #item_path(#(#trait_path::clone(#self_ident)),*), + #self_pattern => #item_path { #(#fields),* }, } } SimpleType::Unit(pattern) => { diff --git a/tests/skip/field_trait.rs b/tests/skip/field_trait.rs index 567b4bf9..168fb4b7 100644 --- a/tests/skip/field_trait.rs +++ b/tests/skip/field_trait.rs @@ -4,7 +4,8 @@ use std::cmp::Ordering; use derive_where::derive_where; use crate::util::{ - self, AssertDebug, AssertHash, AssertOrd, AssertPartialEq, AssertPartialOrd, Wrapper, + self, AssertClone, AssertDebug, AssertHash, AssertOrd, AssertPartialEq, AssertPartialOrd, + Wrapper, }; #[test] @@ -19,6 +20,18 @@ fn debug() { assert_eq!(format!("{:?}", test_1), "Test"); } +#[test] +fn clone() { + #[derive_where(Clone)] + struct Test(#[derive_where(skip(Clone))] Wrapper); + + let test_1 = Test(42.into()); + + let _ = AssertClone(&test_1); + + assert_eq!(test_1.clone().0, 0) +} + #[test] fn hash() { #[derive_where(Hash)] diff --git a/tests/skip/struct_trait.rs b/tests/skip/struct_trait.rs index e9df4a71..07dfd200 100644 --- a/tests/skip/struct_trait.rs +++ b/tests/skip/struct_trait.rs @@ -4,7 +4,8 @@ use std::cmp::Ordering; use derive_where::derive_where; use crate::util::{ - self, AssertDebug, AssertHash, AssertOrd, AssertPartialEq, AssertPartialOrd, Wrapper, + self, AssertClone, AssertDebug, AssertHash, AssertOrd, AssertPartialEq, AssertPartialOrd, + Wrapper, }; #[test] @@ -20,6 +21,19 @@ fn debug() { assert_eq!(format!("{:?}", test_1), "Test"); } +#[test] +fn clone() { + #[derive_where(Clone)] + #[derive_where(skip_inner(Clone))] + struct Test(Wrapper); + + let test_1 = Test(42.into()); + + let _ = AssertClone(&test_1); + + assert_eq!(test_1.clone().0, 0); +} + #[test] fn hash() { #[derive_where(Hash)] @@ -67,8 +81,8 @@ fn ord() { #[test] fn all() { - #[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] - #[derive_where(skip_inner(Debug, EqHashOrd))] + #[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Clone)] + #[derive_where(skip_inner(Debug, EqHashOrd, Clone))] struct Test(Wrapper); let test_1 = Test(42.into()); @@ -76,6 +90,7 @@ fn all() { let test_le = Test(41.into()); let test_ge = Test(43.into()); + let _ = AssertClone(&test_1); let _ = AssertDebug(&test_1); let _ = AssertHash(&test_1); let _ = AssertOrd(&test_1); @@ -84,6 +99,8 @@ fn all() { assert_eq!(format!("{:?}", test_1), "Test"); + assert_eq!(test_1.clone().0, 0); + util::hash_eq(&test_1, &test_2); util::hash_eq(&test_1, &test_ge); diff --git a/tests/skip/variant_trait.rs b/tests/skip/variant_trait.rs index 83ab7c36..9fd92fa1 100644 --- a/tests/skip/variant_trait.rs +++ b/tests/skip/variant_trait.rs @@ -2,9 +2,11 @@ use std::cmp::Ordering; use derive_where::derive_where; +use pretty_assertions::assert_eq; use crate::util::{ - self, AssertDebug, AssertHash, AssertOrd, AssertPartialEq, AssertPartialOrd, Wrapper, + self, AssertClone, AssertDebug, AssertHash, AssertOrd, AssertPartialEq, AssertPartialOrd, + Wrapper, }; #[test] @@ -22,6 +24,22 @@ fn debug() { assert_eq!(format!("{:?}", test_1), "A"); } +#[test] +fn clone() { + #[derive_where(Clone)] + enum Test { + #[derive_where(skip_inner(Clone))] + A(Wrapper), + } + + let test_1 = Test::A(42.into()); + + let _ = AssertClone(&test_1); + + let Test::A(cloned) = test_1.clone(); + assert_eq!(cloned, 0); +} + #[test] fn hash() { #[derive_where(Hash)] @@ -73,9 +91,9 @@ fn ord() { #[test] fn all() { - #[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] + #[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Clone)] enum Test { - #[derive_where(skip_inner(Debug, EqHashOrd))] + #[derive_where(skip_inner(Debug, EqHashOrd, Clone))] A(Wrapper), } @@ -85,6 +103,7 @@ fn all() { let test_ge = Test::A(43.into()); let _ = AssertDebug(&test_1); + let _ = AssertClone(&test_1); let _ = AssertHash(&test_1); let _ = AssertOrd(&test_1); let _ = AssertPartialEq(&test_1); @@ -92,6 +111,8 @@ fn all() { assert_eq!(format!("{:?}", test_1), "A"); + assert_eq!(test_1.clone(), Test::A(0.into())); + util::hash_eq(&test_1, &test_2); util::hash_eq(&test_1, &test_ge); diff --git a/tests/ui/not-zeroize/item_skip.rs b/tests/ui/not-zeroize/item_skip.rs index aebf8031..3a9464cb 100644 --- a/tests/ui/not-zeroize/item_skip.rs +++ b/tests/ui/not-zeroize/item_skip.rs @@ -2,8 +2,8 @@ use std::marker::PhantomData; use derive_where::derive_where; -#[derive_where(Clone; T)] -#[derive_where(skip_inner(Clone))] +#[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 index f2faddbb..de66a5e7 100644 --- a/tests/ui/not-zeroize/item_skip.stderr +++ b/tests/ui/not-zeroize/item_skip.stderr @@ -1,5 +1,5 @@ -error: unsupported skip group, expected one of Debug, EqHashOrd, Hash +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(Clone))] - | ^^^^^ +6 | #[derive_where(skip_inner(Copy))] + | ^^^^ diff --git a/tests/ui/not-zeroize/skip.rs b/tests/ui/not-zeroize/skip.rs index c4e6a326..f8f861df 100644 --- a/tests/ui/not-zeroize/skip.rs +++ b/tests/ui/not-zeroize/skip.rs @@ -2,7 +2,7 @@ use std::marker::PhantomData; use derive_where::derive_where; -#[derive_where(Clone; T)] -struct UnsupportedTrait(#[derive_where(skip(Clone))] PhantomData); +#[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 index 99105e49..44e02135 100644 --- a/tests/ui/not-zeroize/skip.stderr +++ b/tests/ui/not-zeroize/skip.stderr @@ -1,5 +1,5 @@ -error: unsupported skip group, expected one of Debug, EqHashOrd, Hash +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(Clone))] PhantomData); - | ^^^^^ +6 | struct UnsupportedTrait(#[derive_where(skip(Copy))] PhantomData); + | ^^^^ diff --git a/tests/ui/skip.rs b/tests/ui/skip.rs index 40a7742c..838b4a95 100644 --- a/tests/ui/skip.rs +++ b/tests/ui/skip.rs @@ -45,4 +45,7 @@ struct DuplicateTraitSeparate( #[derive_where(Clone; T)] struct MissingDeriveTrait(#[derive_where(skip(Debug))] PhantomData); +#[derive_where(Clone, Copy)] +struct SkipCloneWhileCopy(#[derive_where(skip(Clone))] PhantomData); + fn main() {} diff --git a/tests/ui/skip.stderr b/tests/ui/skip.stderr index eadeee49..d1191b25 100644 --- a/tests/ui/skip.stderr +++ b/tests/ui/skip.stderr @@ -51,3 +51,9 @@ error: trait to be skipped isn't being implemented | 46 | struct MissingDeriveTrait(#[derive_where(skip(Debug))] PhantomData); | ^^^^^ + +error: Cannot skip `Clone` while deriving `Copy` + --> tests/ui/skip.rs:49:50 + | +49 | struct SkipCloneWhileCopy(#[derive_where(skip(Clone))] PhantomData); + | ^^^^^ diff --git a/tests/ui/zeroize/item_skip.rs b/tests/ui/zeroize/item_skip.rs index aebf8031..3a9464cb 100644 --- a/tests/ui/zeroize/item_skip.rs +++ b/tests/ui/zeroize/item_skip.rs @@ -2,8 +2,8 @@ use std::marker::PhantomData; use derive_where::derive_where; -#[derive_where(Clone; T)] -#[derive_where(skip_inner(Clone))] +#[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 index 2723f957..9f8ec6f8 100644 --- a/tests/ui/zeroize/item_skip.stderr +++ b/tests/ui/zeroize/item_skip.stderr @@ -1,5 +1,5 @@ -error: unsupported skip group, expected one of Debug, EqHashOrd, Hash, Zeroize +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(Clone))] - | ^^^^^ +6 | #[derive_where(skip_inner(Copy))] + | ^^^^ diff --git a/tests/ui/zeroize/skip.rs b/tests/ui/zeroize/skip.rs index c4e6a326..6f866f0d 100644 --- a/tests/ui/zeroize/skip.rs +++ b/tests/ui/zeroize/skip.rs @@ -2,7 +2,7 @@ use std::marker::PhantomData; use derive_where::derive_where; -#[derive_where(Clone; T)] -struct UnsupportedTrait(#[derive_where(skip(Clone))] PhantomData); +#[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 index 4dddf78c..acc20bab 100644 --- a/tests/ui/zeroize/skip.stderr +++ b/tests/ui/zeroize/skip.stderr @@ -1,5 +1,5 @@ -error: unsupported skip group, expected one of Debug, EqHashOrd, Hash, Zeroize - --> tests/ui/zeroize/skip.rs:6:48 +error: unsupported trait, expected one of Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize, ZeroizeOnDrop + --> tests/ui/zeroize/skip.rs:5:16 | -6 | struct UnsupportedTrait(#[derive_where(skip(Clone))] PhantomData); - | ^^^^^ +5 | #[derive_where(Skip; T)] + | ^^^^ From be42620a893661497321d6efc57002dfb2b92573 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Tue, 29 Apr 2025 10:53:07 +0200 Subject: [PATCH 2/4] Fix tests --- src/test/bound.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/test/bound.rs b/src/test/bound.rs index c440fdac..a26658c8 100644 --- a/src/test/bound.rs +++ b/src/test/bound.rs @@ -128,7 +128,10 @@ fn for_lifetime() -> Result<()> { #[inline] fn clone(&self) -> Self { match self { - Test(ref __field_0, ref __field_1) => Test(::core::clone::Clone::clone(__field_0), ::core::clone::Clone::clone(__field_1)), + Test(ref __field_0, ref __field_1) => Test { + 0: ::core::clone::Clone::clone(__field_0), + 1: ::core::clone::Clone::clone(__field_1) + }, } } } From 7e5d11780f042a7d291b25305e961f08978ace2c Mon Sep 17 00:00:00 2001 From: daxpedda Date: Tue, 29 Apr 2025 11:13:39 +0200 Subject: [PATCH 3/4] Refine tests --- tests/skip/field_trait.rs | 6 +++--- tests/skip/struct_trait.rs | 16 ++++++++-------- tests/skip/variant_trait.rs | 6 +++--- tests/ui/skip.rs | 6 ++++++ tests/ui/skip.stderr | 13 +++++++++++++ tests/util/mod.rs | 31 +++++++++++++++++++++++++++++++ 6 files changed, 64 insertions(+), 14 deletions(-) diff --git a/tests/skip/field_trait.rs b/tests/skip/field_trait.rs index 168fb4b7..284b04ab 100644 --- a/tests/skip/field_trait.rs +++ b/tests/skip/field_trait.rs @@ -5,7 +5,7 @@ use derive_where::derive_where; use crate::util::{ self, AssertClone, AssertDebug, AssertHash, AssertOrd, AssertPartialEq, AssertPartialOrd, - Wrapper, + NonTrait, Wrapper, }; #[test] @@ -23,13 +23,13 @@ fn debug() { #[test] fn clone() { #[derive_where(Clone)] - struct Test(#[derive_where(skip(Clone))] Wrapper); + struct Test(#[derive_where(skip(Clone))] NonTrait); let test_1 = Test(42.into()); let _ = AssertClone(&test_1); - assert_eq!(test_1.clone().0, 0) + assert_eq!(test_1.clone().0.data(), 0) } #[test] diff --git a/tests/skip/struct_trait.rs b/tests/skip/struct_trait.rs index 07dfd200..e998f350 100644 --- a/tests/skip/struct_trait.rs +++ b/tests/skip/struct_trait.rs @@ -5,7 +5,7 @@ use derive_where::derive_where; use crate::util::{ self, AssertClone, AssertDebug, AssertHash, AssertOrd, AssertPartialEq, AssertPartialOrd, - Wrapper, + NonTrait, Wrapper, }; #[test] @@ -25,13 +25,13 @@ fn debug() { fn clone() { #[derive_where(Clone)] #[derive_where(skip_inner(Clone))] - struct Test(Wrapper); + struct Test(NonTrait); let test_1 = Test(42.into()); let _ = AssertClone(&test_1); - assert_eq!(test_1.clone().0, 0); + assert_eq!(test_1.clone().0.data(), 0); } #[test] @@ -81,9 +81,9 @@ fn ord() { #[test] fn all() { - #[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Clone)] - #[derive_where(skip_inner(Debug, EqHashOrd, Clone))] - struct Test(Wrapper); + #[derive_where(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] + #[derive_where(skip_inner(Clone, Debug, EqHashOrd))] + struct Test(NonTrait); let test_1 = Test(42.into()); let test_2 = Test(42.into()); @@ -97,9 +97,9 @@ fn all() { let _ = AssertPartialEq(&test_1); let _ = AssertPartialOrd(&test_1); - assert_eq!(format!("{:?}", test_1), "Test"); + assert_eq!(test_1.clone().0.data(), 0); - assert_eq!(test_1.clone().0, 0); + assert_eq!(format!("{:?}", test_1), "Test"); util::hash_eq(&test_1, &test_2); util::hash_eq(&test_1, &test_ge); diff --git a/tests/skip/variant_trait.rs b/tests/skip/variant_trait.rs index 9fd92fa1..e53a2255 100644 --- a/tests/skip/variant_trait.rs +++ b/tests/skip/variant_trait.rs @@ -6,7 +6,7 @@ use pretty_assertions::assert_eq; use crate::util::{ self, AssertClone, AssertDebug, AssertHash, AssertOrd, AssertPartialEq, AssertPartialOrd, - Wrapper, + NonTrait, Wrapper, }; #[test] @@ -29,7 +29,7 @@ fn clone() { #[derive_where(Clone)] enum Test { #[derive_where(skip_inner(Clone))] - A(Wrapper), + A(NonTrait), } let test_1 = Test::A(42.into()); @@ -37,7 +37,7 @@ fn clone() { let _ = AssertClone(&test_1); let Test::A(cloned) = test_1.clone(); - assert_eq!(cloned, 0); + assert_eq!(cloned.data(), 0); } #[test] diff --git a/tests/ui/skip.rs b/tests/ui/skip.rs index 838b4a95..bd367bb4 100644 --- a/tests/ui/skip.rs +++ b/tests/ui/skip.rs @@ -48,4 +48,10 @@ struct MissingDeriveTrait(#[derive_where(skip(Debug))] PhantomData); #[derive_where(Clone, Copy)] struct SkipCloneWhileCopy(#[derive_where(skip(Clone))] PhantomData); +struct NonDefault(PhantomData); + +#[derive_where(Clone)] +#[derive_where(skip_inner(Clone))] +struct SkipCloneNonDefault(NonDefault); + fn main() {} diff --git a/tests/ui/skip.stderr b/tests/ui/skip.stderr index d1191b25..a22e7651 100644 --- a/tests/ui/skip.stderr +++ b/tests/ui/skip.stderr @@ -57,3 +57,16 @@ error: Cannot skip `Clone` while deriving `Copy` | 49 | struct SkipCloneWhileCopy(#[derive_where(skip(Clone))] PhantomData); | ^^^^^ + +error[E0277]: the trait bound `NonDefault: Default` is not satisfied + --> tests/ui/skip.rs:53:1 + | +53 | #[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); + | diff --git a/tests/util/mod.rs b/tests/util/mod.rs index d228d104..d2f1eb7f 100644 --- a/tests/util/mod.rs +++ b/tests/util/mod.rs @@ -121,6 +121,37 @@ pub struct AssertZeroize<'a, T: Zeroize>(pub &'a T); #[allow(dead_code)] pub struct AssertZeroizeOnDrop<'a, T: ZeroizeOnDrop>(pub &'a T); +#[allow(dead_code)] +pub struct NonTrait { + data: i32, + hack: PhantomData, +} + +#[allow(dead_code)] +impl NonTrait { + pub fn data(&self) -> i32 { + self.data + } +} + +impl Default for NonTrait { + fn default() -> Self { + Self { + data: i32::default(), + hack: PhantomData, + } + } +} + +impl From for NonTrait<()> { + fn from(data: i32) -> Self { + Self { + data, + hack: PhantomData, + } + } +} + #[allow(dead_code)] pub fn hash_eq(test_1: T, test_2: T) { let mut hasher = DefaultHasher::new(); From 78507a291fad284500430670d9559fe8eb4b1f10 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Tue, 29 Apr 2025 11:17:42 +0200 Subject: [PATCH 4/4] Document new feature --- CHANGELOG.md | 3 ++- README.md | 1 + src/lib.rs | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1075c81b..86919568 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Allow skipping fields for `Clone`, calling `Default::default()` instead. - **Note:** `Clone` is excluded from `#[derive_where(skip)]` to avoid this being a breaking change. + **Note:** `Clone` is excluded from blanket skipping and can only be used with + selective skipping to avoid this being a breaking change. ## [1.3.0] - 2025-04-21 diff --git a/README.md b/README.md index af1d1bf5..9c522140 100644 --- a/README.md +++ b/README.md @@ -144,6 +144,7 @@ Selective skipping of fields for certain traits is also an option, both in `skip` and `skip_inner`. To prevent breaking invariants defined for these traits, some of them can only be skipped in groups. The following groups are available: +- [`Clone`]: Uses [`Default`] instead of [`Clone`]. - [`Debug`] - `EqHashOrd`: Skips [`Eq`], [`Hash`], [`Ord`], [`PartialOrd`] and [`PartialEq`]. diff --git a/src/lib.rs b/src/lib.rs index 5db30e3d..c5df73e2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -170,6 +170,7 @@ //! `skip` and `skip_inner`. To prevent breaking invariants defined for these //! traits, some of them can only be skipped in groups. The following groups are //! available: +//! - [`Clone`]: Uses [`Default`] instead of [`Clone`]. //! - [`Debug`] //! - `EqHashOrd`: Skips [`Eq`], [`Hash`], [`Ord`], [`PartialOrd`] and //! [`PartialEq`].