Skip to content

Commit b1c4dc4

Browse files
authored
Fix class name references (#55262)
1 parent 87c986c commit b1c4dc4

40 files changed

+225
-177
lines changed

Diff for: src/compiler/checker.ts

+12-31
Original file line numberDiff line numberDiff line change
@@ -851,7 +851,6 @@ import {
851851
NodeCheckFlags,
852852
NodeFlags,
853853
nodeHasName,
854-
nodeIsDecorated,
855854
nodeIsMissing,
856855
nodeIsPresent,
857856
nodeIsSynthesized,
@@ -1075,7 +1074,7 @@ import {
10751074
WhileStatement,
10761075
WideningContext,
10771076
WithStatement,
1078-
YieldExpression,
1077+
YieldExpression
10791078
} from "./_namespaces/ts";
10801079
import * as moduleSpecifiers from "./_namespaces/ts.moduleSpecifiers";
10811080
import * as performance from "./_namespaces/ts.performance";
@@ -28001,38 +28000,20 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2800128000

2800228001
let declaration = localOrExportSymbol.valueDeclaration;
2800328002
if (declaration && localOrExportSymbol.flags & SymbolFlags.Class) {
28004-
// Due to the emit for class decorators, any reference to the class from inside of the class body
28005-
// must instead be rewritten to point to a temporary variable to avoid issues with the double-bind
28006-
// behavior of class names in ES6.
28007-
if (declaration.kind === SyntaxKind.ClassDeclaration
28008-
&& nodeIsDecorated(legacyDecorators, declaration as ClassDeclaration)) {
28009-
let container = getContainingClass(node);
28010-
while (container !== undefined) {
28011-
if (container === declaration && container.name !== node) {
28012-
getNodeLinks(declaration).flags |= NodeCheckFlags.ClassWithConstructorReference;
28013-
getNodeLinks(node).flags |= NodeCheckFlags.ConstructorReferenceInClass;
28014-
break;
28015-
}
28016-
28017-
container = getContainingClass(container);
28018-
}
28019-
}
28020-
else if (declaration.kind === SyntaxKind.ClassExpression) {
28021-
// When we emit a class expression with static members that contain a reference
28022-
// to the constructor in the initializer, we will need to substitute that
28023-
// binding with an alias as the class name is not in scope.
28003+
// When we downlevel classes we may emit some code outside of the class body. Due to the fact the
28004+
// class name is double-bound, we must ensure we mark references to the class name so that we can
28005+
// emit an alias to the class later.
28006+
if (isClassLike(declaration) && declaration.name !== node) {
2802428007
let container = getThisContainer(node, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false);
28025-
while (container.kind !== SyntaxKind.SourceFile) {
28026-
if (container.parent === declaration) {
28027-
if (isPropertyDeclaration(container) && isStatic(container) || isClassStaticBlockDeclaration(container)) {
28028-
getNodeLinks(declaration).flags |= NodeCheckFlags.ClassWithConstructorReference;
28029-
getNodeLinks(node).flags |= NodeCheckFlags.ConstructorReferenceInClass;
28030-
}
28031-
break;
28032-
}
28033-
28008+
while (container.kind !== SyntaxKind.SourceFile && container.parent !== declaration) {
2803428009
container = getThisContainer(container, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false);
2803528010
}
28011+
28012+
if (container.kind !== SyntaxKind.SourceFile) {
28013+
getNodeLinks(declaration).flags |= NodeCheckFlags.ContainsConstructorReference;
28014+
getNodeLinks(container).flags |= NodeCheckFlags.ContainsConstructorReference;
28015+
getNodeLinks(node).flags |= NodeCheckFlags.ConstructorReference;
28016+
}
2803628017
}
2803728018
}
2803828019

Diff for: src/compiler/transformers/classFields.ts

+13-3
Original file line numberDiff line numberDiff line change
@@ -1718,6 +1718,9 @@ export function transformClassFields(context: TransformationContext): (x: Source
17181718
}
17191719
else if (isPrivateIdentifierClassElementDeclaration(member)) {
17201720
containsInstancePrivateElements = true;
1721+
if (resolver.getNodeCheckFlags(member) & NodeCheckFlags.ContainsConstructorReference) {
1722+
facts |= ClassFacts.NeedsClassConstructorReference;
1723+
}
17211724
}
17221725
else if (isPropertyDeclaration(member)) {
17231726
containsPublicInstanceFields = true;
@@ -1849,6 +1852,7 @@ export function transformClassFields(context: TransformationContext): (x: Source
18491852
}
18501853
}
18511854

1855+
const isClassWithConstructorReference = resolver.getNodeCheckFlags(node) & NodeCheckFlags.ContainsConstructorReference;
18521856
const isExport = hasSyntacticModifier(node, ModifierFlags.Export);
18531857
const isDefault = hasSyntacticModifier(node, ModifierFlags.Default);
18541858
let modifiers = visitNodes(node.modifiers, modifierVisitor, isModifier);
@@ -1887,6 +1891,12 @@ export function transformClassFields(context: TransformationContext): (x: Source
18871891
));
18881892
}
18891893

1894+
const alias = getClassLexicalEnvironment().classConstructor;
1895+
if (isClassWithConstructorReference && alias) {
1896+
enableSubstitutionForClassAliases();
1897+
classAliases[getOriginalNodeId(node)] = alias;
1898+
}
1899+
18901900
const classDecl = factory.updateClassDeclaration(
18911901
node,
18921902
modifiers,
@@ -1918,7 +1928,8 @@ export function transformClassFields(context: TransformationContext): (x: Source
19181928
// these statements after the class expression variable statement.
19191929
const isDecoratedClassDeclaration = !!(facts & ClassFacts.ClassWasDecorated);
19201930
const staticPropertiesOrClassStaticBlocks = getStaticPropertiesAndClassStaticBlock(node);
1921-
const isClassWithConstructorReference = resolver.getNodeCheckFlags(node) & NodeCheckFlags.ClassWithConstructorReference;
1931+
const classCheckFlags = resolver.getNodeCheckFlags(node);
1932+
const isClassWithConstructorReference = classCheckFlags & NodeCheckFlags.ContainsConstructorReference;
19221933

19231934
let temp: Identifier | undefined;
19241935
function createClassTempVar() {
@@ -1935,7 +1946,6 @@ export function transformClassFields(context: TransformationContext): (x: Source
19351946
return getClassLexicalEnvironment().classConstructor = node.emitNode.classThis;
19361947
}
19371948

1938-
const classCheckFlags = resolver.getNodeCheckFlags(node);
19391949
const requiresBlockScopedVar = classCheckFlags & NodeCheckFlags.BlockScopedBindingInLoop;
19401950
const temp = factory.createTempVariable(requiresBlockScopedVar ? addBlockScopedVariable : hoistVariableDeclaration, /*reservedInNestedScopes*/ true);
19411951
getClassLexicalEnvironment().classConstructor = factory.cloneNode(temp);
@@ -3236,7 +3246,7 @@ export function transformClassFields(context: TransformationContext): (x: Source
32363246

32373247
function trySubstituteClassAlias(node: Identifier): Expression | undefined {
32383248
if (enabledSubstitutions & ClassPropertySubstitutionFlags.ClassAliases) {
3239-
if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.ConstructorReferenceInClass) {
3249+
if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.ConstructorReference) {
32403250
// Due to the emit for class decorators, any reference to the class from inside of the class body
32413251
// must instead be rewritten to point to a temporary variable to avoid issues with the double-bind
32423252
// behavior of class names in ES6.

Diff for: src/compiler/transformers/legacyDecorators.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -756,7 +756,7 @@ export function transformLegacyDecorators(context: TransformationContext): (x: S
756756
* double-binding semantics for the class name.
757757
*/
758758
function getClassAliasIfNeeded(node: ClassDeclaration) {
759-
if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.ClassWithConstructorReference) {
759+
if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.ContainsConstructorReference) {
760760
enableSubstitutionForClassAliases();
761761
const classAlias = factory.createUniqueName(node.name && !isGeneratedIdentifier(node.name) ? idText(node.name) : "default");
762762
classAliases[getOriginalNodeId(node)] = classAlias;
@@ -805,7 +805,7 @@ export function transformLegacyDecorators(context: TransformationContext): (x: S
805805

806806
function trySubstituteClassAlias(node: Identifier): Expression | undefined {
807807
if (classAliases) {
808-
if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.ConstructorReferenceInClass) {
808+
if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.ConstructorReference) {
809809
// Due to the emit for class decorators, any reference to the class from inside of the class body
810810
// must instead be rewritten to point to a temporary variable to avoid issues with the double-bind
811811
// behavior of class names in ES6.

Diff for: src/compiler/types.ts

+7-9
Original file line numberDiff line numberDiff line change
@@ -6022,15 +6022,13 @@ export const enum NodeCheckFlags {
60226022
ContainsCapturedBlockScopeBinding = 1 << 13, // Part of a loop that contains block scoped variable captured in closure
60236023
CapturedBlockScopedBinding = 1 << 14, // Block-scoped binding that is captured in some function
60246024
BlockScopedBindingInLoop = 1 << 15, // Block-scoped binding with declaration nested inside iteration statement
6025-
ClassWithBodyScopedClassBinding = 1 << 16, // Decorated class that contains a binding to itself inside of the class body.
6026-
BodyScopedClassBinding = 1 << 17, // Binding to a decorated class inside of the class's body.
6027-
NeedsLoopOutParameter = 1 << 18, // Block scoped binding whose value should be explicitly copied outside of the converted loop
6028-
AssignmentsMarked = 1 << 19, // Parameter assignments have been marked
6029-
ClassWithConstructorReference = 1 << 20, // Class that contains a binding to its constructor inside of the class body.
6030-
ConstructorReferenceInClass = 1 << 21, // Binding to a class constructor inside of the class's body.
6031-
ContainsClassWithPrivateIdentifiers = 1 << 22, // Marked on all block-scoped containers containing a class with private identifiers.
6032-
ContainsSuperPropertyInStaticInitializer = 1 << 23, // Marked on all block-scoped containers containing a static initializer with 'super.x' or 'super[x]'.
6033-
InCheckIdentifier = 1 << 24,
6025+
NeedsLoopOutParameter = 1 << 16, // Block scoped binding whose value should be explicitly copied outside of the converted loop
6026+
AssignmentsMarked = 1 << 17, // Parameter assignments have been marked
6027+
ContainsConstructorReference = 1 << 18, // Class or class element that contains a binding that references the class constructor.
6028+
ConstructorReference = 1 << 29, // Binding to a class constructor inside of the class's body.
6029+
ContainsClassWithPrivateIdentifiers = 1 << 20, // Marked on all block-scoped containers containing a class with private identifiers.
6030+
ContainsSuperPropertyInStaticInitializer = 1 << 21, // Marked on all block-scoped containers containing a static initializer with 'super.x' or 'super[x]'.
6031+
InCheckIdentifier = 1 << 22,
60346032
}
60356033

60366034
/** @internal */

Diff for: tests/baselines/reference/classBlockScoping.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,10 @@ function f(b) {
4444
function Foo() {
4545
}
4646
Foo.x = function () {
47-
new Foo();
47+
new _a();
4848
};
4949
Foo.prototype.m = function () {
50-
new Foo();
50+
new _a();
5151
};
5252
return Foo;
5353
}()),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
//// [tests/cases/compiler/classNameReferencesInStaticElements.ts] ////
2+
3+
//// [classNameReferencesInStaticElements.ts]
4+
// https://github.com/microsoft/TypeScript/issues/54607
5+
class Foo {
6+
static { console.log(this, Foo) }
7+
static x = () => { console.log(this, Foo) }
8+
static y = function(this: unknown) { console.log(this, Foo) }
9+
10+
#x() { console.log(Foo); }
11+
x() { this.#x(); }
12+
}
13+
14+
const oldFoo = Foo;
15+
(Foo as any) = null;
16+
oldFoo.x();
17+
oldFoo.y();
18+
new oldFoo().x();
19+
20+
//// [classNameReferencesInStaticElements.js]
21+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
22+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
23+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
24+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
25+
};
26+
var _Foo_instances, _a, _Foo_x;
27+
// https://github.com/microsoft/TypeScript/issues/54607
28+
class Foo {
29+
constructor() {
30+
_Foo_instances.add(this);
31+
}
32+
x() { __classPrivateFieldGet(this, _Foo_instances, "m", _Foo_x).call(this); }
33+
}
34+
_a = Foo, _Foo_instances = new WeakSet(), _Foo_x = function _Foo_x() { console.log(_a); };
35+
(() => {
36+
console.log(_a, _a);
37+
})();
38+
Foo.x = () => { console.log(_a, _a); };
39+
Foo.y = function () { console.log(this, _a); };
40+
const oldFoo = Foo;
41+
Foo = null;
42+
oldFoo.x();
43+
oldFoo.y();
44+
new oldFoo().x();

Diff for: tests/baselines/reference/classStaticBlock12.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,5 @@ class C {
2222
_a = C;
2323
_C_x = { value: 1 };
2424
(() => {
25-
__classPrivateFieldGet(C, _a, "f", _C_x);
25+
__classPrivateFieldGet(_a, _a, "f", _C_x);
2626
})();

Diff for: tests/baselines/reference/classStaticBlock13(target=es2015).js

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
2323
var _a, _C_x;
2424
class C {
2525
foo() {
26-
return __classPrivateFieldGet(C, _a, "f", _C_x);
26+
return __classPrivateFieldGet(_a, _a, "f", _C_x);
2727
}
2828
}
2929
_a = C;
3030
_C_x = { value: 123 };
3131
(() => {
32-
console.log(__classPrivateFieldGet(C, _a, "f", _C_x));
32+
console.log(__classPrivateFieldGet(_a, _a, "f", _C_x));
3333
})();

Diff for: tests/baselines/reference/computedPropertyNames52(target=es2015).js

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ for (let i = 0; i < 10; ++i) {
1717
let _b, _c;
1818
array.push((_c = class C {
1919
constructor() {
20-
this[_b] = () => C;
20+
this[_b] = () => _c;
2121
}
2222
},
2323
_b = i,

Diff for: tests/baselines/reference/computedPropertyNames52(target=es5).js

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ var _loop_1 = function (i) {
1717
var _b = void 0, _c = void 0;
1818
array.push((_c = /** @class */ (function () {
1919
function C() {
20-
this[_b] = function () { return C; };
20+
this[_b] = function () { return _c; };
2121
}
2222
return C;
2323
}()),

Diff for: tests/baselines/reference/decoratedClassExportsCommonJS1.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,5 @@ let Testing123 = Testing123_1 = class Testing123 {
2424
exports.Testing123 = Testing123;
2525
Testing123.prop1 = Testing123_1.prop0;
2626
exports.Testing123 = Testing123 = Testing123_1 = __decorate([
27-
Something({ v: () => Testing123_1 })
27+
Something({ v: () => Testing123 })
2828
], Testing123);

Diff for: tests/baselines/reference/decoratedClassExportsCommonJS2.js

+3-4
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,11 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
1414
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1515
return c > 3 && r && Object.defineProperty(target, key, r), r;
1616
};
17-
var Testing123_1;
1817
Object.defineProperty(exports, "__esModule", { value: true });
1918
exports.Testing123 = void 0;
20-
let Testing123 = Testing123_1 = class Testing123 {
19+
let Testing123 = class Testing123 {
2120
};
2221
exports.Testing123 = Testing123;
23-
exports.Testing123 = Testing123 = Testing123_1 = __decorate([
24-
Something({ v: () => Testing123_1 })
22+
exports.Testing123 = Testing123 = __decorate([
23+
Something({ v: () => Testing123 })
2524
], Testing123);

Diff for: tests/baselines/reference/decoratedClassExportsSystem1.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ System.register([], function (exports_1, context_1) {
2828
exports_1("Testing123", Testing123);
2929
Testing123.prop1 = Testing123_1.prop0;
3030
exports_1("Testing123", Testing123 = Testing123_1 = __decorate([
31-
Something({ v: () => Testing123_1 })
31+
Something({ v: () => Testing123 })
3232
], Testing123));
3333
}
3434
};

Diff for: tests/baselines/reference/decoratedClassExportsSystem2.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,16 @@ System.register([], function (exports_1, context_1) {
1515
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1616
return c > 3 && r && Object.defineProperty(target, key, r), r;
1717
};
18-
var Testing123_1, Testing123;
18+
var Testing123;
1919
var __moduleName = context_1 && context_1.id;
2020
return {
2121
setters: [],
2222
execute: function () {
23-
Testing123 = Testing123_1 = class Testing123 {
23+
Testing123 = class Testing123 {
2424
};
2525
exports_1("Testing123", Testing123);
26-
exports_1("Testing123", Testing123 = Testing123_1 = __decorate([
27-
Something({ v: () => Testing123_1 })
26+
exports_1("Testing123", Testing123 = __decorate([
27+
Something({ v: () => Testing123 })
2828
], Testing123));
2929
}
3030
};

Diff for: tests/baselines/reference/decoratorOnClassConstructorParameter5.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,14 @@ class BulkEditPreviewProvider {
1313
}
1414

1515
//// [decoratorOnClassConstructorParameter5.js]
16-
let BulkEditPreviewProvider = class BulkEditPreviewProvider {
16+
var BulkEditPreviewProvider_1;
17+
let BulkEditPreviewProvider = BulkEditPreviewProvider_1 = class BulkEditPreviewProvider {
1718
constructor(_modeService) {
1819
this._modeService = _modeService;
1920
}
2021
};
2122
BulkEditPreviewProvider.Schema = 'vscode-bulkeditpreview';
22-
BulkEditPreviewProvider.emptyPreview = { scheme: BulkEditPreviewProvider.Schema };
23-
BulkEditPreviewProvider = __decorate([
23+
BulkEditPreviewProvider.emptyPreview = { scheme: BulkEditPreviewProvider_1.Schema };
24+
BulkEditPreviewProvider = BulkEditPreviewProvider_1 = __decorate([
2425
__param(0, IFoo)
2526
], BulkEditPreviewProvider);

Diff for: tests/baselines/reference/decoratorReferences.js

+2-4
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,14 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
2222
var C = /** @class */ (function () {
2323
function C() {
2424
}
25-
C_1 = C;
2625
C.prototype.method = function (x, y) { }; // <-- decorator y should be resolved at the class declaration, not the parameter.
27-
var C_1;
2826
__decorate([
2927
y(null) // <-- y should resolve to the function declaration, not the parameter; T should resolve to the type parameter of the class
3028
,
3129
__param(0, y)
3230
], C.prototype, "method", null);
33-
C = C_1 = __decorate([
34-
y(1, C_1) // <-- T should be resolved to the type alias, not the type parameter of the class; C should resolve to the class
31+
C = __decorate([
32+
y(1, C) // <-- T should be resolved to the type alias, not the type parameter of the class; C should resolve to the class
3533
], C);
3634
return C;
3735
}());

Diff for: tests/baselines/reference/privateNameAndObjectRestSpread.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,9 @@ class C {
4646
__classPrivateFieldGet(obj, _C_prop, "f");
4747
const rest = __rest(other, []);
4848
__classPrivateFieldGet(rest, _C_prop, "f");
49-
const statics = Object.assign({}, C);
49+
const statics = Object.assign({}, _a);
5050
__classPrivateFieldGet(statics, _a, "f", _C_propStatic);
51-
const sRest = __rest(C, []);
51+
const sRest = __rest(_a, []);
5252
__classPrivateFieldGet(sRest, _a, "f", _C_propStatic);
5353
}
5454
}

0 commit comments

Comments
 (0)