Title: Feature request: lift the circular constraint for conditional types · Issue #26980 · microsoft/TypeScript · GitHub
Open Graph Title: Feature request: lift the circular constraint for conditional types · Issue #26980 · microsoft/TypeScript
X Title: Feature request: lift the circular constraint for conditional types · Issue #26980 · microsoft/TypeScript
Description: Search Terms circular type conditional type Suggestion Currently, Last1 is valid, but seemingly-equivalent Last2 is not: type Head
Open Graph Description: Search Terms circular type conditional type Suggestion Currently, Last1 is valid, but seemingly-equivalent Last2 is not: type Head
X Description: Search Terms circular type conditional type Suggestion Currently, Last1 is valid, but seemingly-equivalent Last2 is not: type Head<T extends any[]> = T extends [infer X, ...any[]] ? X : never...
Opengraph URL: https://github.com/microsoft/TypeScript/issues/26980
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"Feature request: lift the circular constraint for conditional types","articleBody":"\u003c!-- 🚨 STOP 🚨 𝗦𝗧𝗢𝗣 🚨 𝑺𝑻𝑶𝑷 🚨\r\n\r\nHalf 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.\r\n\r\n--\u003e\r\n\r\n## Search Terms\r\n\r\n\u003c!-- List of keywords you searched for before creating this issue. Write them down here so that others can find this suggestion more easily --\u003e\r\n\r\ncircular type conditional type\r\n\r\n## Suggestion\r\n\r\n\u003c!-- A summary of what you'd like to see added or changed --\u003e\r\n\r\nCurrently, `Last1` is valid, but seemingly-equivalent `Last2` is not:\r\n\r\n```ts\r\ntype Head\u003cT extends any[]\u003e = T extends [infer X, ...any[]] ? X : never;\r\n\r\ntype Tail\u003cT extends any[]\u003e =\r\n ((...x: T) =\u003e void) extends ((x: any, ...xs: infer XS) =\u003e void) ? XS : never;\r\n\r\ntype Last1\u003cT extends any[]\u003e = {\r\n 0: never,\r\n 1: Head\u003cT\u003e,\r\n 2: Last1\u003cTail\u003cT\u003e\u003e,\r\n}[T extends [] ? 0 : T extends [any] ? 1 : 2];\r\n\r\ntype Last2\u003cT extends any[]\u003e =\r\n T extends [] ? never :\r\n T extends [infer R] ? R :\r\n Last2\u003cTail\u003cT\u003e\u003e;\r\n```\r\n\r\nMy suggestion is to make `Last2` valid, since the recursion isn't necessarily unbounded.\r\n\r\n## Use Cases\r\n\r\n\u003c!--\r\nWhat do you want to use this for?\r\nWhat shortcomings exist with current approaches?\r\n--\u003e\r\n\r\nIt'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.)\r\n\r\n## Examples\r\n\r\n\u003c!-- Show how this would be used and what the behavior would be --\u003e\r\n\r\nSee 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:\r\n\r\n```ts\r\ntype Last\u003cL extends any[], D = never\u003e =\r\n L extends [] ? D :\r\n L extends [infer H] ? H :\r\n ((...l: L) =\u003e any) extends ((h: any, ...t: infer T) =\u003e any) ? Last\u003cT\u003e :\r\n D;\r\n\r\ntype Append\u003cT extends any[], H\u003e =\r\n ((h: H, ...t: T) =\u003e any) extends ((...l: infer L) =\u003e any) ? L : never;\r\n\r\ntype Reverse\u003cL extends any[], R extends any[] = []\u003e =\r\n ((...l: L) =\u003e any) extends ((h: infer H, ...t: infer T) =\u003e any) ?\r\n Reverse\u003cT, Append\u003cR, H\u003e\u003e :\r\n R;\r\n\r\ntype Compose\u003cL extends any[], V, R extends any[] = []\u003e =\r\n ((...l: L) =\u003e any) extends ((a: infer H, ...t: infer T) =\u003e any) ?\r\n Compose\u003cT, H, Append\u003cR, (x: V) =\u003e H\u003e\u003e :\r\n R;\r\n\r\nexport type PipeFunc\u003cT extends any[], V\u003e =\r\n (...f: Reverse\u003cCompose\u003cT, V\u003e\u003e) =\u003e ((x: V) =\u003e Last\u003cT, V\u003e);\r\n```\r\n\r\nCurrently, 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:\r\n\r\n```ts\r\ntype Last\u003cL extends any[], D = never\u003e = {\r\n 0: D,\r\n 1: L extends [infer H] ? H : never,\r\n 2: ((...l: L) =\u003e any) extends ((h: any, ...t: infer T) =\u003e any) ? Last\u003cT\u003e : D,\r\n}[L extends [] ? 0 : L extends [any] ? 1 : 2];\r\n\r\ntype Append\u003cT extends any[], H\u003e =\r\n ((h: H, ...t: T) =\u003e any) extends ((...l: infer L) =\u003e any) ? L : never;\r\n\r\ntype Reverse\u003cL extends any[], R extends any[] = []\u003e = {\r\n 0: R,\r\n 1: ((...l: L) =\u003e any) extends ((h: infer H, ...t: infer T) =\u003e any) ?\r\n Reverse\u003cT, Append\u003cR, H\u003e\u003e :\r\n never,\r\n}[L extends [any, ...any[]] ? 1 : 0];\r\n\r\ntype Compose\u003cL extends any[], V, R extends any[] = []\u003e = {\r\n 0: R,\r\n 1: ((...l: L) =\u003e any) extends ((a: infer H, ...t: infer T) =\u003e any) ?\r\n Compose\u003cT, H, Append\u003cR, (x: V) =\u003e H\u003e\u003e\r\n : never,\r\n}[L extends [any, ...any[]] ? 1 : 0];\r\n\r\nexport type PipeFunc\u003cT extends any[], V\u003e =\r\n (...f: Reverse\u003cCompose\u003cT, V\u003e\u003e) =\u003e ((x: V) =\u003e Last\u003cT, V\u003e);\r\n```\r\n\r\n(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.)\r\n\r\n## Checklist\r\n\r\nMy suggestion meets these guidelines:\r\n* [x] This wouldn't be a breaking change in existing TypeScript / JavaScript code\r\n* [x] This wouldn't change the runtime behavior of existing JavaScript code\r\n* [x] This could be implemented without emitting different JS based on the types of the expressions\r\n* [x] This isn't a runtime feature (e.g. new expression-level syntax)\r\n\r\n","author":{"url":"https://github.com/dead-claudia","@type":"Person","name":"dead-claudia"},"datePublished":"2018-09-08T17:42:22.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":29},"url":"https://github.com/26980/TypeScript/issues/26980"}
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:1b734cb3-7fbf-f74b-6a4c-1ee83e57c7df |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | BD82:2CAFF1:2C1FDED:3AA2845:6A6515D0 |
| html-safe-nonce | 951ba2be824ce7b61a208d459f8b224e3d6441f0eee375b5d08654c28b4fdade |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJCRDgyOjJDQUZGMToyQzFGREVEOjNBQTI4NDU6NkE2NTE1RDAiLCJ2aXNpdG9yX2lkIjoiNzgzMzQyOTMyMzUzNDM3NDM1MiIsInJlZ2lvbl9lZGdlIjoiaWFkIiwicmVnaW9uX3JlbmRlciI6ImlhZCJ9 |
| visitor-hmac | 954acb96ebb5689406d076efbc3c4ba4074fdfd03aa534901102a18fff54790a |
| hovercard-subject-tag | issue:358314615 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/microsoft/TypeScript/26980/issue_layout |
| twitter:image | https://opengraph.githubassets.com/fd1673de8565850113d30f2a0e1b8310dece67652f901203e5e097da7aee6ec2/microsoft/TypeScript/issues/26980 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/fd1673de8565850113d30f2a0e1b8310dece67652f901203e5e097da7aee6ec2/microsoft/TypeScript/issues/26980 |
| og:image:alt | Search Terms circular type conditional type Suggestion Currently, Last1 is valid, but seemingly-equivalent Last2 is not: type Head |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | dead-claudia |
| hostname | github.com |
| expected-hostname | github.com |
| None | 52c76df668885aaff23b50bdca1fa1ea44ac9c1553e888ebc70ff1e4daa4625b |
| turbo-cache-control | no-preview |
| go-import | github.com/microsoft/TypeScript git https://github.com/microsoft/TypeScript.git |
| octolytics-dimension-user_id | 6154722 |
| octolytics-dimension-user_login | microsoft |
| octolytics-dimension-repository_id | 20929025 |
| octolytics-dimension-repository_nwo | microsoft/TypeScript |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 20929025 |
| octolytics-dimension-repository_network_root_nwo | microsoft/TypeScript |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 309153364422b3c499922d1a2a6404910a58ed8e |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width