diff --git a/packages/axelar-wasm-std-derive/src/lib.rs b/packages/axelar-wasm-std-derive/src/lib.rs index 4448f6f6a..2bd583971 100644 --- a/packages/axelar-wasm-std-derive/src/lib.rs +++ b/packages/axelar-wasm-std-derive/src/lib.rs @@ -5,7 +5,7 @@ use itertools::Itertools; use proc_macro::TokenStream; use proc_macro2::{Ident, Span, TokenStream as TokenStream2}; use quote::quote; -use syn::{DeriveInput, FieldsNamed, ItemEnum, Variant}; +use syn::{DeriveInput, FieldsNamed, Generics, ItemEnum, Variant}; #[proc_macro_derive(IntoContractError)] pub fn into_contract_error_derive(input: TokenStream) -> TokenStream { @@ -33,6 +33,7 @@ pub fn into_contract_error_derive(input: TokenStream) -> TokenStream { /// ``` /// use std::collections::BTreeMap; /// use serde::Serialize; +/// use cosmwasm_std::Event; /// /// use axelar_wasm_std_derive::IntoEvent; /// @@ -45,7 +46,10 @@ pub fn into_contract_error_derive(input: TokenStream) -> TokenStream { /// } /// /// #[derive(IntoEvent)] -/// enum SomeEvents { +/// enum SomeEvents where +/// T: Serialize, +/// U: Serialize +/// { /// SomeEmptyEvent, /// SomeOtherEmptyEvent {}, /// SomeEvent { @@ -54,17 +58,21 @@ pub fn into_contract_error_derive(input: TokenStream) -> TokenStream { /// some_bool: bool, /// some_object: SomeObject, /// }, +/// SomeGenericsEvent { +/// some_generics: T, +/// some_other_generics: U, +/// } /// } /// -/// let actual = cosmwasm_std::Event::from(SomeEvents::SomeEmptyEvent); -/// let expected = cosmwasm_std::Event::new("some_empty_event"); +/// let actual: Event = SomeEvents::SomeEmptyEvent.non_generic().into(); +/// let expected = Event::new("some_empty_event"); /// assert_eq!(actual, expected); /// -/// let actual = cosmwasm_std::Event::from(SomeEvents::SomeOtherEmptyEvent {}); -/// let expected = cosmwasm_std::Event::new("some_other_empty_event"); +/// let actual: Event = SomeEvents::SomeOtherEmptyEvent {}.non_generic().into(); +/// let expected = Event::new("some_other_empty_event"); /// assert_eq!(actual, expected); /// -/// let actual = cosmwasm_std::Event::from(SomeEvents::SomeEvent { +/// let actual: Event = SomeEvents::SomeEvent { /// some_uint: 42, /// some_string: "some string".to_string(), /// some_bool: true, @@ -74,13 +82,22 @@ pub fn into_contract_error_derive(input: TokenStream) -> TokenStream { /// some_vec: vec!["a".to_string(), "b".to_string()], /// some_map: [("a".to_string(), "b".to_string()), ("c".to_string(), "d".to_string()), ("e".to_string(), "f".to_string())].into_iter().collect(), /// }, -/// }); -/// let expected = cosmwasm_std::Event::new("some_event") +/// }.non_generic().into(); +/// let expected = Event::new("some_event") /// .add_attribute("some_uint", "42") /// .add_attribute("some_string", "\"some string\"") /// .add_attribute("some_bool", "true") /// .add_attribute("some_object", "{\"some_option\":\"some option\",\"some_other_option\":null,\"some_vec\":[\"a\",\"b\"],\"some_map\":{\"a\":\"b\",\"c\":\"d\",\"e\":\"f\"}}"); /// assert_eq!(actual, expected); +/// +/// let actual: Event = SomeEvents::SomeGenericsEvent { +/// some_generics: "some generics".to_string(), +/// some_other_generics: 42, +/// }.into(); +/// let expected = Event::new("some_generics_event") +/// .add_attribute("some_generics", "\"some generics\"") +/// .add_attribute("some_other_generics", "42"); +/// assert_eq!(actual, expected); /// ``` /// /// ```compile_fail @@ -102,15 +119,52 @@ pub fn into_contract_error_derive(input: TokenStream) -> TokenStream { /// # Unnamed(u64), /// # } /// ``` +/// +/// ```compile_fail +/// # use axelar_wasm_std_derive::IntoEvent; +/// +/// # #[derive(IntoEvent)] // should not compile because the event enum has no variants +/// # enum SomeEmptyEvent {} +/// ``` +/// +/// ```compile_fail +/// # use axelar_wasm_std_derive::IntoEvent; +/// +/// # #[derive(IntoEvent)] // should not compile because const generics are not supported +/// # enum SomeConstGenericEvent { +/// # SomeConstGenericEvent { some_array: [u8; N] }, +/// # } +/// ``` +/// +/// ```compile_fail +/// # use axelar_wasm_std_derive::IntoEvent; +/// +/// # #[derive(IntoEvent)] // should not compile because lifetime generics are not supported +/// # enum SomeLifetimeGenericEvent<'a> { +/// # SomeLifetimeGenericEvent { some_str: &'a str }, +/// # } +/// ``` +/// +/// ```compile_fail +/// # use axelar_wasm_std_derive::IntoEvent; +/// +/// # #[derive(IntoEvent)] +/// # enum SomeEventWithoutGenerics { +/// # SomeEvent +/// # } +/// +/// # let _ = SomeEventWithoutGenerics::SomeEvent.non_generic(); // should not compile because the event enum has no generics +/// ``` #[proc_macro_derive(IntoEvent)] pub fn into_event(input: TokenStream) -> TokenStream { let ItemEnum { variants, ident: event_enum, + generics, .. } = syn::parse_macro_input!(input as syn::ItemEnum); - try_into_event(event_enum, variants) + try_into_event(event_enum, variants, generics) .unwrap_or_else(syn::Error::into_compile_error) .into() } @@ -118,6 +172,7 @@ pub fn into_event(input: TokenStream) -> TokenStream { fn try_into_event( event_enum: Ident, variants: impl IntoIterator, + generics: Generics, ) -> Result { let variant_matches: Vec<_> = variants .into_iter() @@ -134,24 +189,56 @@ fn try_into_event( )), }) .try_collect()?; + if variant_matches.is_empty() { + return Err(syn::Error::new(Span::call_site(), "no variants found")); + } + + if generics.lifetimes().count() > 0 || generics.const_params().count() > 0 { + return Err(syn::Error::new( + Span::call_site(), + "lifetimes and const generics are not supported", + )); + } + + let non_generic = impl_non_generic(&event_enum, &generics); + let (impl_generics, type_generics, where_clause) = generics.split_for_impl(); Ok(quote! { - impl From<&#event_enum> for cosmwasm_std::Event { - fn from(event: &#event_enum) -> Self { + #non_generic + + impl #impl_generics From<&#event_enum #type_generics> for cosmwasm_std::Event #where_clause { + fn from(event: &#event_enum #type_generics) -> Self { match event { #(#variant_matches),* } } } - impl From<#event_enum> for cosmwasm_std::Event { - fn from(event: #event_enum) -> Self { + impl #impl_generics From<#event_enum #type_generics> for cosmwasm_std::Event #where_clause { + fn from(event: #event_enum #type_generics) -> Self { (&event).into() } } }) } +fn impl_non_generic(event_enum: &Ident, generics: &Generics) -> TokenStream2 { + let typed_generic_param_count = generics.type_params().count(); + if typed_generic_param_count == 0 { + return quote! {}; + } + + let empties = (0..typed_generic_param_count).map(|_| quote! { cosmwasm_std::Empty }); + + quote! { + impl #event_enum<#(#empties), *> { + pub fn non_generic(self) -> Self { + self + } + } + } +} + fn match_structured_variant( event_enum: &Ident, variant_name: &Ident,