TypeScript icon indicating copy to clipboard operation
TypeScript copied to clipboard

Enum unification and improvements

Open ahejlsberg opened this issue 2 years ago • 23 comments

Since #9407 TypeScript has had two kinds of enum types: Numeric enum types and literal enum types (also known as union enum types). Numeric enum types have permitted enum members to have computed values, but not literal types; whereas literal enum types have required members to be declared using simple numeric or string literals only. The distinction between these two kinds of enums is subtle and has been the source of a fair amount of confusion over time.

With this PR we unify the two kinds into a single hybrid that incorporates the best of both worlds. Specifically, enums are always represented as unions of their member types, member values may be computed by constant or non-constant expressions, and members with constant values are given literal types. The PR only affects type checking of enums. There are no changes to the emitted code (except for better inlining of constant expressions in some cases).

An enum declaration now declares a type representing the entire enum and types representing each member (similar to literal enum types). For example,

enum E {
    A = 10 * 10,  // Numeric literal enum member
    B = "foo",    // String literal enum member
    C = bar(42)   // Opaque computed enum member
}

declares a type E and types E.A, E.B, and E.C, where E is the union E.A | E.B | E.C.

An enum member has a literal enum type when it is initialized by a constant expression or when it has no initializer and is given an auto-incremented numeric value. In the example above, E.A and E.B denote enum literal types with values 100 and "foo" respectively.

An enum member has a unique and opaque enum member type when it is initialized by a non-constant expression. Non-constant initializer expressions must be assignable to type number and are not permitted in const enum declarations. In the example above, E.C denotes a unique enum member type representing the value computed by the non-constant initializer expression. This unique type is assignable to type number, but otherwise incompatible with other types.

An expression is considered a constant expression if it is

  • a number or string literal,
  • a unary +, -, or ~ applied to a numeric constant expression,
  • a binary +, -, *, /, %, **, <<, >>, >>>, |, &, ^ applied to two numeric constant expressions,
  • a binary + applied to two constant expressions whereof at least one is a string,
  • a template expression where each substitution expression is a constant expression,
  • a parenthesized constant expression,
  • a dotted name (e.g. x.y.z) that references a const variable with a constant expression initializer and no type annotation,
  • a dotted name that references an enum member with an enum literal type, or
  • a dotted name indexed by a string literal (e.g. x.y["z"]) that references an enum member with an enum literal type.

Note that constant expressions are not permitted to make forward references--any value referenced by a constant expression must have been previously declared.

Some examples:

const BaseValue = 10;
const Prefix = "/data";
const enum Values {
    First = BaseValue,  // 10
    Second,             // 11
    Third               // 12
}
const enum Routes {
    Parts = `${prefix}/parts`,       // "/data/parts"
    Invoices = `${prefix}/invoices`  // "/data/invoices"
}

NOTE: This PR is technically a breaking change because checking of the new unified enum types is stricter than the old numeric enum types.

Fixes #27976. Fixes #35875. Fixes #40793. Fixes #43902.

ahejlsberg avatar Aug 29 '22 22:08 ahejlsberg

@typescript-bot pack this

RyanCavanaugh avatar Aug 29 '22 23:08 RyanCavanaugh

Heya @RyanCavanaugh, I've started to run the tarball bundle task on this PR at 50a952b95412af80fa2d860aba8df07eb06d0c6a. You can monitor the build here.

typescript-bot avatar Aug 29 '22 23:08 typescript-bot

@typescript-bot test this @typescript-bot user test this inline @typescript-bot run dt @typescript-bot perf test faster

ahejlsberg avatar Aug 29 '22 23:08 ahejlsberg

Heya @ahejlsberg, I've started to run the extended test suite on this PR at 50a952b95412af80fa2d860aba8df07eb06d0c6a. You can monitor the build here.

typescript-bot avatar Aug 29 '22 23:08 typescript-bot

Heya @ahejlsberg, I've started to run the abridged perf test suite on this PR at 50a952b95412af80fa2d860aba8df07eb06d0c6a. You can monitor the build here.

Update: The results are in!

typescript-bot avatar Aug 29 '22 23:08 typescript-bot

Heya @ahejlsberg, I've started to run the parallelized Definitely Typed test suite on this PR at 50a952b95412af80fa2d860aba8df07eb06d0c6a. You can monitor the build here.

typescript-bot avatar Aug 29 '22 23:08 typescript-bot

Heya @ahejlsberg, I've started to run the diff-based user code test suite on this PR at 50a952b95412af80fa2d860aba8df07eb06d0c6a. You can monitor the build here.

Update: The results are in!

typescript-bot avatar Aug 29 '22 23:08 typescript-bot

@ahejlsberg The results of the perf run you requested are in!

Here they are:

Comparison Report - main..50528

Metric main 50528 Delta Best Worst
Angular - node (v14.15.1, x64)
Memory used 338,727k (± 0.00%) 338,717k (± 0.01%) -9k (- 0.00%) 338,668k 338,757k
Parse Time 2.08s (± 0.70%) 2.09s (± 0.93%) +0.02s (+ 0.82%) 2.06s 2.15s
Bind Time 0.80s (± 0.62%) 0.80s (± 0.75%) +0.00s (+ 0.00%) 0.79s 0.81s
Check Time 5.82s (± 0.58%) 5.83s (± 0.27%) +0.01s (+ 0.14%) 5.79s 5.87s
Emit Time 6.18s (± 0.49%) 6.22s (± 0.72%) +0.04s (+ 0.58%) 6.16s 6.37s
Total Time 14.87s (± 0.27%) 14.93s (± 0.40%) +0.06s (+ 0.41%) 14.85s 15.11s
Compiler-Unions - node (v14.15.1, x64)
Memory used 192,654k (± 0.02%) 192,983k (± 0.37%) +329k (+ 0.17%) 192,631k 195,896k
Parse Time 0.86s (± 0.52%) 0.85s (± 0.87%) -0.01s (- 0.58%) 0.84s 0.87s
Bind Time 0.49s (± 0.75%) 0.49s (± 0.82%) +0.00s (+ 0.62%) 0.48s 0.50s
Check Time 6.70s (± 0.55%) 6.73s (± 0.69%) +0.03s (+ 0.43%) 6.64s 6.85s
Emit Time 2.41s (± 1.02%) 2.40s (± 0.72%) -0.01s (- 0.54%) 2.35s 2.43s
Total Time 10.46s (± 0.45%) 10.47s (± 0.51%) +0.02s (+ 0.16%) 10.35s 10.62s
Monaco - node (v14.15.1, x64)
Memory used 326,505k (± 0.01%) 326,536k (± 0.00%) +31k (+ 0.01%) 326,510k 326,581k
Parse Time 1.57s (± 0.59%) 1.60s (± 1.50%) +0.03s (+ 1.65%) 1.57s 1.69s
Bind Time 0.73s (± 0.85%) 0.73s (± 0.68%) -0.00s (- 0.41%) 0.72s 0.74s
Check Time 5.73s (± 0.51%) 5.78s (± 0.51%) +0.05s (+ 0.86%) 5.73s 5.85s
Emit Time 3.35s (± 0.61%) 3.35s (± 0.67%) -0.00s (- 0.12%) 3.30s 3.40s
Total Time 11.39s (± 0.42%) 11.45s (± 0.44%) +0.06s (+ 0.55%) 11.35s 11.55s
TFS - node (v14.15.1, x64)
Memory used 289,645k (± 0.00%) 289,648k (± 0.01%) +3k (+ 0.00%) 289,614k 289,687k
Parse Time 1.31s (± 0.83%) 1.31s (± 0.80%) +0.01s (+ 0.38%) 1.30s 1.34s
Bind Time 0.78s (± 2.29%) 0.80s (± 0.75%) +0.01s (+ 1.40%) 0.78s 0.81s
Check Time 5.36s (± 0.54%) 5.35s (± 0.62%) -0.01s (- 0.24%) 5.27s 5.44s
Emit Time 3.60s (± 0.77%) 3.57s (± 0.76%) -0.03s (- 0.75%) 3.51s 3.63s
Total Time 11.05s (± 0.38%) 11.03s (± 0.59%) -0.03s (- 0.25%) 10.87s 11.20s
material-ui - node (v14.15.1, x64)
Memory used 438,087k (± 0.06%) 438,212k (± 0.00%) +125k (+ 0.03%) 438,180k 438,251k
Parse Time 1.86s (± 0.59%) 1.87s (± 0.59%) +0.01s (+ 0.38%) 1.84s 1.89s
Bind Time 0.59s (± 0.62%) 0.59s (± 1.10%) +0.00s (+ 0.34%) 0.58s 0.61s
Check Time 13.06s (± 0.95%) 12.92s (± 0.81%) -0.14s (- 1.06%) 12.72s 13.15s
Emit Time 0.00s (± 0.00%) 0.00s (± 0.00%) 0.00s ( NaN%) 0.00s 0.00s
Total Time 15.51s (± 0.81%) 15.38s (± 0.72%) -0.13s (- 0.83%) 15.16s 15.62s
xstate - node (v14.15.1, x64)
Memory used 547,388k (± 0.00%) 547,403k (± 0.00%) +15k (+ 0.00%) 547,362k 547,442k
Parse Time 2.60s (± 0.45%) 2.62s (± 0.47%) +0.02s (+ 0.73%) 2.60s 2.65s
Bind Time 0.97s (± 0.70%) 0.98s (± 0.68%) +0.01s (+ 0.62%) 0.97s 0.99s
Check Time 1.54s (± 0.62%) 1.54s (± 0.42%) -0.00s (- 0.13%) 1.53s 1.55s
Emit Time 0.07s (± 4.13%) 0.07s (± 3.14%) -0.00s (- 1.39%) 0.07s 0.08s
Total Time 5.20s (± 0.42%) 5.22s (± 0.21%) +0.02s (+ 0.38%) 5.19s 5.24s
System
Machine Namets-ci-ubuntu
Platformlinux 4.4.0-210-generic
Architecturex64
Available Memory16 GB
Available Memory15 GB
CPUs4 × Intel(R) Core(TM) i7-4770 CPU @ 3.40GHz
Hosts
  • node (v14.15.1, x64)
Scenarios
  • Angular - node (v14.15.1, x64)
  • Compiler-Unions - node (v14.15.1, x64)
  • Monaco - node (v14.15.1, x64)
  • TFS - node (v14.15.1, x64)
  • material-ui - node (v14.15.1, x64)
  • xstate - node (v14.15.1, x64)
Benchmark Name Iterations
Current 50528 10
Baseline main 10
Developer Information:

Download Benchmark

typescript-bot avatar Aug 29 '22 23:08 typescript-bot

@ahejlsberg Here are the results of running the user test suite comparing main and refs/pull/50528/merge:

Everything looks good!

typescript-bot avatar Aug 29 '22 23:08 typescript-bot

Heya @ahejlsberg, I've run the RWC suite on this PR - assuming you're on the TS core team, you can view the resulting diff here.

typescript-bot avatar Aug 29 '22 23:08 typescript-bot

Hey @RyanCavanaugh, I've packed this into an installable tgz. You can install it for testing by referencing it in your package.json like so:

{
    "devDependencies": {
        "typescript": "https://typescript.visualstudio.com/cf7ac146-d525-443c-b23c-0d58337efebc/_apis/build/builds/133037/artifacts?artifactName=tgz&fileId=3F948668C1BD8758CB1C8E59919B3D6E945DCF3815094A729B98FCE15A366B7E02&fileName=/typescript-4.9.0-insiders.20220829.tgz"
    }
}

and then running npm install.


There is also a playground for this build and an npm module you can use via "typescript": "npm:@typescript-deploys/[email protected]".;

typescript-bot avatar Aug 29 '22 23:08 typescript-bot

@typescript-bot test this @typescript-bot user test this inline @typescript-bot run dt @typescript-bot perf test faster @typescript-bot test top100

ahejlsberg avatar Oct 17 '22 17:10 ahejlsberg

Heya @ahejlsberg, I've started to run the diff-based top-repos suite on this PR at 3f66f6c499f9170e5d114e977a1512c0fbd12024. You can monitor the build here.

Update: The results are in!

typescript-bot avatar Oct 17 '22 17:10 typescript-bot

Heya @ahejlsberg, I've started to run the diff-based user code test suite on this PR at 3f66f6c499f9170e5d114e977a1512c0fbd12024. You can monitor the build here.

Update: The results are in!

typescript-bot avatar Oct 17 '22 17:10 typescript-bot

Heya @ahejlsberg, I've started to run the abridged perf test suite on this PR at 3f66f6c499f9170e5d114e977a1512c0fbd12024. You can monitor the build here.

Update: The results are in!

typescript-bot avatar Oct 17 '22 17:10 typescript-bot

Heya @ahejlsberg, I've started to run the extended test suite on this PR at 3f66f6c499f9170e5d114e977a1512c0fbd12024. You can monitor the build here.

typescript-bot avatar Oct 17 '22 17:10 typescript-bot

Heya @ahejlsberg, I've started to run the parallelized Definitely Typed test suite on this PR at 3f66f6c499f9170e5d114e977a1512c0fbd12024. You can monitor the build here.

typescript-bot avatar Oct 17 '22 17:10 typescript-bot

@ahejlsberg Here are the results of running the user test suite comparing main and refs/pull/50528/merge:

Everything looks good!

typescript-bot avatar Oct 17 '22 17:10 typescript-bot

@ahejlsberg The results of the perf run you requested are in!

Here they are:

Comparison Report - main..50528

Metric main 50528 Delta Best Worst
Angular - node (v16.17.1, x64)
Memory used 352,477k (± 0.02%) 352,459k (± 0.01%) -18k (- 0.01%) 352,332k 352,517k
Parse Time 1.90s (± 0.86%) 1.90s (± 0.86%) +0.01s (+ 0.42%) 1.87s 1.93s
Bind Time 0.75s (± 0.50%) 0.75s (± 0.77%) +0.00s (+ 0.67%) 0.74s 0.77s
Check Time 5.69s (± 0.34%) 5.70s (± 0.37%) +0.01s (+ 0.12%) 5.64s 5.75s
Emit Time 6.09s (± 0.60%) 6.13s (± 0.53%) +0.04s (+ 0.57%) 6.09s 6.22s
Total Time 14.43s (± 0.45%) 14.47s (± 0.35%) +0.05s (+ 0.34%) 14.39s 14.65s
Compiler-Unions - node (v16.17.1, x64)
Memory used 197,729k (± 0.37%) 197,791k (± 0.02%) +62k (+ 0.03%) 197,684k 197,878k
Parse Time 0.78s (± 0.60%) 0.78s (± 0.38%) +0.00s (+ 0.26%) 0.78s 0.79s
Bind Time 0.45s (± 0.75%) 0.45s (± 0.80%) +0.00s (+ 0.22%) 0.45s 0.46s
Check Time 6.42s (± 0.47%) 6.47s (± 0.53%) +0.05s (+ 0.72%) 6.40s 6.58s
Emit Time 2.25s (± 0.66%) 2.27s (± 0.87%) +0.03s (+ 1.25%) 2.23s 2.33s
Total Time 9.90s (± 0.43%) 9.98s (± 0.43%) +0.08s (+ 0.79%) 9.91s 10.09s
Monaco - node (v16.17.1, x64)
Memory used 331,132k (± 0.01%) 331,279k (± 0.02%) +148k (+ 0.04%) 331,188k 331,416k
Parse Time 1.44s (± 0.89%) 1.43s (± 0.87%) -0.00s (- 0.28%) 1.41s 1.47s
Bind Time 0.69s (± 0.75%) 0.69s (± 0.72%) +0.00s (+ 0.58%) 0.69s 0.71s
Check Time 5.46s (± 0.48%) 5.48s (± 0.49%) +0.02s (+ 0.44%) 5.43s 5.54s
Emit Time 3.26s (± 0.66%) 3.24s (± 0.53%) -0.01s (- 0.40%) 3.22s 3.30s
Total Time 10.85s (± 0.42%) 10.86s (± 0.38%) +0.01s (+ 0.12%) 10.79s 10.99s
TFS - node (v16.17.1, x64)
Memory used 294,064k (± 0.02%) 294,065k (± 0.02%) +2k (+ 0.00%) 293,861k 294,147k
Parse Time 1.23s (± 0.93%) 1.22s (± 1.23%) -0.01s (- 1.06%) 1.19s 1.26s
Bind Time 0.64s (± 0.62%) 0.64s (± 0.90%) -0.00s (- 0.16%) 0.63s 0.65s
Check Time 5.13s (± 0.57%) 5.11s (± 0.42%) -0.02s (- 0.35%) 5.05s 5.14s
Emit Time 3.49s (± 0.71%) 3.49s (± 0.84%) +0.00s (+ 0.11%) 3.44s 3.56s
Total Time 10.49s (± 0.57%) 10.46s (± 0.47%) -0.03s (- 0.26%) 10.33s 10.55s
material-ui - node (v16.17.1, x64)
Memory used 438,358k (± 0.00%) 438,376k (± 0.01%) +17k (+ 0.00%) 438,313k 438,530k
Parse Time 1.73s (± 1.15%) 1.71s (± 0.97%) -0.02s (- 1.15%) 1.67s 1.76s
Bind Time 0.54s (± 0.91%) 0.54s (± 0.83%) -0.01s (- 1.10%) 0.53s 0.55s
Check Time 12.49s (± 0.63%) 12.52s (± 0.89%) +0.02s (+ 0.19%) 12.36s 12.89s
Emit Time 0.00s (± 0.00%) 0.00s (± 0.00%) 0.00s ( NaN%) 0.00s 0.00s
Total Time 14.77s (± 0.60%) 14.77s (± 0.76%) +0.00s (+ 0.01%) 14.60s 15.12s
xstate - node (v16.17.1, x64)
Memory used 554,393k (± 0.01%) 554,353k (± 0.01%) -40k (- 0.01%) 554,229k 554,493k
Parse Time 2.31s (± 0.68%) 2.31s (± 0.51%) -0.00s (- 0.04%) 2.28s 2.33s
Bind Time 0.88s (± 0.39%) 0.90s (± 2.29%) +0.02s (+ 2.51%) 0.87s 0.95s
Check Time 1.43s (± 0.87%) 1.42s (± 0.90%) -0.01s (- 0.56%) 1.39s 1.44s
Emit Time 0.07s (± 0.00%) 0.07s (± 0.00%) 0.00s ( 0.00%) 0.07s 0.07s
Total Time 4.69s (± 0.44%) 4.70s (± 0.38%) +0.01s (+ 0.28%) 4.67s 4.75s
System
Machine Namets-ci-ubuntu
Platformlinux 5.4.0-126-generic
Architecturex64
Available Memory16 GB
Available Memory15 GB
CPUs4 × Intel(R) Core(TM) i7-4770 CPU @ 3.40GHz
Hosts
  • node (v16.17.1, x64)
Scenarios
  • Angular - node (v16.17.1, x64)
  • Compiler-Unions - node (v16.17.1, x64)
  • Monaco - node (v16.17.1, x64)
  • TFS - node (v16.17.1, x64)
  • material-ui - node (v16.17.1, x64)
  • xstate - node (v16.17.1, x64)
Benchmark Name Iterations
Current 50528 10
Baseline main 10
Developer Information:

Download Benchmark

typescript-bot avatar Oct 17 '22 17:10 typescript-bot

Heya @ahejlsberg, I've run the RWC suite on this PR - assuming you're on the TS core team, you can view the resulting diff here.

typescript-bot avatar Oct 17 '22 17:10 typescript-bot

@ahejlsberg Here are the results of running the top-repos suite comparing main and refs/pull/50528/merge:

Something interesting changed - please have a look.

Details

microsoft/vscode

4 of 53 projects failed to build with the old tsc and were ignored

src/tsconfig.tsec.json

typescript-bot avatar Oct 17 '22 18:10 typescript-bot

This change has surprisingly few effects: Performance, user code test suite, and DT test suite are unaffected. One new error in top100 test suite, correctly identifying an issue in vscode. Small change in extended test suite, emit now uses inlined constants due to better constant expression folding.

There were four new errors in the compiler itself. See fixes here and here. I think these new errors are perfectly fine, even if one of them is a manifestation of #9998.

ahejlsberg avatar Oct 17 '22 21:10 ahejlsberg

what happened to the back referencing Enum[Enum.Value] === "some value"? and is it possible to allow symbols?

Jack-Works avatar Oct 20 '22 09:10 Jack-Works

@typescript-bot pack this

jakebailey avatar Oct 21 '22 05:10 jakebailey

Heya @jakebailey, I've started to run the tarball bundle task on this PR at f2f2d05deca0cdf5165c6ba6f6769483054dfb99. You can monitor the build here.

typescript-bot avatar Oct 21 '22 05:10 typescript-bot

Hey @jakebailey, I've packed this into an installable tgz. You can install it for testing by referencing it in your package.json like so:

{
    "devDependencies": {
        "typescript": "https://typescript.visualstudio.com/cf7ac146-d525-443c-b23c-0d58337efebc/_apis/build/builds/136892/artifacts?artifactName=tgz&fileId=24DEDAB836809F769071A0FB19D0B901663058A9D29C4F7F97BE396AF120751202&fileName=/typescript-4.9.0-insiders.20221021.tgz"
    }
}

and then running npm install.


There is also a playground for this build and an npm module you can use via "typescript": "npm:@typescript-deploys/[email protected]".;

typescript-bot avatar Oct 21 '22 05:10 typescript-bot

Would this have a problem?

const boat = 2;
const goat = 1;
enum NonTier6ItemGraphCodes {
  Moose = boat,
  Goose = goat,
  Loose, // This would ALSO be 2 and stamp over Moose 
}

Currently this throws an Enum member must have initializer error in TS 4.8.4.

WORMSS avatar Nov 17 '22 10:11 WORMSS

Hi, this is still causing a problem in the latest version of TS. "typescript": "^4.9.3"

const path = '/admin/refresh'

const enum AuthEndpoints {
  login = '/admin/login',
  refresh = `${path}`  _//error: Computed values are not permitted in an enum with string valued members.ts(2553)_
}

export default AuthEndpoints

andreymaslovru avatar Nov 21 '22 18:11 andreymaslovru

This PR is in TypeScript 5.0, not 4.9 (which was closed to new features after the beta). If you want to use it now, you can try the nightlies, but you won't see this in 4.9.

jakebailey avatar Nov 21 '22 18:11 jakebailey

So, it seems TS5 still has the enum stomping problem

https://www.typescriptlang.org/play?ts=5.0.0-dev.20221121#code/MYewdgzgLgBARiAhrAvDATAbgFCkrAcyVRgEYcBTMAVwFsYA5cAFQEsKAnANgEkoLaAcQ6IADgAsAwiAAmFCDADe2GDACyIEBAow0CZABoVMQZu26YRQ8YAyZikYC+QA

WORMSS avatar Nov 21 '22 20:11 WORMSS