typocomath/src/infer.ts

89 lines
3.0 KiB
TypeScript

/**
* Idea: instead of writing TypeScript, and inferring the JS-pocomath signature
* from TS that via a TypeScript plugin, we can maybe do this the other way
* around: take the JS-pocomath signature as base (and source of truth), and
* infer TypeScript interfaces from that using infer in template literal types.
*/
// TODO: get generics working
// TODO: how to pass config?
const create = createFactory<{
number: number
bigint: bigint
string: string
any: any
}>()
// These are our string based interfaces, which we can use both in typed-function and in TypeScript:
const Multiply = 'multiply(number,number)=>number'
const Square = 'square(number)=>number'
const Zero = 'zero(number)=>number'
// TODO: turn a generic string like `(T,T)=>T` into a concrete one like `(number,number)=>number`
// const MultiplyNumber = ResolveGeneric<'multiply', number>
const createSquare = create(Square, [Multiply, Zero], dep =>
x => dep.multiply(x, x)
)
// the code works in JS, and works in TS
const multiply = (a: number, b: number) => a * b
const zero = (a: number) => 0
const square = createSquare({ multiply, zero })
console.log('square', square(8)) // 64
function createFactory<BaseTypes extends Record<string, unknown>>() {
type BaseTypeNames = string & keyof BaseTypes
type ResolveType<TypeName extends BaseTypeNames> = BaseTypes[TypeName]
type Value<K> = K
type ResolveArguments<S extends string> = S extends ''
? []
: S extends `${infer Arg extends BaseTypeNames},${infer Tail}`
? [ResolveType<Arg>, ...ResolveArguments<Tail>]
: S extends `${infer Arg extends BaseTypeNames}`
? [ResolveType<Arg>]
: never
type DependencyRecord<FnType> =
FnType extends Value<infer K>
? K extends `${infer Name}(${infer Args})=>${infer ReturnType extends BaseTypeNames}`
? Record<Name, (...args: ResolveArguments<Args>) => ResolveType<ReturnType>>
: never
: never
type CreatedFunctionType<FnType> =
FnType extends Value<infer K>
? K extends `${infer Name}(${infer Args})=>${infer ReturnType extends BaseTypeNames}`
? (...args: ResolveArguments<Args>) => ResolveType<ReturnType>
: never
: never
// inspired by: https://stackoverflow.com/questions/68391632/infer-type-from-array-literal
type DependenciesRecord<
Arr extends Array<Value<any>>,
Result extends Record<string, any> = {}
> = Arr extends []
? Result
: Arr extends [infer H, ...infer Tail]
? Tail extends Array<Value<any>>
? H extends Value<any>
? DependenciesRecord<Tail, Result & DependencyRecord<H>>
: never
: never
: never
return function create<K extends string, Dependencies extends Value<K>[], W extends Value<K>>(
signature: W,
dependencies: [...Dependencies],
callback: (deps: DependenciesRecord<[...Dependencies]>) => CreatedFunctionType<W>
) {
console.log('Creating typed-function with', { signature, dependencies })
// TODO: create a typed-function for real
return callback
}
}