Skip to main content

nextjson_derive/
lib.rs

1//! Zero-dependency derive macros for `nextjson`.
2//!
3//! Implemented entirely with the standard `proc_macro` API: no `syn`, no
4//! `quote`, no `proc-macro2`. The input `TokenStream` is parsed by a
5//! hand-written recursive-descent parser into a small AST, and the output is
6//! generated as text and re-parsed.
7//!
8//! ## Forward compatibility contract
9//!
10//! A derive macro only ever receives a single item's tokens, and this parser
11//! interprets a deliberately **stable grammar subset**: the item header
12//! (`struct` / `enum` + name), the generic parameter list, the `where`
13//! clause, and the field / variant structure, plus `#[njson]` /
14//! `#[nextjson]` / `#[serde]` attributes. Everything inside a field or
15//! generic *type position* is carried through verbatim as an opaque token
16//! sequence, so new Rust syntax that appears in type positions (new
17//! literals, `impl Trait` forms, associated-type paths, ...) needs no parser
18//! change — it is round-tripped unchanged.
19//!
20//! The risk of future item-level grammar changes is handled defensively:
21//! `parse_input` requires that **every** input token be consumed. If a future
22//! Rust release extends item syntax in a way this parser does not understand,
23//! the macro fails with a loud `compile_error!` naming the leftover tokens
24//! instead of silently generating impls from a mis-parsed subset.
25
26#![deny(unsafe_code)]
27#![deny(missing_docs)]
28#![doc(html_root_url = "https://docs.rs/nextjson-derive")]
29
30extern crate proc_macro;
31
32use proc_macro::{Delimiter, Spacing, TokenStream, TokenTree};
33use std::str::FromStr;
34
35mod attr;
36mod case;
37mod de;
38mod schema;
39mod ser;
40
41pub(crate) use attr::{ContainerAttrs, FieldAttrs, Meta, VariantAttrs};
42
43/// Parse a string into a TokenStream.
44pub(crate) fn ts(s: &str) -> TokenStream {
45    TokenStream::from_str(s)
46        .unwrap_or_else(|e| panic!("nextjson-derive: invalid generated tokens: {e:?}"))
47}
48
49/// Build a `compile_error!` expansion from a message.
50pub(crate) fn err(msg: &str) -> TokenStream {
51    ts(&format!("::core::compile_error!({:?});", msg))
52}
53
54/// Error string returned by codegen helpers.
55pub(crate) fn err_str(msg: &str) -> String {
56    msg.to_string()
57}
58
59/// Token cursor over a slice of TokenTrees.
60pub(crate) struct P<'a> {
61    pub toks: &'a [TokenTree],
62    pub i: usize,
63}
64
65impl<'a> P<'a> {
66    pub fn peek(&self) -> Option<&TokenTree> {
67        self.toks.get(self.i)
68    }
69    pub fn next(&mut self) -> Option<TokenTree> {
70        let t = self.toks.get(self.i).cloned();
71        if t.is_some() {
72            self.i += 1;
73        }
74        t
75    }
76    pub fn is_ident(&self, s: &str) -> bool {
77        matches!(self.peek(), Some(TokenTree::Ident(id)) if id.to_string() == s)
78    }
79    pub fn is_punct(&self, ch: char) -> bool {
80        matches!(self.peek(), Some(TokenTree::Punct(p)) if p.as_char() == ch)
81    }
82    pub fn eat_ident(&mut self, s: &str) -> bool {
83        if self.is_ident(s) {
84            self.i += 1;
85            true
86        } else {
87            false
88        }
89    }
90    pub fn eat_punct(&mut self, ch: char) -> bool {
91        if self.is_punct(ch) {
92            self.i += 1;
93            true
94        } else {
95            false
96        }
97    }
98    pub fn expect_ident(&mut self) -> Option<String> {
99        match self.next() {
100            Some(TokenTree::Ident(id)) => Some(id.to_string()),
101            _ => None,
102        }
103    }
104}
105
106/// Join tokens into a re-parseable string, preserving `Joint` spacing so
107/// that punctuation sequences (`::`, `'a`, `->`, `>>`) stay adjacent.
108pub(crate) fn join(toks: &[TokenTree]) -> String {
109    let mut s = String::new();
110    let mut no_space = false;
111    for t in toks {
112        if !s.is_empty() && !no_space {
113            s.push(' ');
114        }
115        no_space = false;
116        match t {
117            TokenTree::Punct(p) => {
118                s.push_str(&p.to_string());
119                no_space = p.spacing() == Spacing::Joint;
120            }
121            _ => s.push_str(&t.to_string()),
122        }
123    }
124    s
125}
126
127/// Split tokens at a top-level separator.
128///
129/// Angle brackets are tracked so that generic types such as
130/// `BTreeMap<String, i32>` stay on a single side of the split.
131pub(crate) fn split_top(toks: &[TokenTree], sep: char) -> Vec<Vec<TokenTree>> {
132    let mut out: Vec<Vec<TokenTree>> = Vec::new();
133    let mut cur: Vec<TokenTree> = Vec::new();
134    let mut angle: usize = 0;
135    for tt in toks {
136        match tt {
137            TokenTree::Group(_) => cur.push(tt.clone()),
138            TokenTree::Punct(p) if p.as_char() == '<' => {
139                angle += 1;
140                cur.push(tt.clone());
141            }
142            TokenTree::Punct(p) if p.as_char() == '>' => {
143                angle = angle.saturating_sub(1);
144                cur.push(tt.clone());
145            }
146            TokenTree::Punct(p) if angle == 0 && p.as_char() == sep => {
147                out.push(std::mem::take(&mut cur));
148            }
149            _ => cur.push(tt.clone()),
150        }
151    }
152    if !cur.is_empty() {
153        out.push(cur);
154    }
155    if out.is_empty() {
156        out.push(Vec::new());
157    }
158    out
159}
160
161/// Read a `<...>` group. proc_macro does not group angle brackets, so this
162/// scans for the matching `>` while ignoring `->` arrow tokens.
163pub(crate) fn read_angle(p: &mut P) -> Option<Vec<TokenTree>> {
164    if !p.eat_punct('<') {
165        return None;
166    }
167    let mut depth = 1usize;
168    let mut out = Vec::new();
169    while let Some(tt) = p.next() {
170        match &tt {
171            TokenTree::Punct(c) if c.as_char() == '<' => {
172                depth += 1;
173                out.push(tt);
174            }
175            TokenTree::Punct(c)
176                if c.as_char() == '-'
177                    && matches!(p.peek(), Some(TokenTree::Punct(n)) if n.as_char() == '>') =>
178            {
179                out.push(tt);
180                out.push(p.next().unwrap());
181            }
182            TokenTree::Punct(c) if c.as_char() == '>' => {
183                if depth == 1 {
184                    return Some(out);
185                }
186                depth -= 1;
187                out.push(tt);
188            }
189            _ => out.push(tt),
190        }
191    }
192    None
193}
194
195// ---------------------------------------------------------------------------
196// AST
197// ---------------------------------------------------------------------------
198
199#[derive(Clone, Copy, PartialEq, Eq)]
200pub(crate) enum ParamKind {
201    Lifetime,
202    Type,
203    Const,
204}
205
206#[derive(Clone)]
207pub(crate) struct GenericParam {
208    pub kind: ParamKind,
209    pub full: String,
210    pub name: String,
211}
212
213#[derive(Clone, Default)]
214pub(crate) struct Generics {
215    pub params: Vec<GenericParam>,
216    pub where_preds: Vec<String>,
217}
218
219#[derive(Clone)]
220pub(crate) struct Field {
221    pub ident: Option<String>,
222    pub ty: String,
223    pub attrs: Vec<attr::Meta>,
224}
225
226#[derive(Clone)]
227pub(crate) enum Fields {
228    Unit,
229    Named(Vec<Field>),
230    Unnamed(Vec<Field>),
231}
232
233impl Fields {
234    pub fn iter(&self) -> core::slice::Iter<'_, Field> {
235        match self {
236            Fields::Unit => [].iter(),
237            Fields::Named(fields) | Fields::Unnamed(fields) => fields.iter(),
238        }
239    }
240}
241
242#[derive(Clone)]
243pub(crate) struct Variant {
244    pub ident: String,
245    pub fields: Fields,
246    pub attrs: Vec<attr::Meta>,
247}
248
249#[derive(Clone)]
250pub(crate) enum Data {
251    Struct(Fields),
252    Enum(Vec<Variant>),
253}
254
255#[derive(Clone)]
256pub(crate) struct Input {
257    pub ident: String,
258    pub generics: Generics,
259    pub data: Data,
260    pub cattr: ContainerAttrs,
261}
262
263// ---------------------------------------------------------------------------
264// Attribute collection
265// ---------------------------------------------------------------------------
266
267/// Collect leading `#[...]` attribute groups.
268fn parse_attrs(p: &mut P) -> Vec<Vec<TokenTree>> {
269    let mut out = Vec::new();
270    while p.is_punct('#') {
271        p.next();
272        if let Some(TokenTree::Group(g)) = p.next() {
273            if g.delimiter() == Delimiter::Bracket {
274                out.push(g.stream().into_iter().collect());
275            }
276        }
277    }
278    out
279}
280
281/// Extract `njson` / `nextjson` metas from a set of attribute groups.
282fn collect_metas(groups: &[Vec<TokenTree>]) -> Vec<attr::Meta> {
283    let mut out = Vec::new();
284    for g in groups {
285        out.extend(attr::metas_from_attr(g));
286    }
287    out
288}
289
290// ---------------------------------------------------------------------------
291// Top-level parse
292// ---------------------------------------------------------------------------
293
294pub(crate) fn parse_input(input: TokenStream) -> Result<Input, String> {
295    let toks: Vec<TokenTree> = input.into_iter().collect();
296    let mut p = P { toks: &toks, i: 0 };
297
298    let attrs = parse_attrs(&mut p);
299    let cattr = ContainerAttrs::from_metas(&collect_metas(&attrs));
300    eat_visibility(&mut p);
301
302    let is_enum = if p.eat_ident("struct") {
303        false
304    } else if p.eat_ident("enum") {
305        true
306    } else {
307        return Err("nextjson: expected `struct` or `enum`".into());
308    };
309
310    let ident = p
311        .expect_ident()
312        .ok_or_else(|| "nextjson: expected type name".to_string())?;
313
314    let mut generics = Generics::default();
315    if let Some(inner) = read_angle(&mut p) {
316        generics = parse_generics(&inner);
317    }
318
319    let data = if !is_enum
320        && matches!(p.peek(), Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Parenthesis)
321    {
322        let Some(TokenTree::Group(body)) = p.next() else {
323            return Err("nextjson: expected a tuple struct body".into());
324        };
325        if p.eat_ident("where") {
326            parse_where_clause(&mut p, &mut generics, false);
327        }
328        // Tuple structs terminate with `;`; consume it so the trailing-input
329        // check below sees a fully parsed item.
330        p.eat_punct(';');
331        let inner: Vec<TokenTree> = body.stream().into_iter().collect();
332        Data::Struct(Fields::Unnamed(parse_unnamed_fields(&inner)))
333    } else {
334        if p.eat_ident("where") {
335            parse_where_clause(&mut p, &mut generics, true);
336        }
337        if !is_enum {
338            match p.next() {
339                Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Brace => {
340                    let inner: Vec<TokenTree> = g.stream().into_iter().collect();
341                    Data::Struct(Fields::Named(parse_named_fields(&inner)))
342                }
343                Some(TokenTree::Punct(pc)) if pc.as_char() == ';' => Data::Struct(Fields::Unit),
344                _ => return Err("nextjson: expected a struct body".into()),
345            }
346        } else {
347            match p.next() {
348                Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Brace => {
349                    let inner: Vec<TokenTree> = g.stream().into_iter().collect();
350                    Data::Enum(parse_variants(&inner))
351                }
352                _ => return Err("nextjson: expected an enum body".into()),
353            }
354        }
355    };
356
357    // The derive input must be a single item. Any tokens left over mean the
358    // hand-written parser did not understand part of the declaration; refuse
359    // to generate code from a silently mis-parsed subset (this is the
360    // forward-compatibility guard: if a future Rust release extends item
361    // syntax, the macro fails loudly instead of emitting wrong impls).
362    if p.i != toks.len() {
363        return Err(format!(
364            "nextjson: cannot parse trailing tokens: {}",
365            join(&toks[p.i..])
366        ));
367    }
368
369    Ok(Input {
370        ident,
371        generics,
372        data,
373        cattr,
374    })
375}
376
377fn parse_where_clause(p: &mut P<'_>, generics: &mut Generics, has_braced_body: bool) {
378    let mut tokens = Vec::new();
379    while let Some(token) = p.peek() {
380        let is_body = has_braced_body
381            && p.i + 1 == p.toks.len()
382            && matches!(token, TokenTree::Group(g) if g.delimiter() == Delimiter::Brace);
383        if is_body || matches!(token, TokenTree::Punct(punct) if punct.as_char() == ';') {
384            break;
385        }
386        if let Some(token) = p.next() {
387            tokens.push(token);
388        }
389    }
390    for piece in split_top(&tokens, ',') {
391        let predicate = join(&piece).trim().to_string();
392        if !predicate.is_empty() {
393            generics.where_preds.push(predicate);
394        }
395    }
396}
397
398fn parse_generics(inner: &[TokenTree]) -> Generics {
399    let mut g = Generics::default();
400    for item in split_top(inner, ',') {
401        if item.is_empty() {
402            continue;
403        }
404        let declaration = strip_generic_default(&item);
405        let mut p = P {
406            toks: &declaration,
407            i: 0,
408        };
409        if p.is_punct('\'') {
410            p.next();
411            let name = p.expect_ident().unwrap_or_default();
412            g.params.push(GenericParam {
413                kind: ParamKind::Lifetime,
414                full: join(&declaration),
415                name: format!("'{name}"),
416            });
417        } else if p.eat_ident("const") {
418            let name = p.expect_ident().unwrap_or_default();
419            g.params.push(GenericParam {
420                kind: ParamKind::Const,
421                full: join(&declaration),
422                name,
423            });
424        } else {
425            let name = p.expect_ident().unwrap_or_default();
426            g.params.push(GenericParam {
427                kind: ParamKind::Type,
428                full: join(&declaration),
429                name,
430            });
431        }
432    }
433    g
434}
435
436fn strip_generic_default(tokens: &[TokenTree]) -> Vec<TokenTree> {
437    let mut angle_depth = 0usize;
438    for (index, token) in tokens.iter().enumerate() {
439        match token {
440            TokenTree::Punct(punct) if punct.as_char() == '<' => angle_depth += 1,
441            TokenTree::Punct(punct) if punct.as_char() == '>' => {
442                angle_depth = angle_depth.saturating_sub(1);
443            }
444            TokenTree::Punct(punct) if punct.as_char() == '=' && angle_depth == 0 => {
445                return tokens[..index].to_vec();
446            }
447            _ => {}
448        }
449    }
450    tokens.to_vec()
451}
452
453fn parse_named_fields(inner: &[TokenTree]) -> Vec<Field> {
454    split_top(inner, ',')
455        .iter()
456        .filter(|s| !s.is_empty())
457        .map(|piece| parse_named_field(piece))
458        .collect()
459}
460
461/// Consume an optional `pub` visibility specifier (`pub`, `pub(crate)`,
462/// `pub(super)`, `pub(in path)`). In the proc-macro token stream the
463/// parenthesized part arrives as a `Group` with `Parenthesis` delimiter, not
464/// as a `Punct('(')`, so it must be matched as a group.
465pub(crate) fn eat_visibility(p: &mut P<'_>) {
466    if !p.eat_ident("pub") {
467        return;
468    }
469    if matches!(p.peek(), Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Parenthesis) {
470        p.next();
471    }
472}
473
474fn parse_named_field(piece: &[TokenTree]) -> Field {
475    let mut p = P { toks: piece, i: 0 };
476    let attrs = parse_attrs(&mut p);
477    eat_visibility(&mut p);
478    // Find the field separator ':' at top level, excluding '::'.
479    let mut colon = None;
480    let mut j = p.i;
481    while j < piece.len() {
482        match &piece[j] {
483            TokenTree::Punct(c) if c.as_char() == ':' => {
484                if matches!(piece.get(j + 1), Some(TokenTree::Punct(n)) if n.as_char() == ':') {
485                    j += 2;
486                    continue;
487                }
488                colon = Some(j);
489                break;
490            }
491            _ => j += 1,
492        }
493    }
494    let (ident, ty) = match colon {
495        Some(c) => (
496            Some(join(&piece[p.i..c]).trim().to_string()),
497            join(&piece[c + 1..]).trim().to_string(),
498        ),
499        None => (None, join(&piece[p.i..]).trim().to_string()),
500    };
501    let mut field_metas = collect_metas(&attrs);
502    // `PhantomData` fields are not part of the data model (serde semantics):
503    // skip them on serialize and default them on deserialize. Normalizing at
504    // parse time keeps every codegen path (ser / de / schema) consistent.
505    if crate::schema::is_phantom_data(&ty) {
506        field_metas.push(Meta::Flag("skip".to_string()));
507    }
508    Field {
509        ident,
510        ty,
511        attrs: field_metas,
512    }
513}
514
515fn parse_unnamed_fields(inner: &[TokenTree]) -> Vec<Field> {
516    split_top(inner, ',')
517        .iter()
518        .filter(|s| !s.is_empty())
519        .map(|piece| {
520            let mut p = P { toks: piece, i: 0 };
521            let attrs = parse_attrs(&mut p);
522            eat_visibility(&mut p);
523            Field {
524                ident: None,
525                ty: join(&piece[p.i..]).trim().to_string(),
526                attrs: collect_metas(&attrs),
527            }
528        })
529        .collect()
530}
531
532fn parse_variants(inner: &[TokenTree]) -> Vec<Variant> {
533    split_top(inner, ',')
534        .iter()
535        .filter(|s| !s.is_empty())
536        .map(|piece| {
537            let mut p = P { toks: piece, i: 0 };
538            let attrs = parse_attrs(&mut p);
539            let ident = p.expect_ident().unwrap_or_default();
540            let fields = match p.next() {
541                Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Brace => {
542                    let inner2: Vec<TokenTree> = g.stream().into_iter().collect();
543                    Fields::Named(parse_named_fields(&inner2))
544                }
545                Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Parenthesis => {
546                    let inner2: Vec<TokenTree> = g.stream().into_iter().collect();
547                    Fields::Unnamed(parse_unnamed_fields(&inner2))
548                }
549                _ => Fields::Unit,
550            };
551            Variant {
552                ident,
553                fields,
554                attrs: collect_metas(&attrs),
555            }
556        })
557        .collect()
558}
559
560// ---------------------------------------------------------------------------
561// Generic helpers for code generation
562// ---------------------------------------------------------------------------
563
564/// Build `(impl_generics, ty_generics, where_clause)` for the impl header.
565pub(crate) fn build_generics(
566    input: &Input,
567    cp: &str,
568    de: bool,
569    has_flatten: bool,
570    has_borrow: bool,
571) -> (String, String, String) {
572    let g = &input.generics;
573    let c = &input.cattr;
574    let name = input.ident.clone();
575    // `remote` implements the traits for an external type; conversion bounds
576    // that mention `Self` must refer to that type instead of the mirror. The
577    // remote path already carries its generic arguments, while a local type
578    // must be written with the mirror's type parameters applied.
579    let (self_ty, remote_typed) = match &c.remote {
580        Some(r) => (r.clone(), true),
581        None => (name.clone(), false),
582    };
583
584    let mut impl_params: Vec<String> = g.params.iter().map(|p| p.full.clone()).collect();
585    if de {
586        impl_params.insert(0, "'de".to_string());
587    }
588    let impl_generics = if impl_params.is_empty() {
589        String::new()
590    } else {
591        format!("<{}>", impl_params.join(", "))
592    };
593
594    let names: Vec<String> = g.params.iter().map(|p| p.name.clone()).collect();
595    let ty_generics = if names.is_empty() {
596        String::new()
597    } else {
598        format!("<{}>", names.join(", "))
599    };
600    // The fully-instantiated `Self` for conversion bounds: for local types the
601    // type parameters must be applied (`Dst<T>`), for remote types the path
602    // already names them (`external::Foreign<T>`).
603    let self_ty_inst = if remote_typed {
604        self_ty.clone()
605    } else {
606        format!("{self_ty}{ty_generics}")
607    };
608
609    // The type's own where-clause predicates are ALWAYS required to name the
610    // type, so they are kept unconditionally. The `bound` attribute only
611    // replaces the *auto-generated per-type-parameter* bounds; serde behaves
612    // the same way.
613    let mut preds: Vec<String> = g.where_preds.clone();
614
615    let directional = if de {
616        c.bound_de.as_ref()
617    } else {
618        c.bound_ser.as_ref()
619    };
620    let bound = directional.or(c.bound.as_ref());
621    let auto_bound = |p: &GenericParam| -> Option<String> {
622        if p.kind != ParamKind::Type {
623            return None;
624        }
625        if de && has_flatten {
626            Some(format!(
627                "{0}: for<'__n> {1}::NsonDeserialize<'__n>",
628                p.name, cp
629            ))
630        } else if de {
631            Some(format!("{}: {}::NsonDeserialize<'de>", p.name, cp))
632        } else {
633            Some(format!("{}: {}::NsonSerialize", p.name, cp))
634        }
635    };
636    if let Some(bound) = bound {
637        let cleaned = bound.trim().trim_matches('"');
638        if !cleaned.is_empty() {
639            for s in cleaned.split(',') {
640                let s = s.trim();
641                if !s.is_empty() {
642                    preds.push(s.to_string());
643                }
644            }
645        }
646    } else {
647        for p in g.params.iter() {
648            if let Some(b) = auto_bound(p) {
649                preds.push(b);
650            }
651        }
652    }
653
654    // Missing-field fallbacks that call `Default::default()` must be able to
655    // name the type parameters they fall back on, so every type parameter
656    // receives a `Default` bound when any fallback can fire for a generic
657    // field. This mirrors serde, which adds `T: Default` for exactly the same
658    // attribute combinations.
659    if de && de_uses_type_param_default(input) {
660        for p in g.params.iter() {
661            if p.kind == ParamKind::Type {
662                preds.push(format!("{}: ::core::default::Default", p.name));
663            }
664        }
665    }
666
667    // Conversion attributes add their own bounds.
668    if de {
669        if let Some(from) = &c.from {
670            preds.push(format!(
671                "{from}: {cp}::NsonDeserialize<'de> + ::core::convert::Into<{self_ty_inst}>"
672            ));
673        }
674        if let Some(from) = &c.try_from {
675            preds.push(format!(
676                "{from}: {cp}::NsonDeserialize<'de> + ::core::convert::TryInto<{self_ty_inst}>"
677            ));
678            preds.push(format!(
679                "<{from} as ::core::convert::TryInto<{self_ty_inst}>>::Error: ::core::fmt::Display"
680            ));
681        }
682    } else {
683        if let Some(into) = &c.into {
684            preds.push(format!("{self_ty_inst}: ::core::clone::Clone"));
685            preds.push(format!("{self_ty_inst}: ::core::convert::Into<{into}>"));
686            preds.push(format!("{into}: {cp}::NsonSerialize"));
687            preds.push(format!("{into}: {cp}::NsonSchema"));
688        }
689    }
690
691    if de && has_borrow {
692        for p in g.params.iter() {
693            if p.kind == ParamKind::Lifetime {
694                preds.push(format!("'de: {}", p.name));
695            }
696        }
697    }
698
699    let where_clause = if preds.is_empty() {
700        String::new()
701    } else {
702        format!(" where {}", preds.join(", "))
703    };
704
705    (impl_generics, ty_generics, where_clause)
706}
707
708/// Validate attribute combinations that serde rejects at compile time.
709///
710/// Returns an error message when the combination is invalid. Called by both
711/// derive entry points so the rejection is identical regardless of which
712/// macro is expanded first.
713fn validate_input(input: &Input) -> Option<String> {
714    if let Some(name) = input.cattr.unknown.first() {
715        return Some(format!(
716            "nextjson: unsupported container attribute `{name}`; refusing to ignore wire semantics"
717        ));
718    }
719    fn unknown_field_attribute(fields: &Fields) -> Option<String> {
720        for field in fields.iter() {
721            if let Some(name) = attr::field_attrs(&field.attrs).unknown.into_iter().next() {
722                return Some(format!(
723                    "nextjson: unsupported field attribute `{name}`; refusing to ignore wire semantics"
724                ));
725            }
726        }
727        None
728    }
729    match &input.data {
730        Data::Struct(fields) => {
731            if let Some(message) = unknown_field_attribute(fields) {
732                return Some(message);
733            }
734        }
735        Data::Enum(variants) => {
736            for variant in variants {
737                if let Some(name) = attr::variant_attrs(&variant.attrs)
738                    .unknown
739                    .into_iter()
740                    .next()
741                {
742                    return Some(format!(
743                        "nextjson: unsupported variant attribute `{name}`; refusing to ignore wire semantics"
744                    ));
745                }
746                if let Some(message) = unknown_field_attribute(&variant.fields) {
747                    return Some(message);
748                }
749            }
750        }
751    }
752    // `transparent` is only meaningful on single-field structs.
753    if input.cattr.transparent {
754        match &input.data {
755            Data::Enum(_) => {
756                return Some("nextjson: `transparent` is not supported on enums".to_string());
757            }
758            Data::Struct(Fields::Named(f)) if f.len() != 1 => {
759                return Some("nextjson: `transparent` requires exactly one field".to_string());
760            }
761            Data::Struct(Fields::Unnamed(f)) if f.len() != 1 => {
762                return Some("nextjson: `transparent` requires exactly one field".to_string());
763            }
764            _ => {}
765        }
766    }
767    // `flatten` splices a nested map into the parent object, which is
768    // impossible for positional (unnamed) shapes.
769    if type_has_flag_on(input, |f, fa| fa.flatten && f.ident.is_none()) {
770        return Some(
771            "nextjson: `flatten` is not allowed on tuple structs or tuple variants".to_string(),
772        );
773    }
774    // `flatten` + `skip_serializing_if` conflicts: the decision to skip would
775    // depend on the flattened value, which serde rejects up front.
776    if type_has_flag_on(input, |_f, fa| {
777        fa.flatten && fa.skip_serializing_if.is_some()
778    }) {
779        return Some(
780            "nextjson: `flatten` cannot be combined with `skip_serializing_if`".to_string(),
781        );
782    }
783    None
784}
785
786/// Like [`type_has_flag`] but the predicate also sees the field (needed to
787/// tell named from unnamed fields).
788fn type_has_flag_on<F: Fn(&Field, &FieldAttrs) -> bool>(input: &Input, f: F) -> bool {
789    let check = |fld: &Field| f(fld, &attr::field_attrs(&fld.attrs));
790    match &input.data {
791        Data::Struct(fields) => fields.iter().any(check),
792        Data::Enum(variants) => variants.iter().any(|v| v.fields.iter().any(check)),
793    }
794}
795
796/// Emit the `NsonSchema` + `NsonSerialize` impls.
797pub(crate) fn generate_impls(input: &Input) -> TokenStream {
798    if let Some(msg) = validate_input(input) {
799        return err(&msg);
800    }
801    let cp = input.cattr.crate_path.clone();
802    let name = input.ident.clone();
803    // `remote` implements the traits for the external type itself. The remote
804    // path already names its generic arguments, so the mirror's type-generics
805    // are not appended a second time (`Foreign<T><T>` would not parse).
806    let (target, use_tg) = match &input.cattr.remote {
807        Some(r) => (r.clone(), false),
808        None => (name.clone(), true),
809    };
810    let (ig, tg, wc) = build_generics(input, &cp, false, false, false);
811    let tg_part = if use_tg { tg.as_str() } else { "" };
812    let body = if let Some(into) = &input.cattr.into {
813        // `into = "T"`: serialize by converting `self` to `T` first.
814        format!(
815            "let __v: {into} = ::core::convert::Into::into(self.clone());\n\
816             <{into} as {cp}::NsonSerialize>::nextencode(&__v, __e)"
817        )
818    } else {
819        match &input.data {
820            Data::Struct(f) => ser::serialize_struct(&name, f, input, &cp),
821            Data::Enum(v) => ser::serialize_enum(&name, v, input, &cp),
822        }
823    };
824    let out = format!(
825        "#[automatically_derived]\n\
826         impl {ig} {cp}::NsonSchema for {target}{tg_part}{wc} {{\n\
827         \x20   const SCHEMA: {cp}::TypeSchema = {schema_expr};\n\
828         }}\n\
829         #[automatically_derived]\n\
830         impl {ig} {cp}::NsonSerialize for {target}{tg_part}{wc} {{\n\
831         \x20   fn nextencode<__E: {cp}::FormatEncoder>(&self, __e: &mut __E) -> ::core::result::Result<(), __E::Error> {{\n\
832         {body}\n\
833         \x20   }}\n\
834         }}",
835        schema_expr = schema::schema_expr(input, &cp)
836    );
837    ts(&out)
838}
839
840/// Emit the `NsonDeserialize` impl.
841pub(crate) fn generate_de_impl(input: &Input) -> TokenStream {
842    if let Some(msg) = validate_input(input) {
843        return err(&msg);
844    }
845    let cp = input.cattr.crate_path.clone();
846    let name = input.ident.clone();
847    // Container-level `default` supplies missing-field values from a `Self`
848    // instance, which only makes sense for structs (serde rejects it on
849    // enums as well).
850    if matches!(&input.data, Data::Enum(_)) && input.cattr.has_default() {
851        return err("nextjson: `default` is not supported on enums");
852    }
853    let has_flatten = type_has_flag(input, |fa| fa.flatten);
854    let has_borrow = type_has_flag(input, |fa| fa.borrow);
855    if has_flatten && type_has_with(input) {
856        return err("nextjson: `flatten` cannot be combined with `with` / `deserialize_with`");
857    }
858    if has_flatten && input.cattr.deny_unknown_fields {
859        // flatten consumes every remaining key, so unknown-field rejection is
860        // silently impossible; serde rejects this combination at compile time.
861        return err("nextjson: `deny_unknown_fields` cannot be combined with `flatten`");
862    }
863    let (target, use_tg) = match &input.cattr.remote {
864        Some(r) => (r.clone(), false),
865        None => (name.clone(), true),
866    };
867    let (ig, tg, wc) = build_generics(input, &cp, true, has_flatten, has_borrow);
868    let tg_part = if use_tg { tg.as_str() } else { "" };
869    // `expecting = "..."` overrides the default `type_name`-based description
870    // used in type-mismatch and length-mismatch error messages.
871    let expecting = match &input.cattr.expecting {
872        Some(e) => format!("\n     fn expecting() -> &'static str {{ {:?} }}\n", e),
873        None => String::new(),
874    };
875    let body = if let Some(from) = &input.cattr.from {
876        // `from = "T"`: deserialize a `T` then convert into `Self`.
877        format!(
878            "let __v: {from} = <{from} as {cp}::NsonDeserialize<'de>>::nextdecode(__d)?;\n\
879             __out.write(::core::convert::Into::into(__v));\n\
880             ::core::result::Result::Ok(())"
881        )
882    } else if let Some(from) = &input.cattr.try_from {
883        // `try_from = "T"`: deserialize a `T` then fallibly convert.
884        format!(
885            "let __v: {from} = <{from} as {cp}::NsonDeserialize<'de>>::nextdecode(__d)?;\n\
886             let __c: Self = ::core::convert::TryInto::try_into(__v).map_err(|__e| {{\n\
887             \x20   {cp}::FormatError::custom({cp}::__private::ToString::to_string(&__e))\n\
888             }})?;\n\
889             __out.write(__c);\n\
890             ::core::result::Result::Ok(())"
891        )
892    } else {
893        match &input.data {
894            Data::Struct(f) => de::deserialize_struct(&name, f, input, &cp, has_flatten),
895            Data::Enum(v) => de::deserialize_enum(&name, v, input, &cp, has_flatten),
896        }
897    };
898    let out = format!(
899        "#[automatically_derived]\n\
900         impl {ig} {cp}::NsonDeserialize<'de> for {target}{tg_part}{wc} {{\n\
901         {expecting}\
902         \x20   fn nextdecode_into<__D: {cp}::FormatDecoder<'de>>(\n\
903         \x20       __d: &mut __D,\n\
904         \x20       __out: &mut {cp}::DecodeSlot<Self>,\n\
905         \x20   ) -> ::core::result::Result<(), __D::Error> {{\n\
906         \x20       __d.set_expecting(Self::expecting());\n\
907         {body}\n\
908         \x20   }}\n\
909         }}"
910    );
911    ts(&out)
912}
913
914fn type_has_flag<F: Fn(&FieldAttrs) -> bool>(input: &Input, f: F) -> bool {
915    match &input.data {
916        Data::Struct(fields) => fields.iter().any(|fld| f(&attr::field_attrs(&fld.attrs))),
917        Data::Enum(variants) => variants
918            .iter()
919            .any(|v| v.fields.iter().any(|fld| f(&attr::field_attrs(&fld.attrs)))),
920    }
921}
922
923/// Whether the generated deserializer can fall back to `Default::default()`
924/// for a field whose type is a generic parameter.
925///
926/// True when the container has a default, when any field has a bare
927/// `default` attribute, or when any field is `skip_deserializing` without an
928/// explicit `default = "path"` (which would supply its own value). Field-level
929/// `default = "path"` does not need the bound. `PhantomData` fields are
930/// excluded: `PhantomData<T>: Default` holds for every `T` with no bound.
931fn de_uses_type_param_default(input: &Input) -> bool {
932    if input.cattr.has_default() {
933        return true;
934    }
935    let mut found = false;
936    let mut scan = |f: &Field| {
937        if crate::schema::is_phantom_data(&f.ty) {
938            return;
939        }
940        let fa = attr::field_attrs(&f.attrs);
941        let bare_default = fa.default == Some(String::new());
942        let skip_without_path =
943            fa.skip_deserializing && !matches!(&fa.default, Some(d) if !d.is_empty());
944        if bare_default || skip_without_path {
945            found = true;
946        }
947    };
948    match &input.data {
949        Data::Struct(fields) => {
950            for f in fields.iter() {
951                scan(f);
952            }
953        }
954        Data::Enum(variants) => {
955            for v in variants {
956                for f in v.fields.iter() {
957                    scan(f);
958                }
959            }
960        }
961    }
962    found
963}
964
965fn type_has_with(input: &Input) -> bool {
966    type_has_flag(input, |fa| {
967        fa.with.is_some() || fa.deserialize_with.is_some()
968    })
969}
970
971// ---------------------------------------------------------------------------
972// Entry points
973// ---------------------------------------------------------------------------
974
975#[proc_macro_derive(NsonSerialize, attributes(njson, nextjson, serde))]
976/// Derive NextJson's native serialization contract and compile-time schema.
977///
978/// Configuration is accepted through `#[njson(...)]` (and, for migration
979/// convenience, `#[serde(...)]`). The generated implementation writes
980/// directly through `NsonSerialize::nextencode` and exposes
981/// `NsonSchema::SCHEMA` without depending on another macro framework.
982pub fn derive_serialize(input: TokenStream) -> TokenStream {
983    match parse_input(input) {
984        Ok(ast) => generate_impls(&ast),
985        Err(e) => err(&e),
986    }
987}
988
989#[proc_macro_derive(NsonDeserialize, attributes(njson, nextjson, serde))]
990/// Derive NextJson's native decoding contract.
991///
992/// Configuration is accepted through `#[njson(...)]` (and, for migration
993/// convenience, `#[serde(...)]`). The generated implementation decodes
994/// through checked `DecodeSlot` state and uses normal Rust drop semantics for
995/// partially initialized fields.
996pub fn derive_deserialize(input: TokenStream) -> TokenStream {
997    match parse_input(input) {
998        Ok(ast) => generate_de_impl(&ast),
999        Err(e) => err(&e),
1000    }
1001}
1002
1003#[cfg(test)]
1004mod tests {
1005    use super::*;
1006
1007    // The `proc_macro` API cannot be used in unit tests (it panics outside a
1008    // macro expansion), so these tests build the small AST by hand and cover
1009    // the pure logic: attribute parsing, validation, and impl-generics
1010    // construction. Parser token-level behavior is exercised by the workspace
1011    // integration tests, which compile real derives.
1012
1013    fn meta_flag(name: &str) -> Meta {
1014        Meta::Flag(name.to_string())
1015    }
1016    fn meta_named(name: &str, value: &str) -> Meta {
1017        Meta::Named(name.to_string(), value.to_string())
1018    }
1019    fn named_field(ident: &str, ty: &str, metas: Vec<Meta>) -> Field {
1020        Field {
1021            ident: Some(ident.to_string()),
1022            ty: ty.to_string(),
1023            attrs: metas,
1024        }
1025    }
1026    fn unnamed_field(ty: &str, metas: Vec<Meta>) -> Field {
1027        Field {
1028            ident: None,
1029            ty: ty.to_string(),
1030            attrs: metas,
1031        }
1032    }
1033    fn cattr(metas: &[Meta]) -> ContainerAttrs {
1034        ContainerAttrs::from_metas(metas)
1035    }
1036    fn struct_named(cattr: ContainerAttrs, fields: Vec<Field>) -> Input {
1037        Input {
1038            ident: "S".into(),
1039            generics: Generics::default(),
1040            data: Data::Struct(Fields::Named(fields)),
1041            cattr,
1042        }
1043    }
1044    fn generic_input(
1045        ident: &str,
1046        params: Vec<GenericParam>,
1047        where_preds: Vec<String>,
1048        data: Data,
1049        cattr: ContainerAttrs,
1050    ) -> Input {
1051        Input {
1052            ident: ident.to_string(),
1053            generics: Generics {
1054                params,
1055                where_preds,
1056            },
1057            data,
1058            cattr,
1059        }
1060    }
1061
1062    // -- validate_input ------------------------------------------------------
1063
1064    #[test]
1065    fn validate_rejects_transparent_enum() {
1066        let input = Input {
1067            ident: "E".into(),
1068            generics: Generics::default(),
1069            data: Data::Enum(vec![Variant {
1070                ident: "A".into(),
1071                fields: Fields::Unit,
1072                attrs: vec![],
1073            }]),
1074            cattr: cattr(&[meta_flag("transparent")]),
1075        };
1076        let msg = validate_input(&input).expect("must reject transparent enum");
1077        assert!(msg.contains("transparent"));
1078    }
1079
1080    #[test]
1081    fn validate_rejects_transparent_multi_field() {
1082        let input = struct_named(
1083            cattr(&[meta_flag("transparent")]),
1084            vec![
1085                named_field("a", "i32", vec![]),
1086                named_field("b", "i32", vec![]),
1087            ],
1088        );
1089        assert!(validate_input(&input).is_some());
1090    }
1091
1092    #[test]
1093    fn validate_rejects_flatten_on_tuple() {
1094        let input = Input {
1095            ident: "T".into(),
1096            generics: Generics::default(),
1097            data: Data::Struct(Fields::Unnamed(vec![unnamed_field(
1098                "std::collections::BTreeMap<String, i32>",
1099                vec![meta_flag("flatten")],
1100            )])),
1101            cattr: cattr(&[]),
1102        };
1103        let msg = validate_input(&input).expect("must reject flatten on tuple field");
1104        assert!(msg.contains("flatten"), "unexpected error: {msg}");
1105    }
1106
1107    #[test]
1108    fn validate_rejects_flatten_with_skip_if() {
1109        let input = struct_named(
1110            cattr(&[]),
1111            vec![named_field(
1112                "m",
1113                "std::collections::BTreeMap<String, i32>",
1114                vec![
1115                    meta_flag("flatten"),
1116                    meta_named("skip_serializing_if", "Option::is_none"),
1117                ],
1118            )],
1119        );
1120        let msg = validate_input(&input).expect("must reject flatten + skip_serializing_if");
1121        assert!(msg.contains("flatten"), "unexpected error: {msg}");
1122    }
1123
1124    #[test]
1125    fn validate_rejects_unknown_wire_attributes() {
1126        let input = struct_named(
1127            cattr(&[]),
1128            vec![named_field(
1129                "a",
1130                "i32",
1131                vec![meta_flag("not_actually_supported")],
1132            )],
1133        );
1134        let message = validate_input(&input).expect("unknown attribute must be rejected");
1135        assert!(message.contains("not_actually_supported"));
1136        assert!(message.contains("refusing to ignore"));
1137    }
1138
1139    #[test]
1140    fn validate_accepts_valid_combinations() {
1141        let ok = struct_named(
1142            cattr(&[]),
1143            vec![
1144                named_field("a", "i32", vec![]),
1145                named_field(
1146                    "m",
1147                    "std::collections::BTreeMap<String, i32>",
1148                    vec![meta_flag("flatten")],
1149                ),
1150            ],
1151        );
1152        assert!(validate_input(&ok).is_none());
1153
1154        let w = Input {
1155            ident: "W".into(),
1156            generics: Generics::default(),
1157            data: Data::Struct(Fields::Unnamed(vec![unnamed_field("i32", vec![])])),
1158            cattr: cattr(&[meta_flag("transparent")]),
1159        };
1160        assert!(validate_input(&w).is_none());
1161    }
1162
1163    // -- build_generics ------------------------------------------------------
1164
1165    #[test]
1166    fn build_generics_keeps_where_clause_with_bound() {
1167        // `bound` must replace only the auto bounds; the struct's own where
1168        // clause must always survive.
1169        let input = generic_input(
1170            "S",
1171            vec![GenericParam {
1172                kind: ParamKind::Type,
1173                full: "T".into(),
1174                name: "T".into(),
1175            }],
1176            vec!["T: core::fmt::Debug".into()],
1177            Data::Struct(Fields::Named(vec![named_field("v", "T", vec![])])),
1178            cattr(&[meta_named("bound", "\"T: Clone\"")]),
1179        );
1180        let (_, _, wc) = build_generics(&input, "::nextjson", false, false, false);
1181        assert!(wc.contains("Clone"), "missing user bound: {wc}");
1182        assert!(wc.contains("Debug"), "missing struct where clause: {wc}");
1183    }
1184
1185    #[test]
1186    fn build_generics_instantiates_conversion_self_type() {
1187        // Conversion bounds must name `Dst<T>`, never bare `Dst`.
1188        let input = generic_input(
1189            "Dst",
1190            vec![GenericParam {
1191                kind: ParamKind::Type,
1192                full: "T".into(),
1193                name: "T".into(),
1194            }],
1195            vec![],
1196            Data::Struct(Fields::Named(vec![named_field("x", "T", vec![])])),
1197            cattr(&[meta_named("from", "\"Src<T>\"")]),
1198        );
1199        let (_, _, wc) = build_generics(&input, "::nextjson", true, false, false);
1200        assert!(
1201            wc.contains("Into<Dst<T>>"),
1202            "missing instantiated self: {wc}"
1203        );
1204        assert!(!wc.contains("Into<Dst>"), "bare self type: {wc}");
1205    }
1206
1207    #[test]
1208    fn build_generics_adds_default_bound_for_generic_default() {
1209        // Container `default` on a generic struct must add `T: Default`.
1210        let input = generic_input(
1211            "S",
1212            vec![GenericParam {
1213                kind: ParamKind::Type,
1214                full: "T".into(),
1215                name: "T".into(),
1216            }],
1217            vec![],
1218            Data::Struct(Fields::Named(vec![named_field("v", "T", vec![])])),
1219            cattr(&[meta_flag("default")]),
1220        );
1221        let (_, _, wc) = build_generics(&input, "::nextjson", true, false, false);
1222        assert!(
1223            wc.contains("T: ::core::default::Default"),
1224            "missing Default bound: {wc}"
1225        );
1226    }
1227
1228    #[test]
1229    fn build_generics_remote_never_appends_generics_twice() {
1230        // The impl target for `remote` carries its own generic arguments.
1231        let input = generic_input(
1232            "Mirror",
1233            vec![GenericParam {
1234                kind: ParamKind::Type,
1235                full: "T".into(),
1236                name: "T".into(),
1237            }],
1238            vec![],
1239            Data::Struct(Fields::Named(vec![named_field("x", "T", vec![])])),
1240            cattr(&[meta_named("remote", "external::Foreign<T>")]),
1241        );
1242        let (_, _, wc) = build_generics(&input, "::nextjson", false, false, false);
1243        assert!(
1244            wc.contains("T: ::nextjson::NsonSerialize"),
1245            "missing auto bound: {wc}"
1246        );
1247    }
1248
1249    // -- default-bound detection ---------------------------------------------
1250
1251    #[test]
1252    fn default_bound_not_needed_for_phantom_data() {
1253        // A `PhantomData` field must not force `T: Default`.
1254        let input = struct_named(
1255            cattr(&[]),
1256            vec![
1257                named_field("_m", "core::marker::PhantomData<T>", vec![]),
1258                named_field("n", "i32", vec![]),
1259            ],
1260        );
1261        assert!(!de_uses_type_param_default(&input));
1262
1263        // With a container default the bound is required again.
1264        let input2 = struct_named(
1265            cattr(&[meta_flag("default")]),
1266            vec![named_field("v", "T", vec![])],
1267        );
1268        assert!(de_uses_type_param_default(&input2));
1269    }
1270
1271    // -- PhantomData detection ----------------------------------------------
1272
1273    #[test]
1274    fn phantom_detection_spellings() {
1275        assert!(crate::schema::is_phantom_data("PhantomData<T>"));
1276        assert!(crate::schema::is_phantom_data(
1277            "core::marker::PhantomData<T>"
1278        ));
1279        assert!(crate::schema::is_phantom_data(
1280            "::core::marker::PhantomData<T>"
1281        ));
1282        assert!(crate::schema::is_phantom_data(
1283            "std::marker::PhantomData<T>"
1284        ));
1285        assert!(!crate::schema::is_phantom_data("Vec<T>"));
1286        assert!(!crate::schema::is_phantom_data("Option<PhantomData<T>>"));
1287        assert!(!crate::schema::is_phantom_data("Phantom"));
1288    }
1289}