diff --git a/Cargo.lock b/Cargo.lock index c380ea9c76b..215e79d2382 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3043,12 +3043,10 @@ name = "iroha_executor_derive" version = "2.0.0-pre-rc.20" dependencies = [ "darling", - "iroha_data_model", "iroha_macro_utils", "manyhow", "proc-macro2", "quote", - "syn 1.0.109", "syn 2.0.38", ] @@ -3270,9 +3268,11 @@ dependencies = [ name = "iroha_smart_contract_derive" version = "2.0.0-pre-rc.20" dependencies = [ + "iroha_macro_utils", + "manyhow", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.38", ] [[package]] @@ -3361,9 +3361,12 @@ dependencies = [ name = "iroha_trigger_derive" version = "2.0.0-pre-rc.20" dependencies = [ + "darling", + "iroha_macro_utils", + "manyhow", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.38", ] [[package]] diff --git a/smart_contract/derive/Cargo.toml b/smart_contract/derive/Cargo.toml index 72658aa9aa8..8ecc878acec 100644 --- a/smart_contract/derive/Cargo.toml +++ b/smart_contract/derive/Cargo.toml @@ -14,6 +14,9 @@ workspace = true proc-macro = true [dependencies] -syn.workspace = true -quote.workspace = true -proc-macro2.workspace = true +iroha_macro_utils = { workspace = true } + +syn2 = { workspace = true } +manyhow = { workspace = true } +quote = { workspace = true } +proc-macro2 = { workspace = true } diff --git a/smart_contract/derive/src/entrypoint.rs b/smart_contract/derive/src/entrypoint.rs index 4970b406ea3..426c2ab091f 100644 --- a/smart_contract/derive/src/entrypoint.rs +++ b/smart_contract/derive/src/entrypoint.rs @@ -1,26 +1,32 @@ //! Macro for writing smart contract entrypoint -use proc_macro::TokenStream; +#![allow(clippy::str_to_string)] + +use iroha_macro_utils::Emitter; +use manyhow::emit; +use proc_macro2::TokenStream; use quote::quote; -use syn::{parse_macro_input, parse_quote}; +use syn2::parse_quote; mod export { pub const SMART_CONTRACT_MAIN: &str = "_iroha_smart_contract_main"; } #[allow(clippy::needless_pass_by_value)] -pub fn impl_entrypoint(_attr: TokenStream, item: TokenStream) -> TokenStream { - let syn::ItemFn { +pub fn impl_entrypoint(emitter: &mut Emitter, item: syn2::ItemFn) -> TokenStream { + let syn2::ItemFn { attrs, vis, sig, mut block, - } = parse_macro_input!(item); + } = item; - assert!( - syn::ReturnType::Default == sig.output, - "Smart contract `main()` function must not have a return type" - ); + if sig.output != syn2::ReturnType::Default { + emit!( + emitter, + "Smart contract entrypoint must not have a return type" + ); + } let fn_name = &sig.ident; @@ -33,7 +39,8 @@ pub fn impl_entrypoint(_attr: TokenStream, item: TokenStream) -> TokenStream { ), ); - let main_fn_name = syn::Ident::new(export::SMART_CONTRACT_MAIN, proc_macro2::Span::call_site()); + let main_fn_name = + syn2::Ident::new(export::SMART_CONTRACT_MAIN, proc_macro2::Span::call_site()); quote! { /// Smart contract entrypoint @@ -51,5 +58,4 @@ pub fn impl_entrypoint(_attr: TokenStream, item: TokenStream) -> TokenStream { #vis #sig #block } - .into() } diff --git a/smart_contract/derive/src/lib.rs b/smart_contract/derive/src/lib.rs index af82cd24fbe..da3faa41190 100644 --- a/smart_contract/derive/src/lib.rs +++ b/smart_contract/derive/src/lib.rs @@ -1,6 +1,8 @@ //! Macros for writing smart contracts. -use proc_macro::TokenStream; +use iroha_macro_utils::Emitter; +use manyhow::{emit, manyhow}; +use proc_macro2::TokenStream; mod entrypoint; @@ -23,7 +25,23 @@ mod entrypoint; /// todo!() /// } /// ``` +#[manyhow] #[proc_macro_attribute] pub fn main(attr: TokenStream, item: TokenStream) -> TokenStream { - entrypoint::impl_entrypoint(attr, item) + let mut emitter = Emitter::new(); + + if !attr.is_empty() { + emit!( + emitter, + "Smart contract entrypoint does not accept attributes" + ); + } + + let Some(item) = emitter.handle(syn2::parse2(item)) else { + return emitter.finish_token_stream(); + }; + + let result = entrypoint::impl_entrypoint(&mut emitter, item); + + emitter.finish_token_stream_with(result) } diff --git a/smart_contract/executor/derive/Cargo.toml b/smart_contract/executor/derive/Cargo.toml index ce43975f33a..0015c80dcea 100644 --- a/smart_contract/executor/derive/Cargo.toml +++ b/smart_contract/executor/derive/Cargo.toml @@ -15,11 +15,10 @@ workspace = true proc-macro = true [dependencies] -iroha_data_model.workspace = true -iroha_macro_utils.workspace = true -syn = { workspace = true, features = ["full", "derive"] } -syn2 = { workspace = true, features = ["full", "derive"] } -quote.workspace = true -proc-macro2.workspace = true -manyhow.workspace = true -darling.workspace = true +iroha_macro_utils = { workspace = true } + +syn2 = { workspace = true } +manyhow = { workspace = true } +darling = { workspace = true } +quote = { workspace = true } +proc-macro2 = { workspace = true } diff --git a/smart_contract/executor/derive/src/conversion.rs b/smart_contract/executor/derive/src/conversion.rs index 87b27becbb5..009ace5a426 100644 --- a/smart_contract/executor/derive/src/conversion.rs +++ b/smart_contract/executor/derive/src/conversion.rs @@ -1,76 +1,66 @@ //! Module with conversion derive macros implementation -use super::*; +use proc_macro2::TokenStream; +use quote::quote; +use syn2::DeriveInput; /// [`derive_ref_into_asset_owner`](crate::derive_ref_into_asset_owner) macro implementation -pub fn impl_derive_ref_into_asset_owner(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - +pub fn impl_derive_ref_into_asset_owner(input: &DeriveInput) -> TokenStream { impl_from( &input.ident, &input.generics, - &syn::parse_quote!(::iroha_executor::permission::asset::Owner), - &syn::parse_quote!(asset_id), + &syn2::parse_quote!(::iroha_executor::permission::asset::Owner), + &syn2::parse_quote!(asset_id), ) - .into() } /// [`derive_ref_into_asset_definition_creator`](crate::derive_ref_into_asset_definition_creator) /// macro implementation -pub fn impl_derive_ref_into_asset_definition_owner(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - +pub fn impl_derive_ref_into_asset_definition_owner(input: &DeriveInput) -> TokenStream { impl_from( &input.ident, &input.generics, - &syn::parse_quote!(::iroha_executor::permission::asset_definition::Owner), - &syn::parse_quote!(asset_definition_id), + &syn2::parse_quote!(::iroha_executor::permission::asset_definition::Owner), + &syn2::parse_quote!(asset_definition_id), ) - .into() } /// [`derive_ref_into_account_owner`](crate::derive_ref_into_account_owner) macro implementation -pub fn impl_derive_ref_into_account_owner(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - +pub fn impl_derive_ref_into_account_owner(input: &DeriveInput) -> TokenStream { impl_from( &input.ident, &input.generics, - &syn::parse_quote!(::iroha_executor::permission::account::Owner), - &syn::parse_quote!(account_id), + &syn2::parse_quote!(::iroha_executor::permission::account::Owner), + &syn2::parse_quote!(account_id), ) - .into() } /// [`derive_ref_into_domain_owner`](crate::derive_ref_into_domain_owner) macro implementation -pub fn impl_derive_ref_into_domain_owner(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - +pub fn impl_derive_ref_into_domain_owner(input: &DeriveInput) -> TokenStream { impl_from( &input.ident, &input.generics, - &syn::parse_quote!(::iroha_executor::permission::domain::Owner), - &syn::parse_quote!(domain_id), + &syn2::parse_quote!(::iroha_executor::permission::domain::Owner), + &syn2::parse_quote!(domain_id), ) - .into() } fn impl_from( - ident: &syn::Ident, - generics: &syn::Generics, - pass_condition_type: &syn::Type, - field: &syn::Ident, -) -> proc_macro2::TokenStream { + ident: &syn2::Ident, + generics: &syn2::Generics, + pass_condition_type: &syn2::Type, + field: &syn2::Ident, +) -> TokenStream { use quote::ToTokens; let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); - let mut generics: proc_macro2::TokenStream = syn::parse_str("<'token, ").unwrap(); + let mut generics: TokenStream = syn2::parse_str("<'token, ").unwrap(); let impl_generics_tokens = impl_generics.into_token_stream(); if impl_generics_tokens.is_empty() { generics.extend(core::iter::once(proc_macro2::TokenTree::Punct( - syn::parse_str(">").unwrap(), + syn2::parse_str(">").unwrap(), ))); } else { generics.extend(impl_generics_tokens.into_iter().skip(1)); diff --git a/smart_contract/executor/derive/src/entrypoint.rs b/smart_contract/executor/derive/src/entrypoint.rs index 3e4e6daa601..e2997491828 100644 --- a/smart_contract/executor/derive/src/entrypoint.rs +++ b/smart_contract/executor/derive/src/entrypoint.rs @@ -1,6 +1,10 @@ //! Module [`executor_entrypoint`](crate::executor_entrypoint) macro implementation -use super::*; +use iroha_macro_utils::Emitter; +use manyhow::emit; +use proc_macro2::TokenStream; +use quote::quote; +use syn2::parse_quote; mod export { pub const EXECUTOR_VALIDATE_TRANSACTION: &str = "_iroha_executor_validate_transaction"; @@ -17,14 +21,7 @@ mod import { /// [`executor_entrypoint`](crate::executor_entrypoint()) macro implementation #[allow(clippy::needless_pass_by_value)] -pub fn impl_entrypoint(attr: TokenStream, item: TokenStream) -> TokenStream { - let fn_item = parse_macro_input!(item as syn::ItemFn); - - assert!( - attr.is_empty(), - "`#[entrypoint]` macro for Executor entrypoints accepts no attributes" - ); - +pub fn impl_entrypoint(emitter: &mut Emitter, item: syn2::ItemFn) -> TokenStream { macro_rules! match_entrypoints { (validate: { $($user_entrypoint_name:ident => @@ -33,23 +30,27 @@ pub fn impl_entrypoint(attr: TokenStream, item: TokenStream) -> TokenStream { other: { $($other_user_entrypoint_name:ident => $branch:block),* $(,)? }) => { - match &fn_item.sig.ident { + match &item.sig.ident { $(fn_name if fn_name == stringify!($user_entrypoint_name) => { impl_validate_entrypoint( - fn_item, + item, stringify!($user_entrypoint_name), export::$generated_entrypoint_name, import::$query_validating_object_fn_name, ) })* $(fn_name if fn_name == stringify!($other_user_entrypoint_name) => $branch),* - _ => panic!( - "Executor entrypoint name must be one of: {:?}", - [ - $(stringify!($user_entrypoint_name),)* - $(stringify!($other_user_entrypoint_name),)* - ] - ), + _ => { + emit!( + emitter, + "Executor entrypoint name must be one of: {:?}", + [ + $(stringify!($user_entrypoint_name),)* + $(stringify!($other_user_entrypoint_name),)* + ] + ); + return quote!(); + }, } }; } @@ -61,18 +62,18 @@ pub fn impl_entrypoint(attr: TokenStream, item: TokenStream) -> TokenStream { validate_query => EXECUTOR_VALIDATE_QUERY(GET_VALIDATE_QUERY_PAYLOAD), } other: { - migrate => { impl_migrate_entrypoint(fn_item) } + migrate => { impl_migrate_entrypoint(item) } } } } fn impl_validate_entrypoint( - fn_item: syn::ItemFn, + fn_item: syn2::ItemFn, user_entrypoint_name: &'static str, generated_entrypoint_name: &'static str, get_validation_payload_fn_name: &'static str, ) -> TokenStream { - let syn::ItemFn { + let syn2::ItemFn { attrs, vis, sig, @@ -81,7 +82,7 @@ fn impl_validate_entrypoint( let fn_name = &sig.ident; assert!( - matches!(sig.output, syn::ReturnType::Type(_, _)), + matches!(sig.output, syn2::ReturnType::Type(_, _)), "Executor `{user_entrypoint_name}` entrypoint must have `Result` return type" ); @@ -92,11 +93,11 @@ fn impl_validate_entrypoint( ), ); - let generated_entrypoint_ident: syn::Ident = syn::parse_str(generated_entrypoint_name) + let generated_entrypoint_ident: syn2::Ident = syn2::parse_str(generated_entrypoint_name) .expect("Provided entrypoint name to generate is not a valid Ident, this is a bug"); - let get_validation_payload_fn_ident: syn::Ident = - syn::parse_str(get_validation_payload_fn_name).expect( + let get_validation_payload_fn_ident: syn2::Ident = + syn2::parse_str(get_validation_payload_fn_name).expect( "Provided function name to query validating object is not a valid Ident, this is a bug", ); @@ -125,11 +126,10 @@ fn impl_validate_entrypoint( #vis #sig #block } - .into() } -fn impl_migrate_entrypoint(fn_item: syn::ItemFn) -> TokenStream { - let syn::ItemFn { +fn impl_migrate_entrypoint(fn_item: syn2::ItemFn) -> TokenStream { + let syn2::ItemFn { attrs, vis, sig, @@ -138,11 +138,12 @@ fn impl_migrate_entrypoint(fn_item: syn::ItemFn) -> TokenStream { let fn_name = &sig.ident; assert!( - matches!(sig.output, syn::ReturnType::Type(_, _)), + matches!(sig.output, syn2::ReturnType::Type(_, _)), "Executor `migrate()` entrypoint must have `MigrationResult` return type" ); - let migrate_fn_name = syn::Ident::new(export::EXECUTOR_MIGRATE, proc_macro2::Span::call_site()); + let migrate_fn_name = + syn2::Ident::new(export::EXECUTOR_MIGRATE, proc_macro2::Span::call_site()); quote! { /// Executor `permission_token_schema` entrypoint @@ -167,5 +168,4 @@ fn impl_migrate_entrypoint(fn_item: syn::ItemFn) -> TokenStream { #vis #sig #block } - .into() } diff --git a/smart_contract/executor/derive/src/lib.rs b/smart_contract/executor/derive/src/lib.rs index d822a1887e1..40a187b84ee 100644 --- a/smart_contract/executor/derive/src/lib.rs +++ b/smart_contract/executor/derive/src/lib.rs @@ -1,11 +1,8 @@ //! Crate with executor-related derive macros. use iroha_macro_utils::Emitter; -use manyhow::manyhow; -use proc_macro::TokenStream; -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; -use syn::{parse_macro_input, parse_quote, DeriveInput}; +use manyhow::{emit, manyhow, Result}; +use proc_macro2::TokenStream; mod conversion; mod default; @@ -46,9 +43,25 @@ mod validate; /// todo!() /// } /// ``` +#[manyhow] #[proc_macro_attribute] pub fn entrypoint(attr: TokenStream, item: TokenStream) -> TokenStream { - entrypoint::impl_entrypoint(attr, item) + let mut emitter = Emitter::new(); + + if !attr.is_empty() { + emit!( + emitter, + "`#[entrypoint]` macro for Executor entrypoints accepts no attributes" + ); + } + + let Some(item) = emitter.handle(syn2::parse2(item)) else { + return emitter.finish_token_stream(); + }; + + let result = entrypoint::impl_entrypoint(&mut emitter, item); + + emitter.finish_token_stream_with(result) } /// Derive macro for `Token` trait. @@ -79,9 +92,12 @@ pub fn entrypoint(attr: TokenStream, item: TokenStream) -> TokenStream { /// }.is_owned_by(&authority) /// } /// ``` +#[manyhow] #[proc_macro_derive(Token)] -pub fn derive_token(input: TokenStream) -> TokenStream { - token::impl_derive_token(input) +pub fn derive_token(input: TokenStream) -> Result { + let input = syn2::parse2(input)?; + + Ok(token::impl_derive_token(&input)) } /// Derive macro for `ValidateGrantRevoke` trait. @@ -144,12 +160,14 @@ pub fn derive_token(input: TokenStream) -> TokenStream { // ... // } // ``` +#[manyhow] #[proc_macro_derive( ValidateGrantRevoke, attributes(validate, validate_grant, validate_revoke) )] -pub fn derive_validate_grant_revoke(input: TokenStream) -> TokenStream { - validate::impl_derive_validate_grant_revoke(input) +pub fn derive_validate_grant_revoke(input: TokenStream) -> Result { + let input = syn2::parse2(input)?; + validate::impl_derive_validate_grant_revoke(&input) } /// Should be used together with [`ValidateGrantRevoke`] derive macro to derive a conversion @@ -159,9 +177,14 @@ pub fn derive_validate_grant_revoke(input: TokenStream) -> TokenStream { /// /// Implements [`From`] for `permission::asset_definition::Owner` /// and not [`Into`] for your type. [`Into`] will be implemented automatically. +#[manyhow] #[proc_macro_derive(RefIntoAssetDefinitionOwner)] -pub fn derive_ref_into_asset_definition_owner(input: TokenStream) -> TokenStream { - conversion::impl_derive_ref_into_asset_definition_owner(input) +pub fn derive_ref_into_asset_definition_owner(input: TokenStream) -> Result { + let input = syn2::parse2(input)?; + + Ok(conversion::impl_derive_ref_into_asset_definition_owner( + &input, + )) } /// Should be used together with [`ValidateGrantRevoke`] derive macro to derive a conversion @@ -171,9 +194,12 @@ pub fn derive_ref_into_asset_definition_owner(input: TokenStream) -> TokenStream /// /// Implements [`From`] for `permission::asset::Owner` /// and not [`Into`] for your type. [`Into`] will be implemented automatically. +#[manyhow] #[proc_macro_derive(RefIntoAssetOwner)] -pub fn derive_ref_into_asset_owner(input: TokenStream) -> TokenStream { - conversion::impl_derive_ref_into_asset_owner(input) +pub fn derive_ref_into_asset_owner(input: TokenStream) -> Result { + let input = syn2::parse2(input)?; + + Ok(conversion::impl_derive_ref_into_asset_owner(&input)) } /// Should be used together with [`ValidateGrantRevoke`] derive macro to derive a conversion @@ -183,9 +209,12 @@ pub fn derive_ref_into_asset_owner(input: TokenStream) -> TokenStream { /// /// Implements [`From`] for `permission::asset::Owner` /// and not [`Into`] for your type. [`Into`] will be implemented automatically. +#[manyhow] #[proc_macro_derive(RefIntoAccountOwner)] -pub fn derive_ref_into_account_owner(input: TokenStream) -> TokenStream { - conversion::impl_derive_ref_into_account_owner(input) +pub fn derive_ref_into_account_owner(input: TokenStream) -> Result { + let input = syn2::parse2(input)?; + + Ok(conversion::impl_derive_ref_into_account_owner(&input)) } /// Should be used together with [`ValidateGrantRevoke`] derive macro to derive a conversion @@ -195,9 +224,12 @@ pub fn derive_ref_into_account_owner(input: TokenStream) -> TokenStream { /// /// Implements [`From`] for `permission::domain::Owner` /// and not [`Into`] for your type. [`Into`] will be implemented automatically. +#[manyhow] #[proc_macro_derive(RefIntoDomainOwner)] -pub fn derive_ref_into_domain_owner(input: TokenStream) -> TokenStream { - conversion::impl_derive_ref_into_domain_owner(input) +pub fn derive_ref_into_domain_owner(input: TokenStream) -> Result { + let input = syn2::parse2(input)?; + + Ok(conversion::impl_derive_ref_into_domain_owner(&input)) } /// Implements the `iroha_executor::Validate` trait for the given `Executor` struct. As @@ -211,7 +243,7 @@ pub fn derive_ref_into_domain_owner(input: TokenStream) -> TokenStream { /// `block_height` are needed. The types can be unqualified, but not aliased. #[manyhow] #[proc_macro_derive(Validate)] -pub fn derive_validate(input: TokenStream2) -> TokenStream2 { +pub fn derive_validate(input: TokenStream) -> TokenStream { let mut emitter = Emitter::new(); let Some(input) = emitter.handle(syn2::parse2(input)) else { @@ -256,7 +288,7 @@ pub fn derive_validate(input: TokenStream2) -> TokenStream2 { /// ``` #[manyhow] #[proc_macro_derive(Visit, attributes(visit))] -pub fn derive_visit(input: TokenStream2) -> TokenStream2 { +pub fn derive_visit(input: TokenStream) -> TokenStream { let mut emitter = Emitter::new(); let Some(input) = emitter.handle(syn2::parse2(input)) else { @@ -297,7 +329,7 @@ pub fn derive_visit(input: TokenStream2) -> TokenStream2 { /// ``` #[manyhow] #[proc_macro_derive(ValidateEntrypoints, attributes(entrypoints))] -pub fn derive_entrypoints(input: TokenStream2) -> TokenStream2 { +pub fn derive_entrypoints(input: TokenStream) -> TokenStream { let mut emitter = Emitter::new(); let Some(input) = emitter.handle(syn2::parse2(input)) else { @@ -318,7 +350,7 @@ pub fn derive_entrypoints(input: TokenStream2) -> TokenStream2 { /// The types can be unqualified, but not aliased. #[manyhow] #[proc_macro_derive(ExpressionEvaluator)] -pub fn derive_expression_evaluator(input: TokenStream2) -> TokenStream2 { +pub fn derive_expression_evaluator(input: TokenStream) -> TokenStream { let mut emitter = Emitter::new(); let Some(input) = emitter.handle(syn2::parse2(input)) else { @@ -340,7 +372,7 @@ pub fn derive_expression_evaluator(input: TokenStream2) -> TokenStream2 { /// `host`: `iroha_executor::smart_contract::Host`. The types can be unqualified, but not aliased. #[manyhow] #[proc_macro_derive(Constructor)] -pub fn derive_constructor(input: TokenStream2) -> TokenStream2 { +pub fn derive_constructor(input: TokenStream) -> TokenStream { let mut emitter = Emitter::new(); let Some(input) = emitter.handle(syn2::parse2(input)) else { diff --git a/smart_contract/executor/derive/src/token.rs b/smart_contract/executor/derive/src/token.rs index 69f7915d65e..6d961c1c3ad 100644 --- a/smart_contract/executor/derive/src/token.rs +++ b/smart_contract/executor/derive/src/token.rs @@ -1,10 +1,10 @@ //! Module with [`derive_token`](crate::derive_token) macro implementation -use super::*; +use proc_macro2::TokenStream; +use quote::quote; /// [`derive_token`](crate::derive_token()) macro implementation -pub fn impl_derive_token(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); +pub fn impl_derive_token(input: &syn2::DeriveInput) -> TokenStream { let generics = &input.generics; let ident = &input.ident; @@ -15,10 +15,9 @@ pub fn impl_derive_token(input: TokenStream) -> TokenStream { #impl_token #impl_try_from_permission_token } - .into() } -fn impl_token(ident: &syn::Ident, generics: &syn::Generics) -> proc_macro2::TokenStream { +fn impl_token(ident: &syn2::Ident, generics: &syn2::Generics) -> proc_macro2::TokenStream { let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); quote! { @@ -46,10 +45,7 @@ fn impl_token(ident: &syn::Ident, generics: &syn::Generics) -> proc_macro2::Toke } } -fn impl_try_from_permission_token( - ident: &syn::Ident, - generics: &syn::Generics, -) -> proc_macro2::TokenStream { +fn impl_try_from_permission_token(ident: &syn2::Ident, generics: &syn2::Generics) -> TokenStream { let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); let token_id = quote! { ::name() }; diff --git a/smart_contract/executor/derive/src/validate.rs b/smart_contract/executor/derive/src/validate.rs index de6cc982cf1..8a6a890b695 100644 --- a/smart_contract/executor/derive/src/validate.rs +++ b/smart_contract/executor/derive/src/validate.rs @@ -1,28 +1,27 @@ //! Module with [`derive_validate`](crate::derive_validate) macro implementation -use proc_macro2::Span; -use syn::{Attribute, Ident, Path, Type}; - -use super::*; +use darling::FromAttributes; +use manyhow::Result; +use proc_macro2::{Span, TokenStream}; +use quote::quote; +use syn2::{Attribute, Ident, Type}; /// [`derive_validate`](crate::derive_validate()) macro implementation -pub fn impl_derive_validate_grant_revoke(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - let ident = input.ident; +pub fn impl_derive_validate_grant_revoke(input: &syn2::DeriveInput) -> Result { + let ident = &input.ident; - let (validate_grant_impl, validate_revoke_impl) = gen_validate_impls(&input.attrs); + let (validate_grant_impl, validate_revoke_impl) = gen_validate_impls(&input.attrs)?; let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); - quote! { + Ok(quote! { impl #impl_generics ::iroha_executor::permission::ValidateGrantRevoke for #ident #ty_generics #where_clause { #validate_grant_impl #validate_revoke_impl } - } - .into() + }) } /// Enum representing possible attributes. @@ -37,119 +36,140 @@ enum ValidateAttribute { }, } -impl ValidateAttribute { - fn from_attributes<'attr, A>(attributes: A) -> Self - where - A: IntoIterator, - { +impl FromAttributes for ValidateAttribute { + // we use `Option::or` to select the first specified condition in case of duplicates + // but we still _want_ to validate that each attribute parses successfully + // this is to ensure that we provide the user with as much validation as possible, instead of bailing out early + // `Option::or_else` would NOT work here, as it would not validate conditions after the first valid one + #[allow(clippy::or_fun_call)] + fn from_attributes(attrs: &[Attribute]) -> darling::Result { + let mut accumulator = darling::error::Accumulator::default(); + let mut general_condition: Option = None; let mut grant_condition: Option = None; let mut revoke_condition: Option = None; - let general_path: Path = syn::parse_str("validate").unwrap(); - let grant_path: Path = syn::parse_str("validate_grant").unwrap(); - let revoke_path: Path = syn::parse_str("validate_revoke").unwrap(); - - for attribute in attributes { - let path = &attribute.path; - - // Skip if it's not our attribute - if path != &general_path && path != &grant_path && path != &revoke_path { + for attr in attrs { + let path = attr.path(); + if !path.is_ident("validate") + && !path.is_ident("validate_grant") + && !path.is_ident("validate_revoke") + { continue; } - let Some(proc_macro2::TokenTree::Group(group)) = - attribute.tokens.clone().into_iter().next() - else { - panic!("Expected parentheses group"); - }; - assert!( - group.delimiter() == proc_macro2::Delimiter::Parenthesis, - "Expected parentheses" - ); - let tokens = group.stream().into(); - - match path { - _general if path == &general_path => { - assert!(grant_condition.is_none() && revoke_condition.is_none(), - "`validate` attribute can't be used with `validate_grant` or `validate_revoke` attributes"); - assert!( - general_condition.is_none(), - "`validate` attribute duplication is not allowed" - ); - - general_condition.replace(syn::parse(tokens).unwrap()); + let Some(list) = accumulator.handle(attr.meta.require_list().map_err(darling::Error::from)) else { continue; }; + let tokens = &list.tokens; + + if path.is_ident("validate") { + if grant_condition.is_some() || revoke_condition.is_some() { + accumulator.push(darling::Error::custom( + "`validate` attribute can't be used with `validate_grant` or `validate_revoke` attributes" + ).with_span(&attr)) } - _grant if path == &grant_path => { - assert!( - general_condition.is_none(), - "`validate_grant` attribute can't be used with `validate` attribute" - ); - assert!( - grant_condition.is_none(), - "`validate_grant` attribute duplication is not allowed" - ); - - grant_condition.replace(syn::parse(tokens).unwrap()); + if general_condition.is_some() { + accumulator.push( + darling::Error::custom("`validate` attribute duplication is not allowed") + .with_span(&attr), + ) } - _revoke if path == &revoke_path => { - assert!( - general_condition.is_none(), - "`validate_revoke` attribute can't be used with `validate` attribute" - ); - assert!( - revoke_condition.is_none(), - "`validate_revoke` attribute duplication is not allowed" - ); - - revoke_condition.replace(syn::parse(tokens).unwrap()); + + general_condition = general_condition + .or(accumulator + .handle(syn2::parse2(tokens.clone()).map_err(darling::Error::from))); + } else if path.is_ident("grant") { + if general_condition.is_some() { + accumulator.push( + darling::Error::custom( + "`validate_grant` attribute can't be used with `validate` attribute", + ) + .with_span(&attr), + ) } - path => { - panic!( - "Unexpected attribute: `{}`. Expected `validate`, `validate_grant` or `validate_revoke`", - path.get_ident().map_or_else(|| "".to_owned(), ToString::to_string) + if grant_condition.is_some() { + accumulator.push( + darling::Error::custom( + "`validate_grant` attribute duplication is not allowed", + ) + .with_span(&attr), ) } + + grant_condition = grant_condition + .or(accumulator + .handle(syn2::parse2(tokens.clone()).map_err(darling::Error::from))); + } else if path.is_ident("revoke") { + if general_condition.is_some() { + accumulator.push( + darling::Error::custom( + "`validate_revoke` attribute can't be used with `validate` attribute", + ) + .with_span(&attr), + ) + } + if revoke_condition.is_some() { + accumulator.push( + darling::Error::custom( + "`validate_revoke` attribute duplication is not allowed", + ) + .with_span(&attr), + ) + } + + revoke_condition = revoke_condition + .or(accumulator + .handle(syn2::parse2(tokens.clone()).map_err(darling::Error::from))); + } else { + unreachable!() } } - match (general_condition, grant_condition, revoke_condition) { - (Some(condition), None, None) => ValidateAttribute::General(condition), + let result = match (general_condition, grant_condition, revoke_condition) { + (Some(condition), None, None) => Ok(ValidateAttribute::General(condition)), (None, Some(grant_condition), Some(revoke_condition)) => { - ValidateAttribute::Separate { + Ok(ValidateAttribute::Separate { grant_condition, revoke_condition, - } + }) } (None, Some(_grant_condition), None) => { - panic!("`validate_grant` attribute should be used together with `validate_revoke` attribute") + Err(darling::Error::custom( + "`validate_grant` attribute should be used together with `validate_revoke` attribute" + )) } (None, None, Some(_revoke_condition)) => { - panic!("`validate_revoke` attribute should be used together with `validate_grant` attribute") + Err(darling::Error::custom( + "`validate_revoke` attribute should be used together with `validate_grant` attribute" + )) } - (None, None, None) => panic!("`validate` attribute or combination of `validate_grant` and `validate_revoke` attributes is required"), - _ => unreachable!(), - } + (None, None, None) => Err(darling::Error::custom( + "`validate` attribute or combination of `validate_grant` and `validate_revoke` attributes is required", + )), + _ => Err(darling::Error::custom("Invalid combination of attributes")), + }; + + let res = accumulator.handle(result); + + accumulator.finish().map(|_| res.unwrap()) } } fn gen_validate_impls( attributes: &[Attribute], -) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) { - let validate_attribute = ValidateAttribute::from_attributes(attributes); - +) -> Result<(proc_macro2::TokenStream, proc_macro2::TokenStream)> { + let validate_attribute = ValidateAttribute::from_attributes(attributes)?; match validate_attribute { - ValidateAttribute::General(pass_condition) => ( + ValidateAttribute::General(pass_condition) => Ok(( gen_validate_impl(IsiName::Grant, &pass_condition), gen_validate_impl(IsiName::Revoke, &pass_condition), - ), + )), ValidateAttribute::Separate { grant_condition, revoke_condition, - } => ( + } => Ok(( gen_validate_impl(IsiName::Grant, &grant_condition), gen_validate_impl(IsiName::Revoke, &revoke_condition), - ), + )), } } diff --git a/smart_contract/trigger/derive/Cargo.toml b/smart_contract/trigger/derive/Cargo.toml index 486eaa75ad7..b2c4d84a6b4 100644 --- a/smart_contract/trigger/derive/Cargo.toml +++ b/smart_contract/trigger/derive/Cargo.toml @@ -15,6 +15,10 @@ workspace = true proc-macro = true [dependencies] -syn.workspace = true -quote.workspace = true -proc-macro2.workspace = true +iroha_macro_utils = { workspace = true } + +syn2 = { workspace = true } +manyhow = { workspace = true } +quote = { workspace = true } +proc-macro2 = { workspace = true } +darling = { workspace = true } diff --git a/smart_contract/trigger/derive/src/entrypoint.rs b/smart_contract/trigger/derive/src/entrypoint.rs index e045d262b5a..57a5b467e93 100644 --- a/smart_contract/trigger/derive/src/entrypoint.rs +++ b/smart_contract/trigger/derive/src/entrypoint.rs @@ -1,6 +1,10 @@ //! Module wht [`main`](super::main) macro implementation -use super::*; +use iroha_macro_utils::Emitter; +use manyhow::emit; +use proc_macro2::TokenStream; +use quote::quote; +use syn2::parse_quote; mod export { pub const TRIGGER_MAIN: &str = "_iroha_trigger_main"; @@ -8,18 +12,21 @@ mod export { /// [`main`](super::main()) macro implementation #[allow(clippy::needless_pass_by_value)] -pub fn impl_entrypoint(_attr: TokenStream, item: TokenStream) -> TokenStream { - let syn::ItemFn { +pub fn impl_entrypoint(emitter: &mut Emitter, item: syn2::ItemFn) -> TokenStream { + let syn2::ItemFn { attrs, vis, sig, mut block, - } = parse_macro_input!(item); - - assert!( - syn::ReturnType::Default == sig.output, - "Trigger `main()` function must not have a return type" - ); + } = item; + + if sig.output != syn2::ReturnType::Default { + emit!( + emitter, + sig.output, + "Trigger `main()` function must not have a return type" + ) + } let fn_name = &sig.ident; @@ -32,7 +39,7 @@ pub fn impl_entrypoint(_attr: TokenStream, item: TokenStream) -> TokenStream { ), ); - let main_fn_name = syn::Ident::new(export::TRIGGER_MAIN, proc_macro2::Span::call_site()); + let main_fn_name = syn2::Ident::new(export::TRIGGER_MAIN, proc_macro2::Span::call_site()); quote! { /// Smart contract entrypoint @@ -50,5 +57,4 @@ pub fn impl_entrypoint(_attr: TokenStream, item: TokenStream) -> TokenStream { #vis #sig #block } - .into() } diff --git a/smart_contract/trigger/derive/src/lib.rs b/smart_contract/trigger/derive/src/lib.rs index 71a0fb417c6..01701a708e9 100644 --- a/smart_contract/trigger/derive/src/lib.rs +++ b/smart_contract/trigger/derive/src/lib.rs @@ -1,8 +1,8 @@ //! Crate with trigger procedural macros. -use proc_macro::TokenStream; -use quote::quote; -use syn::{parse_macro_input, parse_quote}; +use iroha_macro_utils::Emitter; +use manyhow::{emit, manyhow}; +use proc_macro2::TokenStream; mod entrypoint; @@ -22,7 +22,20 @@ mod entrypoint; /// todo!() /// } /// ``` +#[manyhow] #[proc_macro_attribute] pub fn main(attr: TokenStream, item: TokenStream) -> TokenStream { - entrypoint::impl_entrypoint(attr, item) + let mut emitter = Emitter::new(); + + if !attr.is_empty() { + emit!(emitter, "#[main] attribute does not accept arguments"); + } + + let Some(item) = emitter.handle(syn2::parse2(item)) else { + return emitter.finish_token_stream(); + }; + + let result = entrypoint::impl_entrypoint(&mut emitter, item); + + emitter.finish_token_stream_with(result) }