Please have a look at this simple code:
const enum MyEnum {
Zero
}
const foo: MyEnum.Zero = 0 // OK as expected (since MyEnum.Zero is zero)
const bar: MyEnum.Zero = 1 // OK, but expected Error! Why?
How can I enforce exact, narrow number type, i.e. 0
in this case?
Playground
UPD: enums seem to be broken https://github.com/microsoft/TypeScript/issues/11559
Enums or enumerations are a new data type supported in TypeScript. Most object-oriented languages like Java and C# use enums. This is now available in TypeScript too. In simple words, enums allow us to declare a set of named constants i.e. a collection of related values that can be numeric or string values.
The Python "NameError: name 'Enum' is not defined" occurs when we use the Enum class without importing it first. To solve the error, import the class from the enum module before using it - from enum import Enum .
In TypeScript, enums, or enumerated types, are data structures of constant length that hold a set of constant values. Each of these constant values is known as a member of the enum. Enums are useful when setting properties or values that can only be a certain number of possible values.
Enums allow us to define or declare a collection of related values that can be numbers or strings as a set of named constants. Unlike some of the types available in TypeScript, enums are preprocessed and are not tested at compile time or runtime.
Enums in TypeScript are a very useful addition to the JavaScript language when used properly. They can help make it clear the intent of normally “magic values” (strings or numbers) that may exist in an application and give a type safe view of them.
But there’s one special type that we want to discuss today and that is enums. Enum, short for Enumerated Type, is a common language feature of many statically types languages such as C, C#, Java, Swift any many others, is a group of named constant values that you can use within your code.
The error "Type string is not assignable to type Enum" occurs when we try to use a string literal in the place of an enum value. To solve the error, use dot or bracket notation to access the specific enum property or use a type assertion. Here is an example of how the error occurs. Copied!
Let’s create an enum in TypeScript to represent the days of the week: The enum is denoted using the enum keyword followed by the name of the enum (DayOfWeek) and then we define the constant values that we want to make available for the enum. We could then create a function to determine if it’s the weekend and have the argument that enum:
It is not really mentioned in the enum
section of the TypeScript handbook, but all number
values are assignable to any numeric enum type. Another draft of a TypeScript handbook says the following:
https://www.typescriptlang.org/docs/handbook/type-compatibility.html#enums
A few of the [assignability] rules are odd because of historical restrictions. For example, any number is assignable to a numeric enum, but this is not true for string enums. Only strings that are known to be part of a string enum are assignable to it. That's because numeric enums existed before union types and literal types, so their rules were originally looser.
And currently it is mentioned in a section on type compatibility that numeric enums and number
are mutually assignable.
Numeric enums in TypeScript have historically been used to support bit fields, using bit masking and bitwise operations to combine explicitly declared enum values to get new ones:
enum Color {
Red = 0xFF0000,
Green = 0x00FF00,
Blue = 0x0000FF
}
const yellow: Color = Color.Red | Color.Green; // 0xFFFF00
const white: Color = Color.Red | Color.Green | Color.Blue; // 0xFFFFFF
const blue: Color = white & ~yellow; // 0x0000FF
And because this use of enums exists in real-world code, it would be a breaking change to alter this behavior (see comment on microsoft/TypeScript#8020). And the maintainers of the language don't seem particularly inclined to try (see microsoft/TypeScript#22311).
So, for better or for worse, numeric enums are loosely typed to be mostly synonymous with number
.
It is possible to roll your own stricter enum-like object, but it involves doing by hand a number of the things that happen automatically when you use the enum
syntax. Here's one possible implementation (which doesn't give you a reverse mapping):
namespace MyEnum {
export const Zero = 0;
export type Zero = typeof Zero;
export const One = 1;
export type One = typeof One;
export const Two = 2;
export type Two = typeof Two;
}
type MyEnum = typeof MyEnum[keyof typeof MyEnum];
const foo: MyEnum.Zero = 0 // okay
const bar: MyEnum.Zero = 1 // error!
This works as follows... when you write enum X { Y = 123, Z = 456 }
, TypeScript introduces a value at runtime named X
, with properties X.Y
and X.Z
. It also introduces types named X
, X.Y
, and X.Z
. The types X.Y
and X.Z
are just the types of the values X.Y
and X.Z
. But the type X
is not the type of the value X
. Instead, it is the union of the property types X.Y | X.Z
.
I used namespace
, export
, const
, and type
above to achieve a similar effect. But the difference here is that the assignability rule for numeric enums doesn't apply, so you get the strict type checking you expect.
Link to code
As @jcalz has mentioned, Typescript doesn't differentiate between number
and MyEnum.X
in the case of a Numeric Enum:
any number is assignable to a numeric enum, but this is not true for string enums
And that's sad...
It basically means that you cannot rely on the "type" of MyEnum.Zero
by itself at all. So you have two choices:
enum MyEnum {
three = 3,
four,
five,
}
type EnumKeysToTrue = { [ P in MyEnum]: true }; // { 3: true; 4: true; 5: true; }
type TrueObject<T extends MyEnum> = Pick<EnumKeysToTrue, T>;
const a0: TrueObject<MyEnum.three> = { 3: true }; // OK, as expected
const a1: TrueObject<MyEnum.three> = { 4: true }; // Error
const a2: TrueObject<3> = { [MyEnum.three]: true }; // OK, as expected, just in case
const a3: TrueObject<4> = { [MyEnum.three]: true }; // Error, as expected, just in case
Playground Link
Here we are actually storing an object with its single key being the enum value that we want and its value being true
.
It might not be ideal, as getting the value out of these a{x}
variables via Object.keys(a1)[0]
might not be preferred; yet, it can work in certain cases.
enum MyEnum {
three = 3,
four,
five,
}
type EnumKeysToTrue = { [ P in MyEnum]: true }; // { 3: true; 4: true; 5: true; }
type Enumified<T extends number> = EnumKeysToTrue[T] extends true ? T : never;
const v0: Enumified<MyEnum.three> = MyEnum.four; // Error: Type 'MyEnum.four' is not assignable to type 'MyEnum.three'.ts(2322)
const v1: Enumified<MyEnum.three> = 4; // Unfortunately, OK. So don't use it this way!
const v2: Enumified<4> = MyEnum.three; // Type 'MyEnum.three' is not assignable to type '4'
const v3: Enumified<9> = 9; // error: type 9 is not assignable to never
const v4: Enumified<4> = MyEnum.four; // OK, as expected :)
let num: number = 9;
const v5: Enumified<typeof num> = MyEnum.four; // Type 'MyEnum.four' is not assignable to type 'never'.ts(2322)
Playground Link
This one might work much better, since once can take the values of v{x}
variables and go from there! Just be careful that the assigned value must be passed to Enumified<>
and the Enum must be on the RHS of the assignment, otherwise, like v1
above, it's going to work inadvertently.
PS. You cannot generalize EnumKEyToTrue
for all the enums in your codebase. It needs to be one per enum instance.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With