New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow enums of types other than number #1206

Closed
jhlange opened this Issue Nov 19, 2014 · 123 comments

Comments

Projects
None yet
@jhlange

jhlange commented Nov 19, 2014

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.

@jhlange

This comment has been minimized.

jhlange commented Nov 19, 2014

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

@jhlange

This comment has been minimized.

jhlange commented Nov 19, 2014

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)

@basarat

This comment has been minimized.

Contributor

basarat commented Nov 19, 2014

@jhlange I think tagged unions would automatically cater for this use case nicely : #1003

@jhlange

This comment has been minimized.

jhlange commented Nov 19, 2014

@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).

@saschanaz

This comment has been minimized.

Contributor

saschanaz commented Nov 19, 2014

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, ...
@mwisnicki

This comment has been minimized.

mwisnicki commented Dec 30, 2014

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.

@enoshixi

This comment has been minimized.

enoshixi commented Dec 30, 2014

How would this work?

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

This comment has been minimized.

mwisnicki commented Dec 30, 2014

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

@mwisnicki

This comment has been minimized.

mwisnicki commented Dec 30, 2014

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.

@jhlange

This comment has been minimized.

jhlange commented Dec 30, 2014

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" notifications@github.com 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
#1206 (comment)
.

@NN---

This comment has been minimized.

NN--- commented Jan 28, 2015

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
@jhlange

This comment has been minimized.

jhlange commented Feb 20, 2015

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...

@teppeis

This comment has been minimized.

teppeis commented Feb 25, 2015

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.

@mwisnicki

This comment has been minimized.

mwisnicki commented Feb 25, 2015

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

@bvaughn

This comment has been minimized.

bvaughn commented Mar 11, 2015

+1 for the generics suggestion.

@teppeis

This comment has been minimized.

teppeis commented Mar 22, 2015

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

@isiahmeadows

This comment has been minimized.

Contributor

isiahmeadows commented May 17, 2015

+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.)

@jhlange

This comment has been minimized.

jhlange commented May 30, 2015

@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 ?

@mohsen1

This comment has been minimized.

Contributor

mohsen1 commented Jun 16, 2015

+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"
}
@isiahmeadows

This comment has been minimized.

Contributor

isiahmeadows commented Jun 16, 2015

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 notifications@github.com 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
#1206 (comment)
.

@Gambero81

This comment has been minimized.

Gambero81 commented Jul 5, 2015

+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

@danquirk

This comment has been minimized.

Member

danquirk commented Jul 6, 2015

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

@Gambero81

This comment has been minimized.

Gambero81 commented Jul 6, 2015

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

@isiahmeadows

This comment has been minimized.

Contributor

isiahmeadows commented Jul 7, 2015

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),
}
@bertvanbrakel

This comment has been minimized.

bertvanbrakel commented Jul 10, 2015

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.

@isiahmeadows

This comment has been minimized.

Contributor

isiahmeadows commented Jul 10, 2015

@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.

@isiahmeadows

This comment has been minimized.

Contributor

isiahmeadows commented Jul 10, 2015

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?

@RyanCavanaugh

This comment has been minimized.

Member

RyanCavanaugh commented Apr 3, 2017

Some discussion today. Unorganized notes; refer to #1206 (comment) 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
@nevir

This comment has been minimized.

nevir commented Apr 4, 2017

  • 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)?

@RyanCavanaugh

This comment has been minimized.

Member

RyanCavanaugh commented Apr 4, 2017

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 #1206 (comment)) seem to be doing pretty well. And we're definitely not adding boolean enums 😉

@nevir

This comment has been minimized.

nevir commented Apr 4, 2017

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,
};
@Draccoz

This comment has been minimized.

Draccoz commented Apr 4, 2017

Actually you can achieve that with

enum bool {
  true = 0,
  false = 1
}
@nevir

This comment has been minimized.

nevir commented Apr 4, 2017

@isiahmeadows

This comment has been minimized.

Contributor

isiahmeadows commented Apr 5, 2017

@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.

@errorx666

This comment has been minimized.

errorx666 commented Apr 5, 2017

@nevir Don't forget FileNotFound.

@RyanCavanaugh

This comment has been minimized.

Member

RyanCavanaugh commented Apr 5, 2017

@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.

@isiahmeadows

This comment has been minimized.

Contributor

isiahmeadows commented Apr 5, 2017

@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?

@jquintozamora

This comment has been minimized.

jquintozamora commented Apr 23, 2017

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 ?

@mindplay-dk

This comment has been minimized.

mindplay-dk commented Apr 23, 2017

@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
    ) { };
}
@mindplay-dk

This comment has been minimized.

mindplay-dk commented Apr 23, 2017

@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.

@jquintozamora

This comment has been minimized.

jquintozamora commented Apr 24, 2017

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

@ahejlsberg

This comment has been minimized.

Member

ahejlsberg commented May 1, 2017

Implementation now available in #15486.

@mhegazy mhegazy added the Fixed label May 17, 2017

@mhegazy mhegazy added this to the TypeScript 2.4 milestone May 17, 2017

@LMFinney

This comment has been minimized.

LMFinney commented Jul 31, 2017

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 :)

@shafeeqthayyil

This comment has been minimized.

shafeeqthayyil commented Aug 22, 2017

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>

@Microsoft Microsoft locked and limited conversation to collaborators Jun 18, 2018

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.