import {reflect, CallSite} from 'typescript-rtti' type UnionToIntersection = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never; export default function overload( imps: T, callSite?: CallSite): UnionToIntersection { const impTypes = reflect(callSite).parameters[0].elements return ((...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 type MergeArrayValues = Pick> & Pick> & { [P in (keyof L & keyof R)]: [...L[P], ...R[P]] } function mergeImps( impT: T, impU: U): MergeArrayValues { 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 result[k] = [...impT[k], ...impU[k]] } else { result[key] = impT[key] } } else { result[key] = impU[key] } } return result as MergeArrayValues } class ImpMerger { implementations: T constructor(imps: T) { this.implementations = imps } with( moreImps: U, callSite?: CallSite): ImpMerger> { 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(this.implementations, moreImps)) } imps(): T { return this.implementations } } function merge(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 = { [P in keyof T]: UnionToIntersection} function overloadValues(impObject: T) : OverloadedValues { return Object.fromEntries(Object.entries(impObject).map( ([k, v]) => [k, overload(v)])) as OverloadedValues } export {merge, mergeImps, overloadValues}