From 569079e9081a9b29b40d3921955df5b68a0966ea Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Wed, 18 Oct 2023 23:10:58 -0700 Subject: [PATCH] refactor: change Dispatcher class to a closure Also starts work on typing that closure. Gets the property names right, but currently has the "unfilled" method types, rather than the returned "filled-in" function types. Not sure how to fix this. --- src/core/Dispatcher.ts | 136 ++++++++++++++++++++++++++++------------- src/index.ts | 8 ++- src/interfaces/type.ts | 2 +- tsconfig.json | 1 + 4 files changed, 103 insertions(+), 44 deletions(-) diff --git a/src/core/Dispatcher.ts b/src/core/Dispatcher.ts index 4ede7cd..1485160 100644 --- a/src/core/Dispatcher.ts +++ b/src/core/Dispatcher.ts @@ -6,6 +6,7 @@ */ import {parseReflectedType, ImplementationDef} from './parseReflectedType.js' +import type {ValueIntersectionByKeyUnion} from '../interfaces/type.js' // First helper types and functions for the Dispatcher @@ -35,50 +36,103 @@ type TypeSpecification = { infer?: (d: DependenciesType) => (z: unknown) => TypeName } -type SpecObject = Record +type Callable = (...args: any) => any +type SpecObject = Record type SpecificationsGroup = Record -export class Dispatcher { - installSpecification( - name: string, - defn: ImplementationDef, - behavior: Function // possible todo: constrain this type based - // on the signature, return type, and dependencies. Not sure if - // that's really possible, though. - ) { - console.log('Pretending to install', name, 'with signatures') - for (const signature of defn.fn.signatures) { - console.log(' ', signature.args, '=>', signature.returns) - } - if (defn.fn.aliasOf) { - console.log(' As an alias of', defn.fn.aliasOf) - } - //TODO: implement me - } - installType(name: TypeName, typespec: TypeSpecification) { - console.log('Pretending to install type', name, typespec) - //TODO: implement me - } - constructor(collection: SpecificationsGroup) { - const implementations = [] - for (const key in collection) { - console.log('Working on', key) - for (const identifier in collection[key]) { - const item = collection[key][identifier] - if (typeof item === 'function') { - implementations.push([key, identifier, item]) - } else { - console.log('Handling type', key, ':', identifier) - this.installType( - item.name, collection[key][identifier] as TypeSpecification) - } +// Find all of the keys of an object that are functions but not aliases +// to another operation +type BaseOperations = + {[K in keyof Obj]: + Obj[K] extends Callable + ? ReturnType['aliasOf'] extends (string | undefined) + ? never + : K extends string ? K : never + : never + }[keyof Obj] +// Gather all of the operations specified in a SpecificationsGroup +type DispatcherOperations = + {[K in keyof G]: BaseOperations}[keyof G] +// Get the type of a given operation in a SpecObject: +type BaseOperationSignature< + Obj extends SpecObject, + Name extends BaseOperations +> = ValueIntersectionByKeyUnion< + {[K in keyof Obj]: + K extends Name + ? Obj[K] + : Obj[K] extends Callable + ? ReturnType['aliasOf'] extends (Name | undefined) + ? Obj[K] : unknown + : unknown + }, + keyof Obj> +// Get the type of a given operation in a SpecificationsGroup +type OperationSignature< + G extends SpecificationsGroup, + Name extends DispatcherOperations +> = ValueIntersectionByKeyUnion< + {[K in keyof G]: + Name extends BaseOperations + ? BaseOperationSignature + : unknown}, + keyof G> +// Put it all together into the typing of the dispatcher +type DispatcherInterface = + {[Op in DispatcherOperations]: OperationSignature} + +export function createDispatcher( + collection: G): DispatcherInterface +{ + const implementations = [] + const types = {} // who knows what the type of this will be + const behaviors = {} // ditto + for (const key in collection) { + console.log('Working on', key) + for (const identifier in collection[key]) { + const item = collection[key][identifier] + if (typeof item === 'function') { + implementations.push([key, identifier, item]) + } else { + console.log('Handling type', key, ':', identifier) + installType( // In this design, installType would modify + // types by side effect; maybe Jos prefers some other + // factoring of this? + types, item.name, + collection[key][identifier] as TypeSpecification) } } - for (const trio of implementations) { - const [k, id, imp] = trio - console.log('Handling implementation', id, 'from', k) - this.installSpecification( - id, parseReflectedType(id, imp.reflectedType), imp) - } } + for (const trio of implementations) { + const [k, id, imp] = trio + console.log('Handling implementation', id, 'from', k) + installSpecification( + behaviors, id, parseReflectedType(id, imp.reflectedType), imp) + } + return {} as DispatcherInterface +} + +function installType( + types: Object, name: TypeName, typespec: TypeSpecification) +{ + console.log('Pretending to install type', name, typespec) + //TODO: implement me +} + +function installSpecification( + behaviors: Object, // Same issue as mentioned above with side effects... + name: string, + defn: ImplementationDef, + specification: Function // possible todo: constrain this type based + // on the signature, return type, and dependencies. Not sure if + // that's really possible, though. +) { + console.log('Pretending to install', name, 'with signatures') + for (const signature of defn.fn.signatures) { + console.log(' ', signature.args, '=>', signature.returns) + } + if (defn.fn.aliasOf) { + console.log(' As an alias of', defn.fn.aliasOf) + } + //TODO: implement me } diff --git a/src/index.ts b/src/index.ts index e3377e5..cf46df1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,12 @@ import {inspect} from 'node:util' -import {Dispatcher} from './core/Dispatcher.js' +import {createDispatcher} from './core/Dispatcher.js' import * as Specifications from './all.js' -export default new Dispatcher(Specifications) +export Specifications +const math = createDispatcher(Specifications) +export default math + +console.log('Made', math.add} import {Complex} from './Complex/type.js' import {absquare as absquare_complex} from './Complex/arithmetic.js' diff --git a/src/interfaces/type.ts b/src/interfaces/type.ts index 3ab1b9a..55a36a9 100644 --- a/src/interfaces/type.ts +++ b/src/interfaces/type.ts @@ -15,7 +15,7 @@ import {$$typeToString} from 'ts-macros' * but that's OK, the generic parameter doesn't hurt in those cases. ****/ -type ValueIntersectionByKeyUnion = { +export type ValueIntersectionByKeyUnion = { [P in TKey]: (k: T[P])=>void } [TKey] extends ((k: infer I)=>void) ? I : never diff --git a/tsconfig.json b/tsconfig.json index 63c0b04..edf7502 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,7 @@ "rootDir": "./src", "outDir": "./build", "moduleResolution": "nodenext", + "declaration": true, "plugins": [ { "transform": "ts-macros/dist/type-resolve", "transformProgram": true