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 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}