typomath/src/util/overload.ts

118 lines
4.1 KiB
TypeScript

import {reflect, CallSite} from 'typescript-rtti'
type UnionToIntersection<U> =
(U extends any ? (k: U) => void : never) extends ((k: infer I) => void)
? I
: never;
export default function overload<T extends readonly [...any[]]>(
imps: T, callSite?: CallSite): UnionToIntersection<T[number]> {
const impTypes = reflect(callSite).parameters[0].elements
return <any>((...a: any[]) => {
for (let i = 0; i < imps.length; ++i) {
const paramTypes = imps[i].functionType.parameters
|| impTypes[i].type.parameters
let match = true
const haveArgs = a.length
let onArg = 0
for (const param of paramTypes) {
if (param.isRest) {
// All the rest of the arguments must be of param's type
match = a.slice(onArg).every(
arg => param.type.matchesValue(arg))
break
}
if (onArg === haveArgs) {
// We've used all of the arguments, so better be optional
match = param.isOptional
break
}
// This argument must match this param's type and both are used
match = param.type.matchesValue(a[onArg])
onArg += 1
if (!match) break
}
// Make sure we used all the arguments
match &&= (onArg == haveArgs)
if (match) return imps[i](...a)
}
throw new TypeError(
`Actual arguments ${a} of type ${a.map(arg => typeof arg)} `
+ 'did not match any implementation')
})
}
type AVO = Record<string, readonly [...any[]]>
type MergeArrayValues<L extends AVO, R extends AVO> =
Pick<L, Exclude<keyof L, keyof R>>
& Pick<R, Exclude<keyof R, keyof L>>
& { [P in (keyof L & keyof R)]: [...L[P], ...R[P]] }
function mergeImps<T extends AVO, U extends AVO>(
impT: T, impU: U): MergeArrayValues<T, U> {
const dummy = Object.assign({}, impT, impU)
const result = {} as AVO
for (const key in dummy) {
if (key in impT) {
if (key in impU) {
const k = key as Extract<keyof T & keyof U, string>
result[k] = [...impT[k], ...impU[k]]
} else {
result[key] = impT[key]
}
} else {
result[key] = impU[key]
}
}
return result as MergeArrayValues<T, U>
}
class ImpMerger<T extends AVO> {
implementations: T
constructor(imps: T) {
this.implementations = imps
}
with<U extends AVO>(
moreImps: U, callSite?: CallSite): ImpMerger<MergeArrayValues<T,U>> {
const Utype = reflect(callSite).parameters[0]
// Annotate the implementations with their RTTI while we have it
for (const [key, imps] of Object.entries(moreImps)) {
if (!(imps[0].functionType)) {
const tuple = Utype.members.find(m => m.name === key)
for (let i = 0; i < imps.length; ++i) {
imps[i].functionType = tuple.type.elements[i].type
}
}
}
return new ImpMerger(mergeImps<T,U>(this.implementations, moreImps))
}
imps(): T {
return this.implementations
}
}
function merge<T extends AVO>(impsObject: T, callSite?: CallSite) {
const Ttype = reflect(callSite).parameters[0]
// Annotate the implementations with their RTTI while we have it
for (const [key, imps] of Object.entries(impsObject)) {
if (!(imps[0].functionType)) {
const tuple = Ttype.members.find(m => m.name === key)
for (let i = 0; i < imps.length; ++i) {
imps[i].functionType = tuple.type.elements[i].type
}
}
}
return new ImpMerger(impsObject)
}
type OverloadedValues<T extends AVO> = {
[P in keyof T]: UnionToIntersection<T[P][number]>}
function overloadValues<T extends AVO>(impObject: T) : OverloadedValues<T> {
return Object.fromEntries(Object.entries(impObject).map(
([k, v]) => [k, overload(v)])) as OverloadedValues<T>
}
export {merge, mergeImps, overloadValues}