From 5f47ad6947862d73453c867057458d727057f480 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Wed, 20 Dec 2023 23:15:50 +0100 Subject: [PATCH] fix: Fixed `contract_source_metadata` compilation issue when multiple impl blocks are there (#1118) --- .../src/core_impl/code_generator/metadata.rs | 17 +++ .../src/core_impl/code_generator/mod.rs | 1 + near-sdk-macros/src/lib.rs | 109 +++++++++++------- near-sdk/compilation_tests/all.rs | 1 + .../contract_metadata_bindgen.rs | 20 ++++ near-sdk/compilation_tests/impl_generic.rs | 1 + .../compilation_tests/impl_generic.stderr | 10 +- 7 files changed, 115 insertions(+), 44 deletions(-) create mode 100644 near-sdk-macros/src/core_impl/code_generator/metadata.rs create mode 100644 near-sdk/compilation_tests/contract_metadata_bindgen.rs diff --git a/near-sdk-macros/src/core_impl/code_generator/metadata.rs b/near-sdk-macros/src/core_impl/code_generator/metadata.rs new file mode 100644 index 000000000..a42703a1e --- /dev/null +++ b/near-sdk-macros/src/core_impl/code_generator/metadata.rs @@ -0,0 +1,17 @@ +use proc_macro2::Ident; +use quote::quote; +use syn::Generics; + +/// Generates a view method to retrieve the source metadata. +pub(crate) fn generate_contract_metadata_method( + ident: &Ident, + generics: &Generics, +) -> proc_macro2::TokenStream { + quote! { + impl #generics #ident #generics { + pub fn contract_source_metadata() { + near_sdk::env::value_return(CONTRACT_SOURCE_METADATA.as_bytes()) + } + } + } +} diff --git a/near-sdk-macros/src/core_impl/code_generator/mod.rs b/near-sdk-macros/src/core_impl/code_generator/mod.rs index 1e461c2c1..112daedc4 100644 --- a/near-sdk-macros/src/core_impl/code_generator/mod.rs +++ b/near-sdk-macros/src/core_impl/code_generator/mod.rs @@ -11,5 +11,6 @@ mod item_impl_info; pub use item_impl_info::*; pub(crate) mod ext; +pub(crate) mod metadata; pub(crate) mod serializer; diff --git a/near-sdk-macros/src/lib.rs b/near-sdk-macros/src/lib.rs index 4cc86a97b..d6039ea45 100644 --- a/near-sdk-macros/src/lib.rs +++ b/near-sdk-macros/src/lib.rs @@ -3,11 +3,12 @@ extern crate proc_macro; mod core_impl; -use core_impl::ext::generate_ext_structs; +use core_impl::{ext::generate_ext_structs, metadata::generate_contract_metadata_method}; + use proc_macro::TokenStream; use self::core_impl::*; -use proc_macro2::Span; +use proc_macro2::{Ident, Span}; use quote::{quote, ToTokens}; use syn::{parse_quote, ImplItem, ItemEnum, ItemImpl, ItemStruct, ItemTrait, WhereClause}; @@ -114,8 +115,25 @@ pub fn near_bindgen(attr: TokenStream, item: TokenStream) -> TokenStream { return core_impl::near_events(attr, item); } + let generate_metadata = |ident: &Ident, + generics: &syn::Generics| + -> Result { + let metadata_impl_gen = generate_contract_metadata_method(ident, generics).into(); + let metadata_impl_gen = syn::parse::(metadata_impl_gen) + .expect("failed to generate contract metadata"); + process_impl_block(metadata_impl_gen) + }; + if let Ok(input) = syn::parse::(item.clone()) { let metadata = core_impl::contract_source_metadata_const(attr); + + let metadata_impl_gen = generate_metadata(&input.ident, &input.generics); + + let metadata_impl_gen = match metadata_impl_gen { + Ok(metadata) => metadata, + Err(err) => return err.into(), + }; + let ext_gen = generate_ext_structs(&input.ident, Some(&input.generics)); #[cfg(feature = "__abi-embed-checked")] let abi_embedded = abi::embed(); @@ -126,9 +144,17 @@ pub fn near_bindgen(attr: TokenStream, item: TokenStream) -> TokenStream { #ext_gen #abi_embedded #metadata + #metadata_impl_gen }) } else if let Ok(input) = syn::parse::(item.clone()) { let metadata = core_impl::contract_source_metadata_const(attr); + let metadata_impl_gen = generate_metadata(&input.ident, &input.generics); + + let metadata_impl_gen = match metadata_impl_gen { + Ok(metadata) => metadata, + Err(err) => return err.into(), + }; + let ext_gen = generate_ext_structs(&input.ident, Some(&input.generics)); #[cfg(feature = "__abi-embed-checked")] let abi_embedded = abi::embed(); @@ -139,8 +165,9 @@ pub fn near_bindgen(attr: TokenStream, item: TokenStream) -> TokenStream { #ext_gen #abi_embedded #metadata + #metadata_impl_gen }) - } else if let Ok(mut input) = syn::parse::(item) { + } else if let Ok(input) = syn::parse::(item) { for method in &input.items { if let ImplItem::Fn(m) = method { let ident = &m.sig.ident; @@ -155,46 +182,11 @@ pub fn near_bindgen(attr: TokenStream, item: TokenStream) -> TokenStream { } } } - - if input.trait_.is_none() { - let contract_source_metadata = quote! { - pub fn contract_source_metadata() { - near_sdk::env::value_return(CONTRACT_SOURCE_METADATA.as_bytes()) - } - }; - - match syn::parse2::(contract_source_metadata) { - Ok(x) => { - input.items.push(x); - } - Err(err) => { - return err.to_compile_error().into(); - } - } + match process_impl_block(input) { + Ok(output) => output, + Err(output) => output, } - - let item_impl_info = match ItemImplInfo::new(&mut input) { - Ok(x) => x, - Err(err) => { - return err.to_compile_error().into(); - } - }; - - #[cfg(not(feature = "__abi-generate"))] - let abi_generated = quote! {}; - #[cfg(feature = "__abi-generate")] - let abi_generated = abi::generate(&item_impl_info); - - let generated_code = item_impl_info.wrapper_code(); - - // Add wrapper methods for ext call API - let ext_generated_code = item_impl_info.generate_ext_wrapper_code(); - TokenStream::from(quote! { - #ext_generated_code - #input - #generated_code - #abi_generated - }) + .into() } else { TokenStream::from( syn::Error::new( @@ -206,6 +198,39 @@ pub fn near_bindgen(attr: TokenStream, item: TokenStream) -> TokenStream { } } +// This function deals with impl block processing, generating wrappers and ABI. +// +// # Arguments +// * input - impl block to process. +// +// The Result has a TokenStream error type, because those need to be propagated to the compiler. +fn process_impl_block( + mut input: ItemImpl, +) -> Result { + let item_impl_info = match ItemImplInfo::new(&mut input) { + Ok(x) => x, + Err(err) => return Err(err.to_compile_error()), + }; + + #[cfg(not(feature = "__abi-generate"))] + let abi_generated = quote! {}; + #[cfg(feature = "__abi-generate")] + let abi_generated = abi::generate(&item_impl_info); + + let generated_code = item_impl_info.wrapper_code(); + + // Add wrapper methods for ext call API + let ext_generated_code = item_impl_info.generate_ext_wrapper_code(); + + Ok(TokenStream::from(quote! { + #ext_generated_code + #input + #generated_code + #abi_generated + }) + .into()) +} + /// `ext_contract` takes a Rust Trait and converts it to a module with static methods. /// Each of these static methods takes positional arguments defined by the Trait, /// then the receiver_id, the attached deposit and the amount of gas and returns a new Promise. diff --git a/near-sdk/compilation_tests/all.rs b/near-sdk/compilation_tests/all.rs index e893c8eba..e78f316e2 100644 --- a/near-sdk/compilation_tests/all.rs +++ b/near-sdk/compilation_tests/all.rs @@ -35,4 +35,5 @@ fn compilation_tests() { t.pass("compilation_tests/handle_result_alias.rs"); t.pass("compilation_tests/contract_metadata.rs"); t.compile_fail("compilation_tests/contract_metadata_fn_name.rs"); + t.pass("compilation_tests/contract_metadata_bindgen.rs"); } diff --git a/near-sdk/compilation_tests/contract_metadata_bindgen.rs b/near-sdk/compilation_tests/contract_metadata_bindgen.rs new file mode 100644 index 000000000..02d9ab5c3 --- /dev/null +++ b/near-sdk/compilation_tests/contract_metadata_bindgen.rs @@ -0,0 +1,20 @@ +use near_account_id::AccountIdRef; +use near_sdk::near_bindgen; + +#[near_bindgen] +struct Contract {} + +#[near_bindgen] +impl Contract { + pub fn anything() {} +} + +#[near_bindgen] +impl Contract { + pub fn anything_else() {} +} + +fn main() { + let ext = Contract::ext(AccountIdRef::new_or_panic("0000").into()); + ext.contract_source_metadata(); +} diff --git a/near-sdk/compilation_tests/impl_generic.rs b/near-sdk/compilation_tests/impl_generic.rs index 4348cdbf9..11f2ec5d7 100644 --- a/near-sdk/compilation_tests/impl_generic.rs +++ b/near-sdk/compilation_tests/impl_generic.rs @@ -2,6 +2,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use near_sdk::near_bindgen; +#[allow(unused_imports)] use std::marker::PhantomData; #[near_bindgen] diff --git a/near-sdk/compilation_tests/impl_generic.stderr b/near-sdk/compilation_tests/impl_generic.stderr index 866318f58..b4850777b 100644 --- a/near-sdk/compilation_tests/impl_generic.stderr +++ b/near-sdk/compilation_tests/impl_generic.stderr @@ -1,5 +1,11 @@ error: Impl type parameters are not supported for smart contracts. - --> $DIR/impl_generic.rs:15:6 + --> compilation_tests/impl_generic.rs:10:20 | -15 | impl<'a, T: 'a + std::fmt::Display> Incrementer { +10 | struct Incrementer { + | ^ + +error: Impl type parameters are not supported for smart contracts. + --> compilation_tests/impl_generic.rs:16:6 + | +16 | impl<'a, T: 'a + std::fmt::Display> Incrementer { | ^^