Skip to content

TS 2535 when using const enum values derived from other const enum values #35875

Closed

Description

TypeScript Version: 3.7.2

Search Terms: 2535 const enum

Code

// A *self-contained* demonstration of the problem follows...
// Test this by running `tsc` on the command-line, rather than through another build tool such as Gulp, Webpack, etc.
const enum Foo { Value = 0x01 }
const enum Bar { Value = 0x02 }
const enum Baz { Value = Foo.Value | Bar.Value }
type Test = Baz.Value

const foo: Foo.Value = Foo.Value
const bar: Bar.Value = Bar.Value
const baz: Baz.Value = Baz.Value

Expected behavior: It to type-check without errors

Actual behavior: "Enum type 'Baz' has members with initializers that are not literals. (2535)" on both the type alias and the baz type, referencing Baz.Value in the type position.

Playground Link: https://www.typescriptlang.org/play/?ssl=1&ssc=1&pln=8&pc=33#code/MYewdgzgLgBApmArgWxgMRCGBvGA1AQwBtE4YBeGABgA8qBGGAXwChRJYEUYAhAgJxz5ipCtToAmZm3DR4SVHwBeQwiTKUMIAHRrRAH14DdIsqygBPAA5kAKnDmVlJ9SxkcYAM0wAudJhdRTQC9OHc5ACMBPz5+QI0jONDw2CilGIIleLFnUKA

Related Issues:

Activity

  1. dead-claudia commented on Dec 27, 2019

    @dead-claudia
    Author

    Accidentally filed this - please ignore. Updated with intended text.

  2. changed the title [-]2535[/-] [+]TS 2535 when using const enum values derived from other const enum values[/+] on Dec 27, 2019
  3. dead-claudia commented on Dec 27, 2019

    @dead-claudia
    Author

    My immediate use case is that of using a discriminated union based on part of a bit mask. Something roughly like this, but on a bit larger of a scale:

    export const enum Mask {
        Type = 0x00FF,
        IsStatic = 1 << 4,
    }
    
    export const enum Type {
        One = 0x00,
        Two = 0x01,
        Three = 0x02,
    }
    
    export const enum MaybeStatic {
        One = Type.One,
        Two = Type.Two,
        Three = Type.Three,
        StaticOne = Type.One | Mask.IsStatic,
        StaticTwo = Type.Two | Mask.IsStatic,
        StaticThree = Type.Three | Mask.IsStatic,
    }
    
    export type Node = NodeOne | NodeTwo | NodeThree;
    
    export interface NodeOne {
        type: MaybeStatic.One | MaybeStatic.StaticOne;
        value: string;
    }
    
    export interface NodeTwo {
        type: MaybeStatic.Two | MaybeStatic.StaticTwo;
        value: number;
    }
    
    export interface NodeThree {
        type: MaybeStatic.Three | MaybeStatic.StaticThree;
        value: symbol;
    }

    My workaround is specifying these directly, but this is very much so not ideal. (I'm trying to take advantage of TS's constant evaluation to avoid duplicating work with the compile-time constants.)

  4. zimtsui commented on Dec 23, 2020

    @zimtsui

    same problem, in my version 4.0.2

    const enum A { a = 1 }
    const enum B { b = A.a }
    
    const a = A.a; // typeof 'a' is 'A.a'
    const b = B.b; // typeof 'b' is 'B' rather than 'B.b'

    that's because typescript requires only literal members of a const enum can be used as a type. but typescript regards B.b's initializer A.a as a computing expression rather than a literal, even though this computing expression can be computed in compile time. note that is literal is a stricter requirement than can be computed in compile time.

    const enum A { a = 1 }
    const enum B { b = A.a }
    
    type Ta = A.a; // ok
    type Tb = B.b; // error

    actually I don't think it's a good convention to use a member of a const enum, like A.a/B.b, as a type, it has ambiguous semantics.

    so there is a workaround:

    const A = { a: 1 } as const;
    type A = (typeof A)[keyof typeof A];
    const B = { b: A.a } as const;
    type B = (typeof B)[keyof typeof B];
    
    const a = A.a;  // ok, typeof 'a' is 1
    const b = B.b;  // ok, typeof 'b' is 1
  5. peter50216 commented on Jan 11, 2022

    @peter50216

    In the TypeScript handbook about enums, it says that a reference to previously defined constant enum member (which can originate from a different enum) is also consider a constant enum expression, which seems to contradict to this issue.

    Maybe the (which can originate from a different enum) part from the handbook is not implemented?

  6. Walkalone13 commented on Oct 19, 2023

    @Walkalone13

    got this in such case:

    const enum Foo {
      a = 0,
      b = 1 << 1,
      c = 1 << 2,
      all = a | b | c
    }

    resolved with using literals without any calculations

    const enum Foo {
      a = 0,
      b = 1,
      c = 2,
      all = 3
    }
  7. dead-claudia commented on Oct 19, 2023

    @dead-claudia
    Author

    Walkalone13 Please file a new issue. This one's been closed for over a year.

  8. locked as resolved and limited conversation to collaborators on Oct 23, 2023
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

    Fix AvailableA PR has been opened for this issueIn DiscussionNot yet reached consensusSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions