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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`].
Expand Down
2 changes: 1 addition & 1 deletion src/attr/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
25 changes: 22 additions & 3 deletions src/attr/skip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))]
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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_)),
Expand All @@ -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`].
Expand All @@ -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),
Expand All @@ -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",
Expand All @@ -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(),
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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_),
Expand Down
6 changes: 6 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
[
Expand All @@ -300,6 +305,7 @@ impl Error {
/// List of available [`SkipGroup`](crate::SkipGroup)s.
fn skip_group_list() -> String {
[
"Clone",
"Debug",
"EqHashOrd",
"Hash",
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`].
Expand Down
21 changes: 12 additions & 9 deletions src/test/bound.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) },
}
}
}
Expand Down Expand Up @@ -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) },
}
}
}
Expand Down Expand Up @@ -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) },
}
}
}
Expand All @@ -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) },
}
}
}
Expand All @@ -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)
},
}
}
}
Expand All @@ -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) },
}
}
}
Expand All @@ -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) },
}
}
}
Expand All @@ -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) },
}
}
}
Expand Down Expand Up @@ -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) },
}
}
}
Expand Down
54 changes: 51 additions & 3 deletions src/test/clone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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) },
}
}
}
Expand Down Expand Up @@ -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,
}
}
Expand Down
30 changes: 16 additions & 14 deletions src/trait_/clone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down Expand Up @@ -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) => {
Expand Down
15 changes: 14 additions & 1 deletion tests/skip/field_trait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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)]
Expand Down
Loading