boring-derive 0.1.1

Derive macros for some common patterns
Documentation
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned, ToTokens};
use syn::{spanned::Spanned, Error, Ident};

use crate::core::{
    attr::BoolAttr,
    container::{AttrContainer, AttrField, AttrVariant, Container},
    context::Context,
    data::{Data, Field, Style},
    symbol::Symbol,
};

const FROM: Symbol = Symbol("from");
const SKIP: Symbol = Symbol("skip");

struct FromContainer;

impl AttrContainer for FromContainer {
    fn from_ast(_cx: &Context, _item: &syn::DeriveInput) -> Self {
        FromContainer
    }
}

struct FromVariant {
    skip: bool,
}

impl AttrVariant for FromVariant {
    fn from_ast(cx: &Context, variant: &syn::Variant) -> Self {
        let mut skip = BoolAttr::none(cx, SKIP);

        for attr in &variant.attrs {
            if attr.path() != FROM {
                continue;
            }

            if let Err(err) = attr.parse_nested_meta(|meta| {
                if meta.path == SKIP {
                    skip.set_true(&meta.path);
                } else {
                    let path = meta.path.to_token_stream().to_string().replace(' ', "");
                    return Err(
                        meta.error(format_args!("unknown from variant attribute: `{}`", path))
                    );
                }
                Ok(())
            }) {
                cx.syn_error(err);
            }
        }

        FromVariant { skip: skip.get() }
    }
}

struct FromField;

impl AttrField for FromField {
    fn from_ast(_cx: &Context, _index: usize, _field: &syn::Field) -> Self {
        FromField
    }
}

pub(crate) fn impl_from(ast: &syn::DeriveInput) -> syn::Result<TokenStream> {
    let ctxt = Context::new();
    let cont: Option<Container<FromField, FromVariant, FromContainer>> =
        Container::from_ast(&ctxt, ast);
    let cont = match cont {
        Some(cont) => cont,
        None => return Err(ctxt.check().unwrap_err()),
    };

    ctxt.check()?;
    let ident = &cont.ident;
    let (impl_generics, type_generics, where_clause) = cont.generics.split_for_impl();

    let expanded = match cont.data {
        Data::Struct(style, fields) => {
            let (from_type, from_body) = gen_info(ident, &style, &fields);
            quote! {
                impl #impl_generics From<#from_type> for #ident #type_generics #where_clause {
                    fn from(value: #from_type) -> Self {
                        #from_body
                    }
                }
            }
        }

        Data::Enum(variants) => {
            let variants = variants.iter().filter_map(|v| {
                if v.attrs.skip {
                    None
                } else {
                let v_name = &v.ident;
                let (from_type, from_body) = gen_info(v_name, &v.style, &v.fields);
                Some(quote! {
                    impl #impl_generics From<#from_type> for #ident #type_generics #where_clause {
                        fn from(value: #from_type) -> Self {
                            #ident::#from_body
                        }
                    }
                })
                }
            });

            quote! { #(#variants)* }
        }
        Data::Union(_) => {
            return Err(Error::new(
                cont.ident.span(),
                format_args!("deriving from not supported for unions"),
            ));
        }
    };

    // panic!("{:?}", expanded.to_string());
    Ok(expanded)
}

fn gen_info<F: AttrField>(
    constructor: &Ident,
    style: &Style,
    fields: &[Field<'_, F>],
) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
    match style {
        Style::Unit => (quote! {()}, quote! {#constructor}),
        Style::Newtype => {
            let field = &fields[0];
            let ty = field.ty;
            (quote! {#ty}, quote! {#constructor(value)})
        }
        Style::Tuple => {
            if fields.len() == 1 {
                let field = &fields[0];
                let ty = field.ty;
                (quote! {#ty}, quote! {#constructor(value)})
            } else {
                let recurse = fields.iter().map(|f| {
                    let ty = &f.ty;
                    quote_spanned! {f.original.span() => #ty}
                });
                let from_type = quote! { ( #(#recurse),* )};
                let recurse = fields.iter().enumerate().map(|(i, f)| {
                    let index = syn::Index::from(i);
                    quote_spanned! {f.original.span() => value.#index}
                });
                let from_body = quote! { #constructor(#(#recurse),*) };
                (from_type, from_body)
            }
        }
        Style::Struct => {
            if fields.len() == 1 {
                let field = &fields[0];
                let name = &field.original.ident;
                let ty = field.ty;
                (quote! {#ty}, quote! {#constructor { #name: value }})
            } else {
                let recurse = fields.iter().map(|f| {
                    let ty = &f.ty;
                    quote_spanned! {f.original.span() => #ty}
                });
                let from_type = quote! { ( #(#recurse),* )};
                let recurse = fields.iter().enumerate().map(|(i, f)| {
                    let index = syn::Index::from(i);
                    let name = &f.original.ident;
                    quote_spanned! {f.original.span() => #name: value.#index}
                });
                let from_body = quote! { #constructor { #(#recurse),* } };

                (from_type, from_body)
            }
        }
    }
}