tetsy_scale_codec_derive/
lib.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
15//! Derives serialization and deserialization codec for complex structs for simple marshalling.
16
17#![recursion_limit = "128"]
18extern crate proc_macro;
19
20#[macro_use]
21extern crate syn;
22
23#[macro_use]
24extern crate quote;
25
26use proc_macro2::{Ident, Span};
27use proc_macro_crate::crate_name;
28use syn::spanned::Spanned;
29use syn::{Data, Field, Fields, DeriveInput, Error};
30
31mod decode;
32mod encode;
33mod utils;
34mod trait_bounds;
35
36/// Include the `tetsy-scale-codec` crate under a known name (`_tetsy_scale_codec`).
37fn include_tetsy_scale_codec_crate() -> proc_macro2::TokenStream {
38	// This "hack" is required for the tests.
39	if std::env::var("CARGO_PKG_NAME").unwrap() == "tetsy-scale-codec" {
40		quote!( extern crate tetsy_scale_codec as _tetsy_scale_codec; )
41	} else {
42		match crate_name("tetsy-scale-codec") {
43			Ok(tetsy_codec_crate) => {
44				let ident = Ident::new(&tetsy_codec_crate, Span::call_site());
45				quote!( extern crate #ident as _tetsy_scale_codec; )
46			},
47			Err(e) => Error::new(Span::call_site(), &e).to_compile_error(),
48		}
49	}
50}
51
52/// Wraps the impl block in a "dummy const"
53fn wrap_with_dummy_const(impl_block: proc_macro2::TokenStream) -> proc_macro::TokenStream {
54	let tetsy_codec_crate = include_tetsy_scale_codec_crate();
55
56	let generated = quote! {
57		const _: () = {
58			#[allow(unknown_lints)]
59			#[cfg_attr(feature = "cargo-clippy", allow(useless_attribute))]
60			#[allow(rust_2018_idioms)]
61			#tetsy_codec_crate
62			#impl_block
63		};
64	};
65
66	generated.into()
67}
68
69/// Derive `tetsy_scale_codec::Encode` and `tetsy_scale_codec::EncodeLike` for struct and enum.
70///
71/// # Struct
72///
73/// A struct is encoded by encoding each of its fields successively.
74///
75/// Fields can have some attributes:
76/// * `#[codec(skip)]`: the field is not encoded. It must derive `Default` if Decode is derived.
77/// * `#[codec(compact)]`: the field is encoded in its compact representation i.e. the field must
78///   implement `tetsy_scale_codec::HasCompact` and will be encoded as `HasCompact::Type`.
79/// * `#[codec(encoded_as = "$EncodeAs")]`: the field is encoded as an alternative type. $EncodedAs
80///   type must implement `tetsy_scale_codec::EncodeAsRef<'_, $FieldType>` with $FieldType the
81///   type of the field with the attribute. This is intended to be used for types implementing
82///   `HasCompact` as shown in the example.
83///
84/// ```
85/// # use tetsy_scale_codec_derive::Encode;
86/// # use tetsy_scale_codec::{Encode as _, HasCompact};
87/// #[derive(Encode)]
88/// struct StructType {
89///		#[codec(skip)]
90///		a: u32,
91///		#[codec(compact)]
92/// 	b: u32,
93///		#[codec(encoded_as = "<u32 as HasCompact>::Type")]
94///		c: u32,
95/// }
96/// ```
97///
98/// # Enum
99///
100/// The variable is encoded with one byte for the variant and then the variant struct encoding.
101/// The variant number is:
102/// * if variant has attribute: `#[codec(index = "$n")]` then n
103/// * else if variant has discrimant (like 3 in `enum T { A = 3 }`) then the discrimant.
104/// * else its position in the variant set, excluding skipped variants, but including variant with
105/// discrimant or attribute. Warning this position does collision with discrimant or attribute
106/// index.
107///
108/// variant attributes:
109/// * `#[codec(skip)]`: the variant is not encoded.
110/// * `#[codec(index = "$n")]`: override variant index.
111///
112/// field attributes: same as struct fields attributes.
113///
114/// ```
115/// # use tetsy_scale_codec_derive::Encode;
116/// # use tetsy_scale_codec::Encode as _;
117/// #[derive(Encode)]
118/// enum EnumType {
119/// 	#[codec(index = 15)]
120/// 	A,
121/// 	#[codec(skip)]
122/// 	B,
123/// 	C = 3,
124/// 	D,
125/// }
126///
127/// assert_eq!(EnumType::A.encode(), vec![15]);
128/// assert_eq!(EnumType::B.encode(), vec![]);
129/// assert_eq!(EnumType::C.encode(), vec![3]);
130/// assert_eq!(EnumType::D.encode(), vec![2]);
131/// ```
132#[proc_macro_derive(Encode, attributes(codec))]
133pub fn encode_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
134	let mut input: DeriveInput = match syn::parse(input) {
135		Ok(input) => input,
136		Err(e) => return e.to_compile_error().into(),
137	};
138
139	if let Err(e) = utils::check_attributes(&input) {
140		return e.to_compile_error().into();
141	}
142
143	if let Err(e) = trait_bounds::add(
144		&input.ident,
145		&mut input.generics,
146		&input.data,
147		parse_quote!(_tetsy_scale_codec::Encode),
148		None,
149		utils::has_dumb_trait_bound(&input.attrs),
150	) {
151		return e.to_compile_error().into();
152	}
153
154	let name = &input.ident;
155	let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
156
157	let encode_impl = encode::quote(&input.data, name);
158
159	let impl_block = quote! {
160		impl #impl_generics _tetsy_scale_codec::Encode for #name #ty_generics #where_clause {
161			#encode_impl
162		}
163
164		impl #impl_generics _tetsy_scale_codec::EncodeLike for #name #ty_generics #where_clause {}
165	};
166
167	wrap_with_dummy_const(impl_block)
168}
169
170/// Derive `tetsy_scale_codec::Decode` and for struct and enum.
171///
172/// see derive `Encode` documentation.
173#[proc_macro_derive(Decode, attributes(codec))]
174pub fn decode_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
175	let mut input: DeriveInput = match syn::parse(input) {
176		Ok(input) => input,
177		Err(e) => return e.to_compile_error().into(),
178	};
179
180	if let Err(e) = utils::check_attributes(&input) {
181		return e.to_compile_error().into();
182	}
183
184	if let Err(e) = trait_bounds::add(
185		&input.ident,
186		&mut input.generics,
187		&input.data,
188		parse_quote!(_tetsy_scale_codec::Decode),
189		Some(parse_quote!(Default)),
190		utils::has_dumb_trait_bound(&input.attrs),
191	) {
192		return e.to_compile_error().into();
193	}
194
195	let name = &input.ident;
196	let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
197
198	let input_ = quote!(__codec_input_edqy);
199	let decoding = decode::quote(&input.data, name, &input_);
200
201	let impl_block = quote! {
202		impl #impl_generics _tetsy_scale_codec::Decode for #name #ty_generics #where_clause {
203			fn decode<__CodecInputEdqy: _tetsy_scale_codec::Input>(
204				#input_: &mut __CodecInputEdqy
205			) -> core::result::Result<Self, _tetsy_scale_codec::Error> {
206				#decoding
207			}
208		}
209	};
210
211	wrap_with_dummy_const(impl_block)
212}
213
214/// Derive `tetsy_scale_codec::Compact` and `tetsy_scale_codec::CompactAs` for struct with single
215/// field.
216///
217/// Attribute skip can be used to skip other fields.
218///
219/// # Example
220///
221/// ```
222/// # use tetsy_scale_codec_derive::CompactAs;
223/// # use tetsy_scale_codec::{Encode, HasCompact};
224/// # use std::marker::PhantomData;
225/// #[derive(CompactAs)]
226/// struct MyWrapper<T>(u32, #[codec(skip)] PhantomData<T>);
227/// ```
228#[proc_macro_derive(CompactAs, attributes(codec))]
229pub fn compact_as_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
230	let mut input: DeriveInput = match syn::parse(input) {
231		Ok(input) => input,
232		Err(e) => return e.to_compile_error().into(),
233	};
234
235	if let Err(e) = utils::check_attributes(&input) {
236		return e.to_compile_error().into();
237	}
238
239	if let Err(e) = trait_bounds::add(
240		&input.ident,
241		&mut input.generics,
242		&input.data,
243		parse_quote!(_tetsy_scale_codec::CompactAs),
244		None,
245		utils::has_dumb_trait_bound(&input.attrs),
246	) {
247		return e.to_compile_error().into();
248	}
249
250	let name = &input.ident;
251	let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
252
253	fn val_or_default(field: &Field) -> proc_macro2::TokenStream {
254		let skip = utils::should_skip(&field.attrs);
255		if skip {
256			quote_spanned!(field.span()=> Default::default())
257		} else {
258			quote_spanned!(field.span()=> x)
259		}
260	}
261
262	let (inner_ty, inner_field, constructor) = match input.data {
263		Data::Struct(ref data) => {
264			match data.fields {
265				Fields::Named(ref fields) if utils::filter_skip_named(fields).count() == 1 => {
266					let recurse = fields.named.iter().map(|f| {
267						let name_ident = &f.ident;
268						let val_or_default = val_or_default(&f);
269						quote_spanned!(f.span()=> #name_ident: #val_or_default)
270					});
271					let field = utils::filter_skip_named(fields).next().expect("Exactly one field");
272					let field_name = &field.ident;
273					let constructor = quote!( #name { #( #recurse, )* });
274					(&field.ty, quote!(&self.#field_name), constructor)
275				},
276				Fields::Unnamed(ref fields) if utils::filter_skip_unnamed(fields).count() == 1 => {
277					let recurse = fields.unnamed.iter().enumerate().map(|(_, f) | {
278						let val_or_default = val_or_default(&f);
279						quote_spanned!(f.span()=> #val_or_default)
280					});
281					let (id, field) = utils::filter_skip_unnamed(fields).next().expect("Exactly one field");
282					let id = syn::Index::from(id);
283					let constructor = quote!( #name(#( #recurse, )*));
284					(&field.ty, quote!(&self.#id), constructor)
285				},
286				_ => {
287					return Error::new(
288						data.fields.span(),
289						"Only structs with a single non-skipped field can derive CompactAs"
290					).to_compile_error().into();
291				},
292			}
293		},
294		Data::Enum(syn::DataEnum { enum_token: syn::token::Enum { span }, .. }) |
295		Data::Union(syn::DataUnion { union_token: syn::token::Union { span }, .. }) => {
296			return Error::new(span, "Only structs can derive CompactAs").to_compile_error().into();
297		},
298	};
299
300	let impl_block = quote! {
301		impl #impl_generics _tetsy_scale_codec::CompactAs for #name #ty_generics #where_clause {
302			type As = #inner_ty;
303			fn encode_as(&self) -> &#inner_ty {
304				#inner_field
305			}
306			fn decode_from(x: #inner_ty)
307				-> core::result::Result<#name #ty_generics, _tetsy_scale_codec::Error>
308			{
309				Ok(#constructor)
310			}
311		}
312
313		impl #impl_generics From<_tetsy_scale_codec::Compact<#name #ty_generics>>
314			for #name #ty_generics #where_clause
315		{
316			fn from(x: _tetsy_scale_codec::Compact<#name #ty_generics>) -> #name #ty_generics {
317				x.0
318			}
319		}
320	};
321
322	wrap_with_dummy_const(impl_block)
323}