Skip to content

Instantly share code, notes, and snippets.

@fvilante
Last active October 28, 2020 07:04
Show Gist options
  • Save fvilante/a76659e6d9c864a29144571cb87b1feb to your computer and use it in GitHub Desktop.
Save fvilante/a76659e6d9c864a29144571cb87b1feb to your computer and use it in GitHub Desktop.
Unit Test With Types - Experimental
// ========================================================================================
// Utilities types
// ========================================================================================
type TU11 = '(T extends U) and (U extends T)'
type TU10 = '(T extends U) and (U NOT extends T)'
type TU01 = '(T NOT extends U) and (U extends T)'
type TU00 = '(T NOT extends U) and (U NOT extends T)'
type Ext<T,U,A,B> = T extends U ? A : B // In type system jargon we can say that "T is assignable to U".
type ExtendsCheck_<T,U> = T extends U ? (U extends T ? TU11 : TU10) : (U extends T ? TU01 : TU00)
type ExtendsCheck<T,U> = Ext<T,U,Ext<U,T,TU11,TU10>,Ext<U,T,TU01, TU00>>
type ExtendsCheck__<T,U> = ExtendsCheck<ExtendsCheck<T,U>,ExtendsCheck<U,T>>
type IsEqual<A,B,X=true,Y=false> = ExtendsCheck<A,B> extends TU11 ? X : Y
type IsNotEqual<A,B,X=true,Y=false> = IsEqual<A,B,Y,X>
type UnionFilterByExclusion<T, K> = T extends K ? never : T
type UnionFilterByInclusion<T, K> = T extends K ? T : never // todo: when possible discover why Ext<T,K,T,never> do not works
type TMap_<T> = {kind: T }
type ToBeEqual<T,U> = ExtendsCheck<T,U> extends TU11 ? true : false
type AssertsEqual<Expected, Actual> = ToBeEqual<Expected,Actual>
type TestAll<T extends true[]> = T
// ========================================================================================
// Variant Lib
// ========================================================================================
type Variant<K extends string = string, A = unknown> = {
readonly kind: "Variant"
readonly ctor: K
readonly value: A
}
type VariantConstructor<K extends string, A> = {
readonly kind: "VariantConstructor"
readonly ctor: K
readonly run: (data:A) => Variant<K,A>
} & ((data:A) => Variant<K,A>)
declare const VariantConstructor: <V extends Variant>(k: V["ctor"]) => VariantConstructor<V["ctor"],V["value"]>
// unit test
// NoBytes
type NoBytes = Variant<"NoBytes",undefined>
const NoBytes = VariantConstructor<NoBytes>("NoBytes")
// Byte(number)
type Byte = Variant<"Byte",number>
const Byte = VariantConstructor<Byte>("Byte")
// Bytes(number[])
type Bytes = Variant<"Bytes",number[]>
const Bytes = VariantConstructor<Bytes>("Bytes")
// type Data = NoBytes | Byte(number) | Bytes(number)
type Data = NoBytes | Byte | Bytes
const Data = VariantConstructor<Data>("Bytes")
declare const data: Data
// ========================================================================================
// Pattern Match lib
// ========================================================================================
// lib single
type Exhausted = "exhausted"
type Pending = "pending"
type Unused = "unused"
type E = Exhausted | Pending | Unused
type State<C extends E = E, T = unknown> =
C extends Exhausted ? [Exhausted, never] :
C extends Unused ? [Unused, never] :
C extends Pending ? [/*switchState:*/ Pending, /*pending:*/ T]
: never
type PendingTypes<S extends State> = S[1]
type SwitchState<S extends State> = S[0]
type Remove<T,U> = T extends T ? Exclude<T,U> : never
type Next<S extends State,U extends PendingTypes<S>> =
// Is exhausted?
S extends State<Exhausted>
// yes, so you are over-exhausting
? State<Unused>
// no, so if remove next types will we get to nothing ?
: State<Pending,Remove<PendingTypes<S>,U>> extends State<Pending,never>
// yes, so we are exhausted
? State<Exhausted>
// no, so remove types out
: State<Pending,Remove<PendingTypes<S>,U>>
// unit test
type U00 = string | number | 1[] | Data
type S00 = State<Pending,U00>
type S01 = Next<S00,number>
type T01 = AssertsEqual<PendingTypes<S00>,U00>
type T02 = AssertsEqual<UnionFilterByExclusion<U00,number>,PendingTypes<S01>>
type T03 = AssertsEqual<[Pending, Pending], [SwitchState<S00>,SwitchState<S01>]>
type U01 = Bytes | Byte | 1[]
type S02 = Next<S01, U01>
type T04 = AssertsEqual<S02,State<Pending, UnionFilterByExclusion<PendingTypes<S01>,U01>>>
type U02 = string | Variant<"NoBytes", undefined>
type S03 = Next<S02, U02>
type T05 = AssertsEqual<S03,State<Exhausted, never>>
//type S04 = Next<S03, 2[]>
type T06 = TestAll<[T01,T02,T03,T04,T05]>
type FilterVariant<T> = UnionFilterByInclusion<T,Variant>
type FilterNonVariant<T> = UnionFilterByExclusion<T,Variant>
type MasterType<S extends State = State> = [
Variant: {
head: FilterVariant<PendingTypes<S>>
pattern: MasterType<S>[0]['head']['ctor']
data: MasterType<S>[0]['head']['value']
step: MasterType<S>[0]['data']
},
NonVariant: {
head: FilterNonVariant<PendingTypes<S>>
pattern: MasterType<S>[1]['head']
data: MasterType<S>[1]['head']
step: MasterType<S>[1]['pattern']
}
]
type Pattern<P> = P extends Variant ? P['ctor'] : P
type Data_<P> = P extends Variant ? P : P
type Step<P> = P extends Variant ? P : P
type DefaultResultState<S extends State> = S extends State<Exhausted> ? State<Unused> : State<Exhausted>
type Master<S extends State = State> = PendingTypes<S>
type SelectVariant<T extends Variant, K extends string> =
T extends Variant<K, infer A> ? Variant<K,A> : never
type InferPattern<P> = P extends Variant ? P['ctor'] : P
type Match<S extends State = State,R = unknown> = {
withNonVariant: <P extends FilterNonVariant<PendingTypes<S>>>(pattern: P, match: (data: P) => R) => Match<Next<S,Step<P>>,R>
withVariant: <P extends FilterVariant<PendingTypes<S>>, X extends P['ctor'] | P >(pattern: X, match: (data:SelectVariant<P,X>) => R) => Match<Next<S,Step<SelectVariant<P,X>>>,R>
with_: <X,R>(..._:[pattern: X]) => R
when: <P extends MasterType<S>>(pattern: Pattern<P>, whenCondition: (_: Data_<P>) => boolean, match: (data: Data_<P>) => R) => Match<Next<S,P>,R>
default: (match: (data: PendingTypes<S>) => R) => Match<DefaultResultState<S>,R>
run: () => S extends State<Exhausted> ? R : never
getState: () => S
}
declare const Match: <A,R>(data:A) => Match<State<Pending,A>,R>
// unit test
type Q0 = 0
type Q1 = 1
type Q2 = 2
type Q = Q0 | Q1 | Q2
declare const q0: Q0
declare const q1: Q1
declare const q2: Q2
declare const q: Q
type X0 = `a`
type X1 = `b`
type X2 = `c`
type X = X0 | X1 | X2
declare const x0: X0
declare const x1: X1
declare const x2: X2
declare const x: X
type S11 = ReturnType<Match<S00,undefined>['getState']>
type T10 = AssertsEqual<S11, ["pending", U00]>
const T11 = Match<Q|Data,undefined>(q)
.with_()
//.withVariant({ variant: 'Byte'}, data => undefined)
//.with(0, data => undefined)
//.with(1, data => undefined)
//.with(2, data => undefined)
//.withVariant('Byte', data => undefined)
///.with
// lib multi
type Param2<T extends State[]> = {
readonly [K in keyof T]: T[K] extends State<E,infer A> ? A : never
}
// Map Next in a tuple of states and transitions
type Next2<SS extends State[],P extends Param2<SS>> = {
[K in keyof SS]: Next<SS[K] extends State ? SS[K] : never, P[K]>
//fix: I`d like to avoid extends in line up above.
}
type ToTuple<U extends unknown[]> = {
[K in keyof U]: State<"not_exhausted", U[K]>
}
type MultMatch<SS extends State[],R> = {
with: <PP extends Param2<SS>>(pattern: PP, match: (data: PP) => R) => MultMatch<Next2<SS,PP>,R>
when: <PP extends Param2<SS>>(pattern: PP, whenCondition: (data:PP) => boolean, match: (data: PP) => R) => MultMatch<SS,R>
run: () => SS extends State<"exhausted">[] ? R : never
default: (match: (data: Param2<SS>) => R) => MultMatch<SS extends State<"exhausted">[] ? State<"unused">[] : State<"exhausted">[],R> // note: may improve to detect if there is a over-exaustion
}
declare const MultMatch: <U extends unknown[],R>(data: U) => MultMatch<ToTuple<U>,R>
// Lib pre-test
// =================================================================
// Test
// =================================================================
// ----------------------
// use playground
// ----------------------
// single
const y = Match<Q,string>(q)
.with(0, _ => "foo1")
//.when(0, data => data === data, _ => "foo2")
.with(1, _ => "foo3")
.with(2, _ => "foo4")
//.default( data => `foo5 ${data}`)
.run()
// mult
const y2 = MultMatch<[Q,X],string>([q,x])
.with([q0,x1], _ => "foo1")
.when([q1,x0], ([x,y]) => String(x) !== y, _ => "foo2")
//.with([Q1,X1], _ => "foo3")
//.with([Q2,X2], _ => "foo4")
.default( data => `foo5 ${data}`)
.run()
//
// single
const y1_ = Match<Data,string>(data)
.withVariant("NoBytes", _ => `there's no bytes there`)
//.with( {variant: "Byte"}, bytes => `bytes: ${bytes}`)
//.with()
//.with({variant: ""})
//.with({variant: ""}, _ => "foo3")
//.with("NoBytes", _ => "foo4")
//.run()
// mult
const y2__ = MultMatch<[Q,X],string>([q,x])
.with([q0,x1], _ => "foo1")
.with([q1,x2], _ => "foo3")
.with([q2,x0], _ => "foo4")
.run()
//
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment