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::{self, const_eval_check_variant_indexes};
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::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	// This may have different hygiene than the field span
71	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		// Based on the seen attribute, we call a handler that generates code for a specific
134		// attribute type.
135		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 = &quote!(__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 the enum has no variants, we don't need to encode anything.
306			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				// The variant index uses 1 byte.
404				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}