use crate::{
generator,
GenerateCode,
};
use derive_more::From;
use ir::Callable;
use proc_macro2::TokenStream as TokenStream2;
use quote::{
format_ident,
quote,
quote_spanned,
};
use syn::spanned::Spanned as _;
#[derive(From)]
pub struct CallBuilder<'a> {
contract: &'a ir::Contract,
}
impl GenerateCode for CallBuilder<'_> {
fn generate_code(&self) -> TokenStream2 {
let call_builder_struct = self.generate_struct();
let auxiliary_trait_impls = self.generate_auxiliary_trait_impls();
let call_builder_impls = self.generate_call_forwarder_impls();
let call_builder_inherent_impls = self.generate_call_builder_inherent_impls();
quote! {
const _: () = {
#call_builder_struct
#auxiliary_trait_impls
#call_builder_impls
#call_builder_inherent_impls
};
}
}
}
impl CallBuilder<'_> {
fn call_builder_ident() -> syn::Ident {
format_ident!("CallBuilder")
}
fn generate_struct(&self) -> TokenStream2 {
let span = self.contract.module().storage().span();
let storage_ident = self.contract.module().storage().ident();
let cb_ident = Self::call_builder_ident();
quote_spanned!(span=>
#[repr(transparent)]
#[cfg_attr(feature = "std", derive(
::ink::storage::traits::StorageLayout,
))]
#[derive(
::core::fmt::Debug,
::core::hash::Hash,
::core::cmp::PartialEq,
::core::cmp::Eq,
::core::clone::Clone,
)]
#[::ink::scale_derive(Encode, Decode, TypeInfo)]
pub struct #cb_ident {
account_id: AccountId,
}
const _: () = {
impl ::ink::codegen::ContractCallBuilder for #storage_ident {
type Type = #cb_ident;
}
impl ::ink::env::ContractEnv for #cb_ident {
type Env = <#storage_ident as ::ink::env::ContractEnv>::Env;
}
};
)
}
fn generate_auxiliary_trait_impls(&self) -> TokenStream2 {
let span = self.contract.module().storage().span();
let cb_ident = Self::call_builder_ident();
quote_spanned!(span=>
impl ::ink::env::call::FromAccountId<Environment> for #cb_ident {
#[inline]
fn from_account_id(account_id: AccountId) -> Self {
Self { account_id }
}
}
impl ::ink::ToAccountId<Environment> for #cb_ident {
#[inline]
fn to_account_id(&self) -> AccountId {
<AccountId as ::core::clone::Clone>::clone(&self.account_id)
}
}
impl ::core::convert::AsRef<AccountId> for #cb_ident {
fn as_ref(&self) -> &AccountId {
&self.account_id
}
}
impl ::core::convert::AsMut<AccountId> for #cb_ident {
fn as_mut(&mut self) -> &mut AccountId {
&mut self.account_id
}
}
)
}
fn generate_call_forwarder_impls(&self) -> TokenStream2 {
self.contract
.module()
.impls()
.filter_map(|impl_block| {
impl_block.trait_path().map(|trait_path| {
self.generate_code_for_trait_impl(trait_path, impl_block)
})
})
.collect()
}
fn generate_code_for_trait_impl(
&self,
trait_path: &syn::Path,
impl_block: &ir::ItemImpl,
) -> TokenStream2 {
let call_forwarder_impl =
self.generate_call_forwarder_for_trait_impl(trait_path, impl_block);
let ink_trait_impl = self.generate_ink_trait_impl(trait_path, impl_block);
quote! {
#call_forwarder_impl
#ink_trait_impl
}
}
fn generate_call_forwarder_for_trait_impl(
&self,
trait_path: &syn::Path,
impl_block: &ir::ItemImpl,
) -> TokenStream2 {
let span = impl_block.span();
let cb_ident = Self::call_builder_ident();
let trait_info_id = generator::generate_reference_to_trait_info(span, trait_path);
quote_spanned!(span=>
#[doc(hidden)]
impl ::ink::codegen::TraitCallForwarderFor<{#trait_info_id}> for #cb_ident {
type Forwarder = <<Self as #trait_path>::__ink_TraitInfo as ::ink::codegen::TraitCallForwarder>::Forwarder;
#[inline]
fn forward(&self) -> &Self::Forwarder {
unsafe {
&*(&self.account_id as *const AccountId as *const Self::Forwarder)
}
}
#[inline]
fn forward_mut(&mut self) -> &mut Self::Forwarder {
unsafe {
&mut *(&mut self.account_id as *mut AccountId as *mut Self::Forwarder)
}
}
#[inline]
fn build(&self) -> &<Self::Forwarder as ::ink::codegen::TraitCallBuilder>::Builder {
<_ as ::ink::codegen::TraitCallBuilder>::call(
<Self as ::ink::codegen::TraitCallForwarderFor<{#trait_info_id}>>::forward(self)
)
}
#[inline]
fn build_mut(&mut self)
-> &mut <Self::Forwarder as ::ink::codegen::TraitCallBuilder>::Builder
{
<_ as ::ink::codegen::TraitCallBuilder>::call_mut(
<Self as ::ink::codegen::TraitCallForwarderFor<{#trait_info_id}>>::forward_mut(self)
)
}
}
)
}
fn generate_ink_trait_impl(
&self,
trait_path: &syn::Path,
impl_block: &ir::ItemImpl,
) -> TokenStream2 {
let span = impl_block.span();
let cb_ident = Self::call_builder_ident();
let messages = impl_block
.iter_messages()
.map(|message| self.generate_ink_trait_impl_for_message(trait_path, message));
quote_spanned!(span=>
impl #trait_path for #cb_ident {
type __ink_TraitInfo = <::ink::reflect::TraitDefinitionRegistry<Environment>
as #trait_path>::__ink_TraitInfo;
#( #messages )*
}
)
}
fn generate_ink_trait_impl_for_message(
&self,
trait_path: &syn::Path,
message: ir::CallableWithSelector<ir::Message>,
) -> TokenStream2 {
use ir::Callable as _;
let span = message.span();
let message_ident = message.ident();
let output_ident = generator::output_ident(message_ident);
let cfg_attrs = message.get_cfg_attrs(span);
let trait_info_id = generator::generate_reference_to_trait_info(span, trait_path);
let (input_bindings, input_types): (Vec<_>, Vec<_>) = message
.callable()
.inputs()
.map(|input| (&input.pat, &input.ty))
.unzip();
let mut_token = message
.receiver()
.is_ref_mut()
.then(|| Some(quote! { mut }));
let build_cmd = match message.receiver() {
ir::Receiver::Ref => quote! { build },
ir::Receiver::RefMut => quote! { build_mut },
};
let attrs = self
.contract
.config()
.whitelisted_attributes()
.filter_attr(message.attrs().to_vec());
quote_spanned!(span=>
#( #cfg_attrs )*
type #output_ident = <<<
Self
as ::ink::codegen::TraitCallForwarderFor<{#trait_info_id}>>::Forwarder
as ::ink::codegen::TraitCallBuilder>::Builder
as #trait_path>::#output_ident;
#[inline]
#( #attrs )*
fn #message_ident(
& #mut_token self
#( , #input_bindings: #input_types )*
) -> Self::#output_ident {
<_ as #trait_path>::#message_ident(
<Self as ::ink::codegen::TraitCallForwarderFor<{#trait_info_id}>>::#build_cmd(self)
#( , #input_bindings )*
)
}
)
}
fn generate_call_builder_inherent_impls(&self) -> TokenStream2 {
self.contract
.module()
.impls()
.filter(|impl_block| impl_block.trait_path().is_none())
.map(|impl_block| self.generate_call_builder_inherent_impl(impl_block))
.collect()
}
fn generate_call_builder_inherent_impl(
&self,
impl_block: &ir::ItemImpl,
) -> TokenStream2 {
let span = impl_block.span();
let cb_ident = Self::call_builder_ident();
let messages = impl_block
.iter_messages()
.map(|message| self.generate_call_builder_inherent_impl_for_message(message));
quote_spanned!(span=>
impl #cb_ident {
#( #messages )*
}
)
}
fn generate_call_builder_inherent_impl_for_message(
&self,
message: ir::CallableWithSelector<ir::Message>,
) -> TokenStream2 {
let span = message.span();
let callable = message.callable();
let message_ident = message.ident();
let attrs = self
.contract
.config()
.whitelisted_attributes()
.filter_attr(message.attrs().to_vec());
let selector = message.composed_selector();
let selector_bytes = selector.hex_lits();
let input_bindings = generator::input_bindings(callable.inputs());
let input_types = generator::input_types(message.inputs());
let arg_list = generator::generate_argument_list(input_types.iter().cloned());
let mut_tok = callable.receiver().is_ref_mut().then(|| quote! { mut });
let return_type = message
.output()
.map(quote::ToTokens::to_token_stream)
.unwrap_or_else(|| quote::quote! { () });
let output_span = return_type.span();
let output_type = quote_spanned!(output_span=>
::ink::env::call::CallBuilder<
Environment,
::ink::env::call::utils::Set< ::ink::env::call::Call< Environment > >,
::ink::env::call::utils::Set< ::ink::env::call::ExecutionInput<#arg_list> >,
::ink::env::call::utils::Set< ::ink::env::call::utils::ReturnType<#return_type> >,
>
);
quote_spanned!(span=>
#( #attrs )*
#[allow(clippy::type_complexity)]
#[inline]
pub fn #message_ident(
& #mut_tok self
#( , #input_bindings : #input_types )*
) -> #output_type {
::ink::env::call::build_call::<Environment>()
.call(::ink::ToAccountId::to_account_id(self))
.exec_input(
::ink::env::call::ExecutionInput::new(
::ink::env::call::Selector::new([ #( #selector_bytes ),* ])
)
#(
.push_arg(#input_bindings)
)*
)
.returns::<#return_type>()
}
)
}
}