Skip to content

Commit b2d1f53

Browse files
sandersnjoeywatts
andauthored
Bloomberg computed property name fix (#43197)
* Fix property name bindings for class expr in loops * Fix block-scope capturing with prop initializers Co-authored-by: Joey Watts <[email protected]>
1 parent 998ecd9 commit b2d1f53

31 files changed

+587
-41
lines changed

Diff for: src/compiler/checker.ts

+38-17
Original file line numberDiff line numberDiff line change
@@ -23775,14 +23775,20 @@ namespace ts {
2377523775
return assignmentKind ? getBaseTypeOfLiteralType(flowType) : flowType;
2377623776
}
2377723777

23778-
function isInsideFunction(node: Node, threshold: Node): boolean {
23779-
return !!findAncestor(node, n => n === threshold ? "quit" : isFunctionLike(n));
23778+
function isInsideFunctionOrInstancePropertyInitializer(node: Node, threshold: Node): boolean {
23779+
return !!findAncestor(node, n => n === threshold ? "quit" : isFunctionLike(n) || (
23780+
n.parent && isPropertyDeclaration(n.parent) && !hasStaticModifier(n.parent) && n.parent.initializer === n
23781+
));
2378023782
}
2378123783

2378223784
function getPartOfForStatementContainingNode(node: Node, container: ForStatement) {
2378323785
return findAncestor(node, n => n === container ? "quit" : n === container.initializer || n === container.condition || n === container.incrementor || n === container.statement);
2378423786
}
2378523787

23788+
function getEnclosingIterationStatement(node: Node): Node | undefined {
23789+
return findAncestor(node, n => (!n || nodeStartsNewLexicalEnvironment(n)) ? "quit" : isIterationStatement(n, /*lookInLabeledStatements*/ false));
23790+
}
23791+
2378623792
function checkNestedBlockScopedBinding(node: Identifier, symbol: Symbol): void {
2378723793
if (languageVersion >= ScriptTarget.ES2015 ||
2378823794
(symbol.flags & (SymbolFlags.BlockScopedVariable | SymbolFlags.Class)) === 0 ||
@@ -23798,20 +23804,11 @@ namespace ts {
2379823804
// if there is an iteration statement in between declaration and boundary (is binding/class declared inside iteration statement)
2379923805

2380023806
const container = getEnclosingBlockScopeContainer(symbol.valueDeclaration);
23801-
const usedInFunction = isInsideFunction(node.parent, container);
23802-
let current = container;
23803-
23804-
let containedInIterationStatement = false;
23805-
while (current && !nodeStartsNewLexicalEnvironment(current)) {
23806-
if (isIterationStatement(current, /*lookInLabeledStatements*/ false)) {
23807-
containedInIterationStatement = true;
23808-
break;
23809-
}
23810-
current = current.parent;
23811-
}
23807+
const isCaptured = isInsideFunctionOrInstancePropertyInitializer(node, container);
2381223808

23813-
if (containedInIterationStatement) {
23814-
if (usedInFunction) {
23809+
const enclosingIterationStatement = getEnclosingIterationStatement(container);
23810+
if (enclosingIterationStatement) {
23811+
if (isCaptured) {
2381523812
// mark iteration statement as containing block-scoped binding captured in some function
2381623813
let capturesBlockScopeBindingInLoopBody = true;
2381723814
if (isForStatement(container)) {
@@ -23832,7 +23829,7 @@ namespace ts {
2383223829
}
2383323830
}
2383423831
if (capturesBlockScopeBindingInLoopBody) {
23835-
getNodeLinks(current).flags |= NodeCheckFlags.LoopWithCapturedBlockScopedBinding;
23832+
getNodeLinks(enclosingIterationStatement).flags |= NodeCheckFlags.LoopWithCapturedBlockScopedBinding;
2383623833
}
2383723834
}
2383823835

@@ -23849,7 +23846,7 @@ namespace ts {
2384923846
getNodeLinks(symbol.valueDeclaration).flags |= NodeCheckFlags.BlockScopedBindingInLoop;
2385023847
}
2385123848

23852-
if (usedInFunction) {
23849+
if (isCaptured) {
2385323850
getNodeLinks(symbol.valueDeclaration).flags |= NodeCheckFlags.CapturedBlockScopedBinding;
2385423851
}
2385523852
}
@@ -25529,6 +25526,20 @@ namespace ts {
2552925526
const links = getNodeLinks(node.expression);
2553025527
if (!links.resolvedType) {
2553125528
links.resolvedType = checkExpression(node.expression);
25529+
// The computed property name of a non-static class field within a loop must be stored in a block-scoped binding.
25530+
// (It needs to be bound at class evaluation time.)
25531+
if (isPropertyDeclaration(node.parent) && !hasStaticModifier(node.parent) && isClassExpression(node.parent.parent)) {
25532+
const container = getEnclosingBlockScopeContainer(node.parent.parent);
25533+
const enclosingIterationStatement = getEnclosingIterationStatement(container);
25534+
if (enclosingIterationStatement) {
25535+
// The computed field name will use a block scoped binding which can be unique for each iteration of the loop.
25536+
getNodeLinks(enclosingIterationStatement).flags |= NodeCheckFlags.LoopWithCapturedBlockScopedBinding;
25537+
// The generated variable which stores the computed field name must be block-scoped.
25538+
getNodeLinks(node).flags |= NodeCheckFlags.BlockScopedBindingInLoop;
25539+
// The generated variable which stores the class must be block-scoped.
25540+
getNodeLinks(node.parent.parent).flags |= NodeCheckFlags.BlockScopedBindingInLoop;
25541+
}
25542+
}
2553225543
// This will allow types number, string, symbol or any. It will also allow enums, the unknown
2553325544
// type, and any union of these types (like string | number).
2553425545
if (links.resolvedType.flags & TypeFlags.Nullable ||
@@ -32617,6 +32628,16 @@ namespace ts {
3261732628
for (let lexicalScope = getEnclosingBlockScopeContainer(node); !!lexicalScope; lexicalScope = getEnclosingBlockScopeContainer(lexicalScope)) {
3261832629
getNodeLinks(lexicalScope).flags |= NodeCheckFlags.ContainsClassWithPrivateIdentifiers;
3261932630
}
32631+
32632+
// If this is a private field in a class expression inside the body of a loop,
32633+
// then we must use a block-scoped binding to store the WeakMap.
32634+
if (isClassExpression(node.parent)) {
32635+
const enclosingIterationStatement = getEnclosingIterationStatement(node.parent);
32636+
if (enclosingIterationStatement) {
32637+
getNodeLinks(node.name).flags |= NodeCheckFlags.BlockScopedBindingInLoop;
32638+
getNodeLinks(enclosingIterationStatement).flags |= NodeCheckFlags.LoopWithCapturedBlockScopedBinding;
32639+
}
32640+
}
3262032641
}
3262132642
}
3262232643

Diff for: src/compiler/transformer.ts

+49
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,9 @@ namespace ts {
159159
let lexicalEnvironmentFlagsStack: LexicalEnvironmentFlags[] = [];
160160
let lexicalEnvironmentStackOffset = 0;
161161
let lexicalEnvironmentSuspended = false;
162+
let blockScopedVariableDeclarationsStack: Identifier[][] = [];
163+
let blockScopeStackOffset = 0;
164+
let blockScopedVariableDeclarations: Identifier[];
162165
let emitHelpers: EmitHelper[] | undefined;
163166
let onSubstituteNode: TransformationContext["onSubstituteNode"] = noEmitSubstitution;
164167
let onEmitNode: TransformationContext["onEmitNode"] = noEmitNotification;
@@ -182,6 +185,9 @@ namespace ts {
182185
hoistVariableDeclaration,
183186
hoistFunctionDeclaration,
184187
addInitializationStatement,
188+
startBlockScope,
189+
endBlockScope,
190+
addBlockScopedVariable,
185191
requestEmitHelper,
186192
readEmitHelpers,
187193
enableSubstitution,
@@ -473,6 +479,46 @@ namespace ts {
473479
return lexicalEnvironmentFlags;
474480
}
475481

482+
/**
483+
* Starts a block scope. Any existing block hoisted variables are pushed onto the stack and the related storage variables are reset.
484+
*/
485+
function startBlockScope() {
486+
Debug.assert(state > TransformationState.Uninitialized, "Cannot start a block scope during initialization.");
487+
Debug.assert(state < TransformationState.Completed, "Cannot start a block scope after transformation has completed.");
488+
blockScopedVariableDeclarationsStack[blockScopeStackOffset] = blockScopedVariableDeclarations;
489+
blockScopeStackOffset++;
490+
blockScopedVariableDeclarations = undefined!;
491+
}
492+
493+
/**
494+
* Ends a block scope. The previous set of block hoisted variables are restored. Any hoisted declarations are returned.
495+
*/
496+
function endBlockScope() {
497+
Debug.assert(state > TransformationState.Uninitialized, "Cannot end a block scope during initialization.");
498+
Debug.assert(state < TransformationState.Completed, "Cannot end a block scope after transformation has completed.");
499+
const statements: Statement[] | undefined = some(blockScopedVariableDeclarations) ?
500+
[
501+
factory.createVariableStatement(
502+
/*modifiers*/ undefined,
503+
factory.createVariableDeclarationList(
504+
blockScopedVariableDeclarations.map(identifier => factory.createVariableDeclaration(identifier)),
505+
NodeFlags.Let
506+
)
507+
)
508+
] : undefined;
509+
blockScopeStackOffset--;
510+
blockScopedVariableDeclarations = blockScopedVariableDeclarationsStack[blockScopeStackOffset];
511+
if (blockScopeStackOffset === 0) {
512+
blockScopedVariableDeclarationsStack = [];
513+
}
514+
return statements;
515+
}
516+
517+
function addBlockScopedVariable(name: Identifier): void {
518+
Debug.assert(blockScopeStackOffset > 0, "Cannot add a block scoped variable outside of an iteration body.");
519+
(blockScopedVariableDeclarations || (blockScopedVariableDeclarations = [])).push(name);
520+
}
521+
476522
function requestEmitHelper(helper: EmitHelper): void {
477523
Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the transformation context during initialization.");
478524
Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed.");
@@ -539,5 +585,8 @@ namespace ts {
539585
startLexicalEnvironment: noop,
540586
suspendLexicalEnvironment: noop,
541587
addDiagnostic: noop,
588+
startBlockScope: noop,
589+
endBlockScope: returnUndefined,
590+
addBlockScopedVariable: noop
542591
};
543592
}

Diff for: 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();
@@ -310,7 +311,7 @@ namespace ts {
310311
visitNode(node.initializer, visitor, isForInitializer),
311312
visitNode(node.condition, visitor, isExpression),
312313
visitPostfixUnaryExpression(node.incrementor, /*valueIsDiscarded*/ true),
313-
visitNode(node.statement, visitor, isStatement)
314+
visitIterationBody(node.statement, visitor, context)
314315
);
315316
}
316317
return visitEachChild(node, visitor, context);
@@ -540,8 +541,10 @@ namespace ts {
540541
}
541542
else {
542543
const expressions: Expression[] = [];
543-
const isClassWithConstructorReference = resolver.getNodeCheckFlags(node) & NodeCheckFlags.ClassWithConstructorReference;
544-
const temp = factory.createTempVariable(hoistVariableDeclaration, !!isClassWithConstructorReference);
544+
const classCheckFlags = resolver.getNodeCheckFlags(node);
545+
const isClassWithConstructorReference = classCheckFlags & NodeCheckFlags.ClassWithConstructorReference;
546+
const requiresBlockScopedVar = classCheckFlags & NodeCheckFlags.BlockScopedBindingInLoop;
547+
const temp = factory.createTempVariable(requiresBlockScopedVar ? addBlockScopedVariable : hoistVariableDeclaration, !!isClassWithConstructorReference);
545548
if (isClassWithConstructorReference) {
546549
// record an alias as the class name is not in scope for statics.
547550
enableSubstitutionForClassAliases();
@@ -869,7 +872,6 @@ namespace ts {
869872
return undefined;
870873
}
871874

872-
873875
/**
874876
* If the name is a computed property, this function transforms it, then either returns an expression which caches the
875877
* value of the result or the expression itself if the value is either unused or safe to inline into multiple locations
@@ -883,7 +885,12 @@ namespace ts {
883885
const alreadyTransformed = isAssignmentExpression(innerExpression) && isGeneratedIdentifier(innerExpression.left);
884886
if (!alreadyTransformed && !inlinable && shouldHoist) {
885887
const generatedName = factory.getGeneratedNameForNode(name);
886-
hoistVariableDeclaration(generatedName);
888+
if (resolver.getNodeCheckFlags(name) & NodeCheckFlags.BlockScopedBindingInLoop) {
889+
addBlockScopedVariable(generatedName);
890+
}
891+
else {
892+
hoistVariableDeclaration(generatedName);
893+
}
887894
return factory.createAssignment(generatedName, expression);
888895
}
889896
return (inlinable || isIdentifier(innerExpression)) ? undefined : expression;
@@ -910,7 +917,12 @@ namespace ts {
910917
function addPrivateIdentifierToEnvironment(name: PrivateIdentifier) {
911918
const text = getTextOfPropertyName(name) as string;
912919
const weakMapName = factory.createUniqueName("_" + text.substring(1), GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.ReservedInNestedScopes);
913-
hoistVariableDeclaration(weakMapName);
920+
if (resolver.getNodeCheckFlags(name) & NodeCheckFlags.BlockScopedBindingInLoop) {
921+
addBlockScopedVariable(weakMapName);
922+
}
923+
else {
924+
hoistVariableDeclaration(weakMapName);
925+
}
914926
getPrivateIdentifierEnvironment().set(name.escapedText, { placement: PrivateIdentifierPlacement.InstanceField, weakMapName });
915927
getPendingExpressions().push(
916928
factory.createAssignment(

Diff for: 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

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -575,7 +575,7 @@ namespace ts {
575575
visitNode(node.initializer, visitorWithUnusedExpressionResult, isForInitializer),
576576
visitNode(node.condition, visitor, isExpression),
577577
visitNode(node.incrementor, visitorWithUnusedExpressionResult, isExpression),
578-
visitNode(node.statement, visitor, isStatement)
578+
visitIterationBody(node.statement, visitor, context)
579579
);
580580
}
581581

@@ -648,7 +648,7 @@ namespace ts {
648648
let bodyLocation: TextRange | undefined;
649649
let statementsLocation: TextRange | undefined;
650650
const statements: Statement[] = [visitNode(binding, visitor, isStatement)];
651-
const statement = visitNode(node.statement, visitor, isStatement);
651+
const statement = visitIterationBody(node.statement, visitor, context);
652652
if (isBlock(statement)) {
653653
addRange(statements, statement.statements);
654654
bodyLocation = statement;

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -1502,7 +1502,7 @@ namespace ts {
15021502
: undefined,
15031503
visitNode(node.condition, visitor, isExpression),
15041504
visitNode(node.incrementor, visitor, isExpression),
1505-
visitNode(node.statement, visitor, isStatement, factory.liftToBlock)
1505+
visitIterationBody(node.statement, visitor, context)
15061506
);
15071507
}
15081508
else {

Diff for: 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

0 commit comments

Comments
 (0)