fields-converter-derive 0.1.0

Fields-wise type conversions derive macros
Documentation
//! [![pipeline status](https://gitlab.com/mexus/fields-converter/badges/master/pipeline.svg)](https://gitlab.com/mexus/fields-converter/commits/master)
//! [![crates.io](https://img.shields.io/crates/v/fields-converter-derive.svg)](https://crates.io/crates/fields-converter-derive)
//! [![docs.rs](https://docs.rs/fields-converter-derive/badge.svg)](https://docs.rs/fields-converter-derive)
//!
//! Procedural macro that automatically derives `CloneInto` and `CloneFrom` traits for you.

#[macro_use]
extern crate quote;
#[macro_use]
extern crate syn;

extern crate proc_macro;
extern crate proc_macro2;

mod input_data;
mod struct_data;

use self::{input_data::InputData, struct_data::StructData};
use proc_macro::TokenStream;
use quote::ToTokens;

/// A derive macro for `CloneInto` and `CloneFrom` traits.
///
/// To automagically derive the traits for your type against a `DesiredTypeName` add the
/// following attributes to it:
///   * `#[derive(CloneFields)]`,
///   * and `#[destinations("DesiredTypeName")]`.
///
/// ... and the macro will generate an implementations of `CloneInto<DesiredTypeName>` and
/// `CloneFrom<DesiredTypeName>` for you type then.
///
/// You can add more than one type, like `#[destinations("Type1", "Type2", ...)]`.
///
/// It is possible to use structs with fields with different types, the only requirement is that
/// respective types should be "clonable" with the `CloneFrom` and `CloneInto` traits.
///
/// Please refer to [clone-fields](https://docs.rs/clone-fields) docs for more info on why do you
/// ;) *implied you need it* ... and for some examples.
#[proc_macro_derive(CloneFields, attributes(destinations))]
pub fn clone_fields_derive(input: TokenStream) -> TokenStream {
    let InputData {
        struct_data,
        destination_types,
    } = parse_macro_input!(input as InputData);
    let impls = destination_types
        .iter()
        .map(|ty| clone_fields(&struct_data, ty));
    quote!(
        #(#impls)*
        )
    .into()
}

/// A derive macro for `Into` and `From` traits, converting the structures field by field.
///
/// To automagically derive the traits for your type against a `DesiredTypeName` add the
/// following attributes to it:
///   * `#[derive(MoveFields)]`,
///   * and `#[destinations("DesiredTypeName")]`.
///
/// ... and the macro will generate an implementations of `Into<DesiredTypeName>` and
/// `From<DesiredTypeName>` for you type then.
///
/// You can add more than one type, like `#[destinations("Type1", "Type2", ...)]`.
///
/// It is possible to use structs with fields with different types, the only requirement is that
/// respective types should be "convertible" with the `From` and `Into` traits.
///
/// ```
/// #[macro_use]
/// extern crate fields_converter_derive;
///
/// #[derive(MoveFields)]
/// #[destinations("ext::Remote")]
/// struct Local<'a, T, S: 'a> {
///   x: T,
///   y: &'a S,
/// }
///
/// mod ext {
///     pub struct Remote<'a, T, S: 'a> {
///       // All the fields of the `Remote` type need to be public since in our derived
///       // implementations we construct the `Local` type by assigning (and converting)
///       // each field.
///       pub x: T,
///       // Generics and lifetimes are fully supported, fear not!
///       pub y: &'a S,
///     }
/// }
///
/// fn main() {
///   let remote = ext::Remote{x: 14, y: &String::from("wow")};
///   let local = Local::from(remote);
///   assert_eq!(local.x, 14);
///   assert_eq!(local.y, &"wow");
///   let remote2: ext::Remote<_, _> = local.into();
///   assert_eq!(remote2.x, 14);
///   assert_eq!(remote2.y, &"wow");
/// }
/// ```
#[proc_macro_derive(MoveFields, attributes(destinations))]
pub fn move_fields_derive(input: TokenStream) -> TokenStream {
    let InputData {
        struct_data,
        destination_types,
    } = parse_macro_input!(input as InputData);
    let impls = destination_types
        .iter()
        .map(|ty| move_fields(&struct_data, ty));
    quote!(
        #(#impls)*
        )
    .into()
}

fn clone_fields(struct_data: &StructData, to_ty: &syn::Path) -> impl ToTokens {
    let from_ty = &struct_data.name;
    let fields = &struct_data.field_idents;
    let (impl_generics, ty_generics, where_clause) = struct_data.generics.split_for_impl();
    let direct_clones = fields
        .iter()
        .map(|field| quote!( #field: CloneInto::clone_into(&self.#field) ));
    let reverse_clones = fields
        .iter()
        .map(|field| quote!( #field: CloneFrom::clone_from(&other.#field) ));
    quote!(
        impl #impl_generics CloneInto<#to_ty #ty_generics> for #from_ty #ty_generics #where_clause {
            fn clone_into(&self) -> #to_ty #ty_generics {
                #to_ty {
                    #( #direct_clones ),*
                }
            }
        }

        impl #impl_generics CloneFrom<#to_ty #ty_generics> for #from_ty #ty_generics #where_clause {
            fn clone_from(other: &#to_ty #ty_generics) -> Self {
                #from_ty {
                    #( #reverse_clones ),*
                }
            }
        }
    )
}

fn move_fields(struct_data: &StructData, to_ty: &syn::Path) -> impl ToTokens {
    let (impl_generics, ty_generics, where_clause) = struct_data.generics.split_for_impl();
    let into_fields = struct_data
        .field_idents
        .iter()
        .map(|field| quote!( #field: Into::into(self.#field) ));
    let from_fields = struct_data
        .field_idents
        .iter()
        .map(|field| quote!( #field: From::from(other.#field) ));
    let from_ty = &struct_data.name;
    quote!(
        impl #impl_generics Into<#to_ty #ty_generics> for #from_ty #ty_generics #where_clause {
            fn into(self) -> #to_ty #ty_generics {
                #to_ty {
                    #( #into_fields ),*
                }
            }
        }

        impl #impl_generics From<#to_ty #ty_generics> for #from_ty #ty_generics #where_clause {
            fn from(other: #to_ty #ty_generics) -> Self {
                #from_ty {
                    #( #from_fields ),*
                }
            }
        }
    )
}