Title: `as enum` assertion for object literals · Issue #60790 · microsoft/TypeScript · GitHub
Open Graph Title: `as enum` assertion for object literals · Issue #60790 · microsoft/TypeScript
X Title: `as enum` assertion for object literals · Issue #60790 · microsoft/TypeScript
Description: 🔍 Search Terms enum, object literal, type-stripping ✅ Viability Checklist 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 imple...
Open Graph Description: 🔍 Search Terms enum, object literal, type-stripping ✅ Viability Checklist This wouldn't be a breaking change in existing TypeScript/JavaScript code This wouldn't change the runtime behavior of exis...
X Description: 🔍 Search Terms enum, object literal, type-stripping ✅ Viability Checklist This wouldn't be a breaking change in existing TypeScript/JavaScript code This wouldn't change the runtime behavior...
Opengraph URL: https://github.com/microsoft/TypeScript/issues/60790
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"`as enum` assertion for object literals","articleBody":"### 🔍 Search Terms\n\nenum, object literal, type-stripping\n\n### ✅ Viability Checklist\n\n- [x] This wouldn't be a breaking change in existing TypeScript/JavaScript code\n- [x] This wouldn't change the runtime behavior of existing JavaScript code\n- [x] This could be implemented without emitting different JS based on the types of the expressions\n- [x] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)\n- [x] This isn't a request to add a new utility type: https://github.com/microsoft/TypeScript/wiki/No-New-Utility-Types\n- [x] This feature would agree with the rest of our Design Goals: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals\n\n### Context\n\nWhile TypeScript already allows declaring runtime enums values:\n\n```ts\nexport enum Compass {\n N = \"N\",\n S = \"S\",\n E = \"E\",\n W = \"W\",\n}\n```\n\nThis is not standard JavaScript, and does not work in Node.js unless [`--experimental-transform-types`](https://nodejs.org/api/cli.html#--experimental-transform-types) is passed.\n\nAn alternative \"pure JS\" pattern from the TypeScript handbook is:\n\n```ts\nexport const Compass = {\n N: \"N\",\n S: \"S\",\n E: \"E\",\n W: \"W\",\n} as const;\nexport type Compass = typeof Compass[keyof typeof Compass];\n```\n\nhttps://www.typescriptlang.org/docs/handbook/enums.html#objects-vs-enums\n\nThere are two downsides to this pattern:\n\n- `typeof X[keyof typeof X]` is both verbose and not beginner friendly.\n- The type aliases are not nominal, they are a _plain_ union type.\n\n### ⭐ Suggestion\n\nIntroduce some new syntax to TypeScript to help with the object-literal-as-enum pattern.\n\nFor example would be allowing `as enum`:\n\n```ts\nexport const Compass = {\n N: \"N\",\n S: \"S\",\n E: \"E\",\n W: \"W\",\n} as enum;\n```\n\n(from #59658)\n\nAn alternative design could be allowing an `enum` type annotation on `const` variable declarations `export const Compass: enum = {...}`.\n\nThis annotation would effectively be the same as writing:\n\n```ts\n/** secret internal type - here to get nominal typing */\ndeclare const enum __Compass__ {\n N = \"N\",\n S = \"S\",\n E = \"E\",\n W = \"W\",\n}\nexport const Compass = {\n N: \"N\" as __Compass__.N,\n S: \"S\" as __Compass__.S,\n E: \"E\" as __Compass__.E,\n W: \"W\" as __Compass__.W,\n} as const;\nexport type Compass = __Compass__;\n```\n\n### Rules\n\nThe `enum` annotation would only be permitted for object literals that are\n - in a declarative position\n - have compile-time constant key+values\n - all values are either strings, numbers, or references to constant strings/numbers.\n\ni.e. the object literal would follow very similar rules that are applied to `const enum C {}` syntax\n\n```ts\n// @ts-expect-error\nfoo({ a: \"a\" } as enum);\n\nclass C {\n // @ts-expect-error\n f: enum = {}\n}\n\nconst o = {\n // @ts-expect-error\n p: Math.random()\n} as enum;\n```\n\n### Benefits\n\n- The standard JS of an object lieral with the type-checking of an `enum`\n- An explicit marker for tools such as linters to provide extra checks (e.g. enum naming conventions)\n\n### Downsides\n\nWhile this object literal as enum pattern is popular in codebases that avoid non-standard runtime syntax it does not have all the features available with `enum` syntax such as self-reference during construction.\n\n`__proto__: null` is currently not supported #38385 making it difficult to avoid object literals from inheriting non-enum properties resulting in false positives with `key in MyEnum`.\n\n\n### 📃 Motivating Example\n\n```ts\nexport const Compass = {\n N: \"N\",\n S: \"S\",\n E: \"E\",\n W: \"W\",\n} as enum;\nObject.freeze(Compass);\n\nexport function reverse(c: Compass): Compass {\n if (c === Compass.N) return Compass.S;\n if (c === Compass.S) return Compass.N;\n if (c === Compass.E) return Compass.W;\n if (c === Compass.W) return Compass.E;\n throw new Error(\"unreachable code was run\");\n}\n```\n\nThe above module will work out-of-the-box in Node.js (assuming https://github.com/nodejs/typescript/issues/17).\n\n\n### 💻 Use Cases\n\n1. What do you want to use this for?\n\nCreating an enum like value using standard Object literal syntax with some of the type system benefits that `enum` syntax has.\n\n2. What shortcomings exist with current approaches?\n\n- `typeof Foo[keyof typeof Foo]` is not beginner friendly and is not a nominal type\n\n3. What workarounds are you using in the meantime?\n\nOne workaround is to have a small utility for emulating an enum like nominal type from an object literal ([playground](https://www.typescriptlang.org/play/?ts=5.7.2#code/FAEwpgxgNghgTmABBA9gOwM4BdEFE0CuAtgMoCeRAXIgWgJYCOBSGFARilANzBZkAOSfMQBicFEQA8AFURgAHljBoQGRAG8AvgD5EAXkSyFSlWvWIA2v3H9q2OHTQBzALrVHAMzBw8iTYgB+X2o0MAA3bx5gD1oILDp0PEIpVEwcI0VlVUQAJUgUOBBJe0cnABpEEudEAB9EZLZvbW0ACgxBCGppAEpqcwQYEHQoMksAa0RHRDGwMhQPQzdDCzGXRAAyDUthUgolmbmF6X9-dWBEC8QELAI4NEqOxBg1GDQyHk1gYAV+ApxU7CIADCEn4zzUBh2LTOFwActQAESwhEVEiIkgovCI3CYgDqiNxCOAmm6PB+f0QfEEwNB4P0SVE4ikVLA8xpRDBGAw2h4QA)). The `\"literal\" \u0026 { __key__: val }` trick _works_ but results in _noisey_ types when displayed to the developer (e.g. in an error message)\n","author":{"url":"https://github.com/acutmore","@type":"Person","name":"acutmore"},"datePublished":"2024-12-17T16:29:54.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":8},"url":"https://github.com/60790/TypeScript/issues/60790"}
| 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:6cd9c60c-9d9a-b623-97e5-d2d24187d5a1 |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | EB14:34AAF2:59051B:79A063:6A4D4049 |
| html-safe-nonce | 28ac3140f0cd69edaef29c6f95fd83e1c569665588e5b0f965512b4e0027981f |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJFQjE0OjM0QUFGMjo1OTA1MUI6NzlBMDYzOjZBNEQ0MDQ5IiwidmlzaXRvcl9pZCI6IjE1ODA0Njg5NTIxOTgxMDMxMTQiLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ== |
| visitor-hmac | 94441364d64c190296565542736a2e78b313ac66c7705a298255dccbf985f66a |
| hovercard-subject-tag | issue:2745418842 |
| 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/60790/issue_layout |
| twitter:image | https://opengraph.githubassets.com/7949e535ccc4a992b4d9bcbf4b4dc843373b5273685a5e61c05fd18fac0f83e2/microsoft/TypeScript/issues/60790 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/7949e535ccc4a992b4d9bcbf4b4dc843373b5273685a5e61c05fd18fac0f83e2/microsoft/TypeScript/issues/60790 |
| og:image:alt | 🔍 Search Terms enum, object literal, type-stripping ✅ Viability Checklist This wouldn't be a breaking change in existing TypeScript/JavaScript code This wouldn't change the runtime behavior of exis... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | acutmore |
| hostname | github.com |
| expected-hostname | github.com |
| None | 92571a8944142227b7e19cd10918b1ddd06e5066c1ad5bc7e4769cf6140a87e6 |
| 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 | 56fc8347865a14e2ec811533d68f929cf4e0ec19 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width