Skip to main content

borsh_incremental/
lib.rs

1use proc_macro::TokenStream;
2use quote::{format_ident, quote};
3use syn::{parse_macro_input, Attribute, Data, DeriveInput, Expr, Fields, Path};
4
5#[proc_macro_derive(BorshDeserializeIncremental, attributes(incremental))]
6pub fn derive(input: TokenStream) -> TokenStream {
7    // Parse input token stream into a syntactic representation of the struct
8    let input = parse_macro_input!(input as DeriveInput);
9    let ident = input.ident;
10    let (impl_g, ty_g, where_c) = input.generics.split_for_impl();
11
12    // Parse the struct-level #[incremental(error = ...)] attribute
13    let mut error_ty: Option<Path> = None;
14    for a in &input.attrs {
15        if a.path().is_ident("incremental") {
16            let _ = a.parse_nested_meta(|meta| {
17                if meta.path.is_ident("error") {
18                    let value: Expr = meta.value()?.parse()?;
19                    if let Expr::Path(p) = value {
20                        error_ty = Some(p.path);
21                    } else {
22                        return Err(meta.error("expected path for error type"));
23                    }
24                }
25                Ok(())
26            });
27        }
28    }
29    // Default to std::io::Error if not provided
30    let error_ty = error_ty.unwrap_or_else(|| syn::parse_quote!(::std::io::Error));
31
32    // Extract named fields (only support structs with named fields)
33    let fields = match input.data {
34        Data::Struct(s) => match s.fields {
35            Fields::Named(n) => n.named,
36            _ => panic!("BorshDeserializeIncremental only supports named structs"),
37        },
38        _ => panic!("BorshDeserializeIncremental only supports structs"),
39    };
40
41    // Parse per-field #[incremental(...)] attributes: deser_with, default
42    fn parse_field_attrs(attrs: &[Attribute]) -> (Option<Path>, Option<Expr>) {
43        let mut deser_with: Option<Path> = None;
44        let mut default_expr: Option<Expr> = None;
45        for a in attrs {
46            if a.path().is_ident("incremental") {
47                let _ = a.parse_nested_meta(|meta| {
48                    if meta.path.is_ident("deser_with") {
49                        let value: Expr = meta.value()?.parse()?;
50                        if let Expr::Path(p) = value {
51                            deser_with = Some(p.path);
52                        }
53                    } else if meta.path.is_ident("default") {
54                        let value: Expr = meta.value()?.parse()?;
55                        default_expr = Some(value);
56                    }
57                    Ok(())
58                });
59            }
60        }
61        (deser_with, default_expr)
62    }
63
64    // Collect parsed field info
65    struct F {
66        name: syn::Ident,
67        ty: syn::Type,
68        deser_with: Option<Path>,
69        default_expr: Option<Expr>,
70    }
71    let mut fdefs = Vec::new();
72    for f in fields {
73        let name = f.ident.unwrap();
74        let ty = f.ty;
75        let (deser_with, default_expr) = parse_field_attrs(&f.attrs);
76        fdefs.push(F {
77            name,
78            ty,
79            deser_with,
80            default_expr,
81        });
82    }
83
84    // Create unique temporary variable names for each field
85    let temps: Vec<_> = fdefs
86        .iter()
87        .map(|f| format_ident!("__tmp_{}", f.name))
88        .collect();
89
90    // Generate deserialization logic for each field
91    // - Use deser_with if provided
92    // - Otherwise fall back to normal BorshDeserialize
93    // - On error or EOF, substitute the configured default or Default::default()
94    let reads = fdefs.iter().zip(&temps).map(|(f, tmp)| {
95        let ty = &f.ty;
96        let fallback = f
97            .default_expr
98            .as_ref()
99            .map(|e| quote!(#e))
100            .unwrap_or_else(|| quote!(<#ty as ::core::default::Default>::default()));
101
102        if let Some(dw) = &f.deser_with {
103            quote! {
104                let before_len = data.len();
105                let #tmp: #ty = match (#dw)(&mut data) {
106                    Ok(v) => v,
107                    Err(e_any) => {
108                        let after_len = data.len();
109                        if after_len == before_len {
110                            #fallback
111                        } else {
112                            let e: ::std::io::Error = ::core::convert::Into::into(e_any);
113                            return Err(<#error_ty as ::core::convert::From<::std::io::Error>>::from(e));
114                        }
115                    }
116                };
117            }
118        } else {
119            quote! {
120                let before_len = data.len();
121                let #tmp: #ty = match <#ty as ::borsh::BorshDeserialize>::deserialize(&mut data) {
122                    Ok(v) => v,
123                    Err(e) => {
124                        let after_len = data.len();
125                        if after_len == before_len {
126                            #fallback
127                        } else {
128                            return Err(<#error_ty as ::core::convert::From<::std::io::Error>>::from(e));
129                        }
130                    }
131                };
132            }
133        }
134    });
135
136    // Assign all temporary variables back into struct fields
137    let assigns = fdefs.iter().zip(&temps).map(|(f, tmp)| {
138        let name = &f.name;
139        quote!(#name: #tmp)
140    });
141
142    // Generate TryFrom<&[u8]> impl using the incremental decoding logic
143    let expanded = quote! {
144        impl #impl_g ::core::convert::TryFrom<&[u8]> for #ident #ty_g #where_c {
145            type Error = #error_ty;
146            fn try_from(mut data: &[u8]) -> ::core::result::Result<Self, Self::Error> {
147                #( #reads )*
148                Ok(Self { #( #assigns ),* })
149            }
150        }
151    };
152
153    TokenStream::from(expanded)
154}