Repository: TypeScript
Issue: `as enum` assertion for object literals
Labels: Suggestion,Awaiting More Feedback

### 🔍 Search Terms

enum, object literal, type-stripping

### ✅ Viability Checklist

- [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. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
- [x] This isn't a request to add a new utility type: https://github.com/microsoft/TypeScript/wiki/No-New-Utility-Types
- [x] This feature would agree with the rest of our Design Goals: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals

### Context

While TypeScript already allows declaring runtime enums values:

```ts
export enum Compass {
  N = "N",
  S = "S",
  E = "E",
  W = "W",
}
```

This 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.

An alternative "pure JS" pattern from the TypeScript handbook is:

```ts
export const Compass = {
  N: "N",
  S: "S",
  E: "E",
  W: "W",
} as const;
export type Compass = typeof Compass[keyof typeof Compass];
```

https://www.typescriptlang.org/docs/handbook/enums.html#objects-vs-enums

There are two downsides to this pattern:

- `typeof X[keyof typeof X]` is both verbose and not beginner friendly.
- The type aliases are not nominal, they are a _plain_ union type.

### ⭐ Suggestion

Introduce some new syntax to TypeScript to help with the object-literal-as-enum pattern.

For example would be allowing `as enum`:

```ts
export const Compass = {
  N: "N",
  S: "S",
  E: "E",
  W: "W",
} as enum;
```

(from #59658)

An alternative design could be allowing an `enum` type annotation on `const` variable declarations `export const Compass: enum = {...}`.

This annotation would effectively be the same as writing:

```ts
/** secret internal type - here to get nominal typing */
declare const enum __Compass__ {
    N = "N",
    S = "S",
    E = "E",
    W = "W",
}
export const Compass = {
  N: "N" as __Compass__.N,
  S: "S" as __Compass__.S,
  E: "E" as __Compass__.E,
  W: "W" as __Compass__.W,
} as const;
export type Compass = __Compass__;
```

### Rules

The `enum` annotation would only be permitted for object literals that are
 - in a declarative position
 - have compile-time constant key+values
 - all values are either strings, numbers, or references to constant strings/numbers.

i.e. the object literal would follow very similar rules that are applied to `const enum C {}` syntax

```ts
// @ts-expect-error
foo({ a: "a" } as enum);

class C {
   // @ts-expect-error
   f: enum = {}
}

const o = {
   // @ts-expect-error
   p: Math.random()
} as enum;
```

### Benefits

- The standard JS of an object lieral with the type-checking of an `enum`
- An explicit marker for tools such as linters to provide extra checks (e.g. enum naming conventions)

### Downsides

While 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.

`__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`.


### 📃 Motivating Example

```ts
export const Compass = {
  N: "N",
  S: "S",
  E: "E",
  W: "W",
} as enum;
Object.freeze(Compass);

export function reverse(c: Compass): Compass {
   if (c === Compass.N) return Compass.S;
   if (c === Compass.S) return Compass.N;
   if (c === Compass.E) return Compass.W;
   if (c === Compass.W) return Compass.E;
   throw new Error("unreachable code was run");
}
```

The above module will work out-of-the-box in Node.js (assuming  https://github.com/nodejs/typescript/issues/17).


### 💻 Use Cases

1. What do you want to use this for?

Creating an enum like value using standard Object literal syntax with some of the type system benefits that `enum` syntax has.

2. What shortcomings exist with current approaches?

- `typeof Foo[keyof typeof Foo]` is not beginner friendly and is not a nominal type

3. What workarounds are you using in the meantime?

One 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" & { __key__: val }` trick _works_ but results in _noisey_ types when displayed to the developer (e.g. in an error message)