use super::typed_fact_set::TypedFactType;
use super::typed_payload::TypedPayload;
use std::marker::PhantomData;
mod sealed {
pub trait Sealed {}
pub trait Member<T, Index> {}
pub trait SubsetOf<Super, Proof> {}
}
#[doc(hidden)]
pub struct EmptySet;
#[doc(hidden)]
pub struct WithMember<Head, Rest>(PhantomData<fn() -> (Head, Rest)>);
#[doc(hidden)]
pub trait MemberList: sealed::Sealed + Send + Sync + 'static {}
impl sealed::Sealed for EmptySet {}
impl MemberList for EmptySet {}
impl<Head: 'static, Rest: MemberList> sealed::Sealed for WithMember<Head, Rest> {}
impl<Head: 'static, Rest: MemberList> MemberList for WithMember<Head, Rest> {}
#[doc(hidden)]
pub struct MemberFound;
#[doc(hidden)]
pub struct MemberAfter<Index>(PhantomData<fn() -> Index>);
#[diagnostic::on_unimplemented(
message = "`{T}` is not a declared member of this stage's set",
label = "`{T}` is missing from the declared set",
note = "`emit` requires the fact type in the handler's `Output` set and `perform` \
requires the effect type in its `AllowedEffects` set; add the member to the \
handler declaration and the matching DSL clause, or remove this call \
(FLOWIP-120z)"
)]
pub trait Member<T, Index>: sealed::Member<T, Index> {}
impl<T, Rest> sealed::Member<T, MemberFound> for WithMember<T, Rest> {}
impl<T, Rest> Member<T, MemberFound> for WithMember<T, Rest> {}
#[diagnostic::do_not_recommend]
impl<T, Head, Rest, Index> sealed::Member<T, MemberAfter<Index>> for WithMember<Head, Rest> where
Rest: Member<T, Index>
{
}
#[diagnostic::do_not_recommend]
impl<T, Head, Rest, Index> Member<T, MemberAfter<Index>> for WithMember<Head, Rest> where
Rest: Member<T, Index>
{
}
#[doc(hidden)]
pub struct SubsetProofEnd;
#[doc(hidden)]
pub struct SubsetProofStep<AtIndex, RestProof>(PhantomData<fn() -> (AtIndex, RestProof)>);
#[diagnostic::on_unimplemented(
message = "not every member of this fact set is declared by the enclosing set",
note = "every fact an allowed effect may author must be a member of the handler's \
`Output` set, and a stage arrow must equal the handler's declared set in both \
directions (FLOWIP-120z)"
)]
pub trait SubsetOf<Super, Proof>: sealed::SubsetOf<Super, Proof> {}
impl<Super> sealed::SubsetOf<Super, SubsetProofEnd> for EmptySet {}
impl<Super> SubsetOf<Super, SubsetProofEnd> for EmptySet {}
#[diagnostic::do_not_recommend]
impl<Head, Rest, Super, At, RestProof> sealed::SubsetOf<Super, SubsetProofStep<At, RestProof>>
for WithMember<Head, Rest>
where
Super: Member<Head, At>,
Rest: SubsetOf<Super, RestProof>,
{
}
#[diagnostic::do_not_recommend]
impl<Head, Rest, Super, At, RestProof> SubsetOf<Super, SubsetProofStep<At, RestProof>>
for WithMember<Head, Rest>
where
Super: Member<Head, At>,
Rest: SubsetOf<Super, RestProof>,
{
}
#[doc(hidden)]
#[must_use]
pub const fn const_str_eq(a: &str, b: &str) -> bool {
let a = a.as_bytes();
let b = b.as_bytes();
if a.len() != b.len() {
return false;
}
let mut i = 0;
while i < a.len() {
if a[i] != b[i] {
return false;
}
i += 1;
}
true
}
#[doc(hidden)]
pub const fn require_distinct(_head_disjoint: (), _rest_distinct: ()) {}
#[doc(hidden)]
pub trait FactList: MemberList {
const DISTINCT_EVENT_TYPES: ();
fn append_fact_types(out: &mut Vec<TypedFactType>);
}
impl FactList for EmptySet {
const DISTINCT_EVENT_TYPES: () = ();
fn append_fact_types(_out: &mut Vec<TypedFactType>) {}
}
impl<Head, Rest> FactList for WithMember<Head, Rest>
where
Head: TypedPayload + Send + Sync + 'static,
Rest: FactList + FactNameDisjoint<Head>,
{
const DISTINCT_EVENT_TYPES: () = require_distinct(
<Rest as FactNameDisjoint<Head>>::OK,
Rest::DISTINCT_EVENT_TYPES,
);
fn append_fact_types(out: &mut Vec<TypedFactType>) {
out.push(TypedFactType::of::<Head>());
Rest::append_fact_types(out);
}
}
#[doc(hidden)]
pub trait FactNameDisjoint<X> {
const OK: ();
}
impl<X> FactNameDisjoint<X> for EmptySet {
const OK: () = ();
}
impl<X, Head, Rest> FactNameDisjoint<X> for WithMember<Head, Rest>
where
X: TypedPayload,
Head: TypedPayload,
Rest: FactNameDisjoint<X>,
{
const OK: () = {
assert!(
!const_str_eq(X::EVENT_TYPE, Head::EVENT_TYPE),
"duplicate member in a declared stage fact set: two members share an event \
type; each fact type appears exactly once (FLOWIP-120z)"
);
Rest::OK
};
}
#[cfg(test)]
mod tests {
use super::*;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
struct First {
value: u32,
}
impl TypedPayload for First {
const EVENT_TYPE: &'static str = "member_set.first";
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Second {
value: u32,
}
impl TypedPayload for Second {
const EVENT_TYPE: &'static str = "member_set.second";
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Third {
value: u32,
}
impl TypedPayload for Third {
const EVENT_TYPE: &'static str = "member_set.third";
}
type Pair = WithMember<First, WithMember<Second, EmptySet>>;
type Triple = WithMember<First, WithMember<Second, WithMember<Third, EmptySet>>>;
fn member_holds<List, T, Index>()
where
List: Member<T, Index>,
{
}
fn subset_holds<Sub, Super, Proof>()
where
Sub: SubsetOf<Super, Proof>,
{
}
#[test]
fn membership_witnesses_resolve_at_any_position() {
member_holds::<Pair, First, _>();
member_holds::<Pair, Second, _>();
member_holds::<Triple, Third, _>();
}
#[test]
fn subset_witnesses_resolve_order_insensitively() {
type Reversed = WithMember<Second, WithMember<First, EmptySet>>;
subset_holds::<EmptySet, Pair, _>();
subset_holds::<Pair, Triple, _>();
subset_holds::<Pair, Reversed, _>();
subset_holds::<Reversed, Pair, _>();
}
#[test]
fn const_str_eq_compares_bytewise() {
assert!(const_str_eq("payment.authorized", "payment.authorized"));
assert!(!const_str_eq("payment.authorized", "payment.declined"));
assert!(!const_str_eq("payment", "payment.authorized"));
}
#[test]
fn fact_list_projects_member_types_in_declared_order() {
let mut out = Vec::new();
Triple::append_fact_types(&mut out);
let names: Vec<&str> = out.iter().map(|t| t.event_type.as_str()).collect();
assert_eq!(
names,
vec![
"member_set.first.v1",
"member_set.second.v1",
"member_set.third.v1"
]
);
}
#[test]
fn distinct_event_types_proof_evaluates_for_distinct_sets() {
const _: () = Triple::DISTINCT_EVENT_TYPES;
}
}