zink_codegen/
event.rs

1use heck::ToSnakeCase;
2use proc_macro::TokenStream;
3use proc_macro2::{Ident, Span};
4use quote::{quote, ToTokens};
5use syn::{
6    parse::Parse, parse_quote, punctuated::Punctuated, spanned::Spanned, Arm, Data, DataEnum,
7    DeriveInput, Expr, ExprMatch, Fields, FnArg, ImplItemFn, ItemFn, LitByteStr, Result, Token,
8    Type, Variant, Visibility,
9};
10
11/// Expand the event interface with better error handling
12pub fn parse(item: DeriveInput) -> TokenStream {
13    let name = &item.ident;
14    let name_str = name.to_string();
15    let name_bytes = LitByteStr::new(name_str.as_bytes(), Span::call_site());
16
17    // 1. Check if the name is too long
18    if name_str.len() > 32 {
19        panic!("Event name too long: {name_str}");
20    }
21
22    // 2. Ensure we are working with an enum
23    let Data::Enum(event_enum) = &item.data else {
24        panic!("Event can only be derived for enums");
25    };
26
27    // 3. Generate variant implementations
28    let mut expr_match: ExprMatch = parse_quote!(match self {});
29    let variant_fns = event_enum
30        .variants
31        .iter()
32        .map(|variant| impl_variant_fns(variant, &mut expr_match))
33        .collect::<Vec<_>>();
34
35    // 4. Generate the impl block
36    quote! {
37        impl #name {
38            /// Name of the event
39            pub const fn name() -> &'static [u8] {
40                #name_bytes
41            }
42
43            /// Emit the event name
44            pub fn emit_name() {
45                unsafe { zink::ffi::evm::log0(Self::name()) }
46            }
47
48            #(#variant_fns)*
49
50            /// Emit the event
51            pub fn emit(self) {
52                #expr_match
53            }
54        }
55    }
56    .into()
57}
58
59/// Generate Variant Implementation with validation
60fn impl_variant_fns(variant: &Variant, expr_match: &mut ExprMatch) -> ImplItemFn {
61    let name = &variant.ident;
62    let topic = variant.fields.len();
63
64    // Parse function inputs
65    let mut inputs: Punctuated<FnArg, Token![,]> = Punctuated::new();
66    let mut args: Vec<Ident> = Vec::new();
67    for (index, field) in variant.fields.iter().enumerate() {
68        let var = field
69            .ident
70            .clone()
71            .unwrap_or(Ident::new(&format!("param_{index}"), Span::call_site()));
72        let ty = &field.ty;
73
74        args.push(var.clone());
75        inputs.push(FnArg::Typed(parse_quote!(#var: #ty)));
76    }
77
78    // Generate the snake case name
79    let name_snake: Ident = Ident::new(&name.to_string().to_snake_case(), Span::call_site());
80
81    // Generate the match arm
82    let arm: Arm = parse_quote! {
83        Self::#name( #(#args),* ) => Self::#name_snake( #(#args),* ),
84    };
85    expr_match.arms.push(arm);
86
87    // Generate the impl block
88    let logn = Ident::new(&format!("log{topic}"), Span::call_site());
89    let args = args
90        .iter()
91        .map(|arg| quote!(#arg.bytes32()))
92        .collect::<Vec<_>>();
93    parse_quote! {
94        pub fn #name_snake(#inputs) {
95            unsafe {zink::ffi::evm::#logn(#(#args),*, &Self::name()) }
96        }
97    }
98}