itemize_derive_impl 0.1.0

Implementation details for itemize derive macros
Documentation
use proc_macro2::TokenStream;
use quote::{ToTokens, quote};

use crate::{
    context::{CollectionType, Context},
    util::{const_ident, item_ident, tuple_items_impl, tuple_type_ident},
};

pub(crate) fn generate(ctx: &Context<'_>) -> TokenStream {
    let mut configs = vec![];

    for collection_type in &ctx.attributes.collections {
        configs.push(Config::from_collection(ctx, collection_type).generate(ctx))
    }

    for type_ in &ctx.attributes.types {
        configs.push(Config::from_type(ctx, type_).generate(ctx))
    }

    if let Some(tuples) = ctx.attributes.tuples {
        for len in tuples.iter() {
            configs.push(Config::from_tuple(ctx, len).generate(ctx))
        }
    }

    quote! { #(#configs)* }
}

struct Config {
    concrete: TokenStream,
    associated: TokenStream,
    body: TokenStream,
    generics: TokenStream,
    constraints: TokenStream,
}

impl Config {
    fn from_type(ctx: &Context<'_>, type_: &syn::Type) -> Self {
        let item: &TokenStream = &ctx.concrete;
        let generics = ctx
            .generics()
            .with_lifetimes_from_type(type_)
            .to_token_stream();

        Self {
            associated: quote! { ::std::iter::Once<#item> },
            body: quote! { ::std::iter::once(<#item as ::std::convert::From<#type_>>::from(self)) },
            concrete: quote! { #type_ },
            generics,
            constraints: quote! { #item: ::std::convert::From<#type_> },
        }
    }

    fn from_collection(ctx: &Context<'_>, collection_type: &CollectionType) -> Self {
        let item_ty = item_ident();
        let const_ty = const_ident();
        let for_type = &ctx.concrete;

        let generics = ctx.generics().with_types([&item_ty]);

        let map_item = map_item_from();

        match collection_type {
            CollectionType::Vec => Self {
                associated: quote! { ::std::iter::Map<::std::vec::IntoIter<#item_ty>, fn(#item_ty) -> #for_type> },
                body: quote! { #map_item self.into_iter().map(map_item::<#for_type, #item_ty>) },
                concrete: quote! { Vec<#item_ty> },
                generics: generics.to_token_stream(),
                constraints: quote! { #for_type: ::std::convert::From<#item_ty> },
            },
            CollectionType::Slice => Self {
                associated: quote! { ::std::iter::Map<::std::slice::Iter<'a, #item_ty>, fn(&'a #item_ty) -> #for_type> },
                body: quote! { #map_item self.iter().map(map_item::<#for_type, &'a #item_ty>) },
                concrete: quote! { &'a [#item_ty] },
                generics: generics.with_lifetimes([quote! { 'a }]).to_token_stream(),
                constraints: quote! { #for_type: ::std::convert::From<&'a #item_ty> },
            },
            CollectionType::Array => Self {
                generics: generics
                    .with_consts([quote! { const #const_ty: usize }])
                    .to_token_stream(),
                associated: quote! { ::std::iter::Map<::std::array::IntoIter<#item_ty, #const_ty>, fn(#item_ty) -> #for_type> },
                body: quote! { #map_item self.into_iter().map(map_item::<#for_type, #item_ty>) },
                concrete: quote! { [#item_ty; #const_ty] },
                constraints: quote! { #for_type: ::std::convert::From<#item_ty> },
            },
        }
    }

    fn from_tuple(ctx: &Context<'_>, len: usize) -> Self {
        let for_type: &TokenStream = &ctx.concrete;

        let target = (0..len).map(tuple_type_ident).collect::<Vec<_>>();

        let generics = ctx.generics().with_types(&target).to_token_stream();

        let constraint = target
            .iter()
            .map(|target| quote! { #for_type: ::std::convert::From<#target> });

        let body = tuple_items_impl(
            len,
            |name| quote! { <#for_type>::from(#name) },
            |block| quote! { [#block].into_iter() },
        );

        Self {
            associated: quote! { ::std::array::IntoIter<#for_type, #len> },
            body,
            concrete: quote! { (#(#target,)*) },
            generics,
            constraints: quote! { #(#constraint,)* },
        }
    }

    fn generate(self, ctx: &Context<'_>) -> TokenStream {
        let associated = self.associated;
        let body = self.body;
        let generics = self.generics;
        let concrete = self.concrete;
        let constraints = self.constraints;

        let predicates = ctx.where_predicates.iter().flatten();
        let item = &ctx.concrete;

        quote! {
            impl #generics itemize::IntoItems<#item> for #concrete
            where
                #(#predicates,)*
                #constraints
            {
                type IntoIter = #associated;
                #[inline]
                fn into_items(self) -> Self::IntoIter {
                    #body
                }
            }
        }
    }
}

fn map_item_from() -> TokenStream {
    quote! {
        #[inline]
        fn map_item<Target, Item>(item: Item) -> Target
        where
            Target: ::std::convert::From<Item>,
        {
            Target::from(item)
        }
    }
}