teloc_macros 0.2.0

Macros for teloc framework
Documentation
use crate::common::{compile_error, ident_generator};
use crate::generics::{get_impl_block_generics, get_where_clause};
use crate::parse::ParseInjectImpl;
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::parse::{Parse, ParseBuffer};
use syn::{FnArg, ImplItemMethod, ItemFn, ItemImpl, ReturnType, Type};

pub fn expand(input: &InjectInput) -> Result<TokenStream, TokenStream> {
    let dependencies = input
        .sig()
        .inputs
        .iter()
        .map(|inp| match inp {
            FnArg::Receiver(_) => Err(compile_error("Function must not give self as arg!")),
            FnArg::Typed(pat) => Ok(pat.ty.as_ref()),
        })
        .collect::<Result<Vec<_>, _>>()?;

    let struct_ty = get_struct_ty(input)?;
    let fn_ident = &input.sig().ident;

    let generics = &input.generics();
    let impl_block_generics = generics.map(get_impl_block_generics);
    let where_clause = generics.map(get_where_clause);

    let mut destructure = quote! { teloc::reexport::frunk::HNil };
    ident_generator(dependencies.len())
        .into_iter()
        .rev()
        .for_each(|id| {
            destructure = quote! {
                teloc::reexport::frunk::HCons {
                    head: #id,
                    tail: #destructure
                }
            };
        });
    let names = ident_generator(dependencies.len());

    let init = match input {
        InjectInput::Impl(_, _) => quote! { <#struct_ty>::#fn_ident(#(#names),*) },
        InjectInput::Function(_) => quote! { #fn_ident(#(#names),*) },
    };

    Ok(quote! {
        impl #impl_block_generics teloc::Dependency<teloc::reexport::HList![#(#dependencies),*]> for #struct_ty #where_clause {
            fn init(data: teloc::reexport::HList![#(#dependencies),*]) -> Self {
                let #destructure = data;
                #init
            }
        }
    })
}

fn get_struct_ty(inp: &InjectInput) -> Result<&Type, TokenStream> {
    match inp {
        InjectInput::Impl(x, _) => Ok(x.self_ty.as_ref()),
        InjectInput::Function(f) => match &f.sig.output {
            ReturnType::Default => Err(compile_error(
                "Expected return type, found default return type",
            )),
            ReturnType::Type(_, ty) => Ok(ty.as_ref()),
        },
    }
}

pub enum InjectInput {
    Impl(ItemImpl, ImplItemMethod),
    Function(ItemFn),
}
impl InjectInput {
    fn sig(&self) -> &syn::Signature {
        match self {
            InjectInput::Impl(_, x) => &x.sig,
            InjectInput::Function(x) => &x.sig,
        }
    }
    fn generics(&self) -> Option<&syn::Generics> {
        match self {
            InjectInput::Impl(x, _) => Some(&x.generics),
            InjectInput::Function(_) => None,
        }
    }
}

impl Parse for InjectInput {
    fn parse(input: &ParseBuffer) -> Result<Self, syn::Error> {
        if let Ok(f) = input.parse::<ItemFn>() {
            Ok(Self::Function(f))
        } else {
            let item_impl: ItemImpl = input.parse()?;
            let ParseInjectImpl {
                item_impl,
                init_method,
            } = ParseInjectImpl::parse(item_impl)?;
            Ok(Self::Impl(item_impl, init_method))
        }
    }
}

impl ToTokens for InjectInput {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        match self {
            InjectInput::Impl(x, _) => x.to_tokens(tokens),
            InjectInput::Function(x) => x.to_tokens(tokens),
        }
    }
}