1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
use std::convert::TryFrom;

use crate::attrs::{partition_attributes, OdraAttribute};
use quote::{quote, ToTokens};

use super::utils;

/// Odra constructor definition.
///
/// # Examples
/// ```
/// # <odra_ir::module::Constructor as TryFrom<syn::ImplItemMethod>>::try_from(syn::parse_quote! {
/// #[odra(init)]
/// #[other_attribute]
/// pub fn set_initial_value(&self, value: u32) {
///     // initialization logic goes here
/// }
/// # }).unwrap();
/// ```
pub struct Constructor {
    pub attrs: Vec<OdraAttribute>,
    pub impl_item: syn::ImplItemMethod,
    pub ident: syn::Ident,
    pub args: syn::punctuated::Punctuated<syn::PatType, syn::token::Comma>,
    pub full_sig: syn::Signature
}

impl ToTokens for Constructor {
    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
        let is_non_reentrant = self.attrs.iter().any(OdraAttribute::is_non_reentrant);
        let is_mut = utils::is_mut(&self.full_sig);
        let name = &self.ident.to_string();
        let args = &self
            .args
            .iter()
            .flat_map(|arg| {
                let name = &*arg.pat;
                let ty = utils::ty(arg);
                let is_ref = utils::is_ref(arg);
                let ty = quote!(<#ty as odra::types::Typed>::ty());
                quote! {
                    odra::types::contract_def::Argument {
                        ident: odra::prelude::string::String::from(stringify!(#name)),
                        ty: #ty,
                        is_ref: #is_ref,
                    },
                }
            })
            .collect::<proc_macro2::TokenStream>();
        let ep = quote! {
            odra::types::contract_def::Entrypoint {
                ident: odra::prelude::string::String::from(#name),
                args: odra::prelude::vec![#args],
                is_mut: #is_mut,
                ret: odra::types::Type::Unit,
                ty: odra::types::contract_def::EntrypointType::Constructor { non_reentrant: #is_non_reentrant },
            },
        };

        tokens.extend(ep)
    }
}

impl TryFrom<syn::ImplItemMethod> for Constructor {
    type Error = syn::Error;

    fn try_from(method: syn::ImplItemMethod) -> Result<Self, Self::Error> {
        let (odra_attrs, attrs) = partition_attributes(method.clone().attrs).unwrap();
        let ident = method.sig.ident.to_owned();
        let args = utils::extract_typed_inputs(&method.sig);
        if let syn::ReturnType::Type(_, _) = method.sig.output {
            return Err(syn::Error::new_spanned(
                method.sig,
                "Constructor must not return value."
            ));
        }
        let full_sig = method.sig.clone();

        Ok(Self {
            attrs: odra_attrs,
            impl_item: syn::ImplItemMethod { attrs, ..method },
            ident,
            args,
            full_sig
        })
    }
}

#[cfg(test)]
mod test {
    use std::convert::TryFrom;

    use super::Constructor;

    #[test]
    fn test_attrs() {
        let item: syn::ImplItemMethod = syn::parse_quote! {
            #[odra(init, non_reentrant)]
            #[some(a)]
            pub fn set_initial_value(&self, value: u32) {
                self.set_value(value);
            }
        };
        let constructor = Constructor::try_from(item).unwrap();
        assert_eq!(constructor.attrs.len(), 1);
    }
}