Skip to content

Commit 3f30125

Browse files
authored
Merge pull request microsoft#11128 from Microsoft/fix11038
Change this capturing algorithm for converted loops
2 parents 76e71ab + e345f47 commit 3f30125

File tree

5 files changed

+108
-72
lines changed

5 files changed

+108
-72
lines changed

src/compiler/transformers/es6.ts

Lines changed: 48 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -166,10 +166,9 @@ namespace ts {
166166
let enclosingVariableStatement: VariableStatement;
167167
let enclosingBlockScopeContainer: Node;
168168
let enclosingBlockScopeContainerParent: Node;
169-
let containingNonArrowFunction: FunctionLikeDeclaration | ClassElement;
170-
171-
/** Tracks the container that determines whether `super.x` is a static. */
172-
let superScopeContainer: FunctionLikeDeclaration | ClassElement;
169+
let enclosingFunction: FunctionLikeDeclaration;
170+
let enclosingNonArrowFunction: FunctionLikeDeclaration;
171+
let enclosingNonAsyncFunctionBody: FunctionLikeDeclaration | ClassElement;
173172

174173
/**
175174
* Used to track if we are emitting body of the converted loop
@@ -183,11 +182,6 @@ namespace ts {
183182
*/
184183
let enabledSubstitutions: ES6SubstitutionFlags;
185184

186-
/**
187-
* This is used to determine whether we need to emit `_this` instead of `this`.
188-
*/
189-
let useCapturedThis: boolean;
190-
191185
return transformSourceFile;
192186

193187
function transformSourceFile(node: SourceFile) {
@@ -207,14 +201,14 @@ namespace ts {
207201
}
208202

209203
function saveStateAndInvoke<T>(node: Node, f: (node: Node) => T): T {
210-
const savedContainingNonArrowFunction = containingNonArrowFunction;
211-
const savedSuperScopeContainer = superScopeContainer;
212-
const savedCurrentParent = currentParent;
213-
const savedCurrentNode = currentNode;
214-
const savedEnclosingVariableStatement = enclosingVariableStatement;
204+
const savedEnclosingFunction = enclosingFunction;
205+
const savedEnclosingNonArrowFunction = enclosingNonArrowFunction;
206+
const savedEnclosingNonAsyncFunctionBody = enclosingNonAsyncFunctionBody;
215207
const savedEnclosingBlockScopeContainer = enclosingBlockScopeContainer;
216208
const savedEnclosingBlockScopeContainerParent = enclosingBlockScopeContainerParent;
217-
209+
const savedEnclosingVariableStatement = enclosingVariableStatement;
210+
const savedCurrentParent = currentParent;
211+
const savedCurrentNode = currentNode;
218212
const savedConvertedLoopState = convertedLoopState;
219213
if (nodeStartsNewLexicalEnvironment(node)) {
220214
// don't treat content of nodes that start new lexical environment as part of converted loop copy
@@ -225,13 +219,14 @@ namespace ts {
225219
const visited = f(node);
226220

227221
convertedLoopState = savedConvertedLoopState;
228-
containingNonArrowFunction = savedContainingNonArrowFunction;
229-
superScopeContainer = savedSuperScopeContainer;
230-
currentParent = savedCurrentParent;
231-
currentNode = savedCurrentNode;
232-
enclosingVariableStatement = savedEnclosingVariableStatement;
222+
enclosingFunction = savedEnclosingFunction;
223+
enclosingNonArrowFunction = savedEnclosingNonArrowFunction;
224+
enclosingNonAsyncFunctionBody = savedEnclosingNonAsyncFunctionBody;
233225
enclosingBlockScopeContainer = savedEnclosingBlockScopeContainer;
234226
enclosingBlockScopeContainerParent = savedEnclosingBlockScopeContainerParent;
227+
enclosingVariableStatement = savedEnclosingVariableStatement;
228+
currentParent = savedCurrentParent;
229+
currentNode = savedCurrentNode;
235230
return visited;
236231
}
237232

@@ -254,22 +249,13 @@ namespace ts {
254249
}
255250

256251
function visitorForConvertedLoopWorker(node: Node): VisitResult<Node> {
257-
const savedUseCapturedThis = useCapturedThis;
258-
259-
if (nodeStartsNewLexicalEnvironment(node)) {
260-
useCapturedThis = false;
261-
}
262-
263252
let result: VisitResult<Node>;
264-
265253
if (shouldCheckNode(node)) {
266254
result = visitJavaScript(node);
267255
}
268256
else {
269257
result = visitNodesInConvertedLoop(node);
270258
}
271-
272-
useCapturedThis = savedUseCapturedThis;
273259
return result;
274260
}
275261

@@ -412,36 +398,28 @@ namespace ts {
412398
}
413399

414400
function onBeforeVisitNode(node: Node) {
415-
const currentGrandparent = currentParent;
416-
currentParent = currentNode;
417-
currentNode = node;
418-
419-
if (currentParent) {
420-
if (isBlockScope(currentParent, currentGrandparent)) {
421-
enclosingBlockScopeContainer = currentParent;
422-
enclosingBlockScopeContainerParent = currentGrandparent;
401+
if (currentNode) {
402+
if (isBlockScope(currentNode, currentParent)) {
403+
enclosingBlockScopeContainer = currentNode;
404+
enclosingBlockScopeContainerParent = currentParent;
423405
}
424406

425-
switch (currentParent.kind) {
426-
case SyntaxKind.FunctionExpression:
427-
case SyntaxKind.Constructor:
428-
case SyntaxKind.MethodDeclaration:
429-
case SyntaxKind.GetAccessor:
430-
case SyntaxKind.SetAccessor:
431-
case SyntaxKind.FunctionDeclaration:
432-
containingNonArrowFunction = <FunctionLikeDeclaration>currentParent;
433-
if (!(containingNonArrowFunction.emitFlags & NodeEmitFlags.AsyncFunctionBody)) {
434-
superScopeContainer = containingNonArrowFunction;
407+
if (isFunctionLike(currentNode)) {
408+
enclosingFunction = currentNode;
409+
if (currentNode.kind !== SyntaxKind.ArrowFunction) {
410+
enclosingNonArrowFunction = currentNode;
411+
if (!(currentNode.emitFlags & NodeEmitFlags.AsyncFunctionBody)) {
412+
enclosingNonAsyncFunctionBody = currentNode;
435413
}
436-
break;
414+
}
437415
}
438416

439417
// keep track of the enclosing variable statement when in the context of
440418
// variable statements, variable declarations, binding elements, and binding
441419
// patterns.
442-
switch (currentParent.kind) {
420+
switch (currentNode.kind) {
443421
case SyntaxKind.VariableStatement:
444-
enclosingVariableStatement = <VariableStatement>currentParent;
422+
enclosingVariableStatement = <VariableStatement>currentNode;
445423
break;
446424

447425
case SyntaxKind.VariableDeclarationList:
@@ -455,6 +433,9 @@ namespace ts {
455433
enclosingVariableStatement = undefined;
456434
}
457435
}
436+
437+
currentParent = currentNode;
438+
currentNode = node;
458439
}
459440

460441
function visitSwitchStatement(node: SwitchStatement): SwitchStatement {
@@ -490,9 +471,8 @@ namespace ts {
490471

491472
function visitThisKeyword(node: Node): Node {
492473
Debug.assert(convertedLoopState !== undefined);
493-
494-
if (useCapturedThis) {
495-
// if useCapturedThis is true then 'this' keyword is contained inside an arrow function.
474+
if (enclosingFunction && enclosingFunction.kind === SyntaxKind.ArrowFunction) {
475+
// if the enclosing function is an ArrowFunction is then we use the captured 'this' keyword.
496476
convertedLoopState.containsLexicalThis = true;
497477
return node;
498478
}
@@ -1328,12 +1308,7 @@ namespace ts {
13281308
enableSubstitutionsForCapturedThis();
13291309
}
13301310

1331-
const savedUseCapturedThis = useCapturedThis;
1332-
useCapturedThis = true;
1333-
13341311
const func = transformFunctionLikeToExpression(node, /*location*/ node, /*name*/ undefined);
1335-
1336-
useCapturedThis = savedUseCapturedThis;
13371312
setNodeEmitFlags(func, NodeEmitFlags.CapturesThis);
13381313
return func;
13391314
}
@@ -1376,9 +1351,9 @@ namespace ts {
13761351
* @param name The name of the new FunctionExpression.
13771352
*/
13781353
function transformFunctionLikeToExpression(node: FunctionLikeDeclaration, location: TextRange, name: Identifier): FunctionExpression {
1379-
const savedContainingNonArrowFunction = containingNonArrowFunction;
1354+
const savedContainingNonArrowFunction = enclosingNonArrowFunction;
13801355
if (node.kind !== SyntaxKind.ArrowFunction) {
1381-
containingNonArrowFunction = node;
1356+
enclosingNonArrowFunction = node;
13821357
}
13831358

13841359
const expression = setOriginalNode(
@@ -1394,7 +1369,7 @@ namespace ts {
13941369
/*original*/ node
13951370
);
13961371

1397-
containingNonArrowFunction = savedContainingNonArrowFunction;
1372+
enclosingNonArrowFunction = savedContainingNonArrowFunction;
13981373
return expression;
13991374
}
14001375

@@ -2091,8 +2066,8 @@ namespace ts {
20912066
}
20922067

20932068
const isAsyncBlockContainingAwait =
2094-
containingNonArrowFunction
2095-
&& (containingNonArrowFunction.emitFlags & NodeEmitFlags.AsyncFunctionBody) !== 0
2069+
enclosingNonArrowFunction
2070+
&& (enclosingNonArrowFunction.emitFlags & NodeEmitFlags.AsyncFunctionBody) !== 0
20962071
&& (node.statement.transformFlags & TransformFlags.ContainsYield) !== 0;
20972072

20982073
let loopBodyFlags: NodeEmitFlags = 0;
@@ -2854,9 +2829,9 @@ namespace ts {
28542829
* Visits the `super` keyword
28552830
*/
28562831
function visitSuperKeyword(node: PrimaryExpression): LeftHandSideExpression {
2857-
return superScopeContainer
2858-
&& isClassElement(superScopeContainer)
2859-
&& !hasModifier(superScopeContainer, ModifierFlags.Static)
2832+
return enclosingNonAsyncFunctionBody
2833+
&& isClassElement(enclosingNonAsyncFunctionBody)
2834+
&& !hasModifier(enclosingNonAsyncFunctionBody, ModifierFlags.Static)
28602835
&& currentParent.kind !== SyntaxKind.CallExpression
28612836
? createPropertyAccess(createIdentifier("_super"), "prototype")
28622837
: createIdentifier("_super");
@@ -2881,17 +2856,16 @@ namespace ts {
28812856
* @param node The node to be printed.
28822857
*/
28832858
function onEmitNode(node: Node, emit: (node: Node) => void) {
2884-
const savedUseCapturedThis = useCapturedThis;
2859+
const savedEnclosingFunction = enclosingFunction;
28852860

28862861
if (enabledSubstitutions & ES6SubstitutionFlags.CapturedThis && isFunctionLike(node)) {
2887-
// If we are tracking a captured `this`, push a bit that indicates whether the
2888-
// containing function is an arrow function.
2889-
useCapturedThis = (getNodeEmitFlags(node) & NodeEmitFlags.CapturesThis) !== 0;
2862+
// If we are tracking a captured `this`, keep track of the enclosing function.
2863+
enclosingFunction = node;
28902864
}
28912865

28922866
previousOnEmitNode(node, emit);
28932867

2894-
useCapturedThis = savedUseCapturedThis;
2868+
enclosingFunction = savedEnclosingFunction;
28952869
}
28962870

28972871
/**
@@ -3019,7 +2993,9 @@ namespace ts {
30192993
* @param node The ThisKeyword node.
30202994
*/
30212995
function substituteThisKeyword(node: PrimaryExpression): PrimaryExpression {
3022-
if (enabledSubstitutions & ES6SubstitutionFlags.CapturedThis && useCapturedThis) {
2996+
if (enabledSubstitutions & ES6SubstitutionFlags.CapturedThis
2997+
&& enclosingFunction
2998+
&& enclosingFunction.emitFlags & NodeEmitFlags.CapturesThis) {
30232999
return createIdentifier("_this", /*location*/ node);
30243000
}
30253001

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//// [blockScopedBindingCaptureThisInFunction.ts]
2+
// https://github.com/Microsoft/TypeScript/issues/11038
3+
() => function () {
4+
for (let someKey in {}) {
5+
this.helloWorld();
6+
() => someKey;
7+
}
8+
};
9+
10+
//// [blockScopedBindingCaptureThisInFunction.js]
11+
// https://github.com/Microsoft/TypeScript/issues/11038
12+
(function () { return function () {
13+
var _loop_1 = function (someKey) {
14+
this_1.helloWorld();
15+
(function () { return someKey; });
16+
};
17+
var this_1 = this;
18+
for (var someKey in {}) {
19+
_loop_1(someKey);
20+
}
21+
}; });
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
=== tests/cases/compiler/blockScopedBindingCaptureThisInFunction.ts ===
2+
// https://github.com/Microsoft/TypeScript/issues/11038
3+
() => function () {
4+
for (let someKey in {}) {
5+
>someKey : Symbol(someKey, Decl(blockScopedBindingCaptureThisInFunction.ts, 2, 12))
6+
7+
this.helloWorld();
8+
() => someKey;
9+
>someKey : Symbol(someKey, Decl(blockScopedBindingCaptureThisInFunction.ts, 2, 12))
10+
}
11+
};
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
=== tests/cases/compiler/blockScopedBindingCaptureThisInFunction.ts ===
2+
// https://github.com/Microsoft/TypeScript/issues/11038
3+
() => function () {
4+
>() => function () { for (let someKey in {}) { this.helloWorld(); () => someKey; }} : () => () => void
5+
>function () { for (let someKey in {}) { this.helloWorld(); () => someKey; }} : () => void
6+
7+
for (let someKey in {}) {
8+
>someKey : string
9+
>{} : {}
10+
11+
this.helloWorld();
12+
>this.helloWorld() : any
13+
>this.helloWorld : any
14+
>this : any
15+
>helloWorld : any
16+
17+
() => someKey;
18+
>() => someKey : () => string
19+
>someKey : string
20+
}
21+
};
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// https://github.com/Microsoft/TypeScript/issues/11038
2+
() => function () {
3+
for (let someKey in {}) {
4+
this.helloWorld();
5+
() => someKey;
6+
}
7+
};

0 commit comments

Comments
 (0)