ink_codegen/generator/
chain_extension.rs

1// Copyright (C) Use Ink (UK) Ltd.
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
15use crate::GenerateCode;
16use derive_more::From;
17use ir::ChainExtensionMethod;
18use proc_macro2::TokenStream as TokenStream2;
19use quote::{
20    format_ident,
21    quote_spanned,
22};
23use syn::spanned::Spanned;
24
25/// Generator to create an ink! chain extension.
26#[derive(From)]
27pub struct ChainExtension<'a> {
28    extension: &'a ir::ChainExtension,
29}
30
31impl ChainExtension<'_> {
32    fn generate_for_instance_method(
33        method: &ChainExtensionMethod,
34        error_code: &syn::Type,
35    ) -> TokenStream2 {
36        let span = method.span();
37        let attrs = method.attrs();
38        let ident = method.ident();
39        let id = method.id().into_u32();
40        let sig = method.sig();
41        let inputs = &sig.inputs;
42        let input_bindings = method.inputs().map(|pat_type| &pat_type.pat);
43        let input_types = method.inputs().map(|pat_type| &pat_type.ty);
44
45        // Mostly tuple-based type representation of the inputs:
46        // Special case for when there is exactly one input type.
47        // - 0 inputs          -> ()
48        // - 1 input T         -> T
49        // - n inputs A, B. .. -> (A, B, ..)
50        let compound_input_type = match inputs.len() {
51            0 => quote_spanned!(span=> ()),
52            1 => quote_spanned!(span=> #( #input_types )* ),
53            _n => quote_spanned!(span=> ( #( #input_types ),* ) ),
54        };
55
56        // Mostly tuple-based value representation of the inputs:
57        // Special case for when there is exactly one input value.
58        // - 0 inputs          -> ()
59        // - 1 input a         -> a
60        // - n inputs a, b. .. -> (a, b, ..)
61        let compound_input_bindings = match inputs.len() {
62            0 => quote_spanned!(span=> ()),
63            1 => quote_spanned!(span=> #( #input_bindings )* ),
64            _n => quote_spanned!(span=> ( #( #input_bindings ),* ) ),
65        };
66
67        let output = &sig.output;
68        let output_type = match output {
69            syn::ReturnType::Default => quote_spanned!(output.span()=> ()),
70            syn::ReturnType::Type(_arrow, ty) => {
71                quote_spanned!(output.span()=> #ty)
72            }
73        };
74
75        let handle_status = method.handle_status();
76
77        let handle_status_token = if handle_status {
78            quote_spanned!(span=>
79                true
80            )
81        } else {
82            quote_spanned!(span=>
83                false
84            )
85        };
86
87        let error_code_handling = if handle_status {
88            quote_spanned!(span=>
89                .handle_error_code::<#error_code>()
90            )
91        } else {
92            quote_spanned!(span=>
93                .ignore_error_code()
94            )
95        };
96
97        let return_type = quote_spanned!(span =>
98            <::ink::ValueReturned as ::ink::Output<{ ::ink::is_result_type!(#output_type) }, #handle_status_token, #output_type, #error_code>>::ReturnType
99        );
100
101        // we only need to check if handle status is set to true to enable this type bound
102        let where_output_impls_from_error_code = Some(quote_spanned!(span=>
103            <#return_type as ::ink::IsResultType>::Err: ::core::convert::From<#error_code>,
104        )).filter(|_|  handle_status);
105
106        quote_spanned!(span=>
107                #( #attrs )*
108                #[inline]
109                pub fn #ident(self, #inputs) -> #return_type
110                where
111                    #where_output_impls_from_error_code
112                {
113                    ::ink::env::chain_extension::ChainExtensionMethod::build(#id)
114                    .input::<#compound_input_type>()
115                    .output::<#output_type, {::ink::is_result_type!(#output_type)}>()
116                    #error_code_handling
117                    .call(&#compound_input_bindings)
118                }
119        )
120    }
121}
122
123impl GenerateCode for ChainExtension<'_> {
124    fn generate_code(&self) -> TokenStream2 {
125        let span = self.extension.span();
126        let attrs = self.extension.attrs();
127        let ident = self.extension.ident();
128        let error_code = self.extension.error_code();
129        let instance_methods = self
130            .extension
131            .iter_methods()
132            .map(|method| Self::generate_for_instance_method(method, error_code));
133        let instance_ident = format_ident!("__ink_{}Instance", ident);
134        quote_spanned!(span =>
135            #(#attrs)*
136            #[::ink::scale_derive(TypeInfo)]
137            pub enum #ident {}
138
139            const _: () = {
140                #[allow(non_camel_case_types)]
141                struct __ink_Private;
142                #[allow(non_camel_case_types)]
143                pub struct #instance_ident {
144                    __ink_private: __ink_Private
145                }
146
147                impl #instance_ident {
148                    #( #instance_methods )*
149                }
150
151                impl ::ink::ChainExtensionInstance for #ident {
152                    type Instance = #instance_ident;
153
154                    fn instantiate() -> Self::Instance {
155                        Self::Instance { __ink_private: __ink_Private }
156                    }
157                }
158            };
159        )
160    }
161}