Skip to content

skip(Clone) #105

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 29, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -5,6 +5,13 @@ 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 blanket skipping and can only be used with
selective skipping to avoid this being a breaking change.

## [1.3.0] - 2025-04-21

### Added
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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`].
2 changes: 1 addition & 1 deletion src/attr/item.rs
Original file line number Diff line number Diff line change
@@ -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.
25 changes: 22 additions & 3 deletions src/attr/skip.rs
Original file line number Diff line number Diff line change
@@ -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<Item = Trait> {
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
2 changes: 1 addition & 1 deletion src/data.rs
Original file line number Diff line number Diff line change
@@ -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_),
6 changes: 6 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -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",
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -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`].
21 changes: 12 additions & 9 deletions src/test/bound.rs
Original file line number Diff line number Diff line change
@@ -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) },
}
}
}
@@ -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)
},
}
}
}
@@ -151,7 +154,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 +177,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 +200,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 +336,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) },
}
}
}
54 changes: 51 additions & 3 deletions src/test/clone.rs
Original file line number Diff line number Diff line change
@@ -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<T> {
field: std::marker::PhantomData<T>,
}
},
quote! {
#[automatically_derived]
impl<T> ::core::clone::Clone for Test<T> {
#[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<T> {
#[derive_where(skip(Clone))]
field: std::marker::PhantomData<T>,
}
},
quote! {
#[automatically_derived]
impl<T> ::core::clone::Clone for Test<T> {
#[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,
}
}
30 changes: 16 additions & 14 deletions src/trait_/clone.rs
Original file line number Diff line number Diff line change
@@ -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) => {
15 changes: 14 additions & 1 deletion tests/skip/field_trait.rs
Original file line number Diff line number Diff line change
@@ -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,
NonTrait, Wrapper,
};

#[test]
@@ -19,6 +20,18 @@ fn debug() {
assert_eq!(format!("{:?}", test_1), "Test");
}

#[test]
fn clone() {
#[derive_where(Clone)]
struct Test<T>(#[derive_where(skip(Clone))] NonTrait<T>);

let test_1 = Test(42.into());

let _ = AssertClone(&test_1);

assert_eq!(test_1.clone().0.data(), 0)
}

#[test]
fn hash() {
#[derive_where(Hash)]
Loading