- Notifications
You must be signed in to change notification settings - Fork 13.2k
Description
Search Terms
circular type conditional type
Suggestion
Currently, Last1 is valid, but seemingly-equivalent Last2 is not:
typeHead<Textendsany[]>=Textends[infer X, ...any[]] ? X : never;typeTail<Textendsany[]>=((...x: T)=>void)extends((x: any, ...xs: infer XS)=>void) ? XS : never;typeLast1<Textendsany[]>={0: never,1: Head<T>,2: Last1<Tail<T>>,}[Textends[] ? 0 : Textends[any] ? 1 : 2];typeLast2<Textendsany[]>=Textends[] ? never : Textends[infer R] ? R : Last2<Tail<T>>;My suggestion is to make Last2 valid, since the recursion isn't necessarily unbounded.
Use Cases
It'd make recursive conditional types a lot easier and more intuitive to write. It'd also eliminate the need to guard against impossible cases when using the workaround like when using Last1 above. If you wanted to mandate termination, all I'd want is direct recursion - this makes it much easier to check for infinite recursion. (Other types are generally assumed to terminate, so you're only assessing control flow.)
Examples
See above in the suggestion summary. Here's a concrete example of how this file (with dependency) would be simplified:
typeLast<Lextendsany[],D=never>=Lextends[] ? D : Lextends[infer H] ? H : ((...l: L)=>any)extends((h: any, ...t: infer T)=>any) ? Last<T> : D;typeAppend<Textendsany[],H>=((h: H, ...t: T)=>any)extends((...l: infer L)=>any) ? L : never;typeReverse<Lextendsany[],Rextendsany[]=[]>=((...l: L)=>any)extends((h: infer H, ...t: infer T)=>any) ? Reverse<T,Append<R,H>> : R;typeCompose<Lextendsany[],V,Rextendsany[]=[]>=((...l: L)=>any)extends((a: infer H, ...t: infer T)=>any) ? Compose<T,H,Append<R,(x: V)=>H>> : R;exporttypePipeFunc<Textendsany[],V>=(...f: Reverse<Compose<T,V>>)=>((x: V)=>Last<T,V>);Currently, you could achieve similar via this, but as you can see, it's highly repetitive and boilerplatey:
typeLast<Lextendsany[],D=never>={0: D,1: Lextends[infer H] ? H : never,2: ((...l: L)=>any)extends((h: any, ...t: infer T)=>any) ? Last<T> : D,}[Lextends[] ? 0 : Lextends[any] ? 1 : 2];typeAppend<Textendsany[],H>=((h: H, ...t: T)=>any)extends((...l: infer L)=>any) ? L : never;typeReverse<Lextendsany[],Rextendsany[]=[]>={0: R,1: ((...l: L)=>any)extends((h: infer H, ...t: infer T)=>any) ? Reverse<T,Append<R,H>> : never,}[Lextends[any, ...any[]] ? 1 : 0];typeCompose<Lextendsany[],V,Rextendsany[]=[]>={0: R,1: ((...l: L)=>any)extends((a: infer H, ...t: infer T)=>any) ? Compose<T,H,Append<R,(x: V)=>H>> : never,}[Lextends[any, ...any[]] ? 1 : 0];exporttypePipeFunc<Textendsany[],V>=(...f: Reverse<Compose<T,V>>)=>((x: V)=>Last<T,V>);(The original code snippet is linked to from here as a possible solution to the issue of _.compose, _.flow, and friends.)
Checklist
My suggestion meets these guidelines:
- This wouldn't be a breaking change in existing TypeScript / JavaScript code
- This wouldn't change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn't a runtime feature (e.g. new expression-level syntax)