diff --git a/near-sdk-macros/src/core_impl/abi/abi_generator.rs b/near-sdk-macros/src/core_impl/abi/abi_generator.rs index 4089863ce..032402816 100644 --- a/near-sdk-macros/src/core_impl/abi/abi_generator.rs +++ b/near-sdk-macros/src/core_impl/abi/abi_generator.rs @@ -13,7 +13,16 @@ pub fn generate(i: &ItemImplInfo) -> TokenStream2 { } let functions: Vec = i.methods.iter().map(|m| m.abi_struct()).collect(); - let first_function_name = &i.methods[0].attr_signature_info.ident; + let ident_ = &i.methods[0].attr_signature_info.ident; + let first_function_name = match &i.methods[0].attr_signature_info.method_kind { + MethodKind::View(m) => m + .alias + .as_ref() + .map(|alias| syn::Ident::new(alias, ident_.span())) + .unwrap_or(ident_.clone()), + _ => ident_.clone(), + }; + let near_abi_symbol = format_ident!("__near_abi_{}", first_function_name); quote! { #[cfg(not(target_arch = "wasm32"))] @@ -79,7 +88,13 @@ impl ImplItemMethodInfo { pub fn abi_struct(&self) -> TokenStream2 { let attr_signature_info = &self.attr_signature_info; - let function_name_str = attr_signature_info.ident.to_string(); + let ident_ = attr_signature_info.ident.to_string(); + let function_name_str = match &attr_signature_info.method_kind { + MethodKind::View(m) => m.alias.clone().unwrap_or(ident_.to_string()), + MethodKind::Call(m) => m.alias.clone().unwrap_or(ident_.to_string()), + MethodKind::Init(_) => ident_.to_string(), + }; + let function_doc = match parse_rustdoc(&attr_signature_info.non_bindgen_attrs) { Some(doc) => quote! { ::std::option::Option::Some(::std::string::String::from(#doc)) }, None => quote! { ::std::option::Option::None }, @@ -324,7 +339,7 @@ mod tests { #[handle_result] pub fn f3(&mut self, arg0: FancyStruct, arg1: u64) -> Result { } }; - let method_info = ImplItemMethodInfo::new(&mut method, false, impl_type).unwrap().unwrap(); + let method_info = ImplItemMethodInfo::new(&mut method, None, impl_type).unwrap().unwrap(); let actual = method_info.abi_struct(); local_insta_assert_snapshot!(pretty_print_fn_body_syn_str(actual)); @@ -339,7 +354,7 @@ mod tests { #[handle_result] pub fn f3(&mut self, #[serializer(borsh)] arg0: FancyStruct) -> Result { } }; - let method_info = ImplItemMethodInfo::new(&mut method, false, impl_type).unwrap().unwrap(); + let method_info = ImplItemMethodInfo::new(&mut method, None, impl_type).unwrap().unwrap(); let actual = method_info.abi_struct(); local_insta_assert_snapshot!(pretty_print_fn_body_syn_str(actual)); @@ -355,7 +370,7 @@ mod tests { #[callback_vec] x: Vec, ) -> bool { } }; - let method_info = ImplItemMethodInfo::new(&mut method, false, impl_type).unwrap().unwrap(); + let method_info = ImplItemMethodInfo::new(&mut method, None, impl_type).unwrap().unwrap(); let actual = method_info.abi_struct(); local_insta_assert_snapshot!(pretty_print_fn_body_syn_str(actual)); @@ -367,7 +382,7 @@ mod tests { let mut method = parse_quote! { pub fn method(&self, #[callback_unwrap] #[serializer(borsh)] x: &mut u64, #[serializer(borsh)] y: String, #[callback_unwrap] #[serializer(json)] z: Vec) { } }; - let method_info = ImplItemMethodInfo::new(&mut method, false, impl_type).unwrap().unwrap(); + let method_info = ImplItemMethodInfo::new(&mut method, None, impl_type).unwrap().unwrap(); let actual = method_info.abi_struct(); local_insta_assert_snapshot!(pretty_print_fn_body_syn_str(actual)); @@ -380,7 +395,7 @@ mod tests { #[init(ignore_state)] pub fn new() -> u64 { } }; - let method_info = ImplItemMethodInfo::new(&mut method, false, impl_type).unwrap().unwrap(); + let method_info = ImplItemMethodInfo::new(&mut method, None, impl_type).unwrap().unwrap(); let actual = method_info.abi_struct(); local_insta_assert_snapshot!(pretty_print_fn_body_syn_str(actual)); @@ -392,7 +407,7 @@ mod tests { let mut method = parse_quote! { pub fn method() { } }; - let method_info = ImplItemMethodInfo::new(&mut method, false, impl_type).unwrap().unwrap(); + let method_info = ImplItemMethodInfo::new(&mut method, None, impl_type).unwrap().unwrap(); let actual = method_info.abi_struct(); local_insta_assert_snapshot!(pretty_print_fn_body_syn_str(actual)); diff --git a/near-sdk-macros/src/core_impl/code_generator/ext.rs b/near-sdk-macros/src/core_impl/code_generator/ext.rs index 910c8f1f8..76d916e23 100644 --- a/near-sdk-macros/src/core_impl/code_generator/ext.rs +++ b/near-sdk-macros/src/core_impl/code_generator/ext.rs @@ -1,4 +1,7 @@ -use crate::core_impl::{serializer, AttrSigInfo}; +use crate::{ + core_impl::{serializer, AttrSigInfo}, + MethodKind, +}; use proc_macro2::{Ident, Span, TokenStream as TokenStream2}; use quote::{format_ident, quote, ToTokens}; use syn::{parse_quote, Attribute, Generics, Path, Signature}; @@ -116,8 +119,21 @@ fn generate_ext_function(attr_signature_info: &AttrSigInfo) -> TokenStream2 { let serialize = serializer::generate_serializer(attr_signature_info, &attr_signature_info.input_serializer); - let AttrSigInfo { non_bindgen_attrs, ident, original_sig, .. } = attr_signature_info; - let ident_str = ident.to_string(); + let AttrSigInfo { non_bindgen_attrs, ident, original_sig, method_kind, .. } = + attr_signature_info; + + let ident_ = ident.clone().to_string(); + let ident_str = match method_kind { + MethodKind::Call(m) => &m.alias, + MethodKind::Init(_) => &None, + MethodKind::View(m) => &m.alias, + } + .as_ref() + .unwrap_or(&ident_) + .to_string(); + + let ident = &Ident::new(&ident_str, ident.span()); + let mut new_non_bindgen_attrs = TokenStream2::new(); for attribute in non_bindgen_attrs.iter() { if is_fn_attribute_to_forward(attribute) { @@ -154,7 +170,7 @@ mod tests { fn ext_gen() { let st: ItemStruct = parse_quote! { struct Test { a: u8 } }; let actual = generate_ext_structs(&st.ident, Some(&st.generics)); - + local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } @@ -162,7 +178,7 @@ mod tests { fn module_ext_gen() { let ident: Ident = parse_quote! { Test }; let actual = generate_ext_structs(&ident, None); - + local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } @@ -177,7 +193,7 @@ mod tests { #[warn(unused)] pub fn method(&self) { } }; - let method_info = ImplItemMethodInfo::new(&mut method, false, impl_type).unwrap().unwrap(); + let method_info = ImplItemMethodInfo::new(&mut method, None, impl_type).unwrap().unwrap(); let actual = generate_ext_function(&method_info.attr_signature_info); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); @@ -189,9 +205,9 @@ mod tests { let mut method: ImplItemFn = parse_quote! { pub fn method(&self, k: &String) { } }; - let method_info = ImplItemMethodInfo::new(&mut method, false, impl_type).unwrap().unwrap(); + let method_info = ImplItemMethodInfo::new(&mut method, None, impl_type).unwrap().unwrap(); let actual = generate_ext_function(&method_info.attr_signature_info); - + local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } @@ -201,9 +217,9 @@ mod tests { let mut method: syn::ImplItemFn = parse_quote! { pub fn borsh_test(&mut self, #[serializer(borsh)] a: String) {} }; - let method_info = ImplItemMethodInfo::new(&mut method, false, impl_type).unwrap().unwrap(); + let method_info = ImplItemMethodInfo::new(&mut method, None, impl_type).unwrap().unwrap(); let actual = generate_ext_function(&method_info.attr_signature_info); - + local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } } diff --git a/near-sdk-macros/src/core_impl/code_generator/impl_item_method_info.rs b/near-sdk-macros/src/core_impl/code_generator/impl_item_method_info.rs index fd5d507d2..b5f636e9d 100644 --- a/near-sdk-macros/src/core_impl/code_generator/impl_item_method_info.rs +++ b/near-sdk-macros/src/core_impl/code_generator/impl_item_method_info.rs @@ -2,17 +2,17 @@ use crate::core_impl::info_extractor::{ImplItemMethodInfo, SerializerType}; use crate::core_impl::{MethodKind, ReturnKind}; use proc_macro2::TokenStream as TokenStream2; use quote::quote; -use syn::Receiver; +use syn::{Receiver, Type}; impl ImplItemMethodInfo { /// Generate wrapper method for the given method of the contract. pub fn method_wrapper(&self) -> TokenStream2 { let non_bindgen_attrs = self.non_bindgen_attrs_tokens(); - let ident = &self.attr_signature_info.ident; - let panic_hook = self.panic_hook_tokens(); + let ident = self.abi_alias(); + let arg_struct = self.arg_struct_tokens(); let arg_parsing = self.arg_parsing_tokens(); @@ -274,11 +274,29 @@ impl ImplItemMethodInfo { fn method_invocation_tokens(&self) -> TokenStream2 { use MethodKind::*; - let ident = &self.attr_signature_info.ident; let arg_list = self.attr_signature_info.arg_list(); - let method_invocation = || { + let method_invocation = |receiver: &Receiver| { + let _mut = receiver.mutability.map_or(quote! {}, |_| quote! {mut}); + + if let Some((_, path, _)) = &self.attr_signature_info.trait_ { + if let Some(trait_ident) = path.get_ident() { + return quote! { + #trait_ident::#ident(&#_mut contract, #arg_list) + }; + }; + } + + if let Type::Path(val) = &self.struct_type { + if !val.path.segments.is_empty() { + let contract_ty = &val.path.segments[0].ident; + quote! { + #contract_ty::#ident(&#_mut contract, #arg_list) + }; + } + } + quote! { contract.#ident(#arg_list) } @@ -292,24 +310,18 @@ impl ImplItemMethodInfo { }; match &self.attr_signature_info.method_kind { - Call(call_method) => { - if call_method.receiver.is_some() { - method_invocation() - } else { - static_invocation() - } - } + Call(call_method) => match &call_method.receiver { + Some(receiver) => method_invocation(receiver), + None => static_invocation(), + }, // The method invocation in Init methods is done in contract initialization. Init(_) => quote! {}, - View(view_method) => { - if view_method.receiver.is_some() { - method_invocation() - } else { - static_invocation() - } - } + View(view_method) => match &view_method.receiver { + Some(receiver) => method_invocation(receiver), + None => static_invocation(), + }, } } @@ -402,4 +414,15 @@ impl ImplItemMethodInfo { } }) } + + fn abi_alias(&self) -> syn::Ident { + match &self.attr_signature_info.method_kind { + MethodKind::Call(call_method) => &call_method.alias, + MethodKind::Init(_) => &None, + MethodKind::View(view_method) => &view_method.alias, + } + .as_ref() + .map(|alias| syn::Ident::new(alias, self.attr_signature_info.ident.span())) + .unwrap_or(self.attr_signature_info.ident.clone()) + } } diff --git a/near-sdk-macros/src/core_impl/code_generator/item_impl_info.rs b/near-sdk-macros/src/core_impl/code_generator/item_impl_info.rs index 1dd3c3605..ef0733a9f 100644 --- a/near-sdk-macros/src/core_impl/code_generator/item_impl_info.rs +++ b/near-sdk-macros/src/core_impl/code_generator/item_impl_info.rs @@ -28,16 +28,19 @@ impl ItemImplInfo { #[rustfmt::skip] #[cfg(test)] mod tests { - use syn::{Type, ImplItemFn, parse_quote}; + use proc_macro2::Span; + use syn::punctuated::Punctuated; + use syn::token::{Not, For}; + use syn::{parse_quote, ImplItemFn, Path, Type}; use crate::core_impl::info_extractor::ImplItemMethodInfo; use crate::core_impl::utils::test_helpers::{local_insta_assert_snapshot, pretty_print_syn_str}; - #[test] fn trait_implt() { let impl_type: Type = syn::parse_str("Hello").unwrap(); let mut method: ImplItemFn = syn::parse_str("fn method(&self) { }").unwrap(); - let method_info = ImplItemMethodInfo::new(&mut method, true, impl_type).unwrap().unwrap(); + let _trait: (Option, Path, For) = (None,Path{leading_colon: None, segments: Punctuated::new()}, For{span: Span::call_site()}); + let method_info = ImplItemMethodInfo::new(&mut method, Some(_trait), impl_type).unwrap().unwrap(); let actual = method_info.method_wrapper(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } @@ -46,7 +49,7 @@ mod tests { fn no_args_no_return_no_mut() { let impl_type: Type = syn::parse_str("Hello").unwrap(); let mut method: ImplItemFn = syn::parse_str("pub fn method(&self) { }").unwrap(); - let method_info = ImplItemMethodInfo::new(&mut method, false, impl_type).unwrap().unwrap(); + let method_info = ImplItemMethodInfo::new(&mut method, None, impl_type).unwrap().unwrap(); let actual = method_info.method_wrapper(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } @@ -55,17 +58,16 @@ mod tests { fn owned_no_args_no_return_no_mut() { let impl_type: Type = syn::parse_str("Hello").unwrap(); let mut method: ImplItemFn = syn::parse_str("pub fn method(self) { }").unwrap(); - let method_info = ImplItemMethodInfo::new(&mut method, false, impl_type).unwrap().unwrap(); + let method_info = ImplItemMethodInfo::new(&mut method, None, impl_type).unwrap().unwrap(); let actual = method_info.method_wrapper(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } - #[test] fn mut_owned_no_args_no_return() { let impl_type: Type = syn::parse_str("Hello").unwrap(); let mut method: ImplItemFn = syn::parse_str("pub fn method(mut self) { }").unwrap(); - let method_info = ImplItemMethodInfo::new(&mut method, false, impl_type).unwrap().unwrap(); + let method_info = ImplItemMethodInfo::new(&mut method, None, impl_type).unwrap().unwrap(); let actual = method_info.method_wrapper(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } @@ -74,7 +76,7 @@ mod tests { fn no_args_no_return_mut() { let impl_type: Type = syn::parse_str("Hello").unwrap(); let mut method: ImplItemFn = syn::parse_str("pub fn method(&mut self) { }").unwrap(); - let method_info = ImplItemMethodInfo::new(&mut method, false, impl_type).unwrap().unwrap(); + let method_info = ImplItemMethodInfo::new(&mut method, None, impl_type).unwrap().unwrap(); let actual = method_info.method_wrapper(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } @@ -83,7 +85,7 @@ mod tests { fn arg_no_return_no_mut() { let impl_type: Type = syn::parse_str("Hello").unwrap(); let mut method: ImplItemFn = syn::parse_str("pub fn method(&self, k: u64) { }").unwrap(); - let method_info = ImplItemMethodInfo::new(&mut method, false, impl_type).unwrap().unwrap(); + let method_info = ImplItemMethodInfo::new(&mut method, None, impl_type).unwrap().unwrap(); let actual = method_info.method_wrapper(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } @@ -93,7 +95,7 @@ mod tests { let impl_type: Type = syn::parse_str("Hello").unwrap(); let mut method: ImplItemFn = syn::parse_str("pub fn method(&mut self, k: u64, m: Bar) { }").unwrap(); - let method_info = ImplItemMethodInfo::new(&mut method, false, impl_type).unwrap().unwrap(); + let method_info = ImplItemMethodInfo::new(&mut method, None, impl_type).unwrap().unwrap(); let actual = method_info.method_wrapper(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } @@ -103,7 +105,7 @@ mod tests { let impl_type: Type = syn::parse_str("Hello").unwrap(); let mut method: ImplItemFn = syn::parse_str("pub fn method(&mut self, k: u64, m: Bar) -> Option { }").unwrap(); - let method_info = ImplItemMethodInfo::new(&mut method, false, impl_type).unwrap().unwrap(); + let method_info = ImplItemMethodInfo::new(&mut method, None, impl_type).unwrap().unwrap(); let actual = method_info.method_wrapper(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } @@ -113,7 +115,7 @@ mod tests { let impl_type: Type = syn::parse_str("Hello").unwrap(); let mut method: ImplItemFn = syn::parse_str("pub fn method(&self) -> &Option { }").unwrap(); - let method_info = ImplItemMethodInfo::new(&mut method, false, impl_type).unwrap().unwrap(); + let method_info = ImplItemMethodInfo::new(&mut method, None, impl_type).unwrap().unwrap(); let actual = method_info.method_wrapper(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } @@ -122,7 +124,7 @@ mod tests { fn arg_ref() { let impl_type: Type = syn::parse_str("Hello").unwrap(); let mut method: ImplItemFn = syn::parse_str("pub fn method(&self, k: &u64) { }").unwrap(); - let method_info = ImplItemMethodInfo::new(&mut method, false, impl_type).unwrap().unwrap(); + let method_info = ImplItemMethodInfo::new(&mut method, None, impl_type).unwrap().unwrap(); let actual = method_info.method_wrapper(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } @@ -132,7 +134,7 @@ mod tests { let impl_type: Type = syn::parse_str("Hello").unwrap(); let mut method: ImplItemFn = syn::parse_str("pub fn method(&self, k: &mut u64) { }").unwrap(); - let method_info = ImplItemMethodInfo::new(&mut method, false, impl_type).unwrap().unwrap(); + let method_info = ImplItemMethodInfo::new(&mut method, None, impl_type).unwrap().unwrap(); let actual = method_info.method_wrapper(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } @@ -143,7 +145,7 @@ mod tests { let mut method: ImplItemFn = parse_quote! { #[private] pub fn method(&self, #[callback_unwrap] x: &mut u64, y: ::std::string::String, #[callback_unwrap] z: ::std::vec::Vec) { } }; - let method_info = ImplItemMethodInfo::new(&mut method, false, impl_type).unwrap().unwrap(); + let method_info = ImplItemMethodInfo::new(&mut method, None, impl_type).unwrap().unwrap(); let actual = method_info.method_wrapper(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } @@ -154,7 +156,7 @@ mod tests { let mut method: ImplItemFn = parse_quote! { #[private] pub fn method(&self, #[callback_unwrap] x: &mut u64, #[callback_unwrap] y: ::std::string::String) { } }; - let method_info = ImplItemMethodInfo::new(&mut method, false, impl_type).unwrap().unwrap(); + let method_info = ImplItemMethodInfo::new(&mut method, None, impl_type).unwrap().unwrap(); let actual = method_info.method_wrapper(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } @@ -165,7 +167,7 @@ mod tests { let mut method: ImplItemFn = parse_quote! { #[private] pub fn method(&self, #[callback_result] x: &mut Result, #[callback_result] y: Result<::std::string::String, PromiseError>) { } }; - let method_info = ImplItemMethodInfo::new(&mut method, false, impl_type).unwrap().unwrap(); + let method_info = ImplItemMethodInfo::new(&mut method, None, impl_type).unwrap().unwrap(); let actual = method_info.method_wrapper(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } @@ -176,7 +178,7 @@ mod tests { let mut method: ImplItemFn = parse_quote! { #[private] pub fn method(&self, #[callback_vec] x: Vec, y: String) { } }; - let method_info = ImplItemMethodInfo::new(&mut method, false, impl_type).unwrap().unwrap(); + let method_info = ImplItemMethodInfo::new(&mut method, None, impl_type).unwrap().unwrap(); let actual = method_info.method_wrapper(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } @@ -188,7 +190,7 @@ mod tests { #[init] pub fn method(k: &mut u64) -> Self { } }; - let method_info = ImplItemMethodInfo::new(&mut method, false, impl_type).unwrap().unwrap(); + let method_info = ImplItemMethodInfo::new(&mut method, None, impl_type).unwrap().unwrap(); let actual = method_info.method_wrapper(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } @@ -200,7 +202,7 @@ mod tests { #[init] pub fn method(k: &mut u64) { } }; - let actual = ImplItemMethodInfo::new(&mut method, false, impl_type).map(|_| ()).unwrap_err(); + let actual = ImplItemMethodInfo::new(&mut method, None, impl_type).map(|_| ()).unwrap_err(); let expected = "Init function must return the contract state."; assert_eq!(expected, actual.to_string()); } @@ -212,7 +214,7 @@ mod tests { #[init(ignore_state)] pub fn method(k: &mut u64) -> Self { } }; - let method_info = ImplItemMethodInfo::new(&mut method, false, impl_type).unwrap().unwrap(); + let method_info = ImplItemMethodInfo::new(&mut method, None, impl_type).unwrap().unwrap(); let actual = method_info.method_wrapper(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } @@ -225,7 +227,7 @@ mod tests { #[payable] pub fn method(k: &mut u64) -> Self { } }; - let method_info = ImplItemMethodInfo::new(&mut method, false, impl_type).unwrap().unwrap(); + let method_info = ImplItemMethodInfo::new(&mut method, None, impl_type).unwrap().unwrap(); let actual = method_info.method_wrapper(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } @@ -237,7 +239,7 @@ mod tests { #[result_serializer(borsh)] pub fn method(&mut self, #[serializer(borsh)] k: u64, #[serializer(borsh)]m: Bar) -> Option { } }; - let method_info = ImplItemMethodInfo::new(&mut method, false, impl_type).unwrap().unwrap(); + let method_info = ImplItemMethodInfo::new(&mut method, None, impl_type).unwrap().unwrap(); let actual = method_info.method_wrapper(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } @@ -248,7 +250,7 @@ mod tests { let mut method: ImplItemFn = parse_quote! { #[private] pub fn method(&self, #[callback_unwrap] #[serializer(borsh)] x: &mut u64, #[serializer(borsh)] y: ::std::string::String, #[callback_unwrap] #[serializer(json)] z: ::std::vec::Vec) { } }; - let method_info = ImplItemMethodInfo::new(&mut method, false, impl_type).unwrap().unwrap(); + let method_info = ImplItemMethodInfo::new(&mut method, None, impl_type).unwrap().unwrap(); let actual = method_info.method_wrapper(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } @@ -257,7 +259,7 @@ mod tests { fn no_args_no_return_mut_payable() { let impl_type: Type = syn::parse_str("Hello").unwrap(); let mut method: ImplItemFn = syn::parse_str("#[payable] pub fn method(&mut self) { }").unwrap(); - let method_info = ImplItemMethodInfo::new(&mut method, false, impl_type).unwrap().unwrap(); + let method_info = ImplItemMethodInfo::new(&mut method, None, impl_type).unwrap().unwrap(); let actual = method_info.method_wrapper(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } @@ -266,7 +268,7 @@ mod tests { fn private_method() { let impl_type: Type = syn::parse_str("Hello").unwrap(); let mut method: ImplItemFn = syn::parse_str("#[private] pub fn private_method(&mut self) { }").unwrap(); - let method_info = ImplItemMethodInfo::new(&mut method, false, impl_type).unwrap().unwrap(); + let method_info = ImplItemMethodInfo::new(&mut method, None, impl_type).unwrap().unwrap(); let actual = method_info.method_wrapper(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } @@ -278,11 +280,11 @@ mod tests { #[handle_result] pub fn method(&self) -> Result:: { } }; - let method_info = ImplItemMethodInfo::new(&mut method, false, impl_type).unwrap().unwrap(); + let method_info = ImplItemMethodInfo::new(&mut method, None, impl_type).unwrap().unwrap(); let actual = method_info.method_wrapper(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } - + #[test] fn handle_result_mut() { let impl_type: Type = syn::parse_str("Hello").unwrap(); @@ -290,7 +292,7 @@ mod tests { #[handle_result] pub fn method(&mut self) -> Result { } }; - let method_info = ImplItemMethodInfo::new(&mut method, false, impl_type).unwrap().unwrap(); + let method_info = ImplItemMethodInfo::new(&mut method, None, impl_type).unwrap().unwrap(); let actual = method_info.method_wrapper(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } @@ -303,7 +305,7 @@ mod tests { #[result_serializer(borsh)] pub fn method(&self) -> Result { } }; - let method_info = ImplItemMethodInfo::new(&mut method, false, impl_type).unwrap().unwrap(); + let method_info = ImplItemMethodInfo::new(&mut method, None, impl_type).unwrap().unwrap(); let actual = method_info.method_wrapper(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } @@ -316,7 +318,7 @@ mod tests { #[handle_result] pub fn new() -> Result { } }; - let method_info = ImplItemMethodInfo::new(&mut method, false, impl_type).unwrap().unwrap(); + let method_info = ImplItemMethodInfo::new(&mut method, None, impl_type).unwrap().unwrap(); let actual = method_info.method_wrapper(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } @@ -329,7 +331,7 @@ mod tests { #[handle_result] pub fn new() -> Result { } }; - let method_info = ImplItemMethodInfo::new(&mut method, false, impl_type).unwrap().unwrap(); + let method_info = ImplItemMethodInfo::new(&mut method, None, impl_type).unwrap().unwrap(); let actual = method_info.method_wrapper(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } @@ -338,8 +340,58 @@ mod tests { fn handle_no_self() { let impl_type: Type = syn::parse_str("Hello").unwrap(); let mut method: ImplItemFn = syn::parse_str("pub fn method() { }").unwrap(); - let method_info = ImplItemMethodInfo::new(&mut method, false, impl_type).unwrap().unwrap(); + let method_info = ImplItemMethodInfo::new(&mut method, None, impl_type).unwrap().unwrap(); let actual = method_info.method_wrapper(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } + + #[test] + fn serialize_method_name(){ + let impl_type: Type = syn::parse_str("Hello").unwrap(); + let mut method: ImplItemFn = parse_quote! { + pub fn serialize(&mut self){ } + }; + let method_info = ImplItemMethodInfo::new(&mut method, None, impl_type).unwrap().unwrap(); + let actual = method_info.method_wrapper(); + local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()) + } + + #[test] + fn abi_aliased_method(){ + let impl_type: Type = syn::parse_str("Hello").unwrap(); + let mut method: ImplItemFn = parse_quote! { + #[abi_alias("foo_one")] + pub fn foo(&self){ } + }; + let method_info = ImplItemMethodInfo::new(&mut method, None, impl_type).unwrap().unwrap(); + let actual = method_info.method_wrapper(); + local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()) + } + + #[test] + fn abi_aliased_trait_method(){ + let impl_type: Type = syn::parse_str("Hello").unwrap(); + let mut method: ImplItemFn = parse_quote! { + #[abi_alias("foo_one")] + pub fn foo(&self){ } + }; + + let trait_ = (None, parse_quote! { T1 }, parse_quote! { for }); + let method_info = ImplItemMethodInfo::new(&mut method, Some(trait_), impl_type).unwrap().unwrap(); + let actual = method_info.method_wrapper(); + local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()) + } + #[test] + fn abi_aliased_mut_trait_method(){ + let impl_type: Type = syn::parse_str("Hello").unwrap(); + let mut method: ImplItemFn = parse_quote! { + #[abi_alias("foo_one")] + pub fn foo(&mut self){ } + }; + + let trait_ = (None, parse_quote! { T1 }, parse_quote! { for }); + let method_info = ImplItemMethodInfo::new(&mut method, Some(trait_), impl_type).unwrap().unwrap(); + let actual = method_info.method_wrapper(); + local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()) + } } diff --git a/near-sdk-macros/src/core_impl/code_generator/snapshots/abi_aliased_method.snap b/near-sdk-macros/src/core_impl/code_generator/snapshots/abi_aliased_method.snap new file mode 100644 index 000000000..d8e5bafb1 --- /dev/null +++ b/near-sdk-macros/src/core_impl/code_generator/snapshots/abi_aliased_method.snap @@ -0,0 +1,13 @@ +--- +source: near-sdk-macros/src/core_impl/code_generator/item_impl_info.rs +assertion_line: 371 +expression: pretty_print_syn_str(&actual).unwrap() +--- +#[cfg(target_arch = "wasm32")] +#[no_mangle] +pub extern "C" fn foo_one() { + ::near_sdk::env::setup_panic_hook(); + let contract: Hello = ::near_sdk::env::state_read().unwrap_or_default(); + contract.foo(); +} + diff --git a/near-sdk-macros/src/core_impl/code_generator/snapshots/abi_aliased_mut_trait_method.snap b/near-sdk-macros/src/core_impl/code_generator/snapshots/abi_aliased_mut_trait_method.snap new file mode 100644 index 000000000..aee6b2e41 --- /dev/null +++ b/near-sdk-macros/src/core_impl/code_generator/snapshots/abi_aliased_mut_trait_method.snap @@ -0,0 +1,17 @@ +--- +source: near-sdk-macros/src/core_impl/code_generator/item_impl_info.rs +assertion_line: 396 +expression: pretty_print_syn_str(&actual).unwrap() +--- +#[cfg(target_arch = "wasm32")] +#[no_mangle] +pub extern "C" fn foo_one() { + ::near_sdk::env::setup_panic_hook(); + if ::near_sdk::env::attached_deposit().as_yoctonear() != 0 { + ::near_sdk::env::panic_str("Method foo doesn't accept deposit"); + } + let mut contract: Hello = ::near_sdk::env::state_read().unwrap_or_default(); + T1::foo(&mut contract); + ::near_sdk::env::state_write(&contract); +} + diff --git a/near-sdk-macros/src/core_impl/code_generator/snapshots/abi_aliased_trait_method.snap b/near-sdk-macros/src/core_impl/code_generator/snapshots/abi_aliased_trait_method.snap new file mode 100644 index 000000000..13559a781 --- /dev/null +++ b/near-sdk-macros/src/core_impl/code_generator/snapshots/abi_aliased_trait_method.snap @@ -0,0 +1,13 @@ +--- +source: near-sdk-macros/src/core_impl/code_generator/item_impl_info.rs +assertion_line: 383 +expression: pretty_print_syn_str(&actual).unwrap() +--- +#[cfg(target_arch = "wasm32")] +#[no_mangle] +pub extern "C" fn foo_one() { + ::near_sdk::env::setup_panic_hook(); + let contract: Hello = ::near_sdk::env::state_read().unwrap_or_default(); + T1::foo(&contract); +} + diff --git a/near-sdk-macros/src/core_impl/code_generator/snapshots/serialize_method_name.snap b/near-sdk-macros/src/core_impl/code_generator/snapshots/serialize_method_name.snap new file mode 100644 index 000000000..5d8d564c6 --- /dev/null +++ b/near-sdk-macros/src/core_impl/code_generator/snapshots/serialize_method_name.snap @@ -0,0 +1,17 @@ +--- +source: near-sdk-macros/src/core_impl/code_generator/item_impl_info.rs +assertion_line: 354 +expression: pretty_print_syn_str(&actual).unwrap() +--- +#[cfg(target_arch = "wasm32")] +#[no_mangle] +pub extern "C" fn serialize() { + ::near_sdk::env::setup_panic_hook(); + if ::near_sdk::env::attached_deposit().as_yoctonear() != 0 { + ::near_sdk::env::panic_str("Method serialize doesn't accept deposit"); + } + let mut contract: Hello = ::near_sdk::env::state_read().unwrap_or_default(); + contract.serialize(); + ::near_sdk::env::state_write(&contract); +} + diff --git a/near-sdk-macros/src/core_impl/info_extractor/attr_sig_info.rs b/near-sdk-macros/src/core_impl/info_extractor/attr_sig_info.rs index 6eb010fa6..e4f559b31 100644 --- a/near-sdk-macros/src/core_impl/info_extractor/attr_sig_info.rs +++ b/near-sdk-macros/src/core_impl/info_extractor/attr_sig_info.rs @@ -6,12 +6,17 @@ use crate::core_impl::{utils, Returns}; use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::ToTokens; use syn::spanned::Spanned; -use syn::{Attribute, Error, FnArg, GenericParam, Ident, ReturnType, Signature, Type}; +use syn::token::{For, Not}; +use syn::{ + Attribute, Error, Expr, FnArg, GenericParam, Ident, Lit, Path, ReturnType, Signature, Type, +}; /// Information extracted from method attributes and signature. pub struct AttrSigInfo { /// The name of the method. pub ident: Ident, + /// Propagated to allow for the fully qualified path in the generated code. + pub trait_: Option<(Option, Path, For)>, /// Attributes not related to bindgen. pub non_bindgen_attrs: Vec, /// All arguments of the method. @@ -67,6 +72,7 @@ impl AttrSigInfo { original_attrs: &mut Vec, original_sig: &mut Signature, source_type: &TokenStream2, + trait_: Option<(Option, Path, For)>, ) -> syn::Result { let mut self_occurrences = Self::sanitize_self(original_sig, source_type)?; let mut errors = vec![]; @@ -100,6 +106,11 @@ impl AttrSigInfo { let args = AttributeConfig::from_attributes(original_attrs)?; // Visit attributes for attr in original_attrs.iter() { + if let Some(litstr) = retrieve_abi_alias(attr)? { + visitor.visit_alias_attr(litstr); + continue; + } + let attr_str = attr.path().to_token_stream().to_string(); match attr_str.as_str() { "init" => { @@ -176,6 +187,7 @@ impl AttrSigInfo { let mut result = AttrSigInfo { ident, + trait_, non_bindgen_attrs, args, method_kind, @@ -205,6 +217,23 @@ impl AttrSigInfo { } } +pub fn retrieve_abi_alias(attr: &Attribute) -> syn::Result> { + // working without crate darling here to allow for NameValue attributes + if attr.path().is_ident("abi_alias") { + let err = Error::new(attr.span(), "Expected a string literal for `abi_alias` attribute."); + if let Expr::Lit(exprlit) = attr.parse_args()? { + if let Lit::Str(litstr) = exprlit.lit { + return Ok(Some(litstr.value())); + } else { + return Err(err); + } + } else { + return Err(err); + } + } + Ok(None) +} + // Generate errors for a given collection of spans. Returns `Ok` if no spans are provided. fn report_spans(spans: &[Span], msg: &str) -> Result<(), syn::Error> { if spans.is_empty() { diff --git a/near-sdk-macros/src/core_impl/info_extractor/impl_item_method_info.rs b/near-sdk-macros/src/core_impl/info_extractor/impl_item_method_info.rs index e59adc5b3..dc24b7f7d 100644 --- a/near-sdk-macros/src/core_impl/info_extractor/impl_item_method_info.rs +++ b/near-sdk-macros/src/core_impl/info_extractor/impl_item_method_info.rs @@ -1,7 +1,10 @@ use crate::core_impl::info_extractor::AttrSigInfo; use crate::core_impl::utils; use quote::ToTokens; -use syn::{ImplItemFn as ImplItemMethod, Type, Visibility}; +use syn::{ + token::{For, Not}, + ImplItemFn as ImplItemMethod, Path, Type, Visibility, +}; /// Information extracted from `ImplItemMethod`. pub struct ImplItemMethodInfo { @@ -15,14 +18,14 @@ impl ImplItemMethodInfo { /// Process the method and extract information important for near-sdk. pub fn new( original: &mut ImplItemMethod, - is_trait_impl: bool, + trait_: Option<(Option, Path, For)>, struct_type: Type, ) -> syn::Result> { let ImplItemMethod { attrs, sig, .. } = original; utils::sig_is_supported(sig)?; - if is_trait_impl || matches!(original.vis, Visibility::Public(_)) { + if trait_.is_some() || matches!(original.vis, Visibility::Public(_)) { let source_type = &struct_type.to_token_stream(); - let attr_signature_info = AttrSigInfo::new(attrs, sig, source_type)?; + let attr_signature_info = AttrSigInfo::new(attrs, sig, source_type, trait_)?; Ok(Some(Self { attr_signature_info, struct_type })) } else { Ok(None) @@ -35,7 +38,7 @@ impl ImplItemMethodInfo { #[cfg(test)] mod tests { use syn::{parse_quote, Type, ImplItemFn as ImplItemMethod , ReturnType}; - use crate::core_impl::{ImplItemMethodInfo}; + use crate::core_impl::ImplItemMethodInfo; #[test] fn init_no_return() { @@ -44,7 +47,7 @@ mod tests { #[init] pub fn method(k: &mut u64) { } }; - let actual = ImplItemMethodInfo::new(&mut method, false, impl_type).map(|_| ()).unwrap_err(); + let actual = ImplItemMethodInfo::new(&mut method, None, impl_type).map(|_| ()).unwrap_err(); let expected = "Init function must return the contract state."; assert_eq!(expected, actual.to_string()); } @@ -57,7 +60,7 @@ mod tests { #[handle_result] pub fn method(k: &mut u64) -> Result { } }; - let method = ImplItemMethodInfo::new(&mut method, false, impl_type).unwrap().unwrap(); + let method = ImplItemMethodInfo::new(&mut method, None, impl_type).unwrap().unwrap(); let actual = method.attr_signature_info.returns.original; let expected: Type = syn::parse_str("Result").unwrap(); assert!(matches!(actual, ReturnType::Type(_, ty) if ty.as_ref() == &expected)); @@ -70,7 +73,7 @@ mod tests { #[handle_result] pub fn method(&self) -> &'static str { } }; - let actual = ImplItemMethodInfo::new(&mut method, false, impl_type).map(|_| ()).unwrap_err(); + let actual = ImplItemMethodInfo::new(&mut method, None, impl_type).map(|_| ()).unwrap_err(); let expected = "Function marked with #[handle_result] should return Result (where E implements FunctionError). If you're trying to use a type alias for `Result`, try `#[handle_result(aliased)]`."; assert_eq!(expected, actual.to_string()); } @@ -81,7 +84,7 @@ mod tests { let mut method: ImplItemMethod = parse_quote! { pub fn method(&self) -> Result { } }; - let actual = ImplItemMethodInfo::new(&mut method, false, impl_type).map(|_| ()).unwrap_err(); + let actual = ImplItemMethodInfo::new(&mut method, None, impl_type).map(|_| ()).unwrap_err(); let expected = "Serializing Result has been deprecated. Consider marking your method with #[handle_result] if the second generic represents a panicable error or replacing Result with another two type sum enum otherwise. If you really want to keep the legacy behavior, mark the method with #[handle_result] and make it return Result, near_sdk::Abort>."; assert_eq!(expected, actual.to_string()); } @@ -93,12 +96,11 @@ mod tests { #[init] pub fn new() -> Result { } }; - let actual = ImplItemMethodInfo::new(&mut method, false, impl_type).map(|_| ()).unwrap_err(); + let actual = ImplItemMethodInfo::new(&mut method, None, impl_type).map(|_| ()).unwrap_err(); let expected = "Serializing Result has been deprecated. Consider marking your method with #[handle_result] if the second generic represents a panicable error or replacing Result with another two type sum enum otherwise. If you really want to keep the legacy behavior, mark the method with #[handle_result] and make it return Result, near_sdk::Abort>."; assert_eq!(expected, actual.to_string()); } - #[test] fn payable_self_by_value_fails() { let impl_type: Type = syn::parse_str("Hello").unwrap(); @@ -106,7 +108,7 @@ mod tests { #[payable] pub fn method(self) -> Self { } }; - let actual = ImplItemMethodInfo::new(&mut method, false, impl_type).map(|_| ()).unwrap_err(); + let actual = ImplItemMethodInfo::new(&mut method, None, impl_type).map(|_| ()).unwrap_err(); let expected = "View function can't be payable."; assert_eq!(expected.to_string(), actual.to_string()); } diff --git a/near-sdk-macros/src/core_impl/info_extractor/item_impl_info.rs b/near-sdk-macros/src/core_impl/info_extractor/item_impl_info.rs index 130cac43a..0855ede09 100644 --- a/near-sdk-macros/src/core_impl/info_extractor/item_impl_info.rs +++ b/near-sdk-macros/src/core_impl/info_extractor/item_impl_info.rs @@ -18,14 +18,13 @@ impl ItemImplInfo { "Impl type parameters are not supported for smart contracts.", )); } - let is_trait_impl = original.trait_.is_some(); let ty = (*original.self_ty.as_ref()).clone(); let mut methods = vec![]; let mut errors = vec![]; for subitem in &mut original.items { if let ImplItem::Fn(m) = subitem { - match ImplItemMethodInfo::new(m, is_trait_impl, ty.clone()) { + match ImplItemMethodInfo::new(m, original.trait_.clone(), ty.clone()) { Ok(Some(method_info)) => methods.push(method_info), Ok(None) => {} // do nothing Err(e) => errors.push(e), diff --git a/near-sdk-macros/src/core_impl/info_extractor/mod.rs b/near-sdk-macros/src/core_impl/info_extractor/mod.rs index b1793a43a..5481b74ed 100644 --- a/near-sdk-macros/src/core_impl/info_extractor/mod.rs +++ b/near-sdk-macros/src/core_impl/info_extractor/mod.rs @@ -10,7 +10,7 @@ mod handle_result_attr; pub use handle_result_attr::HandleResultAttr; mod attr_sig_info; -pub use attr_sig_info::AttrSigInfo; +pub use attr_sig_info::{retrieve_abi_alias, AttrSigInfo}; mod impl_item_method_info; pub use impl_item_method_info::ImplItemMethodInfo; @@ -55,6 +55,9 @@ pub struct CallMethod { pub result_serializer: SerializerType, /// The receiver, like `mut self`, `self`, `&mut self`, `&self`, or `None`. pub receiver: Option, + /// The alias of the method as would appear in the ABI; here to + /// prevent method name collisions in the case of overloads or colliding trait methods. + pub alias: Option, } #[derive(Clone, PartialEq, Eq)] @@ -65,6 +68,9 @@ pub struct ViewMethod { pub result_serializer: SerializerType, /// The receiver, like `mut self`, `self`, `&mut self`, `&self`, or `None`. pub receiver: Option, + /// The alias of the method as would appear in the ABI; here to + /// prevent method name collisions in the case of overloads or colliding trait methods. + pub alias: Option, } #[derive(Clone, PartialEq, Eq)] diff --git a/near-sdk-macros/src/core_impl/info_extractor/trait_item_method_info.rs b/near-sdk-macros/src/core_impl/info_extractor/trait_item_method_info.rs index f5bcdf57c..44a4c8523 100644 --- a/near-sdk-macros/src/core_impl/info_extractor/trait_item_method_info.rs +++ b/near-sdk-macros/src/core_impl/info_extractor/trait_item_method_info.rs @@ -28,7 +28,7 @@ impl TraitItemMethodInfo { let TraitItemFn { attrs, sig, .. } = original; utils::sig_is_supported(sig)?; - let attr_sig_info = AttrSigInfo::new(attrs, sig, trait_name)?; + let attr_sig_info = AttrSigInfo::new(attrs, sig, trait_name, None)?; let ident_byte_str = LitStr::new(&attr_sig_info.ident.to_string(), attr_sig_info.ident.span()); diff --git a/near-sdk-macros/src/core_impl/info_extractor/visitor.rs b/near-sdk-macros/src/core_impl/info_extractor/visitor.rs index 1d02c1909..45fc9990c 100644 --- a/near-sdk-macros/src/core_impl/info_extractor/visitor.rs +++ b/near-sdk-macros/src/core_impl/info_extractor/visitor.rs @@ -17,6 +17,7 @@ struct ParsedData { ignores_state: bool, result_serializer: SerializerType, receiver: Option, + alias: Option, } #[derive(Copy, Clone, PartialEq, Eq)] @@ -44,6 +45,7 @@ impl Default for ParsedData { ignores_state: Default::default(), result_serializer: SerializerType::JSON, receiver: Default::default(), + alias: Default::default(), } } } @@ -146,6 +148,10 @@ impl Visitor { } } + pub fn visit_alias_attr(&mut self, _alias: String) { + self.parsed_data.alias = Some(_alias); + } + /// Extract the return type of the function. Must be called last as it depends on the value of /// `handles_result`. This is why it's private and called as part of `build`. fn get_return_type(&mut self) -> syn::Result { @@ -178,15 +184,25 @@ impl Visitor { let Visitor { kind, parsed_data, .. } = self; let ParsedData { - is_payable, is_private, ignores_state, result_serializer, receiver, .. + is_payable, + is_private, + ignores_state, + result_serializer, + receiver, + alias, + .. } = parsed_data; let method_kind = match kind { - Call => { - MethodKind::Call(CallMethod { is_payable, is_private, result_serializer, receiver }) - } + Call => MethodKind::Call(CallMethod { + is_payable, + is_private, + result_serializer, + receiver, + alias, + }), Init => MethodKind::Init(InitMethod { is_payable, ignores_state }), - View => MethodKind::View(ViewMethod { is_private, result_serializer, receiver }), + View => MethodKind::View(ViewMethod { is_private, result_serializer, receiver, alias }), }; Ok((method_kind, returns)) diff --git a/near-sdk-macros/src/lib.rs b/near-sdk-macros/src/lib.rs index d6039ea45..bc23def35 100644 --- a/near-sdk-macros/src/lib.rs +++ b/near-sdk-macros/src/lib.rs @@ -3,7 +3,9 @@ extern crate proc_macro; mod core_impl; -use core_impl::{ext::generate_ext_structs, metadata::generate_contract_metadata_method}; +use core_impl::{ + ext::generate_ext_structs, metadata::generate_contract_metadata_method, retrieve_abi_alias, +}; use proc_macro::TokenStream; @@ -12,6 +14,9 @@ use proc_macro2::{Ident, Span}; use quote::{quote, ToTokens}; use syn::{parse_quote, ImplItem, ItemEnum, ItemImpl, ItemStruct, ItemTrait, WhereClause}; +/// Reserved methods that cannot be used as contract method names, unless aliased. +const RESERVED_METHODS: [&str; 2] = ["__contract_abi", "contract_source_metadata"]; + /// This attribute macro is used on a struct and its implementations /// to generate the necessary code to expose `pub` methods from the contract as well /// as generating the glue code to be a valid NEAR contract. @@ -109,6 +114,43 @@ use syn::{parse_quote, ImplItem, ItemEnum, ItemImpl, ItemStruct, ItemTrait, Wher /// ))] /// struct Contract {} /// ``` +/// +/// ABI Name Aliasing: +/// +/// By using `#[abi_alias(alias_name)]`, `near_bindgen` will generate the relevant abi code under the alias. +/// This can be useful in cases where trait methods have the same name like in the example below. +/// +/// Note: We cannot alias methods that have been defined multiple times. This means aliasing a method like `contract_source_metadata` +/// will not work unless the qualified name is something other than `Contract::contract_source_metadata` like `T3::contract_source_metadata` +/// where `T3` is a trait. +/// +/// Examples +/// +/// ```ignore +/// use near_sdk::near_bindgen; +/// +/// #[near_bindgen] +/// struct Contract; +/// +/// // here we have two traits with the same method name +/// trait T1 { fn foo(&self); } +/// trait T2 { fn foo(&self); } +/// +/// #[near_bindgen] +/// impl T1 for Contract { +/// fn foo(&self) { +/// log!("foo_one") +/// } +/// } +/// +/// #[near_bindgen] +/// impl T2 for Contract { +/// #[abi_alias("foo_two")] +/// fn foo(&self) { +/// log!("foo_two") +/// } +/// } +/// ``` #[proc_macro_attribute] pub fn near_bindgen(attr: TokenStream, item: TokenStream) -> TokenStream { if attr.to_string().contains("event_json") { @@ -171,7 +213,24 @@ pub fn near_bindgen(attr: TokenStream, item: TokenStream) -> TokenStream { for method in &input.items { if let ImplItem::Fn(m) = method { let ident = &m.sig.ident; - if ident.eq("__contract_abi") || ident.eq("contract_source_metadata") { + if RESERVED_METHODS.contains(&ident.to_string().as_str()) { + if m.attrs.iter().any(|attr| { + if attr.path().is_ident("abi_alias") && input.trait_.is_some() { + return match retrieve_abi_alias(attr) { + Ok(Some(alias)) => ident.ne(&alias), + Ok(None) => false, + Err(_) => { + // error deferred to `process_impl_block()` + true + } + }; + } + + false + }) { + continue; + } + return TokenStream::from( syn::Error::new_spanned( ident.to_token_stream(), diff --git a/near-sdk/compilation_tests/all.rs b/near-sdk/compilation_tests/all.rs index e78f316e2..e9199712b 100644 --- a/near-sdk/compilation_tests/all.rs +++ b/near-sdk/compilation_tests/all.rs @@ -36,4 +36,6 @@ fn compilation_tests() { 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"); + t.pass("compilation_tests/trait_impl_name_aliasing.rs"); + t.pass("compilation_tests/contract_serialize_method.rs"); } diff --git a/near-sdk/compilation_tests/contract_serialize_method.rs b/near-sdk/compilation_tests/contract_serialize_method.rs new file mode 100644 index 000000000..e062bcde7 --- /dev/null +++ b/near-sdk/compilation_tests/contract_serialize_method.rs @@ -0,0 +1,19 @@ +use near_sdk::{ + borsh::{BorshDeserialize, BorshSerialize}, + log, near_bindgen, PanicOnDefault, +}; + +#[near_bindgen] +#[derive(BorshSerialize, BorshDeserialize, PanicOnDefault)] +#[borsh(crate = "near_sdk::borsh")] +pub struct Contract {} + +#[near_bindgen] +impl Contract { + // See for more information. + pub fn serialize(&mut self) { + log!("serialize"); + } +} + +fn main() {} diff --git a/near-sdk/compilation_tests/trait_impl_name_aliasing.rs b/near-sdk/compilation_tests/trait_impl_name_aliasing.rs new file mode 100644 index 000000000..ba47fe3bd --- /dev/null +++ b/near-sdk/compilation_tests/trait_impl_name_aliasing.rs @@ -0,0 +1,41 @@ +use near_sdk::{ + borsh::{BorshDeserialize, BorshSerialize}, + log, near_bindgen, +}; + +#[near_bindgen] +#[derive(Default, BorshDeserialize, BorshSerialize)] +#[borsh(crate = "near_sdk::borsh")] +struct Contract {} + +trait T1 { + fn foo(&self); +} + +trait T2 { + fn foo(&self); +} + +#[near_bindgen] +impl T1 for Contract { + fn foo(&self) { + log!("foo_one") + } +} + +#[near_bindgen] +impl T2 for Contract { + #[abi_alias("foo_two")] + fn foo(&self) { + log!("foo_two") + } +} + +impl Contract { + fn bar(&self) { + T1::foo(self); + T2::foo(self); + } +} + +fn main() {} diff --git a/near-sdk/tests/abi_alias.rs b/near-sdk/tests/abi_alias.rs new file mode 100644 index 000000000..f2cb328ee --- /dev/null +++ b/near-sdk/tests/abi_alias.rs @@ -0,0 +1,86 @@ +use near_sdk::{ + borsh::{BorshDeserialize, BorshSerialize}, + env, log, near_bindgen, + test_utils::get_logs, + PanicOnDefault, +}; + +trait T1 { + fn foo(&self); +} + +trait T2 { + fn foo(&self); +} + +trait T3 { + fn contract_source_metadata(&self); +} + +#[near_bindgen] +#[derive(BorshSerialize, BorshDeserialize, PanicOnDefault)] +#[borsh(crate = "near_sdk::borsh")] +pub struct Contract {} + +// here to make sure the method does not collide with the serialize method impl from borsh +#[near_bindgen] +impl Contract { + pub fn serialize(&mut self) { + log!("serialize"); + } +} + +#[near_bindgen] +impl T1 for Contract { + fn foo(&self) { + log!("foo_one") + } +} + +#[near_bindgen] +impl T2 for Contract { + #[abi_alias("foo_two")] + fn foo(&self) { + log!("foo_two") + } +} + +#[near_bindgen] +impl T3 for Contract { + #[abi_alias("t3_contract_source_metadata")] + fn contract_source_metadata(&self) { + log!("contract_source_metadata from T3") + } +} + +#[test] +fn test_serialize_method() { + let mut contract = Contract {}; + Contract::serialize(&mut contract); + let logs = get_logs(); + assert_eq!(logs[0], "serialize"); +} + +#[test] +fn test_aliased_method() { + let contract = Contract {}; + T1::foo(&contract); + T2::foo(&contract); + let logs = get_logs(); + + assert_eq!(logs[0], "foo_one"); + assert_eq!(logs[1], "foo_two"); + + // making sure the method T2::foo exists in abi as foo_two + let _ = Contract::ext(env::current_account_id()).foo_two(); + // also making sure the method T1::foo exists in abi as foo + let _ = Contract::ext(env::current_account_id()).foo(); +} + +#[test] +fn test_reserved_method_name() { + // making sure the method T3::contract_source_metadata exists in abi as t3_contract_source_metadata + let _ = Contract::ext(env::current_account_id()).t3_contract_source_metadata(); + // making sure contract_source_metadata is also available + let _ = Contract::ext(env::current_account_id()).contract_source_metadata(); +}