Skip to content

Commit 66c3063

Browse files
authored
Add collision check for 'Reflect' when using super in static initializers (#44876)
* Add collision check for 'Reflect' when using super in static initializers * PR feedback * Accept baseline for new failing test
1 parent 6ee8154 commit 66c3063

18 files changed

+6880
-81
lines changed

src/compiler/checker.ts

Lines changed: 123 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -959,6 +959,7 @@ namespace ts {
959959
const potentialThisCollisions: Node[] = [];
960960
const potentialNewTargetCollisions: Node[] = [];
961961
const potentialWeakMapSetCollisions: Node[] = [];
962+
const potentialReflectCollisions: Node[] = [];
962963
const awaitedTypeStack: number[] = [];
963964

964965
const diagnostics = createDiagnosticCollection();
@@ -24873,6 +24874,18 @@ namespace ts {
2487324874

2487424875
if (isStatic(container) || isCallExpression) {
2487524876
nodeCheckFlag = NodeCheckFlags.SuperStatic;
24877+
if (!isCallExpression &&
24878+
languageVersion >= ScriptTarget.ES2015 && languageVersion <= ScriptTarget.ES2021 &&
24879+
(isPropertyDeclaration(container) || isClassStaticBlockDeclaration(container))) {
24880+
// for `super.x` or `super[x]` in a static initializer, mark all enclosing
24881+
// block scope containers so that we can report potential collisions with
24882+
// `Reflect`.
24883+
forEachEnclosingBlockScopeContainer(node.parent, current => {
24884+
if (!isSourceFile(current) || isExternalOrCommonJsModule(current)) {
24885+
getNodeLinks(current).flags |= NodeCheckFlags.ContainsSuperPropertyInStaticInitializer;
24886+
}
24887+
});
24888+
}
2487624889
}
2487724890
else {
2487824891
nodeCheckFlag = NodeCheckFlags.SuperInstance;
@@ -31159,6 +31172,10 @@ namespace ts {
3115931172
Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node));
3116031173
checkNodeDeferred(node);
3116131174

31175+
if (isFunctionExpression(node)) {
31176+
checkCollisionsForDeclarationName(node, node.name);
31177+
}
31178+
3116231179
// The identityMapper object is used to indicate that function expressions are wildcards
3116331180
if (checkMode && checkMode & CheckMode.SkipContextSensitive && isContextSensitive(node)) {
3116431181
// Skip parameters, return signature with return type that retains noncontextual parts so inferences can still be drawn in an early stage
@@ -34946,8 +34963,7 @@ namespace ts {
3494634963
if (produceDiagnostics) {
3494734964
checkFunctionOrMethodDeclaration(node);
3494834965
checkGrammarForGenerator(node);
34949-
checkCollisionWithRequireExportsInGeneratedCode(node, node.name!);
34950-
checkCollisionWithGlobalPromiseInGeneratedCode(node, node.name!);
34966+
checkCollisionsForDeclarationName(node, node.name);
3495134967
}
3495234968
}
3495334969

@@ -35470,8 +35486,13 @@ namespace ts {
3547035486
});
3547135487
}
3547235488

35489+
/**
35490+
* Checks whether an {@link Identifier}, in the context of another {@link Node}, would collide with a runtime value
35491+
* of {@link name} in an outer scope. This is used to check for collisions for downlevel transformations that
35492+
* require names like `Object`, `Promise`, `Reflect`, `require`, `exports`, etc.
35493+
*/
3547335494
function needCollisionCheckForIdentifier(node: Node, identifier: Identifier | undefined, name: string): boolean {
35474-
if (!(identifier && identifier.escapedText === name)) {
35495+
if (identifier?.escapedText !== name) {
3547535496
return false;
3547635497
}
3547735498

@@ -35480,8 +35501,9 @@ namespace ts {
3548035501
node.kind === SyntaxKind.MethodDeclaration ||
3548135502
node.kind === SyntaxKind.MethodSignature ||
3548235503
node.kind === SyntaxKind.GetAccessor ||
35483-
node.kind === SyntaxKind.SetAccessor) {
35484-
// it is ok to have member named '_super' or '_this' - member access is always qualified
35504+
node.kind === SyntaxKind.SetAccessor ||
35505+
node.kind === SyntaxKind.PropertyAssignment) {
35506+
// it is ok to have member named '_super', '_this', `Promise`, etc. - member access is always qualified
3548535507
return false;
3548635508
}
3548735509

@@ -35490,8 +35512,15 @@ namespace ts {
3549035512
return false;
3549135513
}
3549235514

35515+
if (isImportClause(node) || isImportEqualsDeclaration(node) || isImportSpecifier(node)) {
35516+
// type-only imports do not require collision checks against runtime values.
35517+
if (isTypeOnlyImportOrExportDeclaration(node)) {
35518+
return false;
35519+
}
35520+
}
35521+
3549335522
const root = getRootDeclaration(node);
35494-
if (root.kind === SyntaxKind.Parameter && nodeIsMissing((root.parent as FunctionLikeDeclaration).body)) {
35523+
if (isParameter(root) && nodeIsMissing((root.parent as FunctionLikeDeclaration).body)) {
3549535524
// just an overload - no codegen impact
3549635525
return false;
3549735526
}
@@ -35532,21 +35561,13 @@ namespace ts {
3553235561
});
3553335562
}
3553435563

35535-
function checkWeakMapSetCollision(node: Node) {
35536-
const enclosingBlockScope = getEnclosingBlockScopeContainer(node);
35537-
if (getNodeCheckFlags(enclosingBlockScope) & NodeCheckFlags.ContainsClassWithPrivateIdentifiers) {
35538-
Debug.assert(isNamedDeclaration(node) && isIdentifier(node.name) && typeof node.name.escapedText === "string", "The target of a WeakMap/WeakSet collision check should be an identifier");
35539-
errorSkippedOn("noEmit", node, Diagnostics.Compiler_reserves_name_0_when_emitting_private_identifier_downlevel, node.name.escapedText);
35540-
}
35541-
}
35542-
35543-
function checkCollisionWithRequireExportsInGeneratedCode(node: Node, name: Identifier) {
35564+
function checkCollisionWithRequireExportsInGeneratedCode(node: Node, name: Identifier | undefined) {
3554435565
// No need to check for require or exports for ES6 modules and later
3554535566
if (moduleKind >= ModuleKind.ES2015) {
3554635567
return;
3554735568
}
3554835569

35549-
if (!needCollisionCheckForIdentifier(node, name, "require") && !needCollisionCheckForIdentifier(node, name, "exports")) {
35570+
if (!name || !needCollisionCheckForIdentifier(node, name, "require") && !needCollisionCheckForIdentifier(node, name, "exports")) {
3555035571
return;
3555135572
}
3555235573

@@ -35564,8 +35585,8 @@ namespace ts {
3556435585
}
3556535586
}
3556635587

35567-
function checkCollisionWithGlobalPromiseInGeneratedCode(node: Node, name: Identifier): void {
35568-
if (languageVersion >= ScriptTarget.ES2017 || !needCollisionCheckForIdentifier(node, name, "Promise")) {
35588+
function checkCollisionWithGlobalPromiseInGeneratedCode(node: Node, name: Identifier | undefined): void {
35589+
if (!name || languageVersion >= ScriptTarget.ES2017 || !needCollisionCheckForIdentifier(node, name, "Promise")) {
3556935590
return;
3557035591
}
3557135592

@@ -35583,6 +35604,76 @@ namespace ts {
3558335604
}
3558435605
}
3558535606

35607+
function recordPotentialCollisionWithWeakMapSetInGeneratedCode(node: Node, name: Identifier): void {
35608+
if (languageVersion <= ScriptTarget.ES2021
35609+
&& (needCollisionCheckForIdentifier(node, name, "WeakMap") || needCollisionCheckForIdentifier(node, name, "WeakSet"))) {
35610+
potentialWeakMapSetCollisions.push(node);
35611+
}
35612+
}
35613+
35614+
function checkWeakMapSetCollision(node: Node) {
35615+
const enclosingBlockScope = getEnclosingBlockScopeContainer(node);
35616+
if (getNodeCheckFlags(enclosingBlockScope) & NodeCheckFlags.ContainsClassWithPrivateIdentifiers) {
35617+
Debug.assert(isNamedDeclaration(node) && isIdentifier(node.name) && typeof node.name.escapedText === "string", "The target of a WeakMap/WeakSet collision check should be an identifier");
35618+
errorSkippedOn("noEmit", node, Diagnostics.Compiler_reserves_name_0_when_emitting_private_identifier_downlevel, node.name.escapedText);
35619+
}
35620+
}
35621+
35622+
function recordPotentialCollisionWithReflectInGeneratedCode(node: Node, name: Identifier | undefined): void {
35623+
if (name && languageVersion >= ScriptTarget.ES2015 && languageVersion <= ScriptTarget.ES2021
35624+
&& needCollisionCheckForIdentifier(node, name, "Reflect")) {
35625+
potentialReflectCollisions.push(node);
35626+
}
35627+
}
35628+
35629+
function checkReflectCollision(node: Node) {
35630+
let hasCollision = false;
35631+
if (isClassExpression(node)) {
35632+
// ClassExpression names don't contribute to their containers, but do matter for any of their block-scoped members.
35633+
for (const member of node.members) {
35634+
if (getNodeCheckFlags(member) & NodeCheckFlags.ContainsSuperPropertyInStaticInitializer) {
35635+
hasCollision = true;
35636+
break;
35637+
}
35638+
}
35639+
}
35640+
else if (isFunctionExpression(node)) {
35641+
// FunctionExpression names don't contribute to their containers, but do matter for their contents
35642+
if (getNodeCheckFlags(node) & NodeCheckFlags.ContainsSuperPropertyInStaticInitializer) {
35643+
hasCollision = true;
35644+
}
35645+
}
35646+
else {
35647+
const container = getEnclosingBlockScopeContainer(node);
35648+
if (container && getNodeCheckFlags(container) & NodeCheckFlags.ContainsSuperPropertyInStaticInitializer) {
35649+
hasCollision = true;
35650+
}
35651+
}
35652+
if (hasCollision) {
35653+
Debug.assert(isNamedDeclaration(node) && isIdentifier(node.name), "The target of a Reflect collision check should be an identifier");
35654+
errorSkippedOn("noEmit", node, Diagnostics.Duplicate_identifier_0_Compiler_reserves_name_1_when_emitting_super_references_in_static_initializers,
35655+
declarationNameToString(node.name),
35656+
"Reflect");
35657+
}
35658+
}
35659+
35660+
function checkCollisionsForDeclarationName(node: Node, name: Identifier | undefined) {
35661+
if (!name) return;
35662+
checkCollisionWithRequireExportsInGeneratedCode(node, name);
35663+
checkCollisionWithGlobalPromiseInGeneratedCode(node, name);
35664+
recordPotentialCollisionWithWeakMapSetInGeneratedCode(node, name);
35665+
recordPotentialCollisionWithReflectInGeneratedCode(node, name);
35666+
if (isClassLike(node)) {
35667+
checkTypeNameIsReserved(name, Diagnostics.Class_name_cannot_be_0);
35668+
if (!(node.flags & NodeFlags.Ambient)) {
35669+
checkClassNameCollisionWithObject(name);
35670+
}
35671+
}
35672+
else if (isEnumDeclaration(node)) {
35673+
checkTypeNameIsReserved(name, Diagnostics.Enum_name_cannot_be_0);
35674+
}
35675+
}
35676+
3558635677
function checkVarDeclaredNamesNotShadowed(node: VariableDeclaration | BindingElement) {
3558735678
// - ScriptBody : StatementList
3558835679
// It is a Syntax Error if any element of the LexicallyDeclaredNames of StatementList
@@ -35801,12 +35892,7 @@ namespace ts {
3580135892
if (node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement) {
3580235893
checkVarDeclaredNamesNotShadowed(node);
3580335894
}
35804-
checkCollisionWithRequireExportsInGeneratedCode(node, node.name);
35805-
checkCollisionWithGlobalPromiseInGeneratedCode(node, node.name);
35806-
if (languageVersion < ScriptTarget.ESNext
35807-
&& (needCollisionCheckForIdentifier(node, node.name, "WeakMap") || needCollisionCheckForIdentifier(node, node.name, "WeakSet"))) {
35808-
potentialWeakMapSetCollisions.push(node);
35809-
}
35895+
checkCollisionsForDeclarationName(node, node.name);
3581035896
}
3581135897
}
3581235898

@@ -37365,14 +37451,7 @@ namespace ts {
3736537451
function checkClassLikeDeclaration(node: ClassLikeDeclaration) {
3736637452
checkGrammarClassLikeDeclaration(node);
3736737453
checkDecorators(node);
37368-
if (node.name) {
37369-
checkTypeNameIsReserved(node.name, Diagnostics.Class_name_cannot_be_0);
37370-
checkCollisionWithRequireExportsInGeneratedCode(node, node.name);
37371-
checkCollisionWithGlobalPromiseInGeneratedCode(node, node.name);
37372-
if (!(node.flags & NodeFlags.Ambient)) {
37373-
checkClassNameCollisionWithObject(node.name);
37374-
}
37375-
}
37454+
checkCollisionsForDeclarationName(node, node.name);
3737637455
checkTypeParameters(getEffectiveTypeParameterDeclarations(node));
3737737456
checkExportsOnMergedDeclarations(node);
3737837457
const symbol = getSymbolOfNode(node);
@@ -38099,9 +38178,7 @@ namespace ts {
3809938178
// Grammar checking
3810038179
checkGrammarDecoratorsAndModifiers(node);
3810138180

38102-
checkTypeNameIsReserved(node.name, Diagnostics.Enum_name_cannot_be_0);
38103-
checkCollisionWithRequireExportsInGeneratedCode(node, node.name);
38104-
checkCollisionWithGlobalPromiseInGeneratedCode(node, node.name);
38181+
checkCollisionsForDeclarationName(node, node.name);
3810538182
checkExportsOnMergedDeclarations(node);
3810638183
node.members.forEach(checkEnumMember);
3810738184

@@ -38210,8 +38287,7 @@ namespace ts {
3821038287
}
3821138288

3821238289
if (isIdentifier(node.name)) {
38213-
checkCollisionWithRequireExportsInGeneratedCode(node, node.name);
38214-
checkCollisionWithGlobalPromiseInGeneratedCode(node, node.name);
38290+
checkCollisionsForDeclarationName(node, node.name);
3821538291
}
3821638292

3821738293
checkExportsOnMergedDeclarations(node);
@@ -38428,8 +38504,7 @@ namespace ts {
3842838504
}
3842938505

3843038506
function checkImportBinding(node: ImportEqualsDeclaration | ImportClause | NamespaceImport | ImportSpecifier) {
38431-
checkCollisionWithRequireExportsInGeneratedCode(node, node.name!);
38432-
checkCollisionWithGlobalPromiseInGeneratedCode(node, node.name!);
38507+
checkCollisionsForDeclarationName(node, node.name);
3843338508
checkAliasSymbol(node);
3843438509
if (node.kind === SyntaxKind.ImportSpecifier &&
3843538510
idText(node.propertyName || node.name) === "default" &&
@@ -39147,6 +39222,7 @@ namespace ts {
3914739222
clear(potentialThisCollisions);
3914839223
clear(potentialNewTargetCollisions);
3914939224
clear(potentialWeakMapSetCollisions);
39225+
clear(potentialReflectCollisions);
3915039226

3915139227
forEach(node.statements, checkSourceElement);
3915239228
checkSourceElement(node.endOfFileToken);
@@ -39191,6 +39267,11 @@ namespace ts {
3919139267
clear(potentialWeakMapSetCollisions);
3919239268
}
3919339269

39270+
if (potentialReflectCollisions.length) {
39271+
forEach(potentialReflectCollisions, checkReflectCollision);
39272+
clear(potentialReflectCollisions);
39273+
}
39274+
3919439275
links.flags |= NodeCheckFlags.TypeChecked;
3919539276
}
3919639277
}
@@ -40303,7 +40384,9 @@ namespace ts {
4030340384
}
4030440385

4030540386
function getNodeCheckFlags(node: Node): NodeCheckFlags {
40306-
return getNodeLinks(node).flags || 0;
40387+
const nodeId = node.id || 0;
40388+
if (nodeId < 0 || nodeId >= nodeLinks.length) return 0;
40389+
return nodeLinks[nodeId]?.flags || 0;
4030740390
}
4030840391

4030940392
function getEnumMemberValue(node: EnumMember): string | number | undefined {

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3352,6 +3352,10 @@
33523352
"category": "Error",
33533353
"code": 2817
33543354
},
3355+
"Duplicate identifier '{0}'. Compiler reserves name '{1}' when emitting 'super' references in static initializers.": {
3356+
"category": "Error",
3357+
"code": 2818
3358+
},
33553359

33563360
"Import declaration '{0}' is using private name '{1}'.": {
33573361
"category": "Error",

0 commit comments

Comments
 (0)