You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Enums behave unexpectedly, the workaround is verbose. There should be a less verbose way. #32690
constenumMyEnum{Zero,One}constfoo: MyEnum.Zero=0// Ok as expected (since MyEnum.Zero is zero)constbar: MyEnum.Zero=1// OK, but expected Error!
...people start using this workaround (aka "namespace-as-enum"):
namespaceMyEnum{exportconstZero=0;exporttypeZero=typeofZero;exportconstOne=1;exporttypeOne=typeofOne;}typeMyEnum=typeofMyEnum[keyoftypeofMyEnum];constfoo: MyEnum.Zero=0// okay as expectedconstbar: MyEnum.Zero=1// error as expected
Maybe there should be a less verbose way to do this common stuff?
For example it could be something like enum MyEnum {type Zero, type One}
Use Cases
I use number values (i.e. enums) extensively instead of string values for performance reasons (a lot of JSON.stringify/parse).
Examples
// Syntax A...constenumMyEnum{typeZero,typeOne}// ...or syntax B (C++ style) constenumclassMyEnum{Zero,One}constfoo: MyEnum.Zero=0// Ok constbar: MyEnum.Zero=1// Error!
There are probably a bunch of other issues related to this.
The point being that enums, as they are now, are only really useful if you use bit masks and don't care about variables of that type being any number.
However, that runs counter to how one usually thinks about enums. If anything, people that use bitwise operators on numeric enum values should get back a number that is not assignable to the original enum type.
If backwards compatibility for this thing is so important, then, as above, new syntax for "proper" enums should be introduced that's a shorthand for the namespace-as-enum workaround.
I don't have hard numbers but I feel like most people who use numeric enums expect it to behave like the namespace-as-enum workaround.
Whenever someone asks me why their enum code isn't working (at work, on gitter, where ever), I just tell them because enums are broken and to not use them. It would be nice to have it unbroken
Maybe introduce syntax like enum class (like in c++).
Except, cpp enum classes are a little too restrictive.
It would be nice if TS enum classes would have the behavior of the namespace-as-enum workaround.
Yep, we only need a syntax sugar (I know it might be much harder done than said) for the namespace-as-enum workaround. The behavior of the workaround should remain (it works 100% fine)..
The TypeScript handbook itself states that if all enum members are initialized to a static value,
... enum types themselves effectively become a union of each enum member. While we haven’t discussed union types yet, all that you need to know is that with union enums, the type system is able to leverage the fact that it knows the exact set of values that exist in the enum itself.
This is simply not true, but I wish it was.
What concerns me the most is that this behavior is in conflict with certain type inference rules such as exhaustiveness checking when using strictNullChecks.
For example, the following is considered exhaustive, but in reality isn't:
enumDirection{Left=1,Right=2}functiondirectionName(dir: Direction): string{switch(dir){caseDirection.Left: return"left";caseDirection.Right: return"right";}}// both calls return undefined, even if that shouldn't be possibleconsole.log(directionName(Direction.Left|Direction.Right));console.log(directionName(42));
I was aware that you can use enums as bit flags and combine them using bitwise logic operators, but I assumed that the compiler somehow distinguished between the two cases based on some conventions.
For example, it could activate bit flag behavior only if all constants are initialized to a single bit or a combination of other constants, and otherwise fallback to a strict union type of all constants.
enumDirectionFlags{// single bitsUp=1<<0,Right=1<<1,Down=1<<2,Left=1<<3,// predefined combinationsUpRight=Up|Right,DownRight=Down|Right,DownLeft=Down|Left,UpLeft=Up|Left}
It really think it would be much safer and more convenient to only allow bit flag usage if the enum definition meets certain conditions, to make sure it really was intended to be used in this way.
Also, I don't think flags are used very often.
And of course, a distinction on the syntax level would be even better.
I understand that changing the behavior of enums breaks backward compatibility.
On the other hand, perhaps a large number of developers have an incorrect intuition of how enums work, so maybe the change fixes more than it breaks.
Reacted by Emily Marigold Klassen, Alexey Gerasimov, Nikolay Babanov, Remi Liu, hoclun-rigsep, David Hearnden and Dimitris Karagiannis
Just stumbled upon this and I have to say that I'm really surprised about the current behavior. To me, the keyword enum clearly communicates that only the enumerated values are allowed, not just all numbers. And what's even more surprising is that enums do work as expected with strings. They're even super-strict with strings, treating enum members effectively as symbols:
enumState{Success="SUCCESS",Error="ERROR",}conststate: State="SUCCESS";// Error, because it's not State.Success// This is how symbols work
To me, this behavior is very unexpected and also inconsistent. And given the long list of issues (#17734, #21546, #11559, #15591, #8020, #18409, ...) I'm not the only one that finds that surprising.
I do understand that this is a massive breaking change, especially for the TypeScript project itself. But I think that this needs to be addressed somehow. I honestly never used bit masks in my projects, but I do use enums all the time and there are a lot of places where I relied on the exhaustiveness check (which clearly doesn't work as you can pass in any number).
What about a symbol enum or enum symbol keyword:
enumsymbolState{Success,Error,}// Syntactic sugar for:constState={Success: Symbol("Success")asunique symbol,// as unique symbol is currently notError: Symbol("Error")asunique symbol,// allowed but you probably get the gist :)};typeState=typeofState.Success|typeofState.Error;letstate: State;state=State.Success;// Worksstate="Success";// Error, because it must be the State.Success symbol
This would also be a good trade-off between easy to write enums that do not require you to re-spell the string literal (as string enums currently do) while maintaining debuggability because of symbol descriptions.
Reacted by Emily Marigold Klassen, Haroldo de Oliveira Pinheiro, Matthias Perktold, hanstzou, Nikolay Babanov, James, David Hearnden, Doug Ayers, Dimitris Karagiannis and mg90707
I would also appreciate a proper fix for the enums. As they stand now, they seem a bit useless.
enumMyEnum{B=3}functionfoo(param: MyEnum){}foo(MyEnum.B);// This should be the only working example.foo('asdfasdf');// This does not work, which is good.foo(2);// This should also not work, but it does. :(
Reacted by Nurbol Alpysbayev, Luke , Florent Catiau-Tristant, David Hearnden, syiml, 低级工程师 and Ľudmila Šimková
I'm surprised there isn't a simple way to create a typesafe enum distinct from number. isn't the whole point of typescript to add type-safety to javascript? allowing arbitrary values to be assigned to an enum-typed value seems contrary to this goal.
The whole "bit bitfields are convenient" argument seems specious - assigning a number to a string is convenient, but wrong. using enums like this is a C-ism - fixed in C++.
enum EnumTest {
One = 1, // => typeof EnumTest.One in compile time: EnumTest.One (alias for number), runtime: number
Two = "two", // => typeof EnumTest.Two in compile time: EnumTest.Two (alias for some type be treated as Symbol('two')), runtime: string
}
=> It clearly leads to an unsafe type as in many cases above.
Suggestion:
enum EnumTest {
One = 1, // => typeof EnumTest.One in compile time: EnumTest.One (alias for 1), runtime: number
Two = "two", // => typeof EnumTest.Two in compile time: EnumTest.Two (alias for 'two'), runtime: string
}
Current solution:
Never use "enum" with the initial value as a number (it is not a safe type). Use string initial instead of, the problem here just is we can't directly assign a string value to a variable had enum type
enumMyEnum{B=3}functionfoo(param: MyEnum){}foo(MyEnum.B);// This should be the only working example.foo('asdfasdf');// This does not work, which is good.foo(2);// This should also not work, but it does. :(
Seems like this is finally fixed in TypeScript v5!
Activity
AnyhowStep commentedon Aug 3, 2019
Please don't close this issue as "working as intended" or "question" or "won't fix". At the very least, leave it open so people can vote on it.
It's pretty annoying that enums are basically just
numberjust to support bit masks/flags when that's not what an enum is really supposed to be#31834
#30629
#26362
#22464
#22311
There are probably a bunch of other issues related to this.
The point being that enums, as they are now, are only really useful if you use bit masks and don't care about variables of that type being any number.
However, that runs counter to how one usually thinks about enums. If anything, people that use bitwise operators on numeric enum values should get back a
numberthat is not assignable to the originalenumtype.If backwards compatibility for this thing is so important, then, as above, new syntax for "proper" enums should be introduced that's a shorthand for the
namespace-as-enumworkaround.I don't have hard numbers but I feel like most people who use numeric enums expect it to behave like the
namespace-as-enumworkaround.Whenever someone asks me why their enum code isn't working (at work, on gitter, where ever), I just tell them because enums are broken and to not use them. It would be nice to have it unbroken
Maybe introduce syntax like
enum class(like in c++).Except, cpp enum classes are a little too restrictive.
It would be nice if TS enum classes would have the behavior of the namespace-as-enum workaround.
anurbol commentedon Aug 3, 2019
Yep, we only need a syntax sugar (I know it might be much harder done than said) for the namespace-as-enum workaround. The behavior of the workaround should remain (it works 100% fine)..
forivall commentedon Aug 10, 2019
i think the
--preserveConstEnumscompiler flag can be used for that, although I haven't used it. It's worth investigatingAnyhowStep commentedon Sep 5, 2019
Anyway, one reason for wanting a new type of
enumis because the currentenumtype does not reflect the actual run-time values.Playground
mperktold commentedon Dec 6, 2019
This behavior is really counter-intuitive.
The TypeScript handbook itself states that if all enum members are initialized to a static value,
This is simply not true, but I wish it was.
What concerns me the most is that this behavior is in conflict with certain type inference rules such as exhaustiveness checking when using
strictNullChecks.For example, the following is considered exhaustive, but in reality isn't:
I was aware that you can use enums as bit flags and combine them using bitwise logic operators, but I assumed that the compiler somehow distinguished between the two cases based on some conventions.
For example, it could activate bit flag behavior only if all constants are initialized to a single bit or a combination of other constants, and otherwise fallback to a strict union type of all constants.
It really think it would be much safer and more convenient to only allow bit flag usage if the enum definition meets certain conditions, to make sure it really was intended to be used in this way.
Also, I don't think flags are used very often.
And of course, a distinction on the syntax level would be even better.
I understand that changing the behavior of enums breaks backward compatibility.
On the other hand, perhaps a large number of developers have an incorrect intuition of how enums work, so maybe the change fixes more than it breaks.
jhnns commentedon Dec 21, 2019
Just stumbled upon this and I have to say that I'm really surprised about the current behavior. To me, the keyword
enumclearly communicates that only the enumerated values are allowed, not just all numbers. And what's even more surprising is that enums do work as expected with strings. They're even super-strict with strings, treating enum members effectively as symbols:To me, this behavior is very unexpected and also inconsistent. And given the long list of issues (#17734, #21546, #11559, #15591, #8020, #18409, ...) I'm not the only one that finds that surprising.
I do understand that this is a massive breaking change, especially for the TypeScript project itself. But I think that this needs to be addressed somehow. I honestly never used bit masks in my projects, but I do use enums all the time and there are a lot of places where I relied on the exhaustiveness check (which clearly doesn't work as you can pass in any number).
What about a
symbol enumorenum symbolkeyword:This would also be a good trade-off between easy to write enums that do not require you to re-spell the string literal (as string enums currently do) while maintaining debuggability because of symbol descriptions.
nbabanov commentedon Jun 22, 2020
I would also appreciate a proper fix for the enums. As they stand now, they seem a bit useless.
forivall commentedon Jun 22, 2020
For the case in #32690 (comment)
A workaround for this is in the following gist: https://gist.github.com/forivall/8968ec4ef450ceac9949b3a25583ca6d It leverages the behaviour that an enum can be treated as a const value, and makes it work the other way around.
hoclun-rigsep commentedon Jul 8, 2021
I went with this:
It isn't completely DRY but it's close: the repetition is short and contained to each line. Would love some feedback.
RyanCavanaugh commentedon Jul 8, 2021
hoclun-rigsep you can write
as conston the outside instead ofas 0x1on each linehoclun-rigsep commentedon Jul 8, 2021
Amazing! Cuts down on the RY. Already glad I posted.
Spongman commentedon Jun 27, 2022
I'm surprised there isn't a simple way to create a typesafe enum distinct from
number. isn't the whole point of typescript to add type-safety to javascript? allowing arbitrary values to be assigned to an enum-typed value seems contrary to this goal.The whole "bit bitfields are convenient" argument seems specious - assigning a number to a string is convenient, but wrong. using enums like this is a C-ism - fixed in C++.
hoangfnv commentedon Jul 26, 2022
Current:
=> It clearly leads to an unsafe type as in many cases above.
Suggestion:
Current solution:
nbabanov commentedon Aug 25, 2022
Daniel Rosenwasser (@DanielRosenwasser) Hey Dan, what do you think about this issue? A lot of people have to write more verbose code because of this.
mkarajohn commentedon Sep 1, 2022
Bumped into this today as well, just for the record.
nbabanov commentedon Sep 2, 2022
Ryan Cavanaugh (@RyanCavanaugh) Any breakthrough here?
nbabanov commentedon Apr 6, 2023
Seems like this is finally fixed in TypeScript v5!
Thanks a lot for the work! 🎊