Skip to content

Compiler allows 'number' to be used where enum is expected #48296

Closed
@AbakumovAlexandr

Description

@AbakumovAlexandr

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)

Activity

fatcerberus

fatcerberus commented on Mar 16, 2022

@fatcerberus

This is intentional because numeric enums are often used as bitfields, e.g. FooEnum.Foo | FooEnum.Bar is typed as number because it’s not explicitly part of the enum. You can use string-valued enums for stricter checks.

AbakumovAlexandr

AbakumovAlexandr commented on Mar 16, 2022

@AbakumovAlexandr
Author

This is intentional because numeric enums are often used as bitfields, e.g. FooEnum.Foo | FooEnum.Bar is typed as number because it’s not explicitly part of the enum. You can use string-valued enums for stricter checks.

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

AbakumovAlexandr commented on Mar 16, 2022

@AbakumovAlexandr
Author

@RyanCavanaugh What is the justification for 'Working as Intended'? Especially in 'strict' mode?

fatcerberus

fatcerberus commented on Mar 16, 2022

@fatcerberus
let computedFlags = FlagsEnum.One | FlagsEnum.Two;
obj.flagsEnum = computedFlags;

computedFlags should ideally be assignable back to the enum type, if obj is using it as a bitfield. However it’s currently typed as number because its actual value is not (necessarily) part of FlagsEnum. Thus why this type hole is present.

AbakumovAlexandr

AbakumovAlexandr commented on Mar 16, 2022

@AbakumovAlexandr
Author
let computedFlags = FlagsEnum.One | FlagsEnum.Two;
obj.flagsEnum = computedFlags;

computedFlags should ideally be assignable back to the enum type, if obj is using it as a bitfield. However it’s currently typed as number because its actual value is not (necessarily) part of FlagsEnum. Thus why this type hole is present.

If you needed this type 'abuse' (in my opinion, sorry if too harsh :) ), you always could shut up the compiler explicitly:

let computedFlags: FlagsEnum = (FlagsEnum.One | FlagsEnum.Two) as FlagsEnum;
obj.flagsEnum = computedFlags;

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

RyanCavanaugh commented on Mar 16, 2022

@RyanCavanaugh
Member

@AbakumovAlexandr because we did this behavior on purpose

fatcerberus

fatcerberus commented on Mar 16, 2022

@fatcerberus

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

AbakumovAlexandr commented on Mar 16, 2022

@AbakumovAlexandr
Author

@AbakumovAlexandr because we did this behavior on purpose

Can you elaborate: what is the purpose?

fatcerberus

fatcerberus commented on Mar 16, 2022

@fatcerberus

Can you elaborate: what is the purpose?

🤦‍♂️

RyanCavanaugh

RyanCavanaugh commented on Mar 16, 2022

@RyanCavanaugh
Member

So that you can use bitflag-style enums without getting an error, as described above.

AbakumovAlexandr

AbakumovAlexandr commented on Mar 16, 2022

@AbakumovAlexandr
Author

So that you can use bitflag-style enums without getting an error, as described above.

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

RyanCavanaugh commented on Mar 16, 2022

@RyanCavanaugh
Member

There currently isn't a way to do that. The suggestion tracking adding a way to do so is #32690

fatcerberus

fatcerberus commented on Mar 16, 2022

@fatcerberus

Use a string-valued enum.

RyanCavanaugh

RyanCavanaugh commented on Mar 16, 2022

@RyanCavanaugh
Member

You could also use a const-style enum

const StatusEnum = {
    Cancelled: 4,
    Paid: 6
} as const;
type StatusEnum = (typeof StatusEnum)[keyof typeof StatusEnum];

from there the code sample works as desired

8 remaining items

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Working as IntendedThe behavior described is the intended behavior; this is not a bug

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @fatcerberus@RyanCavanaugh@AbakumovAlexandr

        Issue actions

          Compiler allows 'number' to be used where enum is expected · Issue #48296 · microsoft/TypeScript