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_rows_associated, tuple_rows_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))
    }

    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_iter: TokenStream,
    associated_rows: TokenStream,
    body: TokenStream,
    generics: TokenStream,
    constraints: TokenStream,
    error_ty: TokenStream,
}

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

        match collection_type {
            CollectionType::Vec => {
                let generics = ctx
                    .error_generics()
                    .with_types([&item_ty])
                    .to_token_stream();
                let error_ty = ctx.error_ty();
                let associated_iter =
                    quote! { <#item_ty as itemize::TryIntoItems<#for_type, #error_ty>>::IntoIter };
                let map_fn = quote! { fn(#item_ty) -> #associated_iter };
                Self {
                    concrete: quote! { Vec<#item_ty> },
                    associated_iter,
                    associated_rows: quote! { ::std::iter::Map<::std::vec::IntoIter<#item_ty>, #map_fn> },
                    body: quote! { self.into_iter().map(<#item_ty as itemize::TryIntoItems<#for_type, #error_ty>>::try_into_items) },
                    generics,
                    constraints: quote! { #item_ty: itemize::TryIntoItems<#for_type, #error_ty> },
                    error_ty,
                }
            }
            CollectionType::Slice => {
                let generics = ctx
                    .error_generics()
                    .with_types([&item_ty])
                    .with_lifetimes([quote! { 'a }])
                    .to_token_stream();
                let error_ty = ctx.error_ty();
                let associated_iter = quote! { <&'a #item_ty as itemize::TryIntoItems<#for_type, #error_ty>>::IntoIter };
                let map_fn = quote! { fn(&'a #item_ty) -> #associated_iter };
                Self {
                    concrete: quote! { &'a [#item_ty] },
                    associated_iter,
                    associated_rows: quote! { ::std::iter::Map<::std::slice::Iter<'a, #item_ty>, #map_fn> },
                    body: quote! { self.iter().map(<&'a #item_ty as itemize::TryIntoItems<#for_type, #error_ty>>::try_into_items) },
                    generics,
                    constraints: quote! { &'a #item_ty: itemize::TryIntoItems<#for_type, #error_ty> },
                    error_ty,
                }
            }
            CollectionType::Array => {
                let generics = ctx
                    .error_generics()
                    .with_types([&item_ty])
                    .with_consts([quote! { const #const_ty: usize }])
                    .to_token_stream();
                let error_ty = ctx.error_ty();
                let associated_iter =
                    quote! { <#item_ty as itemize::TryIntoItems<#for_type, #error_ty>>::IntoIter };
                let map_fn = quote! { fn(#item_ty) -> #associated_iter };
                Self {
                    concrete: quote! { [#item_ty; #const_ty] },
                    associated_iter,
                    associated_rows: quote! { ::std::iter::Map<::std::array::IntoIter<#item_ty, #const_ty>, #map_fn> },
                    body: quote! { self.into_iter().map(<#item_ty as itemize::TryIntoItems<#for_type, #error_ty>>::try_into_items) },
                    generics,
                    constraints: quote! { #item_ty: itemize::TryIntoItems<#for_type, #error_ty> },
                    error_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.error_generics().with_types(&target).to_token_stream();

        let error_ty = ctx.error_ty();
        let constraints = target
            .iter()
            .map(|target| quote! { #target: itemize::TryIntoItems<#for_type, #error_ty> });

        let iter_types: Vec<TokenStream> = target
            .iter()
            .map(|t| quote! { <#t as itemize::TryIntoItems<#for_type, #error_ty>>::IntoIter })
            .collect();

        let body = tuple_rows_impl(len, |name| quote! { #name.try_into_items() });

        Self {
            concrete: quote! { (#(#target,)*) },
            associated_iter: tuple_rows_associated(len, &iter_types, for_type),
            associated_rows: quote! { ::std::array::IntoIter<Self::RowIter, #len> },
            body,
            generics,
            constraints: quote! { #(#constraints,)* },
            error_ty,
        }
    }

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

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

        quote! {
            impl #generics itemize::TryIntoRows<#for_type, #error_ty> for #concrete
            where
                #(#predicates,)*
                #constraints
            {
                type RowIter = #associated_iter;
                type Rows = #associated_rows;
                #[inline]
                fn try_into_rows(self) -> Self::Rows {
                    #body
                }
            }
        }
    }
}