diff --git a/README.md b/README.md index d626b30..2965280 100644 --- a/README.md +++ b/README.md @@ -8,3 +8,7 @@ Convenience scripts: * `pnpm build` -- compile the package * `pnpm exec` -- run the compiled code produced by `pnpm build` * `pnpm go` -- both of the above in sequence. + +Important installation note: + +after `pnpm install`, you must execute `npx ts-patch install` to activate the ts-macros compiler plugin. diff --git a/package.json5 b/package.json5 index f233b62..8f26409 100644 --- a/package.json5 +++ b/package.json5 @@ -24,6 +24,8 @@ devDependencies: { '@types/node': '^20.5.0', 'source-map': '^0.7.4', + 'ts-macros': '^2.4.0', + 'ts-patch': '^3.0.2', typescript: '^5.1.6', }, } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 682e112..e828fc9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,12 @@ devDependencies: source-map: specifier: ^0.7.4 version: 0.7.4 + ts-macros: + specifier: ^2.4.0 + version: 2.4.0(typescript@5.1.6) + ts-patch: + specifier: ^3.0.2 + version: 3.0.2 typescript: specifier: ^5.1.6 version: 5.1.6 @@ -21,13 +27,170 @@ packages: resolution: {integrity: sha512-Mgq7eCtoTjT89FqNoTzzXg2XvCi5VMhRV6+I2aYanc6kQCBImeNaAYRs/DyoVqk1YEUJK5gN9VO7HRIdz4Wo3Q==} dev: true + /ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + dev: true + + /ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + dependencies: + color-convert: 2.0.1 + dev: true + + /chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + dev: true + + /color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + dependencies: + color-name: 1.1.4 + dev: true + + /color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + dev: true + + /function-bind@1.1.1: + resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} + dev: true + + /global-prefix@3.0.0: + resolution: {integrity: sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==} + engines: {node: '>=6'} + dependencies: + ini: 1.3.8 + kind-of: 6.0.3 + which: 1.3.1 + dev: true + + /has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + 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 + + /ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + dev: true + + /is-core-module@2.13.0: + resolution: {integrity: sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==} + dependencies: + has: 1.0.3 + dev: true + + /isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + dev: true + + /kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} + dev: true + + /lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + dependencies: + yallist: 4.0.0 + dev: true + + /minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + dev: true + + /path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + dev: true + + /resolve@1.22.4: + resolution: {integrity: sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==} + hasBin: true + dependencies: + is-core-module: 2.13.0 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + dev: true + + /semver@7.5.4: + resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} + engines: {node: '>=10'} + hasBin: true + dependencies: + lru-cache: 6.0.0 + dev: true + /source-map@0.7.4: resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} engines: {node: '>= 8'} dev: true + /strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + dependencies: + ansi-regex: 5.0.1 + dev: true + + /supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + dependencies: + has-flag: 4.0.0 + dev: true + + /supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + dev: true + + /ts-macros@2.4.0(typescript@5.1.6): + resolution: {integrity: sha512-HKt4/r1KvtnBKu+RLUPFB4BJk2L4VjN5SlHHJJQSGBq5ycGhYSpgGi6VvbJjYFQscCUXOvSQRyISYVC+FF7Svg==} + peerDependencies: + typescript: 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x + dependencies: + typescript: 5.1.6 + dev: true + + /ts-patch@3.0.2: + resolution: {integrity: sha512-iTg8euqiNsNM1VDfOsVIsP0bM4kAVXU38n7TGQSkky7YQX/syh6sDPIRkvSS0HjT8ZOr0pq1h+5Le6jdB3hiJQ==} + hasBin: true + dependencies: + chalk: 4.1.2 + global-prefix: 3.0.0 + minimist: 1.2.8 + resolve: 1.22.4 + semver: 7.5.4 + strip-ansi: 6.0.1 + dev: true + /typescript@5.1.6: resolution: {integrity: sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==} engines: {node: '>=14.17'} hasBin: true dev: true + + /which@1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true + dependencies: + isexe: 2.0.0 + dev: true + + /yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + dev: true diff --git a/src/generic/arithmetic.ts b/src/generic/arithmetic.ts index 6f4e949..a61946f 100644 --- a/src/generic/arithmetic.ts +++ b/src/generic/arithmetic.ts @@ -1,7 +1,99 @@ import type {Dependencies, Signature} from '../interfaces/type.js' +import {$$typeToString, $$ts, $$define, $$raw, $$ident, $$escape} from 'ts-macros' +import * as ts from 'typescript' export const square = (dep: Dependencies<'multiply', T>): Signature<'square', T> => z => dep.multiply(z, z) // z => dep.fooBar(z, z) // fails as desired - // z => dep.multiply(z, 'foo') // fails as desired + // z => dep.multiply(z, 'foo') // still fails as desired + +// make sure ts-macros is running +function $contains(value: T, ...possible: Array) { + // repetition which goes over all the elements in the "possible" array + return +["||", [possible], (item: T) => value === item]; +} + +const searchItem = "needle"; +console.log("In generic arithmetic") +console.log($contains!(searchItem, "erwin", "tj")); +// Transpiles to: console.log(false); + +// OK, record the type of square: +square.reflectedType = //$$typeToString!(); + $$typeToString!< + (...args: DeepExpand>>) + => DeepExpand>> + >() + +type ShallowExpand = T extends unknown ? { [K in keyof T]: T[K] } : never; +// Short alias of ShallowExpand for convenience below: +type Deps = T extends unknown ? { [K in keyof T]: T[K] } : never; +type ExpandedParameters any> = {[Index in keyof Parameters]: ShallowExpand[Index]>} +type ExpandTwice = {[Index in keyof T] : ShallowExpand} +type AsArray = T extends any[] ? T : []; +type DeepExpand = + T extends (...args: any) => any + ? (...pars: AsArray>>) => DeepExpand> : + T extends unknown ? { [K in keyof T]: DeepExpand } : never; + +type Out = ShallowExpand>; + +console.log("Deps type is", $$typeToString!<() => Out>()) + +type OutB = ExpandTwice>; + +console.log("Or perhaps", $$typeToString!()) + +console.log("Or maybe even", $$typeToString!< + (...args: DeepExpand>>) => DeepExpand>> + >()) + +// Now try to wrap it up in a macro + +// From the creator of ts-macros; temporary until next release +function $export(name: string, value: unknown) { + $$raw!((ctx, name: ts.Expression, value: ts.Expression) => { + if (!ctx.ts.isStringLiteral(name)) throw ctx.error(name, "Expected a string literal."); + return [ctx.factory.createVariableStatement([ctx.factory.createToken(ctx.ts.SyntaxKind.ExportKeyword)], ctx.factory.createVariableDeclarationList([ + ctx.factory.createVariableDeclaration(name.text, undefined, undefined, value) + ], ctx.ts.NodeFlags.Const))]; + }); +} + +// Obviously we'd prefer the name before the expression, but that won't work +// until the next release +function $exportImpl(expr: Impl, name: string) { + $export!(name, expr); + $$ident!(name).reflectedType = $$typeToString!< + // (...args: DeepExpand>>) => DeepExpand>> // see comment in reflect below. + Impl + >(); +} + +// works but then not visible at import with current ts-macros. +// Author says he will be enhancing this "soon." +$exportImpl!((dep: Dependencies<'multiply', T>): Signature<'square', T> => + z => dep.multiply(z, z), + 'squire') + +function $reflect(expr: Impl) { + return $$escape!(() => { + const temp: Impl & {reflectedType?: string} = expr; + temp.reflectedType = $$typeToString!< + // (...args: DeepExpand>>) => + // DeepExpand>> // error; can't instantiate + // Impl; even if we constrain it to be the type of a generic function, + // that's still not a generic _type_, ugh. + Impl>(); + return temp; + }) as Impl & {reflectedType: string} +} + +export const squre = $reflect!( + (dep: Dependencies<'multiply', T>): Signature<'square', T> => + z => dep.multiply(z, z)) + +export const sqre = $reflect!( + (dep: Deps>): Signature<'square', T> => + z => dep.multiply(z, z)) diff --git a/src/index.ts b/src/index.ts index 297b271..79791d0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -18,3 +18,20 @@ const myabs = quatAbsquare({re: {re: 0, im: 1}, im: {re:2, im: 3}}) const typeTest: typeof myabs = 7 // check myabs is just a number console.log('Result is', myabs) + +// Check type of the generic square implementation +console.log('Type of square is', Specifications.generic.square.reflectedType) + +// Now check the ones that came via macros: + +// Auto-generated export is invisible to TypeScript at the moment, author +// says he will fix: +console.log('Type of squire (auto-exported) is', + // @ts-ignore + Specifications.generic.squire.reflectedType) + +// Via a macro wrapper around the definition, two ways: +console.log('Type of squre (unexpanded) is', + Specifications.generic.squre.reflectedType) +console.log('Type of sqre (expanded) is', + Specifications.generic.sqre.reflectedType) diff --git a/tsconfig.json b/tsconfig.json index 13b7b6c..a5c4a3d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,6 +3,9 @@ "target": "ES2022", "rootDir": "./src", "outDir": "./build", - "moduleResolution": "nodenext" + "moduleResolution": "nodenext", + "plugins": [ + {"transform": "ts-macros" } + ] } }