diff --git a/.gitignore b/.gitignore index 59b51f17..baf66e28 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,6 @@ gh-pages # git files .swp /tags + +# RustRover/Other jetbrains IDE files +.idea/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 679e859c..fec77e48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## master +### Added +- Add `Clone` and `Copy` derives, similar to `std`'s ones. These derives do not + add any trait bounds on generic parameters by default, making them usable + in more situations. Custom trait bounds can be specified via + `#[clone(bound(...))]` and `#[copy(bound(...))]` attributes. + ([#533](https://github.com/JelteF/derive_more/pull/533)) + ### Fixed - Mistakenly generated code for `owned` type in `TryInto`, `Unwrap` and `TryUnwrap` diff --git a/Cargo.toml b/Cargo.toml index 300ed19e..f4f48687 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,7 +56,9 @@ default = ["std"] add = ["derive_more-impl/add"] add_assign = ["derive_more-impl/add_assign"] as_ref = ["derive_more-impl/as_ref"] +clone = ["derive_more-impl/clone"] constructor = ["derive_more-impl/constructor"] +copy = ["derive_more-impl/copy"] debug = ["derive_more-impl/debug"] deref = ["derive_more-impl/deref"] deref_mut = ["derive_more-impl/deref_mut"] @@ -84,7 +86,9 @@ full = [ "add", "add_assign", "as_ref", + "clone", "constructor", + "copy", "debug", "deref", "deref_mut", @@ -135,11 +139,22 @@ name = "boats_display_derive" path = "tests/boats_display_derive.rs" required-features = ["display"] +[[test]] +name = "clone" +path = "tests/clone.rs" +required-features = ["clone"] + [[test]] name = "constructor" path = "tests/constructor.rs" required-features = ["constructor"] +[[test]] +name = "copy" +path = "tests/copy.rs" +required-features = ["copy", "clone"] + + [[test]] name = "debug" path = "tests/debug.rs" diff --git a/README.md b/README.md index 0bd97d97..59578f20 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,11 @@ These are traits that can be used for operator overloading. `ShrAssign` and `ShlAssign` 11. [`Eq`], [`PartialEq`] +### Other traits + +1. [`Clone`] +2. [`Copy`] + ### Static methods @@ -264,6 +269,9 @@ Changing [MSRV] (minimum supported Rust version) of this crate is treated as a * [`Eq`]: https://docs.rs/derive_more/latest/derive_more/derive.Eq.html [`PartialEq`]: https://docs.rs/derive_more/latest/derive_more/derive.PartialEq.html +[`Clone`]: https://docs.rs/derive_more/latest/derive_more/derive.Clone.html +[`Copy`]: https://docs.rs/derive_more/latest/derive_more/derive.Copy.html + [`Constructor`]: https://docs.rs/derive_more/latest/derive_more/derive.Constructor.html [`IsVariant`]: https://docs.rs/derive_more/latest/derive_more/derive.IsVariant.html [`Unwrap`]: https://docs.rs/derive_more/latest/derive_more/derive.Unwrap.html diff --git a/impl/Cargo.toml b/impl/Cargo.toml index a1441154..e1d6506a 100644 --- a/impl/Cargo.toml +++ b/impl/Cargo.toml @@ -52,7 +52,9 @@ default = [] add = ["syn/extra-traits", "syn/visit"] add_assign = ["syn/extra-traits", "syn/visit"] as_ref = ["syn/extra-traits", "syn/visit"] +clone = ["syn/extra-traits"] constructor = [] +copy = ["syn/extra-traits"] debug = ["syn/extra-traits", "dep:unicode-ident"] deref = [] deref_mut = [] @@ -79,7 +81,9 @@ full = [ "add", "add_assign", "as_ref", + "clone", "constructor", + "copy", "debug", "deref", "deref_mut", diff --git a/impl/doc/clone.md b/impl/doc/clone.md new file mode 100644 index 00000000..a28097a1 --- /dev/null +++ b/impl/doc/clone.md @@ -0,0 +1,101 @@ +# Using `#[derive(Clone)]` + +Deriving `Clone` for enums/structs works similarly to the one in `std`, +by cloning all the available fields, but, in contrast: +1. Does not constrain generic parameters. + +## Example usage + +```rust +use derive_more::{Clone}; + +#[derive(Clone, Copy, PartialEq, Debug)] +struct Simple(i32); + +let a = Simple(42); +let b = a.clone(); +assert_eq!(a, b); +``` + +### Cloning non-`Clone` types references + +Because this derive doesn't add any bounds to generic parameters, it can be used with references to non-`Clone` types: + +```rust +use derive_more::{Clone}; + +struct NotClone; + +#[derive(Clone)] +struct ReferenceToGenericType<'a, T>(&'a T); + +let not_clone = NotClone; +let should_be_clonable = ReferenceToGenericType(¬_clone); +let clone = should_be_clonable.clone(); + +assert!(core::ptr::eq(should_be_clonable.0, clone.0)) +``` + +this generates code equivalent to: +```rust +# struct NotClone; +# struct ReferenceToGenericType<'a, T>(&'a T); + +impl<'a, T> Clone for ReferenceToGenericType<'a, T> { + fn clone(&self) -> Self { + Self(core::clone::Clone::clone(&self.0)) + } +} +``` + +The derive from std would have put a `T: Clone` bound on the clone impl. This is a bit too much as references are +perfectly clonable even if the type behind in not. + +### Custom trait bounds + +Sometimes you may want to specify custom trait bounds on your generic type parameters. +This can be done with a `#[clone(bound(...))]` attribute. + +`#[clone(bound(...))]` accepts code tokens in a format similar to the format used in +angle bracket list (or `where` clause predicates): `T: MyTrait, U: Trait1 + Trait2`. + +```rust +use derive_more::Clone; + +trait Foo { + type Bar; +} + +#[derive(Clone)] +#[clone(bound(T::Bar: Clone))] +struct Baz(T::Bar); + +// FooImpl doesn't implement Clone, but that's fine +// because only T::Bar needs to be Clone +struct FooImpl; + +impl Foo for FooImpl { + type Bar = i32; +} + +let baz: Baz = Baz(42); +let cloned = baz.clone(); +assert_eq!(baz.0, cloned.0); +``` +This generates code equivalent to: +```rust +# trait Foo { type Bar; } +# struct FooImpl; +# impl Foo for FooImpl { type Bar = i32; } +# +# struct Baz(T::Bar); + +impl Clone for Baz +where + T::Bar: Clone, // specified via `#[clone(bound(...))]` +{ + fn clone(&self) -> Self { + Self(core::clone::Clone::clone(&self.0)) + } +} +``` diff --git a/impl/doc/copy.md b/impl/doc/copy.md new file mode 100644 index 00000000..edbdd7dd --- /dev/null +++ b/impl/doc/copy.md @@ -0,0 +1,102 @@ +# Using `#[derive(Copy)]` + + +Deriving ` Copy` for enums/structs works similarly to the one in `std`, but, in contrast: +1. Does not constrain generic parameters. + +Note: `Copy` requires `Clone`, so you should derive it as well using [`Clone`]. + +## Example usage + +```rust +use derive_more::{Clone, Copy}; + +#[derive(Clone, Copy, PartialEq, Debug)] +struct Simple(i32); + +let a = Simple(42); +let b = a; +assert_eq!(a, b); +``` + +### Copying non-`Copy` types references + +Because this derive doesn't add any bounds to generic parameters, it can be used with references to non-`Copy` types: + +```rust +use derive_more::{Clone, Copy}; + +struct NotCopy; + +#[derive(Clone, Copy)] +struct ReferenceToGenericType<'a, T>(&'a T); + +let not_copy = NotCopy; +let should_be_copyable = ReferenceToGenericType(¬_copy); +let copy = should_be_copyable; + +assert!(core::ptr::eq(should_be_copyable.0, copy.0)) +``` + +this generates code equivalent to: +```rust +# use derive_more::{Clone}; +# struct NotCopy; +# #[derive(Clone)] +# struct ReferenceToGenericType<'a, T>(&'a T); +impl<'a, T> Copy for ReferenceToGenericType<'a, T> { } +``` + +The derive from std would have put a `T: Copy` bound on the copy impl. This is a bit too much as references are +perfectly copyable even if the type behind in not. + +### Custom trait bounds + +Sometimes you may want to specify custom trait bounds on your generic type parameters. +This can be done with a `#[copy(bound(...))]` attribute. + +`#[copy(bound(...))]` accepts code tokens in a format similar to the format used in +angle bracket list (or `where` clause predicates): `T: MyTrait, U: Trait1 + Trait2`. + +```rust +use derive_more::{Clone, Copy}; + +trait Foo { + type Bar; +} + +#[derive(Clone, Copy)] +#[copy(bound(T::Bar: Copy))] +#[clone(bound(T::Bar: Clone))] +struct Baz(T::Bar); + +// FooImpl doesn't implement Clone or Copy, but that's fine +// because only T::Bar needs to be Copy +struct FooImpl; + +impl Foo for FooImpl { + type Bar = i32; +} + +let baz: Baz = Baz(42); +let copy = baz; +assert_eq!(baz.0, copy.0); +``` + +This generates code equivalent to: +```rust +# use derive_more::{Clone}; +# trait Foo { type Bar; } +# struct FooImpl; +# impl Foo for FooImpl { type Bar = i32; } +# +# #[derive(Clone)] +# #[clone(bound(T::Bar: Clone))] +# struct Baz(T::Bar); + +impl Copy for Baz +where + T::Bar: Copy, // specified via `#[copy(bound(...))]` +{ +} +``` \ No newline at end of file diff --git a/impl/src/clone.rs b/impl/src/clone.rs new file mode 100644 index 00000000..45e32c3b --- /dev/null +++ b/impl/src/clone.rs @@ -0,0 +1,94 @@ +use crate::utils::attr::{self, ParseMultiple}; +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; +use syn::{Data, DeriveInput, Fields}; + +pub fn expand(input: &DeriveInput, _: &'static str) -> syn::Result { + let name = &input.ident; + let generics = &input.generics; + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + let ident = quote::format_ident!("clone"); + let real_where_clause = attr::Bounds::parse_attrs(&input.attrs, &ident)? + .map(|clause| { + let clause = clause.item.0; + quote! { where #clause } + }) + .or(where_clause.map(ToTokens::to_token_stream)); + + let clone_body = match &input.data { + Data::Struct(data) => clone_struct(&data.fields), + Data::Enum(data) => { + let variants = data.variants.iter().map(|v| { + let variant_name = &v.ident; + let (pattern, clone_expr) = clone_variant_fields(&v.fields); + quote! { + Self::#variant_name #pattern => Self::#variant_name #clone_expr + } + }); + quote! { + match self { + #(#variants),* + } + } + } + Data::Union(data) => { + return Err(syn::Error::new_spanned( + data.union_token, + "Clone cannot be derived for unions", + )); + } + }; + + Ok(quote! { + impl #impl_generics derive_more::core::clone::Clone for #name #ty_generics #real_where_clause { + fn clone(&self) -> Self { + #clone_body + } + } + }) +} + +fn clone_struct(fields: &Fields) -> TokenStream { + match fields { + Fields::Named(named) => { + let clones = named.named.iter().map(|f| { + let name = &f.ident; + quote! { #name: derive_more::core::clone::Clone::clone(&self.#name) } + }); + quote! { Self { #(#clones),* } } + } + Fields::Unnamed(unnamed) => { + let clones = (0..unnamed.unnamed.len()).map(|i| { + let idx = syn::Index::from(i); + quote! { derive_more::core::clone::Clone::clone(&self.#idx) } + }); + quote! { Self(#(#clones),*) } + } + Fields::Unit => quote! { Self }, + } +} + +fn clone_variant_fields(fields: &Fields) -> (TokenStream, TokenStream) { + match fields { + Fields::Named(named) => { + let names: Vec<_> = named.named.iter().map(|f| &f.ident).collect(); + let pattern = quote! { { #(#names),* } }; + let clones = names.iter().map(|n| { + quote! { #n: derive_more::core::clone::Clone::clone(#n) } + }); + (pattern, quote! { { #(#clones),* } }) + } + Fields::Unnamed(unnamed) => { + let bindings: Vec<_> = (0..unnamed.unnamed.len()) + .map(|i| quote::format_ident!("_{}", i)) + .collect(); + let pattern = quote! { (#(#bindings),*) }; + let clones = bindings.iter().map(|b| { + quote! { derive_more::core::clone::Clone::clone(#b) } + }); + (pattern, quote! { (#(#clones),*) }) + } + Fields::Unit => (TokenStream::new(), TokenStream::new()), + } +} diff --git a/impl/src/copy.rs b/impl/src/copy.rs new file mode 100644 index 00000000..741f6d7e --- /dev/null +++ b/impl/src/copy.rs @@ -0,0 +1,24 @@ +use crate::utils::attr::{self, ParseMultiple}; +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; +use syn::DeriveInput; + +pub fn expand(input: &DeriveInput, _: &'static str) -> syn::Result { + let name = &input.ident; + let generics = &input.generics; + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + let ident = quote::format_ident!("copy"); + let real_where_clause = attr::Bounds::parse_attrs(&input.attrs, &ident)? + .map(|clause| { + let clause = clause.item.0; + quote! { where #clause } + }) + .or(where_clause.map(ToTokens::to_token_stream)); + + Ok(quote! { + impl #impl_generics derive_more::core::marker::Copy for #name #ty_generics #real_where_clause { + + } + }) +} diff --git a/impl/src/fmt/mod.rs b/impl/src/fmt/mod.rs index d1109951..0addebfb 100644 --- a/impl/src/fmt/mod.rs +++ b/impl/src/fmt/mod.rs @@ -15,7 +15,6 @@ use syn::{ parse::{Parse, ParseStream}, parse_quote, punctuated::Punctuated, - spanned::Spanned as _, token, }; @@ -24,70 +23,6 @@ use crate::{ utils::{attr, Either, Spanning}, }; -/// Representation of a `bound` macro attribute, expressing additional trait bounds. -/// -/// ```rust,ignore -/// #[(bound())] -/// #[(bounds())] -/// #[(where())] -/// ``` -#[derive(Debug, Default)] -struct BoundsAttribute(Punctuated); - -impl Parse for BoundsAttribute { - fn parse(input: ParseStream<'_>) -> syn::Result { - Self::check_legacy_fmt(input)?; - - let _ = input.parse::().and_then(|p| { - if ["bound", "bounds", "where"] - .into_iter() - .any(|i| p.is_ident(i)) - { - Ok(p) - } else { - Err(syn::Error::new( - p.span(), - "unknown attribute argument, expected `bound(...)`", - )) - } - })?; - - let content; - syn::parenthesized!(content in input); - - content - .parse_terminated(syn::WherePredicate::parse, token::Comma) - .map(Self) - } -} - -impl BoundsAttribute { - /// Errors in case legacy syntax is encountered: `bound = "..."`. - fn check_legacy_fmt(input: ParseStream<'_>) -> syn::Result<()> { - let fork = input.fork(); - - let path = fork - .parse::() - .and_then(|path| fork.parse::().map(|_| path)); - match path { - Ok(path) if path.is_ident("bound") => fork - .parse::() - .ok() - .and_then(|lit| match lit { - syn::Lit::Str(s) => Some(s.value()), - _ => None, - }) - .map_or(Ok(()), |bound| { - Err(syn::Error::new( - input.span(), - format!("legacy syntax, use `bound({bound})` instead"), - )) - }), - Ok(_) | Err(_) => Ok(()), - } - } -} - /// Representation of a [`fmt`]-like attribute. /// /// ```rust,ignore @@ -515,7 +450,7 @@ struct ContainerAttributes { fmt: Option, /// Addition trait bounds. - bounds: BoundsAttribute, + bounds: attr::Bounds, } impl Parse for ContainerAttributes { @@ -523,9 +458,9 @@ impl Parse for ContainerAttributes { // We do check `FmtAttribute::check_legacy_fmt` eagerly here, because `Either` will swallow // any error of the `Either::Left` if the `Either::Right` succeeds. FmtAttribute::check_legacy_fmt(input)?; - >::parse(input).map(|v| match v { + >::parse(input).map(|v| match v { Either::Left(fmt) => Self { - bounds: BoundsAttribute::default(), + bounds: attr::Bounds::default(), fmt: Some(fmt), }, Either::Right(bounds) => Self { bounds, fmt: None }, diff --git a/impl/src/lib.rs b/impl/src/lib.rs index ee23a642..99de541e 100644 --- a/impl/src/lib.rs +++ b/impl/src/lib.rs @@ -12,10 +12,14 @@ mod utils; #[cfg(feature = "as_ref")] mod r#as; +#[cfg(feature = "clone")] +mod clone; #[cfg(feature = "eq")] mod cmp; #[cfg(feature = "constructor")] mod constructor; +#[cfg(feature = "copy")] +mod copy; #[cfg(feature = "deref")] mod deref; #[cfg(feature = "deref_mut")] @@ -144,8 +148,12 @@ create_derive!( create_derive!("as_ref", r#as::r#mut, AsMut, as_mut_derive, as_mut); create_derive!("as_ref", r#as::r#ref, AsRef, as_ref_derive, as_ref); +create_derive!("clone", clone, Clone, clone_derive, clone); + create_derive!("constructor", constructor, Constructor, constructor_derive); +create_derive!("copy", copy, Copy, copy_derive, copy); + create_derive!("debug", fmt::debug, Debug, debug_derive, debug); create_derive!("deref", deref, Deref, deref_derive, deref); diff --git a/impl/src/utils.rs b/impl/src/utils.rs index 463e7dae..96d24f9a 100644 --- a/impl/src/utils.rs +++ b/impl/src/utils.rs @@ -16,6 +16,8 @@ use syn::{ feature = "add", feature = "add_assign", feature = "as_ref", + feature = "clone", + feature = "copy", feature = "debug", feature = "display", feature = "eq", @@ -44,6 +46,8 @@ pub(crate) use self::generics_search::GenericsSearch; feature = "add", feature = "add_assign", feature = "as_ref", + feature = "clone", + feature = "copy", feature = "debug", feature = "display", feature = "eq", @@ -1328,6 +1332,8 @@ pub fn is_type_parameter_used_in_type( feature = "as_ref", feature = "debug", feature = "display", + feature = "clone", + feature = "copy", feature = "eq", feature = "from", feature = "from_str", @@ -1404,6 +1410,8 @@ mod either { feature = "add", feature = "add_assign", feature = "as_ref", + feature = "clone", + feature = "copy", feature = "debug", feature = "display", feature = "eq", @@ -1508,6 +1516,8 @@ mod spanning { feature = "as_ref", feature = "debug", feature = "display", + feature = "clone", + feature = "copy", feature = "eq", feature = "from", feature = "from_str", @@ -1527,6 +1537,14 @@ pub(crate) mod attr { use super::{Either, Spanning}; + #[cfg(any( + feature = "debug", + feature = "display", + feature = "clone", + feature = "copy" + ))] + pub(crate) use self::bounds::Bounds; + #[cfg(any( feature = "as_ref", feature = "from", @@ -1693,6 +1711,87 @@ pub(crate) mod attr { } } + #[cfg(any( + feature = "debug", + feature = "display", + feature = "clone", + feature = "copy" + ))] + mod bounds { + use crate::utils::attr::ParseMultiple; + use syn::{ + parse::{Parse, ParseStream}, + punctuated::Punctuated, + spanned::Spanned as _, + token, + }; + + /// Representation of a `bound` macro attribute, expressing additional trait bounds. + /// + /// ```rust,ignore + /// #[(bound())] + /// #[(bounds())] + /// #[(where())] + /// ``` + #[derive(Debug, Default)] + pub struct Bounds(pub Punctuated); + + impl Parse for Bounds { + fn parse(input: ParseStream<'_>) -> syn::Result { + Self::check_legacy_fmt(input)?; + + let _ = input.parse::().and_then(|p| { + if ["bound", "bounds", "where"] + .into_iter() + .any(|i| p.is_ident(i)) + { + Ok(p) + } else { + Err(syn::Error::new( + p.span(), + "unknown attribute argument, expected `bound(...)`", + )) + } + })?; + + let content; + syn::parenthesized!(content in input); + + content + .parse_terminated(syn::WherePredicate::parse, token::Comma) + .map(Self) + } + } + + impl Bounds { + /// Errors in case legacy syntax is encountered: `bound = "..."`. + fn check_legacy_fmt(input: ParseStream<'_>) -> syn::Result<()> { + let fork = input.fork(); + + let path = fork + .parse::() + .and_then(|path| fork.parse::().map(|_| path)); + match path { + Ok(path) if path.is_ident("bound") => fork + .parse::() + .ok() + .and_then(|lit| match lit { + syn::Lit::Str(s) => Some(s.value()), + _ => None, + }) + .map_or(Ok(()), |bound| { + Err(syn::Error::new( + input.span(), + format!("legacy syntax, use `bound({bound})` instead"), + )) + }), + Ok(_) | Err(_) => Ok(()), + } + } + } + impl ParseMultiple for Bounds {} + } + #[cfg(any( feature = "as_ref", feature = "from", diff --git a/src/lib.rs b/src/lib.rs index 2ffd9a62..ecdee894 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -450,7 +450,9 @@ pub mod with_trait { feature = "add", feature = "add_assign", feature = "as_ref", + feature = "clone", feature = "constructor", + feature = "copy", feature = "debug", feature = "deref", feature = "deref_mut", diff --git a/tests/clone.rs b/tests/clone.rs new file mode 100644 index 00000000..d616c892 --- /dev/null +++ b/tests/clone.rs @@ -0,0 +1,124 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +mod structs { + + use derive_more::Clone; + #[test] + fn reference_to_generic_type() { + struct NotClone; + + #[derive(Clone)] + struct ReferenceToGenericType<'a, T>(&'a T); + + let should_be_clonable = ReferenceToGenericType(&NotClone); + let clone = should_be_clonable.clone(); + + assert!(core::ptr::eq(should_be_clonable.0, clone.0)) + } + + #[test] + fn manual_bound() { + trait Foo { + type Bar; + } + + #[derive(Clone)] + #[clone(bound(T::Bar: Clone))] + struct Baz(T::Bar); + + // intentionally not clone + struct FooImpl; + + impl Foo for FooImpl { + type Bar = i32; + } + + let baz: Baz = Baz(42); + let clone = baz.clone(); + assert_eq!(baz.0, clone.0); + } +} + +mod enums { + use derive_more::Clone; + + #[test] + fn simple_enum() { + #[derive(Clone, PartialEq, Debug)] + enum Simple { + A, + B, + C, + } + + let a = Simple::A; + let clone = a.clone(); + assert_eq!(a, clone); + } + + #[test] + fn enum_with_data() { + #[derive(Clone, PartialEq, Debug)] + enum WithData { + Unit, + Tuple(i32, &'static str), + Struct { x: i32, y: i32 }, + } + + let tuple = WithData::Tuple(42, "hello"); + let clone = tuple.clone(); + assert_eq!(tuple, clone); + + let struct_variant = WithData::Struct { x: 1, y: 2 }; + let clone = struct_variant.clone(); + assert_eq!(struct_variant, clone); + } + + #[test] + fn reference_to_generic_type() { + struct NotClone; + + #[derive(Clone)] + enum ReferenceToGenericType<'a, T> { + Ref(&'a T), + None, + } + + let should_be_clonable = ReferenceToGenericType::Ref(&NotClone); + let clone = should_be_clonable.clone(); + + match (&should_be_clonable, &clone) { + (ReferenceToGenericType::Ref(a), ReferenceToGenericType::Ref(b)) => { + assert!(core::ptr::eq(*a, *b)); + } + _ => panic!("unexpected variant"), + } + } + + #[test] + fn manual_bound() { + trait Foo { + type Bar; + } + + #[derive(Clone)] + #[clone(bound(T::Bar: Clone))] + enum Baz { + Value(T::Bar), + Empty, + } + + struct FooImpl; + + impl Foo for FooImpl { + type Bar = i32; + } + + let baz: Baz = Baz::Value(42); + let clone = baz.clone(); + match (baz, clone) { + (Baz::Value(a), Baz::Value(b)) => assert_eq!(a, b), + _ => panic!("unexpected variant"), + } + } +} diff --git a/tests/compile_fail/clone/union.rs b/tests/compile_fail/clone/union.rs new file mode 100644 index 00000000..185bcd79 --- /dev/null +++ b/tests/compile_fail/clone/union.rs @@ -0,0 +1,6 @@ +#[derive(derive_more::Clone)] +union MyUnion { + i: u32, +} + +fn main() {} \ No newline at end of file diff --git a/tests/compile_fail/clone/union.stderr b/tests/compile_fail/clone/union.stderr new file mode 100644 index 00000000..2aecd595 --- /dev/null +++ b/tests/compile_fail/clone/union.stderr @@ -0,0 +1,5 @@ +error: Clone cannot be derived for unions + --> tests/compile_fail/clone/union.rs:2:1 + | +2 | union MyUnion { + | ^^^^^ diff --git a/tests/copy.rs b/tests/copy.rs new file mode 100644 index 00000000..d1da788d --- /dev/null +++ b/tests/copy.rs @@ -0,0 +1,151 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +mod structs { + use derive_more::{Clone, Copy}; + + #[test] + fn simple_struct() { + #[derive(Clone, Copy, PartialEq, Debug)] + struct Simple(i32); + + let a = Simple(42); + let b = a; + assert_eq!(a, b); + } + + #[test] + fn reference_to_generic_type() { + struct NotCopy; + + #[derive(Clone, Copy)] + struct ReferenceToGenericType<'a, T>(&'a T); + + let not_copy = NotCopy; + let should_be_copyable = ReferenceToGenericType(¬_copy); + let copy = should_be_copyable; + + assert!(core::ptr::eq(should_be_copyable.0, copy.0)) + } + + #[test] + fn manual_bound() { + trait Foo { + type Bar; + } + + #[derive(Clone, Copy)] + #[copy(bound(T::Bar: Copy))] + #[clone(bound(T::Bar: Clone))] + struct Baz(T::Bar); + + struct FooImpl; + + impl Foo for FooImpl { + type Bar = i32; + } + + let baz: Baz = Baz(42); + let copy = baz; + assert_eq!(baz.0, copy.0); + } + + #[test] + fn multiple_fields() { + #[derive(Clone, Copy, PartialEq, Debug)] + struct MultipleFields { + x: i32, + y: i64, + z: u8, + } + + let a = MultipleFields { x: 1, y: 2, z: 3 }; + let b = a; + assert_eq!(a, b); + } +} + +mod enums { + use derive_more::{Clone, Copy}; + + #[test] + fn simple_enum() { + #[derive(Clone, Copy, PartialEq, Debug)] + enum Simple { + A, + B, + C, + } + + let a = Simple::A; + let b = a; + assert_eq!(a, b); + } + + #[test] + fn enum_with_data() { + #[derive(Clone, Copy, PartialEq, Debug)] + enum WithData { + Unit, + Tuple(i32, i64), + Struct { x: i32, y: i32 }, + } + + let tuple = WithData::Tuple(42, 100); + let copy = tuple; + assert_eq!(tuple, copy); + + let struct_variant = WithData::Struct { x: 1, y: 2 }; + let copy = struct_variant; + assert_eq!(struct_variant, copy); + } + + #[test] + fn reference_to_generic_type() { + struct NotCopy; + + #[derive(Clone, Copy)] + enum ReferenceToGenericType<'a, T> { + Ref(&'a T), + None, + } + + let not_copy = NotCopy; + let should_be_copyable = ReferenceToGenericType::Ref(¬_copy); + let copy = should_be_copyable; + + match (&should_be_copyable, ©) { + (ReferenceToGenericType::Ref(a), ReferenceToGenericType::Ref(b)) => { + assert!(core::ptr::eq(*a, *b)); + } + _ => panic!("unexpected variant"), + } + } + + #[test] + fn manual_bound() { + trait Foo { + type Bar; + } + + #[derive(Clone, Copy)] + #[copy(bound(T::Bar: Copy))] + #[clone(bound(T::Bar: Clone))] + enum Baz { + Value(T::Bar), + Empty, + } + + struct FooImpl; + + impl Foo for FooImpl { + type Bar = i32; + } + + let baz: Baz = Baz::Value(42); + let copy = baz; + match (baz, copy) { + (Baz::Value(a), Baz::Value(b)) => assert_eq!(a, b), + _ => panic!("unexpected variant"), + } + } +}