Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

const enum not being converted to number #33703

Open
gregveres opened this issue Oct 1, 2019 · 6 comments
Open

const enum not being converted to number #33703

gregveres opened this issue Oct 1, 2019 · 6 comments
Labels
Needs More Info The issue still hasn't been fully clarified

Comments

@gregveres
Copy link

This has been an issue since at least 3.0.
I have lots of enums defined this way:

namespace SkycourtApi {
    export const enum RoundRobinCompetitorStatus {
        Active = 0,
        Injured = 1,
        DropOut = 2
    }
}

then in my code I use them like this:
this.playerNotActive = model.Status !== SkycourtApi.RoundRobinCompetitorStatus.Active;

99% of the time this will get converted to the number 0 properly. But occasionally this will get left as the identifier: SkycourtApi.RoundRobinCompetitorStatus.Active

I compile my TS code twice, once for unit tests and that uses module: "amd" and once for production that uses module:"esnext"
The strange thing is that I have never encountered this problem in my unit tests. I have only ever encountered this problem when creating the production code with module: "esnext". Here is my tsconfig file that causes the problem. The only difference between the production and unit test config files is module and removeComments (true in unit test and false in production - yes I typed that correctly. The comments are removed later when webpacking).

{
  "compilerOptions": {
    "noImplicitAny": true,
    "noEmitOnError": true,
    "removeComments": false,
    "sourceMap": true,
    "target": "es5",
    "module": "esnext",
    "moduleResolution": "node",
    "skipLibCheck": true,
    "typeRoots": [
      "../node_modules/@types",
      "typings"
    ],
    "baseUrl": "Scripts/",
    "importHelpers": true,
    "lib": [
      "dom",
      "es5",
      "scripthost",
      "es2015.promise"
    ]
  },
  "compileOnSave": true,
  "exclude": [
    "node_modules",
    "obj",
    "bin"
  ]
}

Now the really strange part, is that when this problem occurs, all I have to do is move the offending line up or down in the file and that usually fixes the problem and the production code will go back to converting the enum into the number 0.

Search Terms:
const enums
enum not converted to number
enum not inlined
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.

Expected behavior:
The const enums should always be converted to their numeric value.

Actual behavior:
1% of the time, they will be left as the identifier unless I move the line up or down one line in the file being compiled.

Playground Link:
I will see if i can create a playground and comeback and link it here. It has been difficult to reproduce, especially since just moving the line up or down a line in the source file gets it to compile properly.

Related Issues:
none

@RyanCavanaugh RyanCavanaugh added the Needs More Info The issue still hasn't been fully clarified label Oct 1, 2019
@RyanCavanaugh
Copy link
Member

We've never seen a determinism bug like this, so I really have to expect this is some other tool causing a problem. But I'm very curious to see a repro either way.

@gregveres
Copy link
Author

I just spent some time on the playground trying to repro it, but I think it needs separate files. The definition of the enum is in a separate file from the use of the enum and I don't think the playground allows me to set that up.

I think I am running into it more now that I moved from typescript 3.3.333 to 3.6.3. I am realizing that it is happening all over my production code, but 100% of my unit tests pass which implies that compiling each file for the unit tests doesn't cause the issue.

I will look at my webpack pipeline to see if I can figure out if something is processing the files before typescript gets them.

@gregveres
Copy link
Author

I misspoke above when I said 1%. It appears that this has increased dramatically with my upgrade to 3.6.3. I generated the production code and then searched the output js for SkycourtApi. (this namespace should be completely removed in the JS files). When I use 3.3.1 I find no instances of this namespace, but when I do the same build but using 3.6.3, I find 750 instances of SkycourtApi. I don't think that is every time I reference an enum, those are just the instances where the compiler decided for some reason that it didn't know what value the enum has so it left it as an identifier.

I will try to binary search my way to the highest version I can use, because I clearly can't use 3.6.3 and still get work done. :( I will report back on the version that seems to introduce the issue. Although I have to say that I have seen it on the very rare occasion using 3.3.3333 and earlier versions. It is just some change between 3.3.3333 and 3.6.3 makes it much worse.

I am also trying to figure out how to get the input to TS out of webpack so that I can bundle something up to show you.

I believe that this experiment has shown that it isn't another tool that is causing it, it is the TS compiler because the I changed two things between the 750 instances and the 0 instances. I am using TS with an ASP.Net MVC app. So I changed the nuget package Microsoft.Typescript.MSBuild from v3.6.3 to v3.3.1 and I changed the npm package from typescript@3.6.3 to typescript@3.3.1. Those are the only two changes.

More info to come.

@gregveres
Copy link
Author

Ok, I figured out that this must be something that has been introduced in 3.6.3.

I backed up to 3.3.1: all enums converted properly
3.4.5: all enums converted properly
3.5.3: all enums converted properly
3.6.2: all enums converted properly
3.6.3: 950 enums not converted.

So it looks like some change that was introduced in 3.6.3 introduced a problem with the ability to recognize that an enum is a const enum and should be replaced with the const value rather than being left as a variable.

My case is that these enums are defined as listed above in a common namespace and each in their own file (well in a lot of cases there are multiple in a single file, but I have 10s of files with these const enums in them, all contributing to the specific SkycourtApi namespace).

The enums are not "referenced" into the file they are used in, Typescript just ends up picking them up (I confess to not really knowing how they are picked up by Typescript, but they are, I don't have to use any /// reference instruction).

The code tends to use them to assign to variables, or in if checks etc.

@Brandon-Beck
Copy link

Brandon-Beck commented Oct 26, 2019

Possibly related. I found exported enums do not work with isolated modules and an es target. commonjs is happy for me, but only because it has no choice but to import everything. Not sure why it generates an enum object instead of a literal like it does without isolatedModules.
Tested on 3.6.4 & 3.7.0-dev

test.zip

tsconfig.json

{
  "compilerOptions": {
    "target": "es5", 
    "module": "es2015", 
    "outDir": "./out",  
    "rootDir": "./src", 
    "esModuleInterop": true,
    "isolatedModules": true
  }
}

exportingFile.ts

export const enum ThreadType {
  CommentThread,
  DiscussionThread
}

export function cat(a: ThreadType) {
  if (a == ThreadType.CommentThread) {
    console.log('No. No cats. You suck!')
  }
  else {
    console.log('Ok. Sure. I can see how cats could be considered cute....')
  }
}

importingFile.ts

import {cat, ThreadType} from './exportingFile'

cat(ThreadType.CommentThread)

The output:

exportingFile.js

export var ThreadType;
(function (ThreadType) {
    ThreadType[ThreadType["CommentThread"] = 0] = "CommentThread";
    ThreadType[ThreadType["DiscussionThread"] = 1] = "DiscussionThread";
})(ThreadType || (ThreadType = {}));
export function cat(a) {
    if (a == ThreadType.CommentThread) {
        console.log('No. No cats. You suck!');
    }
    else {
        console.log('Ok. Sure. I can see how cats could be considered cute....');
    }
}

importingModule.js

import { cat } from './exportingFile';
cat(ThreadType.CommentThread);

Notice that:

  1. It generated an enum object instead of a numeric literal
  2. the output code obviously wont work (ThreadType is undefined).
  3. the code would work if it kept the import (or generated a literal like normal)
  4. tsc gives no errors nor warnings

@jablko
Copy link
Contributor

jablko commented Nov 3, 2021

@Brandon-Beck Your issue is the same as #16671 (comment), I think? It's fixed since #40499.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs More Info The issue still hasn't been fully clarified
Projects
None yet
Development

No branches or pull requests

4 participants