1#![deny(clippy::pedantic)]
2#![cfg_attr(feature = "diagnostics", feature(proc_macro_diagnostic))]
3
4use itertools::Itertools;
5use proc_macro::TokenStream;
6use proc_macro2::{Ident, TokenStream as TokenStream2};
7use quote::{format_ident, quote};
8use syn::punctuated::Punctuated;
9use syn::token::Comma;
10use syn::{
11 Attribute, Data, DataEnum, DataStruct, DeriveInput, Fields, Index, Lit, Token, Variant,
12 parse_macro_input,
13};
14
15fn is_default_variant_enforce_valid(variant: &Variant) -> bool {
16 let is_default = variant
17 .attrs
18 .iter()
19 .any(|attr| attr.path().is_ident("encodable_default"));
20
21 if is_default {
22 assert_eq!(
23 variant.ident.to_string(),
24 "Default",
25 "Default variant should be called `Default`"
26 );
27 let two_fields = variant.fields.len() == 2;
28 let field_names = variant
29 .fields
30 .iter()
31 .filter_map(|field| field.ident.as_ref().map(ToString::to_string))
32 .sorted()
33 .collect::<Vec<_>>();
34 let correct_fields = field_names == vec!["bytes".to_string(), "variant".to_string()];
35
36 assert!(
37 two_fields && correct_fields,
38 "The default variant should have exactly two field: `variant: u64` and `bytes: Vec<u8>`"
39 );
40 }
41
42 is_default
43}
44
45#[proc_macro_derive(Encodable, attributes(encodable_default, encodable))]
48pub fn derive_encodable(input: TokenStream) -> TokenStream {
49 let DeriveInput {
50 ident,
51 data,
52 generics,
53 ..
54 } = parse_macro_input!(input);
55
56 let encode_inner = match data {
57 Data::Struct(DataStruct { fields, .. }) => derive_struct_encode(&fields),
58 Data::Enum(DataEnum { variants, .. }) => derive_enum_encode(&ident, &variants),
59 Data::Union(_) => error(&ident, "Encodable can't be derived for unions"),
60 };
61 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
62
63 let output = quote! {
64 impl #impl_generics ::fedimint_core::encoding::Encodable for #ident #ty_generics #where_clause {
65 #[allow(deprecated)]
66 fn consensus_encode<W: std::io::Write>(&self, mut writer: &mut W) -> std::result::Result<(), std::io::Error> {
67 #encode_inner
68 }
69 }
70 };
71
72 output.into()
73}
74
75fn derive_struct_encode(fields: &Fields) -> TokenStream2 {
76 if is_tuple_struct(fields) {
77 let field_names = fields
79 .iter()
80 .enumerate()
81 .map(|(idx, _)| Index::from(idx))
82 .collect::<Vec<_>>();
83 quote! {
84 #(::fedimint_core::encoding::Encodable::consensus_encode(&self.#field_names, writer)?;)*
85 Ok(())
86 }
87 } else {
88 let field_names = fields
90 .iter()
91 .map(|field| field.ident.clone().unwrap())
92 .collect::<Vec<_>>();
93 quote! {
94 #(::fedimint_core::encoding::Encodable::consensus_encode(&self.#field_names, writer)?;)*
95 Ok(())
96 }
97 }
98}
99
100fn parse_index_attribute(attributes: &[Attribute]) -> Option<u64> {
103 attributes.iter().find_map(|attr| {
104 if attr.path().is_ident("encodable") {
105 attr.parse_args_with(|input: syn::parse::ParseStream| {
106 input.parse::<syn::Ident>()?.span(); input.parse::<Token![=]>()?; if let Lit::Int(lit_int) = input.parse::<Lit>()? {
109 lit_int.base10_parse()
110 } else {
111 Err(input.error("Expected an integer for 'index'"))
112 }
113 })
114 .ok()
115 } else {
116 None
117 }
118 })
119}
120
121fn extract_variants_with_indices(input_variants: Vec<Variant>) -> Vec<(Option<u64>, Variant)> {
124 input_variants
125 .into_iter()
126 .map(|variant| {
127 let index = parse_index_attribute(&variant.attrs);
128 (index, variant)
129 })
130 .collect()
131}
132
133fn non_default_variant_indices(variants: &Punctuated<Variant, Comma>) -> Vec<(u64, Variant)> {
134 let non_default_variants = variants
135 .into_iter()
136 .filter(|variant| !is_default_variant_enforce_valid(variant))
137 .cloned()
138 .collect::<Vec<_>>();
139
140 let attr_indices = extract_variants_with_indices(non_default_variants.clone());
141
142 let all_have_index = attr_indices.iter().all(|(idx, _)| idx.is_some());
143 let none_have_index = attr_indices.iter().all(|(idx, _)| idx.is_none());
144
145 assert!(
146 all_have_index || none_have_index,
147 "Either all or none of the variants should have an index annotation"
148 );
149
150 if all_have_index {
151 attr_indices
152 .into_iter()
153 .map(|(idx, variant)| (idx.expect("We made sure everything has an index"), variant))
154 .collect()
155 } else {
156 non_default_variants
157 .into_iter()
158 .enumerate()
159 .map(|(idx, variant)| (idx as u64, variant))
160 .collect()
161 }
162}
163
164fn derive_enum_encode(ident: &Ident, variants: &Punctuated<Variant, Comma>) -> TokenStream2 {
165 if variants.is_empty() {
166 return quote! {
167 match *self {}
168 };
169 }
170
171 let non_default_match_arms =
172 non_default_variant_indices(variants)
173 .into_iter()
174 .map(|(variant_idx, variant)| {
175 let variant_ident = variant.ident.clone();
176
177 if is_tuple_struct(&variant.fields) {
178 let variant_fields = variant
179 .fields
180 .iter()
181 .enumerate()
182 .map(|(idx, _)| format_ident!("bound_{}", idx))
183 .collect::<Vec<_>>();
184 let variant_encode_block =
185 derive_enum_variant_encode_block(variant_idx, &variant_fields);
186 quote! {
187 #ident::#variant_ident(#(#variant_fields,)*) => {
188 #variant_encode_block
189 }
190 }
191 } else {
192 let variant_fields = variant
193 .fields
194 .iter()
195 .map(|field| field.ident.clone().unwrap())
196 .collect::<Vec<_>>();
197 let variant_encode_block =
198 derive_enum_variant_encode_block(variant_idx, &variant_fields);
199 quote! {
200 #ident::#variant_ident { #(#variant_fields,)*} => {
201 #variant_encode_block
202 }
203 }
204 }
205 });
206
207 let default_match_arm = variants
208 .iter()
209 .find(|variant| is_default_variant_enforce_valid(variant))
210 .map(|_variant| {
211 quote! {
212 #ident::Default { variant, bytes } => {
213 ::fedimint_core::encoding::Encodable::consensus_encode(variant, writer)?;
214 ::fedimint_core::encoding::Encodable::consensus_encode(bytes, writer)?;
215 }
216 }
217 });
218
219 let match_arms = non_default_match_arms.chain(default_match_arm);
220
221 quote! {
222 match self {
223 #(#match_arms)*
224 }
225 Ok(())
226 }
227}
228
229fn derive_enum_variant_encode_block(idx: u64, fields: &[Ident]) -> TokenStream2 {
230 quote! {
231 ::fedimint_core::encoding::Encodable::consensus_encode(&(#idx), writer)?;
232
233 let mut bytes = Vec::<u8>::new();
234 #(::fedimint_core::encoding::Encodable::consensus_encode(#fields, &mut bytes)?;)*
235
236 ::fedimint_core::encoding::Encodable::consensus_encode(&bytes, writer)?;
237 }
238}
239
240#[proc_macro_derive(Decodable)]
241pub fn derive_decodable(input: TokenStream) -> TokenStream {
242 let DeriveInput { ident, data, .. } = parse_macro_input!(input);
243
244 let decode_inner = match data {
245 Data::Struct(DataStruct { fields, .. }) => derive_struct_decode(&ident, &fields),
246 syn::Data::Enum(DataEnum { variants, .. }) => derive_enum_decode(&ident, &variants),
247 syn::Data::Union(_) => error(&ident, "Encodable can't be derived for unions"),
248 };
249
250 let output = quote! {
251 #[allow(deprecated)]
252 impl ::fedimint_core::encoding::Decodable for #ident {
253 fn consensus_decode_partial_from_finite_reader<D: std::io::Read>(d: &mut D, modules: &::fedimint_core::module::registry::ModuleDecoderRegistry) -> std::result::Result<Self, ::fedimint_core::encoding::DecodeError> {
254 #decode_inner
255 }
256 }
257 };
258
259 output.into()
260}
261
262#[allow(unused_variables, unreachable_code)]
263fn error(ident: &Ident, message: &str) -> TokenStream2 {
264 #[cfg(feature = "diagnostics")]
265 ident.span().unstable().error(message).emit();
266 #[cfg(not(feature = "diagnostics"))]
267 panic!("{message}");
268
269 TokenStream2::new()
270}
271
272fn derive_struct_decode(ident: &Ident, fields: &Fields) -> TokenStream2 {
273 let decode_block =
274 derive_tuple_or_named_decode_block(ident, "e! { #ident }, "e! { d }, fields);
275
276 quote! {
277 Ok(#decode_block)
278 }
279}
280
281fn derive_enum_decode(ident: &Ident, variants: &Punctuated<Variant, Comma>) -> TokenStream2 {
282 if variants.is_empty() {
283 return quote! {
284 Err(::fedimint_core::encoding::DecodeError::custom(
285 "Enum without variants can't be instantiated",
286 ))
287 };
288 }
289
290 let non_default_match_arms = non_default_variant_indices(variants).into_iter()
291 .map(|(variant_idx, variant)| {
292 let variant_ident = variant.ident.clone();
293 let decode_block = derive_tuple_or_named_decode_block(
294 ident,
295 "e! { #ident::#variant_ident },
296 "e! { &mut cursor },
297 &variant.fields,
298 );
299
300 quote! {
302 #variant_idx => {
303 let bytes: Vec<u8> = ::fedimint_core::encoding::DecodeContext::context(
305 ::fedimint_core::encoding::Decodable::consensus_decode_partial_from_finite_reader(d, modules),
306 concat!(
307 "Decoding bytes of ",
308 stringify!(#ident),
309 ),
310 )?;
311 let mut cursor = ::std::io::Cursor::new(&bytes);
312
313 let decoded = ::fedimint_core::encoding::DecodeContext::context(
314 (|| -> ::std::result::Result<_, ::fedimint_core::encoding::DecodeError> {
315 Ok(#decode_block)
316 })(),
317 concat!(
318 "Decoding variant ",
319 stringify!(#variant_ident),
320 " (idx: ",
321 #variant_idx,
322 ")",
323 ),
324 )?;
325
326 let read_bytes = cursor.position();
327 let total_bytes = bytes.len() as u64;
328 if read_bytes != total_bytes {
329 return Err(::fedimint_core::encoding::DecodeError::custom(::std::format!(
330 "Partial read: got {total_bytes} bytes but only read {read_bytes} when decoding {}",
331 concat!(
332 stringify!(#ident),
333 "::",
334 stringify!(#variant),
335 ),
336 )));
337 }
338
339 decoded
340 }
341 }
342 });
343
344 let default_match_arm = if variants.iter().any(is_default_variant_enforce_valid) {
345 quote! {
346 variant => {
347 let bytes: Vec<u8> = ::fedimint_core::encoding::DecodeContext::context(
348 ::fedimint_core::encoding::Decodable::consensus_decode_partial_from_finite_reader(d, modules),
349 concat!(
350 "Decoding default variant of ",
351 stringify!(#ident),
352 ),
353 )?;
354
355 #ident::Default {
356 variant,
357 bytes
358 }
359 }
360 }
361 } else {
362 quote! {
363 variant => {
364 return Err(::fedimint_core::encoding::DecodeError::InvalidVariant {
365 variant,
366 type_name: stringify!(#ident),
367 });
368 }
369 }
370 };
371
372 quote! {
373 let variant = ::fedimint_core::encoding::DecodeContext::context(
374 <u64 as ::fedimint_core::encoding::Decodable>::consensus_decode_partial_from_finite_reader(d, modules),
375 concat!(
376 "Decoding variant index of ",
377 stringify!(#ident),
378 ),
379 )?;
380
381 let decoded = match variant {
382 #(#non_default_match_arms)*
383 #default_match_arm
384 };
385 Ok(decoded)
386 }
387}
388
389fn is_tuple_struct(fields: &Fields) -> bool {
390 fields.iter().any(|field| field.ident.is_none())
391}
392
393fn derive_tuple_or_named_decode_block(
398 ident: &Ident,
399 constructor: &TokenStream2,
400 reader: &TokenStream2,
401 fields: &Fields,
402) -> TokenStream2 {
403 if is_tuple_struct(fields) {
404 derive_tuple_decode_block(ident, constructor, reader, fields)
405 } else {
406 derive_named_decode_block(ident, constructor, reader, fields)
407 }
408}
409
410fn derive_tuple_decode_block(
411 ident: &Ident,
412 constructor: &TokenStream2,
413 reader: &TokenStream2,
414 fields: &Fields,
415) -> TokenStream2 {
416 let field_names = fields
417 .iter()
418 .enumerate()
419 .map(|(idx, _)| format_ident!("field_{}", idx))
420 .collect::<Vec<_>>();
421 quote! {
422 {
423 #(
424 let #field_names = ::fedimint_core::encoding::DecodeContext::context(
425 ::fedimint_core::encoding::Decodable::consensus_decode_partial_from_finite_reader(#reader, modules),
426 concat!(
427 "Decoding tuple block ",
428 stringify!(#ident),
429 " field ",
430 stringify!(#field_names),
431 ),
432 )?;
433 )*
434 #constructor(#(#field_names,)*)
435 }
436 }
437}
438
439fn derive_named_decode_block(
440 ident: &Ident,
441 constructor: &TokenStream2,
442 reader: &TokenStream2,
443 fields: &Fields,
444) -> TokenStream2 {
445 let variant_fields = fields
446 .iter()
447 .map(|field| field.ident.clone().unwrap())
448 .collect::<Vec<_>>();
449 quote! {
450 {
451 #(
452 let #variant_fields = ::fedimint_core::encoding::DecodeContext::context(
453 ::fedimint_core::encoding::Decodable::consensus_decode_partial_from_finite_reader(#reader, modules),
454 concat!(
455 "Decoding named block field: ",
456 stringify!(#ident),
457 "{ ... ",
458 stringify!(#variant_fields),
459 " ... }",
460 ),
461 )?;
462 )*
463 #constructor{
464 #(#variant_fields,)*
465 }
466 }
467 }
468}