alloy_rlp_derive/
en.rs

1use crate::utils::{
2    attributes_include, field_ident, is_optional, make_generics, parse_struct, EMPTY_STRING_CODE,
3};
4use proc_macro2::TokenStream;
5use quote::quote;
6use std::iter::Peekable;
7use syn::{Error, Result};
8
9pub(crate) fn impl_encodable(ast: &syn::DeriveInput) -> Result<TokenStream> {
10    let body = parse_struct(ast, "RlpEncodable")?;
11
12    let mut fields = body
13        .fields
14        .iter()
15        .enumerate()
16        .filter(|(_, field)| !attributes_include(&field.attrs, "skip"))
17        .peekable();
18
19    let supports_trailing_opt = attributes_include(&ast.attrs, "trailing");
20
21    let mut encountered_opt_item = false;
22    let mut length_exprs = Vec::with_capacity(body.fields.len());
23    let mut encode_exprs = Vec::with_capacity(body.fields.len());
24
25    while let Some((i, field)) = fields.next() {
26        let is_opt = is_optional(field);
27        if is_opt {
28            if !supports_trailing_opt {
29                let msg = "optional fields are disabled.\nAdd the `#[rlp(trailing)]` attribute to the struct in order to enable optional fields";
30                return Err(Error::new_spanned(field, msg));
31            }
32            encountered_opt_item = true;
33        } else if encountered_opt_item {
34            let msg = "all the fields after the first optional field must be optional";
35            return Err(Error::new_spanned(field, msg));
36        }
37
38        length_exprs.push(encodable_length(i, field, is_opt, fields.clone()));
39        encode_exprs.push(encodable_field(i, field, is_opt, fields.clone()));
40    }
41
42    let name = &ast.ident;
43    let generics = make_generics(&ast.generics, quote!(alloy_rlp::Encodable));
44    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
45
46    Ok(quote! {
47        const _: () = {
48            extern crate alloy_rlp;
49
50            impl #impl_generics alloy_rlp::Encodable for #name #ty_generics #where_clause {
51                #[inline]
52                fn length(&self) -> usize {
53                    let payload_length = self._alloy_rlp_payload_length();
54                    payload_length + alloy_rlp::length_of_length(payload_length)
55                }
56
57                #[inline]
58                fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
59                    alloy_rlp::Header {
60                        list: true,
61                        payload_length: self._alloy_rlp_payload_length(),
62                    }
63                    .encode(out);
64                    #(#encode_exprs)*
65                }
66            }
67
68            impl #impl_generics #name #ty_generics #where_clause {
69                #[allow(unused_parens)]
70                #[inline]
71                fn _alloy_rlp_payload_length(&self) -> usize {
72                    0usize #( + #length_exprs)*
73                }
74            }
75        };
76    })
77}
78
79pub(crate) fn impl_encodable_wrapper(ast: &syn::DeriveInput) -> Result<TokenStream> {
80    let body = parse_struct(ast, "RlpEncodableWrapper")?;
81
82    let name = &ast.ident;
83    let generics = make_generics(&ast.generics, quote!(alloy_rlp::Encodable));
84    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
85
86    let ident = {
87        let fields: Vec<_> = body.fields.iter().collect();
88        if let [field] = fields[..] {
89            field_ident(0, field)
90        } else {
91            let msg = "`RlpEncodableWrapper` is only derivable for structs with one field";
92            return Err(Error::new(name.span(), msg));
93        }
94    };
95
96    Ok(quote! {
97        const _: () = {
98            extern crate alloy_rlp;
99
100            impl #impl_generics alloy_rlp::Encodable for #name #ty_generics #where_clause {
101                #[inline]
102                fn length(&self) -> usize {
103                    alloy_rlp::Encodable::length(&self.#ident)
104                }
105
106                #[inline]
107                fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
108                    alloy_rlp::Encodable::encode(&self.#ident, out)
109                }
110            }
111        };
112    })
113}
114
115pub(crate) fn impl_max_encoded_len(ast: &syn::DeriveInput) -> Result<TokenStream> {
116    let body = parse_struct(ast, "RlpMaxEncodedLen")?;
117
118    let tys = body
119        .fields
120        .iter()
121        .filter(|field| !attributes_include(&field.attrs, "skip"))
122        .map(|field| &field.ty);
123
124    let name = &ast.ident;
125
126    let generics = make_generics(&ast.generics, quote!(alloy_rlp::MaxEncodedLenAssoc));
127    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
128
129    let imp = quote! {{
130        let _sz = 0usize #( + <#tys as alloy_rlp::MaxEncodedLenAssoc>::LEN )*;
131        _sz + alloy_rlp::length_of_length(_sz)
132    }};
133
134    // can't do operations with const generic params / associated consts in the
135    // non-associated impl
136    let can_derive_non_assoc = ast
137        .generics
138        .params
139        .iter()
140        .all(|g| !matches!(g, syn::GenericParam::Type(_) | syn::GenericParam::Const(_)));
141    let non_assoc_impl =  can_derive_non_assoc.then(|| {
142        quote! {
143            unsafe impl #impl_generics alloy_rlp::MaxEncodedLen<#imp> for #name #ty_generics #where_clause {}
144        }
145    });
146
147    Ok(quote! {
148        #[allow(unsafe_code)]
149        const _: () = {
150            extern crate alloy_rlp;
151
152            #non_assoc_impl
153
154            unsafe impl #impl_generics alloy_rlp::MaxEncodedLenAssoc for #name #ty_generics #where_clause {
155                const LEN: usize = #imp;
156            }
157        };
158    })
159}
160
161fn encodable_length<'a>(
162    index: usize,
163    field: &syn::Field,
164    is_opt: bool,
165    mut remaining: Peekable<impl Iterator<Item = (usize, &'a syn::Field)>>,
166) -> TokenStream {
167    let ident = field_ident(index, field);
168
169    if is_opt {
170        let default = if remaining.peek().is_some() {
171            let condition = remaining_opt_fields_some_condition(remaining);
172            quote! { (#condition) as usize }
173        } else {
174            quote! { 0 }
175        };
176
177        quote! { self.#ident.as_ref().map(|val| alloy_rlp::Encodable::length(val)).unwrap_or(#default) }
178    } else {
179        quote! { alloy_rlp::Encodable::length(&self.#ident) }
180    }
181}
182
183fn encodable_field<'a>(
184    index: usize,
185    field: &syn::Field,
186    is_opt: bool,
187    mut remaining: Peekable<impl Iterator<Item = (usize, &'a syn::Field)>>,
188) -> TokenStream {
189    let ident = field_ident(index, field);
190
191    if is_opt {
192        let if_some_encode = quote! {
193            if let Some(val) = self.#ident.as_ref() {
194                alloy_rlp::Encodable::encode(val, out)
195            }
196        };
197
198        if remaining.peek().is_some() {
199            let condition = remaining_opt_fields_some_condition(remaining);
200            quote! {
201                #if_some_encode
202                else if #condition {
203                    out.put_u8(#EMPTY_STRING_CODE);
204                }
205            }
206        } else {
207            quote! { #if_some_encode }
208        }
209    } else {
210        quote! { alloy_rlp::Encodable::encode(&self.#ident, out); }
211    }
212}
213
214fn remaining_opt_fields_some_condition<'a>(
215    remaining: impl Iterator<Item = (usize, &'a syn::Field)>,
216) -> TokenStream {
217    let conditions = remaining.map(|(index, field)| {
218        let ident = field_ident(index, field);
219        quote! { self.#ident.is_some() }
220    });
221    quote! { #(#conditions)||* }
222}