Skip to content

Instantly share code, notes, and snippets.

@raganwald
Last active July 9, 2025 19:48
Show Gist options
  • Save raganwald/24d3334d6749987557ca4190023466b9 to your computer and use it in GitHub Desktop.
Save raganwald/24d3334d6749987557ca4190023466b9 to your computer and use it in GitHub Desktop.
Proof-of-concept of working around type-level TypeScript's 1,000 invocation limit on tail-recursive types
// --------- Utilities ---------
type Expect<T extends true> = T;
type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2
? true
: false;
type Extends<A, B> = A extends B ? true : false;
type IsNever<T> = Extends<[T], [never]>;
type IsFalse<T> = T extends false ? true : false;
type FAIL = { type: "FAIL" };
type Fail<M extends string> = FAIL & { message: M };
type IsFail<T> = [T] extends [FAIL] ? true : false;
type RepeatTenTimes<S extends string> = `${S}${S}${S}${S}${S}${S}${S}${S}${S}${S}`;
// ---------- Foundational Types ----------
type BlankOr<T> = "" | T;
type Digit = `${0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9}`;
type StartsWithDigit = `${Digit}${string}`; // can start with a zero
type WholeNumber = `${bigint}` & StartsWithDigit; // cannot start with a zero, all numbers
namespace TestWholeNumber {
type _0 = Expect<IsFalse<Extends<"", WholeNumber>>>;
type _1 = Expect<IsFalse<Extends<"QWERTY", WholeNumber>>>;
type _6 = Expect<Extends<"6", WholeNumber>>;
type _42 = Expect<Extends<"42", WholeNumber>>;
type _HUGE = Expect<Extends<"900719925474099190071992547409919007199254740991", WholeNumber>>;
}
type AsWholeNumber<T extends string> =
`${T}` extends infer WholeNumberT extends WholeNumber
? WholeNumberT
: never;
// ---------- Jackson's Widowbird ----------
type DepthLimit = 100;
type DepthZero = [];
type Depth = unknown[];
type Deeper<T extends Depth> = [...T, unknown];
type IsDepthAtLimit<T extends Depth> = T["length"] extends DepthLimit ? true : false;
type BasicReturnToTop = { type: "ReturnToTop", parameters: any[] };
type ReturnToTopWith<Params extends any[]> = { type: "ReturnToTop", parameters: Params };
// ---------- Operations on Foundational Types ----------
type SplitLastDigit<N extends WholeNumber> = SplitLastDigitTop<N, "">;
type SplitLastDigitTop<N extends StartsWithDigit, ButLast extends BlankOr<WholeNumber>> =
SplitLastDigitImpl<N, ButLast, DepthZero> extends infer ReturnValue
? ReturnValue extends { type: "ReturnToTop", parameters: [infer N2 extends StartsWithDigit, infer ButLast2 extends BlankOr<WholeNumber>] }
? SplitLastDigitTop<N2, ButLast2>
: ReturnValue
: never;
type SplitLastDigitImpl<N extends StartsWithDigit, ButLast extends BlankOr<WholeNumber>, D extends unknown[]> =
N extends Digit
? [ButLast, N]
: N extends `${infer First extends Digit}${infer ButFirst extends StartsWithDigit}`
? IsDepthAtLimit<D> extends true
? ReturnToTopWith<[N, ButLast]>
: SplitLastDigitImpl<ButFirst, AsWholeNumber<`${ButLast}${First}`>, Deeper<D>>
: never;
namespace TestSplitLastDigit {
type TenThousandCharNumber = RepeatTenTimes<RepeatTenTimes<RepeatTenTimes<"1234567890">>>;
type TenThousandAndOneCharNumber = `${TenThousandCharNumber}6`;
type _TestTenThousandAndOne = Expect<Equal<[TenThousandCharNumber, "6"], SplitLastDigit<TenThousandAndOneCharNumber>>>;
}
// --------- Utilities ---------
type Expect<T extends true> = T;
type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2
? true
: false;
type Extends<A, B> = A extends B ? true : false;
type IsNever<T> = Extends<[T], [never]>;
type IsFalse<T> = T extends false ? true : false;
type Assume<T, U> = T extends U ? T : U;
type FAIL = { type: "FAIL" };
type Fail<M extends string> = FAIL & { message: M };
type IsFail<T> = [T] extends [FAIL] ? true : false;
type RepeatTenTimes<S extends string> = `${S}${S}${S}${S}${S}${S}${S}${S}${S}${S}`;
// ---------- Foundational Types ----------
type BlankOr<T> = "" | T;
type Digit = `${0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9}`;
type StartsWithDigit = `${Digit}${string}`; // can start with a zero
type WholeNumber = `${bigint}` & StartsWithDigit; // cannot start with a zero, all numbers
namespace TestWholeNumber {
type _0 = Expect<IsFalse<Extends<"", WholeNumber>>>;
type _1 = Expect<IsFalse<Extends<"QWERTY", WholeNumber>>>;
type _6 = Expect<Extends<"6", WholeNumber>>;
type _42 = Expect<Extends<"42", WholeNumber>>;
type _HUGE = Expect<Extends<"900719925474099190071992547409919007199254740991", WholeNumber>>;
}
type AsWholeNumber<T extends string> =
`${T}` extends infer WholeNumberT extends WholeNumber
? WholeNumberT
: never;
// ---------- Higher Kinded Types ----------
//
// See: https://code.lol/post/programming/higher-kinded-types/$0
//
type GenericFunction = (..._: never[]) => unknown;
abstract class HKT {
readonly _1?: unknown;
readonly _2?: unknown;
readonly _3?: unknown;
readonly _4?: unknown;
new?: GenericFunction;
}
// quite the hack
type Apply<F extends HKT, _1, _2 extends any = never, _3 extends any = never, _4 extends any = never> = ReturnType<
(F &
{
readonly _1: _1;
readonly _2: _2;
readonly _3: _3;
readonly _4: _4;
}) extends infer G extends { new: GenericFunction } ? G["new"] : never
>;
// ---------- Jackson's Widowbird ----------
//
// This preliminary version relies on the recursive type incorporating the
// "widowbird" behaviour of jumping back up to the top inline with the
// "business logic" of the recursive type itself.
//
// Non-trivial to-do:
//
// 1. Refactor to use an equivalent of the M combinator instead of hard-coding
// a self-referential type, and;
// 2. Writing "top" such that it can encode all this logic into a decorated
// "myself" that manages depth and knows when to go ack to the top.
type DepthLimit = RepeatTenTimes<RepeatTenTimes<".">>;
type DepthZero = "";
type Depth = string;
type Deeper<T extends Depth> = `${T}.`;
type IsDepthAtLimit<T extends Depth> = Extends<T, DepthLimit>;
type BasicReturnToTop = { type: "ReturnToTop", parameters: any[] };
type ReturnToTopWith<Params extends any[]> = { type: "ReturnToTop", parameters: Params };
// ---------- Operations on Foundational Types ----------
type SplitLastDigit<N extends WholeNumber> = SplitLastDigitTop<N, "">;
type SplitLastDigitTop<N extends StartsWithDigit, ButLast extends BlankOr<WholeNumber>> =
Apply<SplitLastDigitHKT, N, ButLast, DepthZero> extends infer ReturnValue
? ReturnValue extends { type: "ReturnToTop", parameters: [infer N2 extends StartsWithDigit, infer ButLast2 extends BlankOr<WholeNumber>] }
? SplitLastDigitTop<N2, ButLast2>
: ReturnValue
: never;
// ---------- Wrapping SplitLastDigitImpl into an HKT ----------
interface SplitLastDigitHKT extends HKT {
new: (_N: Assume<this["_1"], StartsWithDigit>, _ButLast: Assume<this["_2"], BlankOr<WholeNumber>>, _D: Assume<this["_3"], Depth>) =>
[typeof _N, typeof _ButLast, typeof _D] extends [infer N, infer ButLast extends BlankOr<WholeNumber>, infer D extends Depth]
? IsDepthAtLimit<D> extends true
? ReturnToTopWith<[N, ButLast]>
: N extends Digit
? [ButLast, N]
: N extends `${infer First extends Digit}${infer ButFirst extends StartsWithDigit}`
? Apply<SplitLastDigitHKT, ButFirst, AsWholeNumber<`${ButLast}${First}`>, Deeper<D>>
: never
: never;
}
namespace TestSplitLastDigit {
// @ts-expect-error does not start with a digit
type _0 = SplitLastDigit<"">;
type _1 = Expect<Equal<["", "1"], SplitLastDigit<"1">>>;
type _2 = Expect<Equal<["1", "2"], SplitLastDigit<"12">>>;
type _3 = Expect<Equal<["12", "3"], SplitLastDigit<"123">>>;
type TenThousandCharNumber = RepeatTenTimes<RepeatTenTimes<RepeatTenTimes<"1234567890">>>;
type TenThousandAndOneCharNumber = `${TenThousandCharNumber}6`;
type _TestTenThousandAndOne = Expect<Equal<[TenThousandCharNumber, "6"], SplitLastDigit<TenThousandAndOneCharNumber>>>;
}
// --------- Utilities ---------
type Expect<T extends true> = T;
type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2
? true
: false;
type Extends<A, B> = A extends B ? true : false;
type IsNever<T> = Extends<[T], [never]>;
type IsFalse<T> = T extends false ? true : false;
type Assume<T, U> = T extends U ? T : U;
type FAIL = { type: "FAIL" };
type Fail<M extends string> = FAIL & { message: M };
type IsFail<T> = [T] extends [FAIL] ? true : false;
type RepeatTenTimes<S extends string> = `${S}${S}${S}${S}${S}${S}${S}${S}${S}${S}`;
// ---------- Foundational Types ----------
type BlankOr<T> = "" | T;
type Digit = `${0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9}`;
type StartsWithDigit = `${Digit}${string}`; // can start with a zero
type WholeNumber = `${bigint}` & StartsWithDigit; // cannot start with a zero, all numbers
namespace TestWholeNumber {
type _0 = Expect<IsFalse<Extends<"", WholeNumber>>>;
type _1 = Expect<IsFalse<Extends<"QWERTY", WholeNumber>>>;
type _6 = Expect<Extends<"6", WholeNumber>>;
type _42 = Expect<Extends<"42", WholeNumber>>;
type _HUGE = Expect<Extends<"900719925474099190071992547409919007199254740991", WholeNumber>>;
}
type AsWholeNumber<T extends string> =
`${T}` extends infer WholeNumberT extends WholeNumber
? WholeNumberT
: never;
// ---------- Higher Kinded Types ----------
//
// See: https://code.lol/post/programming/higher-kinded-types/$0
//
type GenericFunction = (..._: never[]) => unknown;
abstract class HKT {
readonly _1?: unknown;
readonly _2?: unknown;
readonly _3?: unknown;
readonly _4?: unknown;
new?: GenericFunction;
}
// quite the hack
type Apply<F extends HKT, _1, _2 extends any = never, _3 extends any = never, _4 extends any = never> = ReturnType<
(F &
{
readonly _1: _1;
readonly _2: _2;
readonly _3: _3;
readonly _4: _4;
}) extends infer G extends { new: GenericFunction } ? G["new"] : never
>;
// ---------- Jackson's Widowbird ----------
//
// This preliminary version relies on the recursive type incorporating the
// "widowbird" behaviour of jumping back up to the top inline with the
// "business logic" of the recursive type itself.
//
// Non-trivial to-do:
//
// 1. Refactor to use an equivalent of the M combinator instead of hard-coding
// a self-referential type, and;
// 2. Writing "top" such that it can encode all this logic into a decorated
// "myself" that manages depth and knows when to go ack to the top.
type DepthLimit = RepeatTenTimes<RepeatTenTimes<".">>;
type DepthZero = "";
type Depth = string;
type Deeper<T extends Depth> = `${T}.`;
type IsDepthAtLimit<T extends Depth> = Extends<T, DepthLimit>;
type BasicReturnToTop = { type: "ReturnToTop", parameters: any[] };
type ReturnToTopWith<Params extends any[]> = { type: "ReturnToTop", parameters: Params };
// ---------- Operations on Foundational Types ----------
type SplitLastDigit<N extends WholeNumber> = SplitLastDigitTop<N, "">;
type SplitLastDigitTop<N extends StartsWithDigit, ButLast extends BlankOr<WholeNumber>> =
Apply<SplitLastDigitHKT, SplitLastDigitHKT, N, ButLast, DepthZero> extends infer ReturnValue
? ReturnValue extends { type: "ReturnToTop", parameters: [infer N2 extends StartsWithDigit, infer ButLast2 extends BlankOr<WholeNumber>] }
? SplitLastDigitTop<N2, ButLast2>
: ReturnValue
: never;
interface SplitLastDigitHKT extends HKT {
new: (
_Myself: Assume<this["_1"], HKT>, _N: Assume<this["_2"], StartsWithDigit>, _ButLast: Assume<this["_3"], BlankOr<WholeNumber>>, _D: Assume<this["_4"], Depth>
) =>
[
typeof _Myself, typeof _N, typeof _ButLast, typeof _D
] extends [
infer Myself extends HKT, infer N, infer ButLast extends BlankOr<WholeNumber>, infer D extends Depth
]
? N extends Digit
? [ButLast, N]
: N extends `${infer First extends Digit}${infer ButFirst extends StartsWithDigit}`
? IsDepthAtLimit<D> extends true
? ReturnToTopWith<[N, ButLast]>
: Apply<SplitLastDigitHKT, SplitLastDigitHKT, ButFirst, AsWholeNumber<`${ButLast}${First}`>, Deeper<D>>
: never
: never;
}
namespace TestSplitLastDigit {
// @ts-expect-error does not start with a digit
type _0 = SplitLastDigit<"">;
type _1 = Expect<Equal<["", "1"], SplitLastDigit<"1">>>;
type _2 = Expect<Equal<["1", "2"], SplitLastDigit<"12">>>;
type _3 = Expect<Equal<["12", "3"], SplitLastDigit<"123">>>;
type TenThousandCharNumber = RepeatTenTimes<RepeatTenTimes<RepeatTenTimes<"1234567890">>>;
type TenThousandAndOneCharNumber = `${TenThousandCharNumber}6`;
type _TestTenThousandAndOne = Expect<Equal<[TenThousandCharNumber, "6"], SplitLastDigit<TenThousandAndOneCharNumber>>>;
}
// --------- Utilities ---------
type Expect<T extends true> = T;
type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2
? true
: false;
type Extends<A, B> = A extends B ? true : false;
type IsNever<T> = Extends<[T], [never]>;
type IsFalse<T> = T extends false ? true : false;
type Assume<T, U> = T extends U ? T : U;
type FAIL = { type: "FAIL" };
type Fail<M extends string> = FAIL & { message: M };
type IsFail<T> = [T] extends [FAIL] ? true : false;
// ---------- Counting small(!) numbers with strings
type RepeatTenTimes<S extends string> = `${S}${S}${S}${S}${S}${S}${S}${S}${S}${S}`;
// ---------- Foundational Types ----------
type BlankOr<T> = "" | T;
type Digit = `${0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9}`;
type StartsWithDigit = `${Digit}${string}`; // can start with a zero
type WholeNumber = `${bigint}` & StartsWithDigit; // cannot start with a zero, all numbers
namespace TestWholeNumber {
type _0 = Expect<IsFalse<Extends<"", WholeNumber>>>;
type _1 = Expect<IsFalse<Extends<"QWERTY", WholeNumber>>>;
type _6 = Expect<Extends<"6", WholeNumber>>;
type _42 = Expect<Extends<"42", WholeNumber>>;
type _HUGE = Expect<Extends<"900719925474099190071992547409919007199254740991", WholeNumber>>;
}
type AsWholeNumber<T extends string> =
`${T}` extends infer WholeNumberT extends WholeNumber
? WholeNumberT
: never;
// ---------- Higher Kinded Types ----------
//
// See: https://code.lol/post/programming/higher-kinded-types/$0
//
type GenericFunction = (..._: never[]) => unknown;
abstract class HKT {
readonly _1?: unknown;
readonly _2?: unknown;
readonly _3?: unknown;
readonly _4?: unknown;
new?: GenericFunction;
}
// quite the hack
type Apply<F extends HKT, _1, _2 extends any = never, _3 extends any = never, _4 extends any = never> = ReturnType<
(F &
{
readonly _1: _1;
readonly _2: _2;
readonly _3: _3;
readonly _4: _4;
}) extends infer G extends { new: GenericFunction } ? G["new"] : never
>;
// ---------- Jackson's Widowbird ----------
//
// This preliminary version relies on the recursive type incorporating the
// "widowbird" behaviour of jumping back up to the top inline with the
// "business logic" of the recursive type itself.
//
// Non-trivial to-do:
//
// 1. Refactor to use an equivalent of the M combinator instead of hard-coding
// a self-referential type, and;
// 2. Writing "top" such that it can encode all this logic into a decorated
// "myself" that manages depth and knows when to go ack to the top.
type DepthLimit = RepeatTenTimes<".........">;
type DepthZero = "";
type Depth = string;
type Deeper<T extends Depth> = `${T}.`;
type IsDepthAtLimit<T extends Depth> = Extends<T, DepthLimit>;
type BasicReturnToTop = { type: "ReturnToTop", parameters: any[] };
type ReturnToTopWith<Params extends any[]> = { type: "ReturnToTop", parameters: Params };
// ---------- Operations on Foundational Types ----------
type SplitLastDigit<N extends WholeNumber> = SplitLastDigitTop<N, "">;
type SplitLastDigitTop<N extends StartsWithDigit, ButLast extends BlankOr<WholeNumber>> =
Apply<SplitLastDigitHKT, SplitLastDigitHKT, N, ButLast, DepthZero> extends infer ReturnValue
? ReturnValue extends { type: "ReturnToTop", parameters: [infer N2 extends StartsWithDigit, infer ButLast2 extends BlankOr<WholeNumber>] }
? SplitLastDigitTop<N2, ButLast2>
: ReturnValue
: never;
interface SplitLastDigitHKT extends HKT {
new: (
_Myself: Assume<this["_1"], HKT>, _N: Assume<this["_2"], StartsWithDigit>, _ButLast: Assume<this["_3"], BlankOr<WholeNumber>>, _D: Assume<this["_4"], Depth>
) =>
[
typeof _Myself, typeof _N, typeof _ButLast, typeof _D
] extends [
infer Myself extends HKT, infer N, infer ButLast extends BlankOr<WholeNumber>, infer D extends Depth
]
? N extends Digit
? [ButLast, N]
: N extends `${infer First extends Digit}${infer ButFirst extends StartsWithDigit}`
? IsDepthAtLimit<D> extends true
? ReturnToTopWith<[N, ButLast]>
: Apply<Myself, Myself, ButFirst, AsWholeNumber<`${ButLast}${First}`>, Deeper<D>>
: never
: never;
}
// appears to be broken when the depth limit is 100, but works for 90!
namespace TestSplitLastDigit {
// @ts-expect-error does not start with a digit
type _0 = SplitLastDigit<"">;
type _1 = Expect<Equal<["", "1"], SplitLastDigit<"1">>>;
type _2 = Expect<Equal<["1", "2"], SplitLastDigit<"12">>>;
type _3 = Expect<Equal<["12", "3"], SplitLastDigit<"123">>>;
type _4 = Expect<Equal<["123", "4"], SplitLastDigit<"1234">>>;
type TenCharNumber = "1234567890";
type _TestTenAndOne = Expect<Equal<[TenCharNumber, "6"], SplitLastDigit<`${TenCharNumber}6`>>>;
type OneHundredCharNumber = RepeatTenTimes<TenCharNumber>;
type _TestFifty = Expect<Equal<
["1234567890123456789012345678901234567890123456789", "0"],
SplitLastDigit<"12345678901234567890123456789012345678901234567890">
>>;
type _TestOneHundred = Expect<Equal<
["123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789", "0"],
SplitLastDigit<OneHundredCharNumber>
>>;
type _TestOneHundredAndOne = Expect<Equal<[OneHundredCharNumber, "6"], SplitLastDigit<`${OneHundredCharNumber}6`>>>;
type OneThousandCharNumber = RepeatTenTimes<OneHundredCharNumber>;
type _TestOneThousandAndOne = Expect<Equal<[OneThousandCharNumber, "6"], SplitLastDigit<`${OneThousandCharNumber}6`>>>;
type TenThousandCharNumber = RepeatTenTimes<OneThousandCharNumber>;
type _TestTenThousandAndOne = Expect<Equal<[TenThousandCharNumber, "6"], SplitLastDigit<`${TenThousandCharNumber}6`>>>;
}
// --------- Utilities ---------
type Expect<T extends true> = T;
type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2
? true
: false;
type Extends<A, B> = A extends B ? true : false;
type IsNever<T> = Extends<[T], [never]>;
type IsFalse<T> = T extends false ? true : false;
type Assume<T, U> = T extends U ? T : U;
type FAIL = { type: "FAIL" };
type Fail<M extends string> = FAIL & { message: M };
type IsFail<T> = [T] extends [FAIL] ? true : false;
// ---------- Counting small(!) numbers with strings
type RepeatTenTimes<S extends string> = `${S}${S}${S}${S}${S}${S}${S}${S}${S}${S}`;
// ---------- Foundational Types ----------
type BlankOr<T> = "" | T;
type Digit = `${0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9}`;
type StartsWithDigit = `${Digit}${string}`; // can start with a zero
type WholeNumber = `${bigint}` & StartsWithDigit; // cannot start with a zero, all numbers
namespace TestWholeNumber {
type _0 = Expect<IsFalse<Extends<"", WholeNumber>>>;
type _1 = Expect<IsFalse<Extends<"QWERTY", WholeNumber>>>;
type _6 = Expect<Extends<"6", WholeNumber>>;
type _42 = Expect<Extends<"42", WholeNumber>>;
type _HUGE = Expect<Extends<"900719925474099190071992547409919007199254740991", WholeNumber>>;
}
type AsWholeNumber<T extends string> =
`${T}` extends infer WholeNumberT extends WholeNumber
? WholeNumberT
: never;
// ---------- Higher Kinded Types ----------
//
// See: https://code.lol/post/programming/higher-kinded-types/$0
//
type GenericFunction = (..._: never[]) => unknown;
abstract class HKT {
readonly _1?: unknown;
readonly _2?: unknown;
readonly _3?: unknown;
readonly _4?: unknown;
new?: GenericFunction;
}
// quite the hack
type Apply<F extends HKT, _1, _2 extends any = never, _3 extends any = never, _4 extends any = never> = ReturnType<
(F &
{
readonly _1: _1;
readonly _2: _2;
readonly _3: _3;
readonly _4: _4;
}) extends infer G extends { new: GenericFunction } ? G["new"] : never
>;
// ---------- Jackson's Widowbird ----------
//
// This preliminary version relies on the recursive type incorporating the
// "widowbird" behaviour of jumping back up to the top inline with the
// "business logic" of the recursive type itself.
//
// Non-trivial to-do:
//
// 1. Refactor to use an equivalent of the M combinator instead of hard-coding
// a self-referential type, and;
// 2. Writing "top" such that it can encode all this logic into a decorated
// "myself" that manages depth and knows when to go ack to the top.
type DepthLimit = RepeatTenTimes<".........">;
type DepthZero = "";
type Depth = string;
type Deeper<T extends Depth> = `${T}.`;
type IsDepthAtLimit<T extends Depth> = Extends<T, DepthLimit>;
type BasicReturnToTop = { type: "ReturnToTop", parameters: any[] };
type ReturnToTopWith<Params extends any[]> = { type: "ReturnToTop", parameters: Params };
// ---------- Operations on Foundational Types ----------
type SplitLastDigit<N extends WholeNumber> = SplitLastDigitTop<N, "">;
type SplitLastDigitTop<N extends StartsWithDigit, ButLast extends BlankOr<WholeNumber>> =
Apply<SplitLastDigitHKT, SplitLastDigitHKT, N, ButLast, DepthZero> extends infer ReturnValue
? ReturnValue extends { type: "ReturnToTop", parameters: [infer N2 extends StartsWithDigit, infer ButLast2 extends BlankOr<WholeNumber>] }
? SplitLastDigitTop<N2, ButLast2>
: ReturnValue
: never;
// TODO: Extract the limit check
// Refactor logic order
interface SplitLastDigitHKT extends HKT {
new: (
_Myself: Assume<this["_1"], HKT>, _N: Assume<this["_2"], StartsWithDigit>, _ButLast: Assume<this["_3"], BlankOr<WholeNumber>>, _D: Assume<this["_4"], Depth>
) =>
[
typeof _Myself, typeof _N, typeof _ButLast, typeof _D
] extends [
infer Myself extends HKT, infer N, infer ButLast extends BlankOr<WholeNumber>, infer D extends Depth
]
? IsDepthAtLimit<D> extends true
? ReturnToTopWith<[N, ButLast]>
: N extends Digit
? [ButLast, N] // generate case
: N extends `${infer First extends Digit}${infer ButFirst extends StartsWithDigit}`
? Apply<Myself, Myself, ButFirst, AsWholeNumber<`${ButLast}${First}`>, Deeper<D>>
: never
: never;
}
// appears to be broken when the depth limit is 100, but works for 90!
namespace TestSplitLastDigit {
// @ts-expect-error does not start with a digit
type _0 = SplitLastDigit<"">;
type _1 = Expect<Equal<["", "1"], SplitLastDigit<"1">>>;
type _2 = Expect<Equal<["1", "2"], SplitLastDigit<"12">>>;
type _3 = Expect<Equal<["12", "3"], SplitLastDigit<"123">>>;
type _4 = Expect<Equal<["123", "4"], SplitLastDigit<"1234">>>;
type TenCharNumber = "1234567890";
type _TestTenAndOne = Expect<Equal<[TenCharNumber, "6"], SplitLastDigit<`${TenCharNumber}6`>>>;
type OneHundredCharNumber = RepeatTenTimes<TenCharNumber>;
type _TestFifty = Expect<Equal<
["1234567890123456789012345678901234567890123456789", "0"],
SplitLastDigit<"12345678901234567890123456789012345678901234567890">
>>;
type _TestOneHundred = Expect<Equal<
["123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789", "0"],
SplitLastDigit<OneHundredCharNumber>
>>;
type _TestOneHundredAndOne = Expect<Equal<[OneHundredCharNumber, "6"], SplitLastDigit<`${OneHundredCharNumber}6`>>>;
type OneThousandCharNumber = RepeatTenTimes<OneHundredCharNumber>;
type _TestOneThousandAndOne = Expect<Equal<[OneThousandCharNumber, "6"], SplitLastDigit<`${OneThousandCharNumber}6`>>>;
type TenThousandCharNumber = RepeatTenTimes<OneThousandCharNumber>;
type _TestTenThousandAndOne = Expect<Equal<[TenThousandCharNumber, "6"], SplitLastDigit<`${TenThousandCharNumber}6`>>>;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment