use proc_macro2::TokenStream;
use quote::quote;
use crate::attrs;
use crate::metadata::{self, type_to_string};
use crate::provider_gen::{self, FieldInfo, FieldInjectKind};
pub fn expand_derive_injectable(input: syn::DeriveInput) -> syn::Result<TokenStream> {
let type_name = &input.ident;
let generics = &input.generics;
let injectable_attrs = attrs::parse_attrs(&input.attrs)?;
let fields = parse_struct_fields(&input.data)?;
let provider_code = provider_gen::generate_field_injection_provider(
type_name,
generics,
&fields,
injectable_attrs.scope.as_str(),
injectable_attrs.has_post_construct,
);
Ok(provider_code)
}
fn parse_struct_fields(data: &syn::Data) -> syn::Result<Vec<FieldInfo>> {
match data {
syn::Data::Struct(data_struct) => match &data_struct.fields {
syn::Fields::Named(named_fields) => named_fields
.named
.iter()
.map(|field| {
let name = field.ident.clone();
let ty = field.ty.clone();
let ty_string = type_to_string(&ty);
let inject_kind = parse_field_inject_kind(&field.attrs, &ty)?;
Ok(FieldInfo {
name,
ty,
ty_string,
inject_kind,
})
})
.collect(),
syn::Fields::Unnamed(unnamed_fields) => unnamed_fields
.unnamed
.iter()
.map(|field| {
let ty = field.ty.clone();
let ty_string = type_to_string(&ty);
let inject_kind = parse_field_inject_kind(&field.attrs, &ty)?;
Ok(FieldInfo {
name: None,
ty,
ty_string,
inject_kind,
})
})
.collect(),
syn::Fields::Unit => Ok(Vec::new()),
},
syn::Data::Enum(_) | syn::Data::Union(_) => Ok(Vec::new()),
}
}
fn parse_field_inject_kind(
attrs: &[syn::Attribute],
ty: &syn::Type,
) -> syn::Result<FieldInjectKind> {
for attr in attrs {
if attr.path().is_ident("injectable") {
return attr.parse_args_with(parse_inject_sub_arg);
}
}
if metadata::extract_inject_inner(ty).is_some() {
Ok(FieldInjectKind::Inject)
} else {
use syn::spanned::Spanned;
Err(syn::Error::new(
ty.span(),
"non-`Inject<T>` fields require an explicit `#[injectable(inject)]` annotation; \
if this field has no DI dependency, use a `#[injectable_ctor]` constructor instead",
))
}
}
fn parse_inject_sub_arg(input: syn::parse::ParseStream) -> syn::Result<FieldInjectKind> {
let kw: syn::Ident = input.parse()?;
if kw != "inject" {
return Err(syn::Error::new(
kw.span(),
format!("expected `inject` inside `#[injectable(...)]` on a field, found `{kw}`"),
));
}
if input.is_empty() {
return Ok(FieldInjectKind::Inject);
}
let content;
syn::parenthesized!(content in input);
let factory_ident: syn::Ident = content.parse()?;
if factory_ident == "external" {
return Ok(FieldInjectKind::External);
}
let is_async = if factory_ident == "use_factory_async" || factory_ident == "use_factory" {
true
} else if factory_ident == "use_factory_sync" {
false
} else {
return Err(syn::Error::new(
factory_ident.span(),
format!(
"unknown inject argument: `{factory_ident}`; \
expected `external`, `use_factory_async = path`, or `use_factory_sync = path`"
),
));
};
content.parse::<syn::Token![=]>()?;
let path: syn::Path = content.parse()?;
if is_async {
Ok(FieldInjectKind::Factory(path))
} else {
Ok(FieldInjectKind::Provider(path))
}
}
pub fn expand_injectable_trait(input: syn::ItemTrait) -> syn::Result<TokenStream> {
let trait_name = &input.ident;
let _dyn_trait_name = quote!(dyn #trait_name);
let trait_provider_name = syn::Ident::new(
&format!("{}TraitProvider", trait_name),
proc_macro2::Span::call_site(),
);
let output = quote! {
#input
pub trait #trait_provider_name: Send + Sync + 'static {}
impl<T: #trait_name + Send + Sync + 'static> #trait_provider_name for T {}
};
Ok(output)
}
pub struct BindInput {
pub trait_ty: syn::Type,
pub concrete_ty: syn::Type,
}
impl syn::parse::Parse for BindInput {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let trait_ty: syn::Type = input.parse()?;
input.parse::<syn::Token![=>]>()?;
let concrete_ty: syn::Type = input.parse()?;
Ok(Self {
trait_ty,
concrete_ty,
})
}
}
pub fn expand_bind(input: BindInput) -> syn::Result<TokenStream> {
let trait_ty = &input.trait_ty;
let concrete_ty = &input.concrete_ty;
let slug = quote!(#concrete_ty)
.to_string()
.replace(['<', '>', ':', ' '], "_");
let type_id_fn = syn::Ident::new(
&format!("__bind_type_id_{slug}"),
proc_macro2::Span::call_site(),
);
let provide_fn = syn::Ident::new(
&format!("__bind_provide_{slug}"),
proc_macro2::Span::call_site(),
);
let output = quote! {
#[doc(hidden)]
#[allow(non_snake_case)]
fn #type_id_fn() -> ::std::any::TypeId {
::std::any::TypeId::of::<::std::sync::Arc<#trait_ty>>()
}
#[doc(hidden)]
#[allow(non_snake_case)]
fn #provide_fn(
ctx: ::std::sync::Arc<injectable_rs_runtime::ResolveContext>,
) -> ::std::pin::Pin<Box<dyn ::std::future::Future<
Output = injectable_rs_runtime::InjectableResult<Box<dyn ::std::any::Any + Send>>
> + Send + 'static>> {
Box::pin(async move {
let value = <<#concrete_ty as injectable_rs_runtime::Injectable>::Provider
as injectable_rs_runtime::Provider<#concrete_ty>>::provide(&*ctx).await?;
let arc: ::std::sync::Arc<#trait_ty> = ::std::sync::Arc::new(value);
Ok(Box::new(arc) as Box<dyn ::std::any::Any + Send>)
})
}
injectable_rs_runtime::inventory::submit! {
injectable_rs_runtime::InjectableArcFactory::new_const(
stringify!(#concrete_ty),
#type_id_fn,
#provide_fn,
)
}
};
Ok(output)
}