question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Proposal: Merge enum and const enum features

See original GitHub issue

Currently there are two enumerable types specified in TypeScript: enum and const enum.

Both of them aren’t bijective, i.e. they both don’t provide the ability to cast them arbitrarily and unambiguously between string, number and enum.

After discussing this on Gitter with @jsobell and @masaeedu I’d like to propose the following:

  1. merge both enumerable types into one: using const enum on constant index expressions and enum on variable enum expressions.
  2. always return a number value when a string index expression is used on an enum.
  3. allow for both, number and string values, to be used as index argument types on the proposed merged enum type.

This would solve some major problems with the current design. Currently …

  1. enum values cannot be converted to their number equivalent, only to their string representation.
  2. const enum values cannot be converted to their string representation, only to their number equivalent. (This blocks const enum values from being used to serialize configuration settings into a commonly expected string value representation.)
  3. const enum values can not be converted to enum values and vice versa.

The key to providing the missing functionality is type inference. At compile time, TSC is able to tell whether a string or a number value is provided in an enum index argument. It’s also able to tell whether the index expression is a constant or a variable.

Given these prerequisites the compiler can easily decide …

  1. whether to use a const enum value or an enum in the generated code,
  2. whether to return the numerical value or string representation of the enum in the generated code.

(Variable index expressions of type any should be regarded as values of type string.This will result in maximum compatibility.)

So I’m proposing the following:

  1. Using a string indexer expression on an enum shall return a number if the type of the L-value isn’t the enum itself: enum[:string] => number.
  2. Using a string indexer expression on an enum shall return an enum if the type of the L-value is the enum itself: enum[:string] => E.
  3. Using a number indexer expression on an enum shall return a string if the type of the L-value isn’t the enum itself: enum[:number] => string.
  4. Using a number indexer expression on an enum shall return an enum if the type of the L-value is the enum itself: enum[:number] => E.
  5. Using a enum indexer expression on the same enum type shall return a number if the type of the L-value isn’t the enum itself: enum[:enum] => number. (During transpilation, this operation is practically redundant and may be cancelled from the output.)
  6. Using a enum indexer expression on the same enum type shall return an enum if the type of the L-value is the enum itself: enum[:enum] => E. (During transpilation, this operation is practically redundant and may be cancelled from the output.)
  7. Using a constant string or number indexer expression on an enum shall emit a constant number value in JavaScript (i.e., this is the const enum equivalent).
  8. Using a variable string or number indexter expression on an enum shall emit an array indexer expression in JavaScript (i.e., this is the enum equivalent).


#### So, given the above prerequisites, here are two examples, hopefully shedding some light upon my proposal:
##### (A) Example illustrating type inference being used at compile time to decide whether to return a `number` or a `string` value from an enum:

The following TypeScript code:

// TypeScript
enum E {a, b, c}
const iN :number = 0, iS1 :string = "b", iS2 :string = "2";

// assigning to primitive types
const s :string = E[iN];
const n1 :number = E[iS1];   // special treatment because index type is string
const n2 :number = E[iS2];   // special treatment because index type is string

// assigning to enum
const es :E = E[iN];
const en1 :E = E[iS1];   // special treatment because index type is string
const en2 :E = E[iS2];   // special treatment because index type is string

… should result in the following JavaScript compilation result:

// JavaScript
var E;
(function (E) {
    E[E["a"] = 0] = "a";
    E[E["b"] = 1] = "b";
    E[E["c"] = 2] = "c";
    E.toNumber = function (e)
                 { return IsNaN(e) ? E[e] : E.isOwnProperty(e) ? +e : undefined; }
})(E || (E = {}));

var iN = 0, iS1 = "b", iS2 = "2";

var s = E[iN];   // === "a"
var n1 = E.toNumber(iS1);   // === 1
var n2 = E.toNumber(iS1);   // === 2

var es = E[iN] ? iN : undefined;  // E[iN] returns a ?:string. --- result == 0
var en1 = E.toNumber(iS1);   // == 1
var en2 = E.toNumber(iS1);   // == 2

##### (B) Example illustrating `const enum` and `enum` being merged into one single type:

The following TypeScript code:

// TypeScript
enum E {a, b, c}

let e :E;
const n :number = 1;
const s :string ="c";

// constant assignments
e = E.a;
e = E[2];
e = E["a"];

// variable assignments
e = E[n];
e = E[E[n]];
e = E[s];

… should result in the following JavaScript compilation result:

// JavaScript
var E;
(function (E) {
    E[E["a"] = 0] = "a";
    E[E["b"] = 1] = "b";
    E[E["c"] = 2] = "c";
    E.toNumber = function (e)
                 { return IsNaN(e) ? E[e] : E.isOwnProperty(e) ? +e : undefined; }
})(E || (E = {}));

var e;
var n = 1;
var s = "c";

e = 0;
e = 2;
e = 0;

e = E[n] ? n : undefined;   // E[n] returns a ?:string. --- result == 1
e = E[n] ? n : undefined;   // The outer indexing operation is redundant and may be cancelled. --- result == 1
e = E.toNumber(s);     // == 2

Some of the `E[n] ? n : undefined` constructs may be cancelled if runtime boundary checking isn't wanted/desired (new boolean compiler option?). So `e = E[n] ? n : undefined;` may then be transpiled to `e = n;`.
In the discussion on Gitter I learned about #3507, #592. Yet I feel the above proposal will add value to the ongoing discussion on improving the `enum` types.

Issue Analytics

  • State:open
  • Created 8 years ago
  • Reactions:6
  • Comments:23 (8 by maintainers)

github_iconTop GitHub Comments

1reaction
SetTrendcommented, Jan 28, 2016

Good call.

The toNumber() member function I used just for visualization. It may alternatively be called __toNumber().

Or a Symbol may be used instead.

0reactions
SetTrendcommented, Jan 7, 2019

I don’t wish to bloat this off-topic-issue, but this morning a query searching for issues raised by me returned the following result:

1

After my ping, the same query now returns the correct result:

2

But, please, let’s not get off topic here dealing with some GitHub peculiarity.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Merge TypeScript Enums - DEV Community ‍ ‍
There is no native support for merging 2 or more enums in typescript but its possible with a combination of type aliases and...
Read more >
How to merge two enums in TypeScript - Stack Overflow
Using an enum is simple: just access any member as a property off of the enum itself, and declare types using the name...
Read more >
TypeScript string enums, and when and how to use them
The enums keyword offers a way for us to define a finite set of values — usually as named constants in a strongly...
Read more >
C++ Feature Proposal: allow "enum class" - Google Groups
What: Allow enum to be marked as classes. ... interfaces and generally speaking reduces headers inclusions. ... opinion and no strong use cases....
Read more >
Implementing Enums in Golang - Level Up Coding
An enum group related constants together in one type. Enums are a powerful feature with a wide range of uses. · What is...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found