Last active
October 28, 2020 07:04
-
-
Save fvilante/a76659e6d9c864a29144571cb87b1feb to your computer and use it in GitHub Desktop.
Unit Test With Types - Experimental
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ======================================================================================== | |
// 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