Skip to content

Commit 5612f64

Browse files
authored
IfNotAnyOrNever: Add note regarding tail recursion (#1276)
1 parent 3c26560 commit 5612f64

2 files changed

Lines changed: 28 additions & 2 deletions

File tree

source/if.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ Note: Sometimes using the `If` type can make an implementation non–tail-recurs
6060
```
6161
import type {If, IsEqual, StringRepeat} from 'type-fest';
6262
63+
type HundredZeroes = StringRepeat<'0', 100>;
64+
6365
// The following implementation is not tail recursive
6466
type Includes<S extends string, Char extends string> =
6567
S extends `${infer First}${infer Rest}`
@@ -69,8 +71,6 @@ type Includes<S extends string, Char extends string> =
6971
: 'not found';
7072
7173
// Hence, instantiations with long strings will fail
72-
type HundredZeroes = StringRepeat<'0', 100>;
73-
7474
// @ts-expect-error
7575
type Fails = Includes<HundredZeroes, '1'>;
7676
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

source/internal/type.d.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,32 @@ type B = IfNotAnyOrNever<any, 'VALID', 'IS_ANY', 'IS_NEVER'>;
9696
type C = IfNotAnyOrNever<never, 'VALID', 'IS_ANY', 'IS_NEVER'>;
9797
//=> 'IS_NEVER'
9898
```
99+
100+
Note: Wrapping a tail-recursive type with `IfNotAnyOrNever` makes the implementation non-tail-recursive. To fix this, move the recursion into a helper type. Refer to the following example:
101+
102+
@example
103+
```ts
104+
import type {StringRepeat} from 'type-fest';
105+
106+
type NineHundredNinetyNineSpaces = StringRepeat<' ', 999>;
107+
108+
// The following implementation is not tail recursive
109+
type TrimLeft<S extends string> = IfNotAnyOrNever<S, S extends ` ${infer R}` ? TrimLeft<R> : S>;
110+
111+
// Hence, instantiations with long strings will fail
112+
// @ts-expect-error
113+
type T1 = TrimLeft<NineHundredNinetyNineSpaces>;
114+
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
115+
// Error: Type instantiation is excessively deep and possibly infinite.
116+
117+
// To fix this, move the recursion into a helper type
118+
type TrimLeftOptimised<S extends string> = IfNotAnyOrNever<S, _TrimLeftOptimised<S>>;
119+
120+
type _TrimLeftOptimised<S extends string> = S extends ` ${infer R}` ? _TrimLeftOptimised<R> : S;
121+
122+
type T2 = TrimLeftOptimised<NineHundredNinetyNineSpaces>;
123+
//=> ''
124+
```
99125
*/
100126
export type IfNotAnyOrNever<T, IfNotAnyOrNever, IfAny = any, IfNever = never> =
101127
If<IsAny<T>, IfAny, If<IsNever<T>, IfNever, IfNotAnyOrNever>>;

0 commit comments

Comments
 (0)