For AI agents: the complete documentation index is available at /tc39-atlas/en/llms.txt, the full documentation bundle is available at /tc39-atlas/en/llms-full.txt, and this page is available as Markdown at /tc39-atlas/en/proposals/year/pending/proposal-enum.md.
  • English
  • Enums S1

    Proposal details
    Proposal overview

    This proposal introduces an enum declaration to ECMAScript, providing a finite set of constant values for discriminants and flags, compatible with TypeScript's enum syntax. It defines closed, non-extensible enum objects with restricted member value types (Number, String, Symbol, BigInt), self-references, and a Symbol.iterator for iteration.

    Note

    The README below comes from the upstream repository and may contain outdated stage or status metadata. Use the proposal details above as the current source of truth.

    Proposal for ECMAScript enums

    A common and oft-used feature of many languages is the concept of an Enumerated Type, or enum. Enums provide a finite domain of constant values that are regularly used to indicate choices, discriminants, and bitwise flags. As a popular and heavily used feature of TypeScript, this proposal seeks the adoption of a compatible form of TypeScript's enum declaration. Where the syntax or semantics of this proposal differ from that of TypeScript, it is with the full knowledge of the TypeScript development team and represents either a change that TypeScript is willing to adopt, or represents a limited subset of functionality that TypeScript expands upon.

    NOTE: This proposal has been heavily reworked from its prior incarnation, which can now be found at https://github.com/rbuckton/proposal-enum/tree/old

    Status

    Stage: 1
    Champion: Ron Buckton (@rbuckton)

    For more information see the TC39 proposal process.

    Authors

    • Ron Buckton (@rbuckton)

    Motivations

    Many ECMAScript hosts and libraries have various ways of distinguishing types or operations via some kind of discriminant:

    • ECMAScript:
      • [Symbol.toStringTag]
      • typeof
    • DOM:
      • Node.prototype.nodeType (Node.ATTRIBUTE_NODE, Node.CDATA_SECTION_NODE, etc.)
      • DOMException.prototype.code (DOMException.ABORT_ERR, DOMException.DATA_CLONE_ERR, etc.)
      • XMLHttpRequest.prototype.readyState (XMLHttpRequest.DONE, XMLHttpRequest.HEADERS_RECEIVED, etc.)
      • CSSRule.prototype.type (CSSRule.CHARSET_RULE, CSSRule.FONT_FACE_RULE, etc.)
      • Animation.prototype.playState ("idle", "running", "paused", "finished")
      • ApplicationCache.prototype.status (ApplicationCache.CHECKING, ApplicationCache.DOWNLOADING, etc.)
    • NodeJS:
      • Buffer encodings ("ascii", "utf8", "base64", etc.)
      • os.platform() ("win32", "linux", "darwin", etc.)
      • "constants" module (ENOENT, EEXIST, etc.; S_IFMT, S_IFREG, etc.)

    In addition, with the recent adoption of TypeScript Type Stripping in NodeJS, there has been renewed interest in ECMA-262 adopting a compatible form of TypeScript's enum declaration as it is one of the most frequently used TypeScript features not supported in this mode.

    An enum declaration provides several advantages over an object literal:

    • Closed domain by default — The declaration is non-extensible and enum members would be non-configurable and non-writable.
    • Restricted domain of allowed values — Restricts initializers to a subset of allowed JS values (Number, String, Symbol, BigInt).
    • Self-references during definition — Referring to prior enum members of the same enum in the initializer of a subsequent enum member.
    • Static Typing (tooling) — Static type systems like TypeScript use enum declarations to discriminate types, provide documentation in hovers, etc.
    • ADT enums (future) — Potential future support for Algebraic Data Type enums (i.e., "discriminated unions").
    • Decorators (future) — Potential future support for enum-specific Decorators.
    • Auto-Initializers (future) — Potential future support for auto-initialized enum members.
    • "shared" enums (future) — Potential future support for a shared enum with restrictions on inputs to align with shared struct

    Prior Art

    Syntax

    While a Stage 1 proposal is generally encouraged to avoid specific syntax, it is a stated goal of this proposal to introduce an enum declaration whose syntax is compatible with TypeScript's. As such, the syntax of this proposal is restricted to a subset of TypeScript's enum.

    // enum declarations
    
    enum Numbers {
      zero = 0,
      one = 1,
      two = 2,
      three = 3,
      alsoThree = three // self reference
    }
    
    enum PlayState {
      idle = "idle",
      running = "running",
      paused = "paused"
    }
    
    enum Symbols {
      alpha = Symbol("alpha"),
      beta = Symbol("beta") 
    }
    
    enum Named {
      Identifier = 0,
      "string name" = 1,
    }
    
    // Accessing enum values:
    let x = Numbers.three;
    let y = Named["string name"];
    
    // Iteration, replaces TypeScript enum "Reverse mapping" (formatting,
    // debugging, diagnostics, etc):
    for (const [key, value] of Numbers) ...

    Semantics

    While a Stage 1 proposal is generally encouraged to avoid specific semantics, it is a stated goal of this proposal to introduce an enum declaration whose semantics are compatible with TypeScript's. As such, the semantics of this proposal are intended to align with that of TypeScript's enum where possible.

    Enum Declarations

    Enum declarations consist of a finite set of enum members that define the names and values for each member of the enum. These results are stored as properties of an enum object. An enum object is an ordinary object whose [[Prototype]] is null. Each enum member defines a property on the enum object.

    In addition, an enum object contains a Symbol.iterator method that yields a [key, value] entry for each declared enum member, in document order. To explain the semantics of the Symbol.iterator method, an enum object may require an [[EnumMembers]] internal slot.

    Enum Members

    Enum members define the set of values that belong to the enum's domain. Each enum member consists of a name and an initializer that defines the value associated with that name. Enum members are [[Writable]]: false and [[Configurable]]: false.

    Enum Member Names

    Enum member names are currently restricted to IdentifierName and StringLiteral, as those are the only member names currently supported by TypeScript's enum. We may opt to expand this to allow other member names like ComputedPropertyName in the future, if there is sufficient motivation.

    An enum may not have duplicate member names. We may opt to introduce restrictions on member names like constructor if we deem it necessary to support ADT enums in the future.

    If an enum member name shares the same string value as the name of the enum itself, it shadows the enum declaration name within the enum body.

    Enum Member Initializers

    An enum member's initializer is restricted to a subset of ECMAScript values (i.e., Number, String, Symbol, and BigInt). This limitation allows us to consider future support for Algebraic Data Types (ADT) in enums without the possibility of conflicting with something like an enum member whose value is a function.

    An IdentifierReference in an enum member initializer may refer to the name of a prior declaration, and to the enum itself (much like a class).

    API

    Aside from the enum declaration itself, there is no other proposed API.

    An enum declaration will have a Symbol.iterator method that can be used to iterate over the key/value pairs of the enum's members.

    Desugaring

    An enum declaration could potentially be implemented as the following desugaring:

    enum E {
      A = 1,
      B = 2,
      C = A | E.B,
    }
    
    let E = (() => {
      let E = Object.create(null), A, B, C;
      Object.defineProperty(E, "A", { value: A = 1 });
      Object.defineProperty(E, "B", { value: B = 2 });
      Object.defineProperty(E, "C", { value: C = A | E.B });
      Object.defineProperty(E, Symbol.iterator, {
        value: function* () {
          yield ["A", E.A];
          yield ["B", E.B];
          yield ["C", E.C];
        }
      });
      Object.defineProperty(E, Symbol.toStringTag, { value: "E" });
      Object.preventExtensions(E);
      return E;
    })();

    Other Considerations

    enum Expressions

    While ECMAScript has both statement and expression forms for class and function declarations, this proposal does not currently support enum expressions. There is no concept of an enum expression in TypeScript, though we may consider enum expressions for ECMAScript if there is sufficient motivation.

    export/export default

    An enum declaration would support both export and export default, much like class.

    Interaction with Shared Structs

    In general, this proposal hopes to align enum member values to those that can be shared with a shared struct, however it is important to note that a Symbol-valued enum that uses Symbol() and not Symbol.for() would produce values that cannot be reconciled between two Agents. In addition, ADT enums may need to contain non-shared data, such as in an Option or Result enum. As such, this proposal may seek to introduce a shared enum declaration that further restricts allowed inputs.

    Differences from TypeScript

    There are several differences in the enum declaration for this proposal compared to TypeScript's enum:

    Auto-Initializers

    TypeScript's enum supports auto-initialization of enum members:

    enum Numbers {
      zero, // 0
      one,  // 1
      two   // 2
    }

    However, this behavior is contentious amongst some TC39 delegates and has been removed from this proposal. The main concern that has been raised is that introducing new auto-initialized enum members in the middle of an existing enum has the potential to be a versioning issue in packages, and that such behavior should be harder to reach for, as opposed to being the default behavior. However, even if this capability is not supported, TypeScript will continue to support auto-initialization due to its frequent use within the developer community, but would emit explicit initializers to JavaScript. It is possible that another form of auto-initialization may be introduced in the future that could be utilized by both TypeScript and ECMAScript. For more information, please refer to the Auto-Initializers topic in the Future Directions section.

    Declaration Merging

    TypeScript (as of v5.8) allows enum declarations to merge with other enum (and namespace) declarations with the same name. This is not a desirable feature for ECMAScript enums and will not be supported. TypeScript is considering deprecating this feature in TypeScript 6.0.

    Reverse Mapping

    TypeScript currently supports reverse-mapping enum values back to enum member names using E[value], but only for Number values. This limitation is intended to avoid collisions for String-valued enum members that could potentially overwrite other members. While this information is invaluable for debugging, diagnostics, formatting, and serialization, it is far less frequently used compared to enum on the whole.

    To avoid this inconsistency, we instead propose using iteration (by way of the Symbol.iterator built-in symbol) to cover the "reverse mapping" case:

    enum E {
      A = 0,
      B = "A",
    }
    
    for (const [key, value] of E) {
      console.log(`${key}: ${value}`);
    }
    
    // prints:
    //  A: 0
    //  B: A
    
    const keyForA = E[Symbol.iterator]().find(([, value]) => value === "A")[0]
    console.log(keyForA); // prints: B

    If adopted, TypeScript would add support for Symbol.iterator while eventually deprecating existing reverse mapping support.

    const enum

    TypeScript supports the concept of a const enum declaration, which is similar to a normal enum declaration except that enum values are inlined into their use sites. Implementations are free to optimize as they see fit, and it's entirely reasonable that an implementation may eventually support similar inlining on a normal enum declaration. As the current const enum requires whole program knowlege and a type system, we believe it should remain a TypeScript-specific capability at this time.

    Symbol values

    TypeScript does not currently support Symbol values for enums, but would add support if this feature were to be adopted.

    BigInt values

    TypeScript does not currently support BigInt values for enums, but would add support if this feature were to be adopted.

    export default

    TypeScript does not currently support export default on an enum, but would add support if this feature were to be adopted.

    Future Directions

    While this proposal is intended to be rather limited in scope, there are several potential areas for future advancement in the form of follow-on proposals:

    Algebraic Data Type (ADT) Enums

    Algebraic Data Type (ADT) enums act like a discriminated union of structured types. ADT enum members describe a constructor function that produces an object with a discriminant property. A future enhancement of an ECMAScript enum declaration might support ADT enums in conjunction with Extractors and Pattern Matching:

    enum Option {
      Some(value),
      None()
    }
    
    const opt = Option.Some(123);
    match (opt) {
      Option.Some(let value): console.log(value);
      Option.None(): console.log("<no value>");
    }
    
    
    enum Result {
      Ok(value),
      Error(reason)
    }
    
    function try_(cb) {
      try {
        return Result.Ok(_cb());
      } catch (e) {
        return Result.Error(e);
      }
    }
    
    const res = try_(() => obj.doWork());
    match (res) {
      Result.Ok(let value): ...;
      Result.Error(let reason): ...;
    }

    Here, Option.Some might describe a "constructor" function that produces an object discriminated by either a well-known symbol field or merely by its [[Prototype]], such that Option.Some(0) instanceof Option.Some is true. ADT enum members could also describe more complex shapes through the use of binding patterns, such as:

    enum Geometry {
      Point({ x, y }),
      Line(p1, p2),
    }
    
    const p1 = Geometry.Point({ x: 0, y: 1 });
    p1[0].x; // 0
    p1[0].y; // 1
    
    const p2 = Geometry.Point({ x: 2, y: 3 });
    const l = Geometry.Line(p1, p2);
    l[0] === p1; // true
    
    const printGeom = geom => match (geom) {
      Geometry.Point({ let x, let y }): console.log(`Point({ x: ${x}, y: ${y} })`);
      Geometry.Line(let p1, let p2): console.log(`Line(${printGeom(p1)}, ${printGeom(p2)})`);
    };
    
    printGeom(p1); // Point({ x: 0, y: 1 })
    printGeom(l); // Line(Point({ x: 0, y: 1 }), Point({ x: 2, y: 3 }))

    ADT enum members may also need a mechanism to implement prototypal or static methods on the enum, which is one reason why we prefer Symbol.iterator to describe the domain of an enum vs. something like Object.entries().

    Decorators

    In the future we may opt to extend support for Decorators to enum declarations to support serialization/deserialization, formatting, and FFI scenarios:

    @WasmType("u1")
    enum Role {
      @Alias(["user", "person"], { ignoreCase: true })
      user = 1,
      @Alias(["admin", "administrator"], { ignoreCase: true })
      admin = 2,
    }

    Auto-Initializers

    While this proposal does not support TypeScript's auto-initialization semantics, we may consider introducing an alternative syntax in a future proposal, such as the of clause described in an older version of this proposal:

    enum Numbers of Number { zero, one, two, three }
    Numbers.zero; // 0
    
    enum Colors of String { red, green, blue }
    Colors.red; // "red"

    Or through some form of statically recognizable syntax:

    auto enum Numbers { zero, one, two, three }
    Numbers.zero; // 0

    History

    TODO

    The following is a high-level list of tasks to progress through each stage of the TC39 proposal process:

    Stage 1 Entrance Criteria

    • Identified a "champion" who will advance the addition.
    • Prose outlining the problem or need and the general shape of a solution.
    • Illustrative examples of usage.
    • High-level API.

    Stage 2 Entrance Criteria

    Stage 3 Entrance Criteria

    Stage 4 Entrance Criteria

    • Test262 acceptance tests have been written for mainline usage scenarios and merged.
    • Two compatible implementations which pass the acceptance tests: [1], [2].
    • A pull request has been sent to tc39/ecma262 with the integrated spec text.
    • The ECMAScript editor has signed off on the pull request.

    Prior Discussion