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