From 538a99f11cb5524552737f6dd2104f57fab0d374 Mon Sep 17 00:00:00 2001 From: sinclair Date: Thu, 23 Nov 2023 23:47:22 +0900 Subject: [PATCH] Reimplement Index Types --- examples/index.ts | 22 ++++++-- src/typebox.ts | 115 +++++++++++++++++++++++++++++++---------- test/static/indexed.ts | 2 +- 3 files changed, 105 insertions(+), 34 deletions(-) diff --git a/examples/index.ts b/examples/index.ts index d73c23a9..47592631 100644 --- a/examples/index.ts +++ b/examples/index.ts @@ -40,16 +40,28 @@ import { TLiteralValue, IsTemplateLiteralFiniteCheck, UnionToTuple, - PatternNumber + PatternNumber, + Accessor, + Indexer, } from '@sinclair/typebox' const T = Type.Union([ Type.Object({ x: Type.Number() }), - Type.Object({ y: Type.Number() }) + Type.Object({ x: Type.Number() }) ]) -const I = Type.Index(T, ['x']) -console.log(I) +const I = Type.Index(T, Type.Literal('x')) -type T = { x: 1 } | { y: 1 } +type C = Accessor.Union +type Z = C['anyOf'][1] + +type P = Z extends TNever ? 1 : 2 + +type N = Accessor.NeverCheck + +const C = Accessor.Union(T.anyOf, 'x') + + + +console.log(C) diff --git a/src/typebox.ts b/src/typebox.ts index a6372c67..a6e649f6 100644 --- a/src/typebox.ts +++ b/src/typebox.ts @@ -46,9 +46,12 @@ export const PatternStringExact = `^${PatternString}$` // -------------------------------------------------------------------------- // Into // -------------------------------------------------------------------------- -export function Into(func: () => T): T { +export function Into(func: () => U): U { return func() } +export function Infer(T: T, func: (T: T) => U): U { + return func(T) +} // -------------------------------------------------------------------------- // Helpers // -------------------------------------------------------------------------- @@ -2625,7 +2628,9 @@ export namespace IndexerOld { // ------------------------------------------------------------------ // prettier-ignore export namespace Indexer { + // ---------------------------------------------------------------- // TemplateLiteral + // ---------------------------------------------------------------- export type TemplateLiteral> = ( F extends true ? UnionToTuple> @@ -2641,7 +2646,9 @@ export namespace Indexer { : [] : [] } + // ---------------------------------------------------------------- // Union + // ---------------------------------------------------------------- export type Union = T extends [infer L extends TSchema, ...infer R extends TSchema[]] ? [...Resolve, ...Union] @@ -2652,7 +2659,9 @@ export namespace Indexer { ? [...Resolve(L), ...Union(R)] : [] } + // ---------------------------------------------------------------- // Literal + // ---------------------------------------------------------------- export type Literal = T extends PropertyKey ? [`${T}`] @@ -2660,7 +2669,9 @@ export namespace Indexer { export function TLiteral(schema: TLiteral): string[] { return [schema.const.toString()] } + // ---------------------------------------------------------------- // Resolve + // ---------------------------------------------------------------- export type Resolve = T extends TTemplateLiteral ? TemplateLiteral : T extends TUnion ? Union : @@ -2678,7 +2689,9 @@ export namespace Indexer { [] ))] } + // ---------------------------------------------------------------- // ResolvePattern + // ---------------------------------------------------------------- export function ResolvePattern(schema: TSchema): string { const keys = Resolve(schema) const pattern = keys.map((key) => key === 'number' ? PatternNumber : key) @@ -2690,6 +2703,74 @@ export namespace Indexer { // ------------------------------------------------------------------ // prettier-ignore export namespace Accessor { + // ---------------------------------------------------------------- + // NeverCheck + // ---------------------------------------------------------------- + export type NeverCheck = + T extends [infer L extends TSchema, ...infer R extends TSchema[]] + ? L extends TNever + ? true + : NeverCheck + : false + export function NeverCheck(T: TSchema[]): boolean { + const [L, ...R] = T + return T.length > 0 + ? TypeGuard.TNever(L) + ? true + : NeverCheck(R) + : false + } + // ---------------------------------------------------------------- + // Intersect + // ---------------------------------------------------------------- + export type IntersectCollect = + T extends [infer L extends TSchema, ...infer R extends TSchema[]] + ? Key extends infer N extends TSchema + ? N extends TNever + ? [...IntersectCollect] + : [N, ...IntersectCollect] + : [] + : [] + function IntersectCollect(T: TSchema[], K: PropertyKey): TSchema[] { + const [L, ...R] = T + return T.length > 0 + ? Infer(Key(L, K), N => { + return TypeGuard.TNever(N) + ? [...IntersectCollect(R, K)] + : [N, ...IntersectCollect(R, K)] + }) + : [] + } + export type Intersect> = + IntersectType.Resolve + export function Intersect(T: TSchema[], K: PropertyKey): TSchema { + const C = IntersectCollect(T, K) + return IntersectType.Resolve(C) + } + // ---------------------------------------------------------------- + // Union + // ---------------------------------------------------------------- + export type UnionCollect = + T extends [infer L extends TSchema, ...infer R extends TSchema[]] + ? [Key, ...UnionCollect] + : [] + export type Union, N extends boolean = NeverCheck> = + N extends true + ? TNever + : TUnion + export function UnionCollect(T: TSchema[], K: PropertyKey): TSchema[] { + const [L, ...R] = T + return T.length > 0 + ? [Key(L, K), ...UnionCollect(R, K)] + : [] + } + export function Union(T: TSchema[], K: PropertyKey): TSchema { + const C = UnionCollect(T, K) + const N = NeverCheck(C) + return N === true + ? Type.Never() + : UnionType.Resolve(C) + } // ---------------------------------------------------------------- // Property // ---------------------------------------------------------------- @@ -2730,34 +2811,12 @@ export namespace Accessor { : Type.Never() } // ---------------------------------------------------------------- - // Rest - // ---------------------------------------------------------------- - export type Rest = - T extends [infer L extends TSchema, ...infer R extends TSchema[]] - ? Key extends infer N extends TSchema - ? N extends TNever - ? [...Rest] - : [N, ...Rest] - : [] - : [] - function Rest(T: TSchema[], K: PropertyKey): TSchema[] { - const [L, ...R] = T - return T.length > 0 - ? Into(() => { - const N = Key(L, K) - return TypeGuard.TNever(N) - ? [...Rest(R, K)] - : [N, ...Rest(R, K)] - }) - : [] - } - // ---------------------------------------------------------------- // Key // ---------------------------------------------------------------- export type Key = ( T extends TRecursive ? Key : - T extends TIntersect ? IntersectType.Resolve> : - T extends TUnion ? UnionType.Resolve> : + T extends TIntersect ? Intersect : + T extends TUnion ? Union : T extends TTuple ? Tuple : T extends TArray ? Array : T extends TObject ? Property : @@ -2765,8 +2824,8 @@ export namespace Accessor { ) export function Key(T: TSchema, K: PropertyKey) { return ( - TypeGuard.TIntersect(T) ? IntersectType.Resolve(Rest(T.allOf, K)) : - TypeGuard.TUnion(T) ? UnionType.Resolve(Rest(T.anyOf, K)) : + TypeGuard.TIntersect(T) ? Intersect(T.allOf, K) : + TypeGuard.TUnion(T) ? Union(T.anyOf, K) : TypeGuard.TTuple(T) ? Tuple(T.items ?? [], K) : TypeGuard.TArray(T) ? Array(T.items, K) : TypeGuard.TObject(T) ? Property(T.properties, K) : @@ -3267,7 +3326,7 @@ export class JsonTypeBuilder extends TypeBuilder { ) as TExtract } /** `[Json]` Returns an Indexed property type for the given keys */ - public Index>(T: T, keys: I, options?: SchemaOptions): Accessor.Resolve + public Index>(T: T, keys: I, options?: SchemaOptions): Accessor.Resolve /** `[Json]` Returns an Indexed property type for the given keys */ public Index(T: T, K: [...K], options?: SchemaOptions): Accessor.Resolve /** `[Json]` Returns an Indexed property type for the given keys */ diff --git a/test/static/indexed.ts b/test/static/indexed.ts index 210e63fe..a224f9de 100644 --- a/test/static/indexed.ts +++ b/test/static/indexed.ts @@ -250,7 +250,7 @@ import { Type, Static } from '@sinclair/typebox' } { const T = Type.Object({ - 0: Type.Number(), + '0': Type.Number(), '1': Type.String(), }) const R = Type.Index(T, Type.KeyOf(T))