diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 07c9a86f167..abcc52652c6 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15752,6 +15752,10 @@ namespace ts { return !!findAncestor(node, n => n === threshold ? "quit" : isFunctionLike(n)); } + function getPartOfForStatementContainingNode(node: Node, container: ForStatement) { + return findAncestor(node, n => n === container ? "quit" : n === container.initializer || n === container.condition || n === container.incrementor || n === container.statement); + } + function checkNestedBlockScopedBinding(node: Identifier, symbol: Symbol): void { if (languageVersion >= ScriptTarget.ES2015 || (symbol.flags & (SymbolFlags.BlockScopedVariable | SymbolFlags.Class)) === 0 || @@ -15780,7 +15784,25 @@ namespace ts { if (containedInIterationStatement) { if (usedInFunction) { // mark iteration statement as containing block-scoped binding captured in some function - getNodeLinks(current).flags |= NodeCheckFlags.LoopWithCapturedBlockScopedBinding; + let capturesBlockScopeBindingInLoopBody = true; + if (isForStatement(container) && + getAncestor(symbol.valueDeclaration, SyntaxKind.VariableDeclarationList)!.parent === container) { + const part = getPartOfForStatementContainingNode(node.parent, container); + if (part) { + const links = getNodeLinks(part); + links.flags |= NodeCheckFlags.ContainsCapturedBlockScopeBinding; + + const capturedBindings = links.capturedBlockScopeBindings || (links.capturedBlockScopeBindings = []); + pushIfUnique(capturedBindings, symbol); + + if (part === container.initializer) { + capturesBlockScopeBindingInLoopBody = false; // Initializer is outside of loop body + } + } + } + if (capturesBlockScopeBindingInLoopBody) { + getNodeLinks(current).flags |= NodeCheckFlags.LoopWithCapturedBlockScopedBinding; + } } // mark variables that are declared in loop initializer and reassigned inside the body of ForStatement. @@ -15800,6 +15822,11 @@ namespace ts { } } + function isBindingCapturedByNode(node: Node, decl: VariableDeclaration | BindingElement) { + const links = getNodeLinks(node); + return !!links && contains(links.capturedBlockScopeBindings, getSymbolOfNode(decl)); + } + function isAssignedInBodyOfForStatement(node: Identifier, container: ForStatement): boolean { // skip parenthesized nodes let current: Node = node; @@ -28686,7 +28713,12 @@ namespace ts { getAccessor }; }, - getSymbolOfExternalModuleSpecifier: moduleName => resolveExternalModuleNameWorker(moduleName, moduleName, /*moduleNotFoundError*/ undefined) + getSymbolOfExternalModuleSpecifier: moduleName => resolveExternalModuleNameWorker(moduleName, moduleName, /*moduleNotFoundError*/ undefined), + isBindingCapturedByNode: (node, decl) => { + const parseNode = getParseTreeNode(node); + const parseDecl = getParseTreeNode(decl); + return !!parseNode && !!parseDecl && (isVariableDeclaration(parseDecl) || isBindingElement(parseDecl)) && isBindingCapturedByNode(parseNode, parseDecl); + } }; function isInHeritageClause(node: PropertyAccessEntityNameExpression) { diff --git a/src/compiler/transformers/es2015.ts b/src/compiler/transformers/es2015.ts index dcb0d9ac208..2e5176c4490 100644 --- a/src/compiler/transformers/es2015.ts +++ b/src/compiler/transformers/es2015.ts @@ -46,10 +46,16 @@ namespace ts { * so nobody can observe this new value. */ interface LoopOutParameter { + flags: LoopOutParameterFlags; originalName: Identifier; outParamName: Identifier; } + const enum LoopOutParameterFlags { + Body = 1 << 0, // Modified in the body of the iteration statement + Initializer = 1 << 1, // Set in the initializer of a ForStatement + } + const enum CopyDirection { ToOriginal, ToOutParameter @@ -129,10 +135,14 @@ namespace ts { */ hoistedLocalVariables?: Identifier[]; + conditionVariable?: Identifier; + + loopParameters: ParameterDeclaration[]; + /** * List of loop out parameters - detailed descripion can be found in the comment to LoopOutParameter */ - loopOutParameters?: LoopOutParameter[]; + loopOutParameters: LoopOutParameter[]; } const enum SuperCaptureResult { @@ -347,7 +357,7 @@ namespace ts { return (node.transformFlags & TransformFlags.ContainsES2015) !== 0 || convertedLoopState !== undefined || (hierarchyFacts & HierarchyFacts.ConstructorWithCapturedSuper && (isStatement(node) || (node.kind === SyntaxKind.Block))) - || (isIterationStatement(node, /*lookInLabeledStatements*/ false) && shouldConvertIterationStatementBody(node)) + || (isIterationStatement(node, /*lookInLabeledStatements*/ false) && shouldConvertIterationStatement(node)) || (getEmitFlags(node) & EmitFlags.TypeScriptClassWrapper) !== 0; } @@ -646,8 +656,8 @@ namespace ts { } } let returnExpression: Expression = createLiteral(labelMarker); - if (convertedLoopState.loopOutParameters!.length) { - const outParams = convertedLoopState.loopOutParameters!; + if (convertedLoopState.loopOutParameters.length) { + const outParams = convertedLoopState.loopOutParameters; let expr: Expression | undefined; for (let i = 0; i < outParams.length; i++) { const copyExpr = copyOutParameter(outParams[i], CopyDirection.ToOutParameter); @@ -2610,7 +2620,40 @@ namespace ts { return visitEachChild(node, visitor, context); } - function shouldConvertIterationStatementBody(node: IterationStatement): boolean { + interface ForStatementWithConvertibleInitializer extends ForStatement { + initializer: VariableDeclarationList; + } + + interface ForStatementWithConvertibleCondition extends ForStatement { + condition: Expression; + } + + interface ForStatementWithConvertibleIncrementor extends ForStatement { + incrementor: Expression; + } + + function shouldConvertPartOfIterationStatement(node: Node) { + return (resolver.getNodeCheckFlags(node) & NodeCheckFlags.ContainsCapturedBlockScopeBinding) !== 0; + } + + function shouldConvertInitializerOfForStatement(node: IterationStatement): node is ForStatementWithConvertibleInitializer { + return isForStatement(node) && !!node.initializer && shouldConvertPartOfIterationStatement(node.initializer); + } + + function shouldConvertConditionOfForStatement(node: IterationStatement): node is ForStatementWithConvertibleCondition { + return isForStatement(node) && !!node.condition && shouldConvertPartOfIterationStatement(node.condition); + } + + function shouldConvertIncrementorOfForStatement(node: IterationStatement): node is ForStatementWithConvertibleIncrementor { + return isForStatement(node) && !!node.incrementor && shouldConvertPartOfIterationStatement(node.incrementor); + } + + function shouldConvertIterationStatement(node: IterationStatement) { + return shouldConvertBodyOfIterationStatement(node) + || shouldConvertInitializerOfForStatement(node); + } + + function shouldConvertBodyOfIterationStatement(node: IterationStatement): boolean { return (resolver.getNodeCheckFlags(node) & NodeCheckFlags.LoopWithCapturedBlockScopedBinding) !== 0; } @@ -2639,7 +2682,7 @@ namespace ts { } function convertIterationStatementBodyIfNecessary(node: IterationStatement, outermostLabeledStatement: LabeledStatement | undefined, convert?: LoopConverter): VisitResult { - if (!shouldConvertIterationStatementBody(node)) { + if (!shouldConvertIterationStatement(node)) { let saveAllowedNonLabeledJumps: Jump | undefined; if (convertedLoopState) { // we get here if we are trying to emit normal loop loop inside converted loop @@ -2658,7 +2701,102 @@ namespace ts { return result; } - const functionName = createUniqueName("_loop"); + const currentState = createConvertedLoopState(node); + const statements: Statement[] = []; + + const outerConvertedLoopState = convertedLoopState; + convertedLoopState = currentState; + + const initializerFunction = shouldConvertInitializerOfForStatement(node) ? createFunctionForInitializerOfForStatement(node, currentState) : undefined; + const bodyFunction = shouldConvertBodyOfIterationStatement(node) ? createFunctionForBodyOfIterationStatement(node, currentState, outerConvertedLoopState) : undefined; + + convertedLoopState = outerConvertedLoopState; + + if (initializerFunction) statements.push(initializerFunction.functionDeclaration); + if (bodyFunction) statements.push(bodyFunction.functionDeclaration); + + addExtraDeclarationsForConvertedLoop(statements, currentState, outerConvertedLoopState); + + if (initializerFunction) { + statements.push(generateCallToConvertedLoopInitializer(initializerFunction.functionName, initializerFunction.containsYield)); + } + + let loop: Statement; + if (bodyFunction) { + if (convert) { + loop = convert(node, outermostLabeledStatement, bodyFunction.part); + } + else { + const clone = convertIterationStatementCore(node, initializerFunction, createBlock(bodyFunction.part, /*multiLine*/ true)); + aggregateTransformFlags(clone); + loop = restoreEnclosingLabel(clone, outermostLabeledStatement, convertedLoopState && resetLabel); + } + } + else { + const clone = convertIterationStatementCore(node, initializerFunction, visitNode(node.statement, visitor, isStatement, liftToBlock)); + aggregateTransformFlags(clone); + loop = restoreEnclosingLabel(clone, outermostLabeledStatement, convertedLoopState && resetLabel); + } + + statements.push(loop); + return statements; + } + + function convertIterationStatementCore(node: IterationStatement, initializerFunction: IterationStatementPartFunction | undefined, convertedLoopBody: Statement) { + switch (node.kind) { + case SyntaxKind.ForStatement: return convertForStatement(node as ForStatement, initializerFunction, convertedLoopBody); + case SyntaxKind.ForInStatement: return convertForInStatement(node as ForInStatement, convertedLoopBody); + case SyntaxKind.ForOfStatement: return convertForOfStatement(node as ForOfStatement, convertedLoopBody); + case SyntaxKind.DoStatement: return convertDoStatement(node as DoStatement, convertedLoopBody); + case SyntaxKind.WhileStatement: return convertWhileStatement(node as WhileStatement, convertedLoopBody); + default: return Debug.failBadSyntaxKind(node, "IterationStatement expected"); + } + } + + function convertForStatement(node: ForStatement, initializerFunction: IterationStatementPartFunction | undefined, convertedLoopBody: Statement) { + const shouldConvertCondition = node.condition && shouldConvertPartOfIterationStatement(node.condition); + const shouldConvertIncrementor = shouldConvertCondition || node.incrementor && shouldConvertPartOfIterationStatement(node.incrementor); + return updateFor( + node, + visitNode(initializerFunction ? initializerFunction.part : node.initializer, visitor, isForInitializer), + visitNode(shouldConvertCondition ? undefined : node.condition, visitor, isExpression), + visitNode(shouldConvertIncrementor ? undefined : node.incrementor, visitor, isExpression), + convertedLoopBody + ); + } + + function convertForOfStatement(node: ForOfStatement, convertedLoopBody: Statement) { + return updateForOf( + node, + /*awaitModifier*/ undefined, + visitNode(node.initializer, visitor, isForInitializer), + visitNode(node.expression, visitor, isExpression), + convertedLoopBody); + } + + function convertForInStatement(node: ForInStatement, convertedLoopBody: Statement) { + return updateForIn( + node, + visitNode(node.initializer, visitor, isForInitializer), + visitNode(node.expression, visitor, isExpression), + convertedLoopBody); + } + + function convertDoStatement(node: DoStatement, convertedLoopBody: Statement) { + return updateDo( + node, + convertedLoopBody, + visitNode(node.expression, visitor, isExpression)); + } + + function convertWhileStatement(node: WhileStatement, convertedLoopBody: Statement) { + return updateWhile( + node, + visitNode(node.expression, visitor, isExpression), + convertedLoopBody); + } + + function createConvertedLoopState(node: IterationStatement) { let loopInitializer: VariableDeclarationList | undefined; switch (node.kind) { case SyntaxKind.ForStatement: @@ -2670,74 +2808,311 @@ namespace ts { } break; } + // variables that will be passed to the loop as parameters const loopParameters: ParameterDeclaration[] = []; // variables declared in the loop initializer that will be changed inside the loop const loopOutParameters: LoopOutParameter[] = []; if (loopInitializer && (getCombinedNodeFlags(loopInitializer) & NodeFlags.BlockScoped)) { + const hasCapturedBindingsInForInitializer = shouldConvertInitializerOfForStatement(node); for (const decl of loopInitializer.declarations) { - processLoopVariableDeclaration(decl, loopParameters, loopOutParameters); + processLoopVariableDeclaration(node, decl, loopParameters, loopOutParameters, hasCapturedBindingsInForInitializer); } } - const outerConvertedLoopState = convertedLoopState; - convertedLoopState = { loopOutParameters }; - if (outerConvertedLoopState) { + const currentState: ConvertedLoopState = { loopParameters, loopOutParameters }; + if (convertedLoopState) { // convertedOuterLoopState !== undefined means that this converted loop is nested in another converted loop. // if outer converted loop has already accumulated some state - pass it through - if (outerConvertedLoopState.argumentsName) { + if (convertedLoopState.argumentsName) { // outer loop has already used 'arguments' so we've already have some name to alias it // use the same name in all nested loops - convertedLoopState.argumentsName = outerConvertedLoopState.argumentsName; + currentState.argumentsName = convertedLoopState.argumentsName; } - if (outerConvertedLoopState.thisName) { + if (convertedLoopState.thisName) { // outer loop has already used 'this' so we've already have some name to alias it // use the same name in all nested loops - convertedLoopState.thisName = outerConvertedLoopState.thisName; + currentState.thisName = convertedLoopState.thisName; } - if (outerConvertedLoopState.hoistedLocalVariables) { + if (convertedLoopState.hoistedLocalVariables) { // we've already collected some non-block scoped variable declarations in enclosing loop // use the same storage in nested loop - convertedLoopState.hoistedLocalVariables = outerConvertedLoopState.hoistedLocalVariables; + currentState.hoistedLocalVariables = convertedLoopState.hoistedLocalVariables; + } + } + return currentState; + } + + function addExtraDeclarationsForConvertedLoop(statements: Statement[], state: ConvertedLoopState, outerState: ConvertedLoopState | undefined) { + let extraVariableDeclarations: VariableDeclaration[] | undefined; + // propagate state from the inner loop to the outer loop if necessary + if (state.argumentsName) { + // if alias for arguments is set + if (outerState) { + // pass it to outer converted loop + outerState.argumentsName = state.argumentsName; + } + else { + // this is top level converted loop and we need to create an alias for 'arguments' object + (extraVariableDeclarations || (extraVariableDeclarations = [])).push( + createVariableDeclaration( + state.argumentsName, + /*type*/ undefined, + createIdentifier("arguments") + ) + ); } } + if (state.thisName) { + // if alias for this is set + if (outerState) { + // pass it to outer converted loop + outerState.thisName = state.thisName; + } + else { + // this is top level converted loop so we need to create an alias for 'this' here + // NOTE: + // if converted loops were all nested in arrow function then we'll always emit '_this' so convertedLoopState.thisName will not be set. + // If it is set this means that all nested loops are not nested in arrow function and it is safe to capture 'this'. + (extraVariableDeclarations || (extraVariableDeclarations = [])).push( + createVariableDeclaration( + state.thisName, + /*type*/ undefined, + createIdentifier("this") + ) + ); + } + } + + if (state.hoistedLocalVariables) { + // if hoistedLocalVariables !== undefined this means that we've possibly collected some variable declarations to be hoisted later + if (outerState) { + // pass them to outer converted loop + outerState.hoistedLocalVariables = state.hoistedLocalVariables; + } + else { + if (!extraVariableDeclarations) { + extraVariableDeclarations = []; + } + // hoist collected variable declarations + for (const identifier of state.hoistedLocalVariables) { + extraVariableDeclarations.push(createVariableDeclaration(identifier)); + } + } + } + + // add extra variables to hold out parameters if necessary + if (state.loopOutParameters.length) { + if (!extraVariableDeclarations) { + extraVariableDeclarations = []; + } + for (const outParam of state.loopOutParameters) { + extraVariableDeclarations.push(createVariableDeclaration(outParam.outParamName)); + } + } + + if (state.conditionVariable) { + if (!extraVariableDeclarations) { + extraVariableDeclarations = []; + } + extraVariableDeclarations.push(createVariableDeclaration(state.conditionVariable, /*type*/ undefined, createFalse())); + } + + // create variable statement to hold all introduced variable declarations + if (extraVariableDeclarations) { + statements.push(createVariableStatement( + /*modifiers*/ undefined, + createVariableDeclarationList(extraVariableDeclarations) + )); + } + } + + interface IterationStatementPartFunction { + functionName: Identifier; + functionDeclaration: Statement; + containsYield: boolean; + part: T; + } + + function createOutVariable(p: LoopOutParameter) { + return createVariableDeclaration(p.originalName, /*type*/ undefined, p.outParamName); + } + + /** + * Creates a `_loop_init` function for a `ForStatement` with a block-scoped initializer + * that is captured in a closure inside of the initializer. The `_loop_init` function is + * used to preserve the per-iteration environment semantics of + * [13.7.4.8 RS: ForBodyEvaluation](https://tc39.github.io/ecma262/#sec-forbodyevaluation). + */ + function createFunctionForInitializerOfForStatement(node: ForStatementWithConvertibleInitializer, currentState: ConvertedLoopState): IterationStatementPartFunction { + const functionName = createUniqueName("_loop_init"); + + const containsYield = (node.initializer.transformFlags & TransformFlags.ContainsYield) !== 0; + let emitFlags = EmitFlags.None; + if (currentState.containsLexicalThis) emitFlags |= EmitFlags.CapturesThis; + if (containsYield && hierarchyFacts & HierarchyFacts.AsyncFunctionBody) emitFlags |= EmitFlags.AsyncFunctionBody; + + const statements: Statement[] = []; + statements.push(createVariableStatement(/*modifiers*/ undefined, node.initializer)); + copyOutParameters(currentState.loopOutParameters, LoopOutParameterFlags.Initializer, CopyDirection.ToOutParameter, statements); + + // This transforms the following ES2015 syntax: + // + // for (let i = (setImmediate(() => console.log(i)), 0); i < 2; i++) { + // // loop body + // } + // + // Into the following ES5 syntax: + // + // var _loop_init_1 = function () { + // var i = (setImmediate(() => console.log(i)), 0); + // out_i_1 = i; + // }; + // var out_i_1; + // _loop_init_1(); + // for (var i = out_i_1; i < 2; i++) { + // // loop body + // } + // + // Which prevents mutations to `i` in the per-iteration environment of the body + // from affecting the initial value for `i` outside of the per-iteration environment. + + const functionDeclaration = createVariableStatement( + /*modifiers*/ undefined, + setEmitFlags( + createVariableDeclarationList([ + createVariableDeclaration( + functionName, + /*type*/ undefined, + setEmitFlags( + createFunctionExpression( + /*modifiers*/ undefined, + containsYield ? createToken(SyntaxKind.AsteriskToken) : undefined, + /*name*/ undefined, + /*typeParameters*/ undefined, + /*parameters*/ undefined, + /*type*/ undefined, + visitNode( + createBlock(statements, /*multiLine*/ true), + visitor, + isBlock + ) + ), + emitFlags + ) + ) + ]), + EmitFlags.NoHoisting + ) + ); + + const part = createVariableDeclarationList(map(currentState.loopOutParameters, createOutVariable)); + return { functionName, containsYield, functionDeclaration, part }; + } + + /** + * Creates a `_loop` function for an `IterationStatement` with a block-scoped initializer + * that is captured in a closure inside of the loop body. The `_loop` function is used to + * preserve the per-iteration environment semantics of + * [13.7.4.8 RS: ForBodyEvaluation](https://tc39.github.io/ecma262/#sec-forbodyevaluation). + */ + function createFunctionForBodyOfIterationStatement(node: IterationStatement, currentState: ConvertedLoopState, outerState: ConvertedLoopState | undefined): IterationStatementPartFunction { + const functionName = createUniqueName("_loop"); startLexicalEnvironment(); - let loopBody = visitNode(node.statement, visitor, isStatement, liftToBlock); + const statement = visitNode(node.statement, visitor, isStatement, liftToBlock); const lexicalEnvironment = endLexicalEnvironment(); - const currentState = convertedLoopState; - convertedLoopState = outerConvertedLoopState; + const statements: Statement[] = []; + if (shouldConvertConditionOfForStatement(node) || shouldConvertIncrementorOfForStatement(node)) { + // If a block-scoped variable declared in the initializer of `node` is captured in + // the condition or incrementor, we must move the condition and incrementor into + // the body of the for loop. + // + // This transforms the following ES2015 syntax: + // + // for (let i = 0; setImmediate(() => console.log(i)), i < 2; setImmediate(() => console.log(i)), i++) { + // // loop body + // } + // + // Into the following ES5 syntax: + // + // var _loop_1 = function (i) { + // if (inc_1) + // setImmediate(() => console.log(i)), i++; + // else + // inc_1 = true; + // if (!(setImmediate(() => console.log(i)), i < 2)) + // return out_i_1 = i, "break"; + // // loop body + // out_i_1 = i; + // } + // var out_i_1, inc_1 = false; + // for (var i = 0;;) { + // var state_1 = _loop_1(i); + // i = out_i_1; + // if (state_1 === "break") + // break; + // } + // + // Which prevents mutations to `i` in the per-iteration environment of the body + // from affecting the value of `i` in the previous per-iteration environment. + // + // Note that the incrementor of a `for` loop is evaluated in a *new* per-iteration + // environment that is carried over to the next iteration of the loop. As a result, + // we must indicate whether this is the first evaluation of the loop body so that + // we only evaluate the incrementor on subsequent evaluations. - if (loopOutParameters.length || lexicalEnvironment) { - const statements = isBlock(loopBody) ? loopBody.statements.slice() : [loopBody]; - if (loopOutParameters.length) { - copyOutParameters(loopOutParameters, CopyDirection.ToOutParameter, statements); + currentState.conditionVariable = createUniqueName("inc"); + statements.push(createIf( + currentState.conditionVariable, + createStatement(visitNode(node.incrementor, visitor, isExpression)), + createStatement(createAssignment(currentState.conditionVariable, createTrue())) + )); + + if (shouldConvertConditionOfForStatement(node)) { + statements.push(createIf( + createPrefix(SyntaxKind.ExclamationToken, visitNode(node.condition, visitor, isExpression)), + visitNode(createBreak(), visitor, isStatement) + )); } - addStatementsAfterPrologue(statements, lexicalEnvironment); - loopBody = createBlock(statements, /*multiline*/ true); } - if (isBlock(loopBody)) { - loopBody.multiLine = true; + if (isBlock(statement)) { + addRange(statements, statement.statements); } else { - loopBody = createBlock([loopBody], /*multiline*/ true); + statements.push(statement); } + copyOutParameters(currentState.loopOutParameters, LoopOutParameterFlags.Body, CopyDirection.ToOutParameter, statements); + addStatementsAfterPrologue(statements, lexicalEnvironment); + + const loopBody = createBlock(statements, /*multiLine*/ true); + if (isBlock(statement)) setOriginalNode(loopBody, statement); + const containsYield = (node.statement.transformFlags & TransformFlags.ContainsYield) !== 0; - const isAsyncBlockContainingAwait = containsYield && (hierarchyFacts & HierarchyFacts.AsyncFunctionBody) !== 0; - let loopBodyFlags: EmitFlags = 0; - if (currentState.containsLexicalThis) { - loopBodyFlags |= EmitFlags.CapturesThis; - } + let emitFlags: EmitFlags = 0; + if (currentState.containsLexicalThis) emitFlags |= EmitFlags.CapturesThis; + if (containsYield && (hierarchyFacts & HierarchyFacts.AsyncFunctionBody) !== 0) emitFlags |= EmitFlags.AsyncFunctionBody; - if (isAsyncBlockContainingAwait) { - loopBodyFlags |= EmitFlags.AsyncFunctionBody; - } + // This transforms the following ES2015 syntax (in addition to other variations): + // + // for (let i = 0; i < 2; i++) { + // setImmediate(() => console.log(i)); + // } + // + // Into the following ES5 syntax: + // + // var _loop_1 = function (i) { + // setImmediate(() => console.log(i)); + // }; + // for (var i = 0; i < 2; i++) { + // _loop_1(i); + // } - const convertedLoopVariable = + const functionDeclaration = createVariableStatement( /*modifiers*/ undefined, setEmitFlags( @@ -2752,11 +3127,11 @@ namespace ts { containsYield ? createToken(SyntaxKind.AsteriskToken) : undefined, /*name*/ undefined, /*typeParameters*/ undefined, - loopParameters, + currentState.loopParameters, /*type*/ undefined, - loopBody + loopBody ), - loopBodyFlags + emitFlags ) ) ] @@ -2765,106 +3140,8 @@ namespace ts { ) ); - const statements: Statement[] = [convertedLoopVariable]; - - let extraVariableDeclarations: VariableDeclaration[] | undefined; - // propagate state from the inner loop to the outer loop if necessary - if (currentState.argumentsName) { - // if alias for arguments is set - if (outerConvertedLoopState) { - // pass it to outer converted loop - outerConvertedLoopState.argumentsName = currentState.argumentsName; - } - else { - // this is top level converted loop and we need to create an alias for 'arguments' object - (extraVariableDeclarations || (extraVariableDeclarations = [])).push( - createVariableDeclaration( - currentState.argumentsName, - /*type*/ undefined, - createIdentifier("arguments") - ) - ); - } - } - - if (currentState.thisName) { - // if alias for this is set - if (outerConvertedLoopState) { - // pass it to outer converted loop - outerConvertedLoopState.thisName = currentState.thisName; - } - else { - // this is top level converted loop so we need to create an alias for 'this' here - // NOTE: - // if converted loops were all nested in arrow function then we'll always emit '_this' so convertedLoopState.thisName will not be set. - // If it is set this means that all nested loops are not nested in arrow function and it is safe to capture 'this'. - (extraVariableDeclarations || (extraVariableDeclarations = [])).push( - createVariableDeclaration( - currentState.thisName, - /*type*/ undefined, - createIdentifier("this") - ) - ); - } - } - - if (currentState.hoistedLocalVariables) { - // if hoistedLocalVariables !== undefined this means that we've possibly collected some variable declarations to be hoisted later - if (outerConvertedLoopState) { - // pass them to outer converted loop - outerConvertedLoopState.hoistedLocalVariables = currentState.hoistedLocalVariables; - } - else { - if (!extraVariableDeclarations) { - extraVariableDeclarations = []; - } - // hoist collected variable declarations - for (const identifier of currentState.hoistedLocalVariables) { - extraVariableDeclarations.push(createVariableDeclaration(identifier)); - } - } - } - - // add extra variables to hold out parameters if necessary - if (loopOutParameters.length) { - if (!extraVariableDeclarations) { - extraVariableDeclarations = []; - } - for (const outParam of loopOutParameters) { - extraVariableDeclarations.push(createVariableDeclaration(outParam.outParamName)); - } - } - - // create variable statement to hold all introduced variable declarations - if (extraVariableDeclarations) { - statements.push(createVariableStatement( - /*modifiers*/ undefined, - createVariableDeclarationList(extraVariableDeclarations) - )); - } - - const convertedLoopBodyStatements = generateCallToConvertedLoop(functionName, loopParameters, currentState, containsYield); - - let loop: Statement; - if (convert) { - loop = convert(node, outermostLabeledStatement, convertedLoopBodyStatements); - } - else { - let clone = getMutableClone(node); - // clean statement part - clone.statement = undefined!; - // visit childnodes to transform initializer/condition/incrementor parts - clone = visitEachChild(clone, visitor, context); - // set loop statement - clone.statement = createBlock(convertedLoopBodyStatements, /*multiline*/ true); - // reset and re-aggregate the transform flags - clone.transformFlags = 0; - aggregateTransformFlags(clone); - loop = restoreEnclosingLabel(clone, outermostLabeledStatement, convertedLoopState && resetLabel); - } - - statements.push(loop); - return statements; + const part = generateCallToConvertedLoop(functionName, currentState, outerState, containsYield); + return { functionName, containsYield, functionDeclaration, part }; } function copyOutParameter(outParam: LoopOutParameter, copyDirection: CopyDirection): BinaryExpression { @@ -2873,14 +3150,26 @@ namespace ts { return createBinary(target, SyntaxKind.EqualsToken, source); } - function copyOutParameters(outParams: LoopOutParameter[], copyDirection: CopyDirection, statements: Statement[]): void { + function copyOutParameters(outParams: LoopOutParameter[], partFlags: LoopOutParameterFlags, copyDirection: CopyDirection, statements: Statement[]): void { for (const outParam of outParams) { - statements.push(createExpressionStatement(copyOutParameter(outParam, copyDirection))); + if (outParam.flags & partFlags) { + statements.push(createExpressionStatement(copyOutParameter(outParam, copyDirection))); + } } } - function generateCallToConvertedLoop(loopFunctionExpressionName: Identifier, parameters: ParameterDeclaration[], state: ConvertedLoopState, isAsyncBlockContainingAwait: boolean): Statement[] { - const outerConvertedLoopState = convertedLoopState; + function generateCallToConvertedLoopInitializer(initFunctionExpressionName: Identifier, containsYield: boolean): Statement { + const call = createCall(initFunctionExpressionName, /*typeArguments*/ undefined, []); + const callResult = containsYield + ? createYield( + createToken(SyntaxKind.AsteriskToken), + setEmitFlags(call, EmitFlags.Iterator) + ) + : call; + return createStatement(callResult); + } + + function generateCallToConvertedLoop(loopFunctionExpressionName: Identifier, state: ConvertedLoopState, outerState: ConvertedLoopState | undefined, containsYield: boolean): Statement[] { const statements: Statement[] = []; // loop is considered simple if it does not have any return statements or break\continue that transfer control outside of the loop @@ -2891,8 +3180,8 @@ namespace ts { !state.labeledNonLocalBreaks && !state.labeledNonLocalContinues; - const call = createCall(loopFunctionExpressionName, /*typeArguments*/ undefined, map(parameters, p => p.name)); - const callResult = isAsyncBlockContainingAwait + const call = createCall(loopFunctionExpressionName, /*typeArguments*/ undefined, map(state.loopParameters, p => p.name)); + const callResult = containsYield ? createYield( createToken(SyntaxKind.AsteriskToken), setEmitFlags(call, EmitFlags.Iterator) @@ -2900,7 +3189,7 @@ namespace ts { : call; if (isSimpleLoop) { statements.push(createExpressionStatement(callResult)); - copyOutParameters(state.loopOutParameters!, CopyDirection.ToOriginal, statements); + copyOutParameters(state.loopOutParameters, LoopOutParameterFlags.Body, CopyDirection.ToOriginal, statements); } else { const loopResultName = createUniqueName("state"); @@ -2911,12 +3200,12 @@ namespace ts { ) ); statements.push(stateVariable); - copyOutParameters(state.loopOutParameters!, CopyDirection.ToOriginal, statements); + copyOutParameters(state.loopOutParameters, LoopOutParameterFlags.Body, CopyDirection.ToOriginal, statements); if (state.nonLocalJumps! & Jump.Return) { let returnStatement: ReturnStatement; - if (outerConvertedLoopState) { - outerConvertedLoopState.nonLocalJumps! |= Jump.Return; + if (outerState) { + outerState.nonLocalJumps! |= Jump.Return; returnStatement = createReturn(loopResultName); } else { @@ -2949,8 +3238,8 @@ namespace ts { if (state.labeledNonLocalBreaks || state.labeledNonLocalContinues) { const caseClauses: CaseClause[] = []; - processLabeledJumps(state.labeledNonLocalBreaks!, /*isBreak*/ true, loopResultName, outerConvertedLoopState, caseClauses); - processLabeledJumps(state.labeledNonLocalContinues!, /*isBreak*/ false, loopResultName, outerConvertedLoopState, caseClauses); + processLabeledJumps(state.labeledNonLocalBreaks!, /*isBreak*/ true, loopResultName, outerState, caseClauses); + processLabeledJumps(state.labeledNonLocalContinues!, /*isBreak*/ false, loopResultName, outerState, caseClauses); statements.push( createSwitch( loopResultName, @@ -2998,20 +3287,28 @@ namespace ts { }); } - function processLoopVariableDeclaration(decl: VariableDeclaration | BindingElement, loopParameters: ParameterDeclaration[], loopOutParameters: LoopOutParameter[]) { + function processLoopVariableDeclaration(container: IterationStatement, decl: VariableDeclaration | BindingElement, loopParameters: ParameterDeclaration[], loopOutParameters: LoopOutParameter[], hasCapturedBindingsInForInitializer: boolean) { const name = decl.name; if (isBindingPattern(name)) { for (const element of name.elements) { if (!isOmittedExpression(element)) { - processLoopVariableDeclaration(element, loopParameters, loopOutParameters); + processLoopVariableDeclaration(container, element, loopParameters, loopOutParameters, hasCapturedBindingsInForInitializer); } } } else { loopParameters.push(createParameter(/*decorators*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, name)); - if (resolver.getNodeCheckFlags(decl) & NodeCheckFlags.NeedsLoopOutParameter) { + const checkFlags = resolver.getNodeCheckFlags(decl); + if (checkFlags & NodeCheckFlags.NeedsLoopOutParameter || hasCapturedBindingsInForInitializer) { const outParamName = createUniqueName("out_" + idText(name)); - loopOutParameters.push({ originalName: name, outParamName }); + let flags: LoopOutParameterFlags = 0; + if (checkFlags & NodeCheckFlags.NeedsLoopOutParameter) { + flags |= LoopOutParameterFlags.Body; + } + if (isForStatement(container) && container.initializer && resolver.isBindingCapturedByNode(container.initializer, decl)) { + flags |= LoopOutParameterFlags.Initializer; + } + loopOutParameters.push({ flags, originalName: name, outParamName }); } } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 97ef69d41e5..9b1c57b2097 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3420,6 +3420,7 @@ namespace ts { getJsxFactoryEntity(location?: Node): EntityName | undefined; getAllAccessorDeclarations(declaration: AccessorDeclaration): AllAccessorDeclarations; getSymbolOfExternalModuleSpecifier(node: StringLiteralLike): Symbol | undefined; + isBindingCapturedByNode(node: Node, decl: VariableDeclaration | BindingElement): boolean; } export const enum SymbolFlags { @@ -3668,14 +3669,15 @@ namespace ts { EnumValuesComputed = 0x00004000, // Values for enum members have been computed, and any errors have been reported for them. LexicalModuleMergesWithClass = 0x00008000, // Instantiated lexical module declaration is merged with a previous class declaration. LoopWithCapturedBlockScopedBinding = 0x00010000, // Loop that contains block scoped variable captured in closure - CapturedBlockScopedBinding = 0x00020000, // Block-scoped binding that is captured in some function - BlockScopedBindingInLoop = 0x00040000, // Block-scoped binding with declaration nested inside iteration statement - ClassWithBodyScopedClassBinding = 0x00080000, // Decorated class that contains a binding to itself inside of the class body. - BodyScopedClassBinding = 0x00100000, // Binding to a decorated class inside of the class's body. - NeedsLoopOutParameter = 0x00200000, // Block scoped binding whose value should be explicitly copied outside of the converted loop - AssignmentsMarked = 0x00400000, // Parameter assignments have been marked - ClassWithConstructorReference = 0x00800000, // Class that contains a binding to its constructor inside of the class body. - ConstructorReferenceInClass = 0x01000000, // Binding to a class constructor inside of the class's body. + ContainsCapturedBlockScopeBinding = 0x00020000, // Part of a loop that contains block scoped variable captured in closure + CapturedBlockScopedBinding = 0x00040000, // Block-scoped binding that is captured in some function + BlockScopedBindingInLoop = 0x00080000, // Block-scoped binding with declaration nested inside iteration statement + ClassWithBodyScopedClassBinding = 0x00100000, // Decorated class that contains a binding to itself inside of the class body. + BodyScopedClassBinding = 0x00200000, // Binding to a decorated class inside of the class's body. + NeedsLoopOutParameter = 0x00400000, // Block scoped binding whose value should be explicitly copied outside of the converted loop + AssignmentsMarked = 0x00800000, // Parameter assignments have been marked + ClassWithConstructorReference = 0x01000000, // Class that contains a binding to its constructor inside of the class body. + ConstructorReferenceInClass = 0x02000000, // Binding to a class constructor inside of the class's body. } /* @internal */ @@ -3700,6 +3702,7 @@ namespace ts { switchTypes?: Type[]; // Cached array of switch case expression types jsxNamespace?: Symbol | false; // Resolved jsx namespace symbol for this node contextFreeType?: Type; // Cached context-free type used by the first pass of inference; used when a function's return is partially contextually sensitive + capturedBlockScopeBindings?: Symbol[]; // Block-scoped bindings captured beneath this part of an IterationStatement } export const enum TypeFlags { diff --git a/tests/baselines/reference/capturedLetConstInLoop1.js b/tests/baselines/reference/capturedLetConstInLoop1.js index 3c064059cb0..7acaf851cc2 100644 --- a/tests/baselines/reference/capturedLetConstInLoop1.js +++ b/tests/baselines/reference/capturedLetConstInLoop1.js @@ -1,4 +1,6 @@ //// [capturedLetConstInLoop1.ts] +declare function use(x: any): any; + //==== let for (let x in {}) { (function() { return x}); @@ -56,6 +58,19 @@ for (let y = 0; y < 1; ++y) { (() => x + y); } +for (let y = (use(() => y), 0); y < 1; ++y) { +} + +for (let y = 0; use(() => y), y < 1; ++y) { +} + +for (let y = 0; y < 1; use(() => y), ++y) { +} + +for (let y = (use(() => y), 0); use(() => y), y < 1; use(() => y), ++y) { + use(() => y); +} + //=========const for (const x in {}) { (function() { return x}); @@ -202,93 +217,145 @@ var _loop_10 = function (y) { for (var y = 0; y < 1; ++y) { _loop_10(y); } -var _loop_11 = function (x) { +var _loop_init_1 = function () { + var y = (use(function () { return y; }), 0); + out_y_1 = y; +}; +var out_y_1; +_loop_init_1(); +for (var y = out_y_1; y < 1; ++y) { +} +var _loop_11 = function (y) { + if (inc_1) + ++y; + else + inc_1 = true; + if (!(use(function () { return y; }), y < 1)) + return "break"; +}; +var inc_1 = false; +for (var y = 0;;) { + var state_1 = _loop_11(y); + if (state_1 === "break") + break; +} +var _loop_12 = function (y) { + if (inc_2) + use(function () { return y; }), ++y; + else + inc_2 = true; +}; +var inc_2 = false; +for (var y = 0; y < 1;) { + _loop_12(y); +} +var _loop_init_2 = function () { + var y = (use(function () { return y; }), 0); + out_y_2 = y; +}; +var _loop_13 = function (y) { + if (inc_3) + use(function () { return y; }), ++y; + else + inc_3 = true; + if (!(use(function () { return y; }), y < 1)) + return out_y_2 = y, "break"; + use(function () { return y; }); +}; +var out_y_2, inc_3 = false; +_loop_init_2(); +for (var y = out_y_2;;) { + var state_2 = _loop_13(y); + if (state_2 === "break") + break; +} +var _loop_14 = function (x) { (function () { return x; }); (function () { return x; }); }; //=========const for (var x in {}) { - _loop_11(x); + _loop_14(x); } -var _loop_12 = function (x) { +var _loop_15 = function (x) { (function () { return x; }); (function () { return x; }); }; for (var _b = 0, _c = []; _b < _c.length; _b++) { var x = _c[_b]; - _loop_12(x); + _loop_15(x); } -var _loop_13 = function (x) { +var _loop_16 = function (x) { (function () { return x; }); (function () { return x; }); }; for (var x = 0; x < 1;) { - _loop_13(x); + _loop_16(x); } -var _loop_14 = function () { +var _loop_17 = function () { var x = 1; (function () { return x; }); (function () { return x; }); }; while (1 === 1) { - _loop_14(); + _loop_17(); } -var _loop_15 = function () { +var _loop_18 = function () { var x = 1; (function () { return x; }); (function () { return x; }); }; do { - _loop_15(); + _loop_18(); } while (1 === 1); -var _loop_16 = function (y) { +var _loop_19 = function (y) { var x = 1; (function () { return x; }); (function () { return x; }); }; for (var y = 0; y < 1;) { - _loop_16(y); + _loop_19(y); } -var _loop_17 = function (x, y) { +var _loop_20 = function (x, y) { (function () { return x + y; }); (function () { return x + y; }); }; for (var x = 0, y = 1; x < 1;) { - _loop_17(x, y); + _loop_20(x, y); } -var _loop_18 = function () { +var _loop_21 = function () { var x = 1, y = 1; (function () { return x + y; }); (function () { return x + y; }); }; while (1 === 1) { - _loop_18(); + _loop_21(); } -var _loop_19 = function () { +var _loop_22 = function () { var x = 1, y = 1; (function () { return x + y; }); (function () { return x + y; }); }; do { - _loop_19(); + _loop_22(); } while (1 === 1); -var _loop_20 = function (y) { +var _loop_23 = function (y) { var x = 1; (function () { return x + y; }); (function () { return x + y; }); }; for (var y = 0; y < 1;) { - _loop_20(y); + _loop_23(y); } -var _loop_21 = function (sx) { +var _loop_24 = function (sx) { (function () { return sobj[sx]; }); }; for (var sx in sobj) { - _loop_21(sx); + _loop_24(sx); } -var _loop_22 = function (ix) { +var _loop_25 = function (ix) { (function () { return iobj[ix]; }); }; for (var ix in iobj) { - _loop_22(ix); + _loop_25(ix); } diff --git a/tests/baselines/reference/capturedLetConstInLoop1.symbols b/tests/baselines/reference/capturedLetConstInLoop1.symbols index afa7898a258..43f263bd73a 100644 --- a/tests/baselines/reference/capturedLetConstInLoop1.symbols +++ b/tests/baselines/reference/capturedLetConstInLoop1.symbols @@ -1,286 +1,330 @@ === tests/cases/compiler/capturedLetConstInLoop1.ts === +declare function use(x: any): any; +>use : Symbol(use, Decl(capturedLetConstInLoop1.ts, 0, 0)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 0, 21)) + //==== let for (let x in {}) { ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 1, 8)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 3, 8)) (function() { return x}); ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 1, 8)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 3, 8)) (() => x); ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 1, 8)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 3, 8)) } for (let x of []) { ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 6, 8)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 8, 8)) (function() { return x}); ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 6, 8)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 8, 8)) (() => x); ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 6, 8)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 8, 8)) } for (let x = 0; x < 1; ++x) { ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 11, 8)) ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 11, 8)) ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 11, 8)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 13, 8)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 13, 8)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 13, 8)) (function() { return x}); ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 11, 8)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 13, 8)) (() => x); ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 11, 8)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 13, 8)) } while (1 === 1) { let x; ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 17, 7)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 19, 7)) (function() { return x}); ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 17, 7)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 19, 7)) (() => x); ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 17, 7)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 19, 7)) } do { let x; ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 23, 7)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 25, 7)) (function() { return x}); ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 23, 7)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 25, 7)) (() => x); ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 23, 7)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 25, 7)) } while (1 === 1) for (let y = 0; y < 1; ++y) { ->y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 28, 8)) ->y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 28, 8)) ->y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 28, 8)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 30, 8)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 30, 8)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 30, 8)) let x = 1; ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 29, 7)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 31, 7)) (function() { return x}); ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 29, 7)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 31, 7)) (() => x); ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 29, 7)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 31, 7)) } for (let x = 0, y = 1; x < 1; ++x) { ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 34, 8)) ->y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 34, 15)) ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 34, 8)) ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 34, 8)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 36, 8)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 36, 15)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 36, 8)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 36, 8)) (function() { return x + y}); ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 34, 8)) ->y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 34, 15)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 36, 8)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 36, 15)) (() => x + y); ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 34, 8)) ->y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 34, 15)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 36, 8)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 36, 15)) } while (1 === 1) { let x, y; ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 40, 7)) ->y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 40, 10)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 42, 7)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 42, 10)) (function() { return x + y}); ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 40, 7)) ->y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 40, 10)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 42, 7)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 42, 10)) (() => x + y); ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 40, 7)) ->y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 40, 10)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 42, 7)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 42, 10)) } do { let x, y; ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 46, 7)) ->y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 46, 10)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 48, 7)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 48, 10)) (function() { return x + y}); ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 46, 7)) ->y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 46, 10)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 48, 7)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 48, 10)) (() => x + y); ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 46, 7)) ->y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 46, 10)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 48, 7)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 48, 10)) } while (1 === 1) for (let y = 0; y < 1; ++y) { ->y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 51, 8)) ->y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 51, 8)) ->y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 51, 8)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 53, 8)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 53, 8)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 53, 8)) let x = 1; ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 52, 7)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 54, 7)) (function() { return x + y}); ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 52, 7)) ->y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 51, 8)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 54, 7)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 53, 8)) (() => x + y); ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 52, 7)) ->y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 51, 8)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 54, 7)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 53, 8)) +} + +for (let y = (use(() => y), 0); y < 1; ++y) { +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 59, 8)) +>use : Symbol(use, Decl(capturedLetConstInLoop1.ts, 0, 0)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 59, 8)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 59, 8)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 59, 8)) +} + +for (let y = 0; use(() => y), y < 1; ++y) { +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 62, 8)) +>use : Symbol(use, Decl(capturedLetConstInLoop1.ts, 0, 0)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 62, 8)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 62, 8)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 62, 8)) +} + +for (let y = 0; y < 1; use(() => y), ++y) { +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 65, 8)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 65, 8)) +>use : Symbol(use, Decl(capturedLetConstInLoop1.ts, 0, 0)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 65, 8)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 65, 8)) +} + +for (let y = (use(() => y), 0); use(() => y), y < 1; use(() => y), ++y) { +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 68, 8)) +>use : Symbol(use, Decl(capturedLetConstInLoop1.ts, 0, 0)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 68, 8)) +>use : Symbol(use, Decl(capturedLetConstInLoop1.ts, 0, 0)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 68, 8)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 68, 8)) +>use : Symbol(use, Decl(capturedLetConstInLoop1.ts, 0, 0)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 68, 8)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 68, 8)) + + use(() => y); +>use : Symbol(use, Decl(capturedLetConstInLoop1.ts, 0, 0)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 68, 8)) } //=========const for (const x in {}) { ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 58, 10)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 73, 10)) (function() { return x}); ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 58, 10)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 73, 10)) (() => x); ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 58, 10)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 73, 10)) } for (const x of []) { ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 63, 10)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 78, 10)) (function() { return x}); ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 63, 10)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 78, 10)) (() => x); ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 63, 10)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 78, 10)) } for (const x = 0; x < 1;) { ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 68, 10)) ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 68, 10)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 83, 10)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 83, 10)) (function() { return x}); ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 68, 10)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 83, 10)) (() => x); ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 68, 10)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 83, 10)) } while (1 === 1) { const x = 1; ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 74, 9)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 89, 9)) (function() { return x}); ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 74, 9)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 89, 9)) (() => x); ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 74, 9)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 89, 9)) } do { const x = 1; ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 80, 9)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 95, 9)) (function() { return x}); ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 80, 9)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 95, 9)) (() => x); ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 80, 9)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 95, 9)) } while (1 === 1) for (const y = 0; y < 1;) { ->y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 85, 10)) ->y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 85, 10)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 100, 10)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 100, 10)) const x = 1; ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 86, 9)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 101, 9)) (function() { return x}); ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 86, 9)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 101, 9)) (() => x); ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 86, 9)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 101, 9)) } for (const x = 0, y = 1; x < 1;) { ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 91, 10)) ->y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 91, 17)) ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 91, 10)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 106, 10)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 106, 17)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 106, 10)) (function() { return x + y}); ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 91, 10)) ->y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 91, 17)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 106, 10)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 106, 17)) (() => x + y); ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 91, 10)) ->y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 91, 17)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 106, 10)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 106, 17)) } while (1 === 1) { const x = 1, y = 1; ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 97, 9)) ->y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 97, 16)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 112, 9)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 112, 16)) (function() { return x + y}); ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 97, 9)) ->y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 97, 16)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 112, 9)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 112, 16)) (() => x + y); ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 97, 9)) ->y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 97, 16)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 112, 9)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 112, 16)) } do { const x = 1, y = 1; ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 103, 9)) ->y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 103, 16)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 118, 9)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 118, 16)) (function() { return x + y}); ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 103, 9)) ->y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 103, 16)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 118, 9)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 118, 16)) (() => x + y); ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 103, 9)) ->y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 103, 16)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 118, 9)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 118, 16)) } while (1 === 1) for (const y = 0; y < 1;) { ->y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 108, 10)) ->y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 108, 10)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 123, 10)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 123, 10)) const x = 1; ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 109, 9)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 124, 9)) (function() { return x + y}); ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 109, 9)) ->y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 108, 10)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 124, 9)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 123, 10)) (() => x + y); ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 109, 9)) ->y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 108, 10)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 124, 9)) +>y : Symbol(y, Decl(capturedLetConstInLoop1.ts, 123, 10)) } // https://github.com/Microsoft/TypeScript/issues/20594 declare const sobj: { [x: string]: any }; ->sobj : Symbol(sobj, Decl(capturedLetConstInLoop1.ts, 115, 13)) ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 115, 23)) +>sobj : Symbol(sobj, Decl(capturedLetConstInLoop1.ts, 130, 13)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 130, 23)) for (let sx in sobj) { ->sx : Symbol(sx, Decl(capturedLetConstInLoop1.ts, 116, 8)) ->sobj : Symbol(sobj, Decl(capturedLetConstInLoop1.ts, 115, 13)) +>sx : Symbol(sx, Decl(capturedLetConstInLoop1.ts, 131, 8)) +>sobj : Symbol(sobj, Decl(capturedLetConstInLoop1.ts, 130, 13)) (() => sobj[sx]); ->sobj : Symbol(sobj, Decl(capturedLetConstInLoop1.ts, 115, 13)) ->sx : Symbol(sx, Decl(capturedLetConstInLoop1.ts, 116, 8)) +>sobj : Symbol(sobj, Decl(capturedLetConstInLoop1.ts, 130, 13)) +>sx : Symbol(sx, Decl(capturedLetConstInLoop1.ts, 131, 8)) } declare const iobj: { [x: number]: any }; ->iobj : Symbol(iobj, Decl(capturedLetConstInLoop1.ts, 119, 13)) ->x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 119, 23)) +>iobj : Symbol(iobj, Decl(capturedLetConstInLoop1.ts, 134, 13)) +>x : Symbol(x, Decl(capturedLetConstInLoop1.ts, 134, 23)) for (let ix in iobj) { ->ix : Symbol(ix, Decl(capturedLetConstInLoop1.ts, 120, 8)) ->iobj : Symbol(iobj, Decl(capturedLetConstInLoop1.ts, 119, 13)) +>ix : Symbol(ix, Decl(capturedLetConstInLoop1.ts, 135, 8)) +>iobj : Symbol(iobj, Decl(capturedLetConstInLoop1.ts, 134, 13)) (() => iobj[ix]); ->iobj : Symbol(iobj, Decl(capturedLetConstInLoop1.ts, 119, 13)) ->ix : Symbol(ix, Decl(capturedLetConstInLoop1.ts, 120, 8)) +>iobj : Symbol(iobj, Decl(capturedLetConstInLoop1.ts, 134, 13)) +>ix : Symbol(ix, Decl(capturedLetConstInLoop1.ts, 135, 8)) } diff --git a/tests/baselines/reference/capturedLetConstInLoop1.types b/tests/baselines/reference/capturedLetConstInLoop1.types index 34ba101752b..a7d3094268c 100644 --- a/tests/baselines/reference/capturedLetConstInLoop1.types +++ b/tests/baselines/reference/capturedLetConstInLoop1.types @@ -1,4 +1,8 @@ === tests/cases/compiler/capturedLetConstInLoop1.ts === +declare function use(x: any): any; +>use : (x: any) => any +>x : any + //==== let for (let x in {}) { >x : string @@ -214,6 +218,84 @@ for (let y = 0; y < 1; ++y) { >y : number } +for (let y = (use(() => y), 0); y < 1; ++y) { +>y : number +>(use(() => y), 0) : 0 +>use(() => y), 0 : 0 +>use(() => y) : any +>use : (x: any) => any +>() => y : () => number +>y : number +>0 : 0 +>y < 1 : boolean +>y : number +>1 : 1 +>++y : number +>y : number +} + +for (let y = 0; use(() => y), y < 1; ++y) { +>y : number +>0 : 0 +>use(() => y), y < 1 : boolean +>use(() => y) : any +>use : (x: any) => any +>() => y : () => number +>y : number +>y < 1 : boolean +>y : number +>1 : 1 +>++y : number +>y : number +} + +for (let y = 0; y < 1; use(() => y), ++y) { +>y : number +>0 : 0 +>y < 1 : boolean +>y : number +>1 : 1 +>use(() => y), ++y : number +>use(() => y) : any +>use : (x: any) => any +>() => y : () => number +>y : number +>++y : number +>y : number +} + +for (let y = (use(() => y), 0); use(() => y), y < 1; use(() => y), ++y) { +>y : number +>(use(() => y), 0) : 0 +>use(() => y), 0 : 0 +>use(() => y) : any +>use : (x: any) => any +>() => y : () => number +>y : number +>0 : 0 +>use(() => y), y < 1 : boolean +>use(() => y) : any +>use : (x: any) => any +>() => y : () => number +>y : number +>y < 1 : boolean +>y : number +>1 : 1 +>use(() => y), ++y : number +>use(() => y) : any +>use : (x: any) => any +>() => y : () => number +>y : number +>++y : number +>y : number + + use(() => y); +>use(() => y) : any +>use : (x: any) => any +>() => y : () => number +>y : number +} + //=========const for (const x in {}) { >x : string diff --git a/tests/cases/compiler/capturedLetConstInLoop1.ts b/tests/cases/compiler/capturedLetConstInLoop1.ts index e4956fd16df..4919429eec7 100644 --- a/tests/cases/compiler/capturedLetConstInLoop1.ts +++ b/tests/cases/compiler/capturedLetConstInLoop1.ts @@ -1,3 +1,5 @@ +declare function use(x: any): any; + //==== let for (let x in {}) { (function() { return x}); @@ -55,6 +57,19 @@ for (let y = 0; y < 1; ++y) { (() => x + y); } +for (let y = (use(() => y), 0); y < 1; ++y) { +} + +for (let y = 0; use(() => y), y < 1; ++y) { +} + +for (let y = 0; y < 1; use(() => y), ++y) { +} + +for (let y = (use(() => y), 0); use(() => y), y < 1; use(() => y), ++y) { + use(() => y); +} + //=========const for (const x in {}) { (function() { return x});