Skip to content

Commit

Permalink
Implement event::Sourced within #[derive(Event)] macro (#5, #1)
Browse files Browse the repository at this point in the history
- add event::Sourcing trait for more handy dynamic dispatch usage

Co-authored-by: tyranron <[email protected]>
  • Loading branch information
ilslv and tyranron authored Sep 3, 2021
1 parent 8efd428 commit 1201086
Show file tree
Hide file tree
Showing 8 changed files with 250 additions and 11 deletions.
107 changes: 105 additions & 2 deletions codegen/impl/src/es/event/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::convert::TryFrom;

use proc_macro2::TokenStream;
use quote::quote;
use syn::spanned::Spanned as _;
use syn::{parse_quote, spanned::Spanned as _};
use synthez::{ParseAttrs, ToTokens};

/// Expands `#[derive(Event)]` macro.
Expand Down Expand Up @@ -35,7 +35,7 @@ pub struct VariantAttrs {
///
/// [`Event`]: arcana_core::es::event::Event
#[derive(Debug, ToTokens)]
#[to_tokens(append(impl_event, gen_uniqueness_glue_code))]
#[to_tokens(append(impl_event, impl_event_sourced, gen_uniqueness_glue_code))]
pub struct Definition {
/// [`syn::Ident`](struct@syn::Ident) of this enum's type.
pub ident: syn::Ident,
Expand Down Expand Up @@ -184,6 +184,51 @@ impl Definition {
}
}

/// Generates code to derive [`event::Sourced`][0] trait, by simply matching
/// each enum variant, which is expected to have itself an
/// [`event::Sourced`][0] implementation.
///
/// [0]: arcana_core::es::event::Sourced
#[must_use]
pub fn impl_event_sourced(&self) -> TokenStream {
let ty = &self.ident;
let (_, ty_gens, _) = self.generics.split_for_impl();
let turbofish_gens = ty_gens.as_turbofish();

let var = self.variants.iter().map(|v| &v.ident);
let var_ty =
self.variants.iter().flat_map(|v| &v.fields).map(|f| &f.ty);

let mut ext_gens = self.generics.clone();
ext_gens.params.push(parse_quote! { __S });
ext_gens.make_where_clause().predicates.push(parse_quote! {
Self: #( ::arcana::es::event::Sourced<#var_ty> )+*
});
let (impl_gens, _, where_clause) = ext_gens.split_for_impl();

let unreachable_arm = self.has_ignored_variants.then(|| {
quote! { _ => unreachable!(), }
});

quote! {
#[automatically_derived]
impl #impl_gens ::arcana::es::event::Sourced<#ty#ty_gens>
for Option<__S> #where_clause
{
fn apply(&mut self, event: &#ty#ty_gens) {
match event {
#(
#ty#turbofish_gens::#var(f) => {
::arcana::es::event::Sourced::apply(self, f);
},
)*
#unreachable_arm
}
}
}
}
}

/// Generates hidden machinery code used to statically check that all the
/// [`Event::name`][0]s and [`Event::version`][1]s pairs are corresponding
/// to a single Rust type.
Expand Down Expand Up @@ -270,6 +315,7 @@ mod spec {
use quote::quote;
use syn::parse_quote;

#[allow(clippy::too_many_lines)]
#[test]
fn derives_enum_impl() {
let input = parse_quote! {
Expand Down Expand Up @@ -305,6 +351,24 @@ mod spec {
}
}

#[automatically_derived]
impl<__S> ::arcana::es::event::Sourced<Event> for Option<__S>
where
Self: ::arcana::es::event::Sourced<FileEvent> +
::arcana::es::event::Sourced<ChatEvent>
{
fn apply(&mut self, event: &Event) {
match event {
Event::File(f) => {
::arcana::es::event::Sourced::apply(self, f);
},
Event::Chat(f) => {
::arcana::es::event::Sourced::apply(self, f);
},
}
}
}

#[automatically_derived]
#[doc(hidden)]
impl ::arcana::es::event::codegen::Versioned for Event {
Expand Down Expand Up @@ -374,6 +438,7 @@ mod spec {
);
}

#[allow(clippy::too_many_lines)]
#[test]
fn derives_enum_with_generics_impl() {
let input = parse_quote! {
Expand Down Expand Up @@ -409,6 +474,25 @@ mod spec {
}
}

#[automatically_derived]
impl<'a, F, C, __S> ::arcana::es::event::Sourced<Event<'a, F, C> >
for Option<__S>
where
Self: ::arcana::es::event::Sourced<FileEvent<'a, F> > +
::arcana::es::event::Sourced<ChatEvent<'a, C> >
{
fn apply(&mut self, event: &Event<'a, F, C>) {
match event {
Event::<'a, F, C>::File(f) => {
::arcana::es::event::Sourced::apply(self, f);
},
Event::<'a, F, C>::Chat(f) => {
::arcana::es::event::Sourced::apply(self, f);
},
}
}
}

#[automatically_derived]
#[doc(hidden)]
impl<'a, F, C> ::arcana::es::event::codegen::Versioned
Expand Down Expand Up @@ -528,6 +612,25 @@ mod spec {
}
}

#[automatically_derived]
impl<__S> ::arcana::es::event::Sourced<Event> for Option<__S>
where
Self: ::arcana::es::event::Sourced<FileEvent> +
::arcana::es::event::Sourced<ChatEvent>
{
fn apply(&mut self, event: &Event) {
match event {
Event::File(f) => {
::arcana::es::event::Sourced::apply(self, f);
},
Event::Chat(f) => {
::arcana::es::event::Sourced::apply(self, f);
},
_ => unreachable!(),
}
}
}

#[automatically_derived]
#[doc(hidden)]
impl ::arcana::es::event::codegen::Versioned for Event {
Expand Down
6 changes: 5 additions & 1 deletion codegen/shim/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ use proc_macro::TokenStream;
/// is that all the underlying [`Event`] or [`Versioned`] impls should be
/// derived too.
///
/// Also, provides a blanket [`Sourced`] implementation for every state, which
/// can be sourced from all the enum variants.
///
/// > __WARNING:__ Currently may not work with complex generics using where
/// > clause because of `const` evaluation limitations. Should be
/// > lifted once [rust-lang/rust#57775] is resolved.
Expand Down Expand Up @@ -87,7 +90,7 @@ use proc_macro::TokenStream;
/// #[derive(Event)]
/// enum AnyEvent {
/// Chat(ChatEvent),
/// #[event(ignore)]
/// #[event(ignore)] // not recommended for real usage
/// DuplicateChat(DuplicateChatEvent),
/// }
///
Expand All @@ -102,6 +105,7 @@ use proc_macro::TokenStream;
/// ```
///
/// [`Event`]: arcana_core::es::Event
/// [`Sourced`]: arcana_core::es::event::Sourced
/// [`Versioned`]: arcana_core::es::event::Versioned
/// [0]: arcana_core::es::Event::name()
/// [1]: arcana_core::es::Event::version()
Expand Down
3 changes: 3 additions & 0 deletions core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ derive_more = { version = "0.99", features = ["deref", "deref_mut", "display", "
ref-cast = "1.0"
sealed = { version = "0.3", optional = true }

[dev-dependencies]
arcana = { version = "0.1.0-dev", path = "..", features = ["derive", "es"] }

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
94 changes: 92 additions & 2 deletions core/src/es/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,17 +90,108 @@ pub trait Sourced<Ev: ?Sized> {
fn apply(&mut self, event: &Ev);
}

impl<Ev: Event + ?Sized, S: Sourced<Ev>> Sourced<Ev> for Option<S> {
impl<Ev: Versioned + ?Sized, S: Sourced<Ev>> Sourced<Ev> for Option<S> {
fn apply(&mut self, event: &Ev) {
if let Some(state) = self {
state.apply(event);
}
}
}

impl<'e, S: Sourced<dyn Event + 'e>> Sourced<dyn Event + 'e> for Option<S> {
fn apply(&mut self, event: &(dyn Event + 'e)) {
if let Some(state) = self {
state.apply(event);
}
}
}

impl<'e, S: Sourced<dyn Event + Send + 'e>> Sourced<dyn Event + Send + 'e>
for Option<S>
{
fn apply(&mut self, event: &(dyn Event + Send + 'e)) {
if let Some(state) = self {
state.apply(event);
}
}
}

impl<'e, S: Sourced<dyn Event + Send + Sync + 'e>>
Sourced<dyn Event + Send + Sync + 'e> for Option<S>
{
fn apply(&mut self, event: &(dyn Event + Send + Sync + 'e)) {
if let Some(state) = self {
state.apply(event);
}
}
}

/// [`Event`] sourcing the specified state.
///
/// This is a reversed version of [`Sourced`] trait intended to simplify usage
/// of trait objects describing sets of [`Event`]s. Shouldn't be implemented
/// manually, but rather used as blanket impl.
///
/// # Example
///
/// ```rust
/// # use arcana::es::event::{self, Sourced as _};
/// #
/// #[derive(Debug, Eq, PartialEq)]
/// struct Chat;
///
/// #[derive(event::Versioned)]
/// #[event(name = "chat", version = 1)]
/// struct ChatEvent;
///
/// impl event::Initialized<ChatEvent> for Chat {
/// fn init(_: &ChatEvent) -> Self {
/// Self
/// }
/// }
///
/// let mut chat = Option::<Chat>::None;
/// let ev = event::Initial(ChatEvent);
/// let ev: &dyn event::Sourcing<Option<Chat>> = &ev;
/// chat.apply(ev);
/// assert_eq!(chat, Some(Chat));
/// ```
pub trait Sourcing<S: ?Sized> {
/// Applies this [`Event`] to the specified `state`.
fn apply_to(&self, state: &mut S);
}

impl<Ev, S: ?Sized> Sourcing<S> for Ev
where
S: Sourced<Ev>,
{
fn apply_to(&self, state: &mut S) {
state.apply(self);
}
}

impl<'e, S> Sourced<dyn Sourcing<S> + 'e> for S {
fn apply(&mut self, event: &(dyn Sourcing<S> + 'e)) {
event.apply_to(self);
}
}

impl<'e, S> Sourced<dyn Sourcing<S> + Send + 'e> for S {
fn apply(&mut self, event: &(dyn Sourcing<S> + Send + 'e)) {
event.apply_to(self);
}
}

impl<'e, S> Sourced<dyn Sourcing<S> + Send + Sync + 'e> for S {
fn apply(&mut self, event: &(dyn Sourcing<S> + Send + Sync + 'e)) {
event.apply_to(self);
}
}

/// Before a state can be [`Sourced`] it needs to be [`Initialized`].
pub trait Initialized<Ev: ?Sized> {
/// Creates an initial state from the given [`Event`].
#[must_use]
fn init(event: &Ev) -> Self;
}

Expand Down Expand Up @@ -247,7 +338,6 @@ pub mod codegen {
///
/// [`Eq`]: std::cmp::Eq
// TODO: Remove once `Eq` trait is allowed in `const` context.
#[must_use]
const fn str_eq(l: &str, r: &str) -> bool {
if l.len() != r.len() {
return false;
Expand Down
4 changes: 2 additions & 2 deletions core/src/es/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ pub mod event;
#[doc(inline)]
pub use self::event::{
Event, Initial as InitialEvent, Initialized as EventInitialized,
Name as EventName, Sourced as EventSourced, Version as EventVersion,
Versioned as VersionedEvent,
Name as EventName, Sourced as EventSourced, Sourcing as EventSourcing,
Version as EventVersion, Versioned as VersionedEvent,
};
41 changes: 40 additions & 1 deletion examples/event.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use arcana::es::event::{self, Event, Initial};
use arcana::es::event::{self, Event, Initial, Initialized, Sourced, Sourcing};

#[derive(event::Versioned)]
#[event(name = "chat.created", version = 1)]
Expand All @@ -25,15 +25,54 @@ enum AnyEvent {
Message(MessageEvent),
}

#[derive(Debug, Eq, PartialEq)]
struct Chat {
message_count: usize,
}

impl Initialized<ChatCreated> for Chat {
fn init(_: &ChatCreated) -> Self {
Self { message_count: 0 }
}
}

impl Sourced<MessagePosted> for Chat {
fn apply(&mut self, _: &MessagePosted) {
self.message_count += 1;
}
}

#[derive(Debug, Eq, PartialEq)]
struct Message;

impl Initialized<MessagePosted> for Message {
fn init(_: &MessagePosted) -> Self {
Self
}
}

fn main() {
let mut chat = Option::<Chat>::None;
let mut message = Option::<Message>::None;

let ev = ChatEvent::Created(ChatCreated.into());
chat.apply(&ev);
assert_eq!(ev.name(), "chat.created");
assert_eq!(chat, Some(Chat { message_count: 0 }));

let ev = ChatEvent::MessagePosted(MessagePosted);
chat.apply(&ev);
assert_eq!(ev.name(), "message.posted");
assert_eq!(chat, Some(Chat { message_count: 1 }));

let ev: &dyn Sourcing<Option<Chat>> = &ev;
chat.apply(ev);
assert_eq!(chat, Some(Chat { message_count: 2 }));

let ev = MessageEvent::MessagePosted(MessagePosted.into());
message.apply(&ev);
assert_eq!(ev.name(), "message.posted");
assert_eq!(message, Some(Message));

let ev = AnyEvent::Chat(ChatEvent::Created(ChatCreated.into()));
assert_eq!(ev.name(), "chat.created");
Expand Down
2 changes: 1 addition & 1 deletion src/es/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#[doc(inline)]
pub use arcana_core::es::event::{
Event, Initial, Initialized, Name, Sourced, Version, Versioned,
Event, Initial, Initialized, Name, Sourced, Sourcing, Version, Versioned,
};

#[cfg(feature = "derive")]
Expand Down
Loading

0 comments on commit 1201086

Please sign in to comment.