-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Design Meeting Notes, 7/15/2022 #49927
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
Comments
A "strictEnums" compiler flag has been on my wish list for years, so I'm overjoyed to see this issue! Would this hypothetical flag prevent the following pattern? (I'm hoping for "yes".) enum Vegetable {
Lettuce = 'lettuce',
Carrot = 'carrot',
}
declare const vegetable: Vegetable;
// Bad
if (vegetable === 'lettuce') {
// The TypeScript compiler currently allows this.
}
// Good
if (vegetable === Vegetable.Lettuce) {
} This pattern is dangerous. Say that I swapped the enum values of I'd rather that the TypeScript compiler forced me to change As for number enums, they have this same problem too, but of course it's much worse, because you can actually assign non-valid values at compile-time. I'd hope that a hypothetical strict flag would address all of the issues that this ESLint rule does. |
Didn't understand most of mentioned notes in the bullet list 😅 But anyway thanks for sharing, considering and implementing this thing 👍 |
@Zamiell I don't believe so - the tightening we're considering is around the fact that "classic"/numeric enums are usable in numeric arithmetic operations and are always assignable to export enum Permissions {
None = 0,
Read = 1 << 0,
Write = 1 << 1,
Execute = 1 << 2,
}
// Debatable whether this should be allowed.
// Today, it is.
let p: Permissions = Permissions.Read | Permissions.Write; The "wtf" we have in the notes refers to the fact that a "classic numeric" enum with computed members would usually disallow a string member. Here's an example where an inline string is disallowed: export enum Permissions {
None = 0,
Read = 1 << 0,
Write = 1 << 1,
Execute = 1 << 2,
// Errors - good! ✅
UnrelatedMember = "what?",
} And here's an example where a const unrelated = "what?";
export enum Permissions {
None = 0,
Read = 1 << 0,
Write = 1 << 1,
Execute = 1 << 2,
// This is also disallowed ✅, although the error message is kind of confusing...
UnrelatedMember = unrelated,
} But you can defeat the check through a layer of indirection by referencing an enum. export enum Unrelated {
Unrelated = "what?",
}
export enum Permissions {
None = 0,
Read = 1 << 0,
Write = 1 << 1,
Execute = 1 << 2,
// What? This *is* allowed? 🤔
UnrelatedMember = Unrelated.Unrelated,
}
// And therefore, this is allowed!? 😬
let p: number = Permissions.UnrelatedMember;
// Runtime: 💥
p.toExponential(); So it's unclear what the flag would do (maybe @RyanCavanaugh has thoughts). We could tighten the declarations to not allow strings in these cases to avoid breaking existing code unless under We could also have the flag disallow the default arithmetic behavior as well. Maybe we'd have to come up with a way to mark how you intend the enum to be used. // Possibly now disallowed unless `Permissions` is declared as a "bitflags" enum or something.
let p: Permissions = Permissions.Read | Permissions.Write; The more I see these examples, the more I'm convinced that we should try to fix the string behavior without a flag if we can. @RyanCavanaugh mentioned that it feels like we see code like this with some regularity, but I still don't have a great sense of this. |
@aghArdeshir sorry if there's anything unclear. The notes are taken while the conversation is relatively fast (and while I often participate). I'll admit that likely makes things less clear, but hopefully my last comment clarifies some of the discussion. |
Enabling Computations on String Enums
#49809
Seems strange to allow
const foo: string = "some string"
.: string
annotation should "spoil" it.Aside: wtf?
Is this an aside? Is it irrelevant?
Well, you really don't want to open the hole further.
Most people don't create a lot of indirect computations between enums and constants.
We really have two enums: "classic numeric" and union.
number
, are subtypes ofnumber
, are not assignable to/from most other enums.Usually you want a "classic numeric" enum to support bit-flags. The most common computation you might have is a left-shift.
How would you model operations other than concatenation?
<T>(x: T) => Uppercase<T>
), then the types and values can diverge, and you have divergences between a non-type resolving compiler (e.g. Babel) and TypeScript itself.Two options for fixing classic enums.
What would be the user fix?
number
. Same fix, but you might never have encountered it.We don't like the idea of needing deeper checks to determine the "kind" of enum that you have (vs. looking at the initializers syntactically).
Are people really using strings in classic/numeric enums?
Strictness flag?
Conclusions
The text was updated successfully, but these errors were encountered: