diff --git a/CHANGELOG.md b/CHANGELOG.md index f5992350..2f20b1c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ([#459](https://github.com/JelteF/derive_more/pull/459)) - Support structs with no fields in `FromStr` derive. ([#469](https://github.com/JelteF/derive_more/pull/469)) +- Support `#[skip]` attribute for the `Add` derive to allow skipping zero sized + fields. ([#472](https://github.com/JelteF/derive_more/pull/472)) ### Changed diff --git a/impl/doc/add.md b/impl/doc/add.md index 5f8e12ad..bbb0d90c 100644 --- a/impl/doc/add.md +++ b/impl/doc/add.md @@ -76,6 +76,29 @@ The behaviour is similar for more or less fields. +## Skipping fields + +Sometimes the struct needs to hold a zero sized field, most commonly +`PhantomData`. A field containing a ZST can be skipped using the `#[add(skip)]` +attribute like this: + +```rust +# use core::marker::PhantomData; +# +#[derive(Add)] +struct TupleWithZst(i32, #[add(skip)] PhantomData); + +#[derive(Add)] +struct StructWithZst { + x: i32, + #[add(skip)] + _marker: PhantomData, +} +``` + + + + ## Enums There's a big difference between the code that is generated for the two struct diff --git a/impl/doc/add_assign.md b/impl/doc/add_assign.md index d52fe252..1c78a224 100644 --- a/impl/doc/add_assign.md +++ b/impl/doc/add_assign.md @@ -69,6 +69,29 @@ The behaviour is similar with more or less fields. +## Skipping fields + +Sometimes the struct needs to hold a zero sized field, most commonly +`PhantomData`. A field containing a ZST can be skipped using the +`#[add_assign(skip)]` attribute like this: + +```rust +# use core::marker::PhantomData; +# +#[derive(AddAssign)] +struct TupleWithZst(i32, #[add_assign(skip)] PhantomData); + +#[derive(AddAssign)] +struct StructWithZst { + x: i32, + #[add_assign(skip)] + _marker: PhantomData, +} +``` + + + + ## Enums Deriving `AddAssign` is not (yet) supported for enums. diff --git a/impl/doc/mul.md b/impl/doc/mul.md index b3a9fbea..27a8b7c0 100644 --- a/impl/doc/mul.md +++ b/impl/doc/mul.md @@ -141,6 +141,31 @@ impl<__RhsT: Copy> derive_more::core::ops::Mul<__RhsT> for Point2D +## Skipping fields + +Struct marked with `#[mul(forward)]` can hold zero sized fields, most commonly +`PhantomData`. A field containing a ZST can be skipped using the +`#[mul(skip)]` attribute like this + +```rust +# use core::marker::PhantomData; +# +#[derive(Mul)] +#[mul(forward)] +struct TupleWithZst(i32, #[mul(skip)] PhantomData); + +#[derive(Mul)] +#[mul(forward)] +struct StructWithZst { + x: i32, + #[mul(skip)] + _marker: PhantomData, +} +``` + + + + ## Enums Deriving `Mul` for enums is not (yet) supported, except when you use diff --git a/impl/doc/mul_assign.md b/impl/doc/mul_assign.md index ea88b2e3..3fea24c4 100644 --- a/impl/doc/mul_assign.md +++ b/impl/doc/mul_assign.md @@ -81,6 +81,31 @@ field. +## Skipping fields + +Struct marked with `#[mul_assign(forward)]` can hold zero sized fields, most +commonly `PhantomData`. A field containing a ZST can be skipped using the +`#[mul_assign(skip)]` attribute like this + +```rust +# use core::marker::PhantomData; +# +#[derive(MulAssign)] +#[mul_assign(forward)] +struct TupleWithZst(i32, #[mul_assign(skip)] PhantomData); + +#[derive(MulAssign)] +#[mul_assign(forward)] +struct StructWithZst { + x: i32, + #[mul_assign(skip)] + _marker: PhantomData, +} +``` + + + + ## Enums Deriving `MulAssign` for enums is not (yet) supported. diff --git a/impl/src/add_assign_like.rs b/impl/src/add_assign_like.rs index 79d3763a..8b4313d2 100644 --- a/impl/src/add_assign_like.rs +++ b/impl/src/add_assign_like.rs @@ -1,25 +1,24 @@ -use crate::add_helpers::{struct_exprs, tuple_exprs}; -use crate::utils::{add_extra_ty_param_bound_op, named_to_vec, unnamed_to_vec}; +use crate::{ + add_helpers::{struct_assign_exprs, tuple_assign_exprs}, + utils::{add_extra_ty_param_bound_op_except, named_to_vec, unnamed_to_vec}, +}; use proc_macro2::TokenStream; use quote::{format_ident, quote}; use syn::{Data, DeriveInput, Fields}; -pub fn expand(input: &DeriveInput, trait_name: &str) -> TokenStream { +pub fn expand(input: &DeriveInput, trait_name: &str) -> syn::Result { let trait_ident = format_ident!("{trait_name}"); let method_name = trait_name.trim_end_matches("Assign").to_lowercase(); let method_ident = format_ident!("{method_name}_assign"); let input_type = &input.ident; - let generics = add_extra_ty_param_bound_op(&input.generics, &trait_ident); - let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); - - let exprs = match input.data { + let (exprs, zst_generics) = match input.data { Data::Struct(ref data_struct) => match data_struct.fields { Fields::Unnamed(ref fields) => { - tuple_exprs(&unnamed_to_vec(fields), &method_ident) + tuple_assign_exprs(&unnamed_to_vec(fields), &method_ident)? } Fields::Named(ref fields) => { - struct_exprs(&named_to_vec(fields), &method_ident) + struct_assign_exprs(&named_to_vec(fields), &method_ident)? } _ => panic!("Unit structs cannot use derive({trait_name})"), }, @@ -27,7 +26,11 @@ pub fn expand(input: &DeriveInput, trait_name: &str) -> TokenStream { _ => panic!("Only structs can use derive({trait_name})"), }; - quote! { + let generics = + add_extra_ty_param_bound_op_except(&input.generics, &trait_ident, zst_generics); + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + Ok(quote! { #[automatically_derived] impl #impl_generics derive_more::core::ops::#trait_ident for #input_type #ty_generics #where_clause { @@ -37,5 +40,5 @@ pub fn expand(input: &DeriveInput, trait_name: &str) -> TokenStream { #( #exprs; )* } } - } + }) } diff --git a/impl/src/add_helpers.rs b/impl/src/add_helpers.rs index b9cee1a6..948675db 100644 --- a/impl/src/add_helpers.rs +++ b/impl/src/add_helpers.rs @@ -1,28 +1,140 @@ +use std::ops::BitOr; + use proc_macro2::TokenStream; use quote::quote; -use syn::{Field, Ident, Index}; +use syn::{Field, Ident, Index, PathArguments}; + +use crate::utils::{ + attr::{ParseMultiple, Skip}, + extract_idents_from_generic_arguments, HashSet, +}; -pub fn tuple_exprs(fields: &[&Field], method_ident: &Ident) -> Vec { +#[cfg(any(feature = "add", feature = "mul",))] +pub fn tuple_exprs( + fields: &[&Field], + method_ident: &Ident, +) -> syn::Result<(Vec, HashSet)> { let mut exprs = vec![]; + let mut zst_generics = HashSet::default(); - for i in 0..fields.len() { - let i = Index::from(i); - // generates `self.0.add(rhs.0)` - let expr = quote! { self.#i.#method_ident(rhs.#i) }; + for (i, field) in fields.iter().enumerate() { + let index = Index::from(i); + let expr = match Skip::parse_attrs(&field.attrs, method_ident)? { + Some(_) => match &field.ty { + syn::Type::Path(path) => { + let mut ty = path.path.segments.clone(); + if let PathArguments::AngleBracketed(args) = + &ty.last_mut().unwrap().arguments + { + let extracted = + extract_idents_from_generic_arguments(&args.args); + zst_generics = zst_generics.bitor(&extracted); + } + ty.last_mut().unwrap().arguments = syn::PathArguments::None; + quote! { #ty } + } + ty => quote! { #ty }, + }, + // generates `self.0.add(rhs.0)` for fields not marked with `#[skip]` + None => quote! { self.#index.#method_ident(rhs.#index) }, + }; exprs.push(expr); } - exprs + Ok((exprs, zst_generics)) } -pub fn struct_exprs(fields: &[&Field], method_ident: &Ident) -> Vec { +#[cfg(any(feature = "add", feature = "mul",))] +pub fn struct_exprs( + fields: &[&Field], + method_ident: &Ident, +) -> syn::Result<(Vec, HashSet)> { let mut exprs = vec![]; + let mut zst_generics = HashSet::default(); for field in fields { // It's safe to unwrap because struct fields always have an identifier let field_id = field.ident.as_ref().unwrap(); - // generates `x: self.x.add(rhs.x)` - let expr = quote! { self.#field_id.#method_ident(rhs.#field_id) }; + let expr = match Skip::parse_attrs(&field.attrs, method_ident)? { + Some(_) => match &field.ty { + syn::Type::Path(path) => { + let mut ty = path.path.segments.clone(); + if let PathArguments::AngleBracketed(args) = + &ty.last_mut().unwrap().arguments + { + let extracted = + extract_idents_from_generic_arguments(&args.args); + zst_generics = zst_generics.bitor(&extracted); + } + ty.last_mut().unwrap().arguments = syn::PathArguments::None; + quote! { #ty } + } + ty => quote! { #ty }, + }, + // generates `self.x.add(rhs.x)` for fields not marked with `#[skip]` + None => quote! { self.#field_id.#method_ident(rhs.#field_id) }, + }; exprs.push(expr) } - exprs + Ok((exprs, zst_generics)) +} + +#[cfg(any(feature = "add_assign", feature = "mul_assign",))] +pub fn tuple_assign_exprs( + fields: &[&Field], + method_ident: &Ident, +) -> syn::Result<(Vec, HashSet)> { + let mut exprs = vec![]; + let mut zst_generics = HashSet::default(); + + for (i, field) in fields.iter().enumerate() { + let index = Index::from(i); + match Skip::parse_attrs(&field.attrs, method_ident)? { + Some(_) => { + if let syn::Type::Path(path) = &field.ty { + let mut ty = path.path.segments.clone(); + if let PathArguments::AngleBracketed(args) = + &ty.last_mut().unwrap().arguments + { + let extracted = + extract_idents_from_generic_arguments(&args.args); + zst_generics = zst_generics.bitor(&extracted); + } + } + } + // generates `self.0.add_assign(rhs.0)` for fields not marked with `#[skip]` + None => exprs.push(quote! { self.#index.#method_ident(rhs.#index) }), + } + } + Ok((exprs, zst_generics)) +} + +#[cfg(any(feature = "add_assign", feature = "mul_assign",))] +pub fn struct_assign_exprs( + fields: &[&Field], + method_ident: &Ident, +) -> syn::Result<(Vec, HashSet)> { + let mut exprs = vec![]; + let mut zst_generics = HashSet::default(); + + for field in fields { + // It's safe to unwrap because struct fields always have an identifier + let field_id = field.ident.as_ref().unwrap(); + match Skip::parse_attrs(&field.attrs, method_ident)? { + Some(_) => { + if let syn::Type::Path(path) = &field.ty { + let mut ty = path.path.segments.clone(); + if let PathArguments::AngleBracketed(args) = + &ty.last_mut().unwrap().arguments + { + let extracted = + extract_idents_from_generic_arguments(&args.args); + zst_generics = zst_generics.bitor(&extracted); + } + } + } + // generates `self.x.add_assign(rhs.x)` for fields not marked with `#[skip]` + None => exprs.push(quote! { self.#field_id.#method_ident(rhs.#field_id) }), + } + } + Ok((exprs, zst_generics)) } diff --git a/impl/src/add_like.rs b/impl/src/add_like.rs index 0c6673b1..784924f8 100644 --- a/impl/src/add_like.rs +++ b/impl/src/add_like.rs @@ -1,46 +1,53 @@ use crate::add_helpers::{struct_exprs, tuple_exprs}; use crate::utils::{ - add_extra_type_param_bound_op_output, field_idents, named_to_vec, numbered_vars, - unnamed_to_vec, + add_extra_type_param_bound_op_output_except, field_idents, named_to_vec, + numbered_vars, unnamed_to_vec, HashSet, }; use proc_macro2::TokenStream; use quote::{format_ident, quote, ToTokens}; use std::iter; use syn::{Data, DataEnum, DeriveInput, Field, Fields, Ident}; -pub fn expand(input: &DeriveInput, trait_name: &str) -> TokenStream { +pub fn expand(input: &DeriveInput, trait_name: &str) -> syn::Result { let trait_name = trait_name.trim_end_matches("Self"); let trait_ident = format_ident!("{trait_name}"); let method_name = trait_name.to_lowercase(); let method_ident = format_ident!("{method_name}"); let input_type = &input.ident; - let generics = add_extra_type_param_bound_op_output(&input.generics, &trait_ident); - let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); - - let (output_type, block) = match input.data { - Data::Struct(ref data_struct) => match data_struct.fields { - Fields::Unnamed(ref fields) => ( - quote! { #input_type #ty_generics }, - tuple_content(input_type, &unnamed_to_vec(fields), &method_ident), - ), - Fields::Named(ref fields) => ( - quote! { #input_type #ty_generics }, - struct_content(input_type, &named_to_vec(fields), &method_ident), - ), + let (block, zst_generics) = match &input.data { + Data::Struct(data_struct) => match &data_struct.fields { + Fields::Unnamed(ref fields) => { + tuple_content(input_type, &unnamed_to_vec(fields), &method_ident)? + } + Fields::Named(ref fields) => { + struct_content(input_type, &named_to_vec(fields), &method_ident)? + } _ => panic!("Unit structs cannot use derive({trait_name})"), }, - Data::Enum(ref data_enum) => ( - quote! { - derive_more::core::result::Result<#input_type #ty_generics, derive_more::BinaryError> - }, + Data::Enum(data_enum) => ( enum_content(input_type, data_enum, &method_ident), + Default::default(), ), + Data::Union(_) => panic!("Only structs and enums can use derive({trait_name})"), + }; + let generics = add_extra_type_param_bound_op_output_except( + &input.generics, + &trait_ident, + zst_generics, + ); + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + let output_type = match input.data { + Data::Struct(_) => quote! { #input_type #ty_generics }, + Data::Enum(_) => quote! { + derive_more::core::result::Result<#input_type #ty_generics, derive_more::BinaryError> + }, _ => panic!("Only structs and enums can use derive({trait_name})"), }; - quote! { + Ok(quote! { #[automatically_derived] impl #impl_generics derive_more::core::ops::#trait_ident for #input_type #ty_generics #where_clause { @@ -52,28 +59,31 @@ pub fn expand(input: &DeriveInput, trait_name: &str) -> TokenStream { #block } } - } + }) } fn tuple_content( input_type: &T, fields: &[&Field], method_ident: &Ident, -) -> TokenStream { - let exprs = tuple_exprs(fields, method_ident); - quote! { #input_type(#(#exprs),*) } +) -> syn::Result<(TokenStream, HashSet)> { + let (exprs, zst_generics) = tuple_exprs(fields, method_ident)?; + Ok((quote! { #input_type(#(#exprs),*) }, zst_generics)) } fn struct_content( input_type: &Ident, fields: &[&Field], method_ident: &Ident, -) -> TokenStream { +) -> syn::Result<(TokenStream, HashSet)> { // It's safe to unwrap because struct fields always have an identifier - let exprs = struct_exprs(fields, method_ident); + let (exprs, zst_generics) = struct_exprs(fields, method_ident)?; let field_names = field_idents(fields); - quote! { #input_type{#(#field_names: #exprs),*} } + Ok(( + quote! { #input_type{#(#field_names: #exprs),*} }, + zst_generics, + )) } #[allow(clippy::cognitive_complexity)] diff --git a/impl/src/lib.rs b/impl/src/lib.rs index eeccdb07..c9b6a8c5 100644 --- a/impl/src/lib.rs +++ b/impl/src/lib.rs @@ -107,31 +107,46 @@ macro_rules! create_derive( } ); -create_derive!("add", add_like, Add, add_derive); -create_derive!("add", add_like, Sub, sub_derive); -create_derive!("add", add_like, BitAnd, bit_and_derive); -create_derive!("add", add_like, BitOr, bit_or_derive); -create_derive!("add", add_like, BitXor, bit_xor_derive); +create_derive!("add", add_like, Add, add_derive, add); +create_derive!("add", add_like, Sub, sub_derive, sub); +create_derive!("add", add_like, BitAnd, bit_and_derive, bitand); +create_derive!("add", add_like, BitOr, bit_or_derive, bitor); +create_derive!("add", add_like, BitXor, bit_xor_derive, bitxor); -create_derive!("add_assign", add_assign_like, AddAssign, add_assign_derive,); -create_derive!("add_assign", add_assign_like, SubAssign, sub_assign_derive,); +create_derive!( + "add_assign", + add_assign_like, + AddAssign, + add_assign_derive, + add_assign, +); +create_derive!( + "add_assign", + add_assign_like, + SubAssign, + sub_assign_derive, + sub_assign, +); create_derive!( "add_assign", add_assign_like, BitAndAssign, bit_and_assign_derive, + bitand_assign, ); create_derive!( "add_assign", add_assign_like, BitOrAssign, bit_or_assign_derive, + bitor_assign, ); create_derive!( "add_assign", add_assign_like, BitXorAssign, bit_xor_assign_derive, + bitxor_assign, ); create_derive!("as_ref", r#as::r#mut, AsMut, as_mut_derive, as_mut); diff --git a/impl/src/mul_assign_like.rs b/impl/src/mul_assign_like.rs index b5fb4e50..5030811e 100644 --- a/impl/src/mul_assign_like.rs +++ b/impl/src/mul_assign_like.rs @@ -13,14 +13,12 @@ pub fn expand(input: &DeriveInput, trait_name: &'static str) -> Result Result { + let mut allowed_attr_params = AttrParams::struct_(vec!["forward"]); + allowed_attr_params.field = vec!["skip"]; let mut state = State::with_attr_params( input, trait_name, trait_name.to_lowercase(), - AttrParams::struct_(vec!["forward"]), + allowed_attr_params, )?; if state.default_info.forward { - return Ok(add_like::expand(input, trait_name)); + return add_like::expand(input, trait_name); } let scalar_ident = format_ident!("__RhsT"); diff --git a/impl/src/utils.rs b/impl/src/utils.rs index cc5f35b3..6e2e5beb 100644 --- a/impl/src/utils.rs +++ b/impl/src/utils.rs @@ -7,18 +7,22 @@ use proc_macro2::TokenStream; use quote::{format_ident, quote, ToTokens}; use syn::{ parse_quote, punctuated::Punctuated, spanned::Spanned, Attribute, Data, - DeriveInput, Error, Field, Fields, FieldsNamed, FieldsUnnamed, GenericParam, - Generics, Ident, Index, Result, Token, Type, TypeGenerics, TypeParamBound, Variant, - WhereClause, + DeriveInput, Error, Field, Fields, FieldsNamed, FieldsUnnamed, GenericArgument, + GenericParam, Generics, Ident, Index, Result, ReturnType, Token, Type, + TypeGenerics, TypeParamBound, Variant, WhereClause, }; #[cfg(any( feature = "as_ref", + feature = "add", + feature = "add_assign", feature = "debug", feature = "display", feature = "from", feature = "from_str", feature = "into", + feature = "mul", + feature = "mul_assign", feature = "try_from", ))] pub(crate) use self::either::Either; @@ -28,10 +32,14 @@ pub(crate) use self::fields_ext::FieldsExt; pub(crate) use self::generics_search::GenericsSearch; #[cfg(any( feature = "as_ref", + feature = "add", + feature = "add_assign", feature = "debug", feature = "display", feature = "from", feature = "into", + feature = "mul", + feature = "mul_assign", feature = "try_from", ))] pub(crate) use self::spanning::Spanning; @@ -136,12 +144,74 @@ pub fn get_field_types<'a>(fields: &'a [&'a Field]) -> Vec<&'a Type> { get_field_types_iter(fields).collect() } +pub fn extract_idents_from_generic_arguments<'a>( + args: impl IntoIterator, +) -> HashSet { + fn extract_from_type(set: &mut HashSet, ty: &syn::Type) { + match ty { + Type::Array(type_array) => extract_from_type(set, &type_array.elem), + Type::BareFn(type_bare_fn) => { + for arg in &type_bare_fn.inputs { + extract_from_type(set, &arg.ty) + } + if let ReturnType::Type(_, ty) = &type_bare_fn.output { + extract_from_type(set, ty) + } + } + Type::Group(type_group) => extract_from_type(set, &type_group.elem), + Type::Paren(type_paren) => extract_from_type(set, &type_paren.elem), + Type::Path(type_path) => { + if type_path.qself.is_none() + && type_path.path.leading_colon.is_none() + && type_path.path.segments.len() == 1 + { + set.insert(type_path.path.segments[0].ident.clone()); + } + } + Type::Ptr(type_ptr) => extract_from_type(set, &type_ptr.elem), + Type::Reference(type_reference) => { + extract_from_type(set, &type_reference.elem) + } + Type::Slice(type_slice) => extract_from_type(set, &type_slice.elem), + Type::Tuple(type_tuple) => { + for ty in &type_tuple.elems { + extract_from_type(set, ty); + } + } + _ => {} + } + } + + let mut type_params = HashSet::default(); + for arg in args { + if let GenericArgument::Type(ty) = arg { + extract_from_type(&mut type_params, ty); + } + } + type_params +} + pub fn add_extra_type_param_bound_op_output<'a>( generics: &'a Generics, trait_ident: &'a Ident, +) -> Generics { + add_extra_type_param_bound_op_output_except( + generics, + trait_ident, + HashSet::default(), + ) +} + +pub fn add_extra_type_param_bound_op_output_except<'a>( + generics: &'a Generics, + trait_ident: &'a Ident, + except: HashSet, ) -> Generics { let mut generics = generics.clone(); - for type_param in &mut generics.type_params_mut() { + for type_param in &mut generics + .type_params_mut() + .skip_while(|t| except.contains(&t.ident)) + { let type_ident = &type_param.ident; let bound: TypeParamBound = parse_quote! { derive_more::core::ops::#trait_ident @@ -152,20 +222,43 @@ pub fn add_extra_type_param_bound_op_output<'a>( generics } -pub fn add_extra_ty_param_bound_op<'a>( +// pub fn add_extra_ty_param_bound_op<'a>( +// generics: &'a Generics, +// trait_ident: &'a Ident, +// ) -> Generics { +// add_extra_ty_param_bound_op_except(generics, trait_ident, HashSet::default()) +// } + +pub fn add_extra_ty_param_bound_op_except<'a>( generics: &'a Generics, trait_ident: &'a Ident, + except: HashSet, ) -> Generics { - add_extra_ty_param_bound(generics, "e! { derive_more::core::ops::#trait_ident }) + add_extra_ty_param_bound_except( + generics, + "e! { derive_more::core::ops::#trait_ident }, + except, + ) } pub fn add_extra_ty_param_bound<'a>( generics: &'a Generics, bound: &'a TokenStream, +) -> Generics { + add_extra_ty_param_bound_except(generics, bound, HashSet::default()) +} + +pub fn add_extra_ty_param_bound_except<'a>( + generics: &'a Generics, + bound: &'a TokenStream, + except: HashSet, ) -> Generics { let mut generics = generics.clone(); let bound: TypeParamBound = parse_quote! { #bound }; - for type_param in &mut generics.type_params_mut() { + for type_param in &mut generics + .type_params_mut() + .skip_while(|t| except.contains(&t.ident)) + { type_param.bounds.push(bound.clone()) } @@ -1007,6 +1100,7 @@ fn parse_punctuated_nested_meta( (None, "owned") => info.owned = Some(true), (None, "ref") => info.ref_ = Some(true), (None, "ref_mut") => info.ref_mut = Some(true), + (None, "skip") => info.skip = Some(true), (None, "source") => info.source = Some(true), (Some("not"), "source") => info.source = Some(false), (Some("source"), "optional") => info.source_optional = Some(true), @@ -1205,6 +1299,7 @@ pub struct MetaInfo { pub owned: Option, pub ref_: Option, pub ref_mut: Option, + pub skip: Option, pub source: Option, pub source_optional: Option, pub backtrace: Option, @@ -1298,11 +1393,15 @@ pub fn is_type_parameter_used_in_type( #[cfg(any( feature = "as_ref", + feature = "add", + feature = "add_assign", feature = "debug", feature = "display", feature = "from", feature = "from_str", feature = "into", + feature = "mul", + feature = "mul_assign", feature = "try_from", ))] mod either { @@ -1370,10 +1469,14 @@ mod either { #[cfg(any( feature = "as_ref", + feature = "add", + feature = "add_assign", feature = "debug", feature = "display", feature = "from", feature = "into", + feature = "mul", + feature = "mul_assign", feature = "try_from", ))] mod spanning { @@ -1465,10 +1568,14 @@ mod spanning { #[cfg(any( feature = "as_ref", + feature = "add", + feature = "add_assign", feature = "debug", feature = "display", feature = "from", feature = "into", + feature = "mul", + feature = "mul_assign", feature = "try_from", ))] pub(crate) mod attr { @@ -1490,9 +1597,13 @@ pub(crate) mod attr { pub(crate) use self::empty::Empty; #[cfg(any( feature = "as_ref", + feature = "add", + feature = "add_assign", feature = "debug", feature = "from", feature = "into", + feature = "mul", + feature = "mul_assign", ))] pub(crate) use self::skip::Skip; #[cfg(any(feature = "as_ref", feature = "from", feature = "try_from"))] @@ -1821,10 +1932,14 @@ pub(crate) mod attr { #[cfg(any( feature = "as_ref", + feature = "add", + feature = "add_assign", feature = "debug", feature = "display", feature = "from", feature = "into", + feature = "mul", + feature = "mul_assign", ))] mod skip { use syn::{ diff --git a/tests/add.rs b/tests/add.rs index c16a08d0..26223d37 100644 --- a/tests/add.rs +++ b/tests/add.rs @@ -1,6 +1,8 @@ #![cfg_attr(not(feature = "std"), no_std)] #![allow(dead_code)] // some code is tested for type checking only +use core::marker::PhantomData; + use derive_more::Add; #[derive(Add)] @@ -12,6 +14,16 @@ struct Point2D { y: i32, } +#[derive(Add)] +struct TupleWithZst(i32, #[add(skip)] PhantomData); + +#[derive(Add)] +struct StructWithZst { + x: i32, + #[add(skip)] + _marker: PhantomData, +} + #[derive(Add)] enum MixedInts { SmallInt(i32), @@ -22,3 +34,29 @@ enum MixedInts { UnsignedTwo(u32), Unit, } + +mod skip { + use super::*; + + #[test] + fn tuple_non_add_generic() { + let a: TupleWithZst<()> = TupleWithZst(12, PhantomData); + let b: TupleWithZst<()> = TupleWithZst(2, PhantomData); + assert_eq!((a + b).0, 14); + } + + #[test] + fn struct_non_add_generic() { + let a: StructWithZst<()> = StructWithZst { + x: 12, + _marker: PhantomData, + }; + + let b: StructWithZst<()> = StructWithZst { + x: 2, + _marker: PhantomData, + }; + + assert_eq!((a + b).x, 14); + } +} diff --git a/tests/add_assign.rs b/tests/add_assign.rs index 66b6f859..7c3fb22a 100644 --- a/tests/add_assign.rs +++ b/tests/add_assign.rs @@ -1,6 +1,8 @@ #![cfg_attr(not(feature = "std"), no_std)] #![allow(dead_code)] // some code is tested for type checking only +use core::marker::PhantomData; + use derive_more::AddAssign; #[derive(AddAssign)] @@ -11,3 +13,40 @@ struct Point2D { x: i32, y: i32, } + +#[derive(AddAssign)] +struct TupleWithZst(i32, #[add_assign(skip)] PhantomData); + +#[derive(AddAssign)] +struct StructWithZst { + x: i32, + #[add_assign(skip)] + _marker: PhantomData, +} + +mod skip { + use super::*; + + #[test] + fn tuple_non_add_generic() { + let mut a: TupleWithZst<()> = TupleWithZst(12, PhantomData); + a += TupleWithZst(2, PhantomData); + assert_eq!(a.0, 14); + } + + #[test] + fn struct_non_add_generic() { + let mut a: StructWithZst<()> = StructWithZst { + x: 12, + _marker: PhantomData, + }; + + let b: StructWithZst<()> = StructWithZst { + x: 2, + _marker: PhantomData, + }; + + a += b; + assert_eq!(a.x, 14); + } +} diff --git a/tests/mul.rs b/tests/mul.rs index 6e8ad745..0784f76d 100644 --- a/tests/mul.rs +++ b/tests/mul.rs @@ -1,6 +1,8 @@ #![cfg_attr(not(feature = "std"), no_std)] #![allow(dead_code)] // some code is tested for type checking only +use core::marker::PhantomData; + use derive_more::Mul; #[derive(Mul)] @@ -19,3 +21,41 @@ struct Point2D { x: i32, y: i32, } + +#[derive(Mul)] +#[mul(forward)] +struct TupleWithZst(i32, #[mul(skip)] PhantomData); + +#[derive(Mul)] +#[mul(forward)] +struct StructWithZst { + x: i32, + #[mul(skip)] + _marker: PhantomData, +} + +mod forward { + use super::*; + + #[test] + fn tuple_non_add_generic() { + let a: TupleWithZst<()> = TupleWithZst(12, PhantomData); + let b: TupleWithZst<()> = TupleWithZst(2, PhantomData); + assert_eq!((a * b).0, 24); + } + + #[test] + fn struct_non_add_generic() { + let a: StructWithZst<()> = StructWithZst { + x: 12, + _marker: PhantomData, + }; + + let b: StructWithZst<()> = StructWithZst { + x: 2, + _marker: PhantomData, + }; + + assert_eq!((a * b).x, 24); + } +} diff --git a/tests/mul_assign.rs b/tests/mul_assign.rs index ae9221b7..61b2a247 100644 --- a/tests/mul_assign.rs +++ b/tests/mul_assign.rs @@ -31,3 +31,42 @@ struct MyInt2 { x: i32, ph: PhantomData, } + +#[derive(MulAssign)] +#[mul_assign(forward)] +struct TupleWithZst(i32, #[mul_assign(skip)] PhantomData); + +#[derive(MulAssign)] +#[mul_assign(forward)] +struct StructWithZst { + x: i32, + #[mul_assign(skip)] + _marker: PhantomData, +} + +mod forward { + use super::*; + + #[test] + fn tuple_non_add_generic() { + let mut a: TupleWithZst<()> = TupleWithZst(12, PhantomData); + a *= TupleWithZst(2, PhantomData); + assert_eq!(a.0, 24); + } + + #[test] + fn struct_non_add_generic() { + let mut a: StructWithZst<()> = StructWithZst { + x: 12, + _marker: PhantomData, + }; + + let b: StructWithZst<()> = StructWithZst { + x: 2, + _marker: PhantomData, + }; + + a *= b; + assert_eq!(a.x, 24); + } +}