Skip to content

Enum and namespace transformer #284

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

Merged
merged 24 commits into from
Feb 13, 2025
Merged

Conversation

rbuckton
Copy link

@rbuckton rbuckton commented Feb 6, 2025

This introduces transformer to handle enum and namespace emit to JavaScript. This transformer does not currently support const enum inlining, nor does it support unqualified enum member name resolution in merged enums (i.e., enum E { A }; enum E { B = A }), nor unqualified namespace export references in merged namespaces (i.e., namespace N { export var x = 1; } namespace N { x; }.

In order to reduce the dependence on the type checker, this transformer pushes some of the logic for auto-incrementing enum members to the runtime. When possible, the transformer uses a number of heuristics to simplify the enum emit.

The following are some examples of the new emit:

Auto-numbered enum

(matches existing emit)

// input
enum E {
  A,
  B,
  C,
}

// output
var E;
(function (E) {
  E[E["A"] = 0] = "A";
  E[E["B"] = 1] = "B";
  E[E["C"] = 2] = "C";
})(E || (E = {}));

Auto-numbered enum with explicit start

(matches existing emit)

// input
enum E {
  A = 2,
  B,
  C,
}

// output
var E;
(function (E) {
  E[E["A"] = 2] = "A";
  E[E["B"] = 3] = "B";
  E[E["C"] = 4] = "C";
})(E || (E = {}));

String enum

(matches existing emit)

// input
enum E {
  A = "x",
  B = "y",
  C = "z",
}

// output
var E;
(function (E) {
  E["A"] = "x";
  E["B"] = "y";
  E["C"] = "z";
})(E || (E = {}));

Auto-numbered enum with self reference

(changed emit)

// input
enum E {
  A,
  B = A,
  C,
}

// output
var E;
(function (E) {
  var A, auto
  E[E["A"] = A = 0] = "A";
  E[E["B"] = auto = A] = "B";
  E[E["C"] = ++auto] = "C";
})(E || (E = {}));

In this example, we introduce a local A binding that is used when evalutating E.B. From that point on, we use the varaible auto to track further auto-incrementing at runtime.

Reference to external variable

(changed emit)

// input
enum E {
  A = x,
  B = y,
}

// output
var E;
(function (E) {
  var A, B
  E["A"] = A = x;
  if (typeof A !== "string") E[A] = "A";
  E["B"] = B = y;
  if (typeof B !== "string") E[B] = "B";
})(E || (E = {}));

This emulates at runtime what we validate at check time. In the old compiler, if the checker determined that x or y were string types, then the reverse mapping would not be applied.

Auto-incrementing after external variable reference

(changed emit)

The old compiler would report an error if you continued using auto-numbered enum members after an enum member initializer that did not produce a numeric literal type. In the new emitter we assume that this check succeeded and resume auto-numbering, as an error would signify that the results were unreliable.

// input
declare var x: any;
enum E {
  A = x,
  B // Error: Enum member must have initializer.
}

// output (old)
var E;
(function (E) {
    E[E["A"] = x] = "A";
    E[E["B"] = void 0] = "B"; // type-directed emit, produces `void 0`
})(E || (E = {}));

// output (new)
var E;
(function (E) {
    var auto;
    E[E["A"] = auto = x] = "A";
    E[E["B"] = ++auto] = "B";
})(E || (E = {}));

In the old compiler, the above emit is type-directed as it changes based on the type of x:

// input
declare const x = 1;
enum E {
  A = x,
  B // Error: Enum member must have initializer.
}

// output (old)
var E;
(function (E) {
    E[E["A"] = 1] = "A"; // type-directed emit, inlines 'x'
    E[E["B"] = 2] = "B"; // type-directed emit
})(E || (E = {}));

// output (new)
var E;
(function (E) {
    var auto;
    E[E["A"] = auto = x] = "A";
    E[E["B"] = ++auto] = "B";
})(E || (E = {}));

However, in the new emit we produce the same output in both cases, which produces the correct result in the non-error case.

@rbuckton
Copy link
Author

rbuckton commented Feb 7, 2025

I've finished most of the namespace transformation. As it depends heavily on the work in this PR, rather than put up a separate PR I will amend this PR to include the namespace transformation as well.

Copy link
Member

@jakebailey jakebailey left a comment

Choose a reason for hiding this comment

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

This seems okay to me, though this PR still has the "changed emit" which I don't remember if were okay with or not compared to using the checker (I am personally okay with it for sure).

type EmitFlags uint32

const (
EFSingleLine EmitFlags = 1 << iota // The contents of this node should be emitted on a single line.
Copy link
Member

Choose a reason for hiding this comment

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

I think we only have one other case where we do this naming scheme, but, I can't really complain 😄

@rbuckton rbuckton changed the title Enum transformer Enum and namespace transformer Feb 7, 2025
@rbuckton rbuckton mentioned this pull request Feb 11, 2025
@@ -767,7 +767,7 @@ func (b *Binder) bindModuleDeclaration(node *ast.Node) {
}

func (b *Binder) declareModuleSymbol(node *ast.Node) ModuleInstanceState {
state := getModuleInstanceState(node, nil /*visited*/)
state := GetModuleInstanceState(node, nil /*visited*/)
Copy link
Member

Choose a reason for hiding this comment

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

https://github.com/microsoft/typescript-go/pull/296/files#diff-9969a067bafd18e3dafe06418204ef65b894c09827a5c98ca142d7cb2e7be4e3R1930 actually moves this all to the AST package, which might be good to pull out into a PR before these two?

@rbuckton rbuckton mentioned this pull request Feb 11, 2025
Copy link
Member

@jakebailey jakebailey left a comment

Choose a reason for hiding this comment

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

Still LGTM

@rbuckton rbuckton merged commit a63bfea into microsoft:main Feb 13, 2025
15 checks passed
@rbuckton rbuckton deleted the enum-transform branch February 13, 2025 18:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants