-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Number literal should not extend enum of different value #31834
Comments
#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 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 /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 Not use some workaround and see /Extra-nerd-rage |
Just adding some code as a note to myself, enum E {
A = 1,
B = 2,
}
declare const discriminated: (
{
d: E.A,
value: "A",
} |
{
//E.B is 2
//2 is 2
d: E.B | 2,
value: "B",
}
);
if (discriminated.d == 1) {
/**
Expected: Assignment works
Actual : Type '"A" | "B"' is not assignable to type '"A"'.
*/
const v: "A" = discriminated.value;
}
if (discriminated.d == 2) {
/**
Expected: Assignment works
Actual : Type '"A" | "B"' is not assignable to type '"B"'.
*/
const v: "B" = discriminated.value;
}
if (discriminated.d == E.A) {
/**
Expected: Assignment works
Actual : Type '"A" | "B"' is not assignable to type '"A"'.
*/
const v: "A" = discriminated.value;
}
if (discriminated.d == E.B) {
/**
Expected: Assignment works
Actual : Type '"A" | "B"' is not assignable to type '"B"'.
*/
const v: "B" = discriminated.value;
}
/////////////////////////////////////////////////////
declare const discriminated2: (
{
d: E.A,
value: "A",
} |
{
//E.B is 2
d: E.B,
value: "B",
}
);
if (discriminated2.d == 1) {
/**
Expected: Assignment works
Actual : Type '"A" | "B"' is not assignable to type '"A"'.
*/
const v: "A" = discriminated2.value;
}
if (discriminated2.d == 2) {
/**
Expected: Assignment works
Actual : Type '"A" | "B"' is not assignable to type '"B"'.
*/
const v: "B" = discriminated2.value;
}
if (discriminated2.d == E.A) {
/**
Expected: Assignment works
Actual : Assignment works
*/
const v: "A" = discriminated2.value;
}
if (discriminated2.d == E.B) {
/**
Expected: Assignment works
Actual : Assignment works
*/
const v: "B" = discriminated2.value;
} |
More notes, enum E {
A = 1,
B = 2,
C = 3,
}
declare const discriminated: (
{
d: 1,
value: "A",
} |
{
d: 2,
value: "B",
}
);
if (discriminated.d == 1) {
/**
Expected: Assignment works
Actual : Assignment works
*/
const v: "A" = discriminated.value;
}
if (discriminated.d == 2) {
/**
Expected: Assignment works
Actual : Assignment works
*/
const v: "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"'.
*/
const v: "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"'.
*/
const v: "B" = discriminated.value;
}
/**
Expected: Equality check not allowed
Actual : Equality check allowed
*/
if (discriminated.d == E.C) {
} |
I ended up just writing yet-another-script to generate the namespace+union enum workaround. |
Yeah, long story short: numeric enum types are secretly just aliases for enum E { A = 1, B = 2, C = 3 };
let e: E = E.C;
e = E.A; // this is perfectly kosher
let n: number = e; // questionable, but makes sense
e = 812; // no error. not cool, man! They're about as typesafe as C enums - which is to say, not typesafe at all. 😛 |
It seems that |
E is narrower than number. But E.A and the literal 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 I had a discriminated union where the http status code, represented with an enum, was the discriminant. I'd like it if both 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. |
TypeScript Version: 3.4.1, 3.5.1
Search Terms: number literal, extend, enum
Code
Expected behavior:
oneShouldNotExtendB
should be"n"
Actual behavior:
oneShouldNotExtendB
is"y"
Playground Link: Playground
Related Issues:
Kind of feels like a different version of #28654 (where true extended false)
The text was updated successfully, but these errors were encountered: