<!-- 🚨 STOP 🚨 𝗦𝗧𝗢𝗣 🚨 𝑺𝑻𝑶𝑷 🚨 Half of all issues filed here are duplicates, answered in the FAQ, or not appropriate for the bug tracker. Please read the FAQ first, especially the "Common Feature Requests" section. --> ## Search Terms <!-- List of keywords you searched for before creating this issue. Write them down here so that others can find this suggestion more easily --> circular type conditional type ## Suggestion <!-- A summary of what you'd like to see added or changed --> Currently, `Last1` is valid, but seemingly-equivalent `Last2` is not: ```ts type Head<T extends any[]> = T extends [infer X, ...any[]] ? X : never; type Tail<T extends any[]> = ((...x: T) => void) extends ((x: any, ...xs: infer XS) => void) ? XS : never; type Last1<T extends any[]> = { 0: never, 1: Head<T>, 2: Last1<Tail<T>>, }[T extends [] ? 0 : T extends [any] ? 1 : 2]; type Last2<T extends any[]> = T extends [] ? never : T extends [infer R] ? R : Last2<Tail<T>>; ``` My suggestion is to make `Last2` valid, since the recursion isn't necessarily unbounded. ## Use Cases <!-- What do you want to use this for? What shortcomings exist with current approaches? --> 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 <!-- Show how this would be used and what the behavior would be --> See above in the suggestion summary. Here's a concrete example of how [this file](https://github.com/kgtkr/typepark/blob/master/src/pipe.ts) (with [dependency](https://github.com/kgtkr/typepark/blob/master/src/list.ts)) would be simplified: ```ts type Last<L extends any[], D = never> = L extends [] ? D : L extends [infer H] ? H : ((...l: L) => any) extends ((h: any, ...t: infer T) => any) ? Last<T> : D; type Append<T extends any[], H> = ((h: H, ...t: T) => any) extends ((...l: infer L) => any) ? L : never; type Reverse<L extends any[], R extends any[] = []> = ((...l: L) => any) extends ((h: infer H, ...t: infer T) => any) ? Reverse<T, Append<R, H>> : R; type Compose<L extends any[], V, R extends any[] = []> = ((...l: L) => any) extends ((a: infer H, ...t: infer T) => any) ? Compose<T, H, Append<R, (x: V) => H>> : R; export type PipeFunc<T extends any[], V> = (...f: Reverse<Compose<T, V>>) => ((x: V) => Last<T, V>); ``` Currently, you could achieve similar via [this](https://www.typescriptlang.org/play/index.html#src=type%20Last%3CL%20extends%20any%5B%5D%2C%20D%20%3D%20never%3E%20%3D%20%7B%0D%0A%20%20%20%200%3A%20D%2C%0D%0A%20%20%20%201%3A%20L%20extends%20%5Binfer%20H%5D%20%3F%20H%20%3A%20never%2C%0D%0A%20%20%20%202%3A%20((...l%3A%20L)%20%3D%3E%20any)%20extends%20((h%3A%20any%2C%20...t%3A%20infer%20T)%20%3D%3E%20any)%20%3F%20Last%3CT%3E%20%3A%20D%2C%0D%0A%7D%5BL%20extends%20%5B%5D%20%3F%200%20%3A%20L%20extends%20%5Bany%5D%20%3F%201%20%3A%202%5D%3B%0D%0A%0D%0Atype%20Append%3CT%20extends%20any%5B%5D%2C%20H%3E%20%3D%0D%0A%20%20%20%20((h%3A%20H%2C%20...t%3A%20T)%20%3D%3E%20any)%20extends%20((...l%3A%20infer%20L)%20%3D%3E%20any)%20%3F%20L%20%3A%20never%3B%0D%0A%0D%0Atype%20Reverse%3CL%20extends%20any%5B%5D%2C%20R%20extends%20any%5B%5D%20%3D%20%5B%5D%3E%20%3D%20%7B%0D%0A%20%20%20%200%3A%20R%2C%0D%0A%20%20%20%201%3A%20((...l%3A%20L)%20%3D%3E%20any)%20extends%20((h%3A%20infer%20H%2C%20...t%3A%20infer%20T)%20%3D%3E%20any)%20%3F%0D%0A%20%20%20%20%20%20%20%20Reverse%3CT%2C%20Append%3CR%2C%20H%3E%3E%20%3A%0D%0A%20%20%20%20%20%20%20%20never%2C%0D%0A%7D%5BL%20extends%20%5Bany%2C%20...any%5B%5D%5D%20%3F%201%20%3A%200%5D%3B%0D%0A%0D%0Atype%20Compose%3CL%20extends%20any%5B%5D%2C%20V%2C%20R%20extends%20any%5B%5D%20%3D%20%5B%5D%3E%20%3D%20%7B%0D%0A%20%20%20%200%3A%20R%2C%0D%0A%20%20%20%201%3A%20((...l%3A%20L)%20%3D%3E%20any)%20extends%20((a%3A%20infer%20H%2C%20...t%3A%20infer%20T)%20%3D%3E%20any)%20%3F%0D%0A%20%20%20%20%20%20%20%20Compose%3CT%2C%20H%2C%20Append%3CR%2C%20(x%3A%20V)%20%3D%3E%20H%3E%3E%0D%0A%20%20%20%20%20%20%20%20%3A%20never%2C%0D%0A%7D%5BL%20extends%20%5Bany%2C%20...any%5B%5D%5D%20%3F%201%20%3A%200%5D%3B%0D%0A%0D%0Aexport%20type%20PipeFunc%3CT%20extends%20any%5B%5D%2C%20V%3E%20%3D%0D%0A%20%20%20%20(...f%3A%20Reverse%3CCompose%3CT%2C%20V%3E%3E)%20%3D%3E%20((x%3A%20V)%20%3D%3E%20Last%3CT%2C%20V%3E)%3B), but as you can see, it's highly repetitive and boilerplatey: ```ts type Last<L extends any[], D = never> = { 0: D, 1: L extends [infer H] ? H : never, 2: ((...l: L) => any) extends ((h: any, ...t: infer T) => any) ? Last<T> : D, }[L extends [] ? 0 : L extends [any] ? 1 : 2]; type Append<T extends any[], H> = ((h: H, ...t: T) => any) extends ((...l: infer L) => any) ? L : never; type Reverse<L extends any[], R extends any[] = []> = { 0: R, 1: ((...l: L) => any) extends ((h: infer H, ...t: infer T) => any) ? Reverse<T, Append<R, H>> : never, }[L extends [any, ...any[]] ? 1 : 0]; type Compose<L extends any[], V, R extends any[] = []> = { 0: R, 1: ((...l: L) => any) extends ((a: infer H, ...t: infer T) => any) ? Compose<T, H, Append<R, (x: V) => H>> : never, }[L extends [any, ...any[]] ? 1 : 0]; export type PipeFunc<T extends any[], V> = (...f: Reverse<Compose<T, V>>) => ((x: V) => Last<T, V>); ``` (The original code snippet is linked to from [here](https://github.com/Microsoft/TypeScript/issues/5453#issuecomment-419636447) as a possible solution to the issue of `_.compose`, `_.flow`, and friends.) ## Checklist My suggestion meets these guidelines: * [x] This wouldn't be a breaking change in existing TypeScript / JavaScript code * [x] This wouldn't change the runtime behavior of existing JavaScript code * [x] This could be implemented without emitting different JS based on the types of the expressions * [x] This isn't a runtime feature (e.g. new expression-level syntax)