Skip to main content

delta_pack_derive/
lib.rs

1mod attrs;
2mod codegen;
3mod ir;
4mod type_map;
5
6use proc_macro::TokenStream;
7use syn::{Data, DeriveInput, Fields};
8
9#[proc_macro_derive(DeltaPack, attributes(delta_pack))]
10pub fn derive_delta_pack(input: TokenStream) -> TokenStream {
11    let input = syn::parse_macro_input!(input as DeriveInput);
12    match expand(input) {
13        Ok(ts) => ts.into(),
14        Err(e) => e.to_compile_error().into(),
15    }
16}
17
18fn expand(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
19    match &input.data {
20        Data::Struct(s) => expand_struct(&input, s),
21        Data::Enum(e) => expand_enum(&input, e),
22        Data::Union(_) => Err(syn::Error::new_spanned(
23            &input.ident,
24            "raw `union` types are not supported",
25        )),
26    }
27}
28
29fn expand_struct(
30    input: &DeriveInput,
31    data: &syn::DataStruct,
32) -> syn::Result<proc_macro2::TokenStream> {
33    let name = &input.ident;
34    let fields = match &data.fields {
35        Fields::Named(named) => &named.named,
36        Fields::Unit => {
37            return Err(syn::Error::new_spanned(
38                name,
39                "unit structs are not supported",
40            ));
41        }
42        Fields::Unnamed(_) => {
43            return Err(syn::Error::new_spanned(
44                name,
45                "tuple structs are not supported; use a struct with named fields",
46            ));
47        }
48    };
49
50    let ctx = type_map::TypeCtx { self_name: name };
51    let mut out_fields = Vec::new();
52    for field in fields {
53        let ident = field.ident.clone().expect("named field");
54        let field_attrs = attrs::parse_field_attrs(&field.attrs)?;
55        let ty = type_map::map_field(&field.ty, &field_attrs, &ctx)?;
56        out_fields.push(codegen::Field { ident, ty });
57    }
58
59    Ok(codegen::emit_struct(name, &out_fields))
60}
61
62fn expand_enum(input: &DeriveInput, data: &syn::DataEnum) -> syn::Result<proc_macro2::TokenStream> {
63    let name = &input.ident;
64
65    if data.variants.is_empty() {
66        return Err(syn::Error::new_spanned(
67            name,
68            "empty enums are not supported",
69        ));
70    }
71
72    let all_unit = data
73        .variants
74        .iter()
75        .all(|v| matches!(v.fields, Fields::Unit));
76    let all_tuple = data.variants.iter().all(|v| match &v.fields {
77        Fields::Unnamed(unnamed) => unnamed.unnamed.len() == 1,
78        _ => false,
79    });
80
81    if all_unit {
82        let variants: Vec<syn::Ident> = data.variants.iter().map(|v| v.ident.clone()).collect();
83        Ok(codegen::emit_c_enum(name, &variants))
84    } else if all_tuple {
85        let mut variants = Vec::with_capacity(data.variants.len());
86        for v in &data.variants {
87            let unnamed = match &v.fields {
88                Fields::Unnamed(u) => u,
89                _ => unreachable!(),
90            };
91            let inner = &unnamed.unnamed[0].ty;
92            let path = match inner {
93                syn::Type::Path(p) if p.qself.is_none() => p.path.clone(),
94                _ => {
95                    return Err(syn::Error::new_spanned(
96                        inner,
97                        "union variant must wrap a single named type (e.g. Variant(SomeStruct))",
98                    ));
99                }
100            };
101            variants.push(codegen::Variant {
102                ident: v.ident.clone(),
103                inner_path: path,
104            });
105        }
106        Ok(codegen::emit_union(name, &variants))
107    } else {
108        Err(syn::Error::new_spanned(
109            name,
110            "all variants must be either unit (C-style enum) or single-tuple `Variant(Inner)` (union). Mixed or struct-variants are not supported.",
111        ))
112    }
113}