1use std::str::from_utf8;
16
17use proc_macro2::{Ident, Span, TokenStream};
18use syn::{punctuated::Punctuated, spanned::Spanned, token::Comma, Data, Error, Field, Fields};
19
20use crate::utils::{self, const_eval_check_variant_indexes};
21
22type FieldsList = Punctuated<Field, Comma>;
23
24fn encode_single_field(
26 field: &Field,
27 field_name: TokenStream,
28 crate_path: &syn::Path,
29) -> TokenStream {
30 let encoded_as = utils::get_encoded_as_type(field);
31 let compact = utils::get_compact_type(field, crate_path);
32
33 if utils::should_skip(&field.attrs) {
34 return Error::new(
35 Span::call_site(),
36 "Internal error: cannot encode single field optimisation if skipped",
37 )
38 .to_compile_error();
39 }
40
41 if encoded_as.is_some() && compact.is_some() {
42 return Error::new(
43 Span::call_site(),
44 "`encoded_as` and `compact` can not be used at the same time!",
45 )
46 .to_compile_error();
47 }
48
49 let final_field_variable = if let Some(compact) = compact {
50 let field_type = &field.ty;
51 quote_spanned! {
52 field.span() => {
53 <#compact as #crate_path::EncodeAsRef<'_, #field_type>>::RefType::from(#field_name)
54 }
55 }
56 } else if let Some(encoded_as) = encoded_as {
57 let field_type = &field.ty;
58 quote_spanned! {
59 field.span() => {
60 <#encoded_as as
61 #crate_path::EncodeAsRef<'_, #field_type>>::RefType::from(#field_name)
62 }
63 }
64 } else {
65 quote_spanned! { field.span() =>
66 #field_name
67 }
68 };
69
70 let i_self = quote! { self };
72
73 quote_spanned! { field.span() =>
74 fn size_hint(&#i_self) -> usize {
75 #crate_path::Encode::size_hint(&#final_field_variable)
76 }
77
78 fn encode_to<__CodecOutputEdqy: #crate_path::Output + ?::core::marker::Sized>(
79 &#i_self,
80 __codec_dest_edqy: &mut __CodecOutputEdqy
81 ) {
82 #crate_path::Encode::encode_to(&#final_field_variable, __codec_dest_edqy)
83 }
84
85 fn encode(&#i_self) -> #crate_path::alloc::vec::Vec<::core::primitive::u8> {
86 #crate_path::Encode::encode(&#final_field_variable)
87 }
88
89 fn using_encoded<
90 __CodecOutputReturn,
91 __CodecUsingEncodedCallback: ::core::ops::FnOnce(
92 &[::core::primitive::u8]
93 ) -> __CodecOutputReturn
94 >(&#i_self, f: __CodecUsingEncodedCallback) -> __CodecOutputReturn
95 {
96 #crate_path::Encode::using_encoded(&#final_field_variable, f)
97 }
98 }
99}
100
101enum FieldAttribute<'a> {
102 None(&'a Field),
103 Compact(&'a Field),
104 EncodedAs { field: &'a Field, encoded_as: &'a TokenStream },
105 Skip,
106}
107
108fn iterate_over_fields<F, H, J>(
109 fields: &FieldsList,
110 field_name: F,
111 field_handler: H,
112 field_joiner: J,
113) -> TokenStream
114where
115 F: Fn(usize, &Option<Ident>) -> TokenStream,
116 H: Fn(TokenStream, FieldAttribute) -> TokenStream,
117 J: Fn(&mut dyn Iterator<Item = TokenStream>) -> TokenStream,
118{
119 let mut recurse = fields.iter().enumerate().map(|(i, f)| {
120 let field = field_name(i, &f.ident);
121 let encoded_as = utils::get_encoded_as_type(f);
122 let compact = utils::is_compact(f);
123 let skip = utils::should_skip(&f.attrs);
124
125 if encoded_as.is_some() as u8 + compact as u8 + skip as u8 > 1 {
126 return Error::new(
127 f.span(),
128 "`encoded_as`, `compact` and `skip` can only be used one at a time!",
129 )
130 .to_compile_error();
131 }
132
133 if compact {
136 field_handler(field, FieldAttribute::Compact(f))
137 } else if let Some(ref encoded_as) = encoded_as {
138 field_handler(field, FieldAttribute::EncodedAs { field: f, encoded_as })
139 } else if skip {
140 field_handler(field, FieldAttribute::Skip)
141 } else {
142 field_handler(field, FieldAttribute::None(f))
143 }
144 });
145
146 field_joiner(&mut recurse)
147}
148
149fn encode_fields<F>(
150 dest: &TokenStream,
151 fields: &FieldsList,
152 field_name: F,
153 crate_path: &syn::Path,
154) -> TokenStream
155where
156 F: Fn(usize, &Option<Ident>) -> TokenStream,
157{
158 iterate_over_fields(
159 fields,
160 field_name,
161 |field, field_attribute| match field_attribute {
162 FieldAttribute::None(f) => quote_spanned! { f.span() =>
163 #crate_path::Encode::encode_to(#field, #dest);
164 },
165 FieldAttribute::Compact(f) => {
166 let field_type = &f.ty;
167 quote_spanned! {
168 f.span() => {
169 #crate_path::Encode::encode_to(
170 &<
171 <#field_type as #crate_path::HasCompact>::Type as
172 #crate_path::EncodeAsRef<'_, #field_type>
173 >::RefType::from(#field),
174 #dest,
175 );
176 }
177 }
178 },
179 FieldAttribute::EncodedAs { field: f, encoded_as } => {
180 let field_type = &f.ty;
181 quote_spanned! {
182 f.span() => {
183 #crate_path::Encode::encode_to(
184 &<
185 #encoded_as as
186 #crate_path::EncodeAsRef<'_, #field_type>
187 >::RefType::from(#field),
188 #dest,
189 );
190 }
191 }
192 },
193 FieldAttribute::Skip => quote! {
194 let _ = #field;
195 },
196 },
197 |recurse| {
198 quote! {
199 #( #recurse )*
200 }
201 },
202 )
203}
204
205fn size_hint_fields<F>(fields: &FieldsList, field_name: F, crate_path: &syn::Path) -> TokenStream
206where
207 F: Fn(usize, &Option<Ident>) -> TokenStream,
208{
209 iterate_over_fields(
210 fields,
211 field_name,
212 |field, field_attribute| match field_attribute {
213 FieldAttribute::None(f) => quote_spanned! { f.span() =>
214 .saturating_add(#crate_path::Encode::size_hint(#field))
215 },
216 FieldAttribute::Compact(f) => {
217 let field_type = &f.ty;
218 quote_spanned! {
219 f.span() => .saturating_add(#crate_path::Encode::size_hint(
220 &<
221 <#field_type as #crate_path::HasCompact>::Type as
222 #crate_path::EncodeAsRef<'_, #field_type>
223 >::RefType::from(#field),
224 ))
225 }
226 },
227 FieldAttribute::EncodedAs { field: f, encoded_as } => {
228 let field_type = &f.ty;
229 quote_spanned! {
230 f.span() => .saturating_add(#crate_path::Encode::size_hint(
231 &<
232 #encoded_as as
233 #crate_path::EncodeAsRef<'_, #field_type>
234 >::RefType::from(#field),
235 ))
236 }
237 },
238 FieldAttribute::Skip => quote!(),
239 },
240 |recurse| {
241 quote! {
242 0_usize #( #recurse )*
243 }
244 },
245 )
246}
247
248fn try_impl_encode_single_field_optimisation(
249 data: &Data,
250 crate_path: &syn::Path,
251) -> Option<TokenStream> {
252 match *data {
253 Data::Struct(ref data) => match data.fields {
254 Fields::Named(ref fields) if utils::filter_skip_named(fields).count() == 1 => {
255 let field = utils::filter_skip_named(fields).next().unwrap();
256 let name = &field.ident;
257 Some(encode_single_field(field, quote!(&self.#name), crate_path))
258 },
259 Fields::Unnamed(ref fields) if utils::filter_skip_unnamed(fields).count() == 1 => {
260 let (id, field) = utils::filter_skip_unnamed(fields).next().unwrap();
261 let id = syn::Index::from(id);
262
263 Some(encode_single_field(field, quote!(&self.#id), crate_path))
264 },
265 _ => None,
266 },
267 _ => None,
268 }
269}
270
271fn impl_encode(data: &Data, type_name: &Ident, crate_path: &syn::Path) -> TokenStream {
272 let self_ = quote!(self);
273 let dest = "e!(__codec_dest_edqy);
274 let [hinting, encoding] = match *data {
275 Data::Struct(ref data) => match data.fields {
276 Fields::Named(ref fields) => {
277 let fields = &fields.named;
278 let field_name = |_, name: &Option<Ident>| quote!(&#self_.#name);
279
280 let hinting = size_hint_fields(fields, field_name, crate_path);
281 let encoding = encode_fields(dest, fields, field_name, crate_path);
282
283 [hinting, encoding]
284 },
285 Fields::Unnamed(ref fields) => {
286 let fields = &fields.unnamed;
287 let field_name = |i, _: &Option<Ident>| {
288 let i = syn::Index::from(i);
289 quote!(&#self_.#i)
290 };
291
292 let hinting = size_hint_fields(fields, field_name, crate_path);
293 let encoding = encode_fields(dest, fields, field_name, crate_path);
294
295 [hinting, encoding]
296 },
297 Fields::Unit => [quote! { 0_usize }, quote!()],
298 },
299 Data::Enum(ref data) => {
300 let variants = match utils::try_get_variants(data) {
301 Ok(variants) => variants,
302 Err(e) => return e.to_compile_error(),
303 };
304
305 if variants.is_empty() {
307 return quote!();
308 }
309
310 let recurse = variants.iter().enumerate().map(|(i, f)| {
311 let name = &f.ident;
312 let index = utils::variant_index(f, i);
313
314 match f.fields {
315 Fields::Named(ref fields) => {
316 let fields = &fields.named;
317 let field_name = |_, ident: &Option<Ident>| quote!(#ident);
318
319 let names = fields.iter().enumerate().map(|(i, f)| field_name(i, &f.ident));
320
321 let field_name = |a, b: &Option<Ident>| field_name(a, b);
322
323 let size_hint_fields = size_hint_fields(fields, field_name, crate_path);
324 let encode_fields = encode_fields(dest, fields, field_name, crate_path);
325
326 let hinting_names = names.clone();
327 let hinting = quote_spanned! { f.span() =>
328 #type_name :: #name { #( ref #hinting_names, )* } => {
329 #size_hint_fields
330 }
331 };
332
333 let encoding_names = names.clone();
334 let encoding = quote_spanned! { f.span() =>
335 #type_name :: #name { #( ref #encoding_names, )* } => {
336 #[allow(clippy::unnecessary_cast)]
337 #dest.push_byte((#index) as ::core::primitive::u8);
338 #encode_fields
339 }
340 };
341
342 (hinting, encoding, index, name.clone())
343 },
344 Fields::Unnamed(ref fields) => {
345 let fields = &fields.unnamed;
346 let field_name = |i, _: &Option<Ident>| {
347 let data = stringify(i as u8);
348 let ident = from_utf8(&data).expect("We never go beyond ASCII");
349 let ident = Ident::new(ident, Span::call_site());
350 quote!(#ident)
351 };
352
353 let names = fields.iter().enumerate().map(|(i, f)| field_name(i, &f.ident));
354
355 let field_name = |a, b: &Option<Ident>| field_name(a, b);
356
357 let size_hint_fields = size_hint_fields(fields, field_name, crate_path);
358 let encode_fields = encode_fields(dest, fields, field_name, crate_path);
359
360 let hinting_names = names.clone();
361 let hinting = quote_spanned! { f.span() =>
362 #type_name :: #name ( #( ref #hinting_names, )* ) => {
363 #size_hint_fields
364 }
365 };
366
367 let encoding_names = names.clone();
368 let encoding = quote_spanned! { f.span() =>
369 #type_name :: #name ( #( ref #encoding_names, )* ) => {
370 #[allow(clippy::unnecessary_cast)]
371 #dest.push_byte((#index) as ::core::primitive::u8);
372 #encode_fields
373 }
374 };
375
376 (hinting, encoding, index, name.clone())
377 },
378 Fields::Unit => {
379 let hinting = quote_spanned! { f.span() =>
380 #type_name :: #name => {
381 0_usize
382 }
383 };
384
385 let encoding = quote_spanned! { f.span() =>
386 #type_name :: #name => {
387 #[allow(clippy::unnecessary_cast)]
388 #[allow(clippy::cast_possible_truncation)]
389 #dest.push_byte((#index) as ::core::primitive::u8);
390 }
391 };
392
393 (hinting, encoding, index, name.clone())
394 },
395 }
396 });
397
398 let recurse_hinting = recurse.clone().map(|(hinting, _, _, _)| hinting);
399 let recurse_encoding = recurse.clone().map(|(_, encoding, _, _)| encoding);
400 let recurse_variant_indices = recurse.clone().map(|(_, _, index, name)| (name, index));
401
402 let hinting = quote! {
403 1_usize + match *#self_ {
405 #( #recurse_hinting )*,
406 _ => 0_usize,
407 }
408 };
409
410 let const_eval_check =
411 const_eval_check_variant_indexes(recurse_variant_indices, crate_path);
412
413 let encoding = quote! {
414 #const_eval_check
415 match *#self_ {
416 #( #recurse_encoding )*,
417 _ => (),
418 }
419 };
420
421 [hinting, encoding]
422 },
423 Data::Union(ref data) =>
424 return Error::new(data.union_token.span(), "Union types are not supported.")
425 .to_compile_error(),
426 };
427 quote! {
428 fn size_hint(&#self_) -> usize {
429 #hinting
430 }
431
432 fn encode_to<__CodecOutputEdqy: #crate_path::Output + ?::core::marker::Sized>(
433 &#self_,
434 #dest: &mut __CodecOutputEdqy
435 ) {
436 #encoding
437 }
438 }
439}
440
441pub fn quote(data: &Data, type_name: &Ident, crate_path: &syn::Path) -> TokenStream {
442 if let Some(implementation) = try_impl_encode_single_field_optimisation(data, crate_path) {
443 implementation
444 } else {
445 impl_encode(data, type_name, crate_path)
446 }
447}
448
449pub fn stringify(id: u8) -> [u8; 2] {
450 const CHARS: &[u8] = b"abcdefghijklmnopqrstuvwxyz";
451 let len = CHARS.len() as u8;
452 let symbol = |id: u8| CHARS[(id % len) as usize];
453 let a = symbol(id);
454 let b = symbol(id / len);
455
456 [a, b]
457}