From 2f0a9936a3e3ae74e01438c056c8a4e33125a5b9 Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Sun, 25 Sep 2022 20:02:01 +0000 Subject: [PATCH] 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 Reviewed-on: https://code.studioinfinity.org/glen/typomath/pulls/4 --- README.md | 2 +- package.json5 | 8 ++ pnpm-lock.yaml | 186 ++++++++++++++++++++++++++++++++++++++++++- src/steps/four.ts | 26 ++++++ src/steps/one.ts | 2 +- src/steps/three.ts | 5 +- src/steps/two.ts | 4 +- src/util/overload.ts | 35 ++++++-- tsconfig.json | 12 ++- 9 files changed, 267 insertions(+), 13 deletions(-) create mode 100644 src/steps/four.ts diff --git a/README.md b/README.md index 9277561..1630517 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/package.json5 b/package.json5 index 6c809b4..f41f4b6 100644 --- a/package.json5 +++ b/package.json5 @@ -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', }, } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f729120..7f11d21 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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} diff --git a/src/steps/four.ts b/src/steps/four.ts new file mode 100644 index 0000000..688f2bb --- /dev/null +++ b/src/steps/four.ts @@ -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)) diff --git a/src/steps/one.ts b/src/steps/one.ts index a9f0eab..0ef25f4 100644 --- a/src/steps/one.ts +++ b/src/steps/one.ts @@ -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', diff --git a/src/steps/three.ts b/src/steps/three.ts index e76253a..05e563c 100644 --- a/src/steps/three.ts +++ b/src/steps/three.ts @@ -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)) + diff --git a/src/steps/two.ts b/src/steps/two.ts index 6e47772..2080f99 100644 --- a/src/steps/two.ts +++ b/src/steps/two.ts @@ -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) // 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)) diff --git a/src/util/overload.ts b/src/util/overload.ts index 71ab3fa..f59b58b 100644 --- a/src/util/overload.ts +++ b/src/util/overload.ts @@ -1,17 +1,42 @@ +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): UnionToIntersection { + imps: T, callSite?: CallSite): UnionToIntersection { + const impTypes = reflect(callSite).parameters[0].elements return ((...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') }) } diff --git a/tsconfig.json b/tsconfig.json index 99d3a5d..ac3755e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -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" ] }