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
Number literal should not extend enum of different value #31834
I must say that I don't like this behaviour of numeric enums.
Luckily, I don't use numeric enums often. Mostly string enums. But that's why I expected numeric enums to work the same.
The whole bit flag thing sounds like a terrible idea. If one is sure (Enum.A | Enum.B) is of type Enum, they should be the ones who are inconvenienced and should add an explicit cast, if TS won't do type-level bitwise-or and check assignability.
Allowing arbitrary numbers to assign to numeric enum types just takes away from the whole type safety thing. It's enough of a deal breaker that I just can't use numeric enums at all.
I tried using numeric enums for a project this weekend and it's just been thoroughly unpleasant.
It's bad for generic code, bad for non-generic code, bad as an index signature, bad in mapped object types, bad for discriminated unions, bad for type safety.
It's a real shame because now I have to union the 50+ http status codes in existence, rather than have a single numeric enum with 50+ elements.
I was getting weird problems where I could assign 200 to an enum called Non2xxStatusCode. Thank God I write compile-time tests and run-time tests.
/Nerd-rage
I understand breaking existing functionality is usually a bad idea but how often is the existing behavior actually desirable?
When I've wanted bit masks, I used int types. Maybe have an enum to represent each flag. But the mask itself is never an enum.
I mean, each flag means 2^(# flags) possible mask values. Unless your enum has 2^(# flags) elements, it really should be an int.
If we can't break the existing behavior, can we introduce a different enum type that behaves like an actual numeric enum?
The existing workaround with namespaces and type unions is just a tonne of boilerplate.
And I would like to actually use a numeric enum, see StatusCode.NOT_FOUND, and have it behave like a regular enum.
Not use some workaround and see 404, 419, 512, etc. How many developers even remember all the uncommon status codes off the top of their head?
enumE{A=1,B=2,}declareconstdiscriminated: ({d: E.A,value: "A",}|{//E.B is 2//2 is 2d: E.B|2,value: "B",});if(discriminated.d==1){/** Expected: Assignment works Actual : Type '"A" | "B"' is not assignable to type '"A"'. */constv: "A"=discriminated.value;}if(discriminated.d==2){/** Expected: Assignment works Actual : Type '"A" | "B"' is not assignable to type '"B"'. */constv: "B"=discriminated.value;}if(discriminated.d==E.A){/** Expected: Assignment works Actual : Type '"A" | "B"' is not assignable to type '"A"'. */constv: "A"=discriminated.value;}if(discriminated.d==E.B){/** Expected: Assignment works Actual : Type '"A" | "B"' is not assignable to type '"B"'. */constv: "B"=discriminated.value;}/////////////////////////////////////////////////////declareconstdiscriminated2: ({d: E.A,value: "A",}|{//E.B is 2d: E.B,value: "B",});if(discriminated2.d==1){/** Expected: Assignment works Actual : Type '"A" | "B"' is not assignable to type '"A"'. */constv: "A"=discriminated2.value;}if(discriminated2.d==2){/** Expected: Assignment works Actual : Type '"A" | "B"' is not assignable to type '"B"'. */constv: "B"=discriminated2.value;}if(discriminated2.d==E.A){/** Expected: Assignment works Actual : Assignment works */constv: "A"=discriminated2.value;}if(discriminated2.d==E.B){/** Expected: Assignment works Actual : Assignment works */constv: "B"=discriminated2.value;}
enumE{A=1,B=2,C=3,}declareconstdiscriminated: ({d: 1,value: "A",}|{d: 2,value: "B",});if(discriminated.d==1){/** Expected: Assignment works Actual : Assignment works */constv: "A"=discriminated.value;}if(discriminated.d==2){/** Expected: Assignment works Actual : Assignment works */constv: "B"=discriminated.value;}/** Expected: Equality check not allowed Actual : Equality check not allowed*/if(discriminated.d==3){}//Equality check is allowed, but narrowing not applied?if(discriminated.d==E.A){/** Expected: Assignment works Actual : Type '"A" | "B"' is not assignable to type '"A"'. */constv: "A"=discriminated.value;}//Equality check is allowed, but narrowing not applied?if(discriminated.d==E.B){/** Expected: Assignment works Actual : Type '"A" | "B"' is not assignable to type '"B"'. */constv: "B"=discriminated.value;}/** Expected: Equality check not allowed Actual : Equality check allowed*/if(discriminated.d==E.C){}
It seems that oneShouldExtendA should also be "n" and not "y". In the perfect parallel universe where bit flags never existed, E is narrower than number; E.A should be equivalent to 1 & E. (Of course that intersection doesn't even work for string enums, see #21998).
E is narrower than number. But E.A and the literal 1 should be assignable to each other (in my head anyway).
But I wouldn't mind your expected behavior if it's just applied consistently.
I'd like it if both enum elements and number literals narrowed numeric enum types.
It just makes sense in my head that E.A is just an alias for the type 1 and E is just an alias for 1|2. Basically, I want the namespace-workaround to be the "real" behavior of numeric enums.
I had a discriminated union where the http status code, represented with an enum, was the discriminant.
I'd like it if both HttpStatusCode.NOT_FOUND and 404 can be used to narrow the union type.
And there was also other weird behavior where I needed a lot of conditional types to get the "correct" type I was looking for.
It's not narrower in practice though, because like he said, bitfields are a thing. E.A | E.B (bitwise OR, not a type union) might yield a value not listed in the enum. That said, E.A not being a literal 1is kind of weird.
Activity
AnyhowStep commentedon Jun 9, 2019
#12647 seems somewhat related
I must say that I don't like this behaviour of numeric enums.
Luckily, I don't use numeric enums often. Mostly string enums. But that's why I expected numeric enums to work the same.
The whole bit flag thing sounds like a terrible idea. If one is sure
(Enum.A | Enum.B)is of typeEnum, they should be the ones who are inconvenienced and should add an explicit cast, if TS won't do type-level bitwise-or and check assignability.Allowing arbitrary numbers to assign to numeric enum types just takes away from the whole type safety thing. It's enough of a deal breaker that I just can't use numeric enums at all.
I tried using numeric enums for a project this weekend and it's just been thoroughly unpleasant.
It's bad for generic code, bad for non-generic code, bad as an index signature, bad in mapped object types, bad for discriminated unions, bad for type safety.
It's a real shame because now I have to union the 50+ http status codes in existence, rather than have a single numeric enum with 50+ elements.
I was getting weird problems where I could assign
200to an enum calledNon2xxStatusCode. Thank God I write compile-time tests and run-time tests./Nerd-rage
I understand breaking existing functionality is usually a bad idea but how often is the existing behavior actually desirable?
When I've wanted bit masks, I used int types. Maybe have an enum to represent each flag. But the mask itself is never an enum.
I mean, each flag means 2^(# flags) possible mask values. Unless your enum has 2^(# flags) elements, it really should be an int.
If we can't break the existing behavior, can we introduce a different enum type that behaves like an actual numeric enum?
The existing workaround with namespaces and type unions is just a tonne of boilerplate.
And I would like to actually use a numeric enum, see
StatusCode.NOT_FOUND, and have it behave like a regular enum.Not use some workaround and see
404,419,512, etc. How many developers even remember all the uncommon status codes off the top of their head?/Extra-nerd-rage
AnyhowStep commentedon Jun 9, 2019
Just adding some code as a note to myself,
Playground
AnyhowStep commentedon Jun 9, 2019
More notes,
Playground
AnyhowStep commentedon Jun 9, 2019
I ended up just writing yet-another-script to generate the namespace+union enum workaround.
fatcerberus commentedon Jun 9, 2019
Yeah, long story short: numeric enum types are secretly just aliases for
number.They're about as typesafe as C enums - which is to say, not typesafe at all. 😛
jcalz commentedon Jun 10, 2019
It seems that
oneShouldExtendAshould also be"n"and not"y". In the perfect parallel universe where bit flags never existed,Eis narrower thannumber;E.Ashould be equivalent to1 & E. (Of course that intersection doesn't even work for string enums, see #21998).AnyhowStep commentedon Jun 10, 2019
E is narrower than number. But E.A and the literal
1should be assignable to each other (in my head anyway).But I wouldn't mind your expected behavior if it's just applied consistently.
I'd like it if both enum elements and number literals narrowed numeric enum types.
It just makes sense in my head that E.A is just an alias for the type
1andEis just an alias for1|2. Basically, I want the namespace-workaround to be the "real" behavior of numeric enums.I had a discriminated union where the http status code, represented with an enum, was the discriminant.
I'd like it if both
HttpStatusCode.NOT_FOUNDand404can be used to narrow the union type.And there was also other weird behavior where I needed a lot of conditional types to get the "correct" type I was looking for.
fatcerberus commentedon Jun 10, 2019
It's not narrower in practice though, because like he said, bitfields are a thing.
E.A | E.B(bitwiseOR, not a type union) might yield a value not listed in the enum. That said,E.Anot being a literal1is kind of weird.