Skip to content

Incorrect transpilation of class name references in static contexts #54607

@evanw

Description

@evanw

Bug Report

πŸ”Ž Search Terms

class static in:title

πŸ•— Version & Regression Information

  • This is the behavior in every version I tried (note: static blocks were introduced in TS 4.4)

⏯ Playground Link

Playground link with relevant code

πŸ’» Code

class Foo {
  static { console.log(this, Foo) }
  static x = () => { console.log(this, Foo) }
}
const oldFoo = Foo;
(Foo as any) = null;
oldFoo.x();

πŸ™ Actual behavior

If you click Run, the code that TypeScript generates prints something like this:

class Foo {} class Foo {}
class Foo {} null

The code that TypeScript generates looks like this:

"use strict";
var _a;
class Foo {
}
_a = Foo;
(() => {
    console.log(_a, Foo);
})();
Foo.x = () => { console.log(_a, Foo); };
const oldFoo = Foo;
Foo = null;
oldFoo.x();

πŸ™‚ Expected behavior

If you run this code natively, it prints something like this:

class Foo {} class Foo {}
class Foo {} class Foo {}

I expected TypeScript to generate something like this instead:

"use strict";
var _a;
class Foo {
}
_a = Foo;
(() => {
    console.log(_a, _a);
})();
_a.x = () => { console.log(_a, _a); };
const oldFoo = Foo;
Foo = null;
oldFoo.x();

Class declarations in JavaScript generate an outer binding name, which turns out to be mutable (and can be reassigned). Separately, JavaScript also generates an immutable inner binding name that is only in scope within the class body. Referencing the class name within the class body gives the inner immutable binding name, and referencing the class name outside the class body gives the mutable outer binding name.

Presumably the _a that TypeScript is generating is this immutable inner binding name for code within the class body that needs to be moved out of the class body during syntax lowering. However, TypeScript isn't using the _a variable in all places where the immutable inner binding name should be referenced instead of the outer binding name. This leads to bugs in the case where the outer binding name is reassigned.

I discovered this bug in TypeScript because I'm trying to figure out how to handle this in esbuild, and as part of that I'm investigating how TypeScript transpiles code that does this.

Metadata

Metadata

Assignees

Labels

BugA bug in TypeScriptFix AvailableA PR has been opened for this issue

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions