Skip to content

Commit 9f5db6a

Browse files
joeywattsJoey Watts
authored and
Joey Watts
committed
Fix property name bindings for class expr in loops
1 parent 73b268e commit 9f5db6a

24 files changed

+428
-37
lines changed

scripts/request-pr-review.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ main().catch(console.error);
4242

4343
async function main() {
4444
const gh = new Octokit({ auth: options.token });
45-
const response = await gh.pulls.requestReviewers({
45+
const response = await (gh.pulls as any).requestReviewers({
4646
owner: options.owner,
4747
repo: options.repo,
4848
pull_number,

src/compiler/checker.ts

+31-12
Original file line numberDiff line numberDiff line change
@@ -22262,6 +22262,10 @@ namespace ts {
2226222262
return findAncestor(node, n => n === container ? "quit" : n === container.initializer || n === container.condition || n === container.incrementor || n === container.statement);
2226322263
}
2226422264

22265+
function getEnclosingIterationStatement(node: Node): Node | undefined {
22266+
return findAncestor(node, n => (!n || nodeStartsNewLexicalEnvironment(n)) ? "quit" : isIterationStatement(n, /*lookInLabeledStatements*/ false));
22267+
}
22268+
2226522269
function checkNestedBlockScopedBinding(node: Identifier, symbol: Symbol): void {
2226622270
if (languageVersion >= ScriptTarget.ES2015 ||
2226722271
(symbol.flags & (SymbolFlags.BlockScopedVariable | SymbolFlags.Class)) === 0 ||
@@ -22277,18 +22281,9 @@ namespace ts {
2227722281

2227822282
const container = getEnclosingBlockScopeContainer(symbol.valueDeclaration);
2227922283
const usedInFunction = isInsideFunction(node.parent, container);
22280-
let current = container;
22281-
22282-
let containedInIterationStatement = false;
22283-
while (current && !nodeStartsNewLexicalEnvironment(current)) {
22284-
if (isIterationStatement(current, /*lookInLabeledStatements*/ false)) {
22285-
containedInIterationStatement = true;
22286-
break;
22287-
}
22288-
current = current.parent;
22289-
}
2229022284

22291-
if (containedInIterationStatement) {
22285+
const enclosingIterationStatement = getEnclosingIterationStatement(container);
22286+
if (enclosingIterationStatement) {
2229222287
if (usedInFunction) {
2229322288
// mark iteration statement as containing block-scoped binding captured in some function
2229422289
let capturesBlockScopeBindingInLoopBody = true;
@@ -22310,7 +22305,7 @@ namespace ts {
2231022305
}
2231122306
}
2231222307
if (capturesBlockScopeBindingInLoopBody) {
22313-
getNodeLinks(current).flags |= NodeCheckFlags.LoopWithCapturedBlockScopedBinding;
22308+
getNodeLinks(enclosingIterationStatement).flags |= NodeCheckFlags.LoopWithCapturedBlockScopedBinding;
2231422309
}
2231522310
}
2231622311

@@ -23882,6 +23877,20 @@ namespace ts {
2388223877
const links = getNodeLinks(node.expression);
2388323878
if (!links.resolvedType) {
2388423879
links.resolvedType = checkExpression(node.expression);
23880+
// The computed property name of a non-static class field within a loop must be stored in a block-scoped binding.
23881+
// (It needs to be bound at class evaluation time.)
23882+
if (isPropertyDeclaration(node.parent) && !hasStaticModifier(node.parent) && isClassExpression(node.parent.parent)) {
23883+
const container = getEnclosingBlockScopeContainer(node.parent.parent);
23884+
const enclosingIterationStatement = getEnclosingIterationStatement(container);
23885+
if (enclosingIterationStatement) {
23886+
// The computed field name will use a block scoped binding which can be unique for each iteration of the loop.
23887+
getNodeLinks(enclosingIterationStatement).flags |= NodeCheckFlags.LoopWithCapturedBlockScopedBinding;
23888+
// The generated variable which stores the computed field name must be block-scoped.
23889+
getNodeLinks(node).flags |= NodeCheckFlags.BlockScopedBindingInLoop;
23890+
// The generated variable which stores the class must be block-scoped.
23891+
getNodeLinks(node.parent.parent).flags |= NodeCheckFlags.BlockScopedBindingInLoop;
23892+
}
23893+
}
2388523894
// This will allow types number, string, symbol or any. It will also allow enums, the unknown
2388623895
// type, and any union of these types (like string | number).
2388723896
if (links.resolvedType.flags & TypeFlags.Nullable ||
@@ -30714,6 +30723,16 @@ namespace ts {
3071430723
for (let lexicalScope = getEnclosingBlockScopeContainer(node); !!lexicalScope; lexicalScope = getEnclosingBlockScopeContainer(lexicalScope)) {
3071530724
getNodeLinks(lexicalScope).flags |= NodeCheckFlags.ContainsClassWithPrivateIdentifiers;
3071630725
}
30726+
30727+
// If this is a private field in a class expression inside the body of a loop,
30728+
// then we must use a block-scoped binding to store the WeakMap.
30729+
if (isClassExpression(node.parent)) {
30730+
const enclosingIterationStatement = getEnclosingIterationStatement(node.parent);
30731+
if (enclosingIterationStatement) {
30732+
getNodeLinks(node.name).flags |= NodeCheckFlags.BlockScopedBindingInLoop;
30733+
getNodeLinks(enclosingIterationStatement).flags |= NodeCheckFlags.LoopWithCapturedBlockScopedBinding;
30734+
}
30735+
}
3071730736
}
3071830737
}
3071930738

src/compiler/transformer.ts

+49
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,9 @@ namespace ts {
156156
let lexicalEnvironmentFlagsStack: LexicalEnvironmentFlags[] = [];
157157
let lexicalEnvironmentStackOffset = 0;
158158
let lexicalEnvironmentSuspended = false;
159+
let blockScopedVariableDeclarationsStack: Identifier[][] = [];
160+
let blockScopeStackOffset = 0;
161+
let blockScopedVariableDeclarations: Identifier[];
159162
let emitHelpers: EmitHelper[] | undefined;
160163
let onSubstituteNode: TransformationContext["onSubstituteNode"] = noEmitSubstitution;
161164
let onEmitNode: TransformationContext["onEmitNode"] = noEmitNotification;
@@ -179,6 +182,9 @@ namespace ts {
179182
hoistVariableDeclaration,
180183
hoistFunctionDeclaration,
181184
addInitializationStatement,
185+
startBlockScope,
186+
endBlockScope,
187+
addBlockScopedVariable,
182188
requestEmitHelper,
183189
readEmitHelpers,
184190
enableSubstitution,
@@ -465,6 +471,46 @@ namespace ts {
465471
return lexicalEnvironmentFlags;
466472
}
467473

474+
/**
475+
* Starts a block scope. Any existing block hoisted variables are pushed onto the stack and the related storage variables are reset.
476+
*/
477+
function startBlockScope() {
478+
Debug.assert(state > TransformationState.Uninitialized, "Cannot start a block scope during initialization.");
479+
Debug.assert(state < TransformationState.Completed, "Cannot start a block scope after transformation has completed.");
480+
blockScopedVariableDeclarationsStack[blockScopeStackOffset] = blockScopedVariableDeclarations;
481+
blockScopeStackOffset++;
482+
blockScopedVariableDeclarations = undefined!;
483+
}
484+
485+
/**
486+
* Ends a block scope. The previous set of block hoisted variables are restored. Any hoisted declarations are returned.
487+
*/
488+
function endBlockScope() {
489+
Debug.assert(state > TransformationState.Uninitialized, "Cannot end a block scope during initialization.");
490+
Debug.assert(state < TransformationState.Completed, "Cannot end a block scope after transformation has completed.");
491+
const statements: Statement[] | undefined = some(blockScopedVariableDeclarations) ?
492+
[
493+
factory.createVariableStatement(
494+
/*modifiers*/ undefined,
495+
factory.createVariableDeclarationList(
496+
blockScopedVariableDeclarations.map(identifier => factory.createVariableDeclaration(identifier)),
497+
NodeFlags.Let
498+
)
499+
)
500+
] : undefined;
501+
blockScopeStackOffset--;
502+
blockScopedVariableDeclarations = blockScopedVariableDeclarationsStack[blockScopeStackOffset];
503+
if (blockScopeStackOffset === 0) {
504+
blockScopedVariableDeclarationsStack = [];
505+
}
506+
return statements;
507+
}
508+
509+
function addBlockScopedVariable(name: Identifier): void {
510+
Debug.assert(blockScopeStackOffset > 0, "Cannot add a block scoped variable outside of an iteration body.");
511+
(blockScopedVariableDeclarations || (blockScopedVariableDeclarations = [])).push(name);
512+
}
513+
468514
function requestEmitHelper(helper: EmitHelper): void {
469515
Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the transformation context during initialization.");
470516
Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed.");
@@ -531,5 +577,8 @@ namespace ts {
531577
startLexicalEnvironment: noop,
532578
suspendLexicalEnvironment: noop,
533579
addDiagnostic: noop,
580+
startBlockScope: noop,
581+
endBlockScope: returnUndefined,
582+
addBlockScopedVariable: noop
534583
};
535584
}

src/compiler/transformers/classFields.ts

+19-7
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ namespace ts {
3636
factory,
3737
hoistVariableDeclaration,
3838
endLexicalEnvironment,
39-
resumeLexicalEnvironment
39+
resumeLexicalEnvironment,
40+
addBlockScopedVariable
4041
} = context;
4142
const resolver = context.getEmitResolver();
4243
const compilerOptions = context.getCompilerOptions();
@@ -312,7 +313,7 @@ namespace ts {
312313
visitNode(node.initializer, visitor, isForInitializer),
313314
visitNode(node.condition, visitor, isExpression),
314315
visitPostfixUnaryExpression(node.incrementor, /*valueIsDiscarded*/ true),
315-
visitNode(node.statement, visitor, isStatement)
316+
visitIterationBody(node.statement, visitor, context)
316317
);
317318
}
318319
return visitEachChild(node, visitor, context);
@@ -533,8 +534,10 @@ namespace ts {
533534
}
534535
else {
535536
const expressions: Expression[] = [];
536-
const isClassWithConstructorReference = resolver.getNodeCheckFlags(node) & NodeCheckFlags.ClassWithConstructorReference;
537-
const temp = factory.createTempVariable(hoistVariableDeclaration, !!isClassWithConstructorReference);
537+
const classCheckFlags = resolver.getNodeCheckFlags(node);
538+
const isClassWithConstructorReference = classCheckFlags & NodeCheckFlags.ClassWithConstructorReference;
539+
const requiresBlockScopedVar = classCheckFlags & NodeCheckFlags.BlockScopedBindingInLoop;
540+
const temp = factory.createTempVariable(requiresBlockScopedVar ? addBlockScopedVariable : hoistVariableDeclaration, !!isClassWithConstructorReference);
538541
if (isClassWithConstructorReference) {
539542
// record an alias as the class name is not in scope for statics.
540543
enableSubstitutionForClassAliases();
@@ -859,7 +862,6 @@ namespace ts {
859862
return undefined;
860863
}
861864

862-
863865
/**
864866
* If the name is a computed property, this function transforms it, then either returns an expression which caches the
865867
* value of the result or the expression itself if the value is either unused or safe to inline into multiple locations
@@ -873,7 +875,12 @@ namespace ts {
873875
const alreadyTransformed = isAssignmentExpression(innerExpression) && isGeneratedIdentifier(innerExpression.left);
874876
if (!alreadyTransformed && !inlinable && shouldHoist) {
875877
const generatedName = factory.getGeneratedNameForNode(name);
876-
hoistVariableDeclaration(generatedName);
878+
if (resolver.getNodeCheckFlags(name) & NodeCheckFlags.BlockScopedBindingInLoop) {
879+
addBlockScopedVariable(generatedName);
880+
}
881+
else {
882+
hoistVariableDeclaration(generatedName);
883+
}
877884
return factory.createAssignment(generatedName, expression);
878885
}
879886
return (inlinable || isIdentifier(innerExpression)) ? undefined : expression;
@@ -900,7 +907,12 @@ namespace ts {
900907
function addPrivateIdentifierToEnvironment(name: PrivateIdentifier) {
901908
const text = getTextOfPropertyName(name) as string;
902909
const weakMapName = factory.createUniqueName("_" + text.substring(1), GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.ReservedInNestedScopes);
903-
hoistVariableDeclaration(weakMapName);
910+
if (resolver.getNodeCheckFlags(name) & NodeCheckFlags.BlockScopedBindingInLoop) {
911+
addBlockScopedVariable(weakMapName);
912+
}
913+
else {
914+
hoistVariableDeclaration(weakMapName);
915+
}
904916
getPrivateIdentifierEnvironment().set(name.escapedText, { placement: PrivateIdentifierPlacement.InstanceField, weakMapName });
905917
getPendingExpressions().push(
906918
factory.createAssignment(

src/compiler/transformers/es2017.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ namespace ts {
225225
? visitVariableDeclarationListWithCollidingNames(node.initializer, /*hasReceiver*/ true)!
226226
: visitNode(node.initializer, visitor, isForInitializer),
227227
visitNode(node.expression, visitor, isExpression),
228-
visitNode(node.statement, asyncBodyVisitor, isStatement, factory.liftToBlock)
228+
visitIterationBody(node.statement, asyncBodyVisitor, context)
229229
);
230230
}
231231

@@ -237,7 +237,7 @@ namespace ts {
237237
? visitVariableDeclarationListWithCollidingNames(node.initializer, /*hasReceiver*/ true)!
238238
: visitNode(node.initializer, visitor, isForInitializer),
239239
visitNode(node.expression, visitor, isExpression),
240-
visitNode(node.statement, asyncBodyVisitor, isStatement, factory.liftToBlock)
240+
visitIterationBody(node.statement, asyncBodyVisitor, context)
241241
);
242242
}
243243

@@ -250,7 +250,7 @@ namespace ts {
250250
: visitNode(node.initializer, visitor, isForInitializer),
251251
visitNode(node.condition, visitor, isExpression),
252252
visitNode(node.incrementor, visitor, isExpression),
253-
visitNode(node.statement, asyncBodyVisitor, isStatement, factory.liftToBlock)
253+
visitIterationBody(node.statement, asyncBodyVisitor, context)
254254
);
255255
}
256256

src/compiler/transformers/es2018.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -542,7 +542,7 @@ namespace ts {
542542
visitNode(node.initializer, visitorNoDestructuringValue, isForInitializer),
543543
visitNode(node.condition, visitor, isExpression),
544544
visitNode(node.incrementor, visitor, isExpression),
545-
visitNode(node.statement, visitor, isStatement)
545+
visitIterationBody(node.statement, visitor, context)
546546
);
547547
}
548548

@@ -615,7 +615,7 @@ namespace ts {
615615
let bodyLocation: TextRange | undefined;
616616
let statementsLocation: TextRange | undefined;
617617
const statements: Statement[] = [visitNode(binding, visitor, isStatement)];
618-
const statement = visitNode(node.statement, visitor, isStatement);
618+
const statement = visitIterationBody(node.statement, visitor, context);
619619
if (isBlock(statement)) {
620620
addRange(statements, statement.statements);
621621
bodyLocation = statement;

src/compiler/transformers/generators.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1477,7 +1477,7 @@ namespace ts {
14771477
: undefined,
14781478
visitNode(node.condition, visitor, isExpression),
14791479
visitNode(node.incrementor, visitor, isExpression),
1480-
visitNode(node.statement, visitor, isStatement, factory.liftToBlock)
1480+
visitIterationBody(node.statement, visitor, context)
14811481
);
14821482
}
14831483
else {

src/compiler/transformers/module/system.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -1231,7 +1231,7 @@ namespace ts {
12311231
node.initializer && visitForInitializer(node.initializer),
12321232
visitNode(node.condition, destructuringAndImportCallVisitor, isExpression),
12331233
visitNode(node.incrementor, destructuringAndImportCallVisitor, isExpression),
1234-
visitNode(node.statement, nestedElementVisitor, isStatement)
1234+
visitIterationBody(node.statement, nestedElementVisitor, context)
12351235
);
12361236

12371237
enclosingBlockScopedContainer = savedEnclosingBlockScopedContainer;
@@ -1251,7 +1251,7 @@ namespace ts {
12511251
node,
12521252
visitForInitializer(node.initializer),
12531253
visitNode(node.expression, destructuringAndImportCallVisitor, isExpression),
1254-
visitNode(node.statement, nestedElementVisitor, isStatement, factory.liftToBlock)
1254+
visitIterationBody(node.statement, nestedElementVisitor, context)
12551255
);
12561256

12571257
enclosingBlockScopedContainer = savedEnclosingBlockScopedContainer;
@@ -1272,7 +1272,7 @@ namespace ts {
12721272
node.awaitModifier,
12731273
visitForInitializer(node.initializer),
12741274
visitNode(node.expression, destructuringAndImportCallVisitor, isExpression),
1275-
visitNode(node.statement, nestedElementVisitor, isStatement, factory.liftToBlock)
1275+
visitIterationBody(node.statement, nestedElementVisitor, context)
12761276
);
12771277

12781278
enclosingBlockScopedContainer = savedEnclosingBlockScopedContainer;
@@ -1320,7 +1320,7 @@ namespace ts {
13201320
function visitDoStatement(node: DoStatement): VisitResult<Statement> {
13211321
return factory.updateDoStatement(
13221322
node,
1323-
visitNode(node.statement, nestedElementVisitor, isStatement, factory.liftToBlock),
1323+
visitIterationBody(node.statement, nestedElementVisitor, context),
13241324
visitNode(node.expression, destructuringAndImportCallVisitor, isExpression)
13251325
);
13261326
}
@@ -1334,7 +1334,7 @@ namespace ts {
13341334
return factory.updateWhileStatement(
13351335
node,
13361336
visitNode(node.expression, destructuringAndImportCallVisitor, isExpression),
1337-
visitNode(node.statement, nestedElementVisitor, isStatement, factory.liftToBlock)
1337+
visitIterationBody(node.statement, nestedElementVisitor, context)
13381338
);
13391339
}
13401340

src/compiler/types.ts

+6
Original file line numberDiff line numberDiff line change
@@ -7311,6 +7311,12 @@ namespace ts {
73117311
/** Hoists a variable declaration to the containing scope. */
73127312
hoistVariableDeclaration(node: Identifier): void;
73137313

7314+
/*@internal*/ startBlockScope(): void;
7315+
7316+
/*@internal*/ endBlockScope(): Statement[] | undefined;
7317+
7318+
/*@internal*/ addBlockScopedVariable(node: Identifier): void;
7319+
73147320
/** Adds an initialization statement to the top of the lexical environment. */
73157321
/* @internal */
73167322
addInitializationStatement(node: Statement): void;

0 commit comments

Comments
 (0)