Closed
Description
Bug Report
Compiler shouldn't allow number
to be used where enum
is expected.
🔎 Search Terms
number where enum expected
🕗 Version & Regression Information
- This is a crash
- This changed between versions ______ and _______
- This is the behavior in every version I tried, and I reviewed the FAQ for entries about enums_and_function_params
- I was unable to test this on prior versions because _______
⏯ Playground Link
Playground link with relevant code
💻 Code
export enum StatusEnum {
Cancelled = 4,
Paid = 6
}
export interface Summary {
readonly status: number;
}
export class TripSummary {
private readonly status: StatusEnum;
public constructor(dto: Summary, status: number) {
this.status = status; // status is number, but allowed to be assigned to an enum field
this.setFullStatus(dto.status); // dto.status is number, but allowed to be passed as an enum param
}
private setFullStatus(status: StatusEnum): void {
console.log(status);
}
}
🙁 Actual behavior
Compiler allows number
to be used where enum
is expected. It's incorrect because narrowing conversion is done implicitly while the compiler cannot make sure that a value passed as the enum param is within a range of the enum values.
🙂 Expected behavior
Implicit conversions from number
to enum
should generate an error requiring an explicit conversion to be performed (for exampe, using as
)
Metadata
Metadata
Assignees
Type
Projects
Milestone
Relationships
Development
No branches or pull requests
Activity
fatcerberus commentedon Mar 16, 2022
This is intentional because numeric enums are often used as bitfields, e.g.
FooEnum.Foo | FooEnum.Bar
is typed asnumber
because it’s not explicitly part of the enum. You can use string-valued enums for stricter checks.AbakumovAlexandr commentedon Mar 16, 2022
Can you provide a full line of code to demostrate what you've meant? I'm not sure how this is related to allowing implicitly assigning numbers into enum typed members.
AbakumovAlexandr commentedon Mar 16, 2022
@RyanCavanaugh What is the justification for 'Working as Intended'? Especially in 'strict' mode?
fatcerberus commentedon Mar 16, 2022
computedFlags
should ideally be assignable back to the enum type, ifobj
is using it as a bitfield. However it’s currently typed asnumber
because its actual value is not (necessarily) part ofFlagsEnum
. Thus why this type hole is present.AbakumovAlexandr commentedon Mar 16, 2022
If you needed this type 'abuse' (in my opinion, sorry if too harsh :) ), you always could shut up the compiler explicitly:
So, fixing this TS compiler bug doesn't affect your use case and can't be the justification for the missing type safety related to enums in 'strict' mode.
RyanCavanaugh commentedon Mar 16, 2022
@AbakumovAlexandr because we did this behavior on purpose
fatcerberus commentedon Mar 16, 2022
I’m not making any value judgments here, just telling you why the behavior is the way it is, and why it’s intentional. It’s also unlikely to change—at least, not without an additional opt-in compiler flag—as it would be pretty disruptive to a lot of code in the wild.
AbakumovAlexandr commentedon Mar 16, 2022
Can you elaborate: what is the purpose?
fatcerberus commentedon Mar 16, 2022
🤦♂️
RyanCavanaugh commentedon Mar 16, 2022
So that you can use bitflag-style enums without getting an error, as described above.
AbakumovAlexandr commentedon Mar 16, 2022
So, what should I do to ensure that the value in an enum-typed member is actually the one I've defined in my enum without any runtime code?
RyanCavanaugh commentedon Mar 16, 2022
There currently isn't a way to do that. The suggestion tracking adding a way to do so is #32690
fatcerberus commentedon Mar 16, 2022
Use a string-valued enum.
RyanCavanaugh commentedon Mar 16, 2022
You could also use a
const
-style enumfrom there the code sample works as desired
AbakumovAlexandr commentedon Mar 16, 2022
it's 3 years old :(
TypeScript is intended to bring static type safety. At the same time, its default behavior is to implicitly allow
(-Infinity, +Infinity)
where(1, 2, 3)
only are explicitly expected including the 'strict' mode.If someone wants to assign a value into an enum member which doesn't exist in its definition (bit flags), doesn't it look like it is him who should explicitly tell it to the compiler? Or what's even the point of enumerating possible values of an enum, then?
I just can't remember any other language with static types implicitly allowing this by default.
fatcerberus commentedon Mar 16, 2022
This is how enums work in C++ and C#, too, fwiw.
AbakumovAlexandr commentedon Mar 16, 2022
Of course, it's not true (https://dotnetfiddle.net/VnMaVJ), since a wider type can't be auto-converted into a narrower type in any statically-typed language:

Otherwise as I said, there would be no point in narrow types (enums) if they would allow random non-existing in its definition values to be assigned. This behavior is simply implicit type breakage.
fatcerberus commentedon Mar 17, 2022
Re: Statically-typed languages and correctness, at this point I'm just going to leave this here:
https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals
AbakumovAlexandr commentedon Mar 17, 2022
So what do you want to say by this exactly? 'The ability to assign the result of bit flagging with enums IMPLICITLY (when there's no problem to use
as
at all) is more productive than completely messing the primitive type system rules and breaking the MOST COMMON use case of enums'? Really?Are you aware that such breakage leads to massive and undetectable at compile time issues with the whole type system? Which is much more counter-productive(!) than the above 'inconvenience'.
How about this one?
RyanCavanaugh commentedon Mar 17, 2022
This doesn't seem like a productive discussion. A trade-off was intentionally made; if you don't agree with the trade-off that's fine but there's no value to be found in reiterating all the same discussion points that occurred at the point of the decision.