1#![deny(unsafe_code)]
9#![deny(missing_docs)]
10#![doc(html_root_url = "https://docs.rs/nextjson-derive")]
11
12extern crate proc_macro;
13
14use proc_macro::{Delimiter, Ident, Spacing, TokenStream, TokenTree};
15use std::str::FromStr;
16
17mod attr;
18mod case;
19mod de;
20mod schema;
21mod ser;
22
23pub(crate) use attr::{ContainerAttrs, FieldAttrs, VariantAttrs};
24
25pub(crate) fn ts(s: &str) -> TokenStream {
27 TokenStream::from_str(s)
28 .unwrap_or_else(|e| panic!("nextjson-derive: invalid generated tokens: {e:?}"))
29}
30
31pub(crate) fn err(msg: &str) -> TokenStream {
33 ts(&format!("::core::compile_error!({:?})", msg))
34}
35
36pub(crate) fn err_str(msg: &str) -> String {
38 msg.to_string()
39}
40
41pub(crate) struct P<'a> {
43 pub toks: &'a [TokenTree],
44 pub i: usize,
45}
46
47impl<'a> P<'a> {
48 pub fn peek(&self) -> Option<&TokenTree> {
49 self.toks.get(self.i)
50 }
51 pub fn next(&mut self) -> Option<TokenTree> {
52 let t = self.toks.get(self.i).cloned();
53 if t.is_some() {
54 self.i += 1;
55 }
56 t
57 }
58 pub fn is_ident(&self, s: &str) -> bool {
59 matches!(self.peek(), Some(TokenTree::Ident(id)) if id.to_string() == s)
60 }
61 pub fn is_punct(&self, ch: char) -> bool {
62 matches!(self.peek(), Some(TokenTree::Punct(p)) if p.as_char() == ch)
63 }
64 pub fn eat_ident(&mut self, s: &str) -> bool {
65 if self.is_ident(s) {
66 self.i += 1;
67 true
68 } else {
69 false
70 }
71 }
72 pub fn eat_punct(&mut self, ch: char) -> bool {
73 if self.is_punct(ch) {
74 self.i += 1;
75 true
76 } else {
77 false
78 }
79 }
80 pub fn expect_ident(&mut self) -> Option<String> {
81 match self.next() {
82 Some(TokenTree::Ident(id)) => Some(id.to_string()),
83 _ => None,
84 }
85 }
86}
87
88pub(crate) fn join(toks: &[TokenTree]) -> String {
91 let mut s = String::new();
92 let mut no_space = false;
93 for t in toks {
94 if !s.is_empty() && !no_space {
95 s.push(' ');
96 }
97 no_space = false;
98 match t {
99 TokenTree::Punct(p) => {
100 s.push_str(&p.to_string());
101 no_space = p.spacing() == Spacing::Joint;
102 }
103 _ => s.push_str(&t.to_string()),
104 }
105 }
106 s
107}
108
109pub(crate) fn split_top(toks: &[TokenTree], sep: char) -> Vec<Vec<TokenTree>> {
114 let mut out: Vec<Vec<TokenTree>> = Vec::new();
115 let mut cur: Vec<TokenTree> = Vec::new();
116 let mut angle: usize = 0;
117 for tt in toks {
118 match tt {
119 TokenTree::Group(_) => cur.push(tt.clone()),
120 TokenTree::Punct(p) if p.as_char() == '<' => {
121 angle += 1;
122 cur.push(tt.clone());
123 }
124 TokenTree::Punct(p) if p.as_char() == '>' => {
125 angle = angle.saturating_sub(1);
126 cur.push(tt.clone());
127 }
128 TokenTree::Punct(p) if angle == 0 && p.as_char() == sep => {
129 out.push(std::mem::take(&mut cur));
130 }
131 _ => cur.push(tt.clone()),
132 }
133 }
134 if !cur.is_empty() {
135 out.push(cur);
136 }
137 if out.is_empty() {
138 out.push(Vec::new());
139 }
140 out
141}
142
143pub(crate) fn read_angle(p: &mut P) -> Option<Vec<TokenTree>> {
146 if !p.eat_punct('<') {
147 return None;
148 }
149 let mut depth = 1usize;
150 let mut out = Vec::new();
151 while let Some(tt) = p.next() {
152 match &tt {
153 TokenTree::Punct(c) if c.as_char() == '<' => {
154 depth += 1;
155 out.push(tt);
156 }
157 TokenTree::Punct(c)
158 if c.as_char() == '-'
159 && matches!(p.peek(), Some(TokenTree::Punct(n)) if n.as_char() == '>') =>
160 {
161 out.push(tt);
162 out.push(p.next().unwrap());
163 }
164 TokenTree::Punct(c) if c.as_char() == '>' => {
165 if depth == 1 {
166 return Some(out);
167 }
168 depth -= 1;
169 out.push(tt);
170 }
171 _ => out.push(tt),
172 }
173 }
174 None
175}
176
177#[derive(Clone, Copy, PartialEq, Eq)]
182pub(crate) enum ParamKind {
183 Lifetime,
184 Type,
185 Const,
186}
187
188#[derive(Clone)]
189pub(crate) struct GenericParam {
190 pub kind: ParamKind,
191 pub full: String,
192 pub name: String,
193}
194
195#[derive(Clone, Default)]
196pub(crate) struct Generics {
197 pub params: Vec<GenericParam>,
198 pub where_preds: Vec<String>,
199}
200
201#[derive(Clone)]
202pub(crate) struct Field {
203 pub ident: Option<String>,
204 pub ty: String,
205 pub attrs: Vec<attr::Meta>,
206}
207
208#[derive(Clone)]
209pub(crate) enum Fields {
210 Unit,
211 Named(Vec<Field>),
212 Unnamed(Vec<Field>),
213}
214
215impl Fields {
216 pub fn iter(&self) -> core::slice::Iter<'_, Field> {
217 match self {
218 Fields::Unit => [].iter(),
219 Fields::Named(fields) | Fields::Unnamed(fields) => fields.iter(),
220 }
221 }
222}
223
224#[derive(Clone)]
225pub(crate) struct Variant {
226 pub ident: String,
227 pub fields: Fields,
228 pub attrs: Vec<attr::Meta>,
229}
230
231#[derive(Clone)]
232pub(crate) enum Data {
233 Struct(Fields),
234 Enum(Vec<Variant>),
235}
236
237#[derive(Clone)]
238pub(crate) struct Input {
239 pub ident: String,
240 pub generics: Generics,
241 pub data: Data,
242 pub cattr: ContainerAttrs,
243}
244
245fn parse_attrs(p: &mut P) -> Vec<Vec<TokenTree>> {
251 let mut out = Vec::new();
252 while p.is_punct('#') {
253 p.next();
254 if let Some(TokenTree::Group(g)) = p.next() {
255 if g.delimiter() == Delimiter::Bracket {
256 out.push(g.stream().into_iter().collect());
257 }
258 }
259 }
260 out
261}
262
263fn collect_metas(groups: &[Vec<TokenTree>]) -> Vec<attr::Meta> {
265 let mut out = Vec::new();
266 for g in groups {
267 out.extend(attr::metas_from_attr(g));
268 }
269 out
270}
271
272pub(crate) fn parse_input(input: TokenStream) -> Result<Input, String> {
277 let toks: Vec<TokenTree> = input.into_iter().collect();
278 let mut p = P { toks: &toks, i: 0 };
279
280 let attrs = parse_attrs(&mut p);
281 let cattr = ContainerAttrs::from_metas(&collect_metas(&attrs));
282 eat_visibility(&mut p);
283
284 let is_enum = if p.eat_ident("struct") {
285 false
286 } else if p.eat_ident("enum") {
287 true
288 } else {
289 return Err("nextjson: expected `struct` or `enum`".into());
290 };
291
292 let ident = p
293 .expect_ident()
294 .ok_or_else(|| "nextjson: expected type name".to_string())?;
295
296 let mut generics = Generics::default();
297 if let Some(inner) = read_angle(&mut p) {
298 generics = parse_generics(&inner);
299 }
300
301 let data = if !is_enum
302 && matches!(p.peek(), Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Parenthesis)
303 {
304 let Some(TokenTree::Group(body)) = p.next() else {
305 return Err("nextjson: expected a tuple struct body".into());
306 };
307 if p.eat_ident("where") {
308 parse_where_clause(&mut p, &mut generics, false);
309 }
310 let inner: Vec<TokenTree> = body.stream().into_iter().collect();
311 Data::Struct(Fields::Unnamed(parse_unnamed_fields(&inner)))
312 } else {
313 if p.eat_ident("where") {
314 parse_where_clause(&mut p, &mut generics, true);
315 }
316 if !is_enum {
317 match p.next() {
318 Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Brace => {
319 let inner: Vec<TokenTree> = g.stream().into_iter().collect();
320 Data::Struct(Fields::Named(parse_named_fields(&inner)))
321 }
322 Some(TokenTree::Punct(pc)) if pc.as_char() == ';' => Data::Struct(Fields::Unit),
323 _ => return Err("nextjson: expected a struct body".into()),
324 }
325 } else {
326 match p.next() {
327 Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Brace => {
328 let inner: Vec<TokenTree> = g.stream().into_iter().collect();
329 Data::Enum(parse_variants(&inner))
330 }
331 _ => return Err("nextjson: expected an enum body".into()),
332 }
333 }
334 };
335
336 Ok(Input {
337 ident,
338 generics,
339 data,
340 cattr,
341 })
342}
343
344fn parse_where_clause(p: &mut P<'_>, generics: &mut Generics, has_braced_body: bool) {
345 let mut tokens = Vec::new();
346 while let Some(token) = p.peek() {
347 let is_body = has_braced_body
348 && p.i + 1 == p.toks.len()
349 && matches!(token, TokenTree::Group(g) if g.delimiter() == Delimiter::Brace);
350 if is_body || matches!(token, TokenTree::Punct(punct) if punct.as_char() == ';') {
351 break;
352 }
353 if let Some(token) = p.next() {
354 tokens.push(token);
355 }
356 }
357 for piece in split_top(&tokens, ',') {
358 let predicate = join(&piece).trim().to_string();
359 if !predicate.is_empty() {
360 generics.where_preds.push(predicate);
361 }
362 }
363}
364
365fn parse_generics(inner: &[TokenTree]) -> Generics {
366 let mut g = Generics::default();
367 for item in split_top(inner, ',') {
368 if item.is_empty() {
369 continue;
370 }
371 let declaration = strip_generic_default(&item);
372 let mut p = P {
373 toks: &declaration,
374 i: 0,
375 };
376 if p.is_punct('\'') {
377 p.next();
378 let name = p.expect_ident().unwrap_or_default();
379 g.params.push(GenericParam {
380 kind: ParamKind::Lifetime,
381 full: join(&declaration),
382 name: format!("'{name}"),
383 });
384 } else if p.eat_ident("const") {
385 let name = p.expect_ident().unwrap_or_default();
386 g.params.push(GenericParam {
387 kind: ParamKind::Const,
388 full: join(&declaration),
389 name,
390 });
391 } else {
392 let name = p.expect_ident().unwrap_or_default();
393 g.params.push(GenericParam {
394 kind: ParamKind::Type,
395 full: join(&declaration),
396 name,
397 });
398 }
399 }
400 g
401}
402
403fn strip_generic_default(tokens: &[TokenTree]) -> Vec<TokenTree> {
404 let mut angle_depth = 0usize;
405 for (index, token) in tokens.iter().enumerate() {
406 match token {
407 TokenTree::Punct(punct) if punct.as_char() == '<' => angle_depth += 1,
408 TokenTree::Punct(punct) if punct.as_char() == '>' => {
409 angle_depth = angle_depth.saturating_sub(1);
410 }
411 TokenTree::Punct(punct) if punct.as_char() == '=' && angle_depth == 0 => {
412 return tokens[..index].to_vec();
413 }
414 _ => {}
415 }
416 }
417 tokens.to_vec()
418}
419
420fn parse_named_fields(inner: &[TokenTree]) -> Vec<Field> {
421 split_top(inner, ',')
422 .iter()
423 .filter(|s| !s.is_empty())
424 .map(|piece| parse_named_field(piece))
425 .collect()
426}
427
428pub(crate) fn eat_visibility(p: &mut P<'_>) {
433 if !p.eat_ident("pub") {
434 return;
435 }
436 if matches!(p.peek(), Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Parenthesis) {
437 p.next();
438 }
439}
440
441fn parse_named_field(piece: &[TokenTree]) -> Field {
442 let mut p = P { toks: piece, i: 0 };
443 let attrs = parse_attrs(&mut p);
444 eat_visibility(&mut p);
445 let mut colon = None;
447 let mut j = p.i;
448 while j < piece.len() {
449 match &piece[j] {
450 TokenTree::Punct(c) if c.as_char() == ':' => {
451 if matches!(piece.get(j + 1), Some(TokenTree::Punct(n)) if n.as_char() == ':') {
452 j += 2;
453 continue;
454 }
455 colon = Some(j);
456 break;
457 }
458 _ => j += 1,
459 }
460 }
461 match colon {
462 Some(c) => Field {
463 ident: Some(join(&piece[p.i..c]).trim().to_string()),
464 ty: join(&piece[c + 1..]).trim().to_string(),
465 attrs: collect_metas(&attrs),
466 },
467 None => Field {
468 ident: None,
469 ty: join(&piece[p.i..]).trim().to_string(),
470 attrs: collect_metas(&attrs),
471 },
472 }
473}
474
475fn parse_unnamed_fields(inner: &[TokenTree]) -> Vec<Field> {
476 split_top(inner, ',')
477 .iter()
478 .filter(|s| !s.is_empty())
479 .map(|piece| {
480 let mut p = P { toks: piece, i: 0 };
481 let attrs = parse_attrs(&mut p);
482 eat_visibility(&mut p);
483 Field {
484 ident: None,
485 ty: join(&piece[p.i..]).trim().to_string(),
486 attrs: collect_metas(&attrs),
487 }
488 })
489 .collect()
490}
491
492fn parse_variants(inner: &[TokenTree]) -> Vec<Variant> {
493 split_top(inner, ',')
494 .iter()
495 .filter(|s| !s.is_empty())
496 .map(|piece| {
497 let mut p = P { toks: piece, i: 0 };
498 let attrs = parse_attrs(&mut p);
499 let ident = p.expect_ident().unwrap_or_default();
500 let fields = match p.next() {
501 Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Brace => {
502 let inner2: Vec<TokenTree> = g.stream().into_iter().collect();
503 Fields::Named(parse_named_fields(&inner2))
504 }
505 Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Parenthesis => {
506 let inner2: Vec<TokenTree> = g.stream().into_iter().collect();
507 Fields::Unnamed(parse_unnamed_fields(&inner2))
508 }
509 _ => Fields::Unit,
510 };
511 Variant {
512 ident,
513 fields,
514 attrs: collect_metas(&attrs),
515 }
516 })
517 .collect()
518}
519
520pub(crate) fn build_generics(
526 input: &Input,
527 cp: &str,
528 de: bool,
529 has_flatten: bool,
530 has_borrow: bool,
531) -> (String, String, String) {
532 let g = &input.generics;
533 let c = &input.cattr;
534
535 let mut impl_params: Vec<String> = g.params.iter().map(|p| p.full.clone()).collect();
536 if de {
537 impl_params.insert(0, "'de".to_string());
538 }
539 let impl_generics = if impl_params.is_empty() {
540 String::new()
541 } else {
542 format!("<{}>", impl_params.join(", "))
543 };
544
545 let names: Vec<String> = g.params.iter().map(|p| p.name.clone()).collect();
546 let ty_generics = if names.is_empty() {
547 String::new()
548 } else {
549 format!("<{}>", names.join(", "))
550 };
551
552 let mut preds: Vec<String> = if let Some(bound) = &c.bound {
553 let cleaned = bound.trim().trim_matches('"');
554 if cleaned.is_empty() {
555 Vec::new()
556 } else {
557 cleaned
558 .split(',')
559 .map(|s| s.trim().to_string())
560 .filter(|s| !s.is_empty())
561 .collect()
562 }
563 } else {
564 let mut v: Vec<String> = g.where_preds.clone();
565 for p in g.params.iter() {
566 if p.kind != ParamKind::Type {
567 continue;
568 }
569 if de && has_flatten {
570 v.push(format!(
571 "{0}: for<'__n> {1}::NsonDeserialize<'__n>",
572 p.name, cp
573 ));
574 } else if de {
575 v.push(format!("{}: {}::NsonDeserialize<'de>", p.name, cp));
576 } else {
577 v.push(format!("{}: {}::NsonSerialize", p.name, cp));
578 }
579 }
580 v
581 };
582
583 if de && has_borrow {
584 for p in g.params.iter() {
585 if p.kind == ParamKind::Lifetime {
586 preds.push(format!("'de: {}", p.name));
587 }
588 }
589 }
590
591 let where_clause = if preds.is_empty() {
592 String::new()
593 } else {
594 format!(" where {}", preds.join(", "))
595 };
596
597 (impl_generics, ty_generics, where_clause)
598}
599
600pub(crate) fn generate_impls(input: &Input) -> TokenStream {
602 let cp = input.cattr.crate_path.clone();
603 let name = input.ident.clone();
604 let (ig, tg, wc) = build_generics(input, &cp, false, false, false);
605 let schema_expr = schema::schema_expr(input, &cp);
606 let body = match &input.data {
607 Data::Struct(f) => ser::serialize_struct(&name, f, input, &cp),
608 Data::Enum(v) => ser::serialize_enum(&name, v, input, &cp),
609 };
610 let out = format!(
611 "#[automatically_derived]\n\
612 impl {ig} {cp}::NsonSchema for {name}{tg}{wc} {{\n\
613 \x20 const SCHEMA: {cp}::TypeSchema = {schema_expr};\n\
614 }}\n\
615 #[automatically_derived]\n\
616 impl {ig} {cp}::NsonSerialize for {name}{tg}{wc} {{\n\
617 \x20 fn nextencode<__E: {cp}::FormatEncoder>(&self, __e: &mut __E) -> {cp}::Result<()> {{\n\
618 {body}\n\
619 \x20 }}\n\
620 }}"
621 );
622 ts(&out)
623}
624
625pub(crate) fn generate_de_impl(input: &Input) -> TokenStream {
627 let cp = input.cattr.crate_path.clone();
628 let name = input.ident.clone();
629 let has_flatten = type_has_flag(input, |fa| fa.flatten);
630 let has_borrow = type_has_flag(input, |fa| fa.borrow);
631 if has_flatten && type_has_with(input) {
632 return err("nextjson: `flatten` cannot be combined with `with` / `deserialize_with`");
633 }
634 let (ig, tg, wc) = build_generics(input, &cp, true, has_flatten, has_borrow);
635 let body = match &input.data {
636 Data::Struct(f) => de::deserialize_struct(&name, f, input, &cp, has_flatten),
637 Data::Enum(v) => de::deserialize_enum(&name, v, input, &cp, has_flatten),
638 };
639 let out = format!(
640 "#[automatically_derived]\n\
641 impl {ig} {cp}::NsonDeserialize<'de> for {name}{tg}{wc} {{\n\
642 \x20 fn nextdecode_into<__D: {cp}::FormatDecoder<'de>>(\n\
643 \x20 __d: &mut __D,\n\
644 \x20 __out: &mut {cp}::DecodeSlot<Self>,\n\
645 \x20 ) -> {cp}::Result<()> {{\n\
646 {body}\n\
647 \x20 }}\n\
648 }}"
649 );
650 ts(&out)
651}
652
653fn type_has_flag<F: Fn(&FieldAttrs) -> bool>(input: &Input, f: F) -> bool {
654 match &input.data {
655 Data::Struct(fields) => fields.iter().any(|fld| f(&attr::field_attrs(&fld.attrs))),
656 Data::Enum(variants) => variants
657 .iter()
658 .any(|v| v.fields.iter().any(|fld| f(&attr::field_attrs(&fld.attrs)))),
659 }
660}
661
662fn type_has_with(input: &Input) -> bool {
663 type_has_flag(input, |fa| {
664 fa.with.is_some() || fa.deserialize_with.is_some()
665 })
666}
667
668#[allow(dead_code)]
670pub(crate) fn ident(name: &str) -> Ident {
671 Ident::new(name, proc_macro::Span::call_site())
672}
673
674#[proc_macro_derive(NsonSerialize, attributes(njson, nextjson))]
679pub fn derive_serialize(input: TokenStream) -> TokenStream {
685 match parse_input(input) {
686 Ok(ast) => generate_impls(&ast),
687 Err(e) => err(&e),
688 }
689}
690
691#[proc_macro_derive(NsonDeserialize, attributes(njson, nextjson))]
692pub fn derive_deserialize(input: TokenStream) -> TokenStream {
698 match parse_input(input) {
699 Ok(ast) => generate_de_impl(&ast),
700 Err(e) => err(&e),
701 }
702}