Skip to content

array enumerations as runtime array + union typeΒ #48193

Closed as not planned
Closed as not planned
@amatiasq

Description

@amatiasq

Suggestion

Array enums

const X = enum ['a', 'b', 'c'];
// or
const X = ['a', 'b', 'c'] as enum;

πŸ” Search Terms

Related tickets

βœ… Viability Checklist

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

⭐ Suggestion

The idea is that if enum keyword is used in a constant array declaration, it leaves the array as runtime value but also returns the type as a union type of the values.

πŸ“ƒ Motivation

Currently enums are causing lots of issues by their nature that violates a few of the TypeScript design goals:

  • Impose no runtime overhead on emitted programs
  • Use a consistent, fully erasable, structural type system.

They require devs to create key-value pairs for each entry in the enum and that transpiles to a considerable amount of runtime code.

const enums came to help there but they lack the ability to get the list of valid values as a runtime array for runtime validation.

This can solve both issues with a terse, clean and removable syntax addition, and facilitates the usage of union types as enumerations.

πŸ’» Use Cases

const Status = enum ['active', 'inactive', 'cancelled'];
// or
const Status = ['active', 'inactive', 'cancelled'] as enum;

Would transpile to...

// just `enum` striped
const Status = ['active', 'inactive', 'cancelled'];

.. and be identical to...

const Status = ['active', 'inactive', 'cancelled'] as const;
type Status = typeof Status[number];

That means that we can use Status both as runtime value:

function validate(status) {
  if (!Status.includes(status)) {
    throw new Error(`${status} is not a valid status: ${Status.join()}`);
  }
}

validate('cancelled') // ok
validate('hello') // Runtime error

And as a type

function set(status: Status) {
   // do something
}

set('active') // ok
set('hello') // type check error

Even combined

const isStatus = (x: unknown) : x is Status => Status.includes(x);

if (isStatus(queryParams.status)) {
  set(queryParams.status) // type narrowed to Status
}

Abstraction in code

This abstraction reaches the same goal but requires users to declare both the runtime array and the type in separated sentences and declare them with the same name.

type ValidKeys = string | number;

type Enum<T extends ValidKeys[]> = T[number];

function Enum<T extends ValidKeys[]>(...keys: [...T]) {
  return keys;
}

const Status = Enum('active', 'inactive', 'cancelled');
type Status = Enum<typeof Status>;

Comparison with enum & const enum

Declaration

enum Enum { A = 'a' }
const enum ConstEnum { A = 'a' }
const ArrayEnum = ['a'] as enum;

Usage as type

function enumFn(x: Enum) {}
function constEnumFn(x: ConstEnum) {}
function arrayEnumFn(x: ArrayEnum) {}

function EnumJsx(props: {x: Enum}) { return null }
function ConstEnumJsx(props: {x: ConstEnum}) { return null }
function ArrayEnumJsx(props: {x: ArrayEnum}) { return null }

Value usage

// Enum
enumFn(Enum.A);
enumFn('a' as Enum);
<EnumJsx x={Enum.A} />
<EnumJsx x={'a' as Enum} />

// ConstEnum
constEnumFn(ConstEnum.A);
constEnumFn('a' as ConstEnum); 
<ConstEnumJsx x={ConstEnum.A} />;
<ConstEnumJsx x={'a' as ConstEnum} />;

// ArrayEnum
arrayEnumFn('a');
<ArrayEnumJsx x="a" />;

Enumerate possible values at runtime

console.log(Object.values(Enum));
// not possible with ConstEnum
console.log(ArrayEnum);

Generated output

// Enum
var Enum;
(function (Enum) {
    Enum["A"] = "a";
})(Enum || (Enum = {}));

// ConstEnum
// no output

// ArrayEnum
const ArrayEnum = ['a'];

Alternative

Alternatively we can extend closer to the as const feature:

Discarded since as const is expression syntax (see comments)

const Status = ['active', 'inactive', 'cancelled'] as enum;

Metadata

Metadata

Assignees

No one assigned

    Labels

    DeclinedThe issue was declined as something which matches the TypeScript visionSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions