From e707078faf34fd448ed0c37decd86264f6518813 Mon Sep 17 00:00:00 2001 From: Nikita Strygin Date: Mon, 25 Sep 2023 14:38:02 +0300 Subject: [PATCH] [refactor] #3882: Add basic generics support to `derive(HasOrigin)` Signed-off-by: Nikita Strygin --- Cargo.lock | 4 +- data_model/derive/src/has_origin.rs | 29 ++++++---- data_model/derive/src/lib.rs | 12 +++-- .../derive/tests/has_origin_generics.rs | 53 +++++++++++++++++++ 4 files changed, 83 insertions(+), 15 deletions(-) create mode 100644 data_model/derive/tests/has_origin_generics.rs diff --git a/Cargo.lock b/Cargo.lock index 0f31f1171e9..9407cd43944 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3183,7 +3183,7 @@ dependencies = [ "quote", "serde", "serde_json", - "syn 2.0.26", + "syn 2.0.28", "trybuild", ] @@ -3321,7 +3321,7 @@ dependencies = [ "proc-macro2", "quote", "syn 1.0.109", - "syn 2.0.26", + "syn 2.0.28", ] [[package]] diff --git a/data_model/derive/src/has_origin.rs b/data_model/derive/src/has_origin.rs index 18df35f8d10..31033128b42 100644 --- a/data_model/derive/src/has_origin.rs +++ b/data_model/derive/src/has_origin.rs @@ -5,8 +5,9 @@ )] use darling::{FromDeriveInput, FromVariant}; -use iroha_macro_utils::{attr_struct2, parse_single_list_attr, parse_single_list_attr_opt}; -use manyhow::Result; +use iroha_macro_utils::{ + attr_struct2, parse_single_list_attr, parse_single_list_attr_opt, Emitter, +}; use proc_macro2::TokenStream; use quote::quote; use syn2::{parse_quote, Ident, Token, Type}; @@ -19,6 +20,8 @@ const HAS_ORIGIN_ATTR: &str = "has_origin"; pub struct HasOriginEnum { ident: Ident, + #[allow(unused)] + generics: syn2::Generics, variants: Vec, origin: Type, } @@ -26,6 +29,7 @@ pub struct HasOriginEnum { impl FromDeriveInput for HasOriginEnum { fn from_derive_input(input: &syn2::DeriveInput) -> darling::Result { let ident = input.ident.clone(); + let generics = input.generics.clone(); let Some(variants) = darling::ast::Data::::try_from(&input.data)?.take_enum() else { return Err(darling::Error::custom("Expected enum")); @@ -35,6 +39,7 @@ impl FromDeriveInput for HasOriginEnum { Ok(Self { ident, + generics, variants, origin, }) @@ -71,12 +76,14 @@ attr_struct2! { } } -pub fn impl_has_origin(input: &syn2::DeriveInput) -> Result { - let enum_ = HasOriginEnum::from_derive_input(input)?; +pub fn impl_has_origin(emitter: &mut Emitter, input: &syn2::DeriveInput) -> TokenStream { + let Some(enum_) = emitter.handle(HasOriginEnum::from_derive_input(input)) else { + return quote!(); + }; - // TODO: verify enum is non-empty (or make it work with empty enums) - // TODO: verify all the enum variants are newtype variants - // TODO: verify there are no generics on the enum + if enum_.variants.is_empty() { + return quote!(); + } let enum_ident = &enum_.ident; let enum_origin = &enum_.origin; @@ -96,8 +103,10 @@ pub fn impl_has_origin(input: &syn2::DeriveInput) -> Result { }) .collect::>(); - Ok(quote! { - impl HasOrigin for #enum_ident { + let (impl_generics, ty_generics, where_clause) = enum_.generics.split_for_impl(); + + quote! { + impl #impl_generics HasOrigin for #enum_ident #ty_generics #where_clause { type Origin = #enum_origin; fn origin_id(&self) -> &::Id { @@ -109,5 +118,5 @@ pub fn impl_has_origin(input: &syn2::DeriveInput) -> Result { } } } - }) + } } diff --git a/data_model/derive/src/lib.rs b/data_model/derive/src/lib.rs index e61eff96d8c..6351fa41329 100644 --- a/data_model/derive/src/lib.rs +++ b/data_model/derive/src/lib.rs @@ -598,8 +598,14 @@ pub fn partially_tagged_deserialize_derive(input: TokenStream) -> Result Result { - let input = syn2::parse2(input)?; +pub fn has_origin_derive(input: TokenStream) -> TokenStream { + let mut emitter = Emitter::new(); - has_origin::impl_has_origin(&input) + let Some(input) = emitter.handle(syn2::parse2(input)) else { + return emitter.finish_token_stream() + }; + + let result = has_origin::impl_has_origin(&mut emitter, &input); + + emitter.finish_token_stream_with(result) } diff --git a/data_model/derive/tests/has_origin_generics.rs b/data_model/derive/tests/has_origin_generics.rs new file mode 100644 index 00000000000..b344aba802e --- /dev/null +++ b/data_model/derive/tests/has_origin_generics.rs @@ -0,0 +1,53 @@ +use iroha_data_model::prelude::{HasOrigin, Identifiable}; +use iroha_data_model_derive::{HasOrigin, IdEqOrdHash}; + +#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash)] +struct ObjectId(pub i32); + +// fake impl for `#[derive(IdEqOrdHash)]` +impl From for iroha_data_model::IdBox { + fn from(_: ObjectId) -> Self { + unimplemented!("fake impl") + } +} + +#[derive(Debug, IdEqOrdHash)] +struct Object { + id: ObjectId, +} + +impl Object { + fn id(&self) -> &ObjectId { + &self.id + } +} + +#[allow(clippy::enum_variant_names)] // it's a test, duh +#[derive(Debug, HasOrigin)] +#[has_origin(origin = Object)] +enum ObjectEvent> { + EventWithId(ObjectId), + #[has_origin(event => &event.0)] + EventWithExtractor((ObjectId, i32)), + #[has_origin(obj => obj.id())] + EventWithAnotherExtractor(T), +} + +#[test] +fn has_origin() { + let events = vec![ + ObjectEvent::EventWithId(ObjectId(1)), + ObjectEvent::EventWithExtractor((ObjectId(2), 2)), + ObjectEvent::EventWithAnotherExtractor(Object { id: ObjectId(3) }), + ]; + let expected_ids = vec![ObjectId(1), ObjectId(2), ObjectId(3)]; + + for (event, expected_id) in events.into_iter().zip(expected_ids) { + assert_eq!( + event.origin_id(), + &expected_id, + "mismatched origin id for event {:?}", + event + ); + } +}