Typescript: Allow enums of types other than number

Created on 19 Nov 2014  ·  123Comments  ·  Source: microsoft/TypeScript

I'm reopening this issue, because it was closed with the move from codeplex, and doesn't seem to have been re-opened. https://typescript.codeplex.com/workitem/1217

I feel like this is very important for a scripting language.-- Especially for objects that are passed back and forth over web service calls.-- People generally don't use integer based enums in json objects that are sent over the wire, which limits the usefulness of the current enum implementation quite a bit.

I would like to re-propose the original contributor's suggestions verbatim.

Committed Fixed Suggestion

Most helpful comment

Things have shifted a bit with the introduction of string literal types. Picking up from @isiahmeadows 's proposal above https://github.com/Microsoft/TypeScript/issues/1206#issuecomment-121926668 , considering a restricted "string-only" enum that would behave as follows

// Please bikeshed syntax!
enum S: string {
  A,
  B = "X",
  C
}

would be _exactly_ equivalent to this code:

namespace S {
  export const A: "A" = "A";
  export type A = "A";
  export const B: "X" = "X";
  export type B = "X";
  export const C: "C" = "C";
  export type C = "C";

  // Imagine this were possible
  [s: string]: S;
}
type S = S.A | S.B | S.C;

the equivalent code starting with const enum S: string { would be identical in the type system, but not have any emit. I'll sketch up an implementation in the next few weeks and we'll see how it looks.

All 123 comments

From the original proposal

As in the title, and as discussed extensively here, it would be very helpful to allow enums of types other than number. At the very least, if allowing arbitrary types is too much work, string enums should be allowed. The current codegen for enums actually works with strings as-is, the compiler just flags errors.

Consider:

enum Dog{
Rover = 'My Dog',
Lassie = 'Your Dog'
}

alert(Dog.Rover);

As of 0.9, this gets to compiled to:

var Dog;
(function (Dog) {
Dog[Dog["Rover"] = 'My Dog'] = "Rover";

Dog[Dog["Lassie"] = 'Your Dog'] = "Lassie";
})(Dog || (Dog = {}));

alert(Dog.Rover);

which is 100% functioning JavaScript that works as you expect it to.

In addition, the whole concept of "overloads on constants" would be a lot cleaner with a string-based enum:

interface Document {
createElement(tagName: TagName): HTMLCanvasElement;
}

enum TagName{
Canvas = "canvas",
Div = "div",
Span = "span"
}

var a = createElement(TagName.Canvas); //a is of type HTMLCanvasElement

Closed Jul 28 at 5:18 PM by jonturner

As part of our move to GitHub, we're closing our CodePlex suggestions and asking that people >move them to the GitHub issue tracker for further discussion. Some feature requests may already >be active on GitHub, so please make sure to look for an existing issue before filing a new one.

You can find our GitHub issue tracker here:
https://github.com/microsoft/typeScript/issues

Changed the one example from the original for the Document interface. It seems that you would only specify the name of the enum.-- Values passed in would become bounded to the use of the enum constants (and possibly to free-form string literals, where their values can be statically determined to be within the enum's domain values)

@jhlange I think tagged unions would automatically cater for this use case nicely : https://github.com/Microsoft/TypeScript/issues/1003

@basarat That seems interesting from a theoretical standpoint, and I can definitely see uses for it.

It would solve my case.--At least giving some level of intellisense and compile-time validation.

I personally believe accessing enum constant fields makes for a much more natural experience for non-functional languages (and at least has parity with enums, which is what they they really are. I really don't think we should create a new concept for a construct that already exists. That will be confusing to too many people).

I would love this when I deal with C++ enums compiled by Emscripten.

// C++
enum Month {
  Jan, Feb, Mar
};
// I can do this, but they really are not numbers!
declare enum Month {
   Jan, Feb, Mar
}

interface EmscriptenEnum {
  value: number; /* some more properties ... */
}
interface Month extends EmscritenEnum {
}
declare module Month {
   var Jan: Month;
   var Feb: Month;
   var Mar: Month;
}
// Month.Jan.value == 0, Month.Feb.value == 1, ...

C#/C++ lets you define base type of enum, although restricted to numeric type. I would like to be able to do the same but generalized to arbitrary type.

It should work with builtin types like string, so:

enum Foo extends string {
    BAR,
    BAZ = "surprise"
}

compiles to:

var Foo;
(function (Foo) {
    Foo["BAR"] = "BAR";
    Foo[Foo["BAZ"] = "surprise"] = "BAZ";
})(Foo || (Foo = {}));

but also user types, to handle enum object pattern that is used by enums in Java and common in other languages (as requested above):

interface IFoo {
    id: string;
    code: number;
}
enum Foo extends IFoo {
    BAR = { id: "BAR", code: 123 },
    BAZ = { id: "", code: 0 }
}

compiles to:

var Foo;
(function (Foo) {
    Foo["BAR"] = { id: "BAR", code: 123 };
    Foo["BAZ"] = { id: "", code: 0 };
})(Foo || (Foo = {}));

Perhaps with a convention that if interface has fields id and/or code (or ordinal as in Java) they can be omitted in initializer and filled by compiler.

How would this work?

enum Test {
    Foo = "Bar",
    Bar = "Baz",
    Baz = "Foo"
}

Either compilation error or omit generation of reverse mapping for Bar. I'm not sure which one I prefer.

In fact I'd prefer if TypeScript didn't generate reverse mapping in the same object. Perhaps all reverse mappings (for number based enums too) should go inside some property, say Test.__reversed.

In my mind these really need to compile down to plain strings. String enums
are already heavily used in json objects for Web services. In my opinion
this is one of the main use cases for string based enums.-- another example
is in the second post-- being able to define the domain values for
predefined Javascript apis.

If it isn't possible to define strongly typed interfaces for these cases,
because typescript uses a different methodology in its implementation, the
implementation would be purely academic.
On Dec 30, 2014 3:16 PM, "Marcin Wisnicki" [email protected] wrote:

In fact I'd prefer if TypeScript didn't generate reverse mapping in the
same object. Perhaps all reverse mappings (for number based enums too)
should go inside some property, say Test.__reversed.


Reply to this email directly or view it on GitHub
https://github.com/Microsoft/TypeScript/issues/1206#issuecomment-68409570
.

I think the first thing should be strings const enum.
This is definitely the most useful feature.
Many APIs has JSON with small amount of possible string values and currently you must use 'string' type rather then a correct subset.
In that case I even think the names are not needed since you can pass only the correct string.

const enum A {
 "x",
 "y",
 "z"
}

var a : A;
a = // Intellisense suggest "x", "y" or "z"
a = "x"; // OK
a = "b"l // Error

My concern with the const enum approach is that it will lead to cases where the value can not be determined to be conforming when being passed into an API. (would this be a warning?, what happens if you get that warning? do you have to add casts all over the place to mitigate the warning?).

If you treat them as something that can never be converted to or from a string, they will do exactly what they need to do.-- It should work exactly like the integer enums, with the exception that the values are strings. I think it would be fine if the keys and values are locked together, meaning that

const enum A {
 "x",
 "y",
 "z"
}

Could be fine, but any string assignments without casts should fail.

public myfunc(someRandomInput: string) {
var a : A;
a = // Intellisense suggest A.x   A.y   A.z
a = "x"; // Error, it is overkill to support this special case. Just use A.x like a regular enum
a = A.x; // OK
a = "b"; // Error
a = someRandomInput; // Error, can not always be determined to be conforming.
}

In cases where they are converted from a string, a cast should be used.-- But even then, behavior like that can indicate that an input did not come from a proper source.

Additionally, this will help tooling like the the schema validation/generators to appropriately generate the appropriate validation code.-- Schema validation is going to become even more important in times to come.--One of my big concerns with javascript is general is the lack of validation.-- Now that people are running servers on this stuff.

As far as I can tell, the bulk of the work needed to get this done is removing one validation/compiler error against enum definitions (assigning types other than number).-- There might be some smarts to make sure people don't directly assign numbers to enum, but they shouldn't be doing that without a cast anyway either...

How about using generics for enum?
Current existing enum means enum<number>. So we can extend it to enum<string> or other types:

enum<string> Foo1 {
    BAR, // default value is "BAR".
    BAZ = "x" // also you can specify the vaule.
}
var e: Foo1 = Foo1.BAR; // ok
var s: string = Foo1.BAR; // ok
var n: number = Foo1.BAR; // error

enum<boolean> Foo2 {
    BAR = true, // assigning is required. only number and string enum have default value.
    BAZ = false
}

You can use every type for enum like enum<YourFavoriteType> and there is no breaking change.

Base type syntax makes more sense IMHO and is already familiar to C# developers.

+1 for the generics suggestion.

and Closure Compiler uses @enum {string}, similar to generics.

/**
 * @enum {string}
 */
var Foo = {
  BAR: 'BAR',
  BAZ: 'BAZ'
};

Also default type of enum in Closure is number. It's in common with TypeScript's case.

If the type of an enum is omitted, number is assumed.
https://developers.google.com/closure/compiler/docs/js-for-compiler

+1 for the generics syntax. I just need some sort of functionality. Here's my use case: I'm wanting to convert this bit of ES6 + Lodash into something I can more conveniently statically check. (Note: the boilerplate is because I use it for other things as well, things that would have to be generated at build time to statically type-check.)

// Enum
const tokenTypes = makeEnumType(([value, length]) => ({value, length}));

export const Tokens = tokenTypes({
    OpenCurved:   ['(', 1],
    CloseCurved:  [')', 1],
    OpenBracket:  ['[', 1],
    CloseBracket: [']', 1],
    OpenCurly:    ['{', 1],
    CloseCurly:   ['}', 1],
    Identifier:   ['Identifier', 0],
    String:       ['String', 0],
    EOF:          ['EOF', 0],
});

// Utilities
function deepFreeze(obj) {
    Object.freeze(obj);
    _.forOwn(obj, value => typeof value === 'object' && deepFreeze(obj));
    return obj;
}

_.mixin({
    removeProto(obj) {
        let ret = Object.create(null);
        _.forOwn(obj, _.partial(_.assign, ret));
        return ret;
    },
    freeze: deepFreeze,
});

function mapObject(obj, f) {
    let ret = Object.create(Object.getPrototypeOf(obj));
    forOwn(obj, (value, i) => ret[i] = f.call(obj, value, i, obj));
    return ret;
}

function makeEnumType(transformer) {
    return obj => _.chain(mapObject(obj, transformer))
        .removeProto()
        .freeze()
        .value();
}

The best I can currently do in the first case is this (the second is similar):

interface TokenType {
    type: string;
    value: string;
}

function type(value: string, length: number): TokenType {
    return {value, length};
}

class TokenTypes {
    // This shouldn't be called as a constructor...but it still type-checks.
    static OpenCurved: TokenType   = type('(', 1);
    static CloseCurved: TokenType  = type(')', 1);
    static OpenBracket: TokenType  = type('[', 1);
    static CloseBracket: TokenType = type(']', 1);
    static OpenCurly: TokenType    = type('{', 1);
    static CloseCurly: TokenType   = type('}', 1);
    static Identifier: TokenType   = type('Identifier', 0);
    static String: TokenType       = type('String', 0);
    static EOF: TokenType          = type('EOF', 0);
}

With the above syntax, I could use the following:

enum<TokenType> TokenTypes {
    OpenCurved   = type('(', 1),
    CloseCurved  = type(')', 1),
    OpenBracket  = type('[', 1),
    CloseBracket = type(']', 1),
    OpenCurly    = type('{', 1),
    CloseCurly   = type('}', 1),
    Identifier   = type('Identifier', 0),
    String       = type('String', 0),
    EOF          = type('EOF', 0),
}

This is where enums can really help.

(The boilerplate is mainly for places where a preprocessor would be helpful.)

@jbondc I like it.

You've picked the simple, obvious and intuitive solution, that can enforce type safety in many of the situations that some of the above options can't without a full static analysis of a program.-- It additionally would support creating straight forward domain bound json schma validations right from the typescript definition.

Any interest in #2491 ?

+1 for this and I am suggesting supporting all primitive types for enum values. Just like Swift. This is the syntax in Swift which I really like:

enum Audience: String {
    case Public = "Public"
    case Friends = "Friends"
    case Private = "Private"
}

I would like to mention that, because of ECMAScript language limitations
themselves, const enums should be limited to non-Symbol primitives. Because
{} !== {} and Symbol("foo") !== Symbol("foo"), other enums can't be
inlined.

I do feel this would be incredibly useful, though.

On Tue, Jun 16, 2015, 16:41 Mohsen Azimi [email protected] wrote:

+1 for this and I am suggesting supporting all primitive types for enum
values. Just like Swift. This is the syntax in Swift which I really like:

enum Audience: String {
case Public = "Public"
case Friends = "Friends"
case Private = "Private"
}


Reply to this email directly or view it on GitHub
https://github.com/Microsoft/TypeScript/issues/1206#issuecomment-112563174
.

+1 for the generics syntax.
+1 for jbondc proposal for primitive type / non primitive type management
It would be useful emit primitive enum value for primitive type enums:

enum myStringEnum < string > {
   one = "myOneValue",
   two = "myTwoValue",
   three = "myThreeValue"
}

var value = myStringEnum.one; //emits 'var value = "myOneValue" /* myStringValue.one */'

emit inline value for primitive types (number, string, boolean ...) is very useful because the enum declaration does not need to be emitted in javascript but is used only to enforce compile-type validation

@Gambero81 are you aware of const enums for the purposing of skipping emit like you want?

@danquirk const enum are great, but attually does not support generics type but is only for numeric type..

Also const enums should be restricted to primitive types, and the whole reason they aren't emitted is to lessen code size (especially minified), which would _not_ usually be the case with other types, such as strings and booleans.

But as for normal enums of non-numeric types, this definitely should be possible. Currently, there is little reason to prefer a normal enum over a const enum, and this would wonderfully fix that.

I do have (another) possible syntax, in case you all might like it:

function type(ch: string, length: num): TokenType {
    // code...
}

enum TokenTypes: TokenType {
    OpenCurved   = type('(', 1),
    CloseCurved  = type(')', 1),
    OpenBracket  = type('[', 1),
    CloseBracket = type(']', 1),
    OpenCurly    = type('{', 1),
    CloseCurly   = type('}', 1),
    Identifier   = type('Identifier', 0),
    String       = type('String', 0),
    EOF          = type('EOF', 0),
}

Coming from the Java world I really miss the ability to define methods on my enums. Would really like for enums to be full class citizens but still able to be used in switch statements (with auto complete support)

Allows for things like:

var planet = Planet.fromOr(myForm.planetSelect.selectedIndex,Planet.Earth)
myForm.swallowEnabled.checked=Planet.Earth.canSwallow(planet);

Example enum:

enum Planet {
     private label:string; <--custom property
     private size:string; <--custom property
     private orbit:number; <--custom property

     Mercury("Mercury",1,1),Venus("Venus",2.8,2),Earth("Home,3,3)...; <--declare 'constants'

     Planet(label,size,orbit){ <--private constructor
       .....
     }

     //a custom instance method
     public canSwallow(planet:Planet):bool { //<--custom method
        return planet.size < this.size;
     }

     public isCloserToSun(planet:Planet):bool { //<--custom method
        return planet.orbit < this.orbit;
     }

     //all functions below auto generated, or implemented in base enum. Shown here in semi typescript

     //convert from string, number or type or use given default
     public static fromOr(nameindexOrType:string,defVal:Planet=null):Planet { //<--auto generated
        var e = from(nameindexOrType);
        return e==null?defVal:e;
     }

     //convert from string, number or type or return null
     public static from(nameindexOrType:string):Planet { //<--auto generated
       if(nameindexOrType == null){ return null; }
       if(typeof(nameindexOrType) =='Number'){
         switch(nameindexOrType){
           case 0:return Planet.Mercury;
           case 1:return Planet.Venus;
           ...
        }
      }if(typeof(nameindexOrType) =='String'){
         nameindexOrType = nameindexOrType.ToUpperCase();
         switch(nameindexOrType){
           case 'MECURY':return Planet.Mercury;
           ...
        }
      }
      return null;
    }

    public static get names():string[] { //<--auto generated
       return ['Mercury','Venus','Earth',...];
    }

    public static get values():Planet[] { //<--auto generated
       return [Planet.Mercury,Planet.Venus,Planet.Earth',...];
    }

  }
}

internally there would also be a field called 'index' and 'name' which are used for comparison checks (or just one of them)

If no custom properties or methods, then everything compiled down to a number or a string only.

@bertvanbrakel I like your idea, but I think that may fit better as another bug, and also, this may fall under "Not yet". Another thing, IMO, there aren't a lot of use cases for having methods on enums, anyways, since they can easily become too coupled to the enum, and hard to generalize.

So far, here's the possible syntaxes I've found here...

// 1.
enum Enum extends string {
    Foo, // "Foo"
    Bar = "something",
}

// 2.
enum<string> Enum {
    Foo, // "Foo"
    Bar = "something",
}

// 3.
enum Enum: string {
    Foo, // "Foo"
    Bar = "something",
}

WDYT?

@bertvanbrakel You can already do this:

enum Color { Red, Green, Blue }
module Color {
  export function getFavorite() { return Color.Green; }
}

He wants methods on enum values (objects), similar to Java enum.

there are many good proposal, but the question is: when will be sheduled and implemented? there is a roadmap for this task?

@Gambero81 The "needs-proposal" tag is that this needs a more formal proposal before it actually gets implemented, including type-checking semantics, required JS emit, etc.

+1 for more versatile enums. Also +1 @bertvanbrakel's proposal. Having enums with multiple properties, private constructors and methods all together in a single/self-contained unit of code is very useful and safe from a developer's pov and useful for testability.
In addition to defining methods, being able to implement interfaces is also quite useful.

Here's an example of a useful Java enum; in it the 'value' property allows to make the enum usage independent of the enum ordinal values; this allows to avoid common issues with enum values reordering and the like that often bite beginners.

public enum DeploymentStatus implements InternationalizedEnum {
    PENDING(100, "STATUS_PENDING", "status.pending"),
    QUEUED_FOR_RELEASE(110, "STATUS_QUEUED_FOR_RELEASE","status.queuedrelease"),
    READY_FOR_RELEASE(120,"STATUS_QUEUED_RELEASE","status.readyrelease"),
    RELEASING(130,"STATUS_RELEASING","status.startedrelease"),
    RELEASED(140,"STATUS_RELEASED","status.suceededrelease"),
    QUEUED(200, "STATUS_QUEUED", "status.queued"),
    READY(300, "STATUS_READY", "status.ready"),
    STARTED(400, "STATUS_STARTED", "status.started"),
    SUCCEEDED(500, "STATUS_SUCCEEDED", "status.succeeded"),
    FAILED(600, "STATUS_FAILED", "status.failed"),
    UNKNOWN(700, "STATUS_UNKNOWN", "status.unknown"),
    UNDEFINED(0, "", "status.undefined");

    private final int value;
    private final String code;
    private final String messageCode;

    private DeploymentStatus(final int value,
                             final String code,
                             final String messageCode) {
        this.value = value;
        this.code = code;
        this.messageCode = messageCode;
    }

    public String getCode() {
        return code;
    }

    public int getValue() {
        return value;
    }

    public String getMessageCode() {
        return messageCode;
    }

    public static DeploymentStatus parse(final Integer id) {
        DeploymentStatus status = DeploymentStatus.UNDEFINED;
        if (id != null) {
            for (DeploymentStatus entry : DeploymentStatus.values()) {
                if (entry.getValue() == id) {
                    status = entry;
                    break;
                }
            }
        }
        return status;
    }

    public boolean isFinalStatus() {
        return this == SUCCEEDED || this == FAILED || this == UNKNOWN;
    }
}

@dsebastien +1 That's almost the use case I needed.

I did find that for my particular use case, it just needed refactored.

@jbondc

The emit is the same but the compiler should treat the entire object as an immutable/const structure.

What about method calls? You have to partially evaluate the code to figure that out.

I stick with my idea that the enums should remain self-contained as that's the cleanest; having to create a separate class just for the purpose of 'linking' it from an enum doesn't make much sense, assuming that the concept it describes is an element of the enum and the operations that it supports. Indeed it would make it much closer to a class but with hard restrictions :)

Also, i don't know if having a mutable enum entry is a good idea at all, it's the kind of thing that would get abused and would lead to side-effects. For me, enums should be as immutable as can be ^^

It would be great if this feature accurately reflects how the APIs people use today are structured (see the original few comments). The whole purpose of typescript is to introduce a basic level of type safety, not implement features from functional languages, etc. The use cases that already exist to support this have already been defined. Some of these include:

  1. By the browser standards (example: document.createElement(elementType: HtmlElementType) )
  2. By well established javascript libraries ( element.on(eventType: EventType) ) https://api.jquery.com/on/
  3. Basically every web api on the internet ( units of measure, etc in example https://developer.yahoo.com/weather/ )
  4. By the return value of JSON.parse() and stringify() (just look at your own projects, around serialization of types)
  5. by popular server side libraries http://expressjs.com/4x/api.html#app.onmount
  6. Probably lots more that I'm not thinking about

If enums become complex types, tagged unions, or any of the other more complex suggestions in this thread, they will be relatively useless for type safety (the primary goal of typescript), as they will not be usable in scenarios surrounding any current APIs (or in the case of tagged unions, you'd still be compatible, but would the type safety can't be inferred without a full static analysis of the project, which typescript does not do).

Many of the comments here have a lot of academic merit, but they simply do not reflect interoperability with any of the built in APIs, and therefore will deliver minimal value.

_Edit 1: add type inference based on first entry_
_Edit 2: It's now a gist_

I'm going to take a stab at a more concrete proposal, but first, I'd like to say the following:

  1. Much of the reason people are pushing for enums of type other than number are for string enums (we all know they are necessary) and type-safe enums. Also, occasionally, object enums come in handy, so why not support that.
  2. I don't like the current syntax floating around, because it's too similar to interface key type declarations, instead of a variable type or enum property. Also, it doesn't make a whole lot of sense to me in theory - the semantics of current enums are closer to that of the following:

ts enum Foo { [prop: Foo]: number; }

  1. It doesn't seem to fit with the syntax of the rest of the enum body, where names and values are separated with an equals operator.
  2. What I'm about to propose would keep the interior as a simple list, simply expanding the inference a bit. That would also simplify the necessary changes to the parser, becoming more purely additive.
  3. This is still backwards-compatible with the original semantics, including with const enums. The explicit version's type is enforced during creation unlike the implicitly numeric version. Example below (the second would fail to compile):

``` ts
enum Foo {
Bar = 'Bar', // not a string, but checks anyways
}

enum Foo: number {
Bar = 'Bar', // Error: not a string
}
```

A little comparison:

// Current format floating around:
enum TokenTypes {
    [prop: string]: TokenType;

    OpenCurved   = type('(', 1),
    CloseCurved  = type(')', 1),
    OpenBracket  = type('[', 1),
    CloseBracket = type(']', 1),
    OpenCurly    = type('{', 1),
    CloseCurly   = type('}', 1),
    Identifier   = type('Identifier', 0),
    String       = type('String', 0),
    EOF          = type('EOF', 0),
}

// What I'm about to propose:
enum TokenTypes: TokenType {
    OpenCurved   = type('(', 1),
    CloseCurved  = type(')', 1),
    OpenBracket  = type('[', 1),
    CloseBracket = type(']', 1),
    OpenCurly    = type('{', 1),
    CloseCurly   = type('}', 1),
    Identifier   = type('Identifier', 0),
    String       = type('String', 0),
    EOF          = type('EOF', 0),
}

_(And yes, my proposal will allow for any arbitrary type for the enum properties. That's intentional - no need to guess the enum type)_

Proposal for typed enums

These are all amendments to the enum part of the spec.

Enum Declaration

The enum syntax would be extended as follows, where _EnumName_ is the enum's name, _Type_ is the enum's type, and _EnumMembers_ are the members and associated values of the enum type.

  _EnumDeclaration:_
   enum _EnumName_ { _EnumBodyopt_ }
   enum _EnumName_ : _Type_ { _EnumBodyopt_ }

  _EnumName:_
   _Identifier_

An enum type of the form above is a subtype of _Type_, and implicitly declares a variable of the same name, with its type being an anonymous object containing all the type's names as keys and _Type_ as the value type of each of them. Moreover, if _Type_ is the Number or Symbol primitive types, then the object's signature includes a numeric index signature with the signature [x: number]: string or [x: symbol]: string, respectively. In the first variation, _Type_ is inferred to be the type of the _EnumValue_ of the first _EnumEntry_ of _EnumBody_ if it has an _EnumValue_ (i.e. it's initialized), or the Number primitive type otherwise. In the second variation, _Type_ is explicitly given.

  _EnumDeclaration:_
   const _EnumBodyopt_ enum _EnumBodyopt_ _EnumName_ _EnumBodyopt_ { _EnumBodyopt_ }
   const _EnumBodyopt_ enum _EnumBodyopt_ _EnumName_ _EnumBodyopt_ : _EnumBodyopt_ _PrimitiveEnumType_ _EnumBodyopt_ { _EnumBodyopt_ }

  _PrimitiveEnumType:_
   boolean
   string
   number
   void

An enum type of the form above is a subtype of _PrimitiveEnumType_. It declares a variable of the same name, with its type being an anonymous object containing all the type's names as keys and _PrimitiveEnumType_ as the value type of each of them. It is said to also be a constant enum type. In the first variation, _Type_ is inferred to be the type of the _EnumValue_ of the first _EnumEntry_ of _EnumBody_ if it has an _EnumValue_ (i.e. it's initialized), or the Number primitive type otherwise. In the second variation, _Type_ is explicitly given. For constant enum types, for the sake of simplicity below, _Type_ references _PrimitiveEnumType_.

The example

enum Color: string { Red, Green, Blue }

declares a subtype of the String primitive type called Color, and introduces a variable 'Color' with a type that corresponds to the declaration

var Color: {
    [x: string]: string;  
    Red: Color;  
    Green: Color;  
    Blue: Color;  
};

The example

enum Color: Type { Red, Green, Blue }

declares a subtype of the type Type called Color, and introduces a variable 'Color' with a type that corresponds to the declaration

var Color: {
    Red: Color;  
    Green: Color;  
    Blue: Color;  
};

Enum Members

Each enum member has an associated value of _Type_ specified by the enum declaration.

  _EnumBody:_
   _EnumMemberList_ ,_opt_

  _EnumMemberList:_
   _EnumMember_

   _EnumMemberList_ , _EnumMember_

  _EnumMember:_
   _PropertyName_

   _PropertyName_ = _EnumValue_

  _EnumValue:_
   _AssignmentExpression_

If in an ambient context:

  • An error occurs if any _EnumMember_ of _EnumMemberList_ has an _EnumValue_.
  • Skip the rest of this section, as it does not apply.

If _Type_ is explicitly given, an error occurs if a given _EnumValue_ of any _EnumMember_ of _EnumMemberList_ is not of type _Type_.

For each _EnumMember_, if _EnumValue_ is not given, then _EnumValue_ is defined in the first of the following to apply:

  1. If _Type_ is the String primitive type, then let _EnumValue_ be _PropertyName_.
  2. Else, if _Type_ is the Number primitive type, then:

    1. If the member is the first in the declaration, then let _EnumValue_ be the primitive number 0.

    2. Else, if the previous member's _EnumValue_ can be classified as a constant numeric enum member, then let _EnumValue_ be the _EnumValue_ of the previous member plus one.

    3. Else, an error occurs.

  3. Else, if _Type_ is the Symbol primitive type, then let _EnumValue_ be _PropertyName_.
  4. Else, if the constructor for _Type_ accepts a single parameter of the String primitive type, then let _EnumValue_ be a newly constructed instance of _Type_ with a sole argument _PropertyName_.

_Non-normative:_

In other words, _Type_'s constructor must implement

ts interface TypeConstructorWithString { new (value: string): <Type>; }

  1. Else, if the constructor for _Type_ accepts a single parameter of the Number primitive type, then:

    1. If the member is the first in the declaration, then let _EnumValue_ be let _EnumValue_ be a newly constructed instance of _Type_ with a sole argument of the primitive number 0.

    2. Else, if the previous member's _EnumValue_ is a newly constructed instance of _Type_, and the constructor is called with a sole argument that can be classified as a constant numeric enum member, then let _EnumValue_ be that constructor call's argument plus one.

    3. Else, an error occurs.

_Non-normative:_

In other words, _Type_'s constructor must implement

ts interface TypeConstructorWithNumber { new (value: number): <Type>; }

  1. Else, if the constructor for _Type_ accepts zero parameters, then let _EnumValue_ be a newly constructed instance of _Type_, with zero arguments.

_Non-normative:_

In other words, _Type_'s constructor must implement

ts interface TypeConstructorWithString { new (): <Type>; }

  1. Else, an error occurs.

A few examples:

_EnumValue_ for Foo is the number 0.

enum E {
    Foo = 0,
}

_EnumValue_ for Foo is the string "Foo".

enum E: string {
    Foo = "Foo",
}

_EnumValue_ for Foo is a symbol wrapping the string "Foo".

enum E: symbol {
    Foo = Symbol("Foo"),
}

_EnumValue_ for Foo is the number 0.

enum E {
    Foo,
}

_EnumValue_ for Foo is the string "Foo".

enum E: string {
    Foo,
}

_EnumValue_ for Foo is new Type("Foo").

class Type {
    constructor(public value: string);
}

enum E: Type {
    Foo,
}

_EnumValue_ for Foo is new Type().

class Type {
    constructor();
}

enum E: Type {
    Foo,
}

An enum member is classified as follows:

  • If the member declaration specifies no value, and _Type_ is either the String primitive type or the Number primitive type, the member is considered a constant enum member, inferred according to their member name.
  • If _Type_ is the Boolean primitive type, and the member declaration specifies a value that can be classified as a constant boolean enum expression (as defined below), the member is considered a constant enum member.
  • If _Type_ is the Number primitive type, and the member declaration specifies a value that can be classified as a constant numeric enum expression (as defined below), the member is considered a constant enum member.
  • If _Type_ is the String primitive type, and the member declaration specifies a value that can be classified as a constant string enum expression (as defined below), the member is considered a constant enum member.
  • If _Type_ is the Void primitive type, and the member declaration specifies a value that can be classified as a constant void enum expression (as defined below), the member is considered a constant enum member.
  • Otherwise, the member is considered a computed enum member.

A _constant enum expression_ is a constant boolean enum expression, a constant numeric enum expression, a constant void enum expression, or a constant string enum expression.

A _constant boolean enum expression_ is a subset of the expression grammar that can be evaluated fully at compile time, and returns only booleans. A constant boolean enum expression is one of the following:

  • A boolean literal
  • An identifier or property access that denotes a previously declared member in the same constant enum declaration, if _Type_ is the Boolean primitive type.
  • A parenthesized constant boolean enum expression.
  • A ! unary operator applied to a constant enum expression.

A _constant numeric enum expression_ is a subset of the expression grammar that can be evaluated fully at compile time, and returns only numbers. An expression is considered a constant numeric enum expression if it is one of the following:

  • A numeric literal.
  • An identifier or property access that denotes a previously declared member in the same constant enum declaration, if _Type_ is the Number primitive type.
  • A parenthesized constant numeric enum expression.
  • A +, –, or ~ unary operator applied to a constant enum expression.
  • A + operator applied to two constant numeric enum expressions.
  • A –, *, /, %, <<, >>, >>>, &, ^, or | operator applied to two constant enum expressions.

A _constant string enum expression_ is a subset of the expression grammar that can be evaluated fully at compile time, and returns only strings. An expression is considered a constant numeric enum expression if it is one of the following:

  • A string literal.
  • A template string literal whose expressions are all constant enum expressions.
  • An identifier or property access that denotes a previously declared member in the same constant enum declaration, if _Type_ is the String primitive type.
  • A parenthesized constant string enum expression.
  • A + operator applied to two constant enum expressions, one of which is a constant string enum expression.

A _constant void enum expression_ is a subset of the expression grammar that consists of either the primitive null or the primitive undefined.

Proposed emit

Section 9.1 is amended to the following:

var <EnumName>;
(function (<EnumName>) {
    <EnumMemberAssignments>  
})(<EnumName>||(<EnumName>={}));

where _EnumName_ is the name of the enum, and _EnumMemberAssignments_ is a sequence of assignments, one for each enum member, in order they are declared. _EnumMemberAssignments_ is defined in the first applicable section below.

  1. If _Type_ is the Number or Symbol primitive types, let the following be _EnumMemberAssignments_.

js <EnumName>[<EnumName>["<PropertyName>"] = <EnumValue>] = "<PropertyName>";

  1. If _Type_ is of any other type, let the following be _EnumMemberAssignments_.

js <EnumName>["<PropertyName>"] = <Value>;

Examples

For either of these sources,

enum Color { Red, Green, Blue }
enum Color: number { Red, Green, Blue }

the following should be emitted:

var Color;
(function (Color) {
    Color[Color["Red"] = 0] = "Red";
    Color[Color["Green"] = 1] = "Green";
    Color[Color["Blue"] = 2] = "Blue";
})(Color||(Color={}));

For this source,

enum Color: string { Red, Green, Blue }

the following should be emitted:

var Color;
(function (Color) {
    Color["Red"] = "Red";
    Color["Green"] = "Green";
    Color["Blue"] = "Blue";
})(Color||(Color={}));

For this source,

enum Color: symbol { Red, Green, Blue }

the following should be emitted:

var Color;
(function (Color) {
    Color[Color["Red"] = Symbol("Red")] = "Red";
    Color[Color["Green"] = Symbol("Green")] = "Green";
    Color[Color["Blue"] = Symbol("Blue")] = "Blue";
})(Color||(Color={}));

For this source,

class Type {
    constructor(public value: String);
}

enum Color {
  Red = new Type("Red"),
  Green,
  Blue,
}
enum Color: Type { Red, Green, Blue }

the following should be emitted for either version of the Color enum:

var Color;
(function (Color) {
    Color["Red"] = new Type("Red");
    Color["Green"] = new Type("Green");
    Color["Blue"] = new Type("Blue");
})(Color||(Color={}));

For this source,

class Type {
    constructor();
}

enum Color: Type { Red, Green, Blue }

the following should be emitted for the Color enum:

var Color;
(function (Color) {
    Color["Red"] = new Type();
    Color["Green"] = new Type();
    Color["Blue"] = new Type();
})(Color||(Color={}));

For this source,

class Type {
    constructor(public value: number);
}

enum Color: Type { Red, Green, Blue }

the following should be emitted for the Color enum:

var Color;
(function (Color) {
    Color["Red"] = new Type(0);
    Color["Green"] = new Type(1);
    Color["Blue"] = new Type(2);
})(Color||(Color={}));

For this source,

class Type {
    constructor(public value: number);
}

enum Color: Type { Red = new Type(1), Green, Blue }

the following should be emitted for the Color enum:

var Color;
(function (Color) {
    Color["Red"] = new Type(1);
    Color["Green"] = new Type(2);
    Color["Blue"] = new Type(3);
})(Color||(Color={}));

For this source,

class Type {
    constructor(public value: string, public index: number);
}

enum Color: Type {
    Red = new Type("Red", 1),
    Green = new Type("Green", 2),
    Blue = new Type("Blue", 3),
}

the following should be emitted for the Color enum:

var Color;
(function (Color) {
    Color["Red"] = new Type("Red", 1);
    Color["Green"] = new Type("Green", 2);
    Color["Blue"] = new Type("Blue", 3);
})(Color||(Color={}));

For this source,

class Type {
    constructor(public value: string, public index: number);
}

enum Color: string {
    Red = "Red",
    Green = "Blue",
    Blue = "Purple",
}

the following should be emitted for the Color enum:

var Color;
(function (Color) {
    Color["Red"] = "Red";
    Color["Green"] = "Blue";
    Color["Blue"] = "Purple";
})(Color||(Color={}));

Some of the above examples translated below:

interface EmscriptenEnumEntry {
  value: number; /* some more properties ... */
}
declare enum Month: EmscriptenEnumEntry {Jan, Feb, Mar}
// Month.Jan.value == 0, Month.Feb.value == 1, ...

interface IFoo {
    id: string;
    code: number;
}
enum Foo: IFoo {
    BAR = { id: "BAR", code: 123 },
    BAZ = { id: "", code: 0 }
}

enum Audience: String {
    Public,
    Friends,
    Private,
}

enum Test: string {
    Foo = "Bar",
    Bar = "Baz",
    Baz = "Foo"
}

// const string enums now exist.
const enum A: string {x, y, z}

enum Foo2: boolean {
    BAR = true, // assigning required
    BAZ = false
}

enum TokenTypes: TokenType {
    OpenCurved   = type('(', 1),
    CloseCurved  = type(')', 1),
    OpenBracket  = type('[', 1),
    CloseBracket = type(']', 1),
    OpenCurly    = type('{', 1),
    CloseCurly   = type('}', 1),
    Identifier   = type('Identifier', 0),
    String       = type('String', 0),
    EOF          = type('EOF', 0),
}

enum tuples: [number, number] {
  a = [0, 1],
  b = [0, 2],
  // tuples...
}

/*
 * The Planets Java example
 */
class PlanetType {
    constructor(
        private label: string,
        private size: string,
        private orbit: number);

    canSwallow(planet: Planet): bool {
       return planet.size < this.size;
    }

    isCloserToSun(planet: Planet): bool {
       return planet.orbit < this.orbit;
    }

    static fromOr(nameindexOrType: string, defVal: Planet = null): Planet {
       var e = from(nameindexOrType);
       return e == null ? defVal : e;
    }

    static from(nameindexOrType: string): Planet {
        if (nameindexOrType == null) {
            return null;
        } else if (typeof nameindexOrType === 'Number') {
            switch(nameindexOrType){
            case 0: return Planet.Mercury;
            case 1: return Planet.Venus;
            case 2: return Planet.Earth;
            // ...
            }
        } else if (typeof nameindexOrType === 'String') {
            nameindexOrType = nameindexOrType.ToUpperCase();
            switch(nameindexOrType){
            case 'MERCURY': return Planet.Mercury;
            case 'VENUS': return Planet.Venus;
            case 'EARTH': return Planet.Earth;
            // ...
            }
        } else {
            return null;
        }
    }
}

enum Planet: PlanetType {
    Mercury = new PlanetType("Mercury", 1, 1),
    Venus = new PlanetType("Venus", 2.8, 2),
    Earth = new PlanetType("Home", 3, 3),
    // ...
}

/*
 * The DeploymentStatus Java example
 */
class DeploymentStatusType implements InternationalizedEnumType {
    constructor(
        private value: number,
        private code: string,
        private messageCode: string);

    getCode() { return code; }

    getValue() { return value; }

    getMessageCode() { return messageCode; }

    static parse(id: number) {
        if (id != null) {
            switch (id) {
            case 100: return DeploymentStatus.PENDING;
            case 110: return DeploymentStatus.QUEUED_FOR_RELEASE;
            case 120: return DeploymentStatus.READY_FOR_RELEASE;
            case 130: return DeploymentStatus.RELEASING;
            case 140: return DeploymentStatus.RELEASED;
            case 200: return DeploymentStatus.QUEUED;
            case 300: return DeploymentStatus.READY;
            case 400: return DeploymentStatus.STARTED;
            case 500: return DeploymentStatus.SUCCEEDED;
            case 600: return DeploymentStatus.FAILED;
            case 700: return DeploymentStatus.UNKNOWN;
            }
        }
        return DeploymentStatus.UNDEFINED;
    }

    isFinalStatus() {
        return this === DeploymentStatus.SUCCEEDED ||
            this === DeploymentStatus.FAILED ||
            this === DeploymentStatus.UNKNOWN;
    }
}

export default enum DeploymentStatus {
    PENDING = new DeploymentStatusType(100, "STATUS_PENDING", "status.pending"),
    QUEUED_FOR_RELEASE = new DeploymentStatusType(110, "STATUS_QUEUED_FOR_RELEASE", "status.queuedrelease"),
    READY_FOR_RELEASE = new DeploymentStatusType(120, "STATUS_QUEUED_RELEASE","status.readyrelease"),
    RELEASING = new DeploymentStatusType(130, "STATUS_RELEASING", "status.startedrelease"),
    RELEASED = new DeploymentStatusType(140, "STATUS_RELEASED", "status.suceededrelease"),
    QUEUED = new DeploymentStatusType(200, "STATUS_QUEUED", "status.queued"),
    READY = new DeploymentStatusType(300, "STATUS_READY", "status.ready"),
    STARTED = new DeploymentStatusType(400, "STATUS_STARTED", "status.started"),
    SUCCEEDED = new DeploymentStatusType(500, "STATUS_SUCCEEDED", "status.succeeded"),
    FAILED = new DeploymentStatusType(600, "STATUS_FAILED", "status.failed"),
    UNKNOWN = new DeploymentStatusType(700, "STATUS_UNKNOWN", "status.unknown"),
    UNDEFINED = new DeploymentStatusType(0, "", "status.undefined");
}

What do you all think about this?

@jbondc

You're welcome. As for your nits:

a. The reverse mapping can still exist. Symbols are allowed to be object keys in ES6, and they are fully distinct. You are correct in that Symbol("Foo") !== Symbol("Foo"), but symbols are still reflexive. The following is still the case:

Enum.Foo = Symbol("Foo")
Enum.Foo !== Symbol("Foo")
Enum.Foo === Enum.Foo

Enum[Enum.Foo] = "Foo"

let sym = Enum.Foo
Enum[sym] === "Foo"

That's just ES6 semantics.

Strings aren't included because of their high risk of collision. Symbols are less likely to collide than even numbers.

b. I'd rather be covering obscure edge cases and be complete than to limit to seemingly sane options and be inconsistent. Who knows, maybe someone actually has some sort of very weird use case for a void enum, filled with only nulls and undefineds. I don't believe it's the language's job to make that judgement, but rather the programmer.

c. If you don't want value inference, then don't use it. Nothing is making you use it, and I know it wouldn't be that hard to write a tslint rule for that. Some people would love that ability to infer values (I know I would).

A lot of the extras are mostly in the realm of flexibility. The compiler's job isn't to enforce one specific code style, IMHO. Even Python's language design avoids that for the most part (main exception: multiline lambdas by Guido's decree).

@jbondc

The reverse mapping is to be consistent with the current state, where there exists a reverse mapping. I'm okay with leaving Symbols out, though, and just sticking wiht numbers.

And there doesn't exist any inference for primitives except for strings, symbols, and numbers - explicit initialization is required for all values for any other primitive type, including booleans. Your third example would not work - it would fail to compile.

I didn't realize you wanted value inference as well, though. Sorry for that incorrect assumption.

As for type inference, I'll add the language in my proposal above.

And as for the type inference part (after the edit), it doesn't care about the value, and string value inference is only based on the key, not previous values [1]. So, you would get the following, potentially surprising behavior in this edge case:

enum Enum: string {
  Foo = "some random string",
  Bar, // Bar
}

[1] Inferring a convention from an arbitrary string is theoretically impossible, and getting a computer to get even remotely close to a reasonable convention is still extremely hard to accomplish for most cases. It would make for a good AI research topic, but even then, it can get ambiguous without enough context. And I highly doubt the TypeScript compiler is going to get true AI any time in the next few years, if even within the next 20.

enum Error {
  UNKNOWN = "Unknown",

  // is this "Bad_Response", "Bad Response", "Bad response", "BadResponse",
  // or something else?
  BAD_RESPONSE,
}

@jbondc (a) is already in my spec draft here.

And for everyone here, I moved my proposal to a gist.

@jbondc Would you mind punting the idea about extra string interpolation stuff until the rest of this gets solidified and merged? Then a followup bug would be a great place to bikeshed the rest of this out.

I feel the rest of my proposal (see this gist) hasn't gotten the proper bikeshedding it needs.

  • Constructor argument inference?
  • Syntax itself?
  • Enforced stronger typing when explicitly typed?
  • Value inference when no value is explicitly given, but type is?
  • Type inference based on first entry's type?
  • Instances not automatically frozen (although the readonly suggestion will help this case)

/cc @danquirk @jhlange @basarat @SaschaNaz @NN--- @mwisnicki @enoshixi @teppeis @bvaughn

Bump...could I get some people to look over my enum proposal?

My interest in this Enum issue was I could receive text in json and have it converted to the Enum type (with it's underlying number or string or whatever)

enum TestEnum {
    Unknown = 0,
    Alpha = 1,
    Beta = 2
}

interface IJsonTest {
    fieldOne: number;
    fieldTwo: TestEnum;
}

The goal is to receive and send this with a IJsonTest typed object:

{
  "fieldOne": 5,
  "fieldTwo": "Alpha"
}

It now seems that even the enum with a string type doesn't help with that situation. Without runtime metadata on the interface; how the json parser could even handle this?

Under my proposal, your enum should be this:

enum TestEnum: string {
  Unknown, Alpha, Beta,
}

And as for inferring value types, it should work with similar machinery as
the current numeric enums. If this works, then my proposal should suffice:

enum Foo { Bar, Baz }
let baz: Foo = 1
let bar: Foo = 0

In terms of validating input at compile time, there is no language or
library in the world that can validate JSON input purely at compile time.
That idea itself would be bad in practice because you can't assume your
incoming JSON is exactly what you are expecting, especially on the server.
Most validation has to happen at runtime.

On Wed, Jul 29, 2015, 18:47 Nathan Brown [email protected] wrote:

My interest in this Enum issue was I could receive text in json and have
it converted to the Enum type (with it's underlying number or string or
whatever)

enum TestEnum {
Unknown = 0,
Alpha = 1,
Beta = 2
}
interface IJsonTest {
fieldOne: number;
fieldTwo: TestEnum;
}

The goal is to receive and send this with a IJsonTest typed object:

{
"fieldOne": 5,
"fieldTwo": "Alpha"
}

It now seems that even the enum with a string type doesn't help with that
situation. Without runtime metadata on the interface; how the json parser
could even handle this?


Reply to this email directly or view it on GitHub
https://github.com/Microsoft/TypeScript/issues/1206#issuecomment-126119055
.

@impinball sorry for the delay, things have been fairly busy. I haven't gotten the chance to read the whole proposal, but I do question the utility of a constant void enum so far.

I am willing to remove it. It was mostly for completeness, but I'm not
really that strongly attached to it.

On Thu, Jul 30, 2015, 13:48 Daniel Rosenwasser [email protected]
wrote:

@impinball https://github.com/impinball sorry for the delay, things
have been fairly busy. I haven't gotten the chance to read the whole
proposal, but I do question the utility of a constant void enum.


Reply to this email directly or view it on GitHub
https://github.com/Microsoft/TypeScript/issues/1206#issuecomment-126415506
.

Hi, I am not sure if I can vote for this or what but I'd just just to say that a String based Enum would be incredibly helpful for me.
I have a problem that all Enums in our json are in string format. The server guys really don't want to convert them to integers so I have to consider changing the value of each one as the data arrives over the wire.

@impinball :+1: +9001 I love this proposal.

I seldom use the enum type because it is only numeric, and it almost never makes sense to pass numeric values to/from e.g. JSON, web services, etc. This (along with #1003) would make enums actually practical to use, and remove the need for separate adapter/serializer classes.

+1

:+1:

I forgot about that part. Will fix as soon as I get to a computer.

On Sun, Sep 20, 2015, 09:26 Jon [email protected] wrote:

@impinball https://github.com/impinball I'd suggest moving the proposal
into a repo. Remove the 'void' part as its not reasonable to implement
since 'undefined' is used to know if a value can be resolved statically.
Haven't looked at implementing non-primitives types so no comments there
yet other then 'seems doable'.


Reply to this email directly or view it on GitHub
https://github.com/Microsoft/TypeScript/issues/1206#issuecomment-141785283
.

@jbondc Done and done. I was considering it for a bit, anyways. I've just been busy these past few weeks (school, trying to get a new web dev startup off the ground, etc.).

Forked :wink: I'll send a pull request as I get around to it.

Just FYI: With the current compiler (test it in the playground on typescriptlang.org), you can write this:

export enum Language {
    English = <any>"English",
    German = <any>"German",
    French = <any>"French",
    Italian = <any>"Italian"
}

Which generates:

(function (Language) {
    Language[Language["English"] = "English"] = "English";
    Language[Language["German"] = "German"] = "German";
    Language[Language["French"] = "French"] = "French";
    Language[Language["Italian"] = "Italian"] = "Italian";
})(exports.Language || (exports.Language = {}));
var Language = exports.Language;

Isn't this what we need?

In VS, IntelliSense works for me... and I do not need to assign string to lang; you can assign using lang = Language.French... But of course, a compiler feature would be better... however, it solves the problem for me..

@rsuter you found a great workaround for string enum, unfortunally it doesn't work with const enum, my primary usage of string enum would be for inline const values without need of transpile code..

Ping on my proposal.. I wish I could get a little more critiquing on it.

@impinball, we discussed the matter at our last design meeting (#5740). We're trying to come up with a future-proof idea where enums might individually have their own types as well, and so it's not clear exactly what the most ideal way to do that is while allowing values other than primitive literals.

@DanielRosenwasser How does that factor into my proposal? I'm willing to withdraw it if it's not very good.

It isn't bad at all, but if we take enums in a direction where each enum member claims a type representing its value (e.g. a string enum creating a string literal type alias for each member), it isn't clear what kind of literal type we would create for each member of a non-primitive enum. That would be a weird inconsistency between a string enum and a Dog enum.

Okay. I'm guessing you're talking in the same vein of string literal types,
like type Foo = "foo" | "bar"?

On Mon, Nov 23, 2015, 23:17 Daniel Rosenwasser [email protected]
wrote:

It isn't bad at all, but if we take enums in a direction where each enum
member claims a type representing its value (e.g. a string enum creating a
string literal type alias for each member), it isn't clear what kind of
literal type we would create for each member of a non-primitive enum. That
would be a weird inconsistency between a string enum and a Dog enum.


Reply to this email directly or view it on GitHub
https://github.com/Microsoft/TypeScript/issues/1206#issuecomment-159145530
.

It is more like trying make string literal types and enums make sense togather.

Any chance this will be implemented?

Just as an update, we're still actively discussing this, but the upcoming features in 2.0 have had priority, so no solid plans yet.

Just a note on this.

Sometimes it's very useful to have methods attached to the enum. As an example take a look at this groovy code that does a custom reverse lookup:

public enum ColorEnum {
    WHITE('white', 'White is mix of all colors'),
    BLACK('black', 'Black is no colors'),
    RED('red', 'Red is the color of blood')

    final String id;
    final String desc;
    static final Map map 

    static {
        map = [:] as TreeMap
        values().each{ color -> 
            println "id: " + color.id + ", desc:" + color.desc
            map.put(color.id, color)
        }

    }

    private ColorEnum(String id, String desc) {
        this.id = id;
        this.desc = desc;
    }

    static getColorEnum( id ) {
        map[id]
    }
}

@rauschma has another good example using his library enumify:

    class Weekday extends Enum {
        isBusinessDay() {
            switch (this) {
                case Weekday.SATURDAY:
                case Weekday.SUNDAY:
                    return false;
                default:
                    return true;
            }
        }
    }
    Weekday.initEnum([
        'MONDAY', 'TUESDAY', 'WEDNESDAY',
        'THURSDAY', 'FRIDAY', 'SATURDAY', 'SUNDAY']);

    console.log(Weekday.SATURDAY.isBusinessDay()); // false
    console.log(Weekday.MONDAY.isBusinessDay()); // true

Is there any chance we could have something similar for Typescript as well?

EDIT: Just noticed that the two examples are somehow misleading. I think the current proposal will allow us to have methods attached to the constant:

class TokenType {
    constructor(public kind: string, public length: number) {}

   myMethod() {
      return;
   }
}

enum TokenTypes: TokenType {
    OpenCurved = new TokenType('(', 1)
}

TokenTypes.OpenCurved.myMethod();

what I'm suggesting is methods attached to the enum:

class TokenType {
    constructor(public kind: string, public length: number) {}
}

enum TokenTypes: TokenType {
    OpenCurved = new TokenType('(', 1)

    fromString(string: tokenKind) {
       return reverseLookupTable.find(token => token.kind === tokenKind);
   }
}

token = TokenTypes.fromString('(');
token === TokenTypes.OpenCurved

@danielepolencic you can add static methods (documented here as well https://basarat.gitbooks.io/typescript/content/docs/enums.html) :

enum Weekday {
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday,
    Sunday
}
namespace Weekday {
    export function isBusinessDay(day: Weekday) {
        switch (day) {
            case Weekday.Saturday:
            case Weekday.Sunday:
                return false;
            default:
                return true;
        }
    }
}

const mon = Weekday.Monday;
const sun = Weekday.Sunday;
console.log(Weekday.isBusinessDay(mon)); // true
console.log(Weekday.isBusinessDay(sun)); // false

:rose:

Thank you for that. I never thought about it 🙇

I think the only outstanding item would be to found a way to do the reverse lookup.

I'll provide an example so that I can shed some light on how I use enums.

Imagine you have a <select> with 3 <option>s: High, Low and Medium. Since those are constants, I'd code them as an enum:

enum Frequency {High, Medium, Low}

At this point, I wish to render the <select> in the page. Unfortunately, I can't enumerate the constants in the enum so I end up doing:

<select>
  <option value="{{Frequency.High}}">{{Frequency.High}}</option>
  <option value="{{Frequency.Low}}">{{Frequency.Low}}</option>
  <option value="{{Frequency.Medium}}">{{Frequency.Medium}}</option>
</select>

Time to write some tests. I'd like to grab the value from the current selection, transform it into an enum and compare it. Ideally I'd write something like this:

// dispatch click and select Low
const currentValue: string = document.querySelector('select').value;
const frequency: Frequency = Frequency.fromString(currentValue);
expect(frequency).toEqual(Frequency.Low);

With the current enum implementation, I can't reverse lookup the string value.

The other issue is connected to the value in the <option> tag. I wish I could use an identifier rather than the value of the constant. E.g.:

<select>
  <option value="{{Frequency.High.id}}">{{Frequency.High.name}}</option>
  <option value="{{Frequency.Low.id}}">{{Frequency.Low.name}}</option>
  <option value="{{Frequency.Medium.id}}">{{Frequency.Medium.name}}</option>
</select>

where the constant is a tuple [name, id]. Since now I can retrieve a constant by name and id, I'd also like to write a new method that does that for me:

enum Frequency {
  fromName(name: string): Frequency {}
  fromId(id: number): Frequency {}
}

Again, I think this is not doable in the current proposal.

What I'm proposing is an enum implementation more similar to Java/Groovy enums. That would solve the issues above.

@danielepolencic BTW, current enums are technically number subtypes, and I don't see anything innately helpful in making Java-like enums, which are instanceof the enum type itself. Also, it doesn't solve the problem of primitive string enums, which would be helpful for, say, diagnostic messages.

@danielepolencic You can do reverse lookup as Frequency["High"] and Frequency[0] both works. Enumeration also works:

enum Frequency { High, Medium, Low }

for (const key in Frequency) if (isNaN(+key)) console.log(key);
// High, Medium, Low

@danielepolencic as mentioned reverse lookup is supported out of the box :

enum Tristate {
    False,
    True,
    Unknown
}
console.log(Tristate[0]); // "False"
console.log(Tristate[Tristate.False]); // "False" because `Tristate.False == 0`

More : https://basarat.gitbooks.io/typescript/content/docs/enums.html#enums-and-strings :rose:

+1 for string enums.

I'd expect the following ( which actually work if you ignore the errors ;) )

export enum SortDirection {
  asc = 'asc',
  desc = 'desc'
}

I don't know any JS dev who ever wants numbers in this scenario.

I'd like it if the type of SortDirection was a string literal type ('asc'|'desc') instead of just string; or at least if 'asc'|'desc' was implicitly coercable to SortDirection.

I really was expecting the following to work:

enum Direction {
    Up,
    Down,
    Left,
    Right
}

enum Direction {
    Sideways,
    InCircles
}

In my opinion, having enums represented by numbers was a mistake - it doesn't work well with declaration merging. Strings would have had a much lower chance of colliding - they would also have been easier to debug, and they are much more commonly used in everyday JS.

Anyways, for backwards compatibility, you could preserve the current enum behavior (enum would be inferred as enum<number> for BC) and simply add generic enum types, e.g.:

enum<string> Direction {
    Up,
    Down,
    Left,
    Right
}

enum<string> Direction {
    Sideways,
    InCircles
}

These would be much simpler to merge and even check the merge at compile-time to make sure the same name was only defined once. They would auto-initialize as strings, much the same way enums work for numbers now, and of course you'd be allowed to initialize them with a string expression yourself.

If somebody wants an enum<Foo> or even enum<any> for some reason, more power to them - an enum is simply a set of named values. It doesn't need to be more than that, because we still have classes:

class Color {
    constructor(red, green, blue) { ... }

    static Red = new Color(255, 0, 0)
    static Green = new Color(0, 255, 0)
    static Blue = new Color(0, 0, 255)
}

That works just as well - in fact, that's what I'm doing now, since numbered enums don't work well for me, and this works better in terms of declaration merging as well.

I don't know, I don't think we need to come up with something massively complicated for this - we still have classes covering a lot of these requirements, just open up enums to other types besides numbers and I'd be happy :-)

Is there any process?

@basarat I was just playing with the reverse lookup and still can get my head around this:

enum Level {Medium, Low, High}

function printVolume(level: Level): void {
  console.log(`volume is ${level}`);
}

const currentVolume = 'Medium';

printVolume(Level[currentVolume]);

throws an error complaining that Element implicitly has an 'any' type because index expression is not of type 'number'.

I tried to cast it to Level, didn't work either.

image

(Yes, I have "noImplicitAny": true in my tsconfig.json)

@danielepolencic that has more to do with allowing using string literal types for element accesses: #6080

I like the workarounds proposed here, but I couldn't get them to easily work with function overloading the way I wanted them to, so I came up with this. It's verbose, and possibly more complicated than it needs to be, but it gives the compiler enough information that it lets me express things the way I expected to be able to:

Here's the playground link, and here is the code:

type MyEnum = "numberType" | "stringType" | "objectType";

// The advantage of declaring this class is that we could add extra methods to it if we wanted to.
class MyEnumClass {
    NumberType: "numberType" = "numberType";
    StringType: "stringType" = "stringType";
    ObjectType: "objectType" = "objectType";

    // You could declare methods here to enumerate the keys in the enum, do lookups, etc.
}

const MyEnum = new MyEnumClass();


// You can use it like a normal enum: 
let x : string = MyEnum.NumberType;
console.log(x); // prints "numberType" 


// But you can also use it for operator overloading:

function someFunc(x : "numberType") : number;
function someFunc(x : "stringType") : string;
// This declaration is equivalent to the above, and personally I find it more readable
function someFunc(x : typeof MyEnum.StringType) : string;
function someFunc(x : "objectType") : Object;

function someFunc(x : MyEnum) : number | string | Object;
function someFunc(x : MyEnum) : number | string | Object
{
    switch(x){
        case MyEnum.NumberType:
            return 5;
        case MyEnum.StringType:
            return "a string";
        case MyEnum.ObjectType:
            return {foo : "bar"};
    }
}

let someNumber1 : number = someFunc(MyEnum.NumberType)
let someString : string = someFunc(MyEnum.StringType);
let someObject : Object = someFunc(MyEnum.ObjectType);

// And this errors with "Type 'string' is not assignable to type 'number'" just as we would expect:
let someNumber2 : number = someFunc(MyEnum.StringType);

Things have shifted a bit with the introduction of string literal types. Picking up from @isiahmeadows 's proposal above https://github.com/Microsoft/TypeScript/issues/1206#issuecomment-121926668 , considering a restricted "string-only" enum that would behave as follows

// Please bikeshed syntax!
enum S: string {
  A,
  B = "X",
  C
}

would be _exactly_ equivalent to this code:

namespace S {
  export const A: "A" = "A";
  export type A = "A";
  export const B: "X" = "X";
  export type B = "X";
  export const C: "C" = "C";
  export type C = "C";

  // Imagine this were possible
  [s: string]: S;
}
type S = S.A | S.B | S.C;

the equivalent code starting with const enum S: string { would be identical in the type system, but not have any emit. I'll sketch up an implementation in the next few weeks and we'll see how it looks.

To clarify:

[…] the equivalent code starting with const enum S: string { would be identical in the type system, but not have any emit.

Does that mean that the following code …

const enum S: string {
  A,
  B = "X",
  C
};

var strings = [S.A, S.B, S.C];

… would be transpiled with inlined string values, like this?

var strings = ["A", "X", "C"];

Correct

Great, that would be useful in many situations. Very much looking forward to further work on this one!

I could see that as potentially useful myself. I've been personally toying
with the idea of rewriting the DOM API definitions from the ground up with
the latest TS, and that would also help clean up many of the occasionally
magical string and boolean parameters used all over the place in it so
they're more descriptive.

(IIRC const enums work as expected in definition files, correct?)

On Fri, Aug 19, 2016, 12:28 Marius Schulz [email protected] wrote:

Great, that would be useful in many situations. Very much looking forward
to further work on this one!


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/Microsoft/TypeScript/issues/1206#issuecomment-241065785,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AERrBLcSvWekopof3YsYZCtBP6vGiz7Nks5qhdmQgaJpZM4C9e4r
.

@RyanCavanaugh I'm hoping Object.keys, Object.values, and Object.entries behave sanely for your revised enums (they should, as long as nothing extra gets tacked on).
The current implementation of enums has properties for both keys and values (for reverse-lookup), so you get a mixture of both when iterating.

@errorx666 I'd expect they would, unless any of the strings clash.

@RyanCavanaugh Could symbols be included in that addition? That'd be fairly useful as well. (They can't be in const enums, but they would make for safer JS interop.)

Should string enums still be generated with the reverse mapping? (value to key)

In some cases it's useful to be able to determine whether a value exists in the enum at runtime. You can currently do that by only looking at numeric values. If both keys and values are strings, I'm not sure how to accomplish that

I'd say that's a strong argument for not producing the reverse map. Object.keys / Object.values would then produce a clean set of key names / values. Going from value -> key would be slightly awkward but I can't imagine why you'd need to do that.

Yeah, turning the reverse map off would be fantastic - or at least behind a flag

On Tue, Aug 30, 2016, 17:31 Ryan Cavanaugh [email protected] wrote:

I'd say that's a strong argument for not producing the reverse map.
Object.keys / Object.values would then produce a clean set of key names /
values. Going from value -> key would be slightly awkward but I can't
imagine why you'd need to do that.


You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/Microsoft/TypeScript/issues/1206#issuecomment-243623868,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAChnUl8PHgQ69whvb2xbzvHMkdzqOj2ks5qlMtngaJpZM4C9e4r
.

I agree in the reverse mapping not existing for strings for that very
reason. I don't usually need them anyways (I usually create the reverse
mapping manually in JS, and I've rarely needed it in practice outside of
pretty printing, which is also rare because of the type system).

On Tue, Aug 30, 2016, 20:39 Ian MacLeod [email protected] wrote:

Yeah, turning the reverse map off would be fantastic - or at least behind a
flag

On Tue, Aug 30, 2016, 17:31 Ryan Cavanaugh [email protected]
wrote:

I'd say that's a strong argument for not producing the reverse map.
Object.keys / Object.values would then produce a clean set of key names /
values. Going from value -> key would be slightly awkward but I can't
imagine why you'd need to do that.


You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<
https://github.com/Microsoft/TypeScript/issues/1206#issuecomment-243623868
,
or mute the thread
<
https://github.com/notifications/unsubscribe-auth/AAChnUl8PHgQ69whvb2xbzvHMkdzqOj2ks5qlMtngaJpZM4C9e4r

.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/Microsoft/TypeScript/issues/1206#issuecomment-243625089,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AERrBOMGFSmm-RMOGOybVTbyv2kBSgB6ks5qlM01gaJpZM4C9e4r
.

@RyanCavanaugh Would I be able to use the enum as an indexer? E.g.

enum S: string {
  A,
  B = "X",
  C
};
const foo = {} as { [ key: S ]: number };

It doesn't work if I use your "exactly equivalent" code. "An index signature parameter type must be 'string' or 'number'." (I suppose this actually applies to literal types in general.)

@errorx666 see #5683 for that

@RyanCavanaugh Any chance for this to land in 2.1.0 or is it further future ?

No idea. We just finished 2.0 and are still working out what's in the 2.1 roadmap.

@RyanCavanaugh Any chance this will end up on the roadmap now?

@RyanCavanaugh Does the idea of string enums (or enums of other primitive types) conflict with Typescript's design goals? Or is it just a matter of the right implementation?

@zspitz What about using this technique ?
http://angularfirst.com/typescript-string-enums/

@NN There are two issues, one minor, and one major:

  • (Minor) The pattern needs modification before it can be used in an ambient context, say a declaration file
  • (Major) The values of the enum are not inlined #9729

The benefits of inlining are that the library could provide a set of named constants which could be used from Typescript, even though they might not exist / be accessible at runtime.

I lining is a general problem, it can be applied to everything.

3964

@NN--- thanks for the link, very interesting. The only big difference between the technique you posted and an enum is that with the latter I can access the types as properties. As an example, this is how one would implement redux's actions in typescript:

export type INCREMENT_COUNTER = 'App/INCREMENT_COUNTER';
export const INCREMENT_COUNTER : INCREMENT_COUNTER = 'App/INCREMENT_COUNTER';

export type DECREMENT_COUNTER = 'App/DECREMENT_COUNTER';
export const DECREMENT_COUNTER : DECREMENT_COUNTER = 'App/DECREMENT_COUNTER';

export type IncrementCounterAction = {
    type: INCREMENT_COUNTER,
    by: number
};
export type DecrementCounterAction = {
    type: DECREMENT_COUNTER,
    by: number
};

type Action = IncrementCounterAction | DecrementCounterAction;

function reducer<S>(state: S, action: Action): S {
  switch(action.type) {
    case DECREMENT_COUNTER:
      //action has .type & .by correctly detect by the compiler 
  }
}

The same code can be easily converted to enums:

enum Actions {
  INCREMENT_COUNTER,
  DECREMENT_COUNTER
}

export type IncrementCounterAction = {
    type: Actions.INCREMENT_COUNTER,
    by: number
};
export type DecrementCounterAction = {
    type: Actions.DECREMENT_COUNTER,
    by: number
};

type Action = IncrementCounterAction | DecrementCounterAction;

function reducer<S>(state: S, action: Action): S {
  switch(action.type) {
    case Actions.DECREMENT_COUNTER:
      //action has .type & .by correctly detect by the compiler
  }
}

but it falls short when I use the workaround:

export const Actions = {
  INCREMENT_COUNTER: 'INCREMENT_COUNTER' as 'INCREMENT_COUNTER',
  DECREMENT_COUNTER: 'DECREMENT_COUNTER' as 'DECREMENT_COUNTER'
}
export type Actions = (typeof Actions)[keyof typeof Actions];

export type IncrementCounterAction = {
    type: Actions.INCREMENT_COUNTER, // ERROR. no way to get type INCREMENT_COUNTER
    by: number
};
export type DecrementCounterAction = {
    type: Actions.DECREMENT_COUNTER,  // ERROR. no way to get type DECREMENT_COUNTER
    by: number
};

type Action = IncrementCounterAction | DecrementCounterAction;

function reducer<S>(state: S, action: Action): S {
  switch(action.type) {
    case Actions.DECREMENT_COUNTER:
      //action has .type & .by correctly detect by the compiler
  }
}

@danielepolencic You may be interested in my solution using ngrx. Here's an issue I created in their repo about updating their sample for typescript 2.1+. It shows pre- and post- typescript 2.1+ versions using discriminated unions. TS 2.1+ introduced a breaking change (arguable a bug fix) regarding how string literals are handled.

Anyway, here's the ngrx issue about it, and here's a direct link to the typescript playground referenced in that issue.

@danielepolencic
Instead of

export type IncrementCounterAction = {
    type: Actions.INCREMENT_COUNTER, // ERROR. no way to get type INCREMENT_COUNTER
    by: number
};
export type DecrementCounterAction = {
    type: Actions.DECREMENT_COUNTER,  // ERROR. no way to get type DECREMENT_COUNTER
    by: number
};

try

export type IncrementCounterAction = {
    type: typeof Actions.INCREMENT_COUNTER,
    by: number
};
export type DecrementCounterAction = {
    type: typeof Actions.DECREMENT_COUNTER,
    by: number
};

@errorx666 @rob3c nice! thanks!

There still a lot of boiler plate, but it's definitely usable.

👍

Some discussion today. Unorganized notes; refer to https://github.com/Microsoft/TypeScript/issues/1206#issuecomment-240581743 for a mini-spec with some changes as follows

  • We don't want some alternate syntax at the declaration site - it's already in the "Oops we added top-level expression code" area and we want to keep that surface area absolutely minimum
  • Generating the reverse map would be a terrible idea

    • Higher chance of conflict between keys and values

    • You'd certainly want to be able to write a "Is this a valid key?" or "Is this a valid value?" function for a string enum; the reverse map would necessarily be a commingling of both

  • Because of transpile, we can't detect string vs non-string enum by looking at the initializers

    • This means all values would need to be exactly string literals

    • Note: No substitution literals! Remember we need to produce a concrete type out of this

  • Because of transpile, we can't detect string vs non-string enum by looking at the initializers

    • This means _all values would need to be exactly string literals_

Would this restrict enums to just primitives (or even just number/string) - or would it be more widely applicable to any value (that exactly matches the enum's type)?

I don't think enums with values other than strings or numbers are on the table at this point. It's unclear what a const enum with a reference type value would mean, and existing solutions (see https://github.com/Microsoft/TypeScript/issues/1206#issuecomment-225813145) seem to be doing pretty well. And we're definitely not adding boolean enums :wink:

Makes sense

And we're definitely not adding boolean enums 😉

Pff, I swear there are _totally legit_ reasons for

enum bool: boolean = {
  true = false,
  false = true,
};

Actually you can achieve that with

enum bool {
  true = 0,
  false = 1
}

Hah

On Tue, Apr 4, 2017 at 3:05 PM Daniel Busłowicz notifications@github.com
wrote:

Actually you can achieve that with

enum bool {
true = 0,
false = 1
}


You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/Microsoft/TypeScript/issues/1206#issuecomment-291645840,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAChndeXT4OF-SiF0Vd16Au4nZcYIGh9ks5rsr6fgaJpZM4C9e4r
.

@RyanCavanaugh Is it possible to allow reference type enums provided they aren't const enums? I don't see why every enum type has to have a const enum variant.

@nevir Don't forget FileNotFound.

@isiahmeadows it's possible, but it'd have to be well-justified because it's a lot more complexity. For a string enum we can just produce a union type out of the literal types of its values, but there's no corresponding behavior for reference types because there's no such thing as a literal type for a reference type value.

@RyanCavanaugh Oh, I see now, and it's not really a short term need for me.

Maybe, in the future, could nominal subtyping could help?

In my scenario I needed sort of custom object enum, since my class does not have any method that would make the inheritance necessary, I just used this class with static props:

export class ViewerItemCardType {
    public static Big: ViewerItemCardType = new ViewerItemCardType(1, "FeaturedBig", 330, 660);
    public static Medium: ViewerItemCardType = new ViewerItemCardType(2, "FeaturedSmall", 155, 310);
    public static Small: ViewerItemCardType = new ViewerItemCardType(3, "NormalArticle", 100, 200);
    private constructor(
        public id: number,
        public name: string,
        public imageHeight: number,
        public imageWidth: number
    ) { };
}

I can access to these "complex" enums like:

ViewerItemCardType.Big.imageHeight
ViewerItemCardType.Big
ViewerItemCardType.Small

@isiahmeadows , Does that particular scenario match with your definition at some point ?

@jquintozamora that's awesome!

I think you can infer the extra type-hints though, and you'd likely want to define a means of enumerating the options as well, depending on your use-case - so like:

export class ViewerItemCardType {
    public static Big = new ViewerItemCardType(1, "FeaturedBig", 330, 660);
    public static Medium = new ViewerItemCardType(2, "FeaturedSmall", 155, 310);
    public static Small = new ViewerItemCardType(3, "NormalArticle", 100, 200);
    public static All: ViewerItemCardType[] = [
        ViewerItemCardType.Big,
        ViewerItemCardType.Medium,
        ViewerItemCardType.Small
    ]
    private constructor(
        public id: number,
        public name: string,
        public imageHeight: number,
        public imageWidth: number
    ) { };
}

@jquintozamora also note that it's a closed set though - you can't use declaration merging to add new values, so that's another thing we'd (hopefully) get from real typed enums.

Hi @mindplay-dk ,
In my current scenario is really helpful when used in combitation with react - style attribute.
Indeed, it would be good to have a official solution for that like real typed complex enums. :)

Implementation now available in #15486.

I released ts-enums as a library that enables creating full-class, Java-style enums. Maybe it can be useful for some people on this thread.

Suggestions for improvements are welcome :)

With Angular 2
//enum

export enum IType
{
Vegitable=0,
Fruits=1,
Fish=2
}

// angular 2 Component in type script

import {IType} from '/itype';
export class DataComponent
{
getType(id:number):any
{
      return IType[id];
}
}

// in your html file

<div>
{{getType(1)}}
</div>
Was this page helpful?
0 / 5 - 0 ratings

Related issues

bgrieder picture bgrieder  ·  3Comments

manekinekko picture manekinekko  ·  3Comments

kyasbal-1994 picture kyasbal-1994  ·  3Comments

siddjain picture siddjain  ·  3Comments

jbondc picture jbondc  ·  3Comments