1use proc_macro::TokenStream;
2use quote::{format_ident, quote};
3use syn::{FnArg, ItemFn, PatType, Type, parse_macro_input};
4
5#[proc_macro_attribute]
6pub fn event_listener(_attr: TokenStream, item: TokenStream) -> TokenStream {
7 let input_fn = parse_macro_input!(item as ItemFn);
8
9 let args = &input_fn.sig.inputs;
11 if args.len() != 1 {
12 return syn::Error::new_spanned(&input_fn.sig.inputs, "#[event_listener] functions must take exactly one argument (event)")
13 .to_compile_error()
14 .into();
15 }
16
17 let is_async = input_fn.sig.asyncness.is_some();
18
19 let (arg_pat, arg_ty, is_ref): (Box<syn::Pat>, Box<Type>, bool) = match args.first().unwrap() {
21 FnArg::Typed(PatType { pat, ty, .. }) => {
22 let is_ref = matches!(**ty, Type::Reference(_));
23 (pat.clone(), ty.clone(), is_ref)
24 }
25 other => {
26 return syn::Error::new_spanned(other, "unsupported argument kind").to_compile_error().into();
27 }
28 };
29
30 let event_ty: Box<Type> = match *arg_ty {
32 Type::Reference(ref tr) => tr.elem.clone(),
33 ref t => Box::new(t.clone()),
34 };
35
36 let fn_ident = &input_fn.sig.ident;
38 let registrar_ident = format_ident!("__jaeb_register_{}", fn_ident);
39
40 let register_body = if is_async {
42 if is_ref {
44 syn::Error::new_spanned(
46 &input_fn.sig.inputs,
47 "async #[event_listener] must take the event by value (e.g., e: MyEvent)",
48 )
49 .to_compile_error()
50 } else {
51 quote! {
52 bus.subscribe_async(|#arg_pat: #event_ty| async move {
54 #fn_ident(#arg_pat).await;
55 }).await;
56 }
57 }
58 } else {
59 if !is_ref {
61 return syn::Error::new_spanned(
62 &input_fn.sig.inputs,
63 "sync #[event_listener] must take the event by reference (e.g., e: &MyEvent)",
64 )
65 .to_compile_error()
66 .into();
67 }
68 quote! {
69 bus.subscribe_sync::<#event_ty, _>(|#arg_pat| {
70 #fn_ident(#arg_pat);
71 }).await;
72 }
73 };
74
75 let expanded = quote! {
77 #input_fn
78
79 #[doc(hidden)]
80 fn #registrar_ident(bus: &::jaeb::EventBus) -> ::jaeb::RegistrarFuture<'_> {
81 Box::pin(async move {
82 #register_body
83 })
84 }
85
86 ::jaeb::_private::inventory::submit!(::jaeb::ListenerRegistrar { func: #registrar_ident });
88 };
89
90 expanded.into()
91}
92
93#[proc_macro]
94pub fn bootstrap_listeners(input: TokenStream) -> TokenStream {
95 let stream = proc_macro2::TokenStream::from(input);
98 let expanded = quote! {
99 ::jaeb::bootstrap_listeners(#stream).await
100 };
101 TokenStream::from(expanded)
102}