diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index b66497fc40d59..d2ac3d2afd152 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -62,6 +62,7 @@ module ts { let parent: Node; let container: Node; let blockScopeContainer: Node; + let iteration: IterationStatement; let lastContainer: Node; let symbolCount = 0; let Symbol = objectAllocator.getSymbolConstructor(); @@ -227,7 +228,7 @@ module ts { // All container nodes are kept on a linked list in declaration order. This list is used by the getLocalNameOfContainer function // in the type checker to validate that the local name used for a container is unique. - function bindChildren(node: Node, symbolKind: SymbolFlags, isBlockScopeContainer: boolean) { + function bindChildren(node: Node, symbolKind: SymbolFlags, isBlockScopeContainer: boolean, isIteration: boolean = false) { if (symbolKind & SymbolFlags.HasLocals) { node.locals = {}; } @@ -235,6 +236,7 @@ module ts { let saveParent = parent; let saveContainer = container; let savedBlockScopeContainer = blockScopeContainer; + let savedIteration = iteration; parent = node; if (symbolKind & SymbolFlags.IsContainer) { container = node; @@ -251,11 +253,17 @@ module ts { // - node is a source file setBlockScopeContainer(node, /*cleanLocals*/ (symbolKind & SymbolFlags.HasLocals) === 0 && node.kind !== SyntaxKind.SourceFile); } + + if (isIteration) { + iteration = node; + iteration.iterationScopedDeclarations = []; + } forEachChild(node, bind); container = saveContainer; parent = saveParent; blockScopeContainer = savedBlockScopeContainer; + iteration = savedIteration; } function addToContainerChain(node: Node) { @@ -266,7 +274,7 @@ module ts { lastContainer = node; } - function bindDeclaration(node: Declaration, symbolKind: SymbolFlags, symbolExcludes: SymbolFlags, isBlockScopeContainer: boolean) { + function bindDeclaration(node: Declaration, symbolKind: SymbolFlags, symbolExcludes: SymbolFlags, isBlockScopeContainer: boolean, isIteration: boolean = false) { switch (container.kind) { case SyntaxKind.ModuleDeclaration: declareModuleMember(node, symbolKind, symbolExcludes); @@ -306,7 +314,7 @@ module ts { declareSymbol(container.symbol.exports, container.symbol, node, symbolKind, symbolExcludes); break; } - bindChildren(node, symbolKind, isBlockScopeContainer); + bindChildren(node, symbolKind, isBlockScopeContainer, isIteration); } function isAmbientContext(node: Node): boolean { @@ -411,6 +419,9 @@ module ts { } declareSymbol(blockScopeContainer.locals, undefined, node, symbolKind, symbolExcludes); } + if (iteration) { + iteration.iterationScopedDeclarations.push(node); + } bindChildren(node, symbolKind, /*isBlockScopeContainer*/ false); } @@ -584,10 +595,14 @@ module ts { // 'let x' will be placed into the function locals and 'let x' - into the locals of the block bindChildren(node, 0, /*isBlockScopeContainer*/ !isFunctionLike(node.parent)); break; - case SyntaxKind.CatchClause: case SyntaxKind.ForStatement: case SyntaxKind.ForInStatement: case SyntaxKind.ForOfStatement: + case SyntaxKind.WhileStatement: + case SyntaxKind.DoStatement: + bindChildren(node, 0, /*isBlockScopeContainer*/ true, /*isIteration*/ true); + break; + case SyntaxKind.CatchClause: case SyntaxKind.CaseBlock: bindChildren(node, 0, /*isBlockScopeContainer*/ true); break; diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9bd0d6475fe75..5d7c09e294774 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5535,7 +5535,7 @@ module ts { while (current && !nodeStartsNewLexicalEnvironment(current)) { if (isIterationStatement(current, /*lookInLabeledStatements*/ false)) { if (inFunction) { - grammarErrorOnFirstToken(current, Diagnostics.Loop_contains_block_scoped_variable_0_referenced_by_a_function_in_the_loop_This_is_only_supported_in_ECMAScript_6_or_higher, declarationNameToString(node)); + symbol.valueDeclaration.blockScopedBindingInLoop = true; } // mark value declaration so during emit they can have a special handling getNodeLinks(symbol.valueDeclaration).flags |= NodeCheckFlags.BlockScopedBindingInLoop; diff --git a/src/compiler/diagnosticInformationMap.generated.ts b/src/compiler/diagnosticInformationMap.generated.ts index 2a8527168464a..5af2bbbab5f22 100644 --- a/src/compiler/diagnosticInformationMap.generated.ts +++ b/src/compiler/diagnosticInformationMap.generated.ts @@ -435,7 +435,6 @@ module ts { Parameter_0_of_exported_function_has_or_is_using_private_name_1: { code: 4078, category: DiagnosticCategory.Error, key: "Parameter '{0}' of exported function has or is using private name '{1}'." }, Exported_type_alias_0_has_or_is_using_private_name_1: { code: 4081, category: DiagnosticCategory.Error, key: "Exported type alias '{0}' has or is using private name '{1}'." }, Default_export_of_the_module_has_or_is_using_private_name_0: { code: 4082, category: DiagnosticCategory.Error, key: "Default export of the module has or is using private name '{0}'." }, - Loop_contains_block_scoped_variable_0_referenced_by_a_function_in_the_loop_This_is_only_supported_in_ECMAScript_6_or_higher: { code: 4091, category: DiagnosticCategory.Error, key: "Loop contains block-scoped variable '{0}' referenced by a function in the loop. This is only supported in ECMAScript 6 or higher." }, The_current_host_does_not_support_the_0_option: { code: 5001, category: DiagnosticCategory.Error, key: "The current host does not support the '{0}' option." }, Cannot_find_the_common_subdirectory_path_for_the_input_files: { code: 5009, category: DiagnosticCategory.Error, key: "Cannot find the common subdirectory path for the input files." }, Cannot_read_file_0_Colon_1: { code: 5012, category: DiagnosticCategory.Error, key: "Cannot read file '{0}': {1}" }, diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index cf1645b140724..abff7b7256e79 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1729,10 +1729,6 @@ "category": "Error", "code": 4082 }, - "Loop contains block-scoped variable '{0}' referenced by a function in the loop. This is only supported in ECMAScript 6 or higher.": { - "category": "Error", - "code": 4091 - }, "The current host does not support the '{0}' option.": { "category": "Error", "code": 5001 diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 32ebeaec08f4b..eff83758587e7 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -2205,16 +2205,80 @@ var __param = (this && this.__param) || function (paramIndex, decorator) { scopeEmitEnd(); } - function emitEmbeddedStatement(node: Node) { + function emitEmbeddedStatement(node: Node, block?: boolean) { if (node.kind === SyntaxKind.Block) { write(" "); emit(node); } else { increaseIndent(); + if (block) write(" {"); writeLine(); emit(node); decreaseIndent(); + if (block) { + writeLine(); + write("}"); + } + } + } + + function emitIteration(node: T, headDeclarations: Declaration[], emitHead: (node: T) => void, emitBody?: (node: T, downlevel: boolean) => void, emitFooter?: (node: T, downlevel: boolean) => void) { + let needsDownlevelEmit = false; + const closureVariables: Identifier[] = []; + + if (languageVersion < ScriptTarget.ES6) { + for (const declaration of node.iterationScopedDeclarations) { + if (declaration.blockScopedBindingInLoop) { + needsDownlevelEmit = true; + if (headDeclarations.indexOf(declaration) !== -1) { + closureVariables.push( declaration.name); + } + } + } + } + + if (needsDownlevelEmit) { + let tempVariable = createTempVariable(TempFlags.Auto); + write("var "); + emit(tempVariable); + write(" = "); + write("function("); + emitList(closureVariables, 0, closureVariables.length, false, false); + write(")"); + + if (emitBody) { + emitBody(node, true); + } else { + emitEmbeddedStatement(node.statement, true); + } + + write(";"); + writeLine(); + + emitHead(node); + write(" "); + emit(tempVariable); + write("("); + emitList(closureVariables, 0, closureVariables.length, false, false); + write(");"); + + if (emitFooter) { + emitFooter(node, true); + } + } else { + emitHead(node); + + if (emitBody) { + emitBody(node, false); + } else { + emitEmbeddedStatement(node.statement); + } + + if (emitFooter) { + emitFooter(node, false); + } + return; } } @@ -2243,10 +2307,11 @@ var __param = (this && this.__param) || function (paramIndex, decorator) { } } - function emitDoStatement(node: DoStatement) { + function emitDoStatementHead(node: DoStatement) { write("do"); - emitEmbeddedStatement(node.statement); - if (node.statement.kind === SyntaxKind.Block) { + } + function emitDoStatementFooter(node: DoStatement, downlevel: boolean) { + if (node.statement.kind === SyntaxKind.Block || downlevel) { write(" "); } else { @@ -2256,12 +2321,17 @@ var __param = (this && this.__param) || function (paramIndex, decorator) { emit(node.expression); write(");"); } + function emitDoStatement(node: DoStatement) { + emitIteration(node, [], emitDoStatementHead, undefined, emitDoStatementFooter) + } - function emitWhileStatement(node: WhileStatement) { + function emitWhileStatementHead(node: WhileStatement) { write("while ("); emit(node.expression); write(")"); - emitEmbeddedStatement(node.statement); + } + function emitWhileStatement(node: WhileStatement) { + emitIteration(node, [], emitWhileStatementHead); } /** @@ -2326,7 +2396,8 @@ var __param = (this && this.__param) || function (paramIndex, decorator) { return started; } - function emitForStatement(node: ForStatement) { + function emitForStatementHead(node: ForStatement) { + let variables: Declaration[] = []; let endPos = emitToken(SyntaxKind.ForKeyword, node.pos); write(" "); endPos = emitToken(SyntaxKind.OpenParenToken, endPos); @@ -2339,6 +2410,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) { else { emitVariableDeclarationListSkippingUninitializedEntries(variableDeclarationList); } + variables = variableDeclarationList.declarations; } else if (node.initializer) { emit(node.initializer); @@ -2348,14 +2420,17 @@ var __param = (this && this.__param) || function (paramIndex, decorator) { write(";"); emitOptional(" ", node.incrementor); write(")"); - emitEmbeddedStatement(node.statement); } - - function emitForInOrForOfStatement(node: ForInStatement | ForOfStatement) { - if (languageVersion < ScriptTarget.ES6 && node.kind === SyntaxKind.ForOfStatement) { - return emitDownLevelForOfStatement(node); + function emitForStatement(node: ForStatement) { + let variables: Declaration[] = []; + if (node.initializer && node.initializer.kind === SyntaxKind.VariableDeclarationList) { + variables = (node.initializer).declarations; } + + emitIteration(node, variables, emitForStatementHead); + } + function emitForInOrForOfStatementHead(node: ForInStatement | ForOfStatement) { let endPos = emitToken(SyntaxKind.ForKeyword, node.pos); write(" "); endPos = emitToken(SyntaxKind.OpenParenToken, endPos); @@ -2378,10 +2453,22 @@ var __param = (this && this.__param) || function (paramIndex, decorator) { } emit(node.expression); emitToken(SyntaxKind.CloseParenToken, node.expression.end); - emitEmbeddedStatement(node.statement); } + function emitForInOrForOfStatement(node: ForInStatement | ForOfStatement) { + if (languageVersion < ScriptTarget.ES6 && node.kind === SyntaxKind.ForOfStatement) { + return emitDownLevelForOfStatement(node); + } + + let variables: Declaration[] = []; - function emitDownLevelForOfStatement(node: ForOfStatement) { + if (node.initializer.kind === SyntaxKind.VariableDeclarationList) { + variables = (node.initializer).declarations; + } + + emitIteration(node, variables, emitForInOrForOfStatementHead); + } + + function emitDownLevelForOfStatementHead(node: ForOfStatement) { // The following ES6 code: // // for (let v of expr) { } @@ -2510,7 +2597,14 @@ var __param = (this && this.__param) || function (paramIndex, decorator) { } emitEnd(node.initializer); write(";"); - + } + + function emitDownLevelForOfStatementBody(node: ForOfStatement, isDownLevelLoopFunction: boolean) { + if (isDownLevelLoopFunction) { + emitEmbeddedStatement(node.statement, true); + return; + } + if (node.statement.kind === SyntaxKind.Block) { emitLines((node.statement).statements); } @@ -2518,11 +2612,21 @@ var __param = (this && this.__param) || function (paramIndex, decorator) { writeLine(); emit(node.statement); } - + } + + function emitDownLevelForOfStatementFooter(node: ForOfStatement) { writeLine(); decreaseIndent(); write("}"); } + + function emitDownLevelForOfStatement(node: ForOfStatement) { + let variables: Declaration[] = []; + if (node.initializer.kind === SyntaxKind.VariableDeclarationList) { + variables = ( node.initializer).declarations; + } + emitIteration(node, variables, emitDownLevelForOfStatementHead, emitDownLevelForOfStatementBody, emitDownLevelForOfStatementFooter); + } function emitBreakOrContinueStatement(node: BreakOrContinueStatement) { emitToken(node.kind === SyntaxKind.BreakStatement ? SyntaxKind.BreakKeyword : SyntaxKind.ContinueKeyword, node.pos); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 86e680ca8b047..bd75e2b8cfd55 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -407,6 +407,7 @@ module ts { export interface Declaration extends Node { _declarationBrand: any; name?: DeclarationName; + blockScopedBindingInLoop?: boolean; } export interface ComputedPropertyName extends Node { @@ -784,6 +785,8 @@ module ts { export interface IterationStatement extends Statement { statement: Statement; + // Block scoped declarations that are declared in this loop, but not in a subloop. + iterationScopedDeclarations: Declaration[]; } export interface DoStatement extends IterationStatement { diff --git a/tests/baselines/reference/downlevelLetConst18.errors.txt b/tests/baselines/reference/downlevelLetConst18.errors.txt index 8f30b0f802a60..066c75abeb11e 100644 --- a/tests/baselines/reference/downlevelLetConst18.errors.txt +++ b/tests/baselines/reference/downlevelLetConst18.errors.txt @@ -1,60 +1,39 @@ -tests/cases/compiler/downlevelLetConst18.ts(3,1): error TS4091: Loop contains block-scoped variable 'x' referenced by a function in the loop. This is only supported in ECMAScript 6 or higher. tests/cases/compiler/downlevelLetConst18.ts(4,14): error TS2393: Duplicate function implementation. -tests/cases/compiler/downlevelLetConst18.ts(7,1): error TS4091: Loop contains block-scoped variable 'x' referenced by a function in the loop. This is only supported in ECMAScript 6 or higher. tests/cases/compiler/downlevelLetConst18.ts(8,14): error TS2393: Duplicate function implementation. -tests/cases/compiler/downlevelLetConst18.ts(11,1): error TS4091: Loop contains block-scoped variable 'x' referenced by a function in the loop. This is only supported in ECMAScript 6 or higher. -tests/cases/compiler/downlevelLetConst18.ts(15,1): error TS4091: Loop contains block-scoped variable 'x' referenced by a function in the loop. This is only supported in ECMAScript 6 or higher. -tests/cases/compiler/downlevelLetConst18.ts(19,1): error TS4091: Loop contains block-scoped variable 'x' referenced by a function in the loop. This is only supported in ECMAScript 6 or higher. -tests/cases/compiler/downlevelLetConst18.ts(23,1): error TS4091: Loop contains block-scoped variable 'x' referenced by a function in the loop. This is only supported in ECMAScript 6 or higher. -tests/cases/compiler/downlevelLetConst18.ts(27,1): error TS4091: Loop contains block-scoped variable 'x' referenced by a function in the loop. This is only supported in ECMAScript 6 or higher. -==== tests/cases/compiler/downlevelLetConst18.ts (9 errors) ==== +==== tests/cases/compiler/downlevelLetConst18.ts (2 errors) ==== 'use strict' for (let x; ;) { - ~~~ -!!! error TS4091: Loop contains block-scoped variable 'x' referenced by a function in the loop. This is only supported in ECMAScript 6 or higher. function foo() { x }; ~~~ !!! error TS2393: Duplicate function implementation. } for (let x; ;) { - ~~~ -!!! error TS4091: Loop contains block-scoped variable 'x' referenced by a function in the loop. This is only supported in ECMAScript 6 or higher. function foo() { x }; ~~~ !!! error TS2393: Duplicate function implementation. } for (let x; ;) { - ~~~ -!!! error TS4091: Loop contains block-scoped variable 'x' referenced by a function in the loop. This is only supported in ECMAScript 6 or higher. (() => { x })(); } for (const x = 1; ;) { - ~~~ -!!! error TS4091: Loop contains block-scoped variable 'x' referenced by a function in the loop. This is only supported in ECMAScript 6 or higher. (() => { x })(); } for (let x; ;) { - ~~~ -!!! error TS4091: Loop contains block-scoped variable 'x' referenced by a function in the loop. This is only supported in ECMAScript 6 or higher. ({ foo() { x }}) } for (let x; ;) { - ~~~ -!!! error TS4091: Loop contains block-scoped variable 'x' referenced by a function in the loop. This is only supported in ECMAScript 6 or higher. ({ get foo() { return x } }) } for (let x; ;) { - ~~~ -!!! error TS4091: Loop contains block-scoped variable 'x' referenced by a function in the loop. This is only supported in ECMAScript 6 or higher. ({ set foo(v) { x } }) } \ No newline at end of file diff --git a/tests/baselines/reference/downlevelLetConst18.js b/tests/baselines/reference/downlevelLetConst18.js index 70b8cb9e34653..fec851a7e0a71 100644 --- a/tests/baselines/reference/downlevelLetConst18.js +++ b/tests/baselines/reference/downlevelLetConst18.js @@ -32,26 +32,33 @@ for (let x; ;) { //// [downlevelLetConst18.js] 'use strict'; -for (var x = void 0;;) { +var _a = function(x) { function foo() { x; } ; -} -for (var x = void 0;;) { +}; +for (var x = void 0;;) _a(x); +var _b = function(x) { function foo() { x; } ; -} -for (var x = void 0;;) { +}; +for (var x = void 0;;) _b(x); +var _c = function(x) { (function () { x; })(); -} -for (var x = 1;;) { +}; +for (var x = void 0;;) _c(x); +var _d = function(x) { (function () { x; })(); -} -for (var x = void 0;;) { +}; +for (var x = 1;;) _d(x); +var _e = function(x) { ({ foo: function () { x; } }); -} -for (var x = void 0;;) { +}; +for (var x = void 0;;) _e(x); +var _f = function(x) { ({ get foo() { return x; } }); -} -for (var x = void 0;;) { +}; +for (var x = void 0;;) _f(x); +var _g = function(x) { ({ set foo(v) { x; } }); -} +}; +for (var x = void 0;;) _g(x);