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..356e8334 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,17 @@ 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)) +- Add `Default` derive, similar to `std`'s one, but not adding any trait bounds, + the ability to specify custom default values for fields, using non-unit enum + variants as default values and set custom bounds. + ([#534](https://github.com/JelteF/derive_more/pull/534)) + ### Fixed - Mistakenly generated code for `owned` type in `TryInto`, `Unwrap` and `TryUnwrap` diff --git a/Cargo.toml b/Cargo.toml index 300ed19e..f6bdde7b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,8 +56,11 @@ 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"] +default_derive = ["derive_more-impl/default_derive"] # can't use `default` as a feature name as it's reserved deref = ["derive_more-impl/deref"] deref_mut = ["derive_more-impl/deref_mut"] display = ["derive_more-impl/display"] @@ -84,8 +87,11 @@ full = [ "add", "add_assign", "as_ref", + "clone", "constructor", + "copy", "debug", + "default_derive", "deref", "deref_mut", "display", @@ -135,16 +141,31 @@ 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" required-features = ["debug"] +[[test]] +name = "default" +path = "tests/default.rs" +required-features = ["default_derive"] + [[test]] name = "deref" path = "tests/deref.rs" diff --git a/README.md b/README.md index 0bd97d97..cd5fde58 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,12 @@ These are traits that can be used for operator overloading. `ShrAssign` and `ShlAssign` 11. [`Eq`], [`PartialEq`] +### Other traits + +1. [`Clone`] +2. [`Copy`] +3. [`Default`] + ### Static methods @@ -264,6 +270,10 @@ 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 +[`Default`]: https://docs.rs/derive_more/latest/derive_more/derive.Default.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..97745176 100644 --- a/impl/Cargo.toml +++ b/impl/Cargo.toml @@ -52,8 +52,11 @@ 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"] +default_derive = ["syn/extra-traits"] # can't use `default` as a feature name as it's reserved deref = [] deref_mut = [] display = ["syn/extra-traits", "dep:unicode-ident", "dep:convert_case"] @@ -79,8 +82,11 @@ full = [ "add", "add_assign", "as_ref", + "clone", "constructor", + "copy", "debug", + "default_derive", "deref", "deref_mut", "display", 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/doc/default_derive.md b/impl/doc/default_derive.md new file mode 100644 index 00000000..8bd6a1f9 --- /dev/null +++ b/impl/doc/default_derive.md @@ -0,0 +1,143 @@ +# Using `#[derive(Default)]` + +Deriving `Default` for structs/enums works similarly to the one in `std`, +by using the default value for all fields, but, in contrast: +1. Does not constrain generic parameters (custom bounds can be specified with `#[default(bound(...))]` attribute). +2. Allows specifying custom default values for fields using `#[default(...)]`. +3. For enums, the variant annotated with `#[default]` does not need to be a unit variant. + +**Note: due to the `default` feature being used for default features of crates, this derive is exposed behind the `derive_default` feature.** + +## Example usage + +```rust +use derive_more::Default; + +#[derive(Default, PartialEq, Debug)] +struct Simple { + field: i32, +} + +assert_eq!(Simple::default(), Simple { field: 0 }); +``` + +### Custom default values + +You can specify custom default values for fields using `#[default(...)]`: + +```rust +use derive_more::Default; + +#[derive(Default, PartialEq, Debug)] +struct CustomDefaults { + #[default(42)] + field1: i32, + #[default(Some("hello"))] + field2: Option<&'static str>, +} + +assert_eq!( + CustomDefaults::default(), + CustomDefaults { field1: 42, field2: Some("hello") } +); +``` + +### Enums + +For enums, you must mark the default variant with `#[default]`: + +```rust +use derive_more::Default; + +#[derive(Default, PartialEq, Debug)] +enum MyEnum { + Variant1, + #[default] + Variant2 { field: i32 }, +} + +assert_eq!(MyEnum::default(), MyEnum::Variant2 { field: 0 }); +``` + +The std derive would only let you annotate the first variant with `#[default]` as it requires unit variant. + +### No constraints on generic bounds by default + +```rust +use derive_more::Default; + +struct NotDefault; + +#[derive(Default)] +struct GenericType(Option); + +let instance: GenericType = GenericType::default(); +assert!(instance.0.is_none()); +``` + +This generates code equivalent to: +```rust +# struct NotDefault; +# struct GenericType(Option); + +impl Default for GenericType { + fn default() -> Self { + Self(core::default::Default::default()) + } +} +``` + +The derive from std would have put a `T: Default` bound on the impl. This is unnecessary when the field type (`Option`) already implements `Default` regardless of `T`. + +### Custom trait bounds + +Sometimes you may want to specify custom trait bounds on your generic type parameters. +This can be done with a `#[default(bound(...))]` attribute. + +`#[default(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::Default; + +trait Foo { + type Bar; +} + +#[derive(Default, PartialEq, Debug)] +#[default(bound(T::Bar: Default))] +struct Baz { + field: T::Bar, +} + +// FooImpl doesn't implement Default, but that's fine +// because only T::Bar needs to be Default +struct FooImpl; + +impl Foo for FooImpl { + type Bar = i32; +} + +let baz: Baz = Baz::default(); +assert_eq!(baz.field, 0); +``` + +This generates code equivalent to: +```rust +# trait Foo { type Bar; } +# struct FooImpl; +# impl Foo for FooImpl { type Bar = i32; } +# +# struct Baz { field: T::Bar } + +impl Default for Baz +where + T::Bar: Default, // specified via `#[default(bound(...))]` +{ + fn default() -> Self { + Self { + field: core::default::Default::default(), + } + } +} +``` 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/default.rs b/impl/src/default.rs new file mode 100644 index 00000000..de5cab56 --- /dev/null +++ b/impl/src/default.rs @@ -0,0 +1,93 @@ +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!("default"); + 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 default_body = match &input.data { + Data::Struct(data) => default(&data.fields, None), + Data::Enum(data) => { + let default_variant = data + .variants + .iter() + .find(|v| v.attrs.iter().any(|a| a.path().is_ident("default"))) + .ok_or_else(|| { + syn::Error::new_spanned( + &input.ident, + "one variant must be marked with #[default]", + ) + })?; + + let default_attr = default_variant + .attrs + .iter() + .find(|a| a.path().is_ident("default")) + .expect("just checked existence"); + + if !matches!(default_attr.meta, syn::Meta::Path(_)) { + return Err(syn::Error::new_spanned( + default_attr, + "#[default] on variant must not have arguments", + )); + } + + let variant_ident = &default_variant.ident; + default(&default_variant.fields, Some(quote! { ::#variant_ident })) + } + 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::default::Default for #name #ty_generics #real_where_clause { + fn default() -> Self { + #default_body + } + } + }) +} + +fn default(fields: &Fields, variant: Option) -> TokenStream { + match fields { + Fields::Named(named) => { + let defaults = named.named.iter().map(|f| { + let name = &f.ident; + let value = field_default_value(f); + quote! { #name: #value } + }); + quote! { Self #variant { #(#defaults),* } } + } + Fields::Unnamed(unnamed) => { + let defaults = unnamed.unnamed.iter().map(field_default_value); + quote! { Self #variant (#(#defaults),*) } + } + Fields::Unit => quote! { Self #variant }, + } +} + +fn field_default_value(field: &syn::Field) -> TokenStream { + for attr in &field.attrs { + if attr.path().is_ident("default") { + if let syn::Meta::List(meta_list) = &attr.meta { + return meta_list.tokens.clone(); + } + } + } + quote! { derive_more::core::default::Default::default() } +} 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..25c21091 100644 --- a/impl/src/lib.rs +++ b/impl/src/lib.rs @@ -12,10 +12,16 @@ 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 = "default_derive")] +mod default; #[cfg(feature = "deref")] mod deref; #[cfg(feature = "deref_mut")] @@ -144,10 +150,16 @@ 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!("default_derive", default, Default, default_derive, default); + create_derive!("deref", deref, Deref, deref_derive, deref); create_derive!( diff --git a/impl/src/utils.rs b/impl/src/utils.rs index 463e7dae..f3577c79 100644 --- a/impl/src/utils.rs +++ b/impl/src/utils.rs @@ -16,7 +16,10 @@ use syn::{ feature = "add", feature = "add_assign", feature = "as_ref", + feature = "clone", + feature = "copy", feature = "debug", + feature = "default_derive", feature = "display", feature = "eq", feature = "from", @@ -44,7 +47,10 @@ pub(crate) use self::generics_search::GenericsSearch; feature = "add", feature = "add_assign", feature = "as_ref", + feature = "clone", + feature = "copy", feature = "debug", + feature = "default_derive", feature = "display", feature = "eq", feature = "from", @@ -1327,7 +1333,10 @@ pub fn is_type_parameter_used_in_type( feature = "add_assign", feature = "as_ref", feature = "debug", + feature = "default_derive", feature = "display", + feature = "clone", + feature = "copy", feature = "eq", feature = "from", feature = "from_str", @@ -1404,7 +1413,10 @@ mod either { feature = "add", feature = "add_assign", feature = "as_ref", + feature = "clone", + feature = "copy", feature = "debug", + feature = "default_derive", feature = "display", feature = "eq", feature = "from", @@ -1507,7 +1519,10 @@ mod spanning { feature = "add_assign", feature = "as_ref", feature = "debug", + feature = "default_derive", feature = "display", + feature = "clone", + feature = "copy", feature = "eq", feature = "from", feature = "from_str", @@ -1527,6 +1542,15 @@ pub(crate) mod attr { use super::{Either, Spanning}; + #[cfg(any( + feature = "debug", + feature = "default_derive", + feature = "display", + feature = "clone", + feature = "copy" + ))] + pub(crate) use self::bounds::Bounds; + #[cfg(any( feature = "as_ref", feature = "from", @@ -1693,6 +1717,88 @@ pub(crate) mod attr { } } + #[cfg(any( + feature = "debug", + feature = "default_derive", + 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..f775c219 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -450,8 +450,11 @@ pub mod with_trait { feature = "add", feature = "add_assign", feature = "as_ref", + feature = "clone", feature = "constructor", + feature = "copy", feature = "debug", + feature = "default_derive", feature = "deref", feature = "deref_mut", feature = "display", 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/compile_fail/default/extra_params_for_default_enum.rs b/tests/compile_fail/default/extra_params_for_default_enum.rs new file mode 100644 index 00000000..f0e9e182 --- /dev/null +++ b/tests/compile_fail/default/extra_params_for_default_enum.rs @@ -0,0 +1,4 @@ +#[derive(derive_more::Default)] +enum Foo { #[default(nope)] Bar, Baz} + +fn main() {} \ No newline at end of file diff --git a/tests/compile_fail/default/extra_params_for_default_enum.stderr b/tests/compile_fail/default/extra_params_for_default_enum.stderr new file mode 100644 index 00000000..2dc3a626 --- /dev/null +++ b/tests/compile_fail/default/extra_params_for_default_enum.stderr @@ -0,0 +1,5 @@ +error: #[default] on variant must not have arguments + --> tests/compile_fail/default/extra_params_for_default_enum.rs:2:12 + | +2 | enum Foo { #[default(nope)] Bar, Baz} + | ^^^^^^^^^^^^^^^^ diff --git a/tests/compile_fail/default/no_default_variant_for_enum.rs b/tests/compile_fail/default/no_default_variant_for_enum.rs new file mode 100644 index 00000000..4f8aa8f3 --- /dev/null +++ b/tests/compile_fail/default/no_default_variant_for_enum.rs @@ -0,0 +1,4 @@ +#[derive(derive_more::Default)] +enum Foo { Bar, Baz} + +fn main() {} \ No newline at end of file diff --git a/tests/compile_fail/default/no_default_variant_for_enum.stderr b/tests/compile_fail/default/no_default_variant_for_enum.stderr new file mode 100644 index 00000000..2a598374 --- /dev/null +++ b/tests/compile_fail/default/no_default_variant_for_enum.stderr @@ -0,0 +1,5 @@ +error: one variant must be marked with #[default] + --> tests/compile_fail/default/no_default_variant_for_enum.rs:2:6 + | +2 | enum Foo { Bar, Baz} + | ^^^ diff --git a/tests/compile_fail/default/union.rs b/tests/compile_fail/default/union.rs new file mode 100644 index 00000000..c771cafc --- /dev/null +++ b/tests/compile_fail/default/union.rs @@ -0,0 +1,6 @@ +#[derive(derive_more::Default)] +union MyUnion { + i: u32, +} + +fn main() {} \ No newline at end of file diff --git a/tests/compile_fail/default/union.stderr b/tests/compile_fail/default/union.stderr new file mode 100644 index 00000000..da1b06c4 --- /dev/null +++ b/tests/compile_fail/default/union.stderr @@ -0,0 +1,5 @@ +error: Clone cannot be derived for unions + --> tests/compile_fail/default/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"), + } + } +} diff --git a/tests/default.rs b/tests/default.rs new file mode 100644 index 00000000..7a02abdf --- /dev/null +++ b/tests/default.rs @@ -0,0 +1,325 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +mod structs { + use derive_more::Default; + + #[test] + fn tuple_struct() { + #[derive(PartialEq, Debug)] + struct NonDefault; + #[derive(Default, PartialEq, Debug)] + struct TupleStruct(i32, Option); + + assert_eq!(TupleStruct::default(), TupleStruct(0, None)) + } + + #[test] + fn named_fields_struct() { + #[derive(PartialEq, Debug)] + struct NonDefault; + #[derive(Default, PartialEq, Debug)] + struct NamedFieldsStruct { + field1: i32, + field2: Option, + } + + assert_eq!( + NamedFieldsStruct::default(), + NamedFieldsStruct { + field1: 0, + field2: None + } + ) + } + + #[test] + fn tuple_struct_with_default_attr() { + #[derive(PartialEq, Debug)] + struct NonDefault; + #[derive(Default, PartialEq, Debug)] + struct TupleStruct( + #[default(42)] i32, + #[default(Some(NonDefault))] Option, + #[default(Self::default_field3())] i32, + ); + impl TupleStruct { + fn default_field3() -> i32 { + 73 + } + } + + assert_eq!( + TupleStruct::default(), + TupleStruct(42, Some(NonDefault), 73) + ) + } + + #[test] + fn named_fields_struct_with_default_attr() { + #[derive(PartialEq, Debug)] + struct NonDefault; + #[derive(Default, PartialEq, Debug)] + struct NamedFieldsStruct { + #[default(42)] + field1: i32, + #[default(Some(NonDefault))] + field2: Option, + #[default(Self::default_field3())] + field3: i32, + } + impl NamedFieldsStruct { + fn default_field3() -> i32 { + 73 + } + } + + assert_eq!( + NamedFieldsStruct::default(), + NamedFieldsStruct { + field1: 42, + field2: Some(NonDefault), + field3: 73, + } + ) + } + + #[test] + fn generic_tuple_struct() { + #[derive(PartialEq, Debug)] + struct NonDefault; + #[derive(Default, PartialEq, Debug)] + struct GenericTupleStruct(u32, Option); + + assert_eq!( + GenericTupleStruct::::default(), + GenericTupleStruct(0, None) + ) + } + + #[test] + fn generic_named_fields_struct() { + #[derive(PartialEq, Debug)] + struct NonDefault; + #[derive(Default, PartialEq, Debug)] + struct GenericNamedFieldsStruct { + field1: u32, + field2: Option, + } + + assert_eq!( + GenericNamedFieldsStruct::::default(), + GenericNamedFieldsStruct { + field1: 0, + field2: None + } + ) + } + + #[test] + fn generic_manual_bounds() { + trait MyTrait { + type Assoc; + } + + #[derive(PartialEq, Debug)] + struct NonDefault; + + impl MyTrait for NonDefault { + type Assoc = u32; + } + + #[derive(Default, PartialEq, Debug)] + #[default(bound(T::Assoc: Default))] + struct GenericWithBound { + field: T::Assoc, + } + + assert_eq!( + GenericWithBound::::default(), + GenericWithBound:: { field: 0 } + ) + } +} + +mod enums { + use derive_more::Default; + #[test] + fn unit_variants() { + #[derive(Default, PartialEq, Debug)] + enum UnitVariants { + #[allow(unused)] + Variant1, + #[default] + Variant2, + #[allow(unused)] + Variant3, + } + assert_eq!(UnitVariants::default(), UnitVariants::Variant2) + } + + #[test] + fn tuple_variants() { + #[derive(Default, PartialEq, Debug)] + enum TupleVariants { + #[allow(unused)] + Variant1(i32), + #[default] + Variant2(Option), + #[allow(unused)] + Variant3, + } + assert_eq!(TupleVariants::default(), TupleVariants::Variant2(None)) + } + + #[test] + fn fields_variants() { + #[derive(Default, PartialEq, Debug)] + enum FieldsVariants { + #[allow(unused)] + Variant1 { field: u32 }, + #[default] + Variant2 { field: Option }, + #[allow(unused)] + Variant3, + } + + assert_eq!( + FieldsVariants::default(), + FieldsVariants::Variant2 { field: None } + ) + } + + #[test] + fn tuple_variants_with_default_attr() { + #[derive(PartialEq, Debug)] + struct NonDefault; + #[derive(Default, PartialEq, Debug)] + enum TupleVariantsWithDefault { + #[allow(unused)] + Variant1(i32), + #[default] + Variant2( + #[default(42)] i32, + #[default(Some(NonDefault))] Option, + #[default(Self::default_field())] i32, + ), + #[allow(unused)] + Variant3, + } + impl TupleVariantsWithDefault { + fn default_field() -> i32 { + 73 + } + } + + assert_eq!( + TupleVariantsWithDefault::default(), + TupleVariantsWithDefault::Variant2(42, Some(NonDefault), 73) + ) + } + + #[test] + fn fields_variants_with_default_attr() { + #[derive(PartialEq, Debug)] + struct NonDefault; + #[derive(Default, PartialEq, Debug)] + enum FieldsVariantsWithDefault { + #[allow(unused)] + Variant1 { field: u32 }, + #[default] + Variant2 { + #[default(42)] + field1: i32, + #[default(Some(NonDefault))] + field2: Option, + #[default(Self::default_field())] + field3: i32, + }, + #[allow(unused)] + Variant3, + } + impl FieldsVariantsWithDefault { + fn default_field() -> i32 { + 73 + } + } + + assert_eq!( + FieldsVariantsWithDefault::default(), + FieldsVariantsWithDefault::Variant2 { + field1: 42, + field2: Some(NonDefault), + field3: 73, + } + ) + } + + #[test] + fn generic_tuple_variants() { + #[derive(PartialEq, Debug)] + + struct NonDefault; + #[derive(Default, PartialEq, Debug)] + enum GenericTupleVariants { + #[allow(unused)] + Variant1(T), + #[default] + Variant2(Option), + #[allow(unused)] + Variant3, + } + + assert_eq!( + GenericTupleVariants::::default(), + GenericTupleVariants::Variant2(None) + ) + } + + #[test] + fn generic_fields_variants() { + #[derive(PartialEq, Debug)] + + struct NonDefault; + #[derive(Default, PartialEq, Debug)] + enum GenericFieldsVariants { + #[allow(unused)] + Variant1 { field: T }, + #[default] + Variant2 { field: Option }, + #[allow(unused)] + Variant3, + } + assert_eq!( + GenericFieldsVariants::::default(), + GenericFieldsVariants::Variant2 { field: None } + ) + } + + #[test] + fn generic_manual_bounds_enum() { + trait MyTrait { + type Assoc; + } + + #[derive(PartialEq, Debug)] + struct NonDefault; + + impl MyTrait for NonDefault { + type Assoc = u32; + } + + #[derive(Default, Debug, PartialEq)] + #[default(bound(T::Assoc: Default))] + enum GenericEnumWithBound { + #[default] + Variant { field: T::Assoc }, + #[allow(unused)] + Other, + } + + assert_eq!( + GenericEnumWithBound::::default(), + GenericEnumWithBound::::Variant { field: 0 } + ); + } +}