plutus_datum_derive/
lib.rs

1//! This crate creates the attribute macro `ToDatum`
2
3use proc_macro::TokenStream;
4use quote::quote;
5
6extern crate alloc;
7
8use alloc::vec::Vec;
9use syn::{GenericParam, TypeParamBound};
10
11/// Derives ToDatum instance for annotated struct.
12/// `constructor_datum` parameter should be used on a single field structs to decide if the field
13/// should be mapped to datum directly or wrapped in constructor datum with one variant and single
14/// field. Such distinction is required because exactly same Datum encoding, as in
15/// <https://github.com/input-output-hk/partner-chains-smart-contracts>, is required.
16#[proc_macro_derive(ToDatum, attributes(constructor_datum))]
17pub fn to_datum_derive(input: TokenStream) -> TokenStream {
18	let ast = syn::parse(input).expect("Cannot parse source");
19
20	impl_to_datum_derive(&ast)
21}
22
23fn impl_to_datum_derive(ast: &syn::DeriveInput) -> TokenStream {
24	let name = &ast.ident;
25	let data = &ast.data;
26	let generics = &ast.generics;
27	let mut bounded_generics = generics.clone();
28	for generic_param in bounded_generics.params.iter_mut() {
29		if let GenericParam::Type(ref mut type_param) = *generic_param {
30			type_param
31				.bounds
32				.push(TypeParamBound::Trait(syn::parse_quote!(plutus::ToDatum)));
33		}
34	}
35	let has_constructor_datum_attribute = ast.attrs.iter().any(|attr| {
36		attr.path()
37			.segments
38			.first()
39			.filter(|ps| ps.ident == *"constructor_datum")
40			.is_some()
41	});
42	let body = match data {
43		syn::Data::Struct(ds) => match &ds.fields {
44			syn::Fields::Unnamed(fields_unnamed) => {
45				let len = fields_unnamed.unnamed.len();
46				if len == 1 && !has_constructor_datum_attribute {
47					quote! { self.0.to_datum() }
48				} else {
49					let fields: Vec<_> = core::ops::Range { start: 0, end: len }
50						.map(syn::Index::from)
51						.map(|i| quote! { self.#i.to_datum()})
52						.collect();
53					quote! {
54						plutus::Datum::ConstructorDatum { constructor: 0, fields: vec![#(#fields),*] }
55					}
56				}
57			},
58			syn::Fields::Named(fields_named) => {
59				let idents: Vec<_> =
60					fields_named.named.iter().filter_map(|f| f.ident.clone()).collect();
61				match (&idents[..], has_constructor_datum_attribute) {
62					([ident], false) => quote! { self.#ident.to_datum() },
63					(idents, _) => {
64						let fields: Vec<_> =
65							idents.iter().map(|token| quote! { self.#token.to_datum()}).collect();
66						quote! {
67							plutus::Datum::ConstructorDatum { constructor: 0, fields: vec![#(#fields),*] }
68						}
69					},
70				}
71			},
72			syn::Fields::Unit => quote! {
73				plutus::Datum::ConstructorDatum { constructor: 0, fields: Vec::new() }
74			},
75		},
76		syn::Data::Enum(_de) => quote! {
77			compile_error!("ToDatum isn't yet implemented for Enum types"),
78		},
79		syn::Data::Union(_du) => quote! {
80			compile_error!("ToDatum isn't yet implemented for Union types"),
81		},
82	};
83	let gen_token_stream = quote! {
84		impl #bounded_generics plutus::ToDatum for #name #generics {
85			fn to_datum(&self) -> plutus::Datum {
86				#body
87			}
88		}
89	};
90	gen_token_stream.into()
91}