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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
23 changes: 23 additions & 0 deletions impl/doc/add.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(i32, #[add(skip)] PhantomData<T>);

#[derive(Add)]
struct StructWithZst<T> {
x: i32,
#[add(skip)]
_marker: PhantomData<T>,
}
```




## Enums

There's a big difference between the code that is generated for the two struct
Expand Down
23 changes: 23 additions & 0 deletions impl/doc/add_assign.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(i32, #[add_assign(skip)] PhantomData<T>);

#[derive(AddAssign)]
struct StructWithZst<T> {
x: i32,
#[add_assign(skip)]
_marker: PhantomData<T>,
}
```




## Enums

Deriving `AddAssign` is not (yet) supported for enums.
Expand Down
25 changes: 25 additions & 0 deletions impl/doc/mul.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(i32, #[mul(skip)] PhantomData<T>);

#[derive(Mul)]
#[mul(forward)]
struct StructWithZst<T> {
x: i32,
#[mul(skip)]
_marker: PhantomData<T>,
}
```




## Enums

Deriving `Mul` for enums is not (yet) supported, except when you use
Expand Down
25 changes: 25 additions & 0 deletions impl/doc/mul_assign.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(i32, #[mul_assign(skip)] PhantomData<T>);

#[derive(MulAssign)]
#[mul_assign(forward)]
struct StructWithZst<T> {
x: i32,
#[mul_assign(skip)]
_marker: PhantomData<T>,
}
```




## Enums

Deriving `MulAssign` for enums is not (yet) supported.
Expand Down
25 changes: 14 additions & 11 deletions impl/src/add_assign_like.rs
Original file line number Diff line number Diff line change
@@ -1,33 +1,36 @@
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<TokenStream> {
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})"),
},

_ => 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 {
Expand All @@ -37,5 +40,5 @@ pub fn expand(input: &DeriveInput, trait_name: &str) -> TokenStream {
#( #exprs; )*
}
}
}
})
}
134 changes: 123 additions & 11 deletions impl/src/add_helpers.rs
Original file line number Diff line number Diff line change
@@ -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<TokenStream> {
#[cfg(any(feature = "add", feature = "mul",))]
pub fn tuple_exprs(
fields: &[&Field],
method_ident: &Ident,
) -> syn::Result<(Vec<TokenStream>, HashSet<Ident>)> {
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<TokenStream> {
#[cfg(any(feature = "add", feature = "mul",))]
pub fn struct_exprs(
fields: &[&Field],
method_ident: &Ident,
) -> syn::Result<(Vec<TokenStream>, HashSet<Ident>)> {
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<TokenStream>, HashSet<Ident>)> {
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<TokenStream>, HashSet<Ident>)> {
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))
}
Loading
Loading