import {$$typeToString, $$ident, $$define} from 'ts-macros' /***** * Every typocomath type has some associated types; they need * to be published in the following interface. The key is the * name of the type, and within the subinterface for that key, * the type of the 'type' property is the actual TypeScript type * we are associating the other properties to. Note the interface * is generic with one parameter, corresponding to the fact that * typocomath currently only allows generic types with a single * generic parameter. This way, AssociatedTypes<SubType> can give the * associated types for a generic type instantiated with SubType. * That's not necessary for the 'undefined' type (or if you look in the * `numbers` subdirectory, the 'number' type) or any concrete type, * but that's OK, the generic parameter doesn't hurt in those cases. ****/ type ValueIntersectionByKeyUnion<T, TKey extends keyof T> = { [P in TKey]: (k: T[P])=>void } [TKey] extends ((k: infer I)=>void) ? I : never export interface AssociatedTypes<T> { undefined: { type: undefined zero: undefined one: undefined nan: undefined real: undefined } } type AssociatedTypeNames = keyof AssociatedTypes<unknown>['undefined'] type ALookup<T, Name extends AssociatedTypeNames> = ValueIntersectionByKeyUnion<{ [K in keyof AssociatedTypes<T>]: T extends AssociatedTypes<T>[K]['type'] ? AssociatedTypes<T>[K][Name] : unknown}, keyof AssociatedTypes<T>> // For everything to compile, zero and one must be subtypes of T: export type ZeroType<T> = ALookup<T, 'zero'> & T export type OneType<T> = ALookup<T, 'one'> & T // But I believe 'nan' really might not be, like I think we will have to use // 'undefined' for the nan of 'bigint', as it has nothing at all like NaN, // so don't force it: export type NaNType<T> = ALookup<T, 'nan'> export type RealType<T> = ALookup<T, 'real'> /***** * The global signature patterns for all operations need to be published in the * following interface. Each key is the name of an operation (but note that * the Dispatcher will automatically merge operations that have the same * name when the first underscore `_` and everything thereafter is stripped). * The type of each key should be an interface with two properties: 'params' * whose type is the type of the parameter list for the operation, and * 'returns' whose type is the return type of the operation on those * parameters. These types are generic in a parameter type T which should * be interpreted as the type that the operation is supposed to "primarily" * operate on, although note that some of the parameters and/or return types * may depend on T rather than be exactly T. * So note that the example 're' below provides essentially the same * information that e.g. * `type ReOp<T> = (t: T) => RealType<T>` * would, but in a way that is much easier to manipulate in TypeScript, * and it records the name of the operation as 're' also by virtue of the * key 're' in the interface. ****/ export interface Signatures<T> { zero: (a: T) => ZeroType<T> one: (a: T) => OneType<T> // nan needs to be able to operate on its own output for everything // else to compile. That's why its parameter type is widened: nan: (a: T | NaNType<T>) => NaNType<T> re: (a: T) => RealType<T> } type SignatureKey<T> = keyof Signatures<T> export type Signature<Name extends SignatureKey<T>, T> = Signatures<T>[Name] export type Returns<Name extends SignatureKey<T>, T> = ReturnType<Signatures<T>[Name]> type Deps<T> = T extends unknown ? { [K in keyof T]: T[K] } : never; export type Dependencies<Name extends SignatureKey<T>, T> = Deps<{[K in Name]: Signature<K, T>}> export type AliasOf<Name extends string, T> = T & {aliasOf?: Name} // For defining implementations with type reflection export function $implement<Impl>(name: string, expr: Impl) { $$define!(name, expr, false, true); // Final `true` is export $$ident!(name).reflectedType = $$typeToString!<Impl>(); }