Compare commits
No commits in common. "d50c1a9ccf006ed5227bc8c8046b0a924cb8c7cf" and "2cb8bc0099d151379e88ec2592ff68044453a609" have entirely different histories.
d50c1a9ccf
...
2cb8bc0099
16 changed files with 432 additions and 129 deletions
46
README.md
46
README.md
|
@ -11,28 +11,45 @@ pnpm build-and-run
|
||||||
|
|
||||||
## experiment
|
## experiment
|
||||||
|
|
||||||
Have a look at the section under `/src/experiment` and `/src/plugins`:
|
See: the section under `/src/experiment` and `/src/plugins`.
|
||||||
|
|
||||||
- `src/plugins/typeInferPlugin.ts` is the actual plugin
|
|
||||||
- in `tsconfig.json` we configure TypeScript to run the plugin
|
|
||||||
- `src/experiment/arithmeticInfer.ts` with an example where we define `__infer__`
|
|
||||||
- after running TypeScript: look at `build/experiment/arithmeticInfer.ts` where the `__infer__` string literal is replaced with the actual types
|
|
||||||
|
|
||||||
### The idea
|
### The idea
|
||||||
|
|
||||||
Create a TypeScript plugin which can replace a string literal like `__infer__` in a typed-function definition:
|
Create a TypeScript plugin which can replace structures like:
|
||||||
|
|
||||||
typed('square', '__infer__', <T>(dep: { ... } => { ... })
|
infer(factoryFunction)
|
||||||
|
|
||||||
with the actual types, something like:
|
where `factoryFunction` is a mathjs factory function in TypeScript, with something like:
|
||||||
|
|
||||||
|
infer({ signature: factoryFunction })
|
||||||
|
|
||||||
|
where `signature` is a string containing the type of the factory function and its dependencies.
|
||||||
|
|
||||||
|
Relevant methods of the TypeScript compiler are:
|
||||||
|
|
||||||
typed('square', '{ deps: { multiply: (a: T, b: T) => T; }; return: (a: T) => T }', <T>(dep: { ... } => { ... })
|
```ts
|
||||||
|
const program = ts.createProgram(fileNames, options)
|
||||||
|
const typeChecker = program.getTypeChecker()
|
||||||
|
|
||||||
|
// relevant methods:
|
||||||
|
//
|
||||||
|
// typeChecker.getSymbolAtLocation
|
||||||
|
// typeChecker.getTypeOfSymbolAtLocation
|
||||||
|
// typeChecker.getResolvedSignature
|
||||||
|
// typeChecker.getSignaturesOfType
|
||||||
|
```
|
||||||
|
|
||||||
|
### Status
|
||||||
|
|
||||||
|
None of the experiments (`infer1` and `infer2`) are outputting something useful yet.
|
||||||
|
|
||||||
(We can discuss what syntax we like most, this is just a POC)
|
|
||||||
|
|
||||||
### How to run
|
### How to run
|
||||||
|
|
||||||
pnpm build-and-run
|
pnpm experiment:infer1
|
||||||
|
pnpm experiment:infer1-direct
|
||||||
|
pnpm experiment:infer2
|
||||||
|
pnpm experiment:infer3
|
||||||
|
|
||||||
### Read more
|
### Read more
|
||||||
|
|
||||||
|
@ -44,8 +61,3 @@ with the actual types, something like:
|
||||||
- https://stackoverflow.com/questions/48886508/typechecker-api-how-do-i-find-inferred-type-arguments-to-a-function
|
- https://stackoverflow.com/questions/48886508/typechecker-api-how-do-i-find-inferred-type-arguments-to-a-function
|
||||||
- https://blog.logrocket.com/using-typescript-transforms-to-enrich-runtime-code-3fd2863221ed/
|
- https://blog.logrocket.com/using-typescript-transforms-to-enrich-runtime-code-3fd2863221ed/
|
||||||
- https://github.com/itsdouges/typescript-transformer-handbook#transforms
|
- https://github.com/itsdouges/typescript-transformer-handbook#transforms
|
||||||
|
|
||||||
### Interesting libraries
|
|
||||||
|
|
||||||
- https://github.com/GoogleFeud/ts-macros/
|
|
||||||
- https://ts-morph.com
|
|
||||||
|
|
|
@ -4,7 +4,12 @@
|
||||||
description: 'A hopeful final typescipt-pragmatic mathjs proof-of-concept',
|
description: 'A hopeful final typescipt-pragmatic mathjs proof-of-concept',
|
||||||
main: 'index.ts',
|
main: 'index.ts',
|
||||||
scripts: {
|
scripts: {
|
||||||
'build-and-run': 'ttsc -b && node build/index.js',
|
'build-and-run': 'ttsc -b && node build',
|
||||||
|
'experiment:infer1': 'ttsc -b && node build/plugins/infer1.js ./src/generic/arithmetic.ts',
|
||||||
|
'experiment:infer1-direct': 'ttsc -b && node build/plugins/infer1.js ./src/experiment/arithmeticInfer1.ts',
|
||||||
|
'experiment:infer2': 'ttsc -b && node build/plugins/infer2.js ./src/experiment/arithmeticInfer2.ts',
|
||||||
|
'experiment:infer3': 'ttsc -b && node build/plugins/infer3.js ./src/experiment/arithmeticInfer3.js',
|
||||||
|
test: 'echo "Error: no test specified" && exit 1',
|
||||||
},
|
},
|
||||||
keywords: [
|
keywords: [
|
||||||
'math',
|
'math',
|
||||||
|
@ -18,9 +23,12 @@
|
||||||
url: 'https://code.studioinfinity.org/glen/typocomath.git',
|
url: 'https://code.studioinfinity.org/glen/typocomath.git',
|
||||||
},
|
},
|
||||||
dependencies: {
|
dependencies: {
|
||||||
'@types/node': '20.5.7',
|
'reflect-metadata': '0.1.13',
|
||||||
|
'source-map': '^0.7.4',
|
||||||
|
'typescript-rtti': '0.8.3',
|
||||||
},
|
},
|
||||||
devDependencies: {
|
devDependencies: {
|
||||||
|
'@types/node': '18.11.18',
|
||||||
'ts-node': '10.9.1',
|
'ts-node': '10.9.1',
|
||||||
ttypescript: '1.5.15',
|
ttypescript: '1.5.15',
|
||||||
typescript: '4.7.4',
|
typescript: '4.7.4',
|
||||||
|
|
44
pnpm-lock.yaml
generated
44
pnpm-lock.yaml
generated
|
@ -1,16 +1,22 @@
|
||||||
lockfileVersion: 5.4
|
lockfileVersion: 5.4
|
||||||
|
|
||||||
specifiers:
|
specifiers:
|
||||||
'@types/node': 20.5.7
|
'@types/node': 18.11.18
|
||||||
|
reflect-metadata: 0.1.13
|
||||||
|
source-map: ^0.7.4
|
||||||
ts-node: 10.9.1
|
ts-node: 10.9.1
|
||||||
ttypescript: 1.5.15
|
ttypescript: 1.5.15
|
||||||
typescript: 4.7.4
|
typescript: 4.7.4
|
||||||
|
typescript-rtti: 0.8.3
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 20.5.7
|
reflect-metadata: 0.1.13
|
||||||
|
source-map: 0.7.4
|
||||||
|
typescript-rtti: 0.8.3_qh5shpxvbkbt6m3jqtkkp2svgu
|
||||||
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
ts-node: 10.9.1_l7whiu4appksmcywzzf5ucsgha
|
'@types/node': 18.11.18
|
||||||
|
ts-node: 10.9.1_nv75g3i7xuh23du6z7qul3uiqi
|
||||||
ttypescript: 1.5.15_6oasmw356qmm23djlsjgkwvrtm
|
ttypescript: 1.5.15_6oasmw356qmm23djlsjgkwvrtm
|
||||||
typescript: 4.7.4
|
typescript: 4.7.4
|
||||||
|
|
||||||
|
@ -55,8 +61,9 @@ packages:
|
||||||
resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
|
resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@types/node/20.5.7:
|
/@types/node/18.11.18:
|
||||||
resolution: {integrity: sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA==}
|
resolution: {integrity: sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/acorn-walk/8.2.0:
|
/acorn-walk/8.2.0:
|
||||||
resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==}
|
resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==}
|
||||||
|
@ -107,6 +114,10 @@ packages:
|
||||||
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
|
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/reflect-metadata/0.1.13:
|
||||||
|
resolution: {integrity: sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/resolve/1.22.4:
|
/resolve/1.22.4:
|
||||||
resolution: {integrity: sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==}
|
resolution: {integrity: sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
@ -116,12 +127,17 @@ packages:
|
||||||
supports-preserve-symlinks-flag: 1.0.0
|
supports-preserve-symlinks-flag: 1.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/source-map/0.7.4:
|
||||||
|
resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==}
|
||||||
|
engines: {node: '>= 8'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/supports-preserve-symlinks-flag/1.0.0:
|
/supports-preserve-symlinks-flag/1.0.0:
|
||||||
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
|
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/ts-node/10.9.1_l7whiu4appksmcywzzf5ucsgha:
|
/ts-node/10.9.1_nv75g3i7xuh23du6z7qul3uiqi:
|
||||||
resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==}
|
resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
@ -140,7 +156,7 @@ packages:
|
||||||
'@tsconfig/node12': 1.0.11
|
'@tsconfig/node12': 1.0.11
|
||||||
'@tsconfig/node14': 1.0.3
|
'@tsconfig/node14': 1.0.3
|
||||||
'@tsconfig/node16': 1.0.4
|
'@tsconfig/node16': 1.0.4
|
||||||
'@types/node': 20.5.7
|
'@types/node': 18.11.18
|
||||||
acorn: 8.10.0
|
acorn: 8.10.0
|
||||||
acorn-walk: 8.2.0
|
acorn-walk: 8.2.0
|
||||||
arg: 4.1.3
|
arg: 4.1.3
|
||||||
|
@ -160,15 +176,25 @@ packages:
|
||||||
typescript: '>=3.2.2'
|
typescript: '>=3.2.2'
|
||||||
dependencies:
|
dependencies:
|
||||||
resolve: 1.22.4
|
resolve: 1.22.4
|
||||||
ts-node: 10.9.1_l7whiu4appksmcywzzf5ucsgha
|
ts-node: 10.9.1_nv75g3i7xuh23du6z7qul3uiqi
|
||||||
typescript: 4.7.4
|
typescript: 4.7.4
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/typescript-rtti/0.8.3_qh5shpxvbkbt6m3jqtkkp2svgu:
|
||||||
|
resolution: {integrity: sha512-uX1A0JKs1o/ptLJqkubRCGgN7NOCYSTKRXyRIjG80exsLrPDq4jJWMfQxlHMAcv/zjoX0V6iIGU7bwjGWTzpLg==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
peerDependencies:
|
||||||
|
reflect-metadata: ^0.1.13
|
||||||
|
typescript: ^4.5 || ^4.6 || ^4.7
|
||||||
|
dependencies:
|
||||||
|
reflect-metadata: 0.1.13
|
||||||
|
typescript: 4.7.4
|
||||||
|
dev: false
|
||||||
|
|
||||||
/typescript/4.7.4:
|
/typescript/4.7.4:
|
||||||
resolution: {integrity: sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==}
|
resolution: {integrity: sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==}
|
||||||
engines: {node: '>=4.2.0'}
|
engines: {node: '>=4.2.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dev: true
|
|
||||||
|
|
||||||
/v8-compile-cache-lib/3.0.1:
|
/v8-compile-cache-lib/3.0.1:
|
||||||
resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
|
resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
import "reflect-metadata"
|
||||||
|
import { reflect, type CallSite } from 'typescript-rtti'
|
||||||
|
|
||||||
/* A Dispatcher is a collection of operations that do run-time
|
/* A Dispatcher is a collection of operations that do run-time
|
||||||
* dispatch on the types of their arguments. Thus, every individual
|
* dispatch on the types of their arguments. Thus, every individual
|
||||||
* method is like a typed-function (from the library by that name),
|
* method is like a typed-function (from the library by that name),
|
||||||
|
@ -48,15 +51,15 @@ export class Dispatcher {
|
||||||
console.log('Pretending to install', name, signature, '=>', returns)
|
console.log('Pretending to install', name, signature, '=>', returns)
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
// console.log(name, 'signature', reflect(signature))
|
console.log(name, 'signature', reflect(signature))
|
||||||
// console.log(name, 'dependencies', reflect(dependencies))
|
console.log(name, 'dependencies', reflect(dependencies))
|
||||||
//TODO: implement me
|
//TODO: implement me
|
||||||
}
|
}
|
||||||
installType(name: TypeName, typespec: TypeSpecification) {
|
installType(name: TypeName, typespec: TypeSpecification) {
|
||||||
console.log('Pretending to install type', name, typespec)
|
console.log('Pretending to install type', name, typespec)
|
||||||
//TODO: implement me
|
//TODO: implement me
|
||||||
}
|
}
|
||||||
constructor(collection: SpecificationsGroup) {
|
constructor(collection: SpecificationsGroup) {
|
||||||
for (const key in collection) {
|
for (const key in collection) {
|
||||||
console.log('Working on', key)
|
console.log('Working on', key)
|
||||||
for (const identifier in collection[key]) {
|
for (const identifier in collection[key]) {
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
export function typed<T>(name: string, types: string, arg: T) : T {
|
|
||||||
// TODO: implement typed-function for real
|
|
||||||
if (types === '__infer__') {
|
|
||||||
console.error('__infer__ should be replaced with runtime type information by the TypeScript plugin')
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`TYPED-FUNCTION: Creating function "${name}" with types ${types}`)
|
|
||||||
return arg
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
import { typed } from '../core/typed.js'
|
|
||||||
|
|
||||||
export const square = typed('square', '__infer__', <T>(dep: {
|
|
||||||
multiply: (a: T, b: T) => T,
|
|
||||||
unaryMinus: (x: T) => T, // just for the experiment
|
|
||||||
}): (a: T) => T =>
|
|
||||||
z => dep.multiply(z, z)
|
|
||||||
)
|
|
8
src/experiment/arithmeticInfer1.ts
Normal file
8
src/experiment/arithmeticInfer1.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import {infer} from '../generic/infer'
|
||||||
|
|
||||||
|
export const square = infer(<T>(dep: {
|
||||||
|
multiply: (a: number, b: number) => number,
|
||||||
|
unaryMinus: (x: number) => number, // just for the experiment
|
||||||
|
}): (a: number) => number =>
|
||||||
|
z => dep.multiply(z, z)
|
||||||
|
)
|
15
src/experiment/arithmeticInfer2.ts
Normal file
15
src/experiment/arithmeticInfer2.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import { infer } from '../generic/infer'
|
||||||
|
import { Dependencies, Signature } from '../interfaces/type'
|
||||||
|
|
||||||
|
export type multiplyDep<T> = Dependencies<'multiply', T>
|
||||||
|
|
||||||
|
export const square1 =
|
||||||
|
<T>(dep: Dependencies<'multiply', T>): Signature<'square', T> =>
|
||||||
|
z => dep.multiply(z, z)
|
||||||
|
|
||||||
|
export const square2 = infer(<T>(dep: {
|
||||||
|
multiply: (a: number, b: number) => number,
|
||||||
|
unaryMinus: (x: number) => number, // just for the experiment
|
||||||
|
}): (a: number) => number =>
|
||||||
|
z => dep.multiply(z, z)
|
||||||
|
)
|
16
src/experiment/arithmeticInfer3.js
Normal file
16
src/experiment/arithmeticInfer3.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
// @ts-check
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function square
|
||||||
|
*
|
||||||
|
* Description of function square bla bla bla
|
||||||
|
*
|
||||||
|
* @param {{
|
||||||
|
* multiply: (a: number, b: number) => number,
|
||||||
|
* unaryMinus: (x: number) => number
|
||||||
|
* }} dep
|
||||||
|
* @return {(a: number) => number}
|
||||||
|
*/
|
||||||
|
export function square3 (dep) {
|
||||||
|
return z => dep.multiply(z, z)
|
||||||
|
}
|
4
src/generic/infer.ts
Normal file
4
src/generic/infer.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
export function infer<T>(arg: T) : T {
|
||||||
|
console.error('infer should be replaced with runtime type information by a magic TypeScript plugin')
|
||||||
|
return arg
|
||||||
|
}
|
113
src/index.ts
113
src/index.ts
|
@ -1,28 +1,99 @@
|
||||||
import { Complex } from './Complex/type.js'
|
import 'reflect-metadata'
|
||||||
import { absquare as absquare_complex } from './Complex/arithmetic.js'
|
|
||||||
import { square } from './experiment/arithmeticInfer.js'
|
|
||||||
|
|
||||||
const add = (a: number, b: number) => a + b
|
import {Dispatcher} from './core/Dispatcher.js'
|
||||||
const multiply = (a: number, b: number) => a * b
|
import * as specifications from './all.js'
|
||||||
const unaryMinus = (a: number) => -a
|
import {Complex} from './Complex/type.js'
|
||||||
const absquare = (z: Complex<number>) => z.re * z.re + z.im * z.im
|
import {absquare as absquare_complex} from './Complex/arithmetic.js'
|
||||||
|
|
||||||
|
import { CallSite, ReflectedObjectRef, reflect } from 'typescript-rtti'
|
||||||
|
import { square } from './generic/arithmetic.js'
|
||||||
|
|
||||||
|
// verify that typescript-rtti works (just as experiment)
|
||||||
|
const add = (a: number, b: number): number => a + b
|
||||||
|
console.log('reflect add')
|
||||||
|
console.log('parameterNames', reflect(add).parameterNames)
|
||||||
|
console.log('parameterTypes', reflect(add).parameterTypes.map(type => type.toString()))
|
||||||
|
console.log('returnType', reflect(add).returnType.toString())
|
||||||
|
console.log()
|
||||||
|
// output:
|
||||||
|
// reflect function add
|
||||||
|
// parameterNames [ 'a', 'b' ]
|
||||||
|
// parameterTypes [ 'class Number', 'class Number' ]
|
||||||
|
// returnType class Number
|
||||||
|
|
||||||
|
// try out a very simple case (just as experiment)
|
||||||
|
function createSquare(deps: {
|
||||||
|
multiply: (a: number, b: number) => number,
|
||||||
|
subtract: (a: number, b: number) => number
|
||||||
|
}) {
|
||||||
|
return (a: number) => deps.multiply(a, a)
|
||||||
|
}
|
||||||
|
console.log('reflect createSquare')
|
||||||
|
console.log('parameter names', reflect(createSquare).parameters.map(parameter => parameter.name))
|
||||||
|
// console.log('parameter[0]', (reflect(createSquare).parameters[0] as ReflectedFunctionParameter))
|
||||||
|
// @ts-ignore
|
||||||
|
console.log('parameterTypes[0]', (reflect(createSquare).parameterTypes[0] as ReflectedObjectRef)._ref.m)
|
||||||
|
console.log('parameterTypes[0].ref.m[0]',
|
||||||
|
// @ts-ignore
|
||||||
|
(reflect(createSquare).parameterTypes[0] as ReflectedObjectRef)._ref.m[0].n,
|
||||||
|
// @ts-ignore
|
||||||
|
(reflect(createSquare).parameterTypes[0] as ReflectedObjectRef)._ref.m[0]
|
||||||
|
)
|
||||||
|
console.log('parameterTypes[0].ref.m[0].t.m',
|
||||||
|
// @ts-ignore
|
||||||
|
(reflect(createSquare).parameterTypes[0] as ReflectedObjectRef)._ref.m[0].t.m
|
||||||
|
)
|
||||||
|
// @ts-ignore
|
||||||
|
// console.log('parameters[0]', reflect(createSquare).parameters[0])
|
||||||
|
// FIXME: where to find the information of the types of the dependencies multiply and subtract?
|
||||||
|
|
||||||
|
// Test whether we loose the type information when casting to a generic interface
|
||||||
|
// Conclusion: we keep the information, that is good.
|
||||||
|
console.log()
|
||||||
|
console.log('reflect createFunction')
|
||||||
|
type MathjsDependencies = Record<string, Function>
|
||||||
|
type MathjsCreateFunction = (deps: MathjsDependencies) => Function
|
||||||
|
const createFunction: MathjsCreateFunction = createSquare as MathjsCreateFunction
|
||||||
|
console.log('parameter names', reflect(createFunction).parameters.map(parameter => parameter.name))
|
||||||
|
// @ts-ignore
|
||||||
|
console.log('parameterTypes[0]', (reflect(createFunction).parameterTypes[0] as ReflectedObjectRef)._ref.m)
|
||||||
|
|
||||||
|
// TODO: more specific definition of Specifications
|
||||||
|
type Specifications = Record<string, Record<string, unknown>>
|
||||||
|
|
||||||
|
console.log()
|
||||||
|
console.log('CallSite')
|
||||||
|
function reflectSpecifications<T>(specifications: Specifications, callSite? : CallSite) {
|
||||||
|
console.log('specifications', reflect(callSite).parameters[0])
|
||||||
|
// @ts-ignore
|
||||||
|
console.log('specifications', reflect(callSite).parameters[0]._ref) // shows 'numbers', 'Complex, 'complex', 'generic'
|
||||||
|
// @ts-ignore
|
||||||
|
console.log('specifications', reflect(callSite).parameters[0]._ref.m
|
||||||
|
.find(item => item.n === 'generic').t.m) // shows 'square', 'unequal'
|
||||||
|
// @ts-ignore
|
||||||
|
console.log('specifications', reflect(callSite).parameters[0]._ref.m
|
||||||
|
.find(item => item.n === 'generic').t.m
|
||||||
|
.find(item => item.n === 'square').t.p) // [ { n: 'dep', t: [Function: t], b: undefined, v: null } ]
|
||||||
|
// @ts-ignore
|
||||||
|
// FIXME: now, we should be able to get the signature of the multiply dependency of the function square, but how?
|
||||||
|
}
|
||||||
|
reflectSpecifications<Specifications>(specifications);
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: import all specifications (turned off for debugging purposes)
|
||||||
|
// export default new Dispatcher(Specifications)
|
||||||
|
|
||||||
|
|
||||||
|
const mockRealAdd = (a: number, b: number) => a+b
|
||||||
|
const mockComplexAbsquare = (z: Complex<number>) => z.re*z.re + z.im*z.im
|
||||||
|
|
||||||
const quatAbsquare = absquare_complex({
|
const quatAbsquare = absquare_complex({
|
||||||
add,
|
add: mockRealAdd,
|
||||||
absquare
|
absquare: mockComplexAbsquare
|
||||||
})
|
})
|
||||||
|
|
||||||
const result = quatAbsquare({re: {re: 0, im: 1}, im: {re:2, im: 3}})
|
const myabs = quatAbsquare({re: {re: 0, im: 1}, im: {re:2, im: 3}})
|
||||||
const typeTest: typeof result = 7 // check myabs is just a number
|
const typeTest: typeof myabs = 7 // check myabs is just a number
|
||||||
|
|
||||||
console.log()
|
console.log()
|
||||||
console.log('Result is', result)
|
console.log('Result is', myabs)
|
||||||
|
|
||||||
|
|
||||||
const mySquare = square({
|
|
||||||
multiply,
|
|
||||||
unaryMinus
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log()
|
|
||||||
console.log('mySquare(4)=', mySquare(4))
|
|
||||||
|
|
73
src/plugins/infer1.ts
Normal file
73
src/plugins/infer1.ts
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
import { readFileSync } from "fs";
|
||||||
|
import * as ts from "typescript";
|
||||||
|
import { inspect } from 'util'
|
||||||
|
|
||||||
|
export function infer(sourceFile: ts.SourceFile) {
|
||||||
|
recurse(sourceFile);
|
||||||
|
|
||||||
|
function getType(kind: number) {
|
||||||
|
switch(kind) {
|
||||||
|
case ts.SyntaxKind.NumberKeyword: return 'number'
|
||||||
|
case ts.SyntaxKind.StringKeyword: return 'string'
|
||||||
|
case ts.SyntaxKind.BooleanKeyword: return 'boolean'
|
||||||
|
default: return String(ts.SyntaxKind[kind]) // TODO: work out all types
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function recurse(node: ts.Node) {
|
||||||
|
if (node.kind === ts.SyntaxKind.Identifier) {
|
||||||
|
console.log('Identifier', node['escapedText'], ts.SyntaxKind[node.kind])
|
||||||
|
}
|
||||||
|
|
||||||
|
// recognize a structure like:
|
||||||
|
//
|
||||||
|
// export const square = infer(<T>(dep: {
|
||||||
|
// multiply: (a: number, b: number) => number
|
||||||
|
// }): (a: number) => number =>
|
||||||
|
// z => dep.multiply(z, z)
|
||||||
|
// )
|
||||||
|
if (node?.['name']?.kind === ts.SyntaxKind.Identifier && node?.['name']['escapedText'] === 'dep') {
|
||||||
|
// console.log('dep', getType(node['type'].kind), node)
|
||||||
|
|
||||||
|
node['type']?.members?.forEach(member => {
|
||||||
|
console.log('member', {
|
||||||
|
name: member.name.escapedText,
|
||||||
|
parameters: member.type.parameters.map(parameter => {
|
||||||
|
return parameter.name.escapedText + ': ' + getType(parameter.type.kind)
|
||||||
|
}),
|
||||||
|
returns: getType(member.type.type.kind)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// recognize a structure like:
|
||||||
|
//
|
||||||
|
// export const square =
|
||||||
|
// <T>(dep: Dependencies<'multiply' | 'unaryMinus', T>): Signature<'square', T> =>
|
||||||
|
// z => dep.multiply(z, z)
|
||||||
|
if (node?.['name']?.kind === ts.SyntaxKind.Identifier && node?.['name']['escapedText'] === 'dep') {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
ts.forEachChild(node, recurse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileNames = process.argv.slice(2);
|
||||||
|
console.log('infer files', fileNames)
|
||||||
|
fileNames.forEach(fileName => {
|
||||||
|
// Parse a file
|
||||||
|
const sourceFile = ts.createSourceFile(
|
||||||
|
fileName,
|
||||||
|
readFileSync(fileName).toString(),
|
||||||
|
ts.ScriptTarget.ES2022,
|
||||||
|
/*setParentNodes */ true
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('AST', fileName, inspect(sourceFile, { depth: null, colors: true }))
|
||||||
|
|
||||||
|
console.log(sourceFile.text)
|
||||||
|
console.log()
|
||||||
|
|
||||||
|
infer(sourceFile);
|
||||||
|
});
|
74
src/plugins/infer2.ts
Normal file
74
src/plugins/infer2.ts
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
import * as ts from "typescript"
|
||||||
|
|
||||||
|
// based on: https://github.com/Microsoft/TypeScript/wiki/Using-the-Compiler-API#using-the-type-checker
|
||||||
|
|
||||||
|
infer2(process.argv.slice(2), {
|
||||||
|
target: ts.ScriptTarget.ES5,
|
||||||
|
module: ts.ModuleKind.CommonJS
|
||||||
|
})
|
||||||
|
|
||||||
|
function infer2(
|
||||||
|
fileNames: string[],
|
||||||
|
options: ts.CompilerOptions
|
||||||
|
): void {
|
||||||
|
const program = ts.createProgram(fileNames, options)
|
||||||
|
const typeChecker = program.getTypeChecker()
|
||||||
|
|
||||||
|
for (const sourceFile of program.getSourceFiles()) {
|
||||||
|
if (!sourceFile.isDeclarationFile) {
|
||||||
|
ts.forEachChild(sourceFile, visit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
function visit(node: ts.Node) {
|
||||||
|
// // Only consider exported nodes
|
||||||
|
// if (!isNodeExported(node)) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// console.log('Node', node.kind, node?.['name']?.escapedText)
|
||||||
|
|
||||||
|
if (ts.isModuleDeclaration(node)) {
|
||||||
|
// This is a namespace, visit its children
|
||||||
|
console.log('check')
|
||||||
|
ts.forEachChild(node, visit);
|
||||||
|
} else if (ts.isTypeAliasDeclaration(node)) {
|
||||||
|
console.log('isTypeAliasDeclaration', node.name.escapedText)
|
||||||
|
|
||||||
|
let symbol = typeChecker.getSymbolAtLocation(node.name);
|
||||||
|
if (symbol) {
|
||||||
|
const symbolType = typeChecker.getTypeOfSymbolAtLocation(symbol, symbol.valueDeclaration)
|
||||||
|
const symbolSignature = typeChecker.getSignaturesOfType(symbolType, ts.SignatureKind.Call)
|
||||||
|
|
||||||
|
// checker.getResolvedSignature(symbol)
|
||||||
|
console.log('symbol', symbol.getName(), symbolSignature)
|
||||||
|
|
||||||
|
// getTypeOfSymbolAtLocation
|
||||||
|
// getResolvedSignature
|
||||||
|
}
|
||||||
|
} else if (ts.isCallExpression(node)) {
|
||||||
|
console.log('isCallExpression', node.expression)
|
||||||
|
} else if (ts.isFunctionDeclaration(node)) {
|
||||||
|
console.log('isFunctionDeclaration', node.name.escapedText, { typeParameter0: node.typeParameters[0] })
|
||||||
|
|
||||||
|
if (node.name.escapedText === 'infer') {
|
||||||
|
const param0 = node.typeParameters[0]
|
||||||
|
if (ts.isPropertyDeclaration(param0)) {
|
||||||
|
const symbol = typeChecker.getSymbolAtLocation(param0)
|
||||||
|
|
||||||
|
// TODO: get resolving
|
||||||
|
|
||||||
|
// console.log('getResolvedSignature', typeChecker.getResolvedSignature(node) )
|
||||||
|
|
||||||
|
// const symbolType = typeChecker.getTypeOfSymbolAtLocation(symbol, symbol.valueDeclaration)
|
||||||
|
// const symbolSignature = typeChecker.getSignaturesOfType(symbolType, ts.SignatureKind.Call)
|
||||||
|
// console.log('symbol', symbol.getName(), symbolSignature)
|
||||||
|
|
||||||
|
// console.log('getSignaturesOfType', typeChecker.getSignaturesOfType(param0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
65
src/plugins/infer3.ts
Normal file
65
src/plugins/infer3.ts
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
import { readFileSync } from "fs";
|
||||||
|
import * as ts from "typescript";
|
||||||
|
import { inspect } from 'util'
|
||||||
|
|
||||||
|
export function infer3(sourceFile: ts.SourceFile) {
|
||||||
|
recurse(sourceFile);
|
||||||
|
|
||||||
|
function getType(kind: number) {
|
||||||
|
switch(kind) {
|
||||||
|
case ts.SyntaxKind.NumberKeyword: return 'number'
|
||||||
|
case ts.SyntaxKind.StringKeyword: return 'string'
|
||||||
|
case ts.SyntaxKind.BooleanKeyword: return 'boolean'
|
||||||
|
case ts.SyntaxKind.JSDoc: return 'jsdoc'
|
||||||
|
default: return String(ts.SyntaxKind[kind]) // TODO: work out all types
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function recurse(node: ts.Node) {
|
||||||
|
if (node.kind === ts.SyntaxKind.Identifier) {
|
||||||
|
console.log('Identifier', node['escapedText'], ts.SyntaxKind[node.kind])
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node['jsDoc']) {
|
||||||
|
console.log('Found a JSDoc comment:')
|
||||||
|
// console.log(inspect(node['jsDoc'], { depth: null, colors: true }))
|
||||||
|
|
||||||
|
const fullComment = sourceFile.text.slice(node.pos, node.end)
|
||||||
|
console.log(fullComment)
|
||||||
|
// TODO: next steps:
|
||||||
|
// - either get the types from the TypeScript AST,
|
||||||
|
// or extract them ourselves with regex or anything from the comment text
|
||||||
|
// - After that, we have to transform the source file and insert the comments
|
||||||
|
// as string or object that is runtime accessible in JavaScript
|
||||||
|
}
|
||||||
|
|
||||||
|
ts.forEachChild(node, recurse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileNames = process.argv.slice(2);
|
||||||
|
console.log('infer files', fileNames)
|
||||||
|
fileNames.forEach(fileName => {
|
||||||
|
// Parse a file
|
||||||
|
const sourceFile = ts.createSourceFile(
|
||||||
|
fileName,
|
||||||
|
readFileSync(fileName).toString(),
|
||||||
|
ts.ScriptTarget.ES2022,
|
||||||
|
/*setParentNodes */ true
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('FILE')
|
||||||
|
console.log(fileName)
|
||||||
|
console.log()
|
||||||
|
|
||||||
|
console.log('SOURCE')
|
||||||
|
console.log(sourceFile.text)
|
||||||
|
console.log()
|
||||||
|
|
||||||
|
console.log('AST')
|
||||||
|
console.log(inspect(sourceFile, { depth: null, colors: true }))
|
||||||
|
console.log()
|
||||||
|
|
||||||
|
console.log('INFER')
|
||||||
|
infer3(sourceFile);
|
||||||
|
});
|
|
@ -1,56 +0,0 @@
|
||||||
import ts from 'typescript'
|
|
||||||
|
|
||||||
const transformer: ts.TransformerFactory<ts.SourceFile> = context => {
|
|
||||||
// TODO: get a reference to the program instance that the plugin is running in instead of creating a new program?
|
|
||||||
const program = ts.createProgram([], {})
|
|
||||||
const checker = program.getTypeChecker()
|
|
||||||
|
|
||||||
return sourceFile => {
|
|
||||||
const visitor = (node: ts.Node): ts.Node => {
|
|
||||||
// @ts-ignore
|
|
||||||
if (ts.isStringLiteral(node) && node.text === '__infer__') {
|
|
||||||
console.log('PLUGIN: FOUND AN OCCURRENCE OF __infer__')
|
|
||||||
|
|
||||||
// we're looking for a function call like typed('name', '__infer__', deps => ...)
|
|
||||||
const parentNode = node.parent
|
|
||||||
if (ts.isCallExpression(parentNode) && ts.isIdentifier(parentNode.expression) && parentNode.expression.escapedText === 'typed') {
|
|
||||||
// console.log('PARENT')
|
|
||||||
// console.log(parentNode)
|
|
||||||
|
|
||||||
// TODO: validate that argNode is an ArrowFunction
|
|
||||||
// @ts-ignore
|
|
||||||
const argNode = parentNode.arguments[2]
|
|
||||||
// @ts-ignore
|
|
||||||
const returnType = argNode.type.getText(sourceFile)
|
|
||||||
console.log('PLUGIN: RETURN TYPE')
|
|
||||||
console.log(returnType)
|
|
||||||
// (a: number) => number
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
const paramNode = argNode.parameters[0]
|
|
||||||
const paramTypeSrc = paramNode.type.getText(sourceFile)
|
|
||||||
console.log('PLUGIN: PARAM TYPE SRC', paramTypeSrc)
|
|
||||||
// {
|
|
||||||
// multiply: (a: number, b: number) => number,
|
|
||||||
// unaryMinus: (x: number) => number, // just for the experiment
|
|
||||||
// }
|
|
||||||
|
|
||||||
const type = checker.getTypeAtLocation(paramNode)
|
|
||||||
const paramType = checker.typeToString(type, paramNode, ts.TypeFormatFlags.InTypeAlias)
|
|
||||||
console.log('PLUGIN: PARAM TYPE STRING', paramType)
|
|
||||||
// { multiply: (a: number, b: number) => number; unaryMinus: (x: number) => number; }
|
|
||||||
|
|
||||||
const depsAndReturnType = `{ deps: ${paramType}; return: ${returnType} }`
|
|
||||||
|
|
||||||
return ts.factory.createStringLiteral(depsAndReturnType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ts.visitEachChild(node, visitor, context)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ts.visitNode(sourceFile, visitor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default transformer
|
|
|
@ -8,9 +8,10 @@
|
||||||
"noImplicitAny": false,
|
"noImplicitAny": false,
|
||||||
"moduleResolution": "Node",
|
"moduleResolution": "Node",
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"plugins": [{
|
"plugins": [
|
||||||
"transform": "./src/plugins/typeInferPlugin.ts",
|
{
|
||||||
"type": "raw"
|
"transform": "typescript-rtti/dist/transformer"
|
||||||
}]
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue