use proc_macro::TokenStream;
use quote::{quote, ToTokens};
use syn::parse::{Parse, ParseStream};
use syn::{parse_macro_input, ImplItem, ImplItemFn, ItemImpl};
use syn::{punctuated::Punctuated, Ident, LitStr, Token};
#[derive(Debug)]
enum ServiceArgKind {
Name(String),
}
#[derive(Debug, Default)]
struct ServiceArgs {
name: Option<String>,
}
impl Parse for ServiceArgs {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut args = ServiceArgs::default();
let punct: Punctuated<ArgItem, Token![,]> =
input.parse_terminated(ArgItem::parse, Token![,])?;
for item in punct {
match item.kind {
ServiceArgKind::Name(v) => {
if args.name.is_some() {
return Err(syn::Error::new(item.span, "duplicate name"));
}
args.name = Some(v);
}
}
}
Ok(args)
}
}
struct ArgItem {
kind: ServiceArgKind,
span: proc_macro2::Span,
}
impl Parse for ArgItem {
fn parse(input: ParseStream) -> syn::Result<Self> {
let ident: Ident = input.parse()?;
let span = ident.span();
if ident == "name" {
let _eq: Token![=] = input.parse()?;
let lit: LitStr = input.parse()?;
return Ok(ArgItem {
kind: ServiceArgKind::Name(lit.value()),
span,
});
}
Err(syn::Error::new(
span,
"unsupported #[service] argument; expected name=...",
))
}
}
#[proc_macro_attribute]
pub fn service(attr: TokenStream, item: TokenStream) -> TokenStream {
let parsed_args = if attr.is_empty() {
ServiceArgs::default()
} else {
parse_macro_input!(attr as ServiceArgs)
};
let name_block = parse_macro_input!(item as ItemImpl);
if name_block.trait_.is_some() {
return syn::Error::new_spanned(
&name_block.self_ty,
"#[service] must be applied to an inherent impl (e.g. impl Type { ... })",
)
.to_compile_error()
.into();
}
if !name_block.generics.params.is_empty() {
return syn::Error::new_spanned(
&name_block.generics,
"#[service] does not currently support generic impl blocks",
)
.to_compile_error()
.into();
}
let self_ty = name_block.self_ty.clone();
let mut has_new = false;
for item in &name_block.items {
if let ImplItem::Fn(ImplItemFn { sig, .. }) = item {
if sig.ident == "new" && sig.inputs.is_empty() {
has_new = true;
break;
}
}
}
if !has_new {
return syn::Error::new_spanned(
&name_block.self_ty,
"No zero-arg `new()` found in this impl block; define one for #[service]",
)
.to_compile_error()
.into();
}
let name_tokens = if let Some(n) = &parsed_args.name {
quote!(#n)
} else {
let type_name = self_ty.to_token_stream().to_string().replace(' ', "");
quote!(#type_name)
};
let gen = quote! { #name_block inventory::submit! { allora::ServiceDescriptor { name: #name_tokens, constructor: || { use std::sync::Arc; Arc::new(#self_ty::new()) as Arc<dyn allora::Service> } } } };
gen.into()
}