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

Ensure enum members syntactically determinable to be strings do not get reverse mappings #57686

Conversation

andrewbranch
Copy link
Member

Unblocks #56736?

In #56164, we decided that under isolatedModules, (from the notes) “enum members referencing another external enum must always be numeric, or explicitly converted to a string. Otherwise, it is an error.” This proposed rule has since undergone a number of interpretations on its way to an attempt to implement it. In #56153 (comment), Ryan stated that we should issue an error when “an enum initializer expression that isn't a string literal has a string type.” In #56736, @frigus02 worked off this statement, but interpreted “string literal” as any expression that could be evaluated to a known constant string value with single-file syntactic information only.

An invariant guiding the decision is that error-free (under --isolatedModules) code should produce semantically equivalent JS between ts.transpileModule and tsc. Currently, that’s violated by this test case:

// @filename: ./foo.ts
import { BAR } from './bar';
enum Foo { A = `${BAR}` }

// @filename: ./bar.ts
export const BAR = 'bar';

This is currently error-free, but the emit varies. tsc:

import { BAR } from './bar';
var Foo;
(function (Foo) {
    Foo["A"] = "bar";
})(Foo || (Foo = {}));

ts.transpileModule:

import { BAR } from './bar';
var Foo;
(function (Foo) {
    Foo[Foo["A"] = `${BAR}`] = "A";
})(Foo || (Foo = {}));

The problematic part isn’t the inlining of the value "bar"; it’s the fact that the transpiled output includes a reverse mapping when it absolutely shouldn’t.

To fix this, we either need an error under isolatedModules or we need to fix the ts.transpileModule emit. Both Ryan’s formulation of the rule proposed in #56164 and @frigus02’s expanded interpretation in #56736 make this code an error. This PR fixes the emit by determining that the initializer is definitely going to be a string without looking at any type or extra-file info.

I’m fine with adopting Ryan’s very strict rule instead of this, but it does seem like fairly low hanging fruit here. I’m less of a fan of the approach currently in #56736, since constant evaluation feels like the wrong thing to control whether a reverse mapping gets created, even though that’s what actually controls the emit today.

@typescript-bot typescript-bot added Author: Team For Uncommitted Bug PR for untriaged, rejected, closed or missing bug labels Mar 8, 2024
Copy link
Contributor

@frigus02 frigus02 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, Andrew. I'm personally in favor of this approach.

Co-authored-by: Jan Kühle <[email protected]>
@jakebailey
Copy link
Member

since constant evaluation feels like the wrong thing to control whether a reverse mapping gets created

I personally agree with this, and so think I prefer this PR.

even though that’s what actually controls the emit today.

Is there some way we can instead use isSyntacticallyString to do that, and we stop using constant eval entirely for the other determinations?

@andrewbranch
Copy link
Member Author

This PR moves in that direction, but we can't fully control emit by isSyntacticallyString because that would break the case

import { SomeOtherStringEnum } from './m';

enum Foo {
  A = SomeOtherStringEnum.StringValue
}

This works in non-isolatedModules tsc. #56736 will make it an error under isolatedModules. Basically, you're asking for the isolatedModules rules to apply to everyone, which might be the design choice we would make starting fresh, but would be a big breaking change now.

@andrewbranch
Copy link
Member Author

@typescript-bot pack this

@typescript-bot
Copy link
Collaborator

typescript-bot commented Mar 12, 2024

Starting jobs; this comment will be updated as builds start and complete.

Command Status Results
pack this ✅ Started ✅ Results

@typescript-bot
Copy link
Collaborator

typescript-bot commented Mar 12, 2024

Hey @andrewbranch, 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/160329/artifacts?artifactName=tgz&fileId=869CEB102DBC9A8715F1606BF689837B82FDE0677C3EC0E8EEE6C0299669009802&fileName=/typescript-5.5.0-insiders.20240312.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]".;

@andrewbranch
Copy link
Member Author

Jake and Ryan have expressed support for this. Can I have a ✅? 😄

@andrewbranch andrewbranch merged commit f9ef943 into microsoft:main Mar 13, 2024
19 checks passed
@andrewbranch andrewbranch deleted the no-reverse-mapping-string-enum-members branch March 13, 2024 20:11
frigus02 added a commit to frigus02/TypeScript that referenced this pull request Mar 15, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Author: Team For Uncommitted Bug PR for untriaged, rejected, closed or missing bug
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants