From ea10576aa859cc371a8350e8e6ccfe217e77b865 Mon Sep 17 00:00:00 2001 From: akorchyn Date: Thu, 6 Feb 2025 16:55:32 +0200 Subject: [PATCH 1/2] feat: improved error reporting for `near` macro --- near-sdk-macros/src/lib.rs | 47 +++++++++++++++++++ near-sdk/compilation_tests/all.rs | 2 + .../double_contract_state_error.rs | 13 +++++ .../double_contract_state_error.stderr | 7 +++ .../compilation_tests/nested_near_error.rs | 14 ++++++ .../nested_near_error.stderr | 7 +++ 6 files changed, 90 insertions(+) create mode 100644 near-sdk/compilation_tests/double_contract_state_error.rs create mode 100644 near-sdk/compilation_tests/double_contract_state_error.stderr create mode 100644 near-sdk/compilation_tests/nested_near_error.rs create mode 100644 near-sdk/compilation_tests/nested_near_error.stderr diff --git a/near-sdk-macros/src/lib.rs b/near-sdk-macros/src/lib.rs index 6f1f909ac..6429bf25f 100644 --- a/near-sdk-macros/src/lib.rs +++ b/near-sdk-macros/src/lib.rs @@ -43,6 +43,32 @@ struct NearMacroArgs { inside_nearsdk: Option, } +fn has_nested_near_macros(item: TokenStream) -> bool { + syn::parse::(item) + .ok() + .and_then(|item_ast| { + let attrs = match item_ast { + syn::Item::Struct(s) => s.attrs, + syn::Item::Enum(e) => e.attrs, + syn::Item::Impl(i) => i.attrs, + _ => vec![], // Other cases don't support near macros anyway + }; + + attrs.into_iter().find(|attr| { + let path_str = attr.path().to_token_stream().to_string(); + path_str == "near" || path_str == "near_bindgen" + }) + }) + .is_some() +} + +fn check_duplicate_contract_state() -> bool { + static CONTRACT_STATE_DEFINED: ::std::sync::atomic::AtomicBool = + ::std::sync::atomic::AtomicBool::new(false); + + CONTRACT_STATE_DEFINED.swap(true, ::std::sync::atomic::Ordering::SeqCst) +} + #[proc_macro_attribute] pub fn near(attr: TokenStream, item: TokenStream) -> TokenStream { if attr.to_string().contains("event_json") { @@ -68,12 +94,33 @@ pub fn near(attr: TokenStream, item: TokenStream) -> TokenStream { } else { quote! {::near_sdk} }; + + // Check for nested near macros by parsing the input and examining actual attributes + if has_nested_near_macros(item.clone()) { + return TokenStream::from( + syn::Error::new( + Span::call_site(), + "The `near` macro cannot be nested with other `near` or `near_bindgen` macros. Please comma-separate the attributes instead of nesting them", + ) + .to_compile_error(), + ); + } let string_borsh_crate = quote! {#near_sdk_crate::borsh}.to_string(); let string_serde_crate = quote! {#near_sdk_crate::serde}.to_string(); let mut expanded: proc_macro2::TokenStream = quote! {}; if near_macro_args.contract_state.unwrap_or(false) { + if check_duplicate_contract_state() { + return TokenStream::from( + syn::Error::new( + Span::call_site(), + "Contract state can only be defined once per crate", + ) + .to_compile_error(), + ); + } + if let Some(metadata) = near_macro_args.contract_metadata { expanded = quote! {#[#near_sdk_crate::near_bindgen(#metadata)]} } else { diff --git a/near-sdk/compilation_tests/all.rs b/near-sdk/compilation_tests/all.rs index 9a8007083..c200ccac6 100644 --- a/near-sdk/compilation_tests/all.rs +++ b/near-sdk/compilation_tests/all.rs @@ -40,4 +40,6 @@ fn compilation_tests() { t.compile_fail("compilation_tests/contract_metadata_fn_name.rs"); t.pass("compilation_tests/contract_metadata_bindgen.rs"); t.pass("compilation_tests/types.rs"); + t.compile_fail("compilation_tests/nested_near_error.rs"); + t.compile_fail("compilation_tests/double_contract_state_error.rs"); } diff --git a/near-sdk/compilation_tests/double_contract_state_error.rs b/near-sdk/compilation_tests/double_contract_state_error.rs new file mode 100644 index 000000000..bd15b957a --- /dev/null +++ b/near-sdk/compilation_tests/double_contract_state_error.rs @@ -0,0 +1,13 @@ +use near_sdk::near; + +#[near(contract_state)] +pub struct Contract {} + +pub mod mod1 { + use near_sdk::near; + + #[near(contract_state)] + struct Contract {} +} + +fn main() {} diff --git a/near-sdk/compilation_tests/double_contract_state_error.stderr b/near-sdk/compilation_tests/double_contract_state_error.stderr new file mode 100644 index 000000000..c0a1f206f --- /dev/null +++ b/near-sdk/compilation_tests/double_contract_state_error.stderr @@ -0,0 +1,7 @@ +error: Contract state can only be defined once per crate + --> compilation_tests/double_contract_state_error.rs:9:5 + | +9 | #[near(contract_state)] + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `near` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/near-sdk/compilation_tests/nested_near_error.rs b/near-sdk/compilation_tests/nested_near_error.rs new file mode 100644 index 000000000..c10cc9f80 --- /dev/null +++ b/near-sdk/compilation_tests/nested_near_error.rs @@ -0,0 +1,14 @@ +//! Method signature uses lifetime. + +use near_sdk::near; + +#[near(contract_state)] +#[near(contract_metadata( + version = "39f2d2646f2f60e18ab53337501370dc02a5661c", + link = "https://github.com/near-examples/nft-tutorial", + standard(standard = "nep171", version = "1.0.0"), + standard(standard = "nep177", version = "2.0.0"), +))] +struct CompileFailure {} + +fn main() {} diff --git a/near-sdk/compilation_tests/nested_near_error.stderr b/near-sdk/compilation_tests/nested_near_error.stderr new file mode 100644 index 000000000..2835493e9 --- /dev/null +++ b/near-sdk/compilation_tests/nested_near_error.stderr @@ -0,0 +1,7 @@ +error: The `near` macro cannot be nested with other `near` or `near_bindgen` macros. Please comma-separate the attributes instead of nesting them + --> compilation_tests/nested_near_error.rs:5:1 + | +5 | #[near(contract_state)] + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `near` (in Nightly builds, run with -Z macro-backtrace for more info) From 2bd96ada7364ad94c186c35341b2fc115d9e01fd Mon Sep 17 00:00:00 2001 From: akorchyn Date: Fri, 7 Feb 2025 13:26:09 +0200 Subject: [PATCH 2/2] review --- near-sdk-macros/src/lib.rs | 4 ++-- near-sdk/compilation_tests/nested_near_error.stderr | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/near-sdk-macros/src/lib.rs b/near-sdk-macros/src/lib.rs index 6429bf25f..edb149e0b 100644 --- a/near-sdk-macros/src/lib.rs +++ b/near-sdk-macros/src/lib.rs @@ -66,7 +66,7 @@ fn check_duplicate_contract_state() -> bool { static CONTRACT_STATE_DEFINED: ::std::sync::atomic::AtomicBool = ::std::sync::atomic::AtomicBool::new(false); - CONTRACT_STATE_DEFINED.swap(true, ::std::sync::atomic::Ordering::SeqCst) + CONTRACT_STATE_DEFINED.swap(true, ::std::sync::atomic::Ordering::AcqRel) } #[proc_macro_attribute] @@ -100,7 +100,7 @@ pub fn near(attr: TokenStream, item: TokenStream) -> TokenStream { return TokenStream::from( syn::Error::new( Span::call_site(), - "The `near` macro cannot be nested with other `near` or `near_bindgen` macros. Please comma-separate the attributes instead of nesting them", + "#[near] or #[near_bindgen] attributes are not allowed to be nested inside of the outmost #[near] attribute. Only a single #[near] attribute is allowed", ) .to_compile_error(), ); diff --git a/near-sdk/compilation_tests/nested_near_error.stderr b/near-sdk/compilation_tests/nested_near_error.stderr index 2835493e9..ddd5ffbd7 100644 --- a/near-sdk/compilation_tests/nested_near_error.stderr +++ b/near-sdk/compilation_tests/nested_near_error.stderr @@ -1,4 +1,4 @@ -error: The `near` macro cannot be nested with other `near` or `near_bindgen` macros. Please comma-separate the attributes instead of nesting them +error: #[near] or #[near_bindgen] attributes are not allowed to be nested inside of the outmost #[near] attribute. Only a single #[near] attribute is allowed --> compilation_tests/nested_near_error.rs:5:1 | 5 | #[near(contract_state)]