1use proc_macro::TokenStream;
2use quote::{quote, ToTokens};
3use syn::parse::{Parse, ParseStream};
4use syn::{parse_macro_input, ImplItem, ImplItemFn, ItemImpl};
5use syn::{punctuated::Punctuated, Ident, LitStr, Token};
6
7#[derive(Debug)]
8enum ServiceArgKind {
9 Name(String),
10}
11
12#[derive(Debug, Default)]
13struct ServiceArgs {
14 name: Option<String>,
15}
16
17impl Parse for ServiceArgs {
18 fn parse(input: ParseStream) -> syn::Result<Self> {
19 let mut args = ServiceArgs::default();
20 let punct: Punctuated<ArgItem, Token![,]> =
21 input.parse_terminated(ArgItem::parse, Token![,])?;
22 for item in punct {
23 match item.kind {
24 ServiceArgKind::Name(v) => {
25 if args.name.is_some() {
26 return Err(syn::Error::new(item.span, "duplicate name"));
27 }
28 args.name = Some(v);
29 }
30 }
31 }
32 Ok(args)
33 }
34}
35
36struct ArgItem {
37 kind: ServiceArgKind,
38 span: proc_macro2::Span,
39}
40impl Parse for ArgItem {
41 fn parse(input: ParseStream) -> syn::Result<Self> {
42 let ident: Ident = input.parse()?;
43 let span = ident.span();
44 if ident == "name" {
45 let _eq: Token![=] = input.parse()?;
46 let lit: LitStr = input.parse()?;
47 return Ok(ArgItem {
48 kind: ServiceArgKind::Name(lit.value()),
49 span,
50 });
51 }
52 Err(syn::Error::new(
53 span,
54 "unsupported #[service] argument; expected name=...",
55 ))
56 }
57}
58
59#[proc_macro_attribute]
91pub fn service(attr: TokenStream, item: TokenStream) -> TokenStream {
92 let parsed_args = if attr.is_empty() {
93 ServiceArgs::default()
94 } else {
95 parse_macro_input!(attr as ServiceArgs)
96 };
97 let name_block = parse_macro_input!(item as ItemImpl);
98
99 if name_block.trait_.is_some() {
100 return syn::Error::new_spanned(
101 &name_block.self_ty,
102 "#[service] must be applied to an inherent impl (e.g. impl Type { ... })",
103 )
104 .to_compile_error()
105 .into();
106 }
107 if !name_block.generics.params.is_empty() {
108 return syn::Error::new_spanned(
109 &name_block.generics,
110 "#[service] does not currently support generic impl blocks",
111 )
112 .to_compile_error()
113 .into();
114 }
115
116 let self_ty = name_block.self_ty.clone();
117
118 let mut has_new = false;
119 for item in &name_block.items {
121 if let ImplItem::Fn(ImplItemFn { sig, .. }) = item {
122 if sig.ident == "new" && sig.inputs.is_empty() {
123 has_new = true;
124 break;
125 }
126 }
127 }
128 if !has_new {
129 return syn::Error::new_spanned(
130 &name_block.self_ty,
131 "No zero-arg `new()` found in this impl block; define one for #[service]",
132 )
133 .to_compile_error()
134 .into();
135 }
136
137 let name_tokens = if let Some(n) = &parsed_args.name {
138 quote!(#n)
139 } else {
140 let type_name = self_ty.to_token_stream().to_string().replace(' ', "");
141 quote!(#type_name)
142 };
143 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> } } } };
144 gen.into()
145}