Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deduplicate Clone by always using match and { } #31

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
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
32 changes: 32 additions & 0 deletions src/common/field_info.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use std::ops::Deref;

use syn::Field;

use crate::common::ident_index::IdentOrIndex;

/// A field, in a `Fields::Named` or a `Fields::Unnamed`
///
/// Allows unification of the data handling code,
/// to always use `{ }` syntax even for tuple data.
pub(crate) struct FieldInfo<'f> {
pub(crate) name: IdentOrIndex,
pub(crate) field: &'f Field,
}

impl<'f> FieldInfo<'f> {
pub(crate) fn new(index: usize, field: &'f Field) -> Self {
let name = IdentOrIndex::from_ident_with_index(field.ident.as_ref(), index);
FieldInfo {
name,
field,
}
}
}

impl<'f> Deref for FieldInfo<'f> {
type Target = &'f Field;

fn deref(&self) -> &&'f Field {
&self.field
}
}
14 changes: 13 additions & 1 deletion src/common/ident_index.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use quote::ToTokens;
use std::fmt;

use quote::{IdentFragment, ToTokens};
use syn::{Ident, Index};

#[derive(Clone)]
pub(crate) enum IdentOrIndex {
Ident(Ident),
Index(Index),
Expand Down Expand Up @@ -54,3 +57,12 @@ impl IdentOrIndex {
}
}
}

impl IdentFragment for IdentOrIndex {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Ident(ident) => IdentFragment::fmt(ident, f),
Self::Index(index) => IdentFragment::fmt(index, f),
}
}
}
18 changes: 6 additions & 12 deletions src/common/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
#[allow(dead_code)]
pub(crate) mod bound;
#[allow(dead_code)]
pub(crate) mod field_info;
#[allow(dead_code)]
pub(crate) mod ident_index;
#[allow(dead_code)]
pub(crate) mod path;
#[allow(dead_code)]
pub(crate) mod r#type;
#[allow(dead_code)]
pub(crate) mod variant_info;
#[allow(dead_code)]
pub(crate) mod where_predicates_bool;

#[cfg(feature = "Default")]
Expand All @@ -20,18 +26,6 @@ pub(crate) mod expr;
))]
#[allow(dead_code)]
pub(crate) mod ident_bool;
#[cfg(any(
feature = "Debug",
feature = "PartialEq",
feature = "PartialOrd",
feature = "Ord",
feature = "Hash",
feature = "Deref",
feature = "DerefMut",
feature = "Into"
))]
#[allow(dead_code)]
pub(crate) mod ident_index;
#[cfg(any(feature = "PartialOrd", feature = "Ord"))]
#[allow(dead_code)]
pub(crate) mod int;
Expand Down
50 changes: 50 additions & 0 deletions src/common/variant_info.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use std::iter;

use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::{Data, Ident, Variant};

pub(crate) struct VariantInfo<'v> {
pub(crate) variant: Option<&'v Variant>,
pub(crate) fields: &'v syn::Fields,
pub(crate) attrs: &'v [syn::Attribute],
}

pub(crate) struct VariantSelector<'v> {
pub(crate) name: Option<&'v Ident>,
}

impl<'v> VariantInfo<'v> {
pub(crate) fn iter_from_data(data: &'v syn::Data) -> Box<dyn Iterator<Item = Self> + 'v> {
match data {
Data::Struct(s) => Box::new(iter::once(VariantInfo {
variant: None,
fields: &s.fields,
attrs: &[],
})),

Data::Enum(e) => Box::new(e.variants.iter().map(|v| VariantInfo {
variant: Some(v),
fields: &v.fields,
attrs: &v.attrs,
})),

Data::Union(_) => panic!("VariantInfo cannot be used for unions"),
}
}

pub(crate) fn selector(&self) -> VariantSelector<'v> {
let name = self.variant.as_ref().map(|v| &v.ident);
VariantSelector {
name,
}
}
}

impl ToTokens for VariantSelector<'_> {
fn to_tokens(&self, out: &mut TokenStream) {
if let Some(name) = &self.name {
quote! { :: #name }.to_tokens(out)
}
}
}
201 changes: 201 additions & 0 deletions src/trait_handlers/clone/clone_data.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
use quote::{format_ident, quote};
use syn::{DeriveInput, Meta, Type};

use super::models::{FieldAttribute, FieldAttributeBuilder, TypeAttributeBuilder};
use crate::{
common::{field_info::FieldInfo, variant_info::VariantInfo},
supported_traits::Trait,
TraitHandler,
};

pub(crate) struct CloneDataHandler;

impl TraitHandler for CloneDataHandler {
#[inline]
fn trait_meta_handler(
ast: &DeriveInput,
token_stream: &mut proc_macro2::TokenStream,
traits: &[Trait],
meta: &Meta,
) -> syn::Result<()> {
let type_attribute = TypeAttributeBuilder {
enable_flag: true, enable_bound: true
}
.build_from_clone_meta(meta)?;

let mut clone_token_stream = proc_macro2::TokenStream::new();
let mut clone_from_token_stream = proc_macro2::TokenStream::new();

type Variants<'a> = Vec<(VariantInfo<'a>, Vec<(FieldInfo<'a>, FieldAttribute)>)>;

let mut variants: Variants = Vec::new();

#[cfg(feature = "Copy")]
let mut has_custom_clone_method = false;

for variant in VariantInfo::iter_from_data(&ast.data) {
let _ = TypeAttributeBuilder {
enable_flag: false, enable_bound: false
}
.build_from_attributes(variant.attrs, traits)?;

let mut variant_fields: Vec<(FieldInfo, FieldAttribute)> = Vec::new();

for (index, field) in variant.fields.iter().enumerate() {
let field_attribute = FieldAttributeBuilder {
enable_method: true
}
.build_from_attributes(&field.attrs, traits)?;

#[cfg(feature = "Copy")]
if field_attribute.method.is_some() {
has_custom_clone_method = true;
}

variant_fields.push((FieldInfo::new(index, field), field_attribute));
}

variants.push((variant, variant_fields));
}

#[cfg(feature = "Copy")]
let contains_copy = !has_custom_clone_method && traits.contains(&Trait::Copy);

#[cfg(not(feature = "Copy"))]
let contains_copy = false;

if contains_copy {
clone_token_stream.extend(quote!(*self));
}

let mut clone_types: Vec<&Type> = Vec::new();

if variants.is_empty() {
if !contains_copy {
clone_token_stream.extend(quote!(unreachable!()));
clone_from_token_stream.extend(quote!(let _ = source;));
}
} else {
let mut clone_variants_token_stream = proc_macro2::TokenStream::new();
let mut clone_from_variants_token_stream = proc_macro2::TokenStream::new();

for (variant, variant_fields) in variants {
let variant_sel = variant.selector();

let mut pattern_src_token_stream = proc_macro2::TokenStream::new();
let mut pattern_dst_token_stream = proc_macro2::TokenStream::new();
let mut cl_fields_token_stream = proc_macro2::TokenStream::new();
let mut cf_body_token_stream = proc_macro2::TokenStream::new();

for (field, field_attribute) in variant_fields {
let field_name_real = &field.name;
let field_name_src = format_ident!("_s_{}", field_name_real);
let field_name_dst = format_ident!("_d_{}", field_name_real);

pattern_src_token_stream.extend(quote!(#field_name_real: #field_name_src,));
pattern_dst_token_stream.extend(quote!(#field_name_real: #field_name_dst,));

if let Some(clone) = field_attribute.method.as_ref() {
cl_fields_token_stream.extend(quote! {
#field_name_real: #clone(#field_name_src),
});
cf_body_token_stream
.extend(quote!(*#field_name_dst = #clone(#field_name_src);));
} else {
clone_types.push(&field.ty);

cl_fields_token_stream.extend(quote! {
#field_name_real: ::core::clone::Clone::clone(#field_name_src),
});
cf_body_token_stream.extend(
quote!( ::core::clone::Clone::clone_from(#field_name_dst, #field_name_src); ),
);
}
}

clone_variants_token_stream.extend(quote! {
Self #variant_sel { #pattern_src_token_stream } => Self #variant_sel { #cl_fields_token_stream },
});

clone_from_variants_token_stream.extend(quote! {
Self #variant_sel { #pattern_dst_token_stream } => {
if let Self #variant_sel { #pattern_src_token_stream } = source {
#cf_body_token_stream
} else {
*self = ::core::clone::Clone::clone(source);
}
},
});
}

if !contains_copy {
clone_token_stream.extend(quote! {
match self {
#clone_variants_token_stream
}
});

clone_from_token_stream.extend(quote! {
match self {
#clone_from_variants_token_stream
}
});
}
}

let bound = type_attribute.bound.into_where_predicates_by_generic_parameters_check_types(
&ast.generics.params,
&syn::parse2(if contains_copy {
quote!(::core::marker::Copy)
} else {
quote!(::core::clone::Clone)
})
.unwrap(),
&clone_types,
&[],
);

let clone_from_fn_token_stream = if clone_from_token_stream.is_empty() {
None
} else {
Some(quote! {
#[inline]
fn clone_from(&mut self, source: &Self) {
#clone_from_token_stream
}
})
};

let ident = &ast.ident;

let mut generics = ast.generics.clone();
let where_clause = generics.make_where_clause();

for where_predicate in bound {
where_clause.predicates.push(where_predicate);
}

let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();

token_stream.extend(quote! {
impl #impl_generics ::core::clone::Clone for #ident #ty_generics #where_clause {
#[inline]
fn clone(&self) -> Self {
#clone_token_stream
}

#clone_from_fn_token_stream
}
});

#[cfg(feature = "Copy")]
if traits.contains(&Trait::Copy) {
token_stream.extend(quote! {
impl #impl_generics ::core::marker::Copy for #ident #ty_generics #where_clause {
}
});
}

Ok(())
}
}
Loading
Loading