In general I thought that using enums is "compatible" with using literal types - (where enums give better support for refactoring, Find all references, intellisense etc.), but seems it's not the case:
Consider the code:
enum S {
A, B, C
}
type T = 0 | 1 | 2
function aTest(a: S) {}
function aTest2(a: S.A | S.B) {}
function bTest(b: T) {}
function bTest2(b: 0|1) {}
aTest(5) // BAD: Does not throw an error
aTest2(S.C) // GOOD: Throws an error
aTest2(5) // BAD: Does not throw an error
bTest(5) // GOOD: Throws an error
bTest2(5) // GOOD: Throws an error
const a:S = 5 // BAD: Does not throw an error
const b:T = 5 // GOOD: Throws an error
Somewhere deep in the search one can find this explanation (not in the docs) https://github.com/Microsoft/TypeScript/issues/8020#issuecomment-208717449 that TS does not distinguish between flag and non-flag enums - that's why no errors where one would expect
Maybe it would be worth introducing it ? - because I guess a lot of people would prefer enums to be used as unions and have TS throw an error if someone tries to assign wrong values.
It will be even more inconsistent if string enums will make it to TS: https://github.com/Microsoft/TypeScript/issues/1206#issuecomment-240581743
The same example then:
enum S: string {
A,
B = "X",
C
}
type T = 'A' | 'X' | 'C'
function aTest(a: S) {}
function aTest2(a: S.A | S.B) {}
function bTest(b: T) {}
function bTest2(b: 0|1) {}
aTest('W') // GOOD: Error
aTest2(S.C) // GOOD: Error
aTest2('W') // GOOD: Error
bTest(5) // GOOD: Error
bTest2(5) // GOOD: Error
const a:S = "W" // GOOD: Error
const b:T = "W" // GOOD: Error
Enums are currently number subtypes, so that could be a source of issues.
This in particular seems surprising and really just a bug: aTest(5) should fail regardless.
Since this is going to be closed soon due to thumbs-down (totally understand braking change) just a quick question - would introducing flag for non-flag enums make sense ? I know you don't want any more flags :D
It's just that probably majority of "normal users" would prefer non-flag enums behavior (unless you are writing compiler ;) - but I might be wrong :) But since it doesn't look like string enums will ever make it - then there won't be any inconsistency in the first place.
Why not write this?
namespace S {
export const A = 0;
export type A = typeof A;
export const B = 1;
export type B = typeof B;
export const C = 2;
export type C = typeof C;
// If you need reverse mapping
Object.entries(S).reduce((result, [key, value]) => {
result[value] = key;
return result;
}, S);
};
type S = S.A | S.B | S.C;
type T = S;
function aTest(a: S) { }
function aTest2(a: S.A | S.B) { }
function bTest(b: T) { }
function bTest2(b: 0 | 1) { }
aTest(5); // GOOD: Throws an error
aTest2(S.C); // GOOD: Throws an error
aTest2(5); // GOOD: Throws an error
bTest(5); // GOOD: Throws an error
bTest2(5); // GOOD: Throws an error
const a: S = 5; // GOOD: Throws an error
const b: T = 5; // GOOD: Throws an error
@wallverb sadly we tried this a few months ago and it caused breaks on our test corpus. So that's why it got the thumbs down in the backlog slog.
Not worth the breaking change. Maybe we'll add a flag someday if there turns out to be a lot of demand