parity_scale_codec_derive/
encode.rs

1// Copyright 2017, 2018 Parity Technologies
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use 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
24// Encode a single field by using using_encoded, must not have skip attribute
25fn 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	// This may have different hygiene than the field span
72	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		// Based on the seen attribute, we call a handler that generates code for a specific
135		// attribute type.
136		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 = &quote!(__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 the enum has no variants, we don't need to encode anything.
313			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				// The variant index uses 1 byte.
407				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}