Skip to main content

neopack_derive/
lib.rs

1//! Derive macros for neopack's `Pack` and `Unpack` traits.
2//!
3//! Instead of writing serialization code by hand,
4//! add `#[derive(Pack, Unpack)]` to your struct or enum.
5//!
6//! Structs encode as a list of their fields:
7//!
8//! ```ignore
9//! #[derive(Pack, Unpack)]
10//! struct Point { x: f64, y: f64 }
11//! ```
12//!
13//! Newtypes (single-field tuple structs) encode as their inner value directly:
14//!
15//! ```ignore
16//! #[derive(Pack, Unpack)]
17//! struct UserId(u64);
18//! ```
19//!
20//! Enums are tagged by variant name. Each variant can be unit, tuple, or struct:
21//!
22//! ```ignore
23//! #[derive(Pack, Unpack)]
24//! enum Shape {
25//!     Empty,
26//!     Circle(f64),
27//!     Rect { w: f64, h: f64 },
28//! }
29//! ```
30//!
31//! Use `#[pack(bytes)]` on a `Vec<u8>` field to encode it as a
32//! byte blob rather than a list of individual bytes:
33//!
34//! ```ignore
35//! #[derive(Pack, Unpack)]
36//! struct Message {
37//!     topic: String,
38//!     #[pack(bytes)]
39//!     payload: Vec<u8>,
40//! }
41//! ```
42
43use proc_macro::TokenStream;
44use quote::quote;
45use syn::Data;
46use syn::DeriveInput;
47use syn::Fields;
48
49#[proc_macro_derive(Pack, attributes(pack))]
50pub fn derive_pack(input: TokenStream) -> TokenStream {
51    let input = syn::parse_macro_input!(input as DeriveInput);
52    match derive_pack_impl(&input) {
53        Ok(ts) => ts.into(),
54        Err(e) => e.to_compile_error().into(),
55    }
56}
57
58#[proc_macro_derive(Unpack, attributes(pack))]
59pub fn derive_unpack(input: TokenStream) -> TokenStream {
60    let input = syn::parse_macro_input!(input as DeriveInput);
61    match derive_unpack_impl(&input) {
62        Ok(ts) => ts.into(),
63        Err(e) => e.to_compile_error().into(),
64    }
65}
66
67fn has_bytes_attr(attrs: &[syn::Attribute]) -> bool {
68    attrs.iter().any(|a| {
69        if !a.path().is_ident("pack") { return false; }
70        let mut found = false;
71        let _ = a.parse_nested_meta(|meta| {
72            if meta.path.is_ident("bytes") { found = true; }
73            Ok(())
74        });
75        found
76    })
77}
78
79// ── Pack ──
80
81fn derive_pack_impl(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
82    let name = &input.ident;
83    let (impl_g, ty_g, where_g) = input.generics.split_for_impl();
84
85    let body = match &input.data {
86        Data::Struct(s) => pack_struct(&s.fields)?,
87        Data::Enum(e) => pack_enum(e)?,
88        Data::Union(_) => return Err(syn::Error::new_spanned(name, "unions are not supported")),
89    };
90
91    Ok(quote! {
92        impl #impl_g neopack::Pack for #name #ty_g #where_g {
93            fn pack(&self, enc: &mut neopack::Encoder) -> neopack::Result<()> {
94                #body
95            }
96        }
97    })
98}
99
100fn pack_struct(fields: &Fields) -> syn::Result<proc_macro2::TokenStream> {
101    match fields {
102        Fields::Named(f) => {
103            let stmts = f.named.iter().map(|field| {
104                let ident = field.ident.as_ref().unwrap();
105                pack_field_expr(quote!(self.#ident), &field.attrs)
106            }).collect::<Vec<_>>();
107            Ok(quote! {
108                enc.list_begin()?;
109                #(#stmts)*
110                enc.list_end()
111            })
112        }
113        Fields::Unnamed(f) if f.unnamed.len() == 1 => {
114            let stmt = pack_field_expr(quote!(self.0), &f.unnamed[0].attrs);
115            Ok(quote! { #stmt Ok(()) })
116        }
117        Fields::Unnamed(f) => {
118            let stmts = f.unnamed.iter().enumerate().map(|(i, field)| {
119                let idx = syn::Index::from(i);
120                pack_field_expr(quote!(self.#idx), &field.attrs)
121            }).collect::<Vec<_>>();
122            Ok(quote! {
123                enc.list_begin()?;
124                #(#stmts)*
125                enc.list_end()
126            })
127        }
128        Fields::Unit => {
129            Ok(quote! { enc.unit() })
130        }
131    }
132}
133
134fn pack_enum(e: &syn::DataEnum) -> syn::Result<proc_macro2::TokenStream> {
135    let arms = e.variants.iter().map(|v| {
136        let vname = &v.ident;
137        let vstr = vname.to_string();
138        match &v.fields {
139            Fields::Unit => {
140                quote! {
141                    Self::#vname => {
142                        enc.variant_begin(#vstr)?;
143                        enc.unit()?;
144                        enc.variant_end()
145                    }
146                }
147            }
148            Fields::Unnamed(f) if f.unnamed.len() == 1 => {
149                let stmt = pack_field_expr(quote!(v0), &f.unnamed[0].attrs);
150                quote! {
151                    Self::#vname(v0) => {
152                        enc.variant_begin(#vstr)?;
153                        #stmt
154                        enc.variant_end()
155                    }
156                }
157            }
158            Fields::Unnamed(f) => {
159                let bindings: Vec<_> = (0..f.unnamed.len())
160                    .map(|i| syn::Ident::new(&format!("v{i}"), proc_macro2::Span::call_site()))
161                    .collect();
162                let stmts: Vec<_> = f.unnamed.iter().enumerate().map(|(i, field)| {
163                    let b = &bindings[i];
164                    pack_field_expr(quote!(#b), &field.attrs)
165                }).collect();
166                quote! {
167                    Self::#vname(#(#bindings),*) => {
168                        enc.variant_begin(#vstr)?;
169                        enc.list_begin()?;
170                        #(#stmts)*
171                        enc.list_end()?;
172                        enc.variant_end()
173                    }
174                }
175            }
176            Fields::Named(f) => {
177                let field_idents: Vec<_> = f.named.iter()
178                    .map(|field| field.ident.as_ref().unwrap())
179                    .collect();
180                let stmts: Vec<_> = f.named.iter().map(|field| {
181                    let ident = field.ident.as_ref().unwrap();
182                    pack_field_expr(quote!(#ident), &field.attrs)
183                }).collect();
184                quote! {
185                    Self::#vname { #(#field_idents),* } => {
186                        enc.variant_begin(#vstr)?;
187                        enc.list_begin()?;
188                        #(#stmts)*
189                        enc.list_end()?;
190                        enc.variant_end()
191                    }
192                }
193            }
194        }
195    }).collect::<Vec<_>>();
196
197    Ok(quote! {
198        match self {
199            #(#arms)*
200        }
201    })
202}
203
204fn pack_field_expr(expr: proc_macro2::TokenStream, attrs: &[syn::Attribute]) -> proc_macro2::TokenStream {
205    if has_bytes_attr(attrs) {
206        quote! { enc.bytes(&#expr)?; }
207    } else {
208        quote! { neopack::Pack::pack(&#expr, enc)?; }
209    }
210}
211
212// ── Unpack ──
213
214fn derive_unpack_impl(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
215    let name = &input.ident;
216    let (impl_g, ty_g, where_g) = input.generics.split_for_impl();
217
218    let body = match &input.data {
219        Data::Struct(s) => unpack_struct(name, &s.fields)?,
220        Data::Enum(e) => unpack_enum(name, e)?,
221        Data::Union(_) => return Err(syn::Error::new_spanned(name, "unions are not supported")),
222    };
223
224    Ok(quote! {
225        impl #impl_g neopack::Unpack for #name #ty_g #where_g {
226            fn unpack(dec: &mut neopack::Decoder<'_>) -> neopack::Result<Self> {
227                #body
228            }
229        }
230    })
231}
232
233fn unpack_struct(name: &syn::Ident, fields: &Fields) -> syn::Result<proc_macro2::TokenStream> {
234    match fields {
235        Fields::Named(f) => {
236            let field_decodes: Vec<_> = f.named.iter().map(|field| {
237                let ident = field.ident.as_ref().unwrap();
238                let ty = &field.ty;
239                let decode = unpack_field_expr(ty, &field.attrs);
240                quote! { let #ident = { let mut d = list.next().ok_or(neopack::Error::UnexpectedEnd)?; #decode }; }
241            }).collect();
242            let field_names: Vec<_> = f.named.iter()
243                .map(|field| field.ident.as_ref().unwrap())
244                .collect();
245            Ok(quote! {
246                let mut list = dec.list()?;
247                #(#field_decodes)*
248                Ok(#name { #(#field_names),* })
249            })
250        }
251        Fields::Unnamed(f) if f.unnamed.len() == 1 => {
252            let ty = &f.unnamed[0].ty;
253            let decode = unpack_field_expr(ty, &f.unnamed[0].attrs);
254            Ok(quote! {
255                let v0 = { let mut d = &mut *dec; #decode };
256                Ok(#name(v0))
257            })
258        }
259        Fields::Unnamed(f) => {
260            let field_decodes: Vec<_> = f.unnamed.iter().enumerate().map(|(i, field)| {
261                let var = syn::Ident::new(&format!("v{i}"), proc_macro2::Span::call_site());
262                let ty = &field.ty;
263                let decode = unpack_field_expr(ty, &field.attrs);
264                quote! { let #var = { let mut d = list.next().ok_or(neopack::Error::UnexpectedEnd)?; #decode }; }
265            }).collect();
266            let vars: Vec<_> = (0..f.unnamed.len())
267                .map(|i| syn::Ident::new(&format!("v{i}"), proc_macro2::Span::call_site()))
268                .collect();
269            Ok(quote! {
270                let mut list = dec.list()?;
271                #(#field_decodes)*
272                Ok(#name(#(#vars),*))
273            })
274        }
275        Fields::Unit => {
276            Ok(quote! { dec.unit()?; Ok(#name) })
277        }
278    }
279}
280
281fn unpack_enum(name: &syn::Ident, e: &syn::DataEnum) -> syn::Result<proc_macro2::TokenStream> {
282    let arms = e.variants.iter().map(|v| {
283        let vname = &v.ident;
284        let vstr = vname.to_string();
285        match &v.fields {
286            Fields::Unit => {
287                quote! { #vstr => { inner.unit()?; Ok(#name::#vname) } }
288            }
289            Fields::Unnamed(f) if f.unnamed.len() == 1 => {
290                let ty = &f.unnamed[0].ty;
291                let decode = unpack_field_expr(ty, &f.unnamed[0].attrs);
292                quote! { #vstr => { let mut d = &mut inner; let v0 = #decode; Ok(#name::#vname(v0)) } }
293            }
294            Fields::Unnamed(f) => {
295                let field_decodes: Vec<_> = f.unnamed.iter().enumerate().map(|(i, field)| {
296                    let var = syn::Ident::new(&format!("v{i}"), proc_macro2::Span::call_site());
297                    let ty = &field.ty;
298                    let decode = unpack_field_expr(ty, &field.attrs);
299                    quote! { let #var = { let mut d = list.next().ok_or(neopack::Error::UnexpectedEnd)?; #decode }; }
300                }).collect();
301                let vars: Vec<_> = (0..f.unnamed.len())
302                    .map(|i| syn::Ident::new(&format!("v{i}"), proc_macro2::Span::call_site()))
303                    .collect();
304                quote! {
305                    #vstr => {
306                        let mut list = inner.list()?;
307                        #(#field_decodes)*
308                        Ok(#name::#vname(#(#vars),*))
309                    }
310                }
311            }
312            Fields::Named(f) => {
313                let field_decodes: Vec<_> = f.named.iter().map(|field| {
314                    let ident = field.ident.as_ref().unwrap();
315                    let ty = &field.ty;
316                    let decode = unpack_field_expr(ty, &field.attrs);
317                    quote! { let #ident = { let mut d = list.next().ok_or(neopack::Error::UnexpectedEnd)?; #decode }; }
318                }).collect();
319                let field_names: Vec<_> = f.named.iter()
320                    .map(|field| field.ident.as_ref().unwrap())
321                    .collect();
322                quote! {
323                    #vstr => {
324                        let mut list = inner.list()?;
325                        #(#field_decodes)*
326                        Ok(#name::#vname { #(#field_names),* })
327                    }
328                }
329            }
330        }
331    }).collect::<Vec<_>>();
332
333    Ok(quote! {
334        let (variant_name, mut inner) = dec.variant()?;
335        match variant_name {
336            #(#arms)*
337            _ => Err(neopack::Error::InvalidTag(0)),
338        }
339    })
340}
341
342fn unpack_field_expr(ty: &syn::Type, attrs: &[syn::Attribute]) -> proc_macro2::TokenStream {
343    if has_bytes_attr(attrs) {
344        quote! { d.bytes()?.to_vec() }
345    } else {
346        quote! { <#ty as neopack::Unpack>::unpack(&mut d)? }
347    }
348}