• Stars
    star
    214
  • Rank 184,678 (Top 4 %)
  • Language
    HTML
  • License
    BSD 3-Clause "New...
  • Created over 7 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Proposal for ECMAScript enums

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.

Status

Stage: 0
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.)

Prior Art

Syntax

// enum declarations

// Each auto-initialized member value is a `Number`, auto-increments values by 1 starting at 0
enum Numbers {
  zero,
  one,
  two,
  three,
  alsoThree = three
}

// Each auto-initialized member value is a `Number`, auto-increments values by 1 starting at 0
enum Colors of Number {
  red,
  green,
  blue
}

// Each auto-initialized member value is a `String` whose value is the SV of its member name.
enum PlayState of String {
  idle,
  running,
  paused
}

// Each auto-initialized member value is a `Symbol` whose description is the SV of its member name.
enum Symbols of Symbol {
  alpha,
  beta
}

enum Named {
  identifierName,
  "string name",
  [expr]
}

// Accessing enum values:
let x = Color.red;
let y = Named["string name"];

Semantics

Well-Known Symbols

This proposal introduces three new well-known symbols that are used with enums:

Specification Name [[Description]] Value and Purpose
@@toEnum "Symbol.toEnum" A method that is used to derive the value for an enum member during EnumMember evaluation.
@@formatEnum "Symbol.formatEnum" A method of an enum object that is used to convert a value into a string representation based on the member names of the enum. Called by Enum.format.
@@parseEnum "Symbol.parseEnum" A method of an enum object that is used to convert a member name String into the value represented by that member of the enum. Called by Enum.parse.

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 with an [[EnumMembers]] internal slot, and whose [[Prototype]] is null.

Automatic Initialization

If an enum member does not supply an Initializer, the value of that enum member will be automatically initialized:

enum DaysOfTheWeek {
  Sunday, // 0
  Monday, // 1
  Tuesday, // 2
  // etc.
}

Auto-initialization can be controlled through the use of an of clause:

enum DaysOfTheWeek of Symbol {
  Sunday, // Symbol("Sunday")
  Monday, // Symbol("Monday")
  Tuesday, // Symbol("Tuesday")
  // etc.
}

Constructors for built-in primitive values like String, Number, Symbol, and BigInt are defined to have a @@toEnum method that is used during evaluation to select an auto-initialization value. If the expression in the of clause does not have a @@toEnum method, it will instead be called directly. This allows constructors for built-ins to be used in the of clause without adding a niche constructor overload. This also allows developers to control the behavior of of if its expression is an ECMAScript class which cannot be called directly.

Evaluation

Before we evaluate the enum members of the declaration, we first choose a mapper Object. If the enum declaration has an of clause, the mapper is the result of evaluating that clause. Otherwise, mapper uses the default value of %Number%.

From the mapper we then get an enumMap function from mapper[@@toEnum]. If enumMap is undefined, then we set enumMap to mapper and mapper to undefined.

To support auto-initialization we also define two variables (both initialized to undefined):

  • value: Stores the result of the last explicit or automatic initialization.
  • autoValue: Stores the result of the last automatic initialization only.

As we evaluate each enum member, we perform the following steps:

  1. Derive key from the enum member's name.
  2. If the enum member has an Initializer, then
    1. Set value to be the result of evaluating Initializer.
  3. Else,
    1. Set autoValue to be ? Call(enumMap, mapper, Β« key, value, autoValue Β»)
    2. Set value to be autoValue
  4. Add key to the List of member names in the [[EnumMembers]] internal slot of the enum object.
  5. Define a new property on the enum object with the name key and the value value, and the attributes [[Writable]]: false, [[Configurable]]: false, and [[Enumerable]]: true.

In addition, the following additional properties are added to enum objects:

  • A @@parseEnum property whose value is a Function that returns the value of the enum member whose name corresponds to the provided argument.
    • This member is [[Writable]]: false, [[Configurable]]: true, and [[Enumerable]]: false.
  • A @@formatEnum property whose value is a Function that returns the name of the first enum member whose value corresponds to the provided argument.
    • This member is [[Writable]]: false, [[Configurable]]: true, and [[Enumerable]]: false.
  • A @@toStringTag property whose value is "Enum".
    • This member is [[Writable]]: false, [[Configurable]]: true, and [[Enumerable]]: false.
  • An @@iterator property whose value is a Function that returns an iterator for this enum's [[EnumMembers]] internal slot where each yielded value is a two-element array containing the enum member name at index 0 and the enum member value at index 1.
    • This member is [[Writable]]: false, [[Configurable]]: true, and [[Enumerable]]: false.

Finally, the enum object is made non-extensible.

Properties of the Number Constructor

The Number constructor would have an additional @@toEnum method with parameters key, value, and autoValue that performs the following steps:

  1. If Type(value) is not Number, set value to autoValue.
  2. If value is undefined, return 0.
  3. Otherwise, return value + 1.

Properties of the String Constructor

The String constructor would have an additional @@toEnum method with parameters key, value, and autoValue that performs the following steps:

  1. Let propKey be ToPropertyKey(key).
  2. If Type(propKey) is Symbol, return propKey.[[Description]].
  3. Otherwise, return propKey.

Properties of the Symbol Constructor

The Symbol constructor would have an additional @@toEnum method that parameters key, value, and autoValue that performs the following steps:

  1. Let propKey be ToPropertyKey(key).
  2. If Type(propKey) is Symbol, let description be propKey.[[Description]].
  3. Otherwise, let description be propKey.
  4. Return a new unique Symbol whose [[Description]] value is description.

Properties of the BigInt Constructor

The BigInt constructor would have an additional @@toEnum method with parameters key, value, and autoValue that performs the following steps:

  1. If Type(value) is not BigInt, set value to autoValue.
  2. If value is undefined, return 0n.
  3. Otherwise, return value + 1n.

API

To make it easier to work with enums, an Enum object is added to the global scope, with the following methods:

  • Enum.keys(E) - Returns an Iterator for the member names in the [[EnumMembers]] internal slot of E.
  • Enum.values(E) - Returns an Iterator for the value on E of each member in the [[EnumMembers]] internal slot of E.
  • Enum.entries(E) - Returns an Iterator for each member in the [[EnumMembers]] internal slot of E, where each result is two-element array containing the enum member name at index 0 and the enum member value at index 1.
  • Enum.has(E, key) - Returns true if the the [[EnumMembers]] internal slot of E contains key.
  • Enum.hasValue(E, value) - Returns true if the [[EnumMembers]] internal slot of E contains a member whose value on E corresponds to value.
  • Enum.getName(E, value) - Gets the first name in the [[EnumMembers]] internal slot of E whose value on E corresponds to value.
  • Enum.format(E, value) - Calls the @@formatEnum method of E with argument value.
  • Enum.parse(E, value) - Calls the @@parseEnum method of E with argument value.
  • Enum.create(members) - Creates an enum object using the property keys and values of members as the enum members for the new enum.
  • Enum.flags(descriptor) - A built-in decorator that modifies the enum object in the following ways:
    • The auto-increment behavior is changed to shift the current auto-increment value left by 1.
    • The @@parseEnum method is modified to parse a comma-separated string and OR the resulting values together. If no corresponding name can be found and the name can be successfully coerced to a number, that number is OR'ed with the result.
    • The @@formatEnum method is modified to convert a bitwise combination of flag values into a comma separated string of corresponding names. If no corresponding name can be found, the SV of the bits is appended to the string.
let Enum: {
  keys(E: object): IterableIterator<string | symbol>;
  values(E: object): IterableIterator<any>;
  entries(E: object): IterableIterator<[string | symbol, any]>;
  has(E: object, key: string | symbol): boolean;
  hasValue(E: object, value: any): boolean;
  getName(E: object, value: any): string | undefined;
  format(E: object, value: any): string | symbol | undefined;
  parse(E: object, value: string): any;
  create(members: object): object;
  flags(descriptor: EnumDescriptor): EnumDescriptor;
};

Examples

enum Numbers { zero, one, two, three, }

typeof Numbers.zero === "number"
Numbers.zero === 0
Enum.getName(Numbers, 0) === "zero"
Enum.parse(Numbers, "zero") === 0

// ... strings, ...
enum HttpMethods of String { GET, PUT, POST, DELETE }

typeof HttpMethods.GET === "string"
HttpMethods.GET === "GET"

// ... booleans, ...
enum Switch { on = true, off = false }

typeof Switch.on === "boolean";
Switch.on === true

// ... symbols, ...
enum AlphaBeta of Symbol { alpha, beta }

typeof AlphaBeta.alpha === "symbol";
AlphaBeta.alpha.toString() === "Symbol(AlphaBeta.alpha)";

// ... or a mix.
enum Mixed {
    number = 0,
    string = "",
    boolean = false,
    symbol = Symbol()
}

// Enums can be exported:
export enum Zoo { lion, tiger, bear };
export default enum { up, down, left, right };

// You can test for name membership using `Enum.has()`
Enum.has(Numbers, "one") === true
Enum.has(Numbers, "five") === false

// You can test for value membership using `Enum.hasValue()`:
Enum.hasValue(Numbers, 0) === true
Enum.hasValue(Numbers, 9) === false

// You can convert enums between names and values using 
// `Enum.parse` and `Enum.format`, respectively.
enum AToB {
    a = "b",
    b = "a",
}

Enum.parse(AToB, "a") === AToB.a
Enum.parse(AToB, "b") === AToB.b

Enum.getName(AToB, AToB.a) === "b"
Enum.getName(AToB, AToB, b) === "a"

// `Enum.create()` lets you create a new enum programmatically:
const SyntaxKind = Enum.create({ 
  identifier: 0, 
  number: 1, 
  string: 2 
});

typeof SyntaxKind.identifier === "number";
SyntaxKind.identifier === 0;


// The `Enum.flags` decorator lets you declare a enum containing 
// bitwise flag values:
@Enum.flags
enum FileMode {
  none
  read,
  write,
  exclusive,
  readWrite = read | write,
}

FileMode.none === 0x0
FileMode.readOnly === 0x1
FileMode.readWrite === 0x3

// `Enum.flags` modifies @@formatEnum:
Enum.format(FileMode, FileMode.readWrite | FileMode.exclusive) === "readWrite, exclusive"

// `EnumFlags` modifies @@parseEnum:
Enum.parse(FileMode, "read, 4") === 5 // FileMode.read | FileMode.exclusive

Remarks

  • Why default to Number?
    • In prior discussions, there are some preferences for the use of symbol values, while there are other preferences that include the use of strings and numbers. This approach gives you the ability to support both scenarios through the optional of clause.

    • The auto-increment behavior of enums in other languages is used fairly regularly. Auto- increment is not viable if String or Symbol were the default type.

    • We could consider switching on auto-increment if the prior declaration was initialized with a Number, but then you would have confusion over declarations like this:

      enum Mixed {
        first, // If this is a Symbol by default...
        second = 1,
        third // ...is this a Symbol or the Number `2`?
      }

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

More Repositories

1

reflect-metadata

Prototype for a Metadata Reflection API for ECMAScript
TypeScript
2,999
star
2

prex

Async coordination primitives and extensions on top of ES6 Promises
TypeScript
149
star
3

grammarkdown

Markdown-like DSL for defining grammatical syntax for programming languages.
HTML
118
star
4

iterable-query

Query API over JavaScript (ECMAScript) Iterators
TypeScript
66
star
5

proposal-shorthand-improvements

A proposal to introduce new shorthand assignment forms for ECMAScript object literals
HTML
64
star
6

obs-remote

A touch-friendly desktop application designed for tablets that can be used to control OBS Studio remotely using 'obs-websocket'
TypeScript
36
star
7

proposal-functional-operators

Proposal for the additon of functional operators to ECMAScript
HTML
23
star
8

proposal-regexp-features

Proposal to investigate additional language features for ECMAScript Regular Expressions
JavaScript
19
star
9

asyncjs

Asynchronous coordination primatives for TypeScript and JavaScript
JavaScript
13
star
10

proposal-statements-as-expressions

Proposal to explore Statements as Expressions
JavaScript
12
star
11

proposal-struct

Value Types (i.e., 'struct') for ECMAScript
JavaScript
12
star
12

regexp-features

A comparison of Regular Expression features in various languages and libraries.
JavaScript
9
star
13

tsserver-live-reload

VSCode Extension to automatically restart the TypeScript Language Server when it changes.
TypeScript
8
star
14

graphmodel

JavaScript library for modeling directed graphs
TypeScript
7
star
15

sourcemap-visualizer

Source Map parser and visualizer
TypeScript
6
star
16

service-composition

Decorator-based dependency injection library.
TypeScript
5
star
17

iterable-query-linq

LINQ-like syntax via ECMAScript tagged templates
TypeScript
5
star
18

regexp-match-indices

Polyfill for the RegExp Match Indices proposal
TypeScript
4
star
19

posh-vsdev

Configures PowerShell to run as a Visual Studio Developer Command Prompt
PowerShell
4
star
20

promisejs

Promise/Future-based asynchrony in javascript
JavaScript
3
star
21

equatable

A low-level API for defining equality.
JavaScript
3
star
22

typedoc-plugin-linkrewriter

A TypeDoc plugin for rewriting links in markdown
TypeScript
2
star
23

chardata

Unicode character data
TypeScript
2
star
24

ecmarkup-vscode

ecmarkup language extensions for Visual Studio Code
HTML
2
star
25

fork-pipe

Fork/join stream processing.
TypeScript
2
star
26

chardata-generator

Generates TypeScript source files from the Unicode Character Database
TypeScript
1
star
27

typedoc-plugin-biblio

A TypeDoc plugin to support references to externally hosted documentation.
TypeScript
1
star
28

grammarkdown-server

Grammarkdown Language Server for VSCode
TypeScript
1
star
29

collection-core

Symbol-based augmentation API for interacting with collection-types in ECMAScript.
JavaScript
1
star
30

decorators

Annotations+Decorators TC39 proposal
HTML
1
star
31

rbuckton.github.io

1
star
32

grammarkdown-syntax

Grammarkdown Language Client for VSCode
JavaScript
1
star
33

proposal-regexp-conditionals

JavaScript
1
star
34

ecmascript-mirrors

Prototype and Proposal for a Mirrors API for ECMAScript, for use in tandem with Decorators.
TypeScript
1
star