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