π Search Terms
enum, object literal, type-stripping
β
Viability Checklist
Context
While TypeScript already allows declaring runtime enums values:
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 is passed.
An alternative "pure JS" pattern from the TypeScript handbook is:
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:
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:
/** 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-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
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 nodejs/typescript#17).
π» Use Cases
- 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.
- What shortcomings exist with current approaches?
typeof Foo[keyof typeof Foo] is not beginner friendly and is not a nominal type
- 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). The "literal" & { __key__: val } trick works but results in noisey types when displayed to the developer (e.g. in an error message)
π Search Terms
enum, object literal, type-stripping
β Viability Checklist
Context
While TypeScript already allows declaring runtime enums values:
This is not standard JavaScript, and does not work in Node.js unless
--experimental-transform-typesis passed.An alternative "pure JS" pattern from the TypeScript handbook is:
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.β Suggestion
Introduce some new syntax to TypeScript to help with the object-literal-as-enum pattern.
For example would be allowing
as enum:(from #59658)
An alternative design could be allowing an
enumtype annotation onconstvariable declarationsexport const Compass: enum = {...}.This annotation would effectively be the same as writing:
Rules
The
enumannotation would only be permitted for object literals that arei.e. the object literal would follow very similar rules that are applied to
const enum C {}syntaxBenefits
enumDownsides
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
enumsyntax such as self-reference during construction.__proto__: nullis currently not supported #38385 making it difficult to avoid object literals from inheriting non-enum properties resulting in false positives withkey in MyEnum.π Motivating Example
The above module will work out-of-the-box in Node.js (assuming nodejs/typescript#17).
π» Use Cases
Creating an enum like value using standard Object literal syntax with some of the type system benefits that
enumsyntax has.typeof Foo[keyof typeof Foo]is not beginner friendly and is not a nominal typeOne workaround is to have a small utility for emulating an enum like nominal type from an object literal (playground). The
"literal" & { __key__: val }trick works but results in noisey types when displayed to the developer (e.g. in an error message)