Skip to main content

iris_ztd_derive/
lib.rs

1use proc_macro::TokenStream;
2use proc_macro2::Ident;
3use proc_macro_crate::FoundCrate;
4use quote::{format_ident, quote};
5use syn::{parse_macro_input, token::PathSep, Data, DeriveInput, Fields};
6
7fn get_crate_path() -> proc_macro2::TokenStream {
8    let (col, ident) = crate_path_ident();
9    quote!(#col #ident)
10}
11
12fn crate_path_ident() -> (Option<PathSep>, Ident) {
13    match crate_path_fixed() {
14        Some(FoundCrate::Itself) => (None, format_ident!("crate")),
15        Some(FoundCrate::Name(name)) => (Some(Default::default()), format_ident!("{}", name)),
16        None => (None, format_ident!("iris_ztd")),
17    }
18}
19
20fn crate_path_fixed() -> Option<FoundCrate> {
21    let found_crate = proc_macro_crate::crate_name("iris-ztd").ok()?;
22
23    let ret = match found_crate {
24        FoundCrate::Itself => {
25            let has_doc_env = std::env::vars().any(|(k, _)| {
26                k == "UNSTABLE_RUSTDOC_TEST_LINE" || k == "UNSTABLE_RUSTDOC_TEST_PATH"
27            });
28
29            if has_doc_env {
30                FoundCrate::Name("iris_ztd".to_string())
31            } else {
32                FoundCrate::Itself
33            }
34        }
35        x => x,
36    };
37
38    Some(ret)
39}
40
41/// Derive macro for implementing the Hashable trait.
42///
43/// This macro automatically implements Hashable for structs by creating
44/// nested tuples of field references and calling .hash() on them.
45///
46/// # Example
47///
48/// ```ignore
49/// #[derive(Hashable)]
50/// struct MyStruct {
51///     x: u64,
52///     y: u64,
53///     z: u64,
54/// }
55/// ```
56///
57/// Expands to:
58///
59/// ```ignore
60/// impl Hashable for MyStruct {
61///     fn hash(&self) -> Digest {
62///         (&self.x, &(&self.y, &self.z)).hash()
63///     }
64/// }
65/// ```
66#[proc_macro_derive(Hashable)]
67pub fn derive_hashable(input: TokenStream) -> TokenStream {
68    let input = parse_macro_input!(input as DeriveInput);
69    let name = &input.ident;
70    let crate_path = get_crate_path();
71
72    let mut generics = input.generics.clone();
73    let where_clause = generics.make_where_clause();
74
75    let [hash_expr, leaf_expr] = [quote!(hash), quote!(leaf_count)].map(|func| {
76        match &input.data {
77            Data::Struct(data) => {
78                for field in &data.fields {
79                    let ty = &field.ty;
80                    where_clause
81                        .predicates
82                        .push(syn::parse_quote!(#ty: #crate_path::Hashable));
83                }
84
85                match &data.fields {
86                    Fields::Named(fields) => {
87                        let field_names: Vec<_> = fields.named.iter().map(|f| &f.ident).collect();
88
89                        if field_names.is_empty() {
90                            // Empty struct hashes as unit
91                            quote! { ().#func() }
92                        } else if field_names.len() == 1 {
93                            // Single field: just hash the field directly
94                            let field = &field_names[0];
95                            quote! { self.#field.#func() }
96                        } else {
97                            // Multiple fields: create nested tuples
98                            let expr = build_nested_tuple_refs(&field_names);
99                            quote! { #expr.#func() }
100                        }
101                    }
102                    Fields::Unnamed(fields) => {
103                        let field_count = fields.unnamed.len();
104
105                        if field_count == 0 {
106                            quote! { ().#func() }
107                        } else if field_count == 1 {
108                            quote! { self.0.#func() }
109                        } else {
110                            // Build nested tuples for tuple structs using indices
111                            let indices: Vec<_> = (0..field_count).map(syn::Index::from).collect();
112                            let expr = build_nested_tuple_refs_indexed(&indices);
113                            quote! { #expr.#func() }
114                        }
115                    }
116                    Fields::Unit => {
117                        quote! { ().#func() }
118                    }
119                }
120            }
121            Data::Enum(_) => {
122                syn::Error::new_spanned(&input, "Hashable derive macro does not support enums yet")
123                    .to_compile_error()
124            }
125            Data::Union(_) => {
126                syn::Error::new_spanned(&input, "Hashable derive macro does not support unions")
127                    .to_compile_error()
128            }
129        }
130    });
131
132    let pair_expr = match &input.data {
133        Data::Struct(data) => {
134            for field in &data.fields {
135                let ty = &field.ty;
136                where_clause
137                    .predicates
138                    .push(syn::parse_quote!(#ty: #crate_path::Hashable));
139            }
140
141            match &data.fields {
142                Fields::Named(fields) => {
143                    let field_names: Vec<_> = fields.named.iter().map(|f| &f.ident).collect();
144
145                    if field_names.is_empty() {
146                        // Empty struct hashes as unit
147                        quote! { ().hashable_pair() }
148                    } else if field_names.len() == 1 {
149                        // Single field: just hash the field directly
150                        let field = &field_names[0];
151                        quote! { self.#field.hashable_pair() }
152                    } else {
153                        // Multiple fields: create nested tuples
154                        let expr = build_nested_tuple_refs(&field_names);
155                        quote! { Some(#expr) }
156                    }
157                }
158                Fields::Unnamed(fields) => {
159                    let field_count = fields.unnamed.len();
160
161                    if field_count == 0 {
162                        quote! { ().hashable_pair() }
163                    } else if field_count == 1 {
164                        quote! { self.0.hashable_pair() }
165                    } else {
166                        // Build nested tuples for tuple structs using indices
167                        let indices: Vec<_> = (0..field_count).map(syn::Index::from).collect();
168                        let expr = build_nested_tuple_refs_indexed(&indices);
169                        quote! { Some(#expr) }
170                    }
171                }
172                Fields::Unit => {
173                    quote! { ().hashable_pair() }
174                }
175            }
176        }
177        Data::Enum(_) => {
178            return syn::Error::new_spanned(
179                &input,
180                "Hashable derive macro does not support enums yet",
181            )
182            .to_compile_error()
183            .into();
184        }
185        Data::Union(_) => {
186            return syn::Error::new_spanned(
187                &input,
188                "Hashable derive macro does not support unions",
189            )
190            .to_compile_error()
191            .into();
192        }
193    };
194
195    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
196
197    TokenStream::from(quote! {
198        impl #impl_generics #crate_path::Hashable for #name #ty_generics #where_clause {
199            fn hash(&self) -> #crate_path::Digest {
200                #hash_expr
201            }
202
203            fn leaf_count(&self) -> usize {
204                #leaf_expr
205            }
206
207            fn hashable_pair(&self) -> Option<(impl #crate_path::Hashable + '_, impl #crate_path::Hashable + '_)> {
208                #pair_expr
209            }
210        }
211    })
212}
213
214/// Derive macro for implementing the `NounEncode` trait.
215#[proc_macro_derive(NounEncode, attributes(noun_tag))]
216pub fn derive_noun_encode(input: TokenStream) -> TokenStream {
217    let input = parse_macro_input!(input as DeriveInput);
218    let name = &input.ident;
219    let crate_path = get_crate_path();
220
221    let mut generics = input.generics.clone();
222    let where_clause = generics.make_where_clause();
223
224    let impl_body = match &input.data {
225        Data::Struct(data) => {
226            for field in &data.fields {
227                let ty = &field.ty;
228                where_clause
229                    .predicates
230                    .push(syn::parse_quote!(#ty: #crate_path::NounEncode));
231            }
232
233            match &data.fields {
234                Fields::Named(fields) => {
235                    let field_names: Vec<_> = fields.named.iter().map(|f| &f.ident).collect();
236
237                    if field_names.is_empty() {
238                        quote! { #crate_path::NounEncode::to_noun(&0u64) }
239                    } else if field_names.len() == 1 {
240                        let field = &field_names[0];
241                        quote! { #crate_path::NounEncode::to_noun(&self.#field) }
242                    } else {
243                        let tuple_expr = build_nested_tuple_refs(&field_names);
244                        quote! { #crate_path::NounEncode::to_noun(&#tuple_expr) }
245                    }
246                }
247                Fields::Unnamed(fields) => {
248                    let field_count = fields.unnamed.len();
249
250                    if field_count == 0 {
251                        quote! { #crate_path::NounEncode::to_noun(&0u64) }
252                    } else if field_count == 1 {
253                        quote! { #crate_path::NounEncode::to_noun(&self.0) }
254                    } else {
255                        let indices: Vec<_> = (0..field_count).map(syn::Index::from).collect();
256                        let tuple_expr = build_nested_tuple_refs_indexed(&indices);
257                        quote! { #crate_path::NounEncode::to_noun(&#tuple_expr) }
258                    }
259                }
260                Fields::Unit => quote! { #crate_path::NounEncode::to_noun(&0u64) },
261            }
262        }
263        Data::Enum(data) => {
264            let mut match_arms = Vec::new();
265
266            for variant in &data.variants {
267                let variant_ident = &variant.ident;
268                let mut tag_value = None;
269
270                for attr in &variant.attrs {
271                    if attr.path().is_ident("noun_tag") {
272                        if let Ok(lit) = attr.parse_args::<syn::LitStr>() {
273                            tag_value = Some(lit.value());
274                        }
275                    }
276                }
277
278                let tag_value = tag_value.unwrap_or_else(|| variant_ident.to_string());
279
280                match &variant.fields {
281                    Fields::Unit => {
282                        match_arms.push(quote! {
283                            Self::#variant_ident => #crate_path::NounEncode::to_noun(&#tag_value),
284                        });
285                    }
286                    Fields::Unnamed(fields) => {
287                        let field_count = fields.unnamed.len();
288                        if field_count == 0 {
289                            match_arms.push(quote! {
290                                Self::#variant_ident => #crate_path::NounEncode::to_noun(&#tag_value),
291                            });
292                        } else if field_count == 1 {
293                            match_arms.push(quote! {
294                                Self::#variant_ident(v0) => {
295                                    let tag_noun = #crate_path::NounEncode::to_noun(&#tag_value);
296                                    let rest_noun = #crate_path::NounEncode::to_noun(v0);
297                                    #crate_path::NounEncode::to_noun(&(tag_noun, rest_noun))
298                                }
299                            });
300                        } else {
301                            let idents: Vec<_> =
302                                (0..field_count).map(|i| format_ident!("v{}", i)).collect();
303                            let ref_idents: Vec<_> = idents.iter().collect();
304                            let tuple_expr = build_nested_tuple_expr_for_idents(&ref_idents);
305                            match_arms.push(quote! {
306                                Self::#variant_ident( #( #idents ),* ) => {
307                                    let tag_noun = #crate_path::NounEncode::to_noun(&#tag_value);
308                                    let rest_noun = #crate_path::NounEncode::to_noun(&#tuple_expr);
309                                    #crate_path::NounEncode::to_noun(&(tag_noun, rest_noun))
310                                }
311                            });
312                        }
313                    }
314                    Fields::Named(fields) => {
315                        let field_names: Vec<_> = fields
316                            .named
317                            .iter()
318                            .map(|f| f.ident.as_ref().unwrap())
319                            .collect();
320                        if field_names.is_empty() {
321                            match_arms.push(quote! {
322                                Self::#variant_ident {} => #crate_path::NounEncode::to_noun(&#tag_value),
323                            });
324                        } else if field_names.len() == 1 {
325                            let field = field_names[0];
326                            match_arms.push(quote! {
327                                Self::#variant_ident { #field } => {
328                                    let tag_noun = #crate_path::NounEncode::to_noun(&#tag_value);
329                                    let rest_noun = #crate_path::NounEncode::to_noun(#field);
330                                    #crate_path::NounEncode::to_noun(&(tag_noun, rest_noun))
331                                }
332                            });
333                        } else {
334                            let tuple_expr = build_nested_tuple_expr_for_idents(&field_names);
335                            match_arms.push(quote! {
336                                Self::#variant_ident { #( #field_names ),* } => {
337                                    let tag_noun = #crate_path::NounEncode::to_noun(&#tag_value);
338                                    let rest_noun = #crate_path::NounEncode::to_noun(&#tuple_expr);
339                                    #crate_path::NounEncode::to_noun(&(tag_noun, rest_noun))
340                                }
341                            });
342                        }
343                    }
344                }
345            }
346
347            quote! {
348                match self {
349                    #( #match_arms )*
350                }
351            }
352        }
353        Data::Union(_) => {
354            return syn::Error::new_spanned(
355                &input,
356                "NounEncode derive macro does not support unions",
357            )
358            .to_compile_error()
359            .into();
360        }
361    };
362
363    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
364
365    TokenStream::from(quote! {
366        impl #impl_generics #crate_path::NounEncode for #name #ty_generics #where_clause {
367            fn to_noun(&self) -> #crate_path::Noun {
368                #impl_body
369            }
370        }
371    })
372}
373
374/// Derive macro for implementing the `NounDecode` trait.
375#[proc_macro_derive(NounDecode, attributes(noun_tag))]
376pub fn derive_noun_decode(input: TokenStream) -> TokenStream {
377    let input = parse_macro_input!(input as DeriveInput);
378    let name = &input.ident;
379    let crate_path = get_crate_path();
380
381    let mut generics = input.generics.clone();
382    let where_clause = generics.make_where_clause();
383
384    let impl_body = match &input.data {
385        Data::Struct(data) => {
386            for field in &data.fields {
387                let ty = &field.ty;
388                where_clause
389                    .predicates
390                    .push(syn::parse_quote!(#ty: #crate_path::NounDecode));
391            }
392
393            match &data.fields {
394                Fields::Named(fields) => {
395                    let field_names: Vec<_> = fields.named.iter().map(|f| &f.ident).collect();
396
397                    if field_names.is_empty() {
398                        quote! {
399                            if noun == #crate_path::noun::atom(0) {
400                                Some(Self)
401                            } else {
402                                None
403                            }
404                        }
405                    } else {
406                        quote! {
407                            let (#( #field_names ),* ) = #crate_path::NounDecode::from_noun(noun)?;
408                            Some(Self {
409                                #( #field_names ),*
410                            })
411                        }
412                    }
413                }
414                Fields::Unnamed(fields) => {
415                    let field_count = fields.unnamed.len();
416
417                    if field_count == 0 {
418                        quote! {
419                            if noun == #crate_path::noun::atom(0) {
420                                Some(Self)
421                            } else {
422                                None
423                            }
424                        }
425                    } else if field_count == 1 {
426                        quote! { Some(Self(#crate_path::NounDecode::from_noun(noun)?)) }
427                    } else {
428                        let indices: Vec<_> = (0..field_count).map(syn::Index::from).collect();
429                        quote! {
430                            let tup = #crate_path::NounDecode::from_noun(noun)?;
431                            Some(Self(
432                                #( tup.#indices ),*
433                            ))
434                        }
435                    }
436                }
437                Fields::Unit => quote! {
438                    if noun == #crate_path::noun::atom(0) {
439                        Some(Self)
440                    } else {
441                        None
442                    }
443                },
444            }
445        }
446        Data::Enum(data) => {
447            let mut cell_match_arms = Vec::new();
448            let mut atom_match_arms = Vec::new();
449
450            for variant in &data.variants {
451                let variant_ident = &variant.ident;
452                let mut tag_value = None;
453
454                for attr in &variant.attrs {
455                    if attr.path().is_ident("noun_tag") {
456                        if let Ok(lit) = attr.parse_args::<syn::LitStr>() {
457                            tag_value = Some(lit.value());
458                        }
459                    }
460                }
461
462                let tag_value = tag_value.unwrap_or_else(|| variant_ident.to_string());
463
464                match &variant.fields {
465                    Fields::Unit => {
466                        atom_match_arms.push(quote! {
467                            #tag_value => Some(Self::#variant_ident),
468                        });
469                    }
470                    Fields::Unnamed(fields) => {
471                        let field_count = fields.unnamed.len();
472                        if field_count == 0 {
473                            atom_match_arms.push(quote! {
474                                #tag_value => Some(Self::#variant_ident()),
475                            });
476                        } else if field_count == 1 {
477                            cell_match_arms.push(quote! {
478                                #tag_value => {
479                                    Some(Self::#variant_ident(#crate_path::NounDecode::from_noun(&rest)?))
480                                }
481                            });
482                        } else {
483                            let idents: Vec<_> =
484                                (0..field_count).map(|i| format_ident!("v{}", i)).collect();
485                            cell_match_arms.push(quote! {
486                                #tag_value => {
487                                    let ( #( #idents ),* ) = #crate_path::NounDecode::from_noun(&rest)?;
488                                    Some(Self::#variant_ident( #( #idents ),* ))
489                                }
490                            });
491                        }
492                    }
493                    Fields::Named(fields) => {
494                        let field_names: Vec<_> = fields.named.iter().map(|f| &f.ident).collect();
495                        if field_names.is_empty() {
496                            atom_match_arms.push(quote! {
497                                #tag_value => Some(Self::#variant_ident {}),
498                            });
499                        } else {
500                            cell_match_arms.push(quote! {
501                                #tag_value => {
502                                    let ( #( #field_names ),* ) = #crate_path::NounDecode::from_noun(&rest)?;
503                                    Some(Self::#variant_ident { #( #field_names ),* })
504                                }
505                            });
506                        }
507                    }
508                }
509            }
510
511            quote! {
512                match noun {
513                    #crate_path::Noun::Atom(atom) => {
514                        let a = atom.to_le_bytes();
515                        let s = core::str::from_utf8(&a).ok()?;
516                        match s {
517                            #( #atom_match_arms )*
518                            _ => None,
519                        }
520                    }
521                    #crate_path::Noun::Cell(ref a, rest) => {
522                        if let #crate_path::Noun::Atom(a) = &**a {
523                            let a = a.to_le_bytes();
524                            let tag = core::str::from_utf8(&a).ok()?;
525                            match tag {
526                                #( #cell_match_arms )*
527                                _ => None,
528                            }
529                        } else {
530                            None
531                        }
532                    }
533                }
534            }
535        }
536        Data::Union(_) => {
537            return syn::Error::new_spanned(
538                &input,
539                "NounDecode derive macro does not support unions",
540            )
541            .to_compile_error()
542            .into();
543        }
544    };
545
546    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
547
548    TokenStream::from(quote! {
549        impl #impl_generics #crate_path::NounDecode for #name #ty_generics #where_clause {
550            fn from_noun(noun: &#crate_path::Noun) -> Option<Self> {
551                #impl_body
552            }
553        }
554    })
555}
556
557/// Build nested tuple references: (&self.x, (&self.y, &self.z))
558fn build_nested_tuple_refs(field_names: &[&Option<syn::Ident>]) -> proc_macro2::TokenStream {
559    let mut iter = field_names.iter().rev();
560    let last = iter.next().unwrap();
561
562    let mut result = quote! { &self.#last };
563
564    for field in iter {
565        result = quote! { (&self.#field, #result) };
566    }
567
568    result
569}
570
571/// Build nested tuple references for indices: (&self.0, (&self.1, &self.2))
572fn build_nested_tuple_refs_indexed(indices: &[syn::Index]) -> proc_macro2::TokenStream {
573    let mut iter = indices.iter().rev();
574    let last = iter.next().unwrap();
575
576    let mut result = quote! { &self.#last };
577
578    for index in iter {
579        result = quote! { (&self.#index, #result) };
580    }
581
582    result
583}
584
585/// Build nested tuple expressions for idents used in destructuring: (a, &(b, &c))
586fn build_nested_tuple_expr_for_idents(idents: &[&syn::Ident]) -> proc_macro2::TokenStream {
587    let mut iter = idents.iter().rev();
588    let last = iter.next().unwrap();
589
590    let mut result = quote! { #last };
591
592    for ident in iter {
593        result = quote! { (#ident, &#result) };
594    }
595
596    result
597}
598
599/// Helper to convert PascalCase to snake_case
600fn to_snake_case(s: &str) -> String {
601    let mut out = String::new();
602    for (i, c) in s.char_indices() {
603        if c.is_uppercase() {
604            if i > 0 && !s.as_bytes()[i - 1].is_ascii_uppercase() {
605                out.push('_');
606            }
607            out.push(c.to_ascii_lowercase());
608        } else {
609            out.push(c);
610        }
611    }
612    out
613}
614
615/// Helper to convert PascalCase or snake_case to lowerCamelCase
616fn to_lower_camel_case(s: &str) -> String {
617    let mut out = String::new();
618    let mut capitalize_next = false;
619    let mut first = true;
620    for c in s.chars() {
621        if c == '_' {
622            capitalize_next = true;
623        } else if first {
624            out.push(c.to_ascii_lowercase());
625            first = false;
626        } else if capitalize_next {
627            out.push(c.to_ascii_uppercase());
628            capitalize_next = false;
629        } else {
630            out.push(c);
631        }
632    }
633    out
634}
635
636/// Attribute macro `#[wasm_noun_codec]` to attach tsify attributes and create js codec functions.
637/// Supports `#[wasm_noun_codec(no_hash)]` to skip generating the `hash` function.
638#[proc_macro_attribute]
639pub fn wasm_noun_codec(attr: TokenStream, item: TokenStream) -> TokenStream {
640    let input = parse_macro_input!(item as DeriveInput);
641    let name = &input.ident;
642    let name_str = name.to_string();
643
644    let attr_str = attr.to_string();
645    let no_hash = attr_str.contains("no_hash");
646    let no_noun = attr_str.contains("no_noun");
647    let with_prove = attr_str.contains("with_prove");
648    let no_derive = attr_str.contains("no_derive");
649
650    let snake_name = to_snake_case(&name_str);
651    let camel_name = to_lower_camel_case(&name_str);
652
653    let to_noun_snake = format_ident!("{}_to_noun", snake_name);
654    let from_noun_snake = format_ident!("{}_from_noun", snake_name);
655    let hash_snake = format_ident!("{}_hash", snake_name);
656    let prove_snake = format_ident!("{}_prove", snake_name);
657
658    let to_noun_camel = format!("{}ToNoun", camel_name);
659    let from_noun_camel = format!("{}FromNoun", camel_name);
660    let hash_camel = format!("{}Hash", camel_name);
661    let prove_camel = format!("{}Prove", camel_name);
662
663    let mod_name = format_ident!("__wasm_noun_codec_{}", snake_name);
664
665    let crate_path = get_crate_path();
666
667    let derive_attrs = if no_derive {
668        quote! {}
669    } else {
670        quote! {
671            #[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
672            #[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
673        }
674    };
675
676    let hash_fn = if no_hash {
677        quote! {}
678    } else {
679        quote! {
680            #[wasm_bindgen(js_name = #hash_camel)]
681            pub fn #hash_snake(v: &#name) -> #crate_path::Digest {
682                #crate_path::Hashable::hash(v)
683            }
684        }
685    };
686
687    let prove_fn = if with_prove {
688        quote! {
689            /// Create a 0-indexed merkle proof for the type's subleaf.
690            ///
691            /// Note that unlike `prove-hashable-by-index:merkle`, which is 1-indexed, this method is 0-indexed.
692            #[wasm_bindgen(js_name = #prove_camel)]
693            pub fn #prove_snake(v: &#name, leaf_index: u32) -> #crate_path::MerkleProvenAxis {
694                #crate_path::MerkleProof::prove_hashable(v, leaf_index as usize)
695            }
696        }
697    } else {
698        quote! {}
699    };
700
701    let noun_fn = if no_noun {
702        quote! {}
703    } else {
704        quote! {
705            /// Convert into `Noun`.
706            #[wasm_bindgen(js_name = #to_noun_camel)]
707            pub fn #to_noun_snake(v: &#name) -> #crate_path::Noun {
708                #crate_path::NounEncode::to_noun(v)
709            }
710
711            /// Convert from `Noun`.
712            #[wasm_bindgen(js_name = #from_noun_camel)]
713            pub fn #from_noun_snake(noun: &#crate_path::Noun) -> ::core::result::Result<#name, JsValue> {
714                #crate_path::NounDecode::from_noun(noun)
715                    .ok_or_else(|| JsValue::from_str("Failed to decode noun"))
716            }
717        }
718    };
719
720    let expanded = quote! {
721        #derive_attrs
722        #input
723
724        #[cfg(feature = "wasm")]
725        mod #mod_name {
726            use super::*;
727            use wasm_bindgen::prelude::*;
728
729            #noun_fn
730            #hash_fn
731            #prove_fn
732        }
733    };
734
735    TokenStream::from(expanded)
736}
737
738/// Extract signatures from an `impl` block and generate corresponding
739/// `wasm_bindgen` exported functions within a private `cfg(feature = "wasm")` submodule.
740#[proc_macro_attribute]
741pub fn wasm_member_methods(_attr: TokenStream, item: TokenStream) -> TokenStream {
742    let mut input = parse_macro_input!(item as syn::ItemImpl);
743
744    // Determine target type name
745    let ty = &input.self_ty;
746    let type_name = if let syn::Type::Path(type_path) = &**ty {
747        if let Some(segment) = type_path.path.segments.last() {
748            segment.ident.to_string()
749        } else {
750            return syn::Error::new_spanned(ty, "Expected a type path")
751                .to_compile_error()
752                .into();
753        }
754    } else {
755        return syn::Error::new_spanned(ty, "Expected a type path")
756            .to_compile_error()
757            .into();
758    };
759
760    let snake_name = to_snake_case(&type_name);
761    let mod_name = format_ident!("__wasm_member_methods_{}", snake_name);
762
763    let mut generated_methods = Vec::new();
764
765    for item in &mut input.items {
766        if let syn::ImplItem::Fn(method) = item {
767            // Check visibility (only export pub methods)
768            if !matches!(method.vis, syn::Visibility::Public(_)) {
769                continue;
770            }
771
772            let sig = &method.sig;
773            let method_ident = &sig.ident;
774            let method_name_str = method_ident.to_string();
775
776            // Extract attributes, looking for transform_output
777            let mut transform_output: Option<(syn::Type, syn::Expr)> = None;
778
779            method.attrs.retain(|attr| {
780                if attr.path().is_ident("transform_output") {
781                    // Parse #[transform_output(Type, expr)]
782                    if let syn::Meta::List(meta_list) = &attr.meta {
783                        let tokens: Vec<_> = meta_list.tokens.clone().into_iter().collect();
784
785                        let mut ty_tokens = proc_macro2::TokenStream::new();
786                        let mut expr_tokens = proc_macro2::TokenStream::new();
787                        let mut in_ty = true;
788
789                        for t in tokens {
790                            if in_ty {
791                                if let proc_macro2::TokenTree::Punct(ref p) = t {
792                                    if p.as_char() == ',' {
793                                        in_ty = false;
794                                        continue;
795                                    }
796                                }
797                                ty_tokens.extend(std::iter::once(t));
798                            } else {
799                                expr_tokens.extend(std::iter::once(t));
800                            }
801                        }
802
803                        // Parse safely, fallback if fails
804                        if let Ok(ty) = syn::parse2(ty_tokens) {
805                            if let Ok(expr) = syn::parse2(expr_tokens) {
806                                transform_output = Some((ty, expr));
807                            }
808                        }
809                    }
810                    false // Remove this attribute
811                } else {
812                    true // Keep others
813                }
814            });
815
816            let camel_method_name = to_lower_camel_case(&method_name_str);
817            let struct_camel = to_lower_camel_case(&type_name);
818            let camel_method_capitalized = {
819                let mut c = camel_method_name.chars();
820                match c.next() {
821                    None => String::new(),
822                    Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
823                }
824            };
825            let js_name_combined = format!("{}{}", struct_camel, camel_method_capitalized);
826            let js_name_attr =
827                quote! { #[wasm_bindgen::prelude::wasm_bindgen(js_name = #js_name_combined)] };
828
829            // Extract doc comments
830            let doc_comments: Vec<_> = method
831                .attrs
832                .iter()
833                .filter(|a| a.path().is_ident("doc"))
834                .collect();
835
836            let mut args = Vec::new();
837            let mut pass_args = Vec::new();
838            let mut has_receiver = false;
839
840            for arg in &sig.inputs {
841                match arg {
842                    syn::FnArg::Receiver(r) => {
843                        has_receiver = true;
844                        let mutf = r.mutability;
845                        // Use the parsed `ty` instead of the string `type_name`
846                        if r.reference.is_some() {
847                            if mutf.is_some() {
848                                args.push(quote! { obj: &mut #ty });
849                            } else {
850                                args.push(quote! { obj: &#ty });
851                            }
852                        } else {
853                            args.push(quote! { obj: #ty });
854                        }
855                    }
856                    syn::FnArg::Typed(pat_type) => {
857                        let pat = &pat_type.pat;
858                        let pt_ty = &pat_type.ty;
859                        args.push(quote! { #pat: #pt_ty });
860                        pass_args.push(quote! { #pat });
861                    }
862                }
863            }
864
865            let call_expr = if has_receiver {
866                quote! { obj.#method_ident(#(#pass_args),*) }
867            } else {
868                quote! { <#ty>::#method_ident(#(#pass_args),*) }
869            };
870
871            let (final_output, body) =
872                if let Some((transform_ty, transform_expr)) = transform_output {
873                    let out = quote! { -> #transform_ty };
874                    let b = quote! {
875                        let out = #call_expr;
876                        #transform_expr
877                    };
878                    (out, b)
879                } else {
880                    let mut output = sig.output.clone();
881                    // Replace `Self` with the actual type name in the return type
882                    if let syn::ReturnType::Type(_, ret_ty) = &mut output {
883                        if let syn::Type::Path(type_path) = &mut **ret_ty {
884                            if type_path.path.is_ident("Self") {
885                                **ret_ty = *(*ty).clone();
886                            }
887                        }
888                    }
889                    let out = quote! { #output };
890                    let b = quote! { #call_expr };
891                    (out, b)
892                };
893
894            generated_methods.push(quote! {
895                #(#doc_comments)*
896                #js_name_attr
897                pub fn #method_ident(#(#args),*) #final_output {
898                    #body
899                }
900            });
901        }
902    }
903
904    let expanded = quote! {
905        #input
906
907        #[cfg(feature = "wasm")]
908        #[allow(non_snake_case)]
909        mod #mod_name {
910            use super::*;
911
912            #(#generated_methods)*
913        }
914    };
915
916    TokenStream::from(expanded)
917}
918
919/// Attribute macro `#[noun_derive(NounEncode, NounDecode, Hashable, Serialize, Deserialize)]`
920///
921/// Supports `#[noun(cell)]` and `#[noun(tag = N)]` on enum variants.
922/// Generates NounEncode/NounDecode/Hashable impls, and serde shadow types for Serialize/Deserialize.
923#[proc_macro_attribute]
924pub fn noun_derive(attr: TokenStream, item: TokenStream) -> TokenStream {
925    let mut input = parse_macro_input!(item as syn::ItemEnum);
926    let crate_path = get_crate_path();
927    let enum_name = &input.ident;
928    let vis = &input.vis;
929
930    // Parse attribute args: comma-separated idents like NounEncode, NounDecode, Hashable, Serialize, Deserialize
931    let attr_args = proc_macro2::TokenStream::from(attr);
932    let requested_derives: Vec<String> = attr_args
933        .into_iter()
934        .filter_map(|tt| {
935            if let proc_macro2::TokenTree::Ident(ident) = tt {
936                Some(ident.to_string())
937            } else {
938                None
939            }
940        })
941        .collect();
942
943    let wants_noun_encode = requested_derives.iter().any(|s| s == "NounEncode");
944    let wants_noun_decode = requested_derives.iter().any(|s| s == "NounDecode");
945    let wants_hashable = requested_derives.iter().any(|s| s == "Hashable");
946    let wants_serialize = requested_derives.iter().any(|s| s == "Serialize");
947    let wants_deserialize = requested_derives.iter().any(|s| s == "Deserialize");
948    let wants_wasm = requested_derives.iter().any(|s| s == "tsify_wasm");
949
950    // Collect passthrough derives (everything other than the ones we handle manually)
951    let handled = [
952        "NounEncode",
953        "NounDecode",
954        "Hashable",
955        "Serialize",
956        "Deserialize",
957        "tsify_wasm",
958    ];
959    let passthrough_derives: Vec<proc_macro2::TokenStream> = requested_derives
960        .iter()
961        .filter(|s| !handled.contains(&s.as_str()))
962        .map(|s| {
963            let ident = format_ident!("{}", s);
964            quote! { #ident }
965        })
966        .collect();
967
968    // --- Parse #[noun(...)] attributes on enum ---
969    let mut tag_ident = format_ident!("tag");
970    input.attrs.retain(|attr| {
971        if !attr.path().is_ident("noun") {
972            return true;
973        }
974        let _ = attr.parse_nested_meta(|meta| {
975            if meta.path.is_ident("tag_ident") {
976                if let Ok(value) = meta.value() {
977                    if let Ok(ident) = value.parse::<syn::Ident>() {
978                        tag_ident = ident;
979                    }
980                }
981            }
982            Ok(())
983        });
984        false
985    });
986
987    // --- Parse #[noun(...)] attributes on each variant ---
988    enum NounVariantKind {
989        Cell,           // #[noun(cell)]
990        TagU64(u64),    // #[noun(tag = 123)]
991        TagStr(String), // #[noun(tag = "foo")]
992        EmbedTag,       // #[noun(embedded_tag = 123)]
993    }
994
995    /// Pre-computed destructure/construction helpers for a variant's fields.
996    struct VariantFieldInfo {
997        /// The pattern for destructuring: `(v)` or `{ a, b }` or empty
998        decl_pattern: proc_macro2::TokenStream,
999        /// The variable idents used in expressions
1000        vars: Vec<syn::Ident>,
1001        /// The field types in order
1002        types: Vec<syn::Type>,
1003        /// Whether the fields are named
1004        named: bool,
1005    }
1006
1007    impl VariantFieldInfo {
1008        fn from_fields(fields: &syn::Fields) -> Self {
1009            match fields {
1010                Fields::Named(f) => {
1011                    let names: Vec<syn::Ident> = f
1012                        .named
1013                        .iter()
1014                        .map(|fld| fld.ident.clone().unwrap())
1015                        .collect();
1016                    let types: Vec<syn::Type> = f.named.iter().map(|fld| fld.ty.clone()).collect();
1017                    let decl_pattern = if names.is_empty() {
1018                        quote! { {} }
1019                    } else {
1020                        quote! { { #(#names),* } }
1021                    };
1022                    VariantFieldInfo {
1023                        decl_pattern,
1024                        vars: names,
1025                        types,
1026                        named: true,
1027                    }
1028                }
1029                Fields::Unnamed(f) => {
1030                    let count = f.unnamed.len();
1031                    let names: Vec<syn::Ident> =
1032                        (0..count).map(|i| format_ident!("v{}", i)).collect();
1033                    let types: Vec<syn::Type> =
1034                        f.unnamed.iter().map(|fld| fld.ty.clone()).collect();
1035                    let decl_pattern = if count == 0 {
1036                        quote! {}
1037                    } else {
1038                        quote! { ( #(#names),* ) }
1039                    };
1040                    VariantFieldInfo {
1041                        decl_pattern,
1042                        vars: names,
1043                        types,
1044                        named: false,
1045                    }
1046                }
1047                Fields::Unit => VariantFieldInfo {
1048                    decl_pattern: quote! {},
1049                    vars: vec![],
1050                    types: vec![],
1051                    named: false,
1052                },
1053            }
1054        }
1055
1056        fn len(&self) -> usize {
1057            self.vars.len()
1058        }
1059
1060        /// Build a nested tuple expression from the vars for NounEncode
1061        fn nested_tuple_expr(&self) -> proc_macro2::TokenStream {
1062            if self.vars.is_empty() {
1063                return quote! {};
1064            }
1065            let mut iter = self.vars.iter().rev();
1066            let last = iter.next().unwrap();
1067            let mut result = quote! { #last };
1068            for ident in iter {
1069                result = quote! { (#ident, &#result) };
1070            }
1071            result
1072        }
1073    }
1074
1075    struct VariantInfo {
1076        ident: syn::Ident,
1077        kind: NounVariantKind,
1078        fields: syn::Fields,
1079        fi: VariantFieldInfo,
1080    }
1081
1082    let mut variants_info = Vec::new();
1083    let mut cell_count = 0;
1084
1085    for variant in &mut input.variants {
1086        let mut kind = None;
1087
1088        variant.attrs.retain(|attr| {
1089            if !attr.path().is_ident("noun") {
1090                return true;
1091            }
1092            let _ = attr.parse_nested_meta(|meta| {
1093                if meta.path.is_ident("cell") {
1094                    kind = Some(NounVariantKind::Cell);
1095                    cell_count += 1;
1096                } else if meta.path.is_ident("tag") {
1097                    let value = meta.value().expect("noun(tag = ...) requires a value");
1098                    let lit: syn::Lit = value.parse().expect("tag value must be a literal");
1099                    match lit {
1100                        syn::Lit::Int(lit_int) => {
1101                            let v: u64 = lit_int.base10_parse().expect("tag must be u64");
1102                            kind = Some(NounVariantKind::TagU64(v));
1103                        }
1104                        syn::Lit::Str(lit_str) => {
1105                            kind = Some(NounVariantKind::TagStr(lit_str.value()));
1106                        }
1107                        _ => panic!("noun(tag = ...) value must be an integer or string literal"),
1108                    }
1109                } else if meta.path.is_ident("embedded_tag") {
1110                    kind = Some(NounVariantKind::EmbedTag);
1111                }
1112
1113                Ok(())
1114            });
1115            false // strip the #[noun(...)] attribute
1116        });
1117
1118        let kind = kind.unwrap_or_else(|| NounVariantKind::TagStr(variant.ident.to_string()));
1119
1120        let fi = VariantFieldInfo::from_fields(&variant.fields);
1121        variants_info.push(VariantInfo {
1122            ident: variant.ident.clone(),
1123            kind,
1124            fields: variant.fields.clone(),
1125            fi,
1126        });
1127    }
1128
1129    if cell_count > 1 {
1130        panic!("Only one #[noun(cell)] variant is allowed per enum");
1131    }
1132
1133    // --- Generate NounEncode ---
1134    let noun_encode_impl = if wants_noun_encode {
1135        let mut arms = Vec::new();
1136        for vi in &variants_info {
1137            let vident = &vi.ident;
1138            let decl = &vi.fi.decl_pattern;
1139            match &vi.kind {
1140                NounVariantKind::Cell | NounVariantKind::EmbedTag => {
1141                    // Encode inner value directly
1142                    if vi.fi.len() == 0 {
1143                        arms.push(quote! {
1144                            Self::#vident #decl => #crate_path::NounEncode::to_noun(&0u64),
1145                        });
1146                    } else if vi.fi.len() == 1 {
1147                        let var = &vi.fi.vars[0];
1148                        arms.push(quote! {
1149                            Self::#vident #decl => #crate_path::NounEncode::to_noun(#var),
1150                        });
1151                    } else {
1152                        let tuple_expr = vi.fi.nested_tuple_expr();
1153                        arms.push(quote! {
1154                            Self::#vident #decl => #crate_path::NounEncode::to_noun(&(#tuple_expr)),
1155                        });
1156                    }
1157                }
1158                NounVariantKind::TagU64(tag) => {
1159                    if vi.fi.len() == 0 {
1160                        arms.push(quote! {
1161                            Self::#vident => #crate_path::NounEncode::to_noun(&(#tag as u64)),
1162                        });
1163                    } else {
1164                        let rest_expr = if vi.fi.len() == 1 {
1165                            let var = &vi.fi.vars[0];
1166                            quote! { #crate_path::NounEncode::to_noun(#var) }
1167                        } else {
1168                            let tuple_expr = vi.fi.nested_tuple_expr();
1169                            quote! { #crate_path::NounEncode::to_noun(&(#tuple_expr)) }
1170                        };
1171                        arms.push(quote! {
1172                            Self::#vident #decl => {
1173                                let tag_noun = #crate_path::NounEncode::to_noun(&(#tag as u64));
1174                                let rest_noun = #rest_expr;
1175                                #crate_path::NounEncode::to_noun(&(tag_noun, rest_noun))
1176                            }
1177                        });
1178                    }
1179                }
1180                NounVariantKind::TagStr(tag) => {
1181                    if vi.fi.len() == 0 {
1182                        arms.push(quote! {
1183                            Self::#vident => #crate_path::NounEncode::to_noun(&#tag),
1184                        });
1185                    } else {
1186                        let rest_expr = if vi.fi.len() == 1 {
1187                            let var = &vi.fi.vars[0];
1188                            quote! { #crate_path::NounEncode::to_noun(#var) }
1189                        } else {
1190                            let tuple_expr = vi.fi.nested_tuple_expr();
1191                            quote! { #crate_path::NounEncode::to_noun(&(#tuple_expr)) }
1192                        };
1193                        arms.push(quote! {
1194                            Self::#vident #decl => {
1195                                let tag_noun = #crate_path::NounEncode::to_noun(&#tag);
1196                                let rest_noun = #rest_expr;
1197                                #crate_path::NounEncode::to_noun(&(tag_noun, rest_noun))
1198                            }
1199                        });
1200                    }
1201                }
1202            }
1203        }
1204        quote! {
1205            impl #crate_path::NounEncode for #enum_name {
1206                fn to_noun(&self) -> #crate_path::Noun {
1207                    match self {
1208                        #(#arms)*
1209                    }
1210                }
1211            }
1212        }
1213    } else {
1214        quote! {}
1215    };
1216
1217    // --- Generate NounDecode ---
1218    let noun_decode_impl = if wants_noun_decode {
1219        // Find the cell variant (if any)
1220        let cell_variant = variants_info
1221            .iter()
1222            .find(|v| matches!(v.kind, NounVariantKind::Cell));
1223
1224        let cell_fallback = if let Some(cv) = cell_variant {
1225            let cv_ident = &cv.ident;
1226            if cv.fi.len() == 0 {
1227                quote! {
1228                    return Some(Self::#cv_ident);
1229                }
1230            } else if cv.fi.len() == 1 {
1231                let var = &cv.fi.vars[0];
1232                let construct = if cv.fi.named {
1233                    quote! { Self::#cv_ident { #var: #crate_path::NounDecode::from_noun(noun)? } }
1234                } else {
1235                    quote! { Self::#cv_ident(#crate_path::NounDecode::from_noun(noun)?) }
1236                };
1237                quote! {
1238                    return Some(#construct);
1239                }
1240            } else {
1241                let vars = &cv.fi.vars;
1242                let construct = if cv.fi.named {
1243                    quote! { Self::#cv_ident { #(#vars),* } }
1244                } else {
1245                    quote! { Self::#cv_ident( #(#vars),* ) }
1246                };
1247                quote! {
1248                    let ( #(#vars),* ) = #crate_path::NounDecode::from_noun(noun)?;
1249                    return Some(#construct);
1250                }
1251            }
1252        } else {
1253            quote! { return None; }
1254        };
1255
1256        // Build match arms for u64 tags
1257        let mut u64_tag_arms = Vec::new();
1258        for vi in &variants_info {
1259            if let NounVariantKind::TagU64(tag) = &vi.kind {
1260                let vident = &vi.ident;
1261                if vi.fi.len() == 0 {
1262                    u64_tag_arms.push(quote! {
1263                        #tag => return Some(Self::#vident),
1264                    });
1265                } else if vi.fi.len() == 1 {
1266                    let var = &vi.fi.vars[0];
1267                    let construct = if vi.fi.named {
1268                        quote! { Self::#vident { #var: #crate_path::NounDecode::from_noun(rest)? } }
1269                    } else {
1270                        quote! { Self::#vident(#crate_path::NounDecode::from_noun(rest)?) }
1271                    };
1272                    u64_tag_arms.push(quote! {
1273                        #tag => return Some(#construct),
1274                    });
1275                } else {
1276                    let vars = &vi.fi.vars;
1277                    let construct = if vi.fi.named {
1278                        quote! { Self::#vident { #(#vars),* } }
1279                    } else {
1280                        quote! { Self::#vident( #(#vars),* ) }
1281                    };
1282                    u64_tag_arms.push(quote! {
1283                        #tag => {
1284                            let ( #(#vars),* ) = #crate_path::NounDecode::from_noun(rest)?;
1285                            return Some(#construct);
1286                        }
1287                    });
1288                }
1289            }
1290        }
1291
1292        // Build match arms for string tags
1293        let mut str_tag_arms = Vec::new();
1294        for vi in &variants_info {
1295            if let NounVariantKind::TagStr(tag) = &vi.kind {
1296                let vident = &vi.ident;
1297                if vi.fi.len() == 0 {
1298                    str_tag_arms.push(quote! {
1299                        #tag => return Some(Self::#vident),
1300                    });
1301                } else if vi.fi.len() == 1 {
1302                    let var = &vi.fi.vars[0];
1303                    let construct = if vi.fi.named {
1304                        quote! { Self::#vident { #var: #crate_path::NounDecode::from_noun(rest)? } }
1305                    } else {
1306                        quote! { Self::#vident(#crate_path::NounDecode::from_noun(rest)?) }
1307                    };
1308                    str_tag_arms.push(quote! {
1309                        #tag => return Some(#construct),
1310                    });
1311                } else {
1312                    let vars = &vi.fi.vars;
1313                    let construct = if vi.fi.named {
1314                        quote! { Self::#vident { #(#vars),* } }
1315                    } else {
1316                        quote! { Self::#vident( #(#vars),* ) }
1317                    };
1318                    str_tag_arms.push(quote! {
1319                        #tag => {
1320                            let ( #(#vars),* ) = #crate_path::NounDecode::from_noun(rest)?;
1321                            return Some(#construct);
1322                        }
1323                    });
1324                }
1325            }
1326        }
1327
1328        let has_u64_tags = !u64_tag_arms.is_empty();
1329        let has_str_tags = !str_tag_arms.is_empty();
1330
1331        let u64_match_block = if has_u64_tags {
1332            quote! {
1333                if let Some(tag_u64) = <u64 as #crate_path::NounDecode>::from_noun(tag_noun) {
1334                    match tag_u64 {
1335                        #(#u64_tag_arms)*
1336                        _ => {}
1337                    }
1338                }
1339            }
1340        } else {
1341            quote! {}
1342        };
1343
1344        let str_match_block = if has_str_tags {
1345            quote! {
1346                {
1347                    let a_bytes = tag_atom.to_le_bytes();
1348                    if let Ok(tag_str) = core::str::from_utf8(&a_bytes) {
1349                        match tag_str {
1350                            #(#str_tag_arms)*
1351                            _ => {}
1352                        }
1353                    }
1354                }
1355            }
1356        } else {
1357            quote! {}
1358        };
1359
1360        let mut embed_match_arms = Vec::new();
1361        for vi in &variants_info {
1362            if let NounVariantKind::EmbedTag = vi.kind {
1363                let vident = &vi.ident;
1364                match &vi.fields {
1365                    Fields::Unnamed(f) if f.unnamed.len() == 1 => {
1366                        embed_match_arms.push(quote! {
1367                            if let Some(v) = #crate_path::NounDecode::from_noun(noun).map(Self::#vident) {
1368                                return Some(v);
1369                            }
1370                        });
1371                    }
1372                    Fields::Named(f) if f.named.len() == 1 => {
1373                        let var = &vi.fi.vars[0];
1374                        embed_match_arms.push(quote! {
1375                            if let Some(#var) = #crate_path::NounDecode::from_noun(noun) {
1376                                return Some(Self::#vident { #var });
1377                            }
1378                        });
1379                    }
1380                    Fields::Unit => {
1381                        panic!("Cannot use embedded_tag on unit variants");
1382                    }
1383                    _ => {}
1384                }
1385            }
1386        }
1387
1388        let embed_match_block = quote! {
1389            {
1390                #(#embed_match_arms)*
1391            }
1392        };
1393
1394        quote! {
1395            impl #crate_path::NounDecode for #enum_name {
1396                fn from_noun(noun: &#crate_path::Noun) -> Option<Self> {
1397                    match noun {
1398                        #crate_path::Noun::Cell(ref tag_noun, ref rest) => {
1399                            // Check if tag is an atom
1400                            if let #crate_path::Noun::Atom(ref tag_atom) = **tag_noun {
1401                                // Try u64 tag matching
1402                                #u64_match_block
1403                                // Try string tag matching
1404                                #str_match_block
1405                                // Try embedded tags
1406                                #embed_match_block
1407                                // No tag matched
1408                                None
1409                            } else {
1410                                // Tag is not an atom (it's a cell) => try cell variant
1411                                #cell_fallback
1412                            }
1413                        }
1414                        #crate_path::Noun::Atom(_) => {
1415                            // Atom at top level - try cell variant as fallback
1416                            #cell_fallback
1417                        }
1418                    }
1419                }
1420            }
1421        }
1422    } else {
1423        quote! {}
1424    };
1425
1426    // --- Generate Hashable ---
1427    let hashable_impl = if wants_hashable {
1428        let mut arms_hash = Vec::new();
1429        let mut arms_leaves = Vec::new();
1430        let mut arms_pairs = Vec::new();
1431        let mut eithers = vec![];
1432        for (i, vi) in variants_info.iter().enumerate() {
1433            let vident = &vi.ident;
1434            let cur_eithers = if i == variants_info.len() - 1 {
1435                eithers.clone()
1436            } else {
1437                [&eithers[..], &[quote!(#crate_path::Either::Left)][..]].concat()
1438            };
1439            let nest = |a: proc_macro2::TokenStream| {
1440                cur_eithers.iter().rev().fold(a, |acc, f| quote!(#f(#acc)))
1441            };
1442            match &vi.kind {
1443                NounVariantKind::Cell | NounVariantKind::EmbedTag => {
1444                    let decl = &vi.fi.decl_pattern;
1445                    if vi.fi.len() >= 1 {
1446                        let var = &vi.fi.vars[0];
1447                        arms_hash.push(quote! {
1448                            Self::#vident #decl => #crate_path::Hashable::hash(#var),
1449                        });
1450                        arms_leaves.push(quote! {
1451                            Self::#vident #decl => #crate_path::Hashable::leaf_count(#var),
1452                        });
1453                        let nested_a = nest(quote!(a));
1454                        let nested_b = nest(quote!(b));
1455                        arms_pairs.push(quote! {
1456                            Self::#vident #decl => #crate_path::Hashable::hashable_pair(#var).map(|(a, b)| (#nested_a, #nested_b)),
1457                        });
1458                    } else {
1459                        arms_hash.push(quote! {
1460                            Self::#vident => #crate_path::Hashable::hash(&0u64),
1461                        });
1462                        arms_leaves.push(quote! {
1463                            Self::#vident => #crate_path::Hashable::leaf_count(&0u64),
1464                        });
1465                        let nested_a = nest(quote!(a));
1466                        let nested_b = nest(quote!(b));
1467                        arms_pairs.push(quote! {
1468                            Self::#vident => Option::<((),())>::None.map(|(a, b)| (#nested_a, #nested_b)),
1469                        });
1470                    }
1471                }
1472                NounVariantKind::TagU64(tag) => {
1473                    let decl = &vi.fi.decl_pattern;
1474                    if vi.fi.len() >= 1 {
1475                        // For single-field: hash(&(tag, v)), for multi: hash(&(tag, &(v0, &v1)))
1476                        let tuple_expr = vi.fi.nested_tuple_expr();
1477                        arms_hash.push(quote! {
1478                            Self::#vident #decl => #crate_path::Hashable::hash(&(#tag as u64, &(#tuple_expr))),
1479                        });
1480                        arms_leaves.push(quote! {
1481                            Self::#vident #decl => #crate_path::Hashable::leaf_count(&(#tag as u64, &(#tuple_expr))),
1482                        });
1483                        let nested_tag = nest(quote!(#tag as u64));
1484                        let nested_value = nest(quote!(v_rest));
1485                        arms_pairs.push(quote! {
1486                            Self::#vident #decl => { let v_rest = (#tuple_expr); Some((#nested_tag, #nested_value)) },
1487                        });
1488                    } else {
1489                        arms_hash.push(quote! {
1490                            Self::#vident => #crate_path::Hashable::hash(&(#tag as u64)),
1491                        });
1492                        arms_leaves.push(quote! {
1493                            Self::#vident => #crate_path::Hashable::leaf_count(&(#tag as u64)),
1494                        });
1495                        let nested_a = nest(quote!(a));
1496                        let nested_b = nest(quote!(b));
1497                        arms_pairs.push(quote! {
1498                            Self::#vident => Option::<((),())>::None.map(|(a, b)| (#nested_a, #nested_b)),
1499                        });
1500                    }
1501                }
1502                NounVariantKind::TagStr(tag) => {
1503                    let decl = &vi.fi.decl_pattern;
1504                    if vi.fi.len() >= 1 {
1505                        let tuple_expr = vi.fi.nested_tuple_expr();
1506                        arms_hash.push(quote! {
1507                            Self::#vident #decl => #crate_path::Hashable::hash(&(#tag, &(#tuple_expr))),
1508                        });
1509                        arms_leaves.push(quote! {
1510                            Self::#vident #decl => #crate_path::Hashable::leaf_count(&(#tag, &(#tuple_expr))),
1511                        });
1512                        let nested_tag = nest(quote!(#tag));
1513                        let nested_value = nest(quote!(v_rest));
1514                        arms_pairs.push(quote! {
1515                            Self::#vident #decl => { let v_rest = (#tuple_expr); Some((#nested_tag, #nested_value)) },
1516                        });
1517                    } else {
1518                        arms_hash.push(quote! {
1519                            Self::#vident => #crate_path::Hashable::hash(&#tag),
1520                        });
1521                        arms_leaves.push(quote! {
1522                            Self::#vident => #crate_path::Hashable::leaf_count(&#tag),
1523                        });
1524                        let nested_a = nest(quote!(a));
1525                        let nested_b = nest(quote!(b));
1526                        arms_pairs.push(quote! {
1527                            Self::#vident => Option::<((),())>::None.map(|(a, b)| (#nested_a, #nested_b)),
1528                        });
1529                    }
1530                }
1531            }
1532            eithers.push(quote!(#crate_path::Either::Right));
1533        }
1534        quote! {
1535            impl #crate_path::Hashable for #enum_name {
1536                fn hash(&self) -> #crate_path::Digest {
1537                    match self {
1538                        #(#arms_hash)*
1539                    }
1540                }
1541
1542                fn leaf_count(&self) -> usize {
1543                    match self {
1544                        #(#arms_leaves)*
1545                    }
1546                }
1547
1548                fn hashable_pair<'a>(&'a self) -> Option<(impl #crate_path::Hashable + 'a, impl #crate_path::Hashable + 'a)> {
1549                    match self {
1550                        #(#arms_pairs)*
1551                    }
1552                }
1553            }
1554        }
1555    } else {
1556        quote! {}
1557    };
1558
1559    // --- Generate Shadow Types & Serde ---
1560    let serde_impl = if wants_serialize || wants_deserialize {
1561        let shadow_mod_name = format_ident!(
1562            "__noun_derive_shadow_{}",
1563            to_snake_case(&enum_name.to_string())
1564        );
1565        let shadow_owned_name = enum_name.clone();
1566        let shadow_borrowed_name = format_ident!("__ShadowBorrowed{}", enum_name);
1567
1568        // Build shadow owned variants
1569        let mut shadow_owned_variants = Vec::new();
1570        let mut shadow_borrowed_variants = Vec::new();
1571        let mut from_shadow_arms = Vec::new(); // ShadowOwned -> Enum
1572        let mut to_shadow_owned_arms = Vec::new(); // Enum -> ShadowOwned
1573        let mut into_shadow_arms = Vec::new(); // &Enum -> ShadowBorrowed
1574
1575        for vi in &variants_info {
1576            let vident = &vi.ident;
1577            match &vi.kind {
1578                NounVariantKind::Cell | NounVariantKind::EmbedTag => {
1579                    // Untagged: transparent
1580                    let decl = &vi.fi.decl_pattern;
1581                    let vars = &vi.fi.vars;
1582                    let types = &vi.fi.types;
1583                    if vi.fi.len() == 0 {
1584                        shadow_owned_variants.push(quote! {
1585                            #vident,
1586                        });
1587                        shadow_borrowed_variants.push(quote! {
1588                            #vident,
1589                        });
1590                        from_shadow_arms.push(quote! {
1591                            #shadow_owned_name::#vident => super::#enum_name::#vident,
1592                        });
1593                        to_shadow_owned_arms.push(quote! {
1594                            super::#enum_name::#vident => #shadow_owned_name::#vident,
1595                        });
1596                        into_shadow_arms.push(quote! {
1597                            super::#enum_name::#vident => #shadow_borrowed_name::#vident,
1598                        });
1599                    } else if vi.fi.len() == 1 && !vi.fi.named {
1600                        let inner_ty = &types[0];
1601                        shadow_owned_variants.push(quote! {
1602                            #vident(#inner_ty),
1603                        });
1604                        shadow_borrowed_variants.push(quote! {
1605                            #vident(&'a #inner_ty),
1606                        });
1607                        from_shadow_arms.push(quote! {
1608                            #shadow_owned_name::#vident(v) => super::#enum_name::#vident(v),
1609                        });
1610                        to_shadow_owned_arms.push(quote! {
1611                            super::#enum_name::#vident(v) => #shadow_owned_name::#vident(v),
1612                        });
1613                        into_shadow_arms.push(quote! {
1614                            super::#enum_name::#vident(ref v) => #shadow_borrowed_name::#vident(v),
1615                        });
1616                    } else {
1617                        // Named fields or multi-field: include all fields by name
1618                        shadow_owned_variants.push(quote! {
1619                            #vident { #(#vars: #types),* },
1620                        });
1621                        shadow_borrowed_variants.push(quote! {
1622                            #vident { #(#vars: &'a #types),* },
1623                        });
1624                        from_shadow_arms.push(quote! {
1625                            #shadow_owned_name::#vident { #(#vars),* } => super::#enum_name::#vident #decl,
1626                        });
1627                        to_shadow_owned_arms.push(quote! {
1628                            super::#enum_name::#vident #decl => #shadow_owned_name::#vident { #(#vars),* },
1629                        });
1630                        into_shadow_arms.push(quote! {
1631                            super::#enum_name::#vident { #(ref #vars),* } => #shadow_borrowed_name::#vident { #(#vars),* },
1632                        });
1633                    }
1634                }
1635                NounVariantKind::TagU64(tag) => {
1636                    let tag_str = tag.to_string();
1637                    let decl = &vi.fi.decl_pattern;
1638                    let vars = &vi.fi.vars;
1639                    let types = &vi.fi.types;
1640                    if vi.fi.len() == 0 {
1641                        shadow_owned_variants.push(quote! {
1642                            #vident {
1643                                #[cfg_attr(feature = "wasm", tsify(type = #tag_str))]
1644                                #tag_ident: u64
1645                            },
1646                        });
1647                        shadow_borrowed_variants.push(quote! {
1648                            #vident { #tag_ident: u64 },
1649                        });
1650                        from_shadow_arms.push(quote! {
1651                            #shadow_owned_name::#vident { .. } => super::#enum_name::#vident,
1652                        });
1653                        to_shadow_owned_arms.push(quote! {
1654                            super::#enum_name::#vident => #shadow_owned_name::#vident { #tag_ident: #tag },
1655                        });
1656                        into_shadow_arms.push(quote! {
1657                            super::#enum_name::#vident => #shadow_borrowed_name::#vident { #tag_ident: #tag },
1658                        });
1659                    } else if vi.fi.len() == 1 && !vi.fi.named {
1660                        let inner_ty = &types[0];
1661                        shadow_owned_variants.push(quote! {
1662                            #vident {
1663                                #[cfg_attr(feature = "wasm", tsify(type = #tag_str))]
1664                                #tag_ident: u64,
1665                                #[serde(flatten)]
1666                                value: #inner_ty,
1667                            },
1668                        });
1669                        shadow_borrowed_variants.push(quote! {
1670                            #vident {
1671                                #tag_ident: u64,
1672                                #[serde(flatten)]
1673                                value: &'a #inner_ty,
1674                            },
1675                        });
1676                        from_shadow_arms.push(quote! {
1677                            #shadow_owned_name::#vident { value, .. } => super::#enum_name::#vident(value),
1678                        });
1679                        to_shadow_owned_arms.push(quote! {
1680                            super::#enum_name::#vident(v) => #shadow_owned_name::#vident { #tag_ident: #tag, value: v },
1681                        });
1682                        into_shadow_arms.push(quote! {
1683                            super::#enum_name::#vident(ref v) => #shadow_borrowed_name::#vident { #tag_ident: #tag, value: v },
1684                        });
1685                    } else {
1686                        // Named fields or multi-field: include tag + all fields by name
1687                        shadow_owned_variants.push(quote! {
1688                            #vident {
1689                                #[cfg_attr(feature = "wasm", tsify(type = #tag_str))]
1690                                #tag_ident: u64,
1691                                #(#vars: #types),*
1692                            },
1693                        });
1694                        shadow_borrowed_variants.push(quote! {
1695                            #vident {
1696                                #tag_ident: u64,
1697                                #(#vars: &'a #types),*
1698                            },
1699                        });
1700                        from_shadow_arms.push(quote! {
1701                            #shadow_owned_name::#vident { #(#vars),*, .. } => super::#enum_name::#vident #decl,
1702                        });
1703                        to_shadow_owned_arms.push(quote! {
1704                            super::#enum_name::#vident #decl => #shadow_owned_name::#vident { #tag_ident: #tag, #(#vars),* },
1705                        });
1706                        into_shadow_arms.push(quote! {
1707                            super::#enum_name::#vident { #(ref #vars),* } => #shadow_borrowed_name::#vident { #tag_ident: #tag, #(#vars),* },
1708                        });
1709                    }
1710                }
1711                NounVariantKind::TagStr(tag) => {
1712                    let tag_str = format!("\"{}\"", tag);
1713                    let decl = &vi.fi.decl_pattern;
1714                    let vars = &vi.fi.vars;
1715                    let types = &vi.fi.types;
1716                    if vi.fi.len() == 0 {
1717                        shadow_owned_variants.push(quote! {
1718                            #vident {
1719                                #[cfg_attr(feature = "wasm", tsify(type = #tag_str))]
1720                                #tag_ident: alloc::string::String
1721                            },
1722                        });
1723                        shadow_borrowed_variants.push(quote! {
1724                            #vident { #tag_ident: &'a str },
1725                        });
1726                        from_shadow_arms.push(quote! {
1727                            #shadow_owned_name::#vident { .. } => super::#enum_name::#vident,
1728                        });
1729                        to_shadow_owned_arms.push(quote! {
1730                            super::#enum_name::#vident => #shadow_owned_name::#vident { #tag_ident: #tag.into() },
1731                        });
1732                        into_shadow_arms.push(quote! {
1733                            super::#enum_name::#vident => #shadow_borrowed_name::#vident { #tag_ident: #tag },
1734                        });
1735                    } else if vi.fi.len() == 1 && !vi.fi.named {
1736                        let inner_ty = &types[0];
1737                        shadow_owned_variants.push(quote! {
1738                            #vident {
1739                                #[cfg_attr(feature = "wasm", tsify(type = #tag_str))]
1740                                #tag_ident: alloc::string::String,
1741                                #[serde(flatten)]
1742                                value: #inner_ty,
1743                            },
1744                        });
1745                        shadow_borrowed_variants.push(quote! {
1746                            #vident {
1747                                #tag_ident: &'a str,
1748                                #[serde(flatten)]
1749                                value: &'a #inner_ty,
1750                            },
1751                        });
1752                        from_shadow_arms.push(quote! {
1753                            #shadow_owned_name::#vident { value, .. } => super::#enum_name::#vident(value),
1754                        });
1755                        to_shadow_owned_arms.push(quote! {
1756                            super::#enum_name::#vident(v) => #shadow_owned_name::#vident { #tag_ident: #tag.into(), value: v },
1757                        });
1758                        into_shadow_arms.push(quote! {
1759                            super::#enum_name::#vident(ref v) => #shadow_borrowed_name::#vident { #tag_ident: #tag, value: v },
1760                        });
1761                    } else {
1762                        // Named fields or multi-field: include tag + all fields by name
1763                        shadow_owned_variants.push(quote! {
1764                            #vident {
1765                                #[cfg_attr(feature = "wasm", tsify(type = #tag_str))]
1766                                #tag_ident: alloc::string::String,
1767                                #(#vars: #types),*
1768                            },
1769                        });
1770                        shadow_borrowed_variants.push(quote! {
1771                            #vident {
1772                                #tag_ident: &'a str,
1773                                #(#vars: &'a #types),*
1774                            },
1775                        });
1776                        from_shadow_arms.push(quote! {
1777                            #shadow_owned_name::#vident { #(#vars),*, .. } => super::#enum_name::#vident #decl,
1778                        });
1779                        to_shadow_owned_arms.push(quote! {
1780                            super::#enum_name::#vident #decl => #shadow_owned_name::#vident { #tag_ident: #tag.into(), #(#vars),* },
1781                        });
1782                        into_shadow_arms.push(quote! {
1783                            super::#enum_name::#vident { #(ref #vars),* } => #shadow_borrowed_name::#vident { #tag_ident: #tag, #(#vars),* },
1784                        });
1785                    }
1786                }
1787            }
1788        }
1789
1790        let serialize_impl = if wants_serialize {
1791            quote! {
1792                impl serde::Serialize for #enum_name {
1793                    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1794                    where
1795                        S: serde::Serializer,
1796                    {
1797                        let shadow: #shadow_mod_name::#shadow_borrowed_name = self.into();
1798                        shadow.serialize(serializer)
1799                    }
1800                }
1801            }
1802        } else {
1803            quote! {}
1804        };
1805
1806        let deserialize_impl = if wants_deserialize {
1807            quote! {
1808                impl<'de> serde::Deserialize<'de> for #enum_name {
1809                    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1810                    where
1811                        D: serde::Deserializer<'de>,
1812                    {
1813                        let shadow = #shadow_mod_name::#shadow_owned_name::deserialize(deserializer)?;
1814                        Ok(shadow.into())
1815                    }
1816                }
1817            }
1818        } else {
1819            quote! {}
1820        };
1821
1822        let wasm_impl = if wants_wasm {
1823            quote! {
1824                #[cfg(feature = "wasm")]
1825                const _: () = {
1826                use tsify::Tsify;
1827                use wasm_bindgen::JsValue;
1828                use wasm_bindgen::convert::*;
1829                use wasm_bindgen::describe::*;
1830                use wasm_bindgen::UnwrapThrowExt;
1831
1832                impl Tsify for #enum_name {
1833                    type JsType = <#shadow_mod_name::#shadow_owned_name as Tsify>::JsType;
1834                    const DECL: &'static str = <#shadow_mod_name::#shadow_owned_name as Tsify>::DECL;
1835                }
1836
1837                impl WasmDescribe for #enum_name {
1838                    #[inline]
1839                    fn describe() {
1840                        <#shadow_mod_name::#shadow_owned_name as WasmDescribe>::describe()
1841                    }
1842                }
1843
1844                #[automatically_derived]
1845                impl WasmDescribeVector for #enum_name {
1846                    #[inline]
1847                    fn describe_vector() {
1848                        <#shadow_mod_name::#shadow_owned_name as WasmDescribeVector>::describe_vector()
1849                    }
1850                }
1851
1852                impl IntoWasmAbi for #enum_name {
1853                    type Abi = <#shadow_mod_name::#shadow_owned_name as IntoWasmAbi>::Abi;
1854
1855                    #[inline]
1856                    fn into_abi(self) -> Self::Abi {
1857                        let shadow: #shadow_mod_name::#shadow_owned_name = self.into();
1858                        shadow.into_abi()
1859                    }
1860                }
1861
1862                impl FromWasmAbi for #enum_name {
1863                    type Abi = <#shadow_mod_name::#shadow_owned_name as FromWasmAbi>::Abi;
1864
1865                    #[inline]
1866                    unsafe fn from_abi(js: Self::Abi) -> Self {
1867                        let shadow = <#shadow_mod_name::#shadow_owned_name as FromWasmAbi>::from_abi(js);
1868                        shadow.into()
1869                    }
1870                }
1871
1872                #[automatically_derived]
1873                impl OptionFromWasmAbi for #enum_name {
1874                    #[inline]
1875                    fn is_none(js: &Self::Abi) -> bool {
1876                        <<Self as Tsify>::JsType as OptionFromWasmAbi>::is_none(js)
1877                    }
1878                }
1879
1880                pub struct SelfOwner<T>(T);
1881
1882                #[automatically_derived]
1883                impl<T> ::core::ops::Deref for SelfOwner<T> {
1884                    type Target = T;
1885
1886                    fn deref(&self) -> &Self::Target {
1887                        &self.0
1888                    }
1889                }
1890
1891                impl RefFromWasmAbi for #enum_name {
1892                    type Abi = <<Self as Tsify>::JsType as RefFromWasmAbi>::Abi;
1893
1894                    type Anchor = SelfOwner<Self>;
1895
1896                    unsafe fn ref_from_abi(js: Self::Abi) -> Self::Anchor {
1897                        let result = <Self as Tsify>::from_js(&*<<Self as Tsify>::JsType as RefFromWasmAbi>::ref_from_abi(js));
1898                        if let Err(err) = result {
1899                            wasm_bindgen::throw_str(err.to_string().as_ref());
1900                        }
1901                        SelfOwner(result.unwrap_throw())
1902                    }
1903                }
1904
1905                #[automatically_derived]
1906                impl VectorFromWasmAbi for #enum_name {
1907                    type Abi = <<Self as Tsify>::JsType as VectorFromWasmAbi>::Abi;
1908
1909                    #[inline]
1910                    unsafe fn vector_from_abi(js: Self::Abi) -> Box<[Self]> {
1911                        <<Self as Tsify>::JsType as VectorFromWasmAbi>::vector_from_abi(js)
1912                            .into_iter()
1913                            .map(|value| {
1914                                let result = Self::from_js(value);
1915                                if let Err(err) = result {
1916                                    wasm_bindgen::throw_str(err.to_string().as_ref());
1917                                }
1918                                result.unwrap_throw()
1919                            })
1920                            .collect()
1921                    }
1922                }
1923                };
1924            }
1925        } else {
1926            quote! {}
1927        };
1928
1929        quote! {
1930            #[allow(non_snake_case)]
1931            mod #shadow_mod_name {
1932                use super::*;
1933
1934                #[derive(serde::Serialize, serde::Deserialize)]
1935                #[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
1936                #[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
1937                #[serde(untagged)]
1938                pub enum #shadow_owned_name {
1939                    #(#shadow_owned_variants)*
1940                }
1941
1942                #[derive(serde::Serialize)]
1943                #[serde(untagged)]
1944                pub enum #shadow_borrowed_name<'a> {
1945                    #(#shadow_borrowed_variants)*
1946                }
1947
1948                impl From<#shadow_owned_name> for super::#enum_name {
1949                    fn from(shadow: #shadow_owned_name) -> Self {
1950                        match shadow {
1951                            #(#from_shadow_arms)*
1952                        }
1953                    }
1954                }
1955
1956                impl From<super::#enum_name> for #shadow_owned_name {
1957                    fn from(val: super::#enum_name) -> Self {
1958                        match val {
1959                            #(#to_shadow_owned_arms)*
1960                        }
1961                    }
1962                }
1963
1964                impl<'a> From<&'a super::#enum_name> for #shadow_borrowed_name<'a> {
1965                    fn from(val: &'a super::#enum_name) -> Self {
1966                        match val {
1967                            #(#into_shadow_arms)*
1968                        }
1969                    }
1970                }
1971            }
1972
1973            #serialize_impl
1974            #deserialize_impl
1975            #wasm_impl
1976        }
1977    } else {
1978        quote! {}
1979    };
1980
1981    // --- Build the output ---
1982    // Add passthrough derives to the original enum
1983    let passthrough_derive_attr = if !passthrough_derives.is_empty() {
1984        quote! { #[derive(#(#passthrough_derives),*)] }
1985    } else {
1986        quote! {}
1987    };
1988
1989    let variants = &input.variants;
1990    let original_attrs = &input.attrs;
1991
1992    let expanded = quote! {
1993        #(#original_attrs)*
1994        #passthrough_derive_attr
1995        #vis enum #enum_name {
1996            #variants
1997        }
1998
1999        #noun_encode_impl
2000        #noun_decode_impl
2001        #hashable_impl
2002        #serde_impl
2003    };
2004
2005    TokenStream::from(expanded)
2006}