feat(overload): Use typescript-rtti to select implementations (#4)

Note that the changes to module resolution cause steps one and two no longer
  to run because node can't find the imports, although they still compile fine.

Co-authored-by: Glen Whitney <glen@studioinfinity.org>
Reviewed-on: #4
This commit is contained in:
Glen Whitney 2022-09-25 20:02:01 +00:00
parent a2d019b021
commit 2f0a9936a3
9 changed files with 267 additions and 13 deletions

26
src/steps/four.ts Normal file
View file

@ -0,0 +1,26 @@
import 'reflect-metadata'
import overload from '../util/overload.js'
const addImps = [
(x: number, y: number) => {
if (typeof x === 'number' && typeof y === 'number') return x + y
throw new TypeError('Can only add numbers')
},
(x: string, y: string) => {
if (typeof x === 'string' && typeof y === 'string') {
return 'Yay' + x + y
}
throw new TypeError('or strings')
},
(x: bigint, final?: string) => x.toString() + final
] as const
const adder = overload(addImps)
console.log(adder(1, 2))
console.log(adder('a', 'b'))
console.log(adder(2n))
// And make sure typescript complains on signatures not covered by an imp:
//@ts-expect-error
console.log(adder(2n, 4n))

View file

@ -1,4 +1,4 @@
import { useTypes } from 'over.ts/src/index.js';
import { useTypes } from '../../node_modules/over.ts/src/index.js';
const types = {
number: (x: unknown): x is number => typeof x === 'number',

View file

@ -6,7 +6,10 @@ const adder = overload([
throw new TypeError('Can only add numbers')
},
(x: string, y: string) => 'Yay' + x + y
])
] as const)
console.log(adder(1, 2))
console.log(adder('a', 'b'))
//@ts-expect-error
console.log(adder(3n, 4n))

View file

@ -1,4 +1,4 @@
import { useTypes } from 'over.ts/src/index.js';
import { useTypes } from 'over.ts';
const types = {
number: (x: unknown): x is number => typeof x === 'number',
@ -69,6 +69,8 @@ addImps.addImp('number, number -> number', (a: number, b: number) => a+b) // ima
const addAll = addImps.addImp('bigint, bigint -> bigint', (a: bigint, b: bigint) => a+b) // and this is in the `bigint` file
const add = overload(addAll.finalize() as Omit<typeof addAll, 'finalize'|'addImp'>) // We wrap everything up elsewhere
// This should **NOT** be an error but sadly it is:
//@ts-expect-error
console.log('Sum of 5 and 7 is', add(5,7))
console.log('Sum of 5n and 7n is', add(5n, 7n))

View file

@ -1,17 +1,42 @@
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): UnionToIntersection<T[number]> {
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) {
try {
const val = imps[i](...a)
return val
} catch {
const paramTypes = 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')
})
}