susyabi_derive/
lib.rs

1#![recursion_limit="256"]
2
3extern crate proc_macro;
4extern crate proc_macro2;
5extern crate syn;
6#[macro_use]
7extern crate quote;
8extern crate heck;
9extern crate susyabi;
10
11mod constructor;
12mod contract;
13mod event;
14mod function;
15
16use std::{env, fs};
17use std::path::PathBuf;
18use heck::SnakeCase;
19use syn::export::Span;
20use susyabi::{Result, ResultExt, Contract, Param, ParamType};
21
22const ERROR_MSG: &'static str = "`derive(SabiContract)` failed";
23
24#[proc_macro_derive(SabiContract, attributes(susyabi_contract_options))]
25pub fn susyabi_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
26	let ast = syn::parse(input).expect(ERROR_MSG);
27	let gen = impl_susyabi_derive(&ast).expect(ERROR_MSG);
28	gen.into()
29}
30
31fn impl_susyabi_derive(ast: &syn::DeriveInput) -> Result<proc_macro2::TokenStream> {
32	let options = get_options(&ast.attrs, "susyabi_contract_options")?;
33	let path = get_option(&options, "path")?;
34	let normalized_path = normalize_path(&path)?;
35	let source_file = fs::File::open(&normalized_path)
36		.chain_err(|| format!("Cannot load contract abi from `{}`", normalized_path.display()))?;
37	let contract = Contract::load(source_file)?;
38	let c = contract::Contract::from(&contract);
39	Ok(c.generate())
40}
41
42fn get_options(attrs: &[syn::Attribute], name: &str) -> Result<Vec<syn::NestedMeta>> {
43	let options = attrs.iter()
44		.flat_map(syn::Attribute::interpret_meta)
45		.find(|meta| meta.name() == name);
46
47
48	match options {
49		Some(syn::Meta::List(list)) => Ok(list.nested.into_iter().collect()),
50		_ => Err("Unexpected meta item".into())
51	}
52}
53
54fn get_option(options: &[syn::NestedMeta], name: &str) -> Result<String> {
55	let item = options.iter()
56		.flat_map(|nested| match *nested {
57			syn::NestedMeta::Meta(ref meta) => Some(meta),
58			_ => None,
59		})
60		.find(|meta| meta.name() == name)
61		.chain_err(|| format!("Expected to find option {}", name))?;
62	str_value_of_meta_item(item, name)
63}
64
65fn str_value_of_meta_item(item: &syn::Meta, name: &str) -> Result<String> {
66	if let syn::Meta::NameValue(ref name_value) = *item {
67		if let syn::Lit::Str(ref value) = name_value.lit {
68			return Ok(value.value());
69		}
70	}
71
72	Err(format!(r#"`{}` must be in the form `#[{}="somsing"]`"#, name, name).into())
73}
74
75fn normalize_path(relative_path: &str) -> Result<PathBuf> {
76	// workaround for https://github.com/rust-lang/rust/issues/43860
77	let cargo_toml_directory = env::var("CARGO_MANIFEST_DIR").chain_err(|| "Cannot find manifest file")?;
78	let mut path: PathBuf = cargo_toml_directory.into();
79	path.push(relative_path);
80	Ok(path)
81}
82
83fn to_syntax_string(param_type: &susyabi::ParamType) -> proc_macro2::TokenStream {
84	match *param_type {
85		ParamType::Address => quote! { susyabi::ParamType::Address },
86		ParamType::Bytes => quote! { susyabi::ParamType::Bytes },
87		ParamType::Int(x) => quote! { susyabi::ParamType::Int(#x) },
88		ParamType::Uint(x) => quote! { susyabi::ParamType::Uint(#x) },
89		ParamType::Bool => quote! { susyabi::ParamType::Bool },
90		ParamType::String => quote! { susyabi::ParamType::String },
91		ParamType::Array(ref param_type) => {
92			let param_type_quote = to_syntax_string(param_type);
93			quote! { susyabi::ParamType::Array(Box::new(#param_type_quote)) }
94		},
95		ParamType::FixedBytes(x) => quote! { susyabi::ParamType::FixedBytes(#x) },
96		ParamType::FixedArray(ref param_type, ref x) => {
97			let param_type_quote = to_syntax_string(param_type);
98			quote! { susyabi::ParamType::FixedArray(Box::new(#param_type_quote), #x) }
99		}
100	}
101}
102
103fn to_susyabi_param_vec<'a, P: 'a>(params: P) -> proc_macro2::TokenStream
104	where P: IntoIterator<Item = &'a Param>
105{
106	let p = params.into_iter().map(|x| {
107		let name = &x.name;
108		let kind = to_syntax_string(&x.kind);
109		quote! {
110			susyabi::Param {
111				name: #name.to_owned(),
112				kind: #kind
113			}
114		}
115	}).collect::<Vec<_>>();
116
117	quote! { vec![ #(#p),* ] }
118}
119
120fn rust_type(input: &ParamType) -> proc_macro2::TokenStream {
121	match *input {
122		ParamType::Address => quote! { susyabi::Address },
123		ParamType::Bytes => quote! { susyabi::Bytes },
124		ParamType::FixedBytes(32) => quote! { susyabi::Hash },
125		ParamType::FixedBytes(size) => quote! { [u8; #size] },
126		ParamType::Int(_) => quote! { susyabi::Int },
127		ParamType::Uint(_) => quote! { susyabi::Uint },
128		ParamType::Bool => quote! { bool },
129		ParamType::String => quote! { String },
130		ParamType::Array(ref kind) => {
131			let t = rust_type(&*kind);
132			quote! { Vec<#t> }
133		},
134		ParamType::FixedArray(ref kind, size) => {
135			let t = rust_type(&*kind);
136			quote! { [#t, #size] }
137		}
138	}
139}
140
141fn template_param_type(input: &ParamType, index: usize) -> proc_macro2::TokenStream {
142	let t_ident = syn::Ident::new(&format!("T{}", index), Span::call_site());
143	let u_ident = syn::Ident::new(&format!("U{}", index), Span::call_site());
144	match *input {
145		ParamType::Address => quote! { #t_ident: Into<susyabi::Address> },
146		ParamType::Bytes => quote! { #t_ident: Into<susyabi::Bytes> },
147		ParamType::FixedBytes(32) => quote! { #t_ident: Into<susyabi::Hash> },
148		ParamType::FixedBytes(size) => quote! { #t_ident: Into<[u8; #size]> },
149		ParamType::Int(_) => quote! { #t_ident: Into<susyabi::Int> },
150		ParamType::Uint(_) => quote! { #t_ident: Into<susyabi::Uint> },
151		ParamType::Bool => quote! { #t_ident: Into<bool> },
152		ParamType::String => quote! { #t_ident: Into<String> },
153		ParamType::Array(ref kind) => {
154			let t = rust_type(&*kind);
155			quote! {
156				#t_ident: IntoIterator<Item = #u_ident>, #u_ident: Into<#t>
157			}
158		},
159		ParamType::FixedArray(ref kind, size) => {
160			let t = rust_type(&*kind);
161			quote! {
162				#t_ident: Into<[#u_ident; #size]>, #u_ident: Into<#t>
163			}
164		}
165	}
166}
167
168fn from_template_param(input: &ParamType, name: &syn::Ident) -> proc_macro2::TokenStream {
169	match *input {
170		ParamType::Array(_) => quote! { #name.into_iter().map(Into::into).collect::<Vec<_>>() },
171		ParamType::FixedArray(_, _) => quote! { (Box::new(#name.into()) as Box<[_]>).into_vec().into_iter().map(Into::into).collect::<Vec<_>>() },
172		_ => quote! {#name.into() },
173	}
174}
175
176fn to_token(name: &proc_macro2::TokenStream, kind: &ParamType) -> proc_macro2::TokenStream {
177	match *kind {
178		ParamType::Address => quote! { susyabi::Token::Address(#name) },
179		ParamType::Bytes => quote! { susyabi::Token::Bytes(#name) },
180		ParamType::FixedBytes(_) => quote! { susyabi::Token::FixedBytes(#name.to_vec()) },
181		ParamType::Int(_) => quote! { susyabi::Token::Int(#name) },
182		ParamType::Uint(_) => quote! { susyabi::Token::Uint(#name) },
183		ParamType::Bool => quote! { susyabi::Token::Bool(#name) },
184		ParamType::String => quote! { susyabi::Token::String(#name) },
185		ParamType::Array(ref kind) => {
186			let inner_name = quote! { inner };
187			let inner_loop = to_token(&inner_name, kind);
188			quote! {
189				// note the double {{
190				{
191					let v = #name.into_iter().map(|#inner_name| #inner_loop).collect();
192					susyabi::Token::Array(v)
193				}
194			}
195		}
196		ParamType::FixedArray(ref kind, _) => {
197			let inner_name = quote! { inner };
198			let inner_loop = to_token(&inner_name, kind);
199			quote! {
200				// note the double {{
201				{
202					let v = #name.into_iter().map(|#inner_name| #inner_loop).collect();
203					susyabi::Token::FixedArray(v)
204				}
205			}
206		},
207	}
208}
209
210fn from_token(kind: &ParamType, token: &proc_macro2::TokenStream) -> proc_macro2::TokenStream {
211	match *kind {
212		ParamType::Address => quote! { #token.to_address().expect(INTERNAL_ERR) },
213		ParamType::Bytes => quote! { #token.to_bytes().expect(INTERNAL_ERR) },
214		ParamType::FixedBytes(32) => quote! {
215			{
216				let mut result = [0u8; 32];
217				let v = #token.to_fixed_bytes().expect(INTERNAL_ERR);
218				result.copy_from_slice(&v);
219				susyabi::Hash::from(result)
220			}
221		},
222		ParamType::FixedBytes(size) => {
223			let size: syn::Index = size.into();
224			quote! {
225				{
226					let mut result = [0u8; #size];
227					let v = #token.to_fixed_bytes().expect(INTERNAL_ERR);
228					result.copy_from_slice(&v);
229					result
230				}
231			}
232		},
233		ParamType::Int(_) => quote! { #token.to_int().expect(INTERNAL_ERR) },
234		ParamType::Uint(_) => quote! { #token.to_uint().expect(INTERNAL_ERR) },
235		ParamType::Bool => quote! { #token.to_bool().expect(INTERNAL_ERR) },
236		ParamType::String => quote! { #token.to_string().expect(INTERNAL_ERR) },
237		ParamType::Array(ref kind) => {
238			let inner = quote! { inner };
239			let inner_loop = from_token(kind, &inner);
240			quote! {
241				#token.to_array().expect(INTERNAL_ERR).into_iter()
242					.map(|#inner| #inner_loop)
243					.collect()
244			}
245		},
246		ParamType::FixedArray(ref kind, size) => {
247			let inner = quote! { inner };
248			let inner_loop = from_token(kind, &inner);
249			let to_array = vec![quote! { iter.next() }; size];
250			quote! {
251				{
252					let iter = #token.to_array().expect(INTERNAL_ERR).into_iter()
253						.map(|#inner| #inner_loop);
254					[#(#to_array),*]
255				}
256			}
257		},
258	}
259}
260
261fn input_names(inputs: &Vec<Param>) -> Vec<syn::Ident> {
262	inputs
263		.iter()
264		.enumerate()
265		.map(|(index, param)| if param.name.is_empty() {
266			syn::Ident::new(&format!("param{}", index), Span::call_site())
267		} else {
268			syn::Ident::new(&rust_variable(&param.name), Span::call_site())
269		})
270		.collect()
271}
272
273fn get_template_names(kinds: &Vec<proc_macro2::TokenStream>) -> Vec<syn::Ident> {
274	kinds.iter().enumerate()
275		.map(|(index, _)| syn::Ident::new(&format!("T{}", index), Span::call_site()))
276		.collect()
277}
278
279fn get_output_kinds(outputs: &Vec<Param>) -> proc_macro2::TokenStream {
280	match outputs.len() {
281		0 => quote! {()},
282		1 => {
283			let t = rust_type(&outputs[0].kind);
284			quote! { #t }
285		},
286		_ => {
287			let outs: Vec<_> = outputs
288				.iter()
289				.map(|param| rust_type(&param.kind))
290				.collect();
291			quote! { (#(#outs),*) }
292		}
293	}
294}
295
296/// Convert input into a rust variable name.
297///
298/// Avoid using keywords by escaping them.
299fn rust_variable(name: &str) -> String {
300	// avoid keyword parameters
301	match name {
302		"self" => "_self".to_string(),
303		other => other.to_snake_case(),
304	}
305}