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

src/compiler/checker.ts

Lines changed: 12 additions & 31 deletions
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

src/compiler/transformers/classFields.ts

Lines changed: 13 additions & 3 deletions
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.

src/compiler/transformers/legacyDecorators.ts

Lines changed: 2 additions & 2 deletions
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.

src/compiler/types.ts

Lines changed: 7 additions & 9 deletions
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 */

tests/baselines/reference/classBlockScoping.js

Lines changed: 2 additions & 2 deletions
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
}()),
Lines changed: 44 additions & 0 deletions
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();

tests/baselines/reference/classStaticBlock12.js

Lines changed: 1 addition & 1 deletion
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
})();

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

Lines changed: 2 additions & 2 deletions
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
})();

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

Lines changed: 1 addition & 1 deletion
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,

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

Lines changed: 1 addition & 1 deletion
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
}()),

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy