derive-from-one 0.1.0

Automatically generates `From` impls so you don't have to
Documentation
//! Autogeneration of `From` impls for enums and structs.
//!
//! ## Installation
//!
//! Add to `Cargo.toml`:
//!
//! ```toml
//! derive-from-one = "0.1"
//! ```
//!
//! ## Usage
//! This macro generates `From` impls for enum constructors with a single field.
//!
//! If you don't want some impls to be generated, apply `#[from(skip)]` to this tag.
//!
//! Also, the macro would automatically skip ambiguous types, i.e., when a type appears in multiple tags regardless of amount of fields.
//!
//! Example:
//!
//! ```rust
//! use derive_from_one::FromOne;
//!
//! #[derive(FromOne)]
//! enum Enum {
//!     A { a: u32 },           // GENERATES From impl
//!     B(bool),                // GENERATES From impl
//!     #[from(skip)]
//!     C(String),              // DOES NOT GENERATE From impl due to #[from(skip)] attribute
//!     D {                     // DOES NOT GENERATE From impl due to multiple fields
//!         foo: Vec<String>,
//!         bar: &'static str,
//!     },
//!     E(Vec<String>)          // DOES NOT GENERATE From impl due to Vec<String> appears in D tag
//! }
//! ```
//!
//! Generated code:
//!
//! ```rust
//! enum Enum {
//!     A { a: u32 },
//!     B(bool),
//!     C(String),
//!     D {
//!         foo: Vec<String>,
//!         bar: &'static str,
//!     },
//!     E(Vec<String>)
//! }
//!
//! impl From<u32> for Enum {
//!     fn from(x: u32) -> Self {
//!         Self::A { a: x }
//!     }
//! }
//!
//! impl From<bool> for Enum {
//!     fn from(x: bool) -> Self {
//!         Self::B(x)
//!     }
//! }
//! ```
//!
//! You can also apply this macro to structs with a single field.
//!
//! ```rust
//! use derive_from_one::FromOne;
//!
//! #[derive(FromOne)]
//! struct StructOne(usize);
//!
//! #[derive(FromOne)]
//! struct StructTwo { a: usize }
//! ```

extern crate proc_macro;

use std::collections::HashMap;

use proc_macro::TokenStream as CompilerTokenStream;
use proc_macro2::TokenStream;
use quote::quote;
use syn::{
    punctuated::Punctuated, token, DeriveInput, Error as SyntaxError, Fields, Ident, Type, Variant,
};

enum Field<'a> {
    Named(&'a syn::Field),
    Unnamed(&'a syn::Field),
}

#[proc_macro_derive(FromOne, attributes(from))]
pub fn derive_from_one(input: CompilerTokenStream) -> CompilerTokenStream {
    let input = syn::parse_macro_input!(input as DeriveInput);

    match &input.data {
        syn::Data::Enum(t) => from_enum(&input.ident, &t.variants),
        syn::Data::Struct(t) => from_struct(&input.ident, &t.fields),
        _ => {
            let error = SyntaxError::new_spanned(
                input,
                "FromOne can only be used with enums or structs with a single field",
            );
            error.to_compile_error().into()
        }
    }
}

fn from_enum(
    target_type: &Ident,
    variants: &Punctuated<Variant, token::Comma>,
) -> CompilerTokenStream {
    let non_unique_types: Vec<&Type> = {
        let mut type_counts = HashMap::new();

        for variant in variants {
            let fields = match &variant.fields {
                Fields::Named(fields) => &fields.named,
                Fields::Unnamed(fields) => &fields.unnamed,
                Fields::Unit => continue,
            };

            for field in fields {
                let count = type_counts.entry(&field.ty).or_insert(0);
                *count += 1;
            }
        }

        type_counts
            .into_iter()
            .filter_map(|(k, v)| if v > 1 { Some(k) } else { None })
            .collect()
    };

    let mut from_impls = Vec::new();

    for variant in variants {
        let field = match &variant.fields {
            Fields::Named(fields) => {
                if fields.named.len() != 1 {
                    continue;
                }

                let field = fields.named.first().unwrap();

                if non_unique_types.contains(&&field.ty) {
                    continue;
                }

                Field::Named(field)
            }
            Fields::Unnamed(fields) => {
                if fields.unnamed.len() != 1 {
                    continue;
                }

                let field = fields.unnamed.first().unwrap();

                if non_unique_types.contains(&&field.ty) {
                    continue;
                }

                Field::Unnamed(field)
            }
            Fields::Unit => continue,
        };

        let skip = get_skip_attribute(&variant.attrs);

        let from_impl = match skip {
            Ok(Some(_)) => continue,
            Ok(None) => match field {
                Field::Named(field) => {
                    let field_type = &field.ty;
                    let field_name = &field.ident;
                    let variant_name = &variant.ident;

                    quote! {
                        impl From<#field_type> for #target_type {
                            fn from(x: #field_type) -> Self {
                                Self::#variant_name { #field_name: x }
                            }
                        }
                    }
                }
                Field::Unnamed(field) => {
                    let field_type = &field.ty;
                    let variant_name = &variant.ident;

                    quote! {
                        impl From<#field_type> for #target_type {
                            fn from(x: #field_type) -> Self {
                                Self::#variant_name(x)
                            }
                        }
                    }
                }
            },
            Err(error) => return error.to_compile_error().into(),
        };

        from_impls.push(from_impl);
    }

    let tokens: TokenStream = from_impls.into_iter().collect();

    tokens.into()
}

fn get_skip_attribute(attrs: &[syn::Attribute]) -> Result<Option<()>, SyntaxError> {
    let mut skip_attr = None;

    for attr in attrs {
        if attr.path().is_ident("from") {
            attr.parse_nested_meta(|meta| {
                if meta.path.is_ident("skip") {
                    skip_attr = Some(());
                    Ok(())
                } else {
                    Err(meta.error("Expected `#[from(skip)]`"))
                }
            })?;
        }
    }

    Ok(skip_attr)
}

fn from_struct(target_type: &Ident, fields: &Fields) -> CompilerTokenStream {
    let from_impl = match fields {
        Fields::Unnamed(fields) => {
            if fields.unnamed.len() != 1 {
                return invalid_number_of_fields_in_struct_error(target_type)
                    .to_compile_error()
                    .into();
            }

            let field = fields.unnamed.first().unwrap();

            let field_type = &field.ty;

            quote! {
                impl From<#field_type> for #target_type {
                    fn from(x: #field_type) -> Self {
                        Self(x)
                    }
                }
            }
        }
        Fields::Named(fields) => {
            if fields.named.len() != 1 {
                return invalid_number_of_fields_in_struct_error(target_type)
                    .to_compile_error()
                    .into();
            }

            let field = fields.named.first().unwrap();

            let field_type = &field.ty;
            let field_name = &field.ident;

            quote! {
                impl From<#field_type> for #target_type {
                    fn from(x: #field_type) -> Self {
                        Self { #field_name: x }
                    }
                }
            }
        }
        Fields::Unit => {
            return invalid_number_of_fields_in_struct_error(target_type)
                .to_compile_error()
                .into()
        }
    };

    from_impl.into()
}

fn invalid_number_of_fields_in_struct_error(t: &Ident) -> SyntaxError {
    SyntaxError::new_spanned(
        t,
        "FromOne can only be used with structs with a single field",
    )
}