1#![deny(missing_docs)]
22#![forbid(unsafe_code)]
23
24use proc_macro::TokenStream;
25use proc_macro2::TokenStream as TokenStream2;
26use quote::{quote, quote_spanned};
27use syn::spanned::Spanned;
28use syn::{
29 Attribute, Data, DataEnum, DataStruct, DeriveInput, Field, Fields, GenericParam, Generics,
30 Ident, Index, Lifetime, LifetimeParam, parse_macro_input, parse_quote,
31};
32
33#[proc_macro_derive(Serialize, attributes(pack_io))]
45pub fn derive_serialize(input: TokenStream) -> TokenStream {
46 let input = parse_macro_input!(input as DeriveInput);
47 expand_serialize(&input)
48 .unwrap_or_else(syn::Error::into_compile_error)
49 .into()
50}
51
52#[proc_macro_derive(Deserialize, attributes(pack_io))]
56pub fn derive_deserialize(input: TokenStream) -> TokenStream {
57 let input = parse_macro_input!(input as DeriveInput);
58 expand_deserialize(&input)
59 .unwrap_or_else(syn::Error::into_compile_error)
60 .into()
61}
62
63#[proc_macro_derive(DeserializeView, attributes(pack_io))]
76pub fn derive_deserialize_view(input: TokenStream) -> TokenStream {
77 let input = parse_macro_input!(input as DeriveInput);
78 expand_deserialize_view(&input)
79 .unwrap_or_else(syn::Error::into_compile_error)
80 .into()
81}
82
83fn expand_serialize(input: &DeriveInput) -> syn::Result<TokenStream2> {
88 let name = &input.ident;
89 let generics = add_trait_bound(&input.generics, parse_quote!(::pack_io::Serialize));
90 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
91 let type_version = parse_type_attrs(&input.attrs)?;
92
93 let body = match &input.data {
94 Data::Struct(data) => serialize_struct_body(name, data, type_version)?,
95 Data::Enum(data) => {
96 if type_version.is_some() {
97 return Err(syn::Error::new_spanned(
98 name,
99 "pack-io: `#[pack_io(version = N)]` is not supported on enums in this \
100 release; wrap the enum in a versioned newtype struct instead",
101 ));
102 }
103 serialize_enum_body(name, data)?
104 }
105 Data::Union(u) => {
106 return Err(syn::Error::new(
107 u.union_token.span(),
108 "pack-io: `union` is not supported (no defined wire format)",
109 ));
110 }
111 };
112
113 Ok(quote! {
114 #[automatically_derived]
115 impl #impl_generics ::pack_io::Serialize for #name #ty_generics #where_clause {
116 #[inline]
117 fn serialize<__E: ::pack_io::Encode + ?Sized>(
118 &self,
119 __encoder: &mut __E,
120 ) -> ::pack_io::Result<()> {
121 #body
122 ::core::result::Result::Ok(())
123 }
124 }
125 })
126}
127
128fn serialize_struct_body(
129 name: &Ident,
130 data: &DataStruct,
131 type_version: Option<u32>,
132) -> syn::Result<TokenStream2> {
133 let fields_meta = collect_field_meta(&data.fields, quote!(self))?;
135
136 if type_version.is_none() {
139 for fm in &fields_meta {
140 if fm.attrs.since.is_some() || fm.attrs.deprecated.is_some() {
141 return Err(syn::Error::new_spanned(
142 name,
143 "pack-io: `#[pack_io(since = N)]` / `#[pack_io(deprecated = N)]` requires \
144 the struct itself to carry `#[pack_io(version = N)]`",
145 ));
146 }
147 }
148 }
149
150 match type_version {
151 None => {
152 let writes = fields_meta.iter().map(|fm| {
154 let acc = &fm.accessor;
155 quote! { ::pack_io::Serialize::serialize(&#acc, __encoder)?; }
156 });
157 Ok(quote! { #(#writes)* })
158 }
159 Some(version) => {
160 let writes = fields_meta
164 .iter()
165 .filter(|fm| fm.attrs.live_at(version))
166 .map(|fm| {
167 let acc = &fm.accessor;
168 quote! { ::pack_io::Serialize::serialize(&#acc, &mut __body)?; }
169 });
170 Ok(quote! {
171 __encoder.write_varint_u64(#version as u64)?;
172 let mut __body = ::pack_io::Encoder::new();
173 #(#writes)*
174 let __body_bytes = ::pack_io::Encoder::into_inner(__body);
175 __encoder.write_varint_u64(__body_bytes.len() as u64)?;
176 __encoder.write_bytes(&__body_bytes)?;
177 })
178 }
179 }
180}
181
182fn serialize_enum_body(name: &Ident, data: &DataEnum) -> syn::Result<TokenStream2> {
183 if data.variants.is_empty() {
184 return Err(syn::Error::new_spanned(
185 name,
186 "pack-io: empty enums cannot be serialised (no value to encode)",
187 ));
188 }
189
190 let arms: Vec<TokenStream2> = data
191 .variants
192 .iter()
193 .enumerate()
194 .map(|(index, variant)| {
195 let index = u32::try_from(index).expect("u32 enum variants");
196 let var_name = &variant.ident;
197 let bindings = variant_bindings(&variant.fields);
198 let pattern = match &variant.fields {
199 Fields::Named(_) => quote!(Self::#var_name { #(#bindings),* }),
200 Fields::Unnamed(_) => quote!(Self::#var_name(#(#bindings),*)),
201 Fields::Unit => quote!(Self::#var_name),
202 };
203 let writes = bindings.iter().map(|b| {
204 quote! { ::pack_io::Serialize::serialize(#b, __encoder)?; }
205 });
206 quote! {
207 #pattern => {
208 __encoder.write_varint_u64(#index as u64)?;
209 #(#writes)*
210 }
211 }
212 })
213 .collect();
214
215 Ok(quote! {
216 match self {
217 #(#arms)*
218 }
219 })
220}
221
222fn expand_deserialize(input: &DeriveInput) -> syn::Result<TokenStream2> {
227 let name = &input.ident;
228 let generics = add_trait_bound(&input.generics, parse_quote!(::pack_io::Deserialize));
229 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
230 let type_version = parse_type_attrs(&input.attrs)?;
231
232 let body = match &input.data {
233 Data::Struct(data) => deserialize_struct_body(name, &data.fields, type_version)?,
234 Data::Enum(data) => {
235 if type_version.is_some() {
236 return Err(syn::Error::new_spanned(
237 name,
238 "pack-io: `#[pack_io(version = N)]` is not supported on enums in this \
239 release; wrap the enum in a versioned newtype struct instead",
240 ));
241 }
242 deserialize_enum_body(name, data)?
243 }
244 Data::Union(u) => {
245 return Err(syn::Error::new(
246 u.union_token.span(),
247 "pack-io: `union` is not supported",
248 ));
249 }
250 };
251
252 Ok(quote! {
253 #[automatically_derived]
254 impl #impl_generics ::pack_io::Deserialize for #name #ty_generics #where_clause {
255 #[inline]
256 fn deserialize<__D: ::pack_io::Decode + ?Sized>(
257 __decoder: &mut __D,
258 ) -> ::pack_io::Result<Self> {
259 #body
260 }
261 }
262 })
263}
264
265fn deserialize_struct_body(
266 name: &Ident,
267 fields: &Fields,
268 type_version: Option<u32>,
269) -> syn::Result<TokenStream2> {
270 let fields_meta = collect_field_meta(fields, quote!(self))?;
271
272 if type_version.is_none() {
273 for fm in &fields_meta {
274 if fm.attrs.since.is_some() || fm.attrs.deprecated.is_some() {
275 return Err(syn::Error::new_spanned(
276 name,
277 "pack-io: `#[pack_io(since = N)]` / `#[pack_io(deprecated = N)]` requires \
278 the struct itself to carry `#[pack_io(version = N)]`",
279 ));
280 }
281 }
282 }
283
284 match type_version {
285 None => {
286 let constructor = construct_from_fields(quote!(#name), fields, |ty| {
288 quote_spanned! { ty.span() =>
289 <#ty as ::pack_io::Deserialize>::deserialize(__decoder)?
290 }
291 });
292 Ok(quote! { ::core::result::Result::Ok(#constructor) })
293 }
294 Some(_) => {
295 let field_inits: Vec<TokenStream2> = fields_meta
304 .iter()
305 .enumerate()
306 .map(|(i, fm)| {
307 let var = field_local_ident(fm, i);
308 let ty = &fm.ty;
309 let always_live =
310 fm.attrs.since.unwrap_or(1) == 1 && fm.attrs.deprecated.is_none();
311 if always_live {
312 quote! {
313 let #var: #ty =
314 <#ty as ::pack_io::Deserialize>::deserialize(&mut __body_dec)?;
315 }
316 } else {
317 let since = fm.attrs.since.unwrap_or(1);
318 let deprecated_check = match fm.attrs.deprecated {
319 Some(d) => quote! { __version < (#d as u32) },
320 None => quote! { true },
321 };
322 quote! {
323 let #var: #ty = if (#since as u32) <= __version
324 && #deprecated_check
325 {
326 <#ty as ::pack_io::Deserialize>::deserialize(&mut __body_dec)?
327 } else {
328 ::core::default::Default::default()
329 };
330 }
331 }
332 })
333 .collect();
334
335 let constructor = match fields {
336 Fields::Named(_) => {
337 let pairs = fields_meta.iter().enumerate().map(|(i, fm)| {
338 let name = fm.field_ident.as_ref().expect("named");
339 let var = field_local_ident(fm, i);
340 quote! { #name: #var }
341 });
342 quote! { #name { #(#pairs),* } }
343 }
344 Fields::Unnamed(_) => {
345 let positions = fields_meta
346 .iter()
347 .enumerate()
348 .map(|(i, fm)| field_local_ident(fm, i));
349 quote! { #name ( #(#positions),* ) }
350 }
351 Fields::Unit => quote! { #name },
352 };
353
354 Ok(quote! {
355 let __version_u64 = __decoder.read_varint_u64()?;
356 let __version = u32::try_from(__version_u64)
357 .map_err(|_| ::pack_io::SerialError::IntegerOutOfRange)?;
358 let __body_len_u64 = __decoder.read_varint_u64()?;
359 let __max = ::pack_io::Decode::max_alloc(__decoder) as u64;
360 if __body_len_u64 > __max {
361 return ::core::result::Result::Err(::pack_io::SerialError::InvalidLength {
362 declared: __body_len_u64,
363 remaining: 0,
364 });
365 }
366 let __body_len = __body_len_u64 as usize;
367 let mut __body_buf = ::std::vec![0u8; __body_len];
368 __decoder.read_into(&mut __body_buf)?;
369 let mut __body_dec = ::pack_io::Decoder::new(&__body_buf);
370 #(#field_inits)*
371 ::core::result::Result::Ok(#constructor)
372 })
373 }
374 }
375}
376
377fn deserialize_view_enum_body(
384 name: &Ident,
385 data: &DataEnum,
386 lifetime: &Lifetime,
387) -> syn::Result<TokenStream2> {
388 if data.variants.is_empty() {
389 return Err(syn::Error::new_spanned(
390 name,
391 "pack-io: empty enums cannot be deserialised",
392 ));
393 }
394
395 let arms: Vec<TokenStream2> = data
396 .variants
397 .iter()
398 .enumerate()
399 .map(|(index, variant)| {
400 let index = u32::try_from(index).expect("u32 enum variants");
401 let var_name = &variant.ident;
402 let constructor =
403 construct_from_fields(quote!(#name :: #var_name), &variant.fields, |ty| {
404 quote_spanned! { ty.span() =>
405 <#ty as ::pack_io::DeserializeView<#lifetime>>::deserialize_view(__decoder)?
406 }
407 });
408 quote! { #index => ::core::result::Result::Ok(#constructor), }
409 })
410 .collect();
411
412 let enum_name = name.to_string();
413 Ok(quote! {
414 let __tag = ::pack_io::Decode::read_varint_u64(__decoder)?;
418 let __idx = u32::try_from(__tag)
419 .map_err(|_| ::pack_io::SerialError::UnknownVariant {
420 kind: #enum_name,
421 index: u64::MAX,
422 })?;
423 match __idx {
424 #(#arms)*
425 other => ::core::result::Result::Err(::pack_io::SerialError::UnknownVariant {
426 kind: #enum_name,
427 index: u64::from(other),
428 }),
429 }
430 })
431}
432
433fn field_local_ident(fm: &FieldMeta<'_>, index: usize) -> Ident {
434 match &fm.field_ident {
435 Some(id) => Ident::new(&format!("__f_{}", id), id.span()),
436 None => Ident::new(&format!("__f_{}", index), fm.ty.span()),
437 }
438}
439
440fn deserialize_enum_body(name: &Ident, data: &DataEnum) -> syn::Result<TokenStream2> {
441 if data.variants.is_empty() {
442 return Err(syn::Error::new_spanned(
443 name,
444 "pack-io: empty enums cannot be deserialised",
445 ));
446 }
447
448 let arms: Vec<TokenStream2> = data
449 .variants
450 .iter()
451 .enumerate()
452 .map(|(index, variant)| {
453 let index = u32::try_from(index).expect("u32 enum variants");
454 let var_name = &variant.ident;
455 let constructor =
456 construct_from_fields(quote!(#name :: #var_name), &variant.fields, |ty| {
457 quote_spanned! { ty.span() =>
458 <#ty as ::pack_io::Deserialize>::deserialize(__decoder)?
459 }
460 });
461 quote! { #index => ::core::result::Result::Ok(#constructor), }
462 })
463 .collect();
464
465 let enum_name = name.to_string();
466 Ok(quote! {
467 let __tag = __decoder.read_varint_u64()?;
468 let __idx = u32::try_from(__tag)
469 .map_err(|_| ::pack_io::SerialError::UnknownVariant {
470 kind: #enum_name,
471 index: u64::MAX,
472 })?;
473 match __idx {
474 #(#arms)*
475 other => ::core::result::Result::Err(::pack_io::SerialError::UnknownVariant {
476 kind: #enum_name,
477 index: u64::from(other),
478 }),
479 }
480 })
481}
482
483fn expand_deserialize_view(input: &DeriveInput) -> syn::Result<TokenStream2> {
488 let name = &input.ident;
489
490 let lifetime = extract_single_lifetime(&input.generics).ok_or_else(|| {
491 syn::Error::new_spanned(
492 &input.generics,
493 "pack-io: `DeserializeView` requires the type to have exactly one lifetime parameter \
494 (used as the borrow of the input buffer)",
495 )
496 })?;
497
498 let generics = add_trait_bound_with_lifetime(
499 &input.generics,
500 parse_quote!(::pack_io::DeserializeView<#lifetime>),
501 &lifetime,
502 );
503 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
504
505 let body = match &input.data {
506 Data::Struct(data) => {
507 let constructor = construct_from_fields(quote!(#name), &data.fields, |ty| {
508 quote_spanned! { ty.span() =>
509 <#ty as ::pack_io::DeserializeView<#lifetime>>::deserialize_view(__decoder)?
510 }
511 });
512 quote! { ::core::result::Result::Ok(#constructor) }
513 }
514 Data::Enum(data) => deserialize_view_enum_body(name, data, &lifetime)?,
515 Data::Union(u) => {
516 return Err(syn::Error::new(
517 u.union_token.span(),
518 "pack-io: `union` is not supported",
519 ));
520 }
521 };
522
523 Ok(quote! {
524 #[automatically_derived]
525 impl #impl_generics ::pack_io::DeserializeView<#lifetime> for #name #ty_generics #where_clause {
526 #[inline]
527 fn deserialize_view(
528 __decoder: &mut ::pack_io::Decoder<#lifetime>,
529 ) -> ::pack_io::Result<Self> {
530 #body
531 }
532 }
533 })
534}
535
536#[derive(Default, Clone, Copy)]
542struct SchemaAttrs {
543 since: Option<u32>,
546 deprecated: Option<u32>,
549}
550
551impl SchemaAttrs {
552 fn live_at(self, version: u32) -> bool {
554 let since = self.since.unwrap_or(1);
555 if version < since {
556 return false;
557 }
558 match self.deprecated {
559 Some(d) => version < d,
560 None => true,
561 }
562 }
563}
564
565struct FieldMeta<'a> {
567 accessor: TokenStream2,
569 field_ident: Option<&'a Ident>,
571 ty: &'a syn::Type,
573 attrs: SchemaAttrs,
575}
576
577fn parse_type_attrs(attrs: &[Attribute]) -> syn::Result<Option<u32>> {
580 let mut version: Option<u32> = None;
581 for attr in attrs {
582 if !attr.path().is_ident("pack_io") {
583 continue;
584 }
585 attr.parse_nested_meta(|meta| {
586 if meta.path.is_ident("version") {
587 let lit: syn::LitInt = meta.value()?.parse()?;
588 let v: u32 = lit.base10_parse()?;
589 if v == 0 {
590 return Err(meta.error("pack-io: schema versions start at 1, not 0"));
591 }
592 version = Some(v);
593 Ok(())
594 } else if meta.path.is_ident("since") || meta.path.is_ident("deprecated") {
595 Err(meta.error(
596 "pack-io: `since` / `deprecated` are field-level attributes; only \
597 `version` is allowed on the type",
598 ))
599 } else {
600 Err(meta.error("pack-io: unknown attribute (expected `version`)"))
601 }
602 })?;
603 }
604 Ok(version)
605}
606
607fn parse_field_attrs(attrs: &[Attribute]) -> syn::Result<SchemaAttrs> {
609 let mut out = SchemaAttrs::default();
610 for attr in attrs {
611 if !attr.path().is_ident("pack_io") {
612 continue;
613 }
614 attr.parse_nested_meta(|meta| {
615 if meta.path.is_ident("since") {
616 let lit: syn::LitInt = meta.value()?.parse()?;
617 let v: u32 = lit.base10_parse()?;
618 if v == 0 {
619 return Err(meta.error("pack-io: schema versions start at 1, not 0"));
620 }
621 out.since = Some(v);
622 Ok(())
623 } else if meta.path.is_ident("deprecated") {
624 let lit: syn::LitInt = meta.value()?.parse()?;
625 let v: u32 = lit.base10_parse()?;
626 if v == 0 {
627 return Err(meta.error("pack-io: schema versions start at 1, not 0"));
628 }
629 out.deprecated = Some(v);
630 Ok(())
631 } else if meta.path.is_ident("version") {
632 Err(meta.error(
633 "pack-io: `version` is a type-level attribute; use `since` / `deprecated` \
634 on individual fields",
635 ))
636 } else {
637 Err(meta.error("pack-io: unknown attribute (expected `since` or `deprecated`)"))
638 }
639 })?;
640 }
641 if let (Some(since), Some(dep)) = (out.since, out.deprecated) {
642 if dep <= since {
643 return Err(syn::Error::new(
644 attrs[0].span(),
645 format!(
646 "pack-io: `deprecated = {dep}` must be strictly greater than \
647 `since = {since}` — a field cannot be removed before it is introduced",
648 ),
649 ));
650 }
651 }
652 Ok(out)
653}
654
655fn collect_field_meta(fields: &Fields, owner: TokenStream2) -> syn::Result<Vec<FieldMeta<'_>>> {
657 let mut out = Vec::new();
658 match fields {
659 Fields::Named(named) => {
660 for f in &named.named {
661 let ident = f.ident.as_ref().expect("named fields have idents");
662 out.push(FieldMeta {
663 accessor: quote!(#owner.#ident),
664 field_ident: Some(ident),
665 ty: &f.ty,
666 attrs: parse_field_attrs(&f.attrs)?,
667 });
668 }
669 }
670 Fields::Unnamed(unnamed) => {
671 for (i, f) in unnamed.unnamed.iter().enumerate() {
672 let idx = Index::from(i);
673 out.push(FieldMeta {
674 accessor: quote!(#owner.#idx),
675 field_ident: None,
676 ty: &f.ty,
677 attrs: parse_field_attrs(&f.attrs)?,
678 });
679 }
680 }
681 Fields::Unit => {}
682 }
683 Ok(out)
684}
685
686fn variant_bindings(fields: &Fields) -> Vec<TokenStream2> {
692 match fields {
693 Fields::Named(named) => named
694 .named
695 .iter()
696 .map(|f| {
697 let name = f.ident.as_ref().expect("named fields have idents");
698 quote!(#name)
699 })
700 .collect(),
701 Fields::Unnamed(unnamed) => unnamed
702 .unnamed
703 .iter()
704 .enumerate()
705 .map(|(i, _)| {
706 let id = Ident::new(&format!("__f{i}"), unnamed.unnamed[i].span());
707 quote!(#id)
708 })
709 .collect(),
710 Fields::Unit => Vec::new(),
711 }
712}
713
714fn construct_from_fields<F>(path: TokenStream2, fields: &Fields, mut gen_expr: F) -> TokenStream2
718where
719 F: FnMut(&syn::Type) -> TokenStream2,
720{
721 match fields {
722 Fields::Named(named) => {
723 let pieces = named.named.iter().map(|f: &Field| {
724 let name = f.ident.as_ref().expect("named fields have idents");
725 let expr = gen_expr(&f.ty);
726 quote! { #name: #expr }
727 });
728 quote! { #path { #(#pieces),* } }
729 }
730 Fields::Unnamed(unnamed) => {
731 let pieces = unnamed.unnamed.iter().map(|f| gen_expr(&f.ty));
732 quote! { #path ( #(#pieces),* ) }
733 }
734 Fields::Unit => quote! { #path },
735 }
736}
737
738fn add_trait_bound(generics: &Generics, bound: syn::TypeParamBound) -> Generics {
741 let mut generics = generics.clone();
742 for param in &mut generics.params {
743 if let GenericParam::Type(t) = param {
744 t.bounds.push(bound.clone());
745 }
746 }
747 generics
748}
749
750fn add_trait_bound_with_lifetime(
754 generics: &Generics,
755 bound: syn::TypeParamBound,
756 _lifetime: &Lifetime,
757) -> Generics {
758 let mut generics = generics.clone();
759 for param in &mut generics.params {
760 if let GenericParam::Type(t) = param {
761 t.bounds.push(bound.clone());
762 }
763 }
764 generics
765}
766
767fn extract_single_lifetime(generics: &Generics) -> Option<Lifetime> {
770 let lifetimes: Vec<&LifetimeParam> = generics
771 .params
772 .iter()
773 .filter_map(|p| {
774 if let GenericParam::Lifetime(l) = p {
775 Some(l)
776 } else {
777 None
778 }
779 })
780 .collect();
781 if lifetimes.len() == 1 {
782 Some(lifetimes[0].lifetime.clone())
783 } else {
784 None
785 }
786}