#![forbid(unsafe_code)]
mod attrs;
mod container_macro;
mod derive;
mod factory_fn;
mod injectable_impl;
mod metadata;
mod provider_gen;
use proc_macro::TokenStream;
use syn::parse_macro_input;
#[proc_macro]
pub fn bind(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as derive::BindInput);
match derive::expand_bind(input) {
Ok(tokens) => tokens.into(),
Err(err) => err.to_compile_error().into(),
}
}
#[proc_macro]
pub fn container(input: TokenStream) -> TokenStream {
container_macro::expand_container(input.into()).into()
}
#[proc_macro_attribute]
pub fn injectable(attr: TokenStream, item: TokenStream) -> TokenStream {
let attr2: proc_macro2::TokenStream = attr.into();
let item2: proc_macro2::TokenStream = item.into();
match first_attr_ident(&attr2).as_deref() {
Some("trait") => {
match syn::parse2::<syn::ItemTrait>(item2) {
Ok(input) => match derive::expand_injectable_trait(input) {
Ok(tokens) => tokens.into(),
Err(e) => e.to_compile_error().into(),
},
Err(_) => syn::Error::new(
proc_macro2::Span::call_site(),
"#[injectable(trait)] can only be applied to a trait",
)
.to_compile_error()
.into(),
}
}
Some("factory") => {
match syn::parse2::<syn::ItemFn>(item2) {
Ok(input) => match factory_fn::expand_inject_fn(input) {
Ok(tokens) => tokens.into(),
Err(e) => e.to_compile_error().into(),
},
Err(_) => syn::Error::new(
proc_macro2::Span::call_site(),
"#[injectable(factory)] can only be applied to a function",
)
.to_compile_error()
.into(),
}
}
_ => {
if let Ok(mut struct_item) = syn::parse2::<syn::ItemStruct>(item2.clone()) {
let normalized = normalize_scope_attr(attr2.clone());
let fake_derive_input = quote::quote! {
#[injectable(#normalized)]
#item2
};
match syn::parse2::<syn::DeriveInput>(fake_derive_input) {
Ok(input) => match derive::expand_derive_injectable(input) {
Ok(tokens) => {
strip_inject_attrs_from_struct(&mut struct_item);
return quote::quote! { #struct_item #tokens }.into();
}
Err(e) => return e.to_compile_error().into(),
},
Err(e) => return e.to_compile_error().into(),
}
}
if syn::parse2::<syn::ItemImpl>(item2.clone()).is_ok() {
let normalized = normalize_scope_attr(attr2);
return match injectable_impl::expand_injectable_impl(normalized, item2) {
Ok(tokens) => tokens.into(),
Err(e) => e.to_compile_error().into(),
};
}
syn::Error::new(
proc_macro2::Span::call_site(),
"#[injectable] can only be applied to a struct, impl block, trait \
(with `#[injectable(trait)]`), or function (with `#[injectable(factory)]`)",
)
.to_compile_error()
.into()
}
}
}
fn first_attr_ident(attr: &proc_macro2::TokenStream) -> Option<String> {
attr.clone().into_iter().next().and_then(|tt| {
if let proc_macro2::TokenTree::Ident(id) = tt {
Some(id.to_string())
} else {
None
}
})
}
fn strip_inject_attrs_from_struct(s: &mut syn::ItemStruct) {
match &mut s.fields {
syn::Fields::Named(named) => {
for field in named.named.iter_mut() {
field.attrs.retain(|a| !a.path().is_ident("injectable"));
}
}
syn::Fields::Unnamed(unnamed) => {
for field in unnamed.unnamed.iter_mut() {
field.attrs.retain(|a| !a.path().is_ident("injectable"));
}
}
syn::Fields::Unit => {}
}
}
fn normalize_scope_attr(attr: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
use proc_macro2::{TokenStream, TokenTree};
use quote::quote;
let tokens: Vec<TokenTree> = attr.into_iter().collect();
let mut out = TokenStream::new();
let mut i = 0;
while i < tokens.len() {
let tok = &tokens[i];
if let TokenTree::Ident(kw) = tok {
if kw == "scope"
&& i + 2 < tokens.len()
&& matches!(tokens.get(i + 1), Some(TokenTree::Punct(_)))
&& matches!(tokens.get(i + 2), Some(TokenTree::Ident(_)))
{
if let Some(TokenTree::Ident(scope_ident)) = tokens.get(i + 2) {
let name = scope_ident.to_string();
let scope_str = match name.as_str() {
"Singleton" => "singleton",
"Transient" => "transient",
"RequestScoped" | "Request" => "request",
other => other,
};
let lit = proc_macro2::Literal::string(scope_str);
out.extend(quote! { scope = #lit });
i += 3;
continue;
}
}
}
out.extend(std::iter::once(tok.clone()));
i += 1;
}
out
}