Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -227,14 +228,15 @@ 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 = {};
}

let saveParent = parent;
let saveContainer = container;
let savedBlockScopeContainer = blockScopeContainer;
let savedIteration = iteration;
parent = node;
if (symbolKind & SymbolFlags.IsContainer) {
container = node;
Expand All @@ -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 = <IterationStatement> node;
iteration.iterationScopedDeclarations = [];
}

forEachChild(node, bind);
container = saveContainer;
parent = saveParent;
blockScopeContainer = savedBlockScopeContainer;
iteration = savedIteration;
}

function addToContainerChain(node: Node) {
Expand All @@ -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);
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -411,6 +419,9 @@ module ts {
}
declareSymbol(blockScopeContainer.locals, undefined, node, symbolKind, symbolExcludes);
}
if (iteration) {
iteration.iterationScopedDeclarations.push(node);
}
bindChildren(node, symbolKind, /*isBlockScopeContainer*/ false);
}

Expand Down Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(<VariableDeclaration>symbol.valueDeclaration).flags |= NodeCheckFlags.BlockScopedBindingInLoop;
Expand Down
1 change: 0 additions & 1 deletion src/compiler/diagnosticInformationMap.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}" },
Expand Down
4 changes: 0 additions & 4 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
136 changes: 120 additions & 16 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(<Block>node);
}
else {
increaseIndent();
if (block) write(" {");
writeLine();
emit(node);
decreaseIndent();
if (block) {
writeLine();
write("}");
}
}
}

function emitIteration<T extends IterationStatement>(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(<Identifier> 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;
}
}

Expand Down Expand Up @@ -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 {
Expand All @@ -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);
}

/**
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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 = (<VariableDeclarationList>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);
Expand All @@ -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 = (<VariableDeclarationList>node.initializer).declarations;
}

emitIteration(node, variables, emitForInOrForOfStatementHead);
}

function emitDownLevelForOfStatementHead(node: ForOfStatement) {
// The following ES6 code:
//
// for (let v of expr) { }
Expand Down Expand Up @@ -2510,19 +2597,36 @@ 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((<Block>node.statement).statements);
}
else {
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 = (<VariableDeclarationList> node.initializer).declarations;
}
emitIteration(node, variables, emitDownLevelForOfStatementHead, emitDownLevelForOfStatementBody, emitDownLevelForOfStatementFooter);
}

function emitBreakOrContinueStatement(node: BreakOrContinueStatement) {
emitToken(node.kind === SyntaxKind.BreakStatement ? SyntaxKind.BreakKeyword : SyntaxKind.ContinueKeyword, node.pos);
Expand Down
3 changes: 3 additions & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,7 @@ module ts {
export interface Declaration extends Node {
_declarationBrand: any;
name?: DeclarationName;
blockScopedBindingInLoop?: boolean;
}

export interface ComputedPropertyName extends Node {
Expand Down Expand Up @@ -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 {
Expand Down
Loading