import { Type } from './type' class Case { type: Type fn: (x: In) => Out constructor(type: Type, fn: (x: In) => Out) { this.type = type this.fn = fn } } type InOfCase> = T extends Case ? In : never type OutOfCase> = T extends Case ? Out : never export class CaseSwitch> { cases: Cases[] private memoAccept: Type> | undefined constructor(cases: Cases[]) { if (cases.length === 0) { throw new Error("must have at least one case") } this.cases = cases } // implemented lazily because not every CaseSwitch will be used get accept(): Type> { if (this.memoAccept) { return this.memoAccept } const cases = this.cases let type = cases[0].type for (let i = 1; i < cases.length; i++) { type = type.or(cases[i].type) } this.memoAccept = type return this.memoAccept } run(val: InOfCase): OutOfCase { for (let c of this.cases) { if (c.type.guard(val)) { return c.fn(val); } } // raise a type error if none of the cases match the value. this.accept.assert(val); /* istanbul ignore next */ throw "unreachable"; } /** * Create a new CaseSwitch that also handles the specified case. */ when(type: Type, fn: (v: In) => Out): CaseSwitch> { const c = new Case(type, fn) return new CaseSwitch([...this.cases, c]) } } /** * Create a type switch that can be used with `match()`. */ export function when(type: Type, fn: (v: In) => Out) { const c = new Case(type, fn) return new CaseSwitch([c]) } /** * Match a value against a type switch created with `when()`. * * @example * const asString = t.match(foo, * t.when(t.array(t.str), xs => xs.join(' and ')) * .when(t.str, s => s) * .when(t.any, x => '' + x)) */ // export function match>(val: I, cases: CaseSwitch): O { export function match>(val: InOfCase, sw: CaseSwitch): OutOfCase { return sw.run(val) }