Skip to content

Commit

Permalink
[refactor] #3882: Add basic generics support to derive(HasOrigin)
Browse files Browse the repository at this point in the history
Signed-off-by: Nikita Strygin <[email protected]>
  • Loading branch information
DCNick3 committed Sep 26, 2023
1 parent a3b1b8b commit 4b768fa
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 15 deletions.
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 19 additions & 10 deletions data_model/derive/src/has_origin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -19,13 +20,16 @@ const HAS_ORIGIN_ATTR: &str = "has_origin";

pub struct HasOriginEnum {
ident: Ident,
#[allow(unused)]
generics: syn2::Generics,
variants: Vec<HasOriginVariant>,
origin: Type,
}

impl FromDeriveInput for HasOriginEnum {
fn from_derive_input(input: &syn2::DeriveInput) -> darling::Result<Self> {
let ident = input.ident.clone();
let generics = input.generics.clone();

let Some(variants) = darling::ast::Data::<HasOriginVariant, ()>::try_from(&input.data)?.take_enum() else {
return Err(darling::Error::custom("Expected enum"));
Expand All @@ -35,6 +39,7 @@ impl FromDeriveInput for HasOriginEnum {

Ok(Self {
ident,
generics,
variants,
origin,
})
Expand Down Expand Up @@ -71,12 +76,14 @@ attr_struct2! {
}
}

pub fn impl_has_origin(input: &syn2::DeriveInput) -> Result<TokenStream> {
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;
Expand All @@ -96,8 +103,10 @@ pub fn impl_has_origin(input: &syn2::DeriveInput) -> Result<TokenStream> {
})
.collect::<Vec<syn2::Arm>>();

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) -> &<Self::Origin as Identifiable>::Id {
Expand All @@ -109,5 +118,5 @@ pub fn impl_has_origin(input: &syn2::DeriveInput) -> Result<TokenStream> {
}
}
}
})
}
}
12 changes: 9 additions & 3 deletions data_model/derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -598,8 +598,14 @@ pub fn partially_tagged_deserialize_derive(input: TokenStream) -> Result<TokenSt
/// ```
#[manyhow]
#[proc_macro_derive(HasOrigin, attributes(has_origin))]
pub fn has_origin_derive(input: TokenStream) -> Result<TokenStream> {
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)
}
53 changes: 53 additions & 0 deletions data_model/derive/tests/has_origin_generics.rs
Original file line number Diff line number Diff line change
@@ -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<ObjectId> 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<T: Identifiable<Id = ObjectId>> {
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
);
}
}

0 comments on commit 4b768fa

Please sign in to comment.