Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TypeScript - Interchangeability of Enums vs Const Enums

tsc permits basic interchangeable usage of two different enum declarations so long as both enum names and each enums members are the same. For example, the below assignments are valid, namespaces are used here, but this is observed to extend to TS module declarations and separate files.

namespace X {
      export enum Colour {
            Blue,
            Green,
            
      }
}

namespace Y {
      export enum Colour {
            Green,
            Blue
      }
}

// assign a Colour enum from Y to a variable with a Colour enum type from X
let greenXY: X.Colour = Y.Colour.Green; // OK

// assign a Colour enum from X to a variable with a Colour enum type from Y
let blueYX: Y.Colour = X.Colour.Blue; // OK

// and equality
let greenX = X.Colour.Green;
let isGreen = greenX == Y.Colour.Green; // OK

Now consider the const enums with the same structure:

namespace X {
      export const enum ConstColour {
            Green,
            Blue
      }
}

namespace Y {
      export const enum ConstColour {
            Green,
            Blue
      }
}

// assign a const Colour enum from Y to a variable with a const Colour enum type from X
let greenConstXY: X.ConstColour = Y.ConstColour.Green; // Type 'ConstColour.Green' is not assignable to type 'ConstColour'.(2322)

// assign a Colour enum from Y to a variable with a Colour enum type from X
let blueConstYX: Y.ConstColour = X.ConstColour.Blue; // Type 'ConstColour.Blue' is not assignable to type 'ConstColour'.(2322)

// and equality
let constGreenX = X.ConstColour.Green;
let isConstGreen = constGreenX == Y.ConstColour.Green; // This condition will always return 'false' since the types 'X.ConstColour' and 'Y.ConstColour' have no overlap.(2367)

We know that const enums are inlined at use sites to the desired value and that tsc views enums as a union type of its members which is how tsc knows the types of which enums comprise.

And so if tsc is able to reliably determine the correct basic usage of two regular enums at compile-time. Why would const enums differ in this case?

Playground Link

like image 819
ethane Avatar asked Oct 16 '22 01:10

ethane


1 Answers

A known bug, microsoft/TypeScript#29879, is unfortunately confusing you. Neither enum nor const enum are intended to be interchangeable in this way. I see you mentioned that TS supports this if they have the same names, but having the same name in different namespaces should not have any significance... and if you change your enum declaration so that they have different names (say, Color in X and Colour in Y):

namespace X {
      export enum Color {
            Green,
            Blue,               
      }
}

namespace Y {
      export enum Colour {
            Green,
            Blue
      }
}

you get the same errors as you get with your const enum:

let greenXY: X.Color = Y.Colour.Green; // error!
let blueYX: Y.Colour = X.Color.Blue; // error!
let greenX = X.Color.Green;
let isGreen = greenX == Y.Colour.Green; // error!

The assignability of same-name-in-different-namespace enums is not intentional; it's a weird bug, and apparently not important enough to be fixed in the short term (it's been relegated to the backlog).


Not sure what to tell you to do about it. Personally I stay as far away from enums as I can since they are an older feature that doesn't adhere to TypeScript's design goals... there are no enums in ECMAScript. Any proposal to add a similar feature to TypeScript today where you implement new expression-level syntax without it first existing in at least a Stage 3 proposal for ECMAScript would be declined.

Instead of enum I sometimes roll my own equivalent like:

namespace U {
      export const Colour = {
            Green: 0,
            Blue: 1
      } as const;
      export type Colour = typeof Colour[keyof typeof Colour];
}
namespace V {
      export const Colour = {
            Green: 0,
            Blue: 1
      } as const;
      export type Colour = typeof Colour[keyof typeof Colour];

}

which is more tedious but, in my opinion, behaves better because the type Colour is just a union of numeric literals 0 | 1 which are always interchangeable:

let greenUV: U.Colour = V.Colour.Green;
let greenVU: V.Colour = U.Colour.Green;
let greenU = U.Colour.Green;
let isGreenU = greenU === V.Colour.Green;

But that's just my opinion.


Anyway, hope this helps make sense of the situation. Good luck!

Playground link to code

like image 83
jcalz Avatar answered Dec 14 '22 22:12

jcalz