#![deny(missing_docs)]
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{parse_macro_input, DeriveInput, Error};
mod attr;
mod ctxt;
mod symbol;
use crate::attr::Container;
use crate::ctxt::Ctxt;
#[proc_macro_derive(Inject, attributes(coi))]
pub fn inject_derive(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let cx = Ctxt::new();
let container = Container::from_ast(&cx, &input, true);
if let Err(e) = cx.check() {
return to_compile_errors(e).into();
}
let container = container.unwrap();
let has_generics = !input.generics.params.is_empty();
let generic_params = input.generics.params;
let generics = if has_generics {
quote! {
<#generic_params>
}
} else {
quote! {}
};
let coi = container.coi_path();
let where_clause = input
.generics
.where_clause
.map(|w| {
let t: Vec<_> = generic_params.iter().collect();
quote! { #w #(, #t: Send + Sync + 'static )* }
})
.unwrap_or_default();
if container.providers.is_empty() {
let ident = input.ident;
return quote! {
impl #generics #coi::Inject for #ident #generics #where_clause {}
}
.into();
}
let container_ident = format_ident!(
"{}",
if container.injected.is_empty() {
"_"
} else {
"container"
}
);
let (resolve, keys): (Vec<_>, Vec<_>) = container
.injected
.into_iter()
.map(|field| {
let ident = field.name;
let ty = field.ty;
let key = format!("{}", ident);
(
quote! {
let #ident = #container_ident.resolve::<#ty>(#key)?;
},
key,
)
})
.unzip();
let input_ident = input.ident;
let dependencies_fn = if cfg!(feature = "debug") {
vec![quote! {
fn dependencies(&self) -> &'static[&'static str] {
&[
#( #keys, )*
]
}
}]
} else {
vec![]
};
let provider_fields = if has_generics {
let tys: Vec<_> = generic_params.iter().cloned().collect();
quote! {
(
#( ::std::marker::PhantomData<#tys> )*
)
}
} else {
quote! {}
};
let phantom_data: Vec<_> = generic_params
.iter()
.map(|_| quote! {::std::marker::PhantomData})
.collect();
let provider_impls = if !phantom_data.is_empty() {
container
.providers
.iter()
.map(|p| {
let provider = p.name_or(&input_ident);
let vis = &p.vis;
quote! {
impl #generics #provider #generics #where_clause {
#vis fn new() -> Self {
Self(#( #phantom_data )*)
}
}
}
})
.collect()
} else {
vec![]
};
let constructed_provides: Vec<_> = container
.providers
.into_iter()
.map(|p| {
let provider = p.name_or(&input_ident);
let vis = p.vis;
let ty = p.ty;
let provides_with = p.with;
quote! {
#vis struct #provider #generics #provider_fields #where_clause;
impl #generics #coi::Provide for #provider #generics #where_clause {
type Output = #ty;
fn provide(
&self,
#container_ident: &#coi::Container,
) -> #coi::Result<::std::sync::Arc<Self::Output>> {
#( #resolve )*
Ok(::std::sync::Arc::new(#provides_with) as ::std::sync::Arc<#ty>)
}
#( #dependencies_fn )*
}
}
})
.collect();
let expanded = quote! {
impl #generics #coi::Inject for #input_ident #generics #where_clause {}
#( #provider_impls )*
#( #constructed_provides )*
};
TokenStream::from(expanded)
}
#[proc_macro_derive(Provide, attributes(coi))]
pub fn provide_derive(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let cx = Ctxt::new();
let container = Container::from_ast(&cx, &input, false);
if let Err(e) = cx.check() {
return to_compile_errors(e).into();
}
let container = container.unwrap();
let provider = input.ident.clone();
let has_generics = !input.generics.params.is_empty();
let generic_params = input.generics.params;
let generics = if has_generics {
quote! {
<#generic_params>
}
} else {
quote! {}
};
let where_clause = input
.generics
.where_clause
.map(|w| {
let t: Vec<_> = generic_params.iter().collect();
quote! { #w #(, #t: Send + Sync + 'static )* }
})
.unwrap_or_default();
let dependencies_fn = if cfg!(feature = "debug") {
vec![{
quote! {
fn dependencies(
&self
) -> &'static [&'static str] {
&[]
}
}
}]
} else {
vec![]
};
let coi = container.coi_path();
let expanded: Vec<_> = container
.providers
.into_iter()
.map(|p| {
let ty = p.ty;
let provides_with = p.with;
quote! {
impl #generics #coi::Provide for #provider #generics #where_clause {
type Output = #ty;
fn provide(
&self,
_: &#coi::Container,
) -> #coi::Result<::std::sync::Arc<Self::Output>> {
Ok(::std::sync::Arc::new(#provides_with) as ::std::sync::Arc<#ty>)
}
#( #dependencies_fn )*
}
}
})
.collect();
TokenStream::from(quote! {
#( #expanded )*
})
}
fn to_compile_errors(errors: Vec<Error>) -> proc_macro2::TokenStream {
let compile_errors = errors.iter().map(Error::to_compile_error);
quote!(#(#compile_errors)*)
}