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

View File

@ -7,7 +7,7 @@ Roadmap:
1. Install over.ts and get an example of add with number and bigint implementations working with it. [DONE]
2. Use the builder pattern to get add working with its implmentations defined incrementally before producing the final overload. [Didn't quite work the way we wnated, but maybe we can do an alternative later.]
3. Make a version of over.ts, call it util/overload.ts, that takes an array of implementations without redundant type annotation. [DONE]
4. Improve that version of overload with rtti to select the implementation without the implementations having to throw errors. [IN PROGRESS]
4. Improve that version of overload with rtti to select the implementation without the implementations having to throw errors. [DONE]
3. Use the builder pattern to get a single object with both an add and a negate method, with both defined incrementally, working.
4. Incorporate a subtract method that works on numbers and bigint by separate definitions but with dependencies on add and negate.
5. Incorporate a subtract method that works with one generic implementation that works for both number and bigint with dependencies on add and negate.

View File

@ -5,6 +5,7 @@
main: 'index.js',
scripts: {
test: 'echo "Error: no test specified" && exit 1',
build: 'ttsc -b',
},
keywords: [
'math',
@ -18,6 +19,13 @@
license: 'Apache-2.0',
dependencies: {
'over.ts': 'github:m93a/over.ts',
'reflect-metadata': '^0.1.13',
typescript: '^4.8.2',
'typescript-rtti': '^0.8.2',
},
devDependencies: {
'@types/node': '^18.7.21',
'ts-node': '^10.9.1',
ttypescript: '^1.5.13',
},
}

View File

@ -1,20 +1,204 @@
lockfileVersion: 5.4
specifiers:
'@types/node': ^18.7.21
over.ts: github:m93a/over.ts
reflect-metadata: ^0.1.13
ts-node: ^10.9.1
ttypescript: ^1.5.13
typescript: ^4.8.2
typescript-rtti: ^0.8.2
dependencies:
over.ts: github.com/m93a/over.ts/0fd6e18afd4ca5a23c9e09d1fcd6b7357b642247
reflect-metadata: 0.1.13
typescript: 4.8.2
typescript-rtti: 0.8.2_jbmzfhhdwyaatac7voo5jihpea
devDependencies:
'@types/node': 18.7.21
ts-node: 10.9.1_xxlomja3uhaizt5vuokco7nale
ttypescript: 1.5.13_s5ojjbx2isjkawqptqpitvy25q
packages:
/@cspotcode/source-map-support/0.8.1:
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
engines: {node: '>=12'}
dependencies:
'@jridgewell/trace-mapping': 0.3.9
dev: true
/@jridgewell/resolve-uri/3.1.0:
resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==}
engines: {node: '>=6.0.0'}
dev: true
/@jridgewell/sourcemap-codec/1.4.14:
resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==}
dev: true
/@jridgewell/trace-mapping/0.3.9:
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
dependencies:
'@jridgewell/resolve-uri': 3.1.0
'@jridgewell/sourcemap-codec': 1.4.14
dev: true
/@tsconfig/node10/1.0.9:
resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==}
dev: true
/@tsconfig/node12/1.0.11:
resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==}
dev: true
/@tsconfig/node14/1.0.3:
resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==}
dev: true
/@tsconfig/node16/1.0.3:
resolution: {integrity: sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==}
dev: true
/@types/node/18.7.21:
resolution: {integrity: sha512-rLFzK5bhM0YPyCoTC8bolBjMk7bwnZ8qeZUBslBfjZQou2ssJdWslx9CZ8DGM+Dx7QXQiiTVZ/6QO6kwtHkZCA==}
dev: true
/acorn-walk/8.2.0:
resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==}
engines: {node: '>=0.4.0'}
dev: true
/acorn/8.8.0:
resolution: {integrity: sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==}
engines: {node: '>=0.4.0'}
hasBin: true
dev: true
/arg/4.1.3:
resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
dev: true
/create-require/1.1.1:
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
dev: true
/diff/4.0.2:
resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
engines: {node: '>=0.3.1'}
dev: true
/function-bind/1.1.1:
resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
dev: true
/has/1.0.3:
resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
engines: {node: '>= 0.4.0'}
dependencies:
function-bind: 1.1.1
dev: true
/is-core-module/2.10.0:
resolution: {integrity: sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==}
dependencies:
has: 1.0.3
dev: true
/make-error/1.3.6:
resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
dev: true
/path-parse/1.0.7:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
dev: true
/reflect-metadata/0.1.13:
resolution: {integrity: sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==}
dev: false
/resolve/1.22.1:
resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==}
hasBin: true
dependencies:
is-core-module: 2.10.0
path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0
dev: true
/supports-preserve-symlinks-flag/1.0.0:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
dev: true
/ts-node/10.9.1_xxlomja3uhaizt5vuokco7nale:
resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==}
hasBin: true
peerDependencies:
'@swc/core': '>=1.2.50'
'@swc/wasm': '>=1.2.50'
'@types/node': '*'
typescript: '>=2.7'
peerDependenciesMeta:
'@swc/core':
optional: true
'@swc/wasm':
optional: true
dependencies:
'@cspotcode/source-map-support': 0.8.1
'@tsconfig/node10': 1.0.9
'@tsconfig/node12': 1.0.11
'@tsconfig/node14': 1.0.3
'@tsconfig/node16': 1.0.3
'@types/node': 18.7.21
acorn: 8.8.0
acorn-walk: 8.2.0
arg: 4.1.3
create-require: 1.1.1
diff: 4.0.2
make-error: 1.3.6
typescript: 4.8.2
v8-compile-cache-lib: 3.0.1
yn: 3.1.1
dev: true
/ttypescript/1.5.13_s5ojjbx2isjkawqptqpitvy25q:
resolution: {integrity: sha512-KT/RBfGGlVJFqEI8cVvI3nMsmYcFvPSZh8bU0qX+pAwbi7/ABmYkzn7l/K8skw0xmYjVCoyaV6WLsBQxdadybQ==}
hasBin: true
peerDependencies:
ts-node: '>=8.0.2'
typescript: '>=3.2.2'
dependencies:
resolve: 1.22.1
ts-node: 10.9.1_xxlomja3uhaizt5vuokco7nale
typescript: 4.8.2
dev: true
/typescript-rtti/0.8.2_jbmzfhhdwyaatac7voo5jihpea:
resolution: {integrity: sha512-MZUHMX+Up1+7dYaAcOYSTxKc4PNLNXhuXYBkIzEKvQvxJ6xp7wcjTtiIYmB9+RJxRH5/9phZLqKTMpy20aQ4Aw==}
engines: {node: '>=10'}
peerDependencies:
reflect-metadata: ^0.1.13
typescript: '4.6'
dependencies:
reflect-metadata: 0.1.13
typescript: 4.8.2
dev: false
/typescript/4.8.2:
resolution: {integrity: sha512-C0I1UsrrDHo2fYI5oaCGbSejwX4ch+9Y5jTQELvovfmFkK3HHSZJB8MSJcWLmCUBzQBchCrZ9rMRV6GuNrvGtw==}
engines: {node: '>=4.2.0'}
hasBin: true
dev: false
/v8-compile-cache-lib/3.0.1:
resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
dev: true
/yn/3.1.1:
resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
engines: {node: '>=6'}
dev: true
github.com/m93a/over.ts/0fd6e18afd4ca5a23c9e09d1fcd6b7357b642247:
resolution: {tarball: https://codeload.github.com/m93a/over.ts/tar.gz/0fd6e18afd4ca5a23c9e09d1fcd6b7357b642247}

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')
})
}

View File

@ -1,12 +1,18 @@
{
"compilerOptions": {
"target": "ES2022",
"moduleResolution": "node",
"rootDir": "src",
"baseUrl": ".",
"paths": {
"over.ts": ["node_modules/over.ts/src/index.js"],
"typescript-rtti": ["node_modules/typescript-rtti/dist/index.js"]
},
"plugins": [{ "transform": "typescript-rtti/dist/transformer" }],
"rootDir": ".",
"outDir": "obj"
},
"include": [
"src/steps/three.ts",
"node_modules/over.ts/src/index.ts",
"src/steps/*.ts",
"src/util/*.ts"
]
}