Proposal: Merge enum and const enum features
See original GitHub issueCurrently 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:
- merge both enumerable types into one: using
const enum
on constant index expressions andenum
on variable enum expressions. - always return a
number
value when astring
index expression is used on anenum
. - allow for both,
number
andstring
values, to be used as index argument types on the proposed mergedenum
type.
This would solve some major problems with the current design. Currently …
enum
values cannot be converted to theirnumber
equivalent, only to theirstring
representation.const enum
values cannot be converted to theirstring
representation, only to theirnumber
equivalent. (This blocksconst enum
values from being used to serialize configuration settings into a commonly expected string value representation.)const enum
values can not be converted toenum
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 …
- whether to use a
const enum
value or anenum
in the generated code, - 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:
- Using a
string
indexer expression on anenum
shall return anumber
if the type of the L-value isn’t the enum itself:enum[:string] => number
. - Using a
string
indexer expression on anenum
shall return anenum
if the type of the L-value is the enum itself:enum[:string] => E
. - Using a
number
indexer expression on anenum
shall return astring
if the type of the L-value isn’t the enum itself:enum[:number] => string
. - Using a
number
indexer expression on anenum
shall return anenum
if the type of the L-value is the enum itself:enum[:number] => E
. - Using a
enum
indexer expression on the sameenum
type shall return anumber
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.) - Using a
enum
indexer expression on the sameenum
type shall return anenum
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.) - Using a constant
string
ornumber
indexer expression on anenum
shall emit a constantnumber
value in JavaScript (i.e., this is theconst enum
equivalent). - Using a variable
string
ornumber
indexter expression on anenum
shall emit an array indexer expression in JavaScript (i.e., this is theenum
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:
- Created 8 years ago
- Reactions:6
- Comments:23 (8 by maintainers)
Top GitHub Comments
Good call.
The
toNumber()
member function I used just for visualization. It may alternatively be called__toNumber()
.Or a Symbol may be used instead.
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:
After my ping, the same query now returns the correct result:
But, please, let’s not get off topic here dealing with some GitHub peculiarity.