use crate::core::{DeriveInput, FactoryExpr, error};
use proc_macro::TokenStream;
use quote::quote;
use syn::{
Expr, PatType, Token,
parse::{Parse, ParseStream},
spanned::Spanned,
};
struct InjectExpr(Box<Expr>, Vec<PatType>);
impl Parse for InjectExpr {
fn parse(input: ParseStream) -> syn::Result<Self> {
if input.peek(Token![|]) {
let expr = FactoryExpr::parse(input)?;
Ok(InjectExpr(expr.body, expr.inputs))
} else {
Ok(InjectExpr(input.parse()?, vec![]))
}
}
}
pub(crate) fn handle_injectable(item: TokenStream) -> syn::Result<TokenStream> {
let input = syn::parse::<DeriveInput>(item)?;
let ident = &input.ident;
let fields = input.fields();
let types = input.field_types();
let keys = input.field_idents();
let attributes = fields
.iter()
.map(|f| {
let Some(attr) = f.attrs.iter().rfind(|a| a.path().is_ident("inject")) else {
return Ok(None);
};
attr.parse_args::<InjectExpr>().map(Some).map_err(|e| {
error::combine(
syn::Error::new(attr.span(), "Unable to parse inject attribute"),
e,
)
})
})
.collect::<syn::Result<Vec<_>>>()?;
let generic_params = input.generic_params();
let generic_keys = input.generic_keys();
let lifetime_keys = input.lifetime_keys();
let prov_lifetimes = match lifetime_keys.is_empty() {
false => quote! { 'prov: #(#lifetime_keys)+*, },
true => quote! {},
};
let where_predicates = match &input.generics.where_clause {
Some(w) => {
let predicates = &w.predicates;
quote! { #predicates }
}
None => quote! {},
};
let creation_output = match keys.is_empty() && !types.is_empty() {
true => {
let items = types.iter().zip(&attributes).map(|(_, a)| match a {
Some(attr) => {
let inputs = attr
.1
.iter()
.map(|x| quote! { let #x = provider.provide(); })
.collect::<Vec<_>>();
let output = &attr.0;
if inputs.is_empty() {
quote! { #output }
} else {
quote! {
{
#(#inputs)*
#output
}
}
}
}
None => quote! { provider.provide() },
});
quote! { #ident(#(#items),*) }
}
false => {
let items = keys.iter().zip(&attributes).map(|(k, a)| match a {
Some(attr) => {
let inputs = attr
.1
.iter()
.map(|x| quote! { let #x = provider.provide(); })
.collect::<Vec<_>>();
let output = &attr.0;
quote! {
#k: {
#(#inputs)*
#output
}
}
}
None => quote! { #k: provider.provide() },
});
quote! { #ident { #(#items),* } }
}
};
let mut prov_types = Vec::<_>::with_capacity(types.len());
for (t, a) in types.iter().zip(&attributes) {
if let Some(attr) = a {
for attr_type in attr.1.iter().map(|x| &x.ty) {
prov_types.push(quote! {#attr_type});
}
} else {
prov_types.push(quote! {#t});
}
}
prov_types.dedup_by(|a, b| a.to_string() == b.to_string());
let output = quote! {
#[derive(nject::InjectableHelperAttr)]
#input
impl<'prov, #(#generic_params,)*NjectProvider> nject::Injectable<'prov, #ident<#(#generic_keys),*>, NjectProvider> for #ident<#(#generic_keys),*>
where
#prov_lifetimes
NjectProvider: #(nject::Provider<'prov, #prov_types>)+*, #where_predicates
{
#[inline]
fn inject(provider: &'prov NjectProvider) -> #ident<#(#generic_keys),*> {
#creation_output
}
}
};
Ok(output.into())
}