1use proc_macro::TokenStream;
17use proc_macro2::TokenStream as TokenStream2;
18use quote::{ToTokens, quote};
19use syn::{
20 Attribute, Data, DataEnum, DataStruct, DeriveInput, Expr, ExprLit, Field, Fields, GenericParam,
21 Ident, Lit, LitInt, Meta, Token, Type, Variant, WhereClause, WherePredicate,
22 parse::{Parse, ParseStream},
23 parse_macro_input, parse_quote,
24 punctuated::Punctuated,
25};
26
27#[proc_macro_derive(MsgpackTagged, attributes(tag, tagged))]
28pub fn derive_msgpack_tagged(input: TokenStream) -> TokenStream {
29 let input = parse_macro_input!(input as DeriveInput);
30 expand(&input).unwrap_or_else(syn::Error::into_compile_error).into()
31}
32
33fn product_struct_literal(
38 entries: &[TaggedField<'_>],
39 reserved: &[u8],
40 allow_unknown_tags: bool,
41 tag_order_matches_source: bool,
42) -> TokenStream2 {
43 let field_entries = entries.iter().map(|e| {
44 let tag = e.tag;
45 let name = &e.name;
46 quote! { (#tag, #name) }
47 });
48 let reserved_entries = reserved.iter().map(|tag| quote! { #tag });
49 quote! {
50 ::msgpack_tagged::Product {
51 fields: &[#(#field_entries),*],
52 reserved: &[#(#reserved_entries),*],
53 allow_unknown_tags: #allow_unknown_tags,
54 tag_order_matches_source: #tag_order_matches_source,
55 }
56 }
57}
58
59fn product_literal(
62 entries: &[TaggedField<'_>],
63 reserved: &[u8],
64 allow_unknown_tags: bool,
65 tag_order_matches_source: bool,
66) -> TokenStream2 {
67 let inner =
68 product_struct_literal(entries, reserved, allow_unknown_tags, tag_order_matches_source);
69 quote! { ::msgpack_tagged::Tagged::Product(#inner) }
70}
71
72fn variant_kind_token(kind: VariantKind) -> TokenStream2 {
75 match kind {
76 VariantKind::Unit => quote! { ::msgpack_tagged::VariantKind::Unit },
77 VariantKind::Newtype => quote! { ::msgpack_tagged::VariantKind::Newtype },
78 VariantKind::Tuple => quote! { ::msgpack_tagged::VariantKind::Tuple },
79 VariantKind::Struct => quote! { ::msgpack_tagged::VariantKind::Struct },
80 }
81}
82
83fn reject_payload_only_attrs_on_empty_variant(
89 variant: &Variant,
90 variant_attrs: &VariantAttrs,
91) -> syn::Result<()> {
92 if !variant_attrs.reserved.is_empty() {
93 return Err(syn::Error::new_spanned(
94 variant,
95 "`#[tagged(reserved(...))]` on a unit or newtype variant has no effect — \
96 the payload has no field tag space to reserve into",
97 ));
98 }
99 if variant_attrs.allow_unknown_tags {
100 return Err(syn::Error::new_spanned(
101 variant,
102 "`#[tagged(allow_unknown_tags)]` on a unit or newtype variant has no effect — \
103 the payload has no field tag space to skip unknown tags from",
104 ));
105 }
106 Ok(())
107}
108
109fn empty_product_literal() -> TokenStream2 {
113 quote! {
114 ::msgpack_tagged::Tagged::empty_product()
115 }
116}
117
118fn sum_literal(
124 variants: &[TaggedVariant<'_>],
125 reserved: &[u8],
126 on_reserved_tag: Option<u8>,
127 on_unknown_tag: Option<u8>,
128) -> TokenStream2 {
129 let variant_entries = variants.iter().map(|v| {
130 let tag = v.tag;
131 let name = &v.name;
132 let kind = variant_kind_token(v.kind);
133 let payload = product_struct_literal(
134 &v.payload,
135 &v.payload_reserved,
136 v.payload_allow_unknown_tags,
137 v.payload_tag_order_matches_source,
138 );
139 quote! {
140 ::msgpack_tagged::Variant {
141 tag: #tag,
142 name: #name,
143 kind: #kind,
144 payload: #payload,
145 }
146 }
147 });
148 let reserved_entries = reserved.iter().map(|tag| quote! { #tag });
149 let option_u8 = |o: Option<u8>| match o {
150 Some(t) => quote! { ::core::option::Option::Some(#t) },
151 None => quote! { ::core::option::Option::None },
152 };
153 let on_reserved_tag = option_u8(on_reserved_tag);
154 let on_unknown_tag = option_u8(on_unknown_tag);
155 quote! {
156 ::msgpack_tagged::Tagged::Sum(::msgpack_tagged::Sum {
157 variants: &[#(#variant_entries),*],
158 reserved: &[#(#reserved_entries),*],
159 on_reserved_tag: #on_reserved_tag,
160 on_unknown_tag: #on_unknown_tag,
161 })
162 }
163}
164
165fn expand(input: &DeriveInput) -> syn::Result<TokenStream2> {
166 let type_attrs = parse_tagged_type_attrs(input)?;
167
168 if let Some(wire_type) = &type_attrs.via {
174 validate_no_field_tag_attrs(input)?;
175 return Ok(expand_via(input, wire_type));
176 }
177
178 match &input.data {
179 Data::Struct(DataStruct { fields: Fields::Named(named), .. }) => {
180 expand_named_struct(input, &named.named, &type_attrs)
181 }
182 Data::Struct(DataStruct { fields: Fields::Unnamed(unnamed), .. }) => {
183 expand_unnamed_struct(input, &unnamed.unnamed, &type_attrs)
184 }
185 Data::Enum(data) => expand_enum(input, data, &type_attrs),
186 _ => Ok(stub(input)),
189 }
190}
191
192fn expand_unnamed_struct(
197 input: &DeriveInput,
198 fields: &Punctuated<Field, Token![,]>,
199 type_attrs: &TypeAttrs,
200) -> syn::Result<TokenStream2> {
201 debug_assert!(type_attrs.via.is_none()); if fields.len() == 1 {
203 expand_newtype(input, fields.first().unwrap(), type_attrs)
204 } else {
205 expand_tuple_struct(input, fields, type_attrs)
206 }
207}
208
209fn expand_newtype(
214 input: &DeriveInput,
215 inner_field: &Field,
216 type_attrs: &TypeAttrs,
217) -> syn::Result<TokenStream2> {
218 if !type_attrs.reserved.is_empty() {
219 return Err(syn::Error::new_spanned(
220 input,
221 "newtype structs (single-element tuple structs) pass through to the inner type \
222 and have no wire shape of their own — `#[tagged(reserved(...))]` doesn't apply",
223 ));
224 }
225 if type_attrs.allow_unknown_tags {
226 return Err(syn::Error::new_spanned(
227 input,
228 "newtype structs (single-element tuple structs) pass through to the inner type \
229 and have no wire shape of their own — `#[tagged(allow_unknown_tags)]` doesn't apply",
230 ));
231 }
232 for attr in &inner_field.attrs {
233 if attr.path().is_ident("tag") {
234 return Err(syn::Error::new_spanned(
235 attr,
236 "newtype structs pass through to the inner type — \
237 `#[tag(...)]` on the inner field is not allowed",
238 ));
239 }
240 }
241
242 let name = &input.ident;
243 let inner_type = &inner_field.ty;
244 let where_clause = build_passthrough_where_clause(input, inner_type);
245 let (impl_generics, ty_generics, _) = input.generics.split_for_impl();
246 let tagged = empty_product_literal();
247
248 Ok(quote! {
249 impl #impl_generics ::msgpack_tagged::MsgpackTagged for #name #ty_generics #where_clause {
250 const TAGGED: ::msgpack_tagged::Tagged = #tagged;
251
252 fn register_into(_reg: &mut ::msgpack_tagged::TagRegistry) {
253 <#inner_type as ::msgpack_tagged::MsgpackTagged>::register_into(_reg);
254 }
255 }
256 })
257}
258
259fn expand_tuple_struct(
272 input: &DeriveInput,
273 fields: &Punctuated<Field, Token![,]>,
274 type_attrs: &TypeAttrs,
275) -> syn::Result<TokenStream2> {
276 let name = &input.ident;
277 let name_str = parse_serde_rename(input)?.unwrap_or_else(|| name.to_string());
278 let reserved = &type_attrs.reserved;
279 let allow_unknown_tags = type_attrs.allow_unknown_tags;
280
281 let (entries, tag_order_matches_source) = parse_tuple_fields(input, fields, reserved)?;
282
283 let recursion_calls = entries.iter().map(|e| {
284 let ty = e.ty;
285 quote! { <#ty as ::msgpack_tagged::MsgpackTagged>::register_into(_reg); }
286 });
287
288 let tagged = product_literal(&entries, reserved, allow_unknown_tags, tag_order_matches_source);
289 let where_clause = build_where_clause(input, &entries, &type_attrs.extra_bounds);
290 let (impl_generics, ty_generics, _) = input.generics.split_for_impl();
291
292 Ok(quote! {
293 impl #impl_generics ::msgpack_tagged::MsgpackTagged for #name #ty_generics #where_clause {
294 const TAGGED: ::msgpack_tagged::Tagged = #tagged;
295
296 fn register_into(_reg: &mut ::msgpack_tagged::TagRegistry) {
297 if _reg.try_insert::<Self>(#name_str) {
298 #(#recursion_calls)*
299 }
300 }
301 }
302 })
303}
304
305#[derive(Clone, Copy)]
309enum VariantKind {
310 Unit,
311 Newtype,
312 Tuple,
313 Struct,
314}
315
316struct TaggedVariant<'a> {
335 tag: u8,
336 name: String,
337 kind: VariantKind,
338 payload: Vec<TaggedField<'a>>,
339 newtype_inner: Option<&'a Type>,
340 payload_reserved: Vec<u8>,
341 payload_allow_unknown_tags: bool,
342 payload_tag_order_matches_source: bool,
348}
349
350fn expand_enum(
371 input: &DeriveInput,
372 data: &DataEnum,
373 type_attrs: &TypeAttrs,
374) -> syn::Result<TokenStream2> {
375 debug_assert!(type_attrs.via.is_none()); if type_attrs.allow_unknown_tags {
377 return Err(syn::Error::new_spanned(
378 input,
379 "`#[tagged(allow_unknown_tags)]` doesn't apply to enums — there's no \
380 meaningful skip semantics for an unknown variant tag (the value's \
381 discriminator itself becomes non-representable). Mark a unit variant \
382 with `#[tagged(on_unknown)]` instead — the wrapper will route \
383 unknown wire tags there on decode",
384 ));
385 }
386 let name = &input.ident;
387 let name_str = parse_serde_rename(input)?.unwrap_or_else(|| name.to_string());
388 let reserved = &type_attrs.reserved;
389
390 let mut variants: Vec<TaggedVariant<'_>> = Vec::with_capacity(data.variants.len());
391 let mut seen_tags = std::collections::HashSet::new();
392 let mut on_reserved_marker: Option<(u8, String)> = None;
393 let mut on_unknown_marker: Option<(u8, String)> = None;
394 for variant in &data.variants {
395 let tag = parse_variant_tag(variant, reserved)?;
396 if !seen_tags.insert(tag) {
397 return Err(syn::Error::new_spanned(
398 variant,
399 format!("variant tag {tag} is used more than once"),
400 ));
401 }
402 let variant_attrs = parse_tagged_variant_attrs(variant)?;
408 if (variant_attrs.on_reserved || variant_attrs.on_unknown)
409 && !matches!(variant.fields, Fields::Unit)
410 {
411 return Err(syn::Error::new_spanned(
412 variant,
413 "`#[tagged(on_reserved)]` and `#[tagged(on_unknown)]` mark fallback \
414 routing targets — the wrapper discards the wire payload when it \
415 routes here, so they're only valid on unit variants",
416 ));
417 }
418 if variant_attrs.on_reserved {
419 if let Some((_, prev)) = &on_reserved_marker {
420 return Err(syn::Error::new_spanned(
421 variant,
422 format!(
423 "multiple `#[tagged(on_reserved)]` variants on the same enum — \
424 only one fallback for retired tags is allowed (previous: {prev:?})",
425 ),
426 ));
427 }
428 on_reserved_marker = Some((tag, variant.ident.to_string()));
429 }
430 if variant_attrs.on_unknown {
431 if let Some((_, prev)) = &on_unknown_marker {
432 return Err(syn::Error::new_spanned(
433 variant,
434 format!(
435 "multiple `#[tagged(on_unknown)]` variants on the same enum — \
436 only one fallback for unknown tags is allowed (previous: {prev:?})",
437 ),
438 ));
439 }
440 on_unknown_marker = Some((tag, variant.ident.to_string()));
441 }
442 let (kind, payload, payload_tag_order_matches_source, newtype_inner) = match &variant.fields
443 {
444 Fields::Unit => {
445 reject_payload_only_attrs_on_empty_variant(variant, &variant_attrs)?;
446 (VariantKind::Unit, Vec::new(), true, None)
448 }
449 Fields::Named(named) => {
450 let (payload, monotonic) =
451 parse_named_fields(&named.named, &variant_attrs.reserved)?;
452 (VariantKind::Struct, payload, monotonic, None)
453 }
454 Fields::Unnamed(unnamed) if unnamed.unnamed.len() == 1 => {
455 let inner = unnamed.unnamed.first().expect("len == 1");
463 for attr in &inner.attrs {
464 if attr.path().is_ident("tag") {
465 return Err(syn::Error::new_spanned(
466 attr,
467 "newtype variants (single-element tuple variants) pass through to \
468 the inner type — `#[tag(...)]` on the inner field is not allowed",
469 ));
470 }
471 }
472 reject_payload_only_attrs_on_empty_variant(variant, &variant_attrs)?;
473 (VariantKind::Newtype, Vec::new(), true, Some(&inner.ty))
474 }
475 Fields::Unnamed(unnamed) => {
476 let (payload, monotonic) =
477 parse_tuple_fields(variant, &unnamed.unnamed, &variant_attrs.reserved)?;
478 (VariantKind::Tuple, payload, monotonic, None)
479 }
480 };
481 variants.push(TaggedVariant {
482 tag,
483 name: variant.ident.to_string(),
484 kind,
485 payload,
486 newtype_inner,
487 payload_reserved: variant_attrs.reserved,
488 payload_allow_unknown_tags: variant_attrs.allow_unknown_tags,
489 payload_tag_order_matches_source,
490 });
491 }
492 variants.sort_by_key(|v| v.tag);
493
494 let recursion_calls = variants.iter().flat_map(|v| {
495 let payload_calls = v.payload.iter().map(|entry| {
499 let ty = entry.ty;
500 quote! { <#ty as ::msgpack_tagged::MsgpackTagged>::register_into(_reg); }
501 });
502 let newtype_call = v.newtype_inner.map(|ty| {
503 quote! { <#ty as ::msgpack_tagged::MsgpackTagged>::register_into(_reg); }
504 });
505 payload_calls.chain(newtype_call)
506 });
507
508 let on_reserved_tag = on_reserved_marker.map(|(tag, _)| tag);
509 let on_unknown_tag = on_unknown_marker.map(|(tag, _)| tag);
510 let tagged = sum_literal(&variants, reserved, on_reserved_tag, on_unknown_tag);
511 let where_clause = build_enum_where_clause(input, &variants, &type_attrs.extra_bounds);
512 let (impl_generics, ty_generics, _) = input.generics.split_for_impl();
513
514 Ok(quote! {
515 impl #impl_generics ::msgpack_tagged::MsgpackTagged for #name #ty_generics #where_clause {
516 const TAGGED: ::msgpack_tagged::Tagged = #tagged;
517
518 fn register_into(_reg: &mut ::msgpack_tagged::TagRegistry) {
519 if _reg.try_insert::<Self>(#name_str) {
520 #(#recursion_calls)*
521 }
522 }
523 }
524 })
525}
526
527fn parse_variant_tag(variant: &Variant, reserved: &[u8]) -> syn::Result<u8> {
531 let mut found: Option<(&Attribute, TagArgs)> = None;
532 for attr in &variant.attrs {
533 if !attr.path().is_ident("tag") {
534 continue;
535 }
536 if found.is_some() {
537 return Err(syn::Error::new_spanned(attr, "duplicate `#[tag(...)]` attribute"));
538 }
539 found = Some((attr, attr.parse_args()?));
540 }
541 let Some((attr, args)) = found else {
542 return Err(syn::Error::new_spanned(
543 variant,
544 "missing `#[tag(N)]` attribute on enum variant — every variant needs an explicit tag",
545 ));
546 };
547 let TagArgs(tag) = args;
548 if reserved.contains(&tag) {
549 return Err(syn::Error::new_spanned(
550 attr,
551 format!(
552 "tag {tag} is in the type's `#[tagged(reserved(...))]` list — pick a different tag, or remove it from the reserved list"
553 ),
554 ));
555 }
556 Ok(tag)
557}
558
559fn build_enum_where_clause(
564 input: &DeriveInput,
565 variants: &[TaggedVariant<'_>],
566 extra_bounds: &[WherePredicate],
567) -> Option<WhereClause> {
568 let has_type_params = input.generics.params.iter().any(|p| matches!(p, GenericParam::Type(_)));
569 let any_bound_source =
570 variants.iter().any(|v| !v.payload.is_empty() || v.newtype_inner.is_some());
571
572 if !any_bound_source && !has_type_params && extra_bounds.is_empty() {
573 return input.generics.where_clause.clone();
574 }
575
576 let mut where_clause = input.generics.where_clause.clone().unwrap_or_else(|| WhereClause {
577 where_token: <Token![where]>::default(),
578 predicates: Punctuated::new(),
579 });
580
581 for param in &input.generics.params {
582 if let GenericParam::Type(type_param) = param {
583 let ident = &type_param.ident;
584 where_clause.predicates.push(parse_quote!(#ident: 'static));
585 }
586 }
587
588 let self_ident = &input.ident;
589 let mut seen_msgpack = std::collections::HashSet::new();
590 for v in variants {
591 for entry in &v.payload {
592 let ty = entry.ty;
593 let key = quote!(#ty).to_string();
594 let self_typed = type_contains_ident(ty, self_ident);
598 if !self_typed && seen_msgpack.insert(key) {
599 where_clause.predicates.push(parse_quote!(#ty: ::msgpack_tagged::MsgpackTagged));
600 }
601 }
602 if let Some(ty) = v.newtype_inner {
609 let key = quote!(#ty).to_string();
610 let self_typed = type_contains_ident(ty, self_ident);
611 if !self_typed && seen_msgpack.insert(key) {
612 where_clause.predicates.push(parse_quote!(#ty: ::msgpack_tagged::MsgpackTagged));
613 }
614 }
615 }
616
617 for predicate in extra_bounds {
618 where_clause.predicates.push(predicate.clone());
619 }
620
621 Some(where_clause)
622}
623
624fn build_passthrough_where_clause(input: &DeriveInput, inner_type: &Type) -> Option<WhereClause> {
629 let mut where_clause = input.generics.where_clause.clone().unwrap_or_else(|| WhereClause {
630 where_token: <Token![where]>::default(),
631 predicates: Punctuated::new(),
632 });
633 for param in &input.generics.params {
634 if let GenericParam::Type(type_param) = param {
635 let ident = &type_param.ident;
636 where_clause.predicates.push(parse_quote!(#ident: 'static));
637 }
638 }
639 where_clause.predicates.push(parse_quote!(#inner_type: ::msgpack_tagged::MsgpackTagged));
640 Some(where_clause)
641}
642
643fn validate_no_field_tag_attrs(input: &DeriveInput) -> syn::Result<()> {
649 let check = |fields: &Fields| -> syn::Result<()> {
650 for field in fields {
651 for attr in &field.attrs {
652 if attr.path().is_ident("tag") {
653 return Err(syn::Error::new_spanned(
654 attr,
655 "field-level `#[tag(...)]` is not allowed on a type with `#[tagged(via(...))]` — \
656 fields of a `via`-delegating type are wire-irrelevant; \
657 tag the wire DTO's fields instead",
658 ));
659 }
660 }
661 }
662 Ok(())
663 };
664 match &input.data {
665 Data::Struct(s) => check(&s.fields)?,
666 Data::Enum(e) => {
667 for variant in &e.variants {
668 for attr in &variant.attrs {
669 if attr.path().is_ident("tag") {
670 return Err(syn::Error::new_spanned(
671 attr,
672 "variant-level `#[tag(...)]` is not allowed on a type with `#[tagged(via(...))]` — \
673 variants of a `via`-delegating enum are wire-irrelevant; \
674 tag the wire DTO's variants instead",
675 ));
676 }
677 if attr.path().is_ident("tagged") {
678 return Err(syn::Error::new_spanned(
679 attr,
680 "variant-level `#[tagged(...)]` is not allowed on a type with `#[tagged(via(...))]` — \
681 variants of a `via`-delegating enum are wire-irrelevant; \
682 configure the wire DTO instead",
683 ));
684 }
685 }
686 check(&variant.fields)?;
687 }
688 }
689 Data::Union(u) => {
690 for field in &u.fields.named {
691 for attr in &field.attrs {
692 if attr.path().is_ident("tag") {
693 return Err(syn::Error::new_spanned(
694 attr,
695 "field-level `#[tag(...)]` is not allowed on a type with `#[tagged(via(...))]` — \
696 fields of a `via`-delegating type are wire-irrelevant; \
697 tag the wire DTO's fields instead",
698 ));
699 }
700 }
701 }
702 }
703 }
704 Ok(())
705}
706
707fn stub(input: &DeriveInput) -> TokenStream2 {
710 let name = &input.ident;
711 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
712 let tagged = empty_product_literal();
713 quote! {
714 impl #impl_generics ::msgpack_tagged::MsgpackTagged for #name #ty_generics #where_clause {
715 const TAGGED: ::msgpack_tagged::Tagged = #tagged;
716 fn register_into(_reg: &mut ::msgpack_tagged::TagRegistry) {}
717 }
718 }
719}
720
721struct TaggedField<'a> {
727 tag: u8,
728 name: String,
729 ty: &'a Type,
730}
731
732fn parse_named_fields<'a>(
738 fields: &'a Punctuated<Field, Token![,]>,
739 reserved: &[u8],
740) -> syn::Result<(Vec<TaggedField<'a>>, bool)> {
741 let mut entries = Vec::with_capacity(fields.len());
742 let mut seen_tags = std::collections::HashSet::new();
743 for field in fields {
744 let ident = field.ident.as_ref().expect("named field has an ident");
745 match classify_field(field, reserved)? {
746 FieldKind::Tagged(tag) => {
747 if !seen_tags.insert(tag) {
748 return Err(syn::Error::new_spanned(
749 field,
750 format!("tag {tag} is used more than once"),
751 ));
752 }
753 let wire_name =
758 parse_serde_field_rename(field)?.unwrap_or_else(|| ident.to_string());
759 entries.push(TaggedField { tag, name: wire_name, ty: &field.ty });
760 }
761 FieldKind::Skipped => {}
762 }
763 }
764 let tag_order_matches_source = is_tag_ascending(&entries);
767 entries.sort_by_key(|e| e.tag);
768 Ok((entries, tag_order_matches_source))
769}
770
771fn is_tag_ascending(entries: &[TaggedField<'_>]) -> bool {
775 entries.windows(2).all(|w| w[0].tag < w[1].tag)
776}
777
778fn parse_tuple_fields<'a>(
789 mixing_error_span: &dyn ToTokens,
790 fields: &'a Punctuated<Field, Token![,]>,
791 reserved: &[u8],
792) -> syn::Result<(Vec<TaggedField<'a>>, bool)> {
793 let explicit_count =
794 fields.iter().filter(|f| f.attrs.iter().any(|a| a.path().is_ident("tag"))).count();
795 if explicit_count != 0 && explicit_count != fields.len() {
796 return Err(syn::Error::new_spanned(
797 mixing_error_span,
798 "tuple-style fields must either all carry `#[tag(N)]` or none — \
799 mixing implicit positional tags with explicit tags is rejected",
800 ));
801 }
802 let all_explicit = explicit_count == fields.len();
803
804 let mut entries = Vec::with_capacity(fields.len());
805 let mut seen_tags = std::collections::HashSet::new();
806 for (position, field) in fields.iter().enumerate() {
807 let position_u8: u8 = position.try_into().map_err(|_| {
808 syn::Error::new_spanned(
809 field,
810 format!("tuple position {position} is out of range for u8 tags"),
811 )
812 })?;
813 let tag = if all_explicit {
814 match classify_field(field, reserved)? {
815 FieldKind::Tagged(tag) => tag,
816 FieldKind::Skipped => {
817 return Err(syn::Error::new_spanned(
818 field,
819 "`#[serde(skip)]` on tuple-style fields is not supported — \
820 it would shift positional indices",
821 ));
822 }
823 }
824 } else {
825 if has_serde_skip(field)? {
828 return Err(syn::Error::new_spanned(
829 field,
830 "`#[serde(skip)]` on tuple-style fields is not supported",
831 ));
832 }
833 if reserved.contains(&position_u8) {
834 return Err(syn::Error::new_spanned(
835 field,
836 format!(
837 "implicit positional tag {position_u8} collides with the type's \
838 `#[tagged(reserved(...))]` list — assign explicit `#[tag(N)]`s, \
839 or remove the reserved entry"
840 ),
841 ));
842 }
843 position_u8
844 };
845 if !seen_tags.insert(tag) {
846 return Err(syn::Error::new_spanned(
847 field,
848 format!("tag {tag} is used more than once"),
849 ));
850 }
851 entries.push(TaggedField { tag, name: position.to_string(), ty: &field.ty });
852 }
853 let tag_order_matches_source = is_tag_ascending(&entries);
854 entries.sort_by_key(|e| e.tag);
855 Ok((entries, tag_order_matches_source))
856}
857
858fn expand_named_struct(
859 input: &DeriveInput,
860 fields: &Punctuated<Field, Token![,]>,
861 type_attrs: &TypeAttrs,
862) -> syn::Result<TokenStream2> {
863 let name = &input.ident;
864 let name_str = parse_serde_rename(input)?.unwrap_or_else(|| name.to_string());
872
873 debug_assert!(type_attrs.via.is_none());
877 let reserved = &type_attrs.reserved;
878 let allow_unknown_tags = type_attrs.allow_unknown_tags;
879
880 let (entries, tag_order_matches_source) = parse_named_fields(fields, reserved)?;
885
886 let recursion_calls = entries.iter().map(|e| {
887 let ty = e.ty;
888 quote! { <#ty as ::msgpack_tagged::MsgpackTagged>::register_into(_reg); }
889 });
890
891 let tagged = product_literal(&entries, reserved, allow_unknown_tags, tag_order_matches_source);
899 let where_clause = build_where_clause(input, &entries, &type_attrs.extra_bounds);
900 let (impl_generics, ty_generics, _) = input.generics.split_for_impl();
901
902 Ok(quote! {
903 impl #impl_generics ::msgpack_tagged::MsgpackTagged for #name #ty_generics #where_clause {
904 const TAGGED: ::msgpack_tagged::Tagged = #tagged;
905
906 fn register_into(_reg: &mut ::msgpack_tagged::TagRegistry) {
907 if _reg.try_insert::<Self>(#name_str) {
908 #(#recursion_calls)*
909 }
910 }
911 }
912 })
913}
914
915fn expand_via(input: &DeriveInput, wire_type: &Type) -> TokenStream2 {
921 let name = &input.ident;
922 let where_clause = build_via_where_clause(input, wire_type);
923 let (impl_generics, ty_generics, _) = input.generics.split_for_impl();
924 let tagged = empty_product_literal();
925
926 quote! {
927 impl #impl_generics ::msgpack_tagged::MsgpackTagged for #name #ty_generics #where_clause {
928 const TAGGED: ::msgpack_tagged::Tagged = #tagged;
929
930 fn register_into(_reg: &mut ::msgpack_tagged::TagRegistry) {
931 <#wire_type as ::msgpack_tagged::MsgpackTagged>::register_into(_reg);
932 }
933 }
934 }
935}
936
937fn build_via_where_clause(input: &DeriveInput, wire_type: &Type) -> Option<WhereClause> {
943 let mut where_clause = input.generics.where_clause.clone().unwrap_or_else(|| WhereClause {
944 where_token: <Token![where]>::default(),
945 predicates: Punctuated::new(),
946 });
947 for param in &input.generics.params {
948 if let GenericParam::Type(type_param) = param {
949 let ident = &type_param.ident;
950 where_clause.predicates.push(parse_quote!(#ident: 'static));
951 }
952 }
953 where_clause.predicates.push(parse_quote!(#wire_type: ::msgpack_tagged::MsgpackTagged));
954 Some(where_clause)
955}
956
957enum FieldKind {
959 Tagged(u8),
961 Skipped,
965}
966
967struct TagArgs(u8);
972
973impl Parse for TagArgs {
974 fn parse(input: ParseStream) -> syn::Result<Self> {
975 let lit: LitInt = input.parse()?;
976 let tag: u8 = lit.base10_parse()?;
977 if !input.is_empty() {
978 return Err(input.error("`#[tag(...)]` accepts a single integer tag literal"));
979 }
980 Ok(TagArgs(tag))
981 }
982}
983
984fn classify_field(field: &Field, reserved: &[u8]) -> syn::Result<FieldKind> {
992 let serde_skip = has_serde_skip(field)?;
993 let mut found: Option<(&Attribute, TagArgs)> = None;
994 for attr in &field.attrs {
995 if !attr.path().is_ident("tag") {
996 continue;
997 }
998 if found.is_some() {
999 return Err(syn::Error::new_spanned(attr, "duplicate `#[tag(...)]` attribute"));
1000 }
1001 found = Some((attr, attr.parse_args()?));
1002 }
1003
1004 if let Some((attr, TagArgs(tag))) = found {
1007 if serde_skip {
1008 return Err(syn::Error::new_spanned(
1009 attr,
1010 "field has both `#[tag(N)]` and `#[serde(skip)]` — these are \
1011 contradictory; pick one (`#[serde(skip)]` to drop the field, \
1012 or `#[tag(N)]` to put the field on the wire under tag N)",
1013 ));
1014 }
1015 if reserved.contains(&tag) {
1016 return Err(syn::Error::new_spanned(
1017 attr,
1018 format!(
1019 "tag {tag} is in the surrounding `#[tagged(reserved(...))]` list — pick a different tag, or remove it from the reserved list"
1020 ),
1021 ));
1022 }
1023 return Ok(FieldKind::Tagged(tag));
1024 }
1025
1026 if serde_skip {
1031 return Ok(FieldKind::Skipped);
1032 }
1033 if is_phantom_data(&field.ty) {
1034 return Ok(FieldKind::Skipped);
1035 }
1036
1037 Err(syn::Error::new_spanned(
1038 field,
1039 "missing `#[tag(N)]` attribute — every field needs an explicit tag, \
1040 `#[serde(skip)]`, or be `PhantomData<_>`",
1041 ))
1042}
1043
1044fn parse_serde_rename_in_attrs(attrs: &[Attribute]) -> syn::Result<Option<String>> {
1055 let mut found: Option<String> = None;
1056 for attr in attrs {
1057 if !attr.path().is_ident("serde") {
1058 continue;
1059 }
1060 let items: Punctuated<Meta, Token![,]> =
1061 attr.parse_args_with(Punctuated::parse_terminated)?;
1062 for item in items {
1063 if let Meta::NameValue(nv) = &item
1064 && nv.path.is_ident("rename")
1065 && let Expr::Lit(ExprLit { lit: Lit::Str(s), .. }) = &nv.value
1066 {
1067 found = Some(s.value());
1068 }
1069 }
1070 }
1071 Ok(found)
1072}
1073
1074fn parse_serde_rename(input: &DeriveInput) -> syn::Result<Option<String>> {
1078 parse_serde_rename_in_attrs(&input.attrs)
1079}
1080
1081fn parse_serde_field_rename(field: &Field) -> syn::Result<Option<String>> {
1087 parse_serde_rename_in_attrs(&field.attrs)
1088}
1089
1090fn has_serde_skip(field: &Field) -> syn::Result<bool> {
1096 for attr in &field.attrs {
1097 if !attr.path().is_ident("serde") {
1098 continue;
1099 }
1100 let items: Punctuated<Meta, Token![,]> =
1101 attr.parse_args_with(Punctuated::parse_terminated)?;
1102 for item in items {
1103 if let Meta::Path(path) = &item
1104 && path.is_ident("skip")
1105 {
1106 return Ok(true);
1107 }
1108 }
1109 }
1110 Ok(false)
1111}
1112
1113#[derive(Default)]
1122struct VariantAttrs {
1123 reserved: Vec<u8>,
1124 allow_unknown_tags: bool,
1125 on_reserved: bool,
1126 on_unknown: bool,
1127}
1128
1129fn parse_tagged_variant_attrs(variant: &Variant) -> syn::Result<VariantAttrs> {
1134 let mut out = VariantAttrs::default();
1135
1136 for attr in &variant.attrs {
1137 if !attr.path().is_ident("tagged") {
1138 continue;
1139 }
1140 let items: Punctuated<Meta, Token![,]> =
1141 attr.parse_args_with(Punctuated::parse_terminated)?;
1142 for item in items {
1143 if let Meta::List(list) = &item
1144 && list.path.is_ident("reserved")
1145 {
1146 let lits: Punctuated<LitInt, Token![,]> =
1147 list.parse_args_with(Punctuated::parse_terminated)?;
1148 for lit in &lits {
1149 let n: u8 = lit.base10_parse()?;
1150 if out.reserved.contains(&n) {
1151 return Err(syn::Error::new_spanned(
1152 lit,
1153 format!("tag {n} listed more than once in `reserved(...)`"),
1154 ));
1155 }
1156 out.reserved.push(n);
1157 }
1158 continue;
1159 }
1160 if let Meta::Path(path) = &item
1161 && path.is_ident("allow_unknown_tags")
1162 {
1163 if out.allow_unknown_tags {
1164 return Err(syn::Error::new_spanned(
1165 path,
1166 "duplicate `allow_unknown_tags` modifier in `#[tagged(...)]`",
1167 ));
1168 }
1169 out.allow_unknown_tags = true;
1170 continue;
1171 }
1172 if let Meta::Path(path) = &item
1173 && path.is_ident("on_reserved")
1174 {
1175 if out.on_reserved {
1176 return Err(syn::Error::new_spanned(
1177 path,
1178 "duplicate `on_reserved` modifier in `#[tagged(...)]`",
1179 ));
1180 }
1181 out.on_reserved = true;
1182 continue;
1183 }
1184 if let Meta::Path(path) = &item
1185 && path.is_ident("on_unknown")
1186 {
1187 if out.on_unknown {
1188 return Err(syn::Error::new_spanned(
1189 path,
1190 "duplicate `on_unknown` modifier in `#[tagged(...)]`",
1191 ));
1192 }
1193 out.on_unknown = true;
1194 continue;
1195 }
1196 return Err(syn::Error::new_spanned(
1197 &item,
1198 "expected `reserved(...)`, `allow_unknown_tags`, `on_reserved`, or \
1199 `on_unknown` inside `#[tagged(...)]` on an enum variant — \
1200 `via(...)` is a type-level modifier, not variant-level",
1201 ));
1202 }
1203 }
1204 Ok(out)
1205}
1206
1207#[derive(Default)]
1211struct TypeAttrs {
1212 reserved: Vec<u8>,
1214 allow_unknown_tags: bool,
1218 via: Option<Type>,
1224 extra_bounds: Vec<WherePredicate>,
1233}
1234
1235fn parse_tagged_type_attrs(input: &DeriveInput) -> syn::Result<TypeAttrs> {
1251 let mut out = TypeAttrs::default();
1252
1253 for attr in &input.attrs {
1254 if !attr.path().is_ident("tagged") {
1255 continue;
1256 }
1257 let items: Punctuated<Meta, Token![,]> =
1258 attr.parse_args_with(Punctuated::parse_terminated)?;
1259 for item in items {
1260 if let Meta::List(list) = &item
1261 && list.path.is_ident("reserved")
1262 {
1263 let lits: Punctuated<LitInt, Token![,]> =
1264 list.parse_args_with(Punctuated::parse_terminated)?;
1265 for lit in &lits {
1266 let n: u8 = lit.base10_parse()?;
1267 if out.reserved.contains(&n) {
1268 return Err(syn::Error::new_spanned(
1269 lit,
1270 format!("tag {n} listed more than once in `reserved(...)`"),
1271 ));
1272 }
1273 out.reserved.push(n);
1274 }
1275 continue;
1276 }
1277 if let Meta::Path(path) = &item
1278 && path.is_ident("allow_unknown_tags")
1279 {
1280 if out.allow_unknown_tags {
1281 return Err(syn::Error::new_spanned(
1282 path,
1283 "duplicate `allow_unknown_tags` modifier in `#[tagged(...)]`",
1284 ));
1285 }
1286 out.allow_unknown_tags = true;
1287 continue;
1288 }
1289 if let Meta::List(list) = &item
1290 && list.path.is_ident("via")
1291 {
1292 if out.via.is_some() {
1293 return Err(syn::Error::new_spanned(
1294 list,
1295 "duplicate `via(...)` modifier in `#[tagged(...)]`",
1296 ));
1297 }
1298 out.via = Some(list.parse_args::<Type>()?);
1299 continue;
1300 }
1301 if let Meta::NameValue(nv) = &item
1302 && nv.path.is_ident("extra_bound")
1303 {
1304 let Expr::Lit(ExprLit { lit: Lit::Str(s), .. }) = &nv.value else {
1310 return Err(syn::Error::new_spanned(
1311 &nv.value,
1312 "`extra_bound` requires a string literal of the form \
1313 `\"T: Trait, U: Trait\"`",
1314 ));
1315 };
1316 let bound_str = s.value();
1317 let where_clause: WhereClause = syn::parse_str(&format!("where {bound_str}"))
1322 .map_err(|e| {
1323 syn::Error::new_spanned(
1324 s,
1325 format!("failed to parse `extra_bound` predicates: {e}"),
1326 )
1327 })?;
1328 out.extra_bounds.extend(where_clause.predicates);
1329 continue;
1330 }
1331 return Err(syn::Error::new_spanned(
1332 &item,
1333 "expected `reserved(...)`, `allow_unknown_tags`, `via(...)`, or \
1334 `extra_bound = \"...\"` inside `#[tagged(...)]` on a type",
1335 ));
1336 }
1337 }
1338
1339 if out.via.is_some() {
1342 if !out.reserved.is_empty() {
1343 return Err(syn::Error::new_spanned(
1344 input,
1345 "`#[tagged(via(...))]` is incompatible with `reserved(...)` — \
1346 the reserved-tag list belongs on the wire DTO, not on the public type",
1347 ));
1348 }
1349 if out.allow_unknown_tags {
1350 return Err(syn::Error::new_spanned(
1351 input,
1352 "`#[tagged(via(...))]` is incompatible with `allow_unknown_tags` — \
1353 that flag belongs on the wire DTO, not on the public type",
1354 ));
1355 }
1356 if !out.extra_bounds.is_empty() {
1357 return Err(syn::Error::new_spanned(
1358 input,
1359 "`#[tagged(via(...))]` is incompatible with `extra_bound = \"...\"` — \
1360 the public type's where clause is just the delegation glue; \
1361 if a custom bound is needed, put it on the wire DTO",
1362 ));
1363 }
1364 }
1365
1366 Ok(out)
1367}
1368
1369fn is_phantom_data(ty: &Type) -> bool {
1375 if let Type::Path(type_path) = ty
1376 && let Some(last) = type_path.path.segments.last()
1377 {
1378 return last.ident == "PhantomData";
1379 }
1380 false
1381}
1382
1383fn type_contains_ident(ty: &Type, target: &Ident) -> bool {
1407 match ty {
1408 Type::Path(p) => {
1409 for seg in &p.path.segments {
1410 if &seg.ident == target {
1411 return true;
1412 }
1413 if let syn::PathArguments::AngleBracketed(args) = &seg.arguments {
1414 for arg in &args.args {
1415 if let syn::GenericArgument::Type(inner) = arg
1416 && type_contains_ident(inner, target)
1417 {
1418 return true;
1419 }
1420 }
1421 }
1422 }
1423 false
1424 }
1425 Type::Reference(r) => type_contains_ident(&r.elem, target),
1426 Type::Array(a) => type_contains_ident(&a.elem, target),
1427 Type::Slice(s) => type_contains_ident(&s.elem, target),
1428 Type::Tuple(t) => t.elems.iter().any(|e| type_contains_ident(e, target)),
1429 Type::Paren(p) => type_contains_ident(&p.elem, target),
1430 Type::Group(g) => type_contains_ident(&g.elem, target),
1431 Type::Ptr(p) => type_contains_ident(&p.elem, target),
1432 _ => false,
1433 }
1434}
1435
1436fn build_where_clause(
1456 input: &DeriveInput,
1457 entries: &[TaggedField<'_>],
1458 extra_bounds: &[WherePredicate],
1459) -> Option<WhereClause> {
1460 let has_type_params = input.generics.params.iter().any(|p| matches!(p, GenericParam::Type(_)));
1461 if entries.is_empty() && !has_type_params && extra_bounds.is_empty() {
1462 return input.generics.where_clause.clone();
1463 }
1464
1465 let mut where_clause = input.generics.where_clause.clone().unwrap_or_else(|| WhereClause {
1466 where_token: <Token![where]>::default(),
1467 predicates: Punctuated::new(),
1468 });
1469
1470 for param in &input.generics.params {
1471 if let GenericParam::Type(type_param) = param {
1472 let ident = &type_param.ident;
1473 where_clause.predicates.push(parse_quote!(#ident: 'static));
1474 }
1475 }
1476
1477 let self_ident = &input.ident;
1478 let mut seen_tagged = std::collections::HashSet::new();
1479 for entry in entries {
1480 let ty = entry.ty;
1481 let key = quote!(#ty).to_string();
1486 let self_typed = type_contains_ident(ty, self_ident);
1491 if !self_typed && seen_tagged.insert(key) {
1492 where_clause.predicates.push(parse_quote!(#ty: ::msgpack_tagged::MsgpackTagged));
1493 }
1494 }
1495 for predicate in extra_bounds {
1496 where_clause.predicates.push(predicate.clone());
1497 }
1498 Some(where_clause)
1499}