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