Skip to main content

bourne_derive/
lib.rs

1//! `syn`-based derive macros for `json-bourne`.
2//!
3//! This crate is not used directly. Enable the `derive` feature on
4//! `json-bourne` and use `#[derive(FromJson, ToJson)]` — the derives are
5//! re-exported from the main crate so there is a single import surface.
6//!
7//! Generated code targets the crate runtime (`Lexer` / `JsonWrite` /
8//! `FromJson` / `ToJson`) via `::json_bourne::…` paths that resolve in
9//! the consumer's crate graph.
10//!
11//! Supported shapes: named and tuple structs, and all four enum tagging
12//! modes (external, internal `#[bourne(tag)]`, adjacent `#[bourne(tag,
13//! content)]`, untagged), both directions. Field attributes: `rename`,
14//! `skip`, `default`, `skip_if_none`. Container attributes: `rename_all`,
15//! `deny_unknown_fields`, `tag`, `content`, `untagged`.
16
17use proc_macro::TokenStream;
18use quote::quote;
19use syn::{
20    Data, DeriveInput, Fields, GenericParam, Ident, Lifetime, LifetimeParam, Type,
21    parse_macro_input, spanned::Spanned,
22};
23
24// ---------------------------------------------------------------------------
25// Attribute model. One small struct replaces the dozens of `cur_*` slots the
26// tt-muncher threads through every recursion.
27// ---------------------------------------------------------------------------
28
29#[derive(Default)]
30struct FieldAttrs {
31    rename: Option<String>,
32    skip: bool,
33    default: bool,
34    skip_if_none: bool,
35}
36
37#[derive(Default)]
38struct ContainerAttrs {
39    rename_all: Option<String>,
40    /// `true` when `#[bourne(deny_unknown_fields = false)]` was set —
41    /// i.e. unknown keys are skipped rather than erroring.
42    lenient: bool,
43    // Enum-only container attrs.
44    tag: Option<String>,
45    content: Option<String>,
46    untagged: bool,
47}
48
49/// Which tagging scheme an enum uses, derived from container attrs.
50enum EnumMode {
51    External,
52    Internal(String),
53    Adjacent(String, String),
54    Untagged,
55}
56
57impl ContainerAttrs {
58    fn enum_mode(&self) -> EnumMode {
59        if self.untagged {
60            EnumMode::Untagged
61        } else if let (Some(t), Some(c)) = (&self.tag, &self.content) {
62            EnumMode::Adjacent(t.clone(), c.clone())
63        } else if let Some(t) = &self.tag {
64            EnumMode::Internal(t.clone())
65        } else {
66            EnumMode::External
67        }
68    }
69}
70
71fn parse_field_attrs(attrs: &[syn::Attribute]) -> syn::Result<FieldAttrs> {
72    let mut out = FieldAttrs::default();
73    for attr in attrs {
74        if !attr.path().is_ident("bourne") {
75            continue;
76        }
77        attr.parse_nested_meta(|meta| {
78            if meta.path.is_ident("rename") {
79                let v = meta.value()?;
80                let s: syn::LitStr = v.parse()?;
81                out.rename = Some(s.value());
82            } else if meta.path.is_ident("skip") {
83                out.skip = true;
84            } else if meta.path.is_ident("default") {
85                out.default = true;
86            } else if meta.path.is_ident("skip_if_none") {
87                out.skip_if_none = true;
88            } else {
89                return Err(meta.error("unknown bourne field attribute"));
90            }
91            Ok(())
92        })?;
93    }
94    Ok(out)
95}
96
97fn parse_container_attrs(attrs: &[syn::Attribute]) -> syn::Result<ContainerAttrs> {
98    let mut out = ContainerAttrs::default();
99    for attr in attrs {
100        if !attr.path().is_ident("bourne") {
101            continue;
102        }
103        attr.parse_nested_meta(|meta| {
104            if meta.path.is_ident("rename_all") {
105                let v = meta.value()?;
106                let s: syn::LitStr = v.parse()?;
107                out.rename_all = Some(s.value());
108            } else if meta.path.is_ident("deny_unknown_fields") {
109                // `= false` toggles lenient; bare presence is strict (default).
110                if let Ok(v) = meta.value() {
111                    let b: syn::LitBool = v.parse()?;
112                    // The macro's flag names the *lenient* toggle: the
113                    // `deny_unknown_fields = false` container form. Store
114                    // "lenient == !deny".
115                    out.lenient = !b.value();
116                } else {
117                    out.lenient = false;
118                }
119            } else if meta.path.is_ident("tag") {
120                let v = meta.value()?;
121                let s: syn::LitStr = v.parse()?;
122                out.tag = Some(s.value());
123            } else if meta.path.is_ident("content") {
124                let v = meta.value()?;
125                let s: syn::LitStr = v.parse()?;
126                out.content = Some(s.value());
127            } else if meta.path.is_ident("untagged") {
128                out.untagged = true;
129            } else {
130                return Err(meta.error("unknown bourne container attribute"));
131            }
132            Ok(())
133        })?;
134    }
135    Ok(out)
136}
137
138// ---------------------------------------------------------------------------
139// Helpers shared by both directions.
140// ---------------------------------------------------------------------------
141
142/// Is this type spelled `Option<...>`? (Textual, matching the macro's
143/// leading-`Option`-ident detection — good enough and identical in effect.)
144fn is_option(ty: &Type) -> bool {
145    if let Type::Path(tp) = ty {
146        if let Some(seg) = tp.path.segments.last() {
147            return seg.ident == "Option";
148        }
149    }
150    false
151}
152
153/// The JSON key expression for a field: explicit rename wins, else
154/// container `rename_all`, else the verbatim name. Yields a `&str`
155/// expression (used in a match guard, mirroring the macro).
156fn key_expr(
157    name: &Ident,
158    rename: &Option<String>,
159    rename_all: &Option<String>,
160) -> proc_macro2::TokenStream {
161    if let Some(r) = rename {
162        quote! { #r }
163    } else if let Some(case) = rename_all {
164        let name_str = name.to_string();
165        quote! {{
166            const __BOURNE_KEY: &str =
167                ::json_bourne::__Casing::rename(#name_str, #case).as_str();
168            __BOURNE_KEY
169        }}
170    } else {
171        let name_str = name.to_string();
172        quote! { #name_str }
173    }
174}
175
176/// Parse a variant-level `#[bourne(rename = "...")]` (the only variant attr).
177fn parse_variant_rename(attrs: &[syn::Attribute]) -> syn::Result<Option<String>> {
178    let mut rename = None;
179    for attr in attrs {
180        if !attr.path().is_ident("bourne") {
181            continue;
182        }
183        attr.parse_nested_meta(|meta| {
184            if meta.path.is_ident("rename") {
185                let v = meta.value()?;
186                let s: syn::LitStr = v.parse()?;
187                rename = Some(s.value());
188                Ok(())
189            } else {
190                Err(meta.error("unsupported variant attribute in prototype"))
191            }
192        })?;
193    }
194    Ok(rename)
195}
196
197/// The classified shape of an enum variant.
198enum VShape<'a> {
199    Unit,
200    Newtype(&'a Type),
201    Tuple(Vec<&'a Type>),
202    Struct(Vec<(&'a Ident, &'a Type)>),
203}
204
205fn classify_variant(v: &syn::Variant) -> VShape<'_> {
206    match &v.fields {
207        Fields::Unit => VShape::Unit,
208        Fields::Unnamed(u) if u.unnamed.len() == 1 => VShape::Newtype(&u.unnamed[0].ty),
209        Fields::Unnamed(u) => VShape::Tuple(u.unnamed.iter().map(|f| &f.ty).collect()),
210        Fields::Named(n) => VShape::Struct(
211            n.named
212                .iter()
213                .map(|f| (f.ident.as_ref().unwrap(), &f.ty))
214                .collect(),
215        ),
216    }
217}
218
219/// Reproduce `__from_json_acquire!`: the integer/`&str` fast paths that
220/// bypass the generic `FromJson::from_lex` dispatch. Fairness for benches
221/// depends on matching these exactly.
222fn acquire_expr(ty: &Type) -> proc_macro2::TokenStream {
223    let ty_str = quote!(#ty).to_string().replace(' ', "");
224    let int_narrow = |t: &str| {
225        let t: proc_macro2::TokenStream = t.parse().unwrap();
226        quote! {
227            <#t>::try_from(__lex.parse_i64_value()?).map_err(|_| {
228                ::json_bourne::Error::new(
229                    ::json_bourne::ErrorKind::NumberOutOfRange,
230                    __lex.position(),
231                )
232            })?
233        }
234    };
235    match ty_str.as_str() {
236        "&str" | "&'inputstr" => quote! { __lex.parse_str_value()? },
237        "i64" => quote! { __lex.parse_i64_value()? },
238        "i8" => int_narrow("i8"),
239        "i16" => int_narrow("i16"),
240        "i32" => int_narrow("i32"),
241        "isize" => int_narrow("isize"),
242        "u8" => int_narrow("u8"),
243        "u16" => int_narrow("u16"),
244        "u32" => int_narrow("u32"),
245        "u64" => int_narrow("u64"),
246        "usize" => int_narrow("usize"),
247        _ => quote! {
248            <#ty as ::json_bourne::FromJson<'_>>::from_lex(__lex)?
249        },
250    }
251}
252
253/// Split generics into the pieces both impls need, synthesizing the
254/// `'input` lifetime the runtime trait is parameterized over. If the type
255/// already carries a lifetime, reuse it (bourne's convention); otherwise
256/// introduce a fresh `'input`.
257///
258/// This one function replaces the entire 4×-arm generics duplication in
259/// `macros.rs` (none / lifetime / lifetime+type / type).
260struct GenericsPlan {
261    input_lt: Lifetime,
262    /// Generics for the `impl<...>` — includes a fresh `'input` iff needed.
263    impl_generics: proc_macro2::TokenStream,
264    /// The `TypeName<...>` reference (original params only).
265    ty_generics: proc_macro2::TokenStream,
266    where_clause: proc_macro2::TokenStream,
267}
268
269fn plan_generics(input: &DeriveInput) -> GenericsPlan {
270    let generics = &input.generics;
271    let name = &input.ident;
272
273    let existing_lt = generics.params.iter().find_map(|p| match p {
274        GenericParam::Lifetime(lt) => Some(lt.lifetime.clone()),
275        _ => None,
276    });
277
278    let (input_lt, needs_fresh) = match existing_lt {
279        Some(lt) => (lt, false),
280        None => (Lifetime::new("'input", name.span()), true),
281    };
282
283    // impl-side param list.
284    let mut impl_params: Vec<proc_macro2::TokenStream> = Vec::new();
285    if needs_fresh {
286        let lp = LifetimeParam::new(input_lt.clone());
287        impl_params.push(quote!(#lp));
288    }
289    for p in &generics.params {
290        impl_params.push(quote!(#p));
291    }
292
293    // type-side param list (original params, names only).
294    let ty_params: Vec<proc_macro2::TokenStream> = generics
295        .params
296        .iter()
297        .map(|p| match p {
298            GenericParam::Lifetime(lt) => {
299                let l = &lt.lifetime;
300                quote!(#l)
301            }
302            GenericParam::Type(t) => {
303                let id = &t.ident;
304                quote!(#id)
305            }
306            GenericParam::Const(c) => {
307                let id = &c.ident;
308                quote!(#id)
309            }
310        })
311        .collect();
312
313    let ty_generics = if ty_params.is_empty() {
314        quote!()
315    } else {
316        quote!(< #(#ty_params),* >)
317    };
318
319    // where-clause: every type param must be FromJson<'input> (parse side).
320    // ToJson doesn't need the bound; we emit a superset and let the ToJson
321    // path build its own. Here we build the FromJson bounds.
322    let type_param_bounds: Vec<proc_macro2::TokenStream> = generics
323        .params
324        .iter()
325        .filter_map(|p| match p {
326            GenericParam::Type(t) => {
327                let id = &t.ident;
328                Some(quote!(#id: ::json_bourne::FromJson<#input_lt>))
329            }
330            _ => None,
331        })
332        .collect();
333
334    let where_clause = if type_param_bounds.is_empty() {
335        quote!()
336    } else {
337        quote!(where #(#type_param_bounds),*)
338    };
339
340    GenericsPlan {
341        input_lt,
342        impl_generics: quote!(< #(#impl_params),* >),
343        ty_generics,
344        where_clause,
345    }
346}
347
348// ===========================================================================
349// FromJson derive
350// ===========================================================================
351
352#[proc_macro_derive(FromJson, attributes(bourne))]
353pub fn derive_from_json(input: TokenStream) -> TokenStream {
354    let input = parse_macro_input!(input as DeriveInput);
355    match from_json_impl(&input) {
356        Ok(ts) => ts.into(),
357        Err(e) => e.to_compile_error().into(),
358    }
359}
360
361fn from_json_impl(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
362    let name = &input.ident;
363    let container = parse_container_attrs(&input.attrs)?;
364    let plan = plan_generics(input);
365    let input_lt = &plan.input_lt;
366    let impl_generics = &plan.impl_generics;
367    let ty_generics = &plan.ty_generics;
368    let where_clause = &plan.where_clause;
369
370    let body = match &input.data {
371        Data::Struct(s) => match &s.fields {
372            Fields::Named(named) => from_json_named(&named.named, &container)?,
373            Fields::Unnamed(unnamed) => from_json_tuple(&unnamed.unnamed)?,
374            Fields::Unit => {
375                return Err(syn::Error::new(
376                    input.span(),
377                    "unit structs have no JSON representation",
378                ));
379            }
380        },
381        Data::Enum(e) => from_json_enum(name, &e.variants, &container)?,
382        Data::Union(_) => {
383            return Err(syn::Error::new(input.span(), "unions are unsupported"));
384        }
385    };
386
387    Ok(quote! {
388        impl #impl_generics ::json_bourne::FromJson<#input_lt> for #name #ty_generics
389            #where_clause
390        {
391            fn from_lex(
392                __lex: &mut ::json_bourne::Lexer<#input_lt>,
393            ) -> ::core::result::Result<Self, ::json_bourne::Error> {
394                #body
395            }
396        }
397    })
398}
399
400fn from_json_named(
401    fields: &syn::punctuated::Punctuated<syn::Field, syn::Token![,]>,
402    container: &ContainerAttrs,
403) -> syn::Result<proc_macro2::TokenStream> {
404    let mut decls = Vec::new();
405    let mut arms = Vec::new();
406    let mut assigns = Vec::new();
407
408    for field in fields {
409        let name = field.ident.as_ref().unwrap();
410        let ty = &field.ty;
411        let fa = parse_field_attrs(&field.attrs)?;
412
413        if fa.skip {
414            assigns.push(quote! { #name: ::core::default::Default::default(), });
415            continue;
416        }
417
418        decls.push(quote! {
419            let mut #name: ::core::option::Option<#ty> = ::core::option::Option::None;
420        });
421
422        let key = key_expr(name, &fa.rename, &container.rename_all);
423        let acquire = acquire_expr(ty);
424        arms.push(quote! {
425            __bourne_k if __bourne_k == #key => {
426                if #name.is_some() {
427                    return ::core::result::Result::Err(::json_bourne::Error::new(
428                        ::json_bourne::ErrorKind::DuplicateKey,
429                        __lex.position(),
430                    ));
431                }
432                #name = ::core::option::Option::Some(#acquire);
433            }
434        });
435
436        // finalize: skip already handled; then default / Option / required.
437        let fin = if fa.default {
438            quote! { #name.unwrap_or_else(::core::default::Default::default) }
439        } else if is_option(ty) {
440            quote! { #name.unwrap_or(::core::option::Option::None) }
441        } else {
442            quote! {
443                #name.ok_or_else(|| ::json_bourne::Error::new(
444                    ::json_bourne::ErrorKind::MissingField,
445                    __lex.position(),
446                ))?
447            }
448        };
449        assigns.push(quote! { #name: #fin, });
450    }
451
452    let unknown_arm = if container.lenient {
453        // `deny_unknown_fields = false` -> skip unknown keys. Default is strict.
454        quote! { _ => { __lex.skip_value()?; } }
455    } else {
456        quote! {
457            _ => {
458                return ::core::result::Result::Err(::json_bourne::Error::new(
459                    ::json_bourne::ErrorKind::UnknownField,
460                    __lex.position(),
461                ));
462            }
463        }
464    };
465
466    Ok(quote! {
467        __lex.object_start()?;
468        #(#decls)*
469        let mut __maybe_key = __lex.object_first_key_lex()?;
470        while let ::core::option::Option::Some(__key_js) = __maybe_key {
471            let __key_cow = ::json_bourne::key_to_cow(__key_js, __lex)?;
472            match __key_cow.as_ref() {
473                #(#arms)*
474                #unknown_arm
475            }
476            __maybe_key = __lex.object_next_key_lex()?;
477        }
478        ::core::result::Result::Ok(Self { #(#assigns)* })
479    })
480}
481
482fn from_json_tuple(
483    fields: &syn::punctuated::Punctuated<syn::Field, syn::Token![,]>,
484) -> syn::Result<proc_macro2::TokenStream> {
485    let n = fields.len();
486    if n == 1 {
487        let ty = &fields[0].ty;
488        return Ok(quote! {
489            ::core::result::Result::Ok(Self(
490                <#ty as ::json_bourne::FromJson<'_>>::from_lex(__lex)?,
491            ))
492        });
493    }
494
495    // Multi-field: JSON array of exactly N elements.
496    let mut reads = Vec::new();
497    let mut idents = Vec::new();
498    for (i, field) in fields.iter().enumerate() {
499        let ty = &field.ty;
500        let id = Ident::new(&format!("__elem_{i}"), field.span());
501        if i == 0 {
502            reads.push(quote! {
503                let #id = <#ty as ::json_bourne::FromJson<'_>>::from_lex(__lex)?;
504            });
505        } else {
506            reads.push(quote! {
507                if __lex.array_continue(b']')? {
508                    return ::core::result::Result::Err(::json_bourne::Error::new(
509                        ::json_bourne::ErrorKind::TypeMismatch,
510                        __lex.position(),
511                    ));
512                }
513                let #id = <#ty as ::json_bourne::FromJson<'_>>::from_lex(__lex)?;
514            });
515        }
516        idents.push(id);
517    }
518
519    Ok(quote! {
520        if __lex.array_start()? {
521            return ::core::result::Result::Err(::json_bourne::Error::new(
522                ::json_bourne::ErrorKind::TypeMismatch,
523                __lex.position(),
524            ));
525        }
526        #(#reads)*
527        if !__lex.array_continue(b']')? {
528            return ::core::result::Result::Err(::json_bourne::Error::new(
529                ::json_bourne::ErrorKind::TypeMismatch,
530                __lex.position(),
531            ));
532        }
533        ::core::result::Result::Ok(Self( #(#idents),* ))
534    })
535}
536
537// ---------------------------------------------------------------------------
538// Enum parse. One function, dispatching by tagging mode, replaces the four
539// separate dispatch macros + their walkers (~700 lines of macros.rs).
540// ---------------------------------------------------------------------------
541
542fn from_json_enum(
543    name: &Ident,
544    variants: &syn::punctuated::Punctuated<syn::Variant, syn::Token![,]>,
545    container: &ContainerAttrs,
546) -> syn::Result<proc_macro2::TokenStream> {
547    match container.enum_mode() {
548        EnumMode::External => from_json_enum_external(name, variants, container),
549        EnumMode::Internal(tag) => from_json_enum_internal(name, variants, &tag),
550        EnumMode::Adjacent(tag, content) => from_json_enum_adjacent(name, variants, &tag, &content),
551        EnumMode::Untagged => from_json_enum_untagged(name, variants),
552    }
553}
554
555/// Read the array body for a tuple variant of arity `>= 2`, after the caller
556/// has consumed `[` and read element 0. Shared by every tagging mode.
557fn tuple_variant_read(ctor: &proc_macro2::TokenStream, tys: &[&Type]) -> proc_macro2::TokenStream {
558    let mut reads = Vec::new();
559    let mut idents = Vec::new();
560    for (i, ty) in tys.iter().enumerate() {
561        let id = Ident::new(&format!("__elem_{i}"), name_span());
562        if i == 0 {
563            reads.push(quote! {
564                let #id = <#ty as ::json_bourne::FromJson<'_>>::from_lex(__lex)?;
565            });
566        } else {
567            reads.push(quote! {
568                if __lex.array_continue(b']')? {
569                    return ::core::result::Result::Err(::json_bourne::Error::new(
570                        ::json_bourne::ErrorKind::TypeMismatch, __lex.position()));
571                }
572                let #id = <#ty as ::json_bourne::FromJson<'_>>::from_lex(__lex)?;
573            });
574        }
575        idents.push(id);
576    }
577    quote! {
578        if __lex.array_start()? {
579            return ::core::result::Result::Err(::json_bourne::Error::new(
580                ::json_bourne::ErrorKind::TypeMismatch, __lex.position()));
581        }
582        #(#reads)*
583        if !__lex.array_continue(b']')? {
584            return ::core::result::Result::Err(::json_bourne::Error::new(
585                ::json_bourne::ErrorKind::TypeMismatch, __lex.position()));
586        }
587        #ctor( #(#idents),* )
588    }
589}
590
591fn name_span() -> proc_macro2::Span {
592    proc_macro2::Span::call_site()
593}
594
595/// Emit the named-object body parse for a struct variant, constructing
596/// `ctor { .. }`. Reuses the same object-walk shape as plain structs.
597fn struct_variant_read(
598    ctor: &proc_macro2::TokenStream,
599    fields: &[(&Ident, &Type)],
600) -> proc_macro2::TokenStream {
601    let mut decls = Vec::new();
602    let mut arms = Vec::new();
603    let mut assigns = Vec::new();
604    for (fname, ty) in fields {
605        let key = fname.to_string();
606        let acquire = acquire_expr(ty);
607        decls.push(quote! {
608            let mut #fname: ::core::option::Option<#ty> = ::core::option::Option::None;
609        });
610        arms.push(quote! {
611            __bourne_k if __bourne_k == #key => {
612                if #fname.is_some() {
613                    return ::core::result::Result::Err(::json_bourne::Error::new(
614                        ::json_bourne::ErrorKind::DuplicateKey, __lex.position()));
615                }
616                #fname = ::core::option::Option::Some(#acquire);
617            }
618        });
619        let fin = if is_option(ty) {
620            quote! { #fname.unwrap_or(::core::option::Option::None) }
621        } else {
622            quote! {
623                #fname.ok_or_else(|| ::json_bourne::Error::new(
624                    ::json_bourne::ErrorKind::MissingField, __lex.position()))?
625            }
626        };
627        assigns.push(quote! { #fname: #fin, });
628    }
629    quote! {{
630        __lex.object_start()?;
631        #(#decls)*
632        let mut __maybe_key = __lex.object_first_key_lex()?;
633        while let ::core::option::Option::Some(__key_js) = __maybe_key {
634            let __key_cow = ::json_bourne::key_to_cow(__key_js, __lex)?;
635            match __key_cow.as_ref() {
636                #(#arms)*
637                _ => {
638                    return ::core::result::Result::Err(::json_bourne::Error::new(
639                        ::json_bourne::ErrorKind::UnknownField, __lex.position()));
640                }
641            }
642            __maybe_key = __lex.object_next_key_lex()?;
643        }
644        #ctor { #(#assigns)* }
645    }}
646}
647
648fn from_json_enum_external(
649    name: &Ident,
650    variants: &syn::punctuated::Punctuated<syn::Variant, syn::Token![,]>,
651    container: &ContainerAttrs,
652) -> syn::Result<proc_macro2::TokenStream> {
653    let mut unit_arms = Vec::new();
654    let mut tagged_arms = Vec::new();
655    for v in variants {
656        let vname = &v.ident;
657        let rename = parse_variant_rename(&v.attrs)?;
658        let key = key_expr(vname, &rename, &container.rename_all);
659        let ctor = quote!( #name::#vname );
660        match classify_variant(v) {
661            VShape::Unit => unit_arms.push(quote! {
662                __bourne_t if __bourne_t == #key => ::core::result::Result::Ok(#ctor),
663            }),
664            VShape::Newtype(ty) => tagged_arms.push(quote! {
665                __bourne_t if __bourne_t == #key =>
666                    #ctor(<#ty as ::json_bourne::FromJson<'_>>::from_lex(__lex)?),
667            }),
668            VShape::Tuple(tys) => {
669                let body = tuple_variant_read(&ctor, &tys);
670                tagged_arms.push(quote! {
671                    __bourne_t if __bourne_t == #key => { #body },
672                });
673            }
674            VShape::Struct(fields) => {
675                let body = struct_variant_read(&ctor, &fields);
676                tagged_arms.push(quote! {
677                    __bourne_t if __bourne_t == #key => #body,
678                });
679            }
680        }
681    }
682
683    let object_dispatch = if tagged_arms.is_empty() {
684        quote!()
685    } else {
686        quote! {
687            ::json_bourne::ValueKind::Object => {
688                __lex.object_start()?;
689                let __key_js = __lex.object_first_key_lex()?.ok_or_else(|| {
690                    ::json_bourne::Error::new(::json_bourne::ErrorKind::UnknownField, __lex.position())
691                })?;
692                let __key_cow = ::json_bourne::key_to_cow(__key_js, __lex)?;
693                let __value = match __key_cow.as_ref() {
694                    #(#tagged_arms)*
695                    _ => return ::core::result::Result::Err(::json_bourne::Error::new(
696                        ::json_bourne::ErrorKind::UnknownField, __lex.position())),
697                };
698                if __lex.object_next_key_lex()?.is_some() {
699                    return ::core::result::Result::Err(::json_bourne::Error::new(
700                        ::json_bourne::ErrorKind::UnknownField, __lex.position()));
701                }
702                ::core::result::Result::Ok(__value)
703            }
704        }
705    };
706
707    Ok(quote! {
708        match __lex.peek_value_kind()? {
709            ::json_bourne::ValueKind::String => {
710                let __tag = __lex.parse_str_value()?;
711                match __tag {
712                    #(#unit_arms)*
713                    _ => ::core::result::Result::Err(::json_bourne::Error::new(
714                        ::json_bourne::ErrorKind::UnknownField, __lex.position())),
715                }
716            }
717            #object_dispatch
718            _ => ::core::result::Result::Err(::json_bourne::Error::new(
719                ::json_bourne::ErrorKind::TypeMismatch, __lex.position())),
720        }
721    })
722}
723
724fn from_json_enum_internal(
725    name: &Ident,
726    variants: &syn::punctuated::Punctuated<syn::Variant, syn::Token![,]>,
727    tag: &str,
728) -> syn::Result<proc_macro2::TokenStream> {
729    let mut arms = Vec::new();
730    for v in variants {
731        let vname = &v.ident;
732        let rename = parse_variant_rename(&v.attrs)?;
733        let key = key_expr(vname, &rename, &None);
734        let ctor = quote!( #name::#vname );
735        match classify_variant(v) {
736            VShape::Unit => arms.push(quote! {
737                __t if __t == #key => {
738                    // Drain remaining keys; only the tag key is allowed.
739                    __lex.object_start()?;
740                    let mut __mk = __lex.object_first_key_lex()?;
741                    while let ::core::option::Option::Some(__kjs) = __mk {
742                        let __kc = ::json_bourne::key_to_cow(__kjs, __lex)?;
743                        if __kc.as_ref() == #tag {
744                            __lex.skip_value()?;
745                        } else {
746                            return ::core::result::Result::Err(::json_bourne::Error::new(
747                                ::json_bourne::ErrorKind::UnknownField, __lex.position()));
748                        }
749                        __mk = __lex.object_next_key_lex()?;
750                    }
751                    ::core::result::Result::Ok(#ctor)
752                }
753            }),
754            VShape::Struct(fields) => {
755                let body = internal_struct_variant_read(&ctor, &fields, tag);
756                arms.push(quote! { __t if __t == #key => #body, });
757            }
758            _ => {
759                return Err(syn::Error::new(
760                    v.ident.span(),
761                    "internally-tagged enums support only unit and struct variants",
762                ));
763            }
764        }
765    }
766
767    Ok(quote! {{
768        let __cp = __lex.checkpoint();
769        __lex.object_start()?;
770        let mut __maybe_key = __lex.object_first_key_lex()?;
771        let __tag_value: &str = loop {
772            let ::core::option::Option::Some(__key_js) = __maybe_key else {
773                return ::core::result::Result::Err(::json_bourne::Error::new(
774                    ::json_bourne::ErrorKind::MissingField, __lex.position()));
775            };
776            let __key_cow = ::json_bourne::key_to_cow(__key_js, __lex)?;
777            if __key_cow.as_ref() == #tag {
778                break __lex.parse_str_value()?;
779            }
780            __lex.skip_value()?;
781            __maybe_key = __lex.object_next_key_lex()?;
782        };
783        __lex.restore(__cp);
784        match __tag_value {
785            #(#arms)*
786            _ => ::core::result::Result::Err(::json_bourne::Error::new(
787                ::json_bourne::ErrorKind::UnknownField, __lex.position())),
788        }
789    }})
790}
791
792/// Struct-variant body for internally-tagged mode: the tag key is a sibling
793/// and must be skipped (not treated as unknown).
794fn internal_struct_variant_read(
795    ctor: &proc_macro2::TokenStream,
796    fields: &[(&Ident, &Type)],
797    tag: &str,
798) -> proc_macro2::TokenStream {
799    let mut decls = Vec::new();
800    let mut arms = Vec::new();
801    let mut assigns = Vec::new();
802    for (fname, ty) in fields {
803        let key = fname.to_string();
804        let acquire = acquire_expr(ty);
805        decls.push(quote! {
806            let mut #fname: ::core::option::Option<#ty> = ::core::option::Option::None;
807        });
808        arms.push(quote! {
809            __bourne_k if __bourne_k == #key => {
810                if #fname.is_some() {
811                    return ::core::result::Result::Err(::json_bourne::Error::new(
812                        ::json_bourne::ErrorKind::DuplicateKey, __lex.position()));
813                }
814                #fname = ::core::option::Option::Some(#acquire);
815            }
816        });
817        let fin = if is_option(ty) {
818            quote! { #fname.unwrap_or(::core::option::Option::None) }
819        } else {
820            quote! {
821                #fname.ok_or_else(|| ::json_bourne::Error::new(
822                    ::json_bourne::ErrorKind::MissingField, __lex.position()))?
823            }
824        };
825        assigns.push(quote! { #fname: #fin, });
826    }
827    quote! {{
828        __lex.object_start()?;
829        #(#decls)*
830        let mut __maybe_key = __lex.object_first_key_lex()?;
831        while let ::core::option::Option::Some(__key_js) = __maybe_key {
832            let __key_cow = ::json_bourne::key_to_cow(__key_js, __lex)?;
833            match __key_cow.as_ref() {
834                #(#arms)*
835                __bourne_k if __bourne_k == #tag => { __lex.skip_value()?; }
836                _ => {
837                    return ::core::result::Result::Err(::json_bourne::Error::new(
838                        ::json_bourne::ErrorKind::UnknownField, __lex.position()));
839                }
840            }
841            __maybe_key = __lex.object_next_key_lex()?;
842        }
843        ::core::result::Result::Ok(#ctor { #(#assigns)* })
844    }}
845}
846
847fn from_json_enum_adjacent(
848    name: &Ident,
849    variants: &syn::punctuated::Punctuated<syn::Variant, syn::Token![,]>,
850    tag: &str,
851    content: &str,
852) -> syn::Result<proc_macro2::TokenStream> {
853    let mut arms = Vec::new();
854    for v in variants {
855        let vname = &v.ident;
856        let rename = parse_variant_rename(&v.attrs)?;
857        let key = key_expr(vname, &rename, &None);
858        let ctor = quote!( #name::#vname );
859        match classify_variant(v) {
860            VShape::Unit => arms.push(quote! {
861                __t if __t == #key => {
862                    if __content_cp.is_some() {
863                        ::core::result::Result::Err(::json_bourne::Error::new(
864                            ::json_bourne::ErrorKind::UnknownField, __lex.position()))
865                    } else {
866                        ::core::result::Result::Ok(#ctor)
867                    }
868                }
869            }),
870            VShape::Newtype(ty) => arms.push(quote! {
871                __t if __t == #key => match __content_cp {
872                    ::core::option::Option::Some(__c) => {
873                        __lex.restore(__c);
874                        ::core::result::Result::Ok(#ctor(
875                            <#ty as ::json_bourne::FromJson<'_>>::from_lex(__lex)?))
876                    }
877                    ::core::option::Option::None => ::core::result::Result::Err(
878                        ::json_bourne::Error::new(::json_bourne::ErrorKind::MissingField, __lex.position())),
879                }
880            }),
881            VShape::Tuple(tys) => {
882                let body = tuple_variant_read(&ctor, &tys);
883                arms.push(quote! {
884                    __t if __t == #key => match __content_cp {
885                        ::core::option::Option::Some(__c) => {
886                            __lex.restore(__c);
887                            ::core::result::Result::Ok({ #body })
888                        }
889                        ::core::option::Option::None => ::core::result::Result::Err(
890                            ::json_bourne::Error::new(::json_bourne::ErrorKind::MissingField, __lex.position())),
891                    }
892                });
893            }
894            VShape::Struct(fields) => {
895                let body = struct_variant_read(&ctor, &fields);
896                arms.push(quote! {
897                    __t if __t == #key => match __content_cp {
898                        ::core::option::Option::Some(__c) => {
899                            __lex.restore(__c);
900                            ::core::result::Result::Ok(#body)
901                        }
902                        ::core::option::Option::None => ::core::result::Result::Err(
903                            ::json_bourne::Error::new(::json_bourne::ErrorKind::MissingField, __lex.position())),
904                    }
905                });
906            }
907        }
908    }
909
910    Ok(quote! {{
911        __lex.object_start()?;
912        let mut __tag_value: ::core::option::Option<&str> = ::core::option::Option::None;
913        let mut __content_cp: ::core::option::Option<::json_bourne::Checkpoint> =
914            ::core::option::Option::None;
915        let mut __maybe_key = __lex.object_first_key_lex()?;
916        while let ::core::option::Option::Some(__key_js) = __maybe_key {
917            let __key_cow = ::json_bourne::key_to_cow(__key_js, __lex)?;
918            if __key_cow.as_ref() == #tag {
919                if __tag_value.is_some() {
920                    return ::core::result::Result::Err(::json_bourne::Error::new(
921                        ::json_bourne::ErrorKind::DuplicateKey, __lex.position()));
922                }
923                __tag_value = ::core::option::Option::Some(__lex.parse_str_value()?);
924            } else if __key_cow.as_ref() == #content {
925                if __content_cp.is_some() {
926                    return ::core::result::Result::Err(::json_bourne::Error::new(
927                        ::json_bourne::ErrorKind::DuplicateKey, __lex.position()));
928                }
929                __content_cp = ::core::option::Option::Some(__lex.checkpoint());
930                __lex.skip_value()?;
931            } else {
932                return ::core::result::Result::Err(::json_bourne::Error::new(
933                    ::json_bourne::ErrorKind::UnknownField, __lex.position()));
934            }
935            __maybe_key = __lex.object_next_key_lex()?;
936        }
937        let __tag = __tag_value.ok_or_else(|| ::json_bourne::Error::new(
938            ::json_bourne::ErrorKind::MissingField, __lex.position()))?;
939        let __post_cp = __lex.checkpoint();
940        let __value = match __tag {
941            #(#arms)*
942            _ => ::core::result::Result::Err(::json_bourne::Error::new(
943                ::json_bourne::ErrorKind::UnknownField, __lex.position())),
944        }?;
945        __lex.restore(__post_cp);
946        ::core::result::Result::Ok(__value)
947    }})
948}
949
950fn from_json_enum_untagged(
951    name: &Ident,
952    variants: &syn::punctuated::Punctuated<syn::Variant, syn::Token![,]>,
953) -> syn::Result<proc_macro2::TokenStream> {
954    let mut attempts = Vec::new();
955    for v in variants {
956        let vname = &v.ident;
957        let ctor = quote!( #name::#vname );
958        let try_body = match classify_variant(v) {
959            VShape::Unit => quote! {
960                <() as ::json_bourne::FromJson<'_>>::from_lex(__lex)?;
961                ::core::result::Result::Ok(#ctor)
962            },
963            VShape::Newtype(ty) => quote! {
964                ::core::result::Result::Ok(#ctor(
965                    <#ty as ::json_bourne::FromJson<'_>>::from_lex(__lex)?))
966            },
967            VShape::Tuple(tys) => {
968                let body = tuple_variant_read(&ctor, &tys);
969                quote! { ::core::result::Result::Ok({ #body }) }
970            }
971            VShape::Struct(fields) => {
972                let body = struct_variant_read(&ctor, &fields);
973                quote! { ::core::result::Result::Ok(#body) }
974            }
975        };
976        attempts.push(quote! {
977            __lex.restore(__cp);
978            let __try: ::core::result::Result<Self, ::json_bourne::Error> = (|| { #try_body })();
979            if let ::core::result::Result::Ok(__v) = __try {
980                return ::core::result::Result::Ok(__v);
981            }
982        });
983    }
984    Ok(quote! {{
985        let __cp = __lex.checkpoint();
986        #(#attempts)*
987        ::core::result::Result::Err(::json_bourne::Error::new(
988            ::json_bourne::ErrorKind::TypeMismatch, __lex.position()))
989    }})
990}
991
992// ===========================================================================
993// ToJson derive
994// ===========================================================================
995
996#[proc_macro_derive(ToJson, attributes(bourne))]
997pub fn derive_to_json(input: TokenStream) -> TokenStream {
998    let input = parse_macro_input!(input as DeriveInput);
999    match to_json_impl(&input) {
1000        Ok(ts) => ts.into(),
1001        Err(e) => e.to_compile_error().into(),
1002    }
1003}
1004
1005fn to_json_impl(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
1006    let name = &input.ident;
1007    let container = parse_container_attrs(&input.attrs)?;
1008
1009    // ToJson has no `'input` bound; use the type's own generics verbatim,
1010    // plus a `T: ToJson` bound per type param.
1011    let generics = &input.generics;
1012    let (impl_g, ty_g, _where_g) = generics.split_for_impl();
1013    let type_param_bounds: Vec<proc_macro2::TokenStream> = generics
1014        .params
1015        .iter()
1016        .filter_map(|p| match p {
1017            GenericParam::Type(t) => {
1018                let id = &t.ident;
1019                Some(quote!(#id: ::json_bourne::ToJson))
1020            }
1021            _ => None,
1022        })
1023        .collect();
1024    let where_clause = if type_param_bounds.is_empty() {
1025        quote!()
1026    } else {
1027        quote!(where #(#type_param_bounds),*)
1028    };
1029
1030    let body = match &input.data {
1031        Data::Struct(s) => match &s.fields {
1032            Fields::Named(named) => to_json_named(&named.named, &container)?,
1033            Fields::Unnamed(unnamed) => to_json_tuple(unnamed.unnamed.len()),
1034            Fields::Unit => {
1035                return Err(syn::Error::new(input.span(), "unit structs unsupported"));
1036            }
1037        },
1038        Data::Enum(e) => to_json_enum(name, &e.variants, &container)?,
1039        Data::Union(_) => {
1040            return Err(syn::Error::new(input.span(), "unions are unsupported"));
1041        }
1042    };
1043
1044    Ok(quote! {
1045        impl #impl_g ::json_bourne::ToJson for #name #ty_g #where_clause {
1046            fn write_json<__W: ::json_bourne::JsonWrite + ?::core::marker::Sized>(
1047                &self,
1048                __w: &mut __W,
1049            ) -> ::core::result::Result<(), __W::Error> {
1050                #body
1051            }
1052        }
1053    })
1054}
1055
1056fn to_json_named(
1057    fields: &syn::punctuated::Punctuated<syn::Field, syn::Token![,]>,
1058    container: &ContainerAttrs,
1059) -> syn::Result<proc_macro2::TokenStream> {
1060    // Split into "static" plain fields (comma placement known at compile
1061    // time -> fused `,"key":` literals) vs skip_if_none (runtime comma).
1062    // The macro does this with a `static_first` state token; here it's just
1063    // a running boolean over the field list.
1064    let mut stmts = Vec::new();
1065    stmts.push(quote! { __w.write_raw_bytes(b"{")?; });
1066    stmts.push(quote! {
1067        #[allow(unused_assignments, unused_mut, unused_variables)]
1068        let mut __first: bool = true;
1069    });
1070
1071    // Static-comma tracking mirrors the macro's `static_first` token:
1072    //   `Yes`   -> nothing committed yet at compile time (first plain field
1073    //             elides its leading comma).
1074    //   `No`    -> a plain field definitely committed; leading comma is static.
1075    //   `Maybe` -> a prior skip_if_none makes comma posture runtime-dependent.
1076    #[derive(Clone, Copy, PartialEq)]
1077    enum StaticFirst {
1078        Yes,
1079        No,
1080        Maybe,
1081    }
1082    let mut sf = StaticFirst::Yes;
1083
1084    for field in fields {
1085        let name = field.ident.as_ref().unwrap();
1086        let ty = &field.ty;
1087        let fa = parse_field_attrs(&field.attrs)?;
1088        if fa.skip {
1089            continue;
1090        }
1091
1092        let plain = fa.rename.is_none() && container.rename_all.is_none();
1093
1094        if fa.skip_if_none && is_option(ty) {
1095            // Runtime-conditional field. Comma depends on prior emits.
1096            let key = key_expr(name, &fa.rename, &container.rename_all);
1097            let comma = match sf {
1098                StaticFirst::Yes => quote! {},
1099                StaticFirst::No => quote! { __w.write_raw_bytes(b",")?; },
1100                StaticFirst::Maybe => quote! { if !__first { __w.write_raw_bytes(b",")?; } },
1101            };
1102            stmts.push(quote! {
1103                if let ::core::option::Option::Some(ref __v) = self.#name {
1104                    #comma
1105                    __w.write_escaped_str(#key)?;
1106                    __w.write_raw_bytes(b":")?;
1107                    ::json_bourne::ToJson::write_json(__v, __w)?;
1108                    __first = false;
1109                }
1110            });
1111            sf = StaticFirst::Maybe;
1112            continue;
1113        }
1114
1115        if plain && sf != StaticFirst::Maybe {
1116            // Fast path: fold (comma?)+key+colon into one compile-time literal,
1117            // no runtime branch. Matches the macro's headline emit.
1118            let name_str = name.to_string();
1119            let lit = match sf {
1120                StaticFirst::Yes => format!("\"{name_str}\":"),
1121                _ => format!(",\"{name_str}\":"),
1122            };
1123            stmts.push(quote! {
1124                __w.write_raw_bytes(#lit.as_bytes())?;
1125                ::json_bourne::ToJson::write_json(&self.#name, __w)?;
1126            });
1127            sf = StaticFirst::No;
1128        } else {
1129            // Dynamic comma (rename/rename_all key may need escaping, or a
1130            // prior skip_if_none made posture runtime-dependent).
1131            let key = key_expr(name, &fa.rename, &container.rename_all);
1132            let comma = match sf {
1133                StaticFirst::Yes => quote! {},
1134                StaticFirst::No => quote! { __w.write_raw_bytes(b",")?; },
1135                StaticFirst::Maybe => quote! { if !__first { __w.write_raw_bytes(b",")?; } },
1136            };
1137            stmts.push(quote! {
1138                #comma
1139                __w.write_escaped_str(#key)?;
1140                __w.write_raw_bytes(b":")?;
1141                ::json_bourne::ToJson::write_json(&self.#name, __w)?;
1142                __first = false;
1143            });
1144            sf = if sf == StaticFirst::Maybe {
1145                StaticFirst::Maybe
1146            } else {
1147                StaticFirst::No
1148            };
1149        }
1150    }
1151
1152    stmts.push(quote! { __w.write_raw_bytes(b"}")?; });
1153    stmts.push(quote! { ::core::result::Result::Ok(()) });
1154    Ok(quote! { #(#stmts)* })
1155}
1156
1157fn to_json_tuple(n: usize) -> proc_macro2::TokenStream {
1158    if n == 1 {
1159        return quote! {
1160            ::json_bourne::ToJson::write_json(&self.0, __w)
1161        };
1162    }
1163    let mut stmts = Vec::new();
1164    stmts.push(quote! { __w.write_raw_bytes(b"[")?; });
1165    for i in 0..n {
1166        let idx = syn::Index::from(i);
1167        if i > 0 {
1168            stmts.push(quote! { __w.write_raw_bytes(b",")?; });
1169        }
1170        stmts.push(quote! { ::json_bourne::ToJson::write_json(&self.#idx, __w)?; });
1171    }
1172    stmts.push(quote! { __w.write_raw_bytes(b"]")?; });
1173    stmts.push(quote! { ::core::result::Result::Ok(()) });
1174    quote! { #(#stmts)* }
1175}
1176
1177// ---------------------------------------------------------------------------
1178// Enum serialize.
1179// ---------------------------------------------------------------------------
1180
1181/// Bindings + emit block for a variant's fields, shared across modes.
1182/// Returns (pattern_after_ident, body_that_writes_payload). The payload
1183/// writer for struct/tuple assumes it is writing the *bare* payload; each
1184/// mode wraps it with the appropriate framing.
1185fn variant_field_idents(n: usize) -> Vec<Ident> {
1186    (0..n)
1187        .map(|i| Ident::new(&format!("__f{i}"), name_span()))
1188        .collect()
1189}
1190
1191fn to_json_enum(
1192    name: &Ident,
1193    variants: &syn::punctuated::Punctuated<syn::Variant, syn::Token![,]>,
1194    container: &ContainerAttrs,
1195) -> syn::Result<proc_macro2::TokenStream> {
1196    let mode = container.enum_mode();
1197    let mut arms = Vec::new();
1198    for v in variants {
1199        let vname = &v.ident;
1200        let rename = parse_variant_rename(&v.attrs)?;
1201        let tagkey = key_expr(vname, &rename, &container.rename_all);
1202        arms.push(to_json_variant_arm(name, v, vname, &tagkey, &mode)?);
1203    }
1204    Ok(quote! {
1205        match self {
1206            #(#arms)*
1207        }
1208    })
1209}
1210
1211/// Write a struct-variant's fields as `"k":v,...` (no surrounding braces).
1212fn write_named_fields(fields: &[(&Ident, Ident)]) -> proc_macro2::TokenStream {
1213    // fields: (json_key_ident_for_stringify, binding_ident)
1214    let mut stmts = Vec::new();
1215    stmts.push(quote! { let mut __first = true; });
1216    for (key_ident, bind) in fields {
1217        let key = key_ident.to_string();
1218        let lit = format!("\"{key}\":");
1219        let lit_comma = format!(",\"{key}\":");
1220        stmts.push(quote! {
1221            if __first {
1222                __w.write_raw_bytes(#lit.as_bytes())?;
1223                __first = false;
1224            } else {
1225                __w.write_raw_bytes(#lit_comma.as_bytes())?;
1226            }
1227            ::json_bourne::ToJson::write_json(#bind, __w)?;
1228        });
1229    }
1230    quote! { #(#stmts)* }
1231}
1232
1233fn to_json_variant_arm(
1234    name: &Ident,
1235    v: &syn::Variant,
1236    vname: &Ident,
1237    tagkey: &proc_macro2::TokenStream,
1238    mode: &EnumMode,
1239) -> syn::Result<proc_macro2::TokenStream> {
1240    let shape = classify_variant(v);
1241    // Build pattern + payload writer generically, then frame per mode.
1242    Ok(match (mode, &shape) {
1243        // ---- Untagged ----
1244        (EnumMode::Untagged, VShape::Unit) => quote! {
1245            #name::#vname => { __w.write_raw_bytes(b"null")?; ::core::result::Result::Ok(()) }
1246        },
1247        (EnumMode::Untagged, VShape::Newtype(_)) => quote! {
1248            #name::#vname(__inner) => {
1249                ::json_bourne::ToJson::write_json(__inner, __w)?;
1250                ::core::result::Result::Ok(())
1251            }
1252        },
1253        (EnumMode::Untagged, VShape::Tuple(tys)) => {
1254            let binds = variant_field_idents(tys.len());
1255            let writes = tuple_payload_writes(&binds);
1256            quote! {
1257                #name::#vname( #(#binds),* ) => { #writes ::core::result::Result::Ok(()) }
1258            }
1259        }
1260        (EnumMode::Untagged, VShape::Struct(fields)) => {
1261            let (pat, pairs) = struct_pat_and_pairs(fields);
1262            let body = write_named_fields(&pairs);
1263            quote! {
1264                #name::#vname { #pat } => {
1265                    __w.write_raw_bytes(b"{")?;
1266                    #body
1267                    __w.write_raw_bytes(b"}")?;
1268                    ::core::result::Result::Ok(())
1269                }
1270            }
1271        }
1272
1273        // ---- External ----
1274        (EnumMode::External, VShape::Unit) => quote! {
1275            #name::#vname => {
1276                __w.write_raw_bytes(b"\"")?;
1277                __w.write_str_raw(#tagkey)?;
1278                __w.write_raw_bytes(b"\"")?;
1279                ::core::result::Result::Ok(())
1280            }
1281        },
1282        (EnumMode::External, VShape::Newtype(_)) => quote! {
1283            #name::#vname(__inner) => {
1284                __w.write_raw_bytes(b"{")?;
1285                __w.write_escaped_str(#tagkey)?;
1286                __w.write_raw_bytes(b":")?;
1287                ::json_bourne::ToJson::write_json(__inner, __w)?;
1288                __w.write_raw_bytes(b"}")?;
1289                ::core::result::Result::Ok(())
1290            }
1291        },
1292        (EnumMode::External, VShape::Tuple(tys)) => {
1293            let binds = variant_field_idents(tys.len());
1294            let writes = tuple_payload_writes(&binds);
1295            quote! {
1296                #name::#vname( #(#binds),* ) => {
1297                    __w.write_raw_bytes(b"{")?;
1298                    __w.write_escaped_str(#tagkey)?;
1299                    __w.write_raw_bytes(b":")?;
1300                    #writes
1301                    __w.write_raw_bytes(b"}")?;
1302                    ::core::result::Result::Ok(())
1303                }
1304            }
1305        }
1306        (EnumMode::External, VShape::Struct(fields)) => {
1307            let (pat, pairs) = struct_pat_and_pairs(fields);
1308            let body = write_named_fields(&pairs);
1309            quote! {
1310                #name::#vname { #pat } => {
1311                    __w.write_raw_bytes(b"{")?;
1312                    __w.write_escaped_str(#tagkey)?;
1313                    __w.write_raw_bytes(b":{")?;
1314                    #body
1315                    __w.write_raw_bytes(b"}}")?;
1316                    ::core::result::Result::Ok(())
1317                }
1318            }
1319        }
1320
1321        // ---- Internal ----
1322        (EnumMode::Internal(tag), VShape::Unit) => quote! {
1323            #name::#vname => {
1324                __w.write_raw_bytes(b"{")?;
1325                __w.write_escaped_str(#tag)?;
1326                __w.write_raw_bytes(b":\"")?;
1327                __w.write_str_raw(#tagkey)?;
1328                __w.write_raw_bytes(b"\"}")?;
1329                ::core::result::Result::Ok(())
1330            }
1331        },
1332        (EnumMode::Internal(tag), VShape::Struct(fields)) => {
1333            let (pat, pairs) = struct_pat_and_pairs(fields);
1334            // Internal mode always has the tag first, so fields use the
1335            // comma-leading form unconditionally.
1336            let mut stmts = Vec::new();
1337            for (key_ident, bind) in &pairs {
1338                let key = key_ident.to_string();
1339                let lit = format!(",\"{key}\":");
1340                stmts.push(quote! {
1341                    __w.write_raw_bytes(#lit.as_bytes())?;
1342                    ::json_bourne::ToJson::write_json(#bind, __w)?;
1343                });
1344            }
1345            quote! {
1346                #name::#vname { #pat } => {
1347                    __w.write_raw_bytes(b"{")?;
1348                    __w.write_escaped_str(#tag)?;
1349                    __w.write_raw_bytes(b":\"")?;
1350                    __w.write_str_raw(#tagkey)?;
1351                    __w.write_raw_bytes(b"\"")?;
1352                    #(#stmts)*
1353                    __w.write_raw_bytes(b"}")?;
1354                    ::core::result::Result::Ok(())
1355                }
1356            }
1357        }
1358        (EnumMode::Internal(_), _) => {
1359            return Err(syn::Error::new(
1360                v.ident.span(),
1361                "internally-tagged enums support only unit and struct variants",
1362            ));
1363        }
1364
1365        // ---- Adjacent ----
1366        (EnumMode::Adjacent(tag, _content), VShape::Unit) => quote! {
1367            #name::#vname => {
1368                __w.write_raw_bytes(b"{")?;
1369                __w.write_escaped_str(#tag)?;
1370                __w.write_raw_bytes(b":\"")?;
1371                __w.write_str_raw(#tagkey)?;
1372                __w.write_raw_bytes(b"\"}")?;
1373                ::core::result::Result::Ok(())
1374            }
1375        },
1376        (EnumMode::Adjacent(tag, content), VShape::Newtype(_)) => quote! {
1377            #name::#vname(__inner) => {
1378                __w.write_raw_bytes(b"{")?;
1379                __w.write_escaped_str(#tag)?;
1380                __w.write_raw_bytes(b":\"")?;
1381                __w.write_str_raw(#tagkey)?;
1382                __w.write_raw_bytes(b"\",")?;
1383                __w.write_escaped_str(#content)?;
1384                __w.write_raw_bytes(b":")?;
1385                ::json_bourne::ToJson::write_json(__inner, __w)?;
1386                __w.write_raw_bytes(b"}")?;
1387                ::core::result::Result::Ok(())
1388            }
1389        },
1390        (EnumMode::Adjacent(tag, content), VShape::Tuple(tys)) => {
1391            let binds = variant_field_idents(tys.len());
1392            let writes = tuple_payload_writes(&binds);
1393            quote! {
1394                #name::#vname( #(#binds),* ) => {
1395                    __w.write_raw_bytes(b"{")?;
1396                    __w.write_escaped_str(#tag)?;
1397                    __w.write_raw_bytes(b":\"")?;
1398                    __w.write_str_raw(#tagkey)?;
1399                    __w.write_raw_bytes(b"\",")?;
1400                    __w.write_escaped_str(#content)?;
1401                    __w.write_raw_bytes(b":")?;
1402                    #writes
1403                    __w.write_raw_bytes(b"}")?;
1404                    ::core::result::Result::Ok(())
1405                }
1406            }
1407        }
1408        (EnumMode::Adjacent(tag, content), VShape::Struct(fields)) => {
1409            let (pat, pairs) = struct_pat_and_pairs(fields);
1410            let body = write_named_fields(&pairs);
1411            quote! {
1412                #name::#vname { #pat } => {
1413                    __w.write_raw_bytes(b"{")?;
1414                    __w.write_escaped_str(#tag)?;
1415                    __w.write_raw_bytes(b":\"")?;
1416                    __w.write_str_raw(#tagkey)?;
1417                    __w.write_raw_bytes(b"\",")?;
1418                    __w.write_escaped_str(#content)?;
1419                    __w.write_raw_bytes(b":{")?;
1420                    #body
1421                    __w.write_raw_bytes(b"}}")?;
1422                    ::core::result::Result::Ok(())
1423                }
1424            }
1425        }
1426    })
1427}
1428
1429/// For a tuple payload write bindings `__f0..` as a JSON array.
1430fn tuple_payload_writes(binds: &[Ident]) -> proc_macro2::TokenStream {
1431    let mut stmts = Vec::new();
1432    stmts.push(quote! { __w.write_raw_bytes(b"[")?; });
1433    for (i, b) in binds.iter().enumerate() {
1434        if i > 0 {
1435            stmts.push(quote! { __w.write_raw_bytes(b",")?; });
1436        }
1437        stmts.push(quote! { ::json_bourne::ToJson::write_json(#b, __w)?; });
1438    }
1439    stmts.push(quote! { __w.write_raw_bytes(b"]")?; });
1440    quote! { #(#stmts)* }
1441}
1442
1443/// Build a struct-variant binding pattern `field: __fN, ...` and the pairs
1444/// `(field_ident_for_key, binding_ident)`.
1445fn struct_pat_and_pairs<'a>(
1446    fields: &'a [(&'a Ident, &'a Type)],
1447) -> (proc_macro2::TokenStream, Vec<(&'a Ident, Ident)>) {
1448    let mut pat = Vec::new();
1449    let mut pairs = Vec::new();
1450    for (i, (fname, _ty)) in fields.iter().enumerate() {
1451        let bind = Ident::new(&format!("__f{i}"), name_span());
1452        pat.push(quote! { #fname: #bind });
1453        pairs.push((*fname, bind));
1454    }
1455    (quote! { #(#pat),* }, pairs)
1456}