Skip to content
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

Enums does not support literal typing with computed values #27976

Closed
michaeljota opened this issue Oct 18, 2018 · 4 comments · Fixed by #50528
Closed

Enums does not support literal typing with computed values #27976

michaeljota opened this issue Oct 18, 2018 · 4 comments · Fixed by #50528
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed Fix Available A PR has been opened for this issue

Comments

@michaeljota
Copy link

TypeScript Version: 3.2.0-dev.201xxxxx

Search Terms:

  • enum flag
  • enum computed literal typing

Code

enum AnimalFlags {
    None           = 0,
    HasClaws       = 1 << 0,
    CanFly         = 1 << 1,
    EatsFish       = 1 << 2,
    Endangered     = 1 << 3,

    EndangeredFlyingClawedFishEating = HasClaws | CanFly | EatsFish | Endangered,
}

enum RegularEnum {
    None,
    HasPowers,
}


const a = AnimalFlags.EatsFish;
const b: AnimalFlags.EndangeredFlyingClawedFishEating = AnimalFlags.EndangeredFlyingClawedFishEating;
const c = RegularEnum.HasPowers;

Expected behavior:

a Should have type of AnimalFlags.EatsFish
b Should not throw. Should allow the assignment of AnimalFlags.EndangeredFlyingClawedFishEating as type.

I would expect a computed Enum to behave as a regular enum, in this case, c has type RegularEnum.HasPowers

Actual behavior:

a is typed as AnimalFlags.
const b: AnimalFlags.EndangeredFlyingClawedFishEating is throwing Enum type 'AnimalFlags' has members with initializers that are not literals.
b should be allowed to be typed as AnimalFlags.EndangeredFlyingClawedFishEating.

Playground Link:

Link

Related Issues:

I think is the same that one reported last year #18393. It was closed as a Design Limitation, but since there has been some big changes in Typescript maybe this would be worth to be reviewed again.

@ahejlsberg
Copy link
Member

This is still a design limitation. For mostly historical reasons we have two fundamentally different kinds of enums in the language, numeric and literal. Numeric enums are just like the number type, except that distinct numeric enums are not assignable to each other. Literal enums are quite different: Each enum member has a distinct unit type, and the enum type itself is a union of the enum member types. As described in #9407, we distinguish between the two based on how the members are declared:

When each member of an enum type has either an automatically assigned value, an initializer that specifies a numeric literal, or an initializer that specifies a single identifier naming another enum member, that enum type is considered a union (a.k.a. literal) enum type.

We chose to do it this way such that we didn't have to introduce an explicit modifier to choose between the two kinds. This still holds. The only way I can see us allowing computed constant expressions for literal enum members is to introduce a modifier, e.g. literal enum E { ... }. But I'm not crazy about that idea.

@lukeramsden
Copy link

Is there any chance of revisiting this? This would allow for composition of enums. My use case is this, as part of an integration with the HubSpot CRM where booleans are passed as "yes"/"no":

// either
export enum HubspotBool {
  True = 'yes',
  False = 'no',
}
// or
export const HUBSPOT_TRUE = 'yes';
export const HUBSPOT_FALSE = 'no';

// doesn't matter which

export enum CustomerIdentityCheckStatus {
  Completed = HubspotBool.True, // fails
  NotCompleted = HUBSPOT_FALSE, // fails
  Failed = 'failed',
}

I know it's not super necessary but it's the sort of thing I would expect to "just work", where as currently it gives me an unexpected error which leads me here.

@ahejlsberg ahejlsberg added this to the TypeScript 5.0.0 milestone Oct 17, 2022
@typescript-bot typescript-bot added the Fix Available A PR has been opened for this issue label Oct 17, 2022
@clockwiser
Copy link

clockwiser commented Jan 9, 2023

In the typescript documents, it says each enum member has a value associated with it which can be either constant or computed.
https://www.typescriptlang.org/docs/handbook/enums.html

enum FileAccess {
// constant members
None,
Read = 1 << 1,
Write = 1 << 2,
ReadWrite = Read | Write,
// computed member
G = "123".length,
}

But the computed doesn't work as the documents described, and throws an error in Vue3 SFC.
TS2535: Enum type has members with initializers that are not literals.

@sisiea
Copy link

sisiea commented Feb 10, 2023

In the typescript documents, it says each enum member has a value associated with it which can be either constant or computed. https://www.typescriptlang.org/docs/handbook/enums.html

enum FileAccess { // constant members None, Read = 1 << 1, Write = 1 << 2, ReadWrite = Read | Write, // computed member G = "123".length, }

But the computed doesn't work as the documents described, and throws an error in Vue3 SFC. TS2535: Enum type has members with initializers that are not literals.

It's ok that you can define the numeric enum "FileAccess" which has both computed and constant members. However, if you want to use the enum member as a type, the members of the numeric enum must be initialized by literal.

const num = 1;
// it‘s ok
enum UserResponse {
  // constant member 
  No = 0,
  // computed member
  Yes = num,
  // constant member 
  NotSure = 1 + 1
}

// throws an error  : Enum type has members with initializers that are not literals
type aType = UserResponse.Yes;
// throws an error  : Enum type has members with initializers that are not literals
type bType = UserResponse.NotSure;



// it‘s ok
enum UserResponse_1 {
  // constant member initialized by literal
  No = 0,
  // constant member initialized by literal
  Yes = 1,
   // constant member initialized by literal
  NotSure = 2,
}

// it's ok
type aType_1 = UserResponse_1.Yes;
type bType_1 = UserResponse_1.NotSure;

In addition, this problum has been resolved in 5.0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed Fix Available A PR has been opened for this issue
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants