experiment_codec_derive/
lib.rs

1// Copyright 2017-2018 Susy 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
17extern crate proc_macro;
18use proc_macro2;
19
20#[macro_use]
21extern crate syn;
22
23#[macro_use]
24extern crate quote;
25
26use proc_macro::TokenStream;
27use proc_macro2::Span;
28use syn::{DeriveInput, Ident, parse::Error};
29use proc_macro_crate::crate_name;
30
31use std::env;
32
33mod decode;
34mod encode;
35mod utils;
36mod trait_bounds;
37
38/// Include the `experiment-codec` crate under a known name (`_susy_codec`).
39fn include_susy_codec_crate() -> proc_macro2::TokenStream {
40	// This "hack" is required for the tests.
41	if env::var("CARGO_PKG_NAME").unwrap() == "experiment-codec" {
42		quote!( extern crate susy_codec as _susy_codec; )
43	} else {
44		match crate_name("experiment-codec") {
45			Ok(susy_codec_crate) => {
46				let ident = Ident::new(&susy_codec_crate, Span::call_site());
47				quote!( extern crate #ident as _susy_codec; )
48			},
49			Err(e) => Error::new(Span::call_site(), &e).to_compile_error(),
50		}
51	}
52}
53
54#[proc_macro_derive(Encode, attributes(codec))]
55pub fn encode_derive(input: TokenStream) -> TokenStream {
56	let mut input: DeriveInput = match syn::parse(input) {
57		Ok(input) => input,
58		Err(e) => return e.to_compile_error().into(),
59	};
60
61	if let Err(e) = trait_bounds::add(
62		&input.ident,
63		&mut input.generics,
64		&input.data,
65		parse_quote!(_susy_codec::Encode)
66	) {
67		return e.to_compile_error().into();
68	}
69
70	let name = &input.ident;
71	let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
72
73	let self_ = quote!(self);
74	let dest_ = quote!(dest);
75	let encoding = encode::quote(&input.data, name, &self_, &dest_);
76
77	let impl_block = quote! {
78		impl #impl_generics _susy_codec::Encode for #name #ty_generics #where_clause {
79			fn encode_to<EncOut: _susy_codec::Output>(&#self_, #dest_: &mut EncOut) {
80				#encoding
81			}
82		}
83	};
84
85	let mut new_name = "_IMPL_ENCODE_FOR_".to_string();
86	new_name.push_str(name.to_string().trim_start_matches("r#"));
87	let dummy_const = Ident::new(&new_name, Span::call_site());
88	let susy_codec_crate = include_susy_codec_crate();
89
90	let generated = quote! {
91		#[allow(non_upper_case_globals, unused_attributes, unused_qualifications)]
92		const #dummy_const: () = {
93			#[allow(unknown_lints)]
94			#[cfg_attr(feature = "cargo-clippy", allow(useless_attribute))]
95			#[allow(rust_2018_idioms)]
96			#susy_codec_crate
97			#impl_block
98		};
99	};
100
101	generated.into()
102}
103
104#[proc_macro_derive(Decode, attributes(codec))]
105pub fn decode_derive(input: TokenStream) -> TokenStream {
106	let mut input: DeriveInput = match syn::parse(input) {
107		Ok(input) => input,
108		Err(e) => return e.to_compile_error().into(),
109	};
110
111	if let Err(e) = trait_bounds::add(
112		&input.ident,
113		&mut input.generics,
114		&input.data,
115		parse_quote!(_susy_codec::Decode),
116	) {
117		return e.to_compile_error().into();
118	}
119
120	let name = &input.ident;
121	let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
122
123	let input_ = quote!(input);
124	let decoding = decode::quote(&input.data, name, &input_);
125
126	let impl_block = quote! {
127		impl #impl_generics _susy_codec::Decode for #name #ty_generics #where_clause {
128			fn decode<DecIn: _susy_codec::Input>(#input_: &mut DecIn) -> Option<Self> {
129				#decoding
130			}
131		}
132	};
133
134	let mut new_name = "_IMPL_DECODE_FOR_".to_string();
135	new_name.push_str(name.to_string().trim_start_matches("r#"));
136	let dummy_const = Ident::new(&new_name, Span::call_site());
137	let susy_codec_crate = include_susy_codec_crate();
138
139	let generated = quote! {
140		#[allow(non_upper_case_globals, unused_attributes, unused_qualifications)]
141		const #dummy_const: () = {
142			#[allow(unknown_lints)]
143			#[cfg_attr(feature = "cargo-clippy", allow(useless_attribute))]
144			#[allow(rust_2018_idioms)]
145			#susy_codec_crate
146			#impl_block
147		};
148	};
149
150	generated.into()
151}