extern crate proc_macro;
use proc_macro::TokenStream;
use quote::{format_ident, quote, ToTokens};
use syn;
#[proc_macro_attribute]
pub fn component(attr: TokenStream, item: TokenStream) -> TokenStream {
let ast: syn::Item = syn::parse(item).unwrap();
let vis: syn::Visibility = syn::parse(attr).unwrap();
match ast {
syn::Item::Struct(struct_ast) => component_from_struct(struct_ast),
syn::Item::Impl(impl_ast) => component_from_impl(vis, impl_ast),
_ => panic!("The #[component] macro can only be used on struct definiton or an impl block"),
}
}
#[proc_macro_attribute]
pub fn scope(_args: TokenStream, item: TokenStream) -> TokenStream {
item
}
fn component_from_struct(ast: syn::ItemStruct) -> TokenStream {
let impl_name = &ast.ident;
let impl_type = syn::parse2(quote! { #impl_name }).unwrap();
let impl_generics = syn::parse2(quote! {}).unwrap();
let args: Vec<_> = ast
.fields
.iter()
.map(|f| (f.ident.clone().unwrap(), f.ty.clone()))
.collect();
let scope_type =
get_scope(&ast.attrs).unwrap_or_else(|| syn::parse_str("::dill::Transient").unwrap());
let mut gen: TokenStream = quote! { #ast }.into();
let builder: TokenStream = implement_builder(
&ast.vis,
&impl_type,
&impl_generics,
scope_type,
args,
false,
);
gen.extend(builder.into_iter());
gen
}
fn component_from_impl(vis: syn::Visibility, ast: syn::ItemImpl) -> TokenStream {
let impl_generics = &ast.generics;
let impl_type = &ast.self_ty;
let new = get_new(&ast.items).expect(
"When using #[component] macro on the impl block it's expected to contain a new() function. \
Otherwise use #[derive(Builder)] on the struct."
);
let args: Vec<_> = new
.sig
.inputs
.iter()
.map(|arg| match arg {
syn::FnArg::Typed(targ) => targ,
_ => panic!("Unexpected argument in new() function"),
})
.map(|arg| {
(
match arg.pat.as_ref() {
syn::Pat::Ident(ident) => ident.ident.clone(),
_ => panic!("Unexpected format of arguments in new() function"),
},
arg.ty.as_ref().clone(),
)
})
.collect();
let scope_type =
get_scope(&ast.attrs).unwrap_or_else(|| syn::parse_str("::dill::Transient").unwrap());
let mut gen: TokenStream = quote! { #ast }.into();
let builder: TokenStream =
implement_builder(&vis, impl_type, impl_generics, scope_type, args, true);
gen.extend(builder.into_iter());
gen
}
fn implement_builder(
impl_vis: &syn::Visibility,
impl_type: &syn::Type,
_impl_generics: &syn::Generics,
scope_type: syn::Path,
args: Vec<(syn::Ident, syn::Type)>,
has_new: bool,
) -> TokenStream {
let builder_name = format_ident!("{}Builder", quote! { #impl_type }.to_string());
let arg_name: Vec<_> = args.iter().map(|(name, _)| name).collect();
let arg_impls: Vec<_> = args
.iter()
.map(|(name, typ)| implement_arg(name, typ, &builder_name))
.collect();
let mut arg_override_fn_field = Vec::new();
let mut arg_override_fn_field_ctor = Vec::new();
let mut arg_override_setters = Vec::new();
let mut arg_prepare_dependency = Vec::new();
let mut arg_provide_dependency = Vec::new();
for (
override_fn_field,
override_fn_field_ctor,
override_setters,
prepare_dependency,
provide_dependency,
) in arg_impls
{
arg_override_fn_field.push(override_fn_field);
arg_override_fn_field_ctor.push(override_fn_field_ctor);
arg_override_setters.push(override_setters);
arg_prepare_dependency.push(prepare_dependency);
arg_provide_dependency.push(provide_dependency);
}
let ctor = if !has_new {
quote! {
#impl_type {
#( #arg_name: #arg_provide_dependency, )*
}
}
} else {
quote! {
#impl_type::new(#( #arg_provide_dependency, )*)
}
};
let gen = quote! {
impl ::dill::BuilderLike for #impl_type {
type Builder = #builder_name;
fn register(cat: &mut ::dill::CatalogBuilder) {
cat.add_builder(Self::builder());
}
fn builder() -> Self::Builder {
#builder_name::new()
}
}
#impl_vis struct #builder_name {
scope: #scope_type,
#(
#arg_override_fn_field
)*
}
impl #builder_name {
pub fn new() -> Self {
Self {
scope: #scope_type::new(),
#(
#arg_override_fn_field_ctor
)*
}
}
#( #arg_override_setters )*
fn build(&self, cat: &::dill::Catalog) -> Result<#impl_type, ::dill::InjectionError> {
#( #arg_prepare_dependency )*
Ok(#ctor)
}
}
impl ::dill::Builder for #builder_name {
fn instance_type_id(&self) -> std::any::TypeId {
::std::any::TypeId::of::<#impl_type>()
}
fn instance_type_name(&self) -> &'static str {
::std::any::type_name::<#impl_type>()
}
fn get(&self, cat: &::dill::Catalog) -> Result<::std::sync::Arc<dyn ::std::any::Any + Send + Sync>, ::dill::InjectionError> {
Ok(::dill::TypedBuilder::get(self, cat)?)
}
}
impl ::dill::TypedBuilder<#impl_type> for #builder_name {
fn get(&self, cat: &::dill::Catalog) -> Result<std::sync::Arc<#impl_type>, ::dill::InjectionError> {
use ::dill::Scope;
if let Some(inst) = self.scope.get() {
return Ok(inst.downcast().unwrap());
}
let inst = ::std::sync::Arc::new(self.build(cat)?);
self.scope.set(inst.clone());
Ok(inst)
}
}
};
gen.into()
}
fn implement_arg(
name: &syn::Ident,
typ: &syn::Type,
builder: &syn::Ident,
) -> (
proc_macro2::TokenStream,
proc_macro2::TokenStream,
proc_macro2::TokenStream,
proc_macro2::TokenStream,
proc_macro2::TokenStream,
) {
let override_fn_name = format_ident!("arg_{}_fn", name);
let override_fn_field = if is_reference(typ) {
proc_macro2::TokenStream::new()
} else {
quote! {
#override_fn_name: Option<Box<dyn Fn(&::dill::Catalog) -> Result<#typ, ::dill::InjectionError> + Send + Sync>>,
}
};
let override_fn_field_ctor = if is_reference(typ) {
proc_macro2::TokenStream::new()
} else {
quote! { #override_fn_name: None, }
};
let override_setters = if is_reference(typ) {
proc_macro2::TokenStream::new()
} else {
let setter_val_name = format_ident!("with_{}", name);
let setter_fn_name = format_ident!("with_{}_fn", name);
quote! {
pub fn #setter_val_name(mut self, val: #typ) -> #builder {
self.#override_fn_name = Some(Box::new(move |_| Ok(val.clone())));
self
}
pub fn #setter_fn_name(
mut self,
fun: impl Fn(&::dill::Catalog) -> Result<#typ, ::dill::InjectionError> + 'static + Send + Sync
) -> #builder {
self.#override_fn_name = Some(Box::new(fun));
self
}
}
};
let from_catalog = if is_reference(typ) {
let stripped = strip_reference(typ);
quote! { cat.get::<::dill::OneOf<#stripped>>()? }
} else if is_smart_ptr(typ) {
let stripped = strip_smart_ptr(typ);
quote! { cat.get::<::dill::OneOf<#stripped>>()? }
} else {
quote! { cat.get::<::dill::OneOf<#typ>>().map(|v| v.as_ref().clone())? }
};
let prepare_dependency = if is_reference(typ) {
quote! { let #name = #from_catalog; }
} else {
quote! {
let #name = match self.#override_fn_name {
Some(ref fun) => fun(cat)?,
_ => #from_catalog,
};
}
};
let provide_dependency = if is_reference(typ) {
quote! { #name.as_ref() }
} else {
quote! { #name }
};
(
override_fn_field,
override_fn_field_ctor,
override_setters,
prepare_dependency,
provide_dependency,
)
}
fn get_scope(attrs: &Vec<syn::Attribute>) -> Option<syn::Path> {
let mut scope = None;
for attr in attrs {
if attr.path().is_ident("scope") {
attr.parse_nested_meta(|meta| {
scope = Some(meta.path);
Ok(())
})
.unwrap();
}
}
scope
}
fn get_new(impl_items: &Vec<syn::ImplItem>) -> Option<&syn::ImplItemFn> {
impl_items
.iter()
.filter_map(|i| match i {
syn::ImplItem::Fn(m) => Some(m),
_ => None,
})
.filter(|m| m.sig.ident == "new")
.next()
}
fn is_reference(typ: &syn::Type) -> bool {
match typ {
syn::Type::Reference(_) => true,
_ => false,
}
}
fn strip_reference(typ: &syn::Type) -> syn::Type {
match typ {
syn::Type::Reference(r) => r.elem.as_ref().clone(),
_ => typ.clone(),
}
}
fn is_smart_ptr(typ: &syn::Type) -> bool {
match typ {
syn::Type::Path(typepath) if typepath.qself.is_none() => {
match typepath.path.segments.first() {
Some(seg) if seg.ident.to_string() == "Arc" => true,
_ => false,
}
}
_ => false,
}
}
fn strip_smart_ptr(typ: &syn::Type) -> syn::Type {
match typ {
syn::Type::Path(typepath) if typepath.qself.is_none() => {
match typepath.path.segments.first() {
Some(seg) if seg.ident.to_string() == "Arc" => match seg.arguments {
syn::PathArguments::AngleBracketed(ref args) => {
syn::parse2(args.args.to_token_stream()).unwrap()
}
_ => typ.clone(),
},
_ => typ.clone(),
}
}
_ => typ.clone(),
}
}