borsh_derive_internal/
struct_ser.rs

1use core::convert::TryFrom;
2
3use proc_macro2::{Span, TokenStream as TokenStream2};
4use quote::quote;
5use syn::{Fields, Ident, Index, ItemStruct, WhereClause};
6
7use crate::attribute_helpers::contains_skip;
8
9pub fn struct_ser(input: &ItemStruct, cratename: Ident) -> syn::Result<TokenStream2> {
10    let name = &input.ident;
11    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
12    let mut where_clause = where_clause.map_or_else(
13        || WhereClause {
14            where_token: Default::default(),
15            predicates: Default::default(),
16        },
17        Clone::clone,
18    );
19    let mut body = TokenStream2::new();
20    match &input.fields {
21        Fields::Named(fields) => {
22            for field in &fields.named {
23                if contains_skip(&field.attrs) {
24                    continue;
25                }
26                let field_name = field.ident.as_ref().unwrap();
27                let delta = quote! {
28                    #cratename::BorshSerialize::serialize(&self.#field_name, writer)?;
29                };
30                body.extend(delta);
31
32                let field_type = &field.ty;
33                where_clause.predicates.push(
34                    syn::parse2(quote! {
35                        #field_type: #cratename::ser::BorshSerialize
36                    })
37                    .unwrap(),
38                );
39            }
40        }
41        Fields::Unnamed(fields) => {
42            for field_idx in 0..fields.unnamed.len() {
43                let field_idx = Index {
44                    index: u32::try_from(field_idx).expect("up to 2^32 fields are supported"),
45                    span: Span::call_site(),
46                };
47                let delta = quote! {
48                    #cratename::BorshSerialize::serialize(&self.#field_idx, writer)?;
49                };
50                body.extend(delta);
51            }
52        }
53        Fields::Unit => {}
54    }
55    Ok(quote! {
56        impl #impl_generics #cratename::ser::BorshSerialize for #name #ty_generics #where_clause {
57            fn serialize<W: #cratename::maybestd::io::Write>(&self, writer: &mut W) -> ::core::result::Result<(), #cratename::maybestd::io::Error> {
58                #body
59                Ok(())
60            }
61        }
62    })
63}
64
65// Rustfmt removes comas.
66#[rustfmt::skip]
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    fn assert_eq(expected: TokenStream2, actual: TokenStream2) {
72        assert_eq!(expected.to_string(), actual.to_string())
73    }
74
75    #[test]
76    fn simple_struct() {
77        let item_struct: ItemStruct = syn::parse2(quote!{
78            struct A {
79                x: u64,
80                y: String,
81            }
82        }).unwrap();
83
84        let actual = struct_ser(&item_struct, Ident::new("borsh", Span::call_site())).unwrap();
85        let expected = quote!{
86            impl borsh::ser::BorshSerialize for A
87            where
88                u64: borsh::ser::BorshSerialize,
89                String: borsh::ser::BorshSerialize
90            {
91                fn serialize<W: borsh::maybestd::io::Write>(&self, writer: &mut W) -> ::core::result::Result<(), borsh::maybestd::io::Error> {
92                    borsh::BorshSerialize::serialize(&self.x, writer)?;
93                    borsh::BorshSerialize::serialize(&self.y, writer)?;
94                    Ok(())
95                }
96            }
97        };
98        assert_eq(expected, actual);
99    }
100
101    #[test]
102    fn simple_generics() {
103        let item_struct: ItemStruct = syn::parse2(quote!{
104            struct A<K, V> {
105                x: HashMap<K, V>,
106                y: String,
107            }
108        }).unwrap();
109
110        let actual = struct_ser(&item_struct, Ident::new("borsh", Span::call_site())).unwrap();
111        let expected = quote!{
112            impl<K, V> borsh::ser::BorshSerialize for A<K, V>
113            where
114                HashMap<K, V>: borsh::ser::BorshSerialize,
115                String: borsh::ser::BorshSerialize
116            {
117                fn serialize<W: borsh::maybestd::io::Write>(&self, writer: &mut W) -> ::core::result::Result<(), borsh::maybestd::io::Error> {
118                    borsh::BorshSerialize::serialize(&self.x, writer)?;
119                    borsh::BorshSerialize::serialize(&self.y, writer)?;
120                    Ok(())
121                }
122            }
123        };
124        assert_eq(expected, actual);
125    }
126
127    #[test]
128    fn bound_generics() {
129        let item_struct: ItemStruct = syn::parse2(quote!{
130            struct A<K: Key, V> where V: Value {
131                x: HashMap<K, V>,
132                y: String,
133            }
134        }).unwrap();
135
136        let actual = struct_ser(&item_struct, Ident::new("borsh", Span::call_site())).unwrap();
137        let expected = quote!{
138            impl<K: Key, V> borsh::ser::BorshSerialize for A<K, V>
139            where
140                V: Value,
141                HashMap<K, V>: borsh::ser::BorshSerialize,
142                String: borsh::ser::BorshSerialize
143            {
144                fn serialize<W: borsh::maybestd::io::Write>(&self, writer: &mut W) -> ::core::result::Result<(), borsh::maybestd::io::Error> {
145                    borsh::BorshSerialize::serialize(&self.x, writer)?;
146                    borsh::BorshSerialize::serialize(&self.y, writer)?;
147                    Ok(())
148                }
149            }
150        };
151        assert_eq(expected, actual);
152    }
153}