mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Merge pull request #5208 from Microsoft/capturedBlockScopedVars
support block scoped vars captured in closures inside loops
This commit is contained in:
+7
-17
@@ -6609,7 +6609,7 @@ namespace 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));
|
||||
getNodeLinks(current).flags |= NodeCheckFlags.LoopWithBlockScopedBindingCapturedInFunction;
|
||||
}
|
||||
// mark value declaration so during emit they can have a special handling
|
||||
getNodeLinks(<VariableDeclaration>symbol.valueDeclaration).flags |= NodeCheckFlags.BlockScopedBindingInLoop;
|
||||
@@ -14580,6 +14580,10 @@ namespace ts {
|
||||
|
||||
// Emitter support
|
||||
|
||||
function isArgumentsLocalBinding(node: Identifier): boolean {
|
||||
return getReferencedValueSymbol(node) === argumentsSymbol;
|
||||
}
|
||||
|
||||
// When resolved as an expression identifier, if the given node references an exported entity, return the declaration
|
||||
// node of the exported entity's container. Otherwise, return undefined.
|
||||
function getReferencedExportContainer(node: Identifier): SourceFile | ModuleDeclaration | EnumDeclaration {
|
||||
@@ -14884,7 +14888,8 @@ namespace ts {
|
||||
collectLinkedAliases,
|
||||
getReferencedValueDeclaration,
|
||||
getTypeReferenceSerializationKind,
|
||||
isOptionalParameter
|
||||
isOptionalParameter,
|
||||
isArgumentsLocalBinding
|
||||
};
|
||||
}
|
||||
|
||||
@@ -15711,21 +15716,6 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
function isIterationStatement(node: Node, lookInLabeledStatements: boolean): boolean {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.ForStatement:
|
||||
case SyntaxKind.ForInStatement:
|
||||
case SyntaxKind.ForOfStatement:
|
||||
case SyntaxKind.DoStatement:
|
||||
case SyntaxKind.WhileStatement:
|
||||
return true;
|
||||
case SyntaxKind.LabeledStatement:
|
||||
return lookInLabeledStatements && isIterationStatement((<LabeledStatement>node).statement, lookInLabeledStatements);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function checkGrammarBreakOrContinueStatement(node: BreakOrContinueStatement): boolean {
|
||||
let current: Node = node;
|
||||
while (current) {
|
||||
|
||||
@@ -2000,10 +2000,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
|
||||
|
||||
+563
-13
@@ -379,6 +379,105 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
|
||||
return true;
|
||||
}
|
||||
|
||||
const enum Jump {
|
||||
Break = 1 << 1,
|
||||
Continue = 1 << 2,
|
||||
Return = 1 << 3
|
||||
}
|
||||
|
||||
interface ConvertedLoopState {
|
||||
/*
|
||||
* set of labels that occured inside the converted loop
|
||||
* used to determine if labeled jump can be emitted as is or it should be dispatched to calling code
|
||||
*/
|
||||
labels?: Map<string>;
|
||||
/*
|
||||
* collection of labeled jumps that transfer control outside the converted loop.
|
||||
* maps store association 'label -> labelMarker' where
|
||||
* - label - value of label as it apprear in code
|
||||
* - label marker - return value that should be interpreted by calling code as 'jump to <label>'
|
||||
*/
|
||||
labeledNonLocalBreaks?: Map<string>;
|
||||
labeledNonLocalContinues?: Map<string>;
|
||||
|
||||
/*
|
||||
* set of non-labeled jumps that transfer control outside the converted loop
|
||||
* used to emit dispatching logic in the caller of converted loop
|
||||
*/
|
||||
nonLocalJumps?: Jump;
|
||||
|
||||
/*
|
||||
* set of non-labeled jumps that should be interpreted as local
|
||||
* i.e. if converted loop contains normal loop or switch statement then inside this loop break should be treated as local jump
|
||||
*/
|
||||
allowedNonLabeledJumps?: Jump;
|
||||
|
||||
/*
|
||||
* alias for 'arguments' object from the calling code stack frame
|
||||
* i.e.
|
||||
* for (let x;;) <statement that captures x in closure and uses 'arguments'>
|
||||
* should be converted to
|
||||
* var loop = function(x) { <code where 'arguments' is replaced witg 'arguments_1'> }
|
||||
* var arguments_1 = arguments
|
||||
* for (var x;;) loop(x);
|
||||
* otherwise semantics of the code will be different since 'arguments' inside converted loop body
|
||||
* will refer to function that holds converted loop.
|
||||
* This value is set on demand.
|
||||
*/
|
||||
argumentsName?: string;
|
||||
|
||||
/*
|
||||
* list of non-block scoped variable declarations that appear inside converted loop
|
||||
* such variable declarations should be moved outside the loop body
|
||||
* for (let x;;) {
|
||||
* var y = 1;
|
||||
* ...
|
||||
* }
|
||||
* should be converted to
|
||||
* var loop = function(x) {
|
||||
* y = 1;
|
||||
* ...
|
||||
* }
|
||||
* var y;
|
||||
* for (var x;;) loop(x);
|
||||
*/
|
||||
hoistedLocalVariables?: Identifier[];
|
||||
}
|
||||
|
||||
function setLabeledJump(state: ConvertedLoopState, isBreak: boolean, labelText: string, labelMarker: string): void {
|
||||
if (isBreak) {
|
||||
if (!state.labeledNonLocalBreaks) {
|
||||
state.labeledNonLocalBreaks = {};
|
||||
}
|
||||
state.labeledNonLocalBreaks[labelText] = labelMarker;
|
||||
}
|
||||
else {
|
||||
if (!state.labeledNonLocalContinues) {
|
||||
state.labeledNonLocalContinues = {};
|
||||
}
|
||||
state.labeledNonLocalContinues[labelText] = labelMarker;
|
||||
}
|
||||
}
|
||||
|
||||
function hoistVariableDeclarationFromLoop(state: ConvertedLoopState, declaration: VariableDeclaration): void {
|
||||
if (!state.hoistedLocalVariables) {
|
||||
state.hoistedLocalVariables = [];
|
||||
}
|
||||
|
||||
visit(declaration.name);
|
||||
|
||||
function visit(node: Identifier | BindingPattern) {
|
||||
if (node.kind === SyntaxKind.Identifier) {
|
||||
state.hoistedLocalVariables.push((<Identifier>node));
|
||||
}
|
||||
else {
|
||||
for (const element of (<BindingPattern>node).elements) {
|
||||
visit(element.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function emitJavaScript(jsFilePath: string, root?: SourceFile) {
|
||||
let writer = createTextWriter(newLine);
|
||||
let { write, writeTextOfNode, writeLine, increaseIndent, decreaseIndent } = writer;
|
||||
@@ -396,6 +495,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
|
||||
let nodeToGeneratedName: string[] = [];
|
||||
let computedPropertyNamesToGeneratedNames: string[];
|
||||
|
||||
let convertedLoopState: ConvertedLoopState;
|
||||
|
||||
let extendsEmitted = false;
|
||||
let decorateEmitted = false;
|
||||
let paramEmitted = false;
|
||||
@@ -1828,6 +1929,15 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
|
||||
}
|
||||
|
||||
function emitIdentifier(node: Identifier) {
|
||||
if (convertedLoopState) {
|
||||
if (node.text == "arguments" && resolver.isArgumentsLocalBinding(node)) {
|
||||
// in converted loop body arguments cannot be used directly.
|
||||
let name = convertedLoopState.argumentsName || (convertedLoopState.argumentsName = makeUniqueName("arguments"));
|
||||
write(name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!node.parent) {
|
||||
write(node.text);
|
||||
}
|
||||
@@ -2996,8 +3106,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
|
||||
}
|
||||
|
||||
function emitDoStatement(node: DoStatement) {
|
||||
emitLoop(node, emitDoStatementWorker);
|
||||
}
|
||||
|
||||
function emitDoStatementWorker(node: DoStatement, loop: ConvertedLoop) {
|
||||
write("do");
|
||||
emitEmbeddedStatement(node.statement);
|
||||
if (loop) {
|
||||
emitConvertedLoopCall(loop, /* emitAsBlock */ true);
|
||||
}
|
||||
else {
|
||||
emitNormalLoopBody(node, /* emitAsEmbeddedStatement */ true);
|
||||
}
|
||||
if (node.statement.kind === SyntaxKind.Block) {
|
||||
write(" ");
|
||||
}
|
||||
@@ -3010,10 +3129,20 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
|
||||
}
|
||||
|
||||
function emitWhileStatement(node: WhileStatement) {
|
||||
emitLoop(node, emitWhileStatementWorker);
|
||||
}
|
||||
|
||||
function emitWhileStatementWorker(node: WhileStatement, loop: ConvertedLoop) {
|
||||
write("while (");
|
||||
emit(node.expression);
|
||||
write(")");
|
||||
emitEmbeddedStatement(node.statement);
|
||||
|
||||
if (loop) {
|
||||
emitConvertedLoopCall(loop, /* emitAsBlock */ true);
|
||||
}
|
||||
else {
|
||||
emitNormalLoopBody(node, /* emitAsEmbeddedStatement */ true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3027,6 +3156,15 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
|
||||
return false;
|
||||
}
|
||||
|
||||
if (convertedLoopState && (getCombinedNodeFlags(decl) & NodeFlags.BlockScoped) === 0) {
|
||||
// we are inside a converted loop - this can only happen in downlevel scenarios
|
||||
// record names for all variable declarations
|
||||
for (const varDecl of decl.declarations) {
|
||||
hoistVariableDeclarationFromLoop(convertedLoopState, varDecl);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
let tokenKind = SyntaxKind.VarKeyword;
|
||||
if (decl && languageVersion >= ScriptTarget.ES6) {
|
||||
if (isLet(decl)) {
|
||||
@@ -3078,7 +3216,295 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
|
||||
return started;
|
||||
}
|
||||
|
||||
interface ConvertedLoop {
|
||||
functionName: string;
|
||||
paramList: string;
|
||||
state: ConvertedLoopState;
|
||||
}
|
||||
|
||||
function shouldConvertLoopBody(node: IterationStatement): boolean {
|
||||
return languageVersion < ScriptTarget.ES6 &&
|
||||
(resolver.getNodeCheckFlags(node) & NodeCheckFlags.LoopWithBlockScopedBindingCapturedInFunction) !== 0;
|
||||
}
|
||||
|
||||
function emitLoop(node: IterationStatement, loopEmitter: (n: IterationStatement, convertedLoop: ConvertedLoop) => void): void {
|
||||
const shouldConvert = shouldConvertLoopBody(node);
|
||||
if (!shouldConvert) {
|
||||
loopEmitter(node, /* convertedLoop*/ undefined);
|
||||
}
|
||||
else {
|
||||
const loop = convertLoopBody(node);
|
||||
if (node.parent.kind === SyntaxKind.LabeledStatement) {
|
||||
// if parent of the loop was labeled statement - attach the label to loop skipping converted loop body
|
||||
emitLabelAndColon(<LabeledStatement>node.parent);
|
||||
}
|
||||
loopEmitter(node, loop);
|
||||
}
|
||||
}
|
||||
|
||||
function convertLoopBody(node: IterationStatement): ConvertedLoop {
|
||||
const functionName = makeUniqueName("_loop");
|
||||
|
||||
let loopInitializer: VariableDeclarationList;
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.ForStatement:
|
||||
case SyntaxKind.ForInStatement:
|
||||
case SyntaxKind.ForOfStatement:
|
||||
if ((<ForStatement | ForInStatement | ForOfStatement>node).initializer.kind === SyntaxKind.VariableDeclarationList) {
|
||||
loopInitializer = <VariableDeclarationList>(<ForStatement | ForInStatement | ForOfStatement>node).initializer;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
let loopParameters: string[];
|
||||
if (loopInitializer && (getCombinedNodeFlags(loopInitializer) & NodeFlags.BlockScoped)) {
|
||||
// if loop initializer contains block scoped variables - they should be passed to converted loop body as parameters
|
||||
loopParameters = [];
|
||||
for (const varDeclaration of loopInitializer.declarations) {
|
||||
collectNames(varDeclaration.name);
|
||||
}
|
||||
}
|
||||
|
||||
const bodyIsBlock = node.statement.kind === SyntaxKind.Block;
|
||||
const paramList = loopParameters ? loopParameters.join(", ") : "";
|
||||
|
||||
writeLine();
|
||||
write(`var ${functionName} = function(${paramList})`);
|
||||
|
||||
if (!bodyIsBlock) {
|
||||
write(" {");
|
||||
writeLine();
|
||||
increaseIndent();
|
||||
}
|
||||
|
||||
const convertedOuterLoopState = convertedLoopState;
|
||||
convertedLoopState = {};
|
||||
|
||||
if (convertedOuterLoopState) {
|
||||
// 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 (convertedOuterLoopState.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 = convertedOuterLoopState.argumentsName;
|
||||
}
|
||||
|
||||
if (convertedOuterLoopState.hoistedLocalVariables) {
|
||||
// we've already collected some non-block scoped variable declarations in enclosing loop
|
||||
// use the same storage in nested loop
|
||||
convertedLoopState.hoistedLocalVariables = convertedOuterLoopState.hoistedLocalVariables;
|
||||
}
|
||||
}
|
||||
|
||||
emitEmbeddedStatement(node.statement);
|
||||
|
||||
if (!bodyIsBlock) {
|
||||
decreaseIndent();
|
||||
writeLine();
|
||||
write("}");
|
||||
}
|
||||
write(";");
|
||||
writeLine();
|
||||
|
||||
if (convertedLoopState.argumentsName) {
|
||||
// if alias for arguments is set
|
||||
if (convertedOuterLoopState) {
|
||||
// pass it to outer converted loop
|
||||
convertedOuterLoopState.argumentsName = convertedLoopState.argumentsName;
|
||||
}
|
||||
else {
|
||||
// this is top level converted loop and we need to create an alias for 'arguments' object
|
||||
write(`var ${convertedLoopState.argumentsName} = arguments;`);
|
||||
writeLine();
|
||||
}
|
||||
}
|
||||
|
||||
if (convertedLoopState.hoistedLocalVariables) {
|
||||
// if hoistedLocalVariables !== undefined this means that we've possibly collected some variable declarations to be hoisted later
|
||||
if (convertedOuterLoopState) {
|
||||
// pass them to outer converted loop
|
||||
convertedOuterLoopState.hoistedLocalVariables = convertedLoopState.hoistedLocalVariables;
|
||||
}
|
||||
else {
|
||||
// deduplicate and hoist collected variable declarations
|
||||
write("var ");
|
||||
let seen: Map<string>;
|
||||
for (const id of convertedLoopState.hoistedLocalVariables) {
|
||||
// Don't initialize seen unless we have at least one element.
|
||||
// Emit a comma to separate for all but the first element.
|
||||
if (!seen) {
|
||||
seen = {};
|
||||
}
|
||||
else {
|
||||
write(", ");
|
||||
}
|
||||
|
||||
if (!hasProperty(seen, id.text)) {
|
||||
emit(id);
|
||||
seen[id.text] = id.text;
|
||||
}
|
||||
}
|
||||
write(";");
|
||||
writeLine();
|
||||
}
|
||||
}
|
||||
|
||||
const currentLoopState = convertedLoopState;
|
||||
convertedLoopState = convertedOuterLoopState;
|
||||
|
||||
return { functionName, paramList, state: currentLoopState };
|
||||
|
||||
function collectNames(name: Identifier | BindingPattern): void {
|
||||
if (name.kind === SyntaxKind.Identifier) {
|
||||
const nameText = isNameOfNestedRedeclaration(<Identifier>name) ? getGeneratedNameForNode(name) : (<Identifier>name).text;
|
||||
loopParameters.push(nameText);
|
||||
}
|
||||
else {
|
||||
for (const element of (<BindingPattern>name).elements) {
|
||||
collectNames(element.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function emitNormalLoopBody(node: IterationStatement, emitAsEmbeddedStatement: boolean): void {
|
||||
let saveAllowedNonLabeledJumps: Jump;
|
||||
if (convertedLoopState) {
|
||||
// we get here if we are trying to emit normal loop loop inside converted loop
|
||||
// set allowedNonLabeledJumps to Break | Continue to mark that break\continue inside the loop should be emitted as is
|
||||
saveAllowedNonLabeledJumps = convertedLoopState.allowedNonLabeledJumps;
|
||||
convertedLoopState.allowedNonLabeledJumps = Jump.Break | Jump.Continue;
|
||||
}
|
||||
|
||||
if (emitAsEmbeddedStatement) {
|
||||
emitEmbeddedStatement(node.statement);
|
||||
}
|
||||
else if (node.statement.kind === SyntaxKind.Block) {
|
||||
emitLines((<Block>node.statement).statements);
|
||||
}
|
||||
else {
|
||||
writeLine();
|
||||
emit(node.statement);
|
||||
}
|
||||
|
||||
if (convertedLoopState) {
|
||||
convertedLoopState.allowedNonLabeledJumps = saveAllowedNonLabeledJumps;
|
||||
}
|
||||
}
|
||||
|
||||
function emitConvertedLoopCall(loop: ConvertedLoop, emitAsBlock: boolean): void {
|
||||
if (emitAsBlock) {
|
||||
write(" {");
|
||||
writeLine();
|
||||
increaseIndent();
|
||||
}
|
||||
|
||||
// loop is considered simple if it does not have any return statements or break\continue that transfer control outside of the loop
|
||||
// simple loops are emitted as just 'loop()';
|
||||
const isSimpleLoop =
|
||||
!loop.state.nonLocalJumps &&
|
||||
!loop.state.labeledNonLocalBreaks &&
|
||||
!loop.state.labeledNonLocalContinues;
|
||||
|
||||
const loopResult = makeUniqueName("state");
|
||||
if (!isSimpleLoop) {
|
||||
write(`var ${loopResult} = `);
|
||||
}
|
||||
|
||||
write(`${loop.functionName}(${loop.paramList});`);
|
||||
|
||||
if (!isSimpleLoop) {
|
||||
// for non simple loops we need to store result returned from converted loop function and use it to do dispatching
|
||||
// converted loop function can return:
|
||||
// - object - used when body of the converted loop contains return statement. Property "value" of this object stores retuned value
|
||||
// - string - used to dispatch jumps. "break" and "continue" are used to non-labeled jumps, other values are used to transfer control to
|
||||
// different labels
|
||||
writeLine();
|
||||
if (loop.state.nonLocalJumps & Jump.Return) {
|
||||
write(`if (typeof ${loopResult} === "object") `);
|
||||
if (convertedLoopState) {
|
||||
// we are currently nested in another converted loop - return unwrapped result
|
||||
write(`return ${loopResult};`);
|
||||
// propagate 'hasReturn' flag to outer loop
|
||||
convertedLoopState.nonLocalJumps |= Jump.Return;
|
||||
}
|
||||
else {
|
||||
// top level converted loop - return unwrapped value
|
||||
write(`return ${loopResult}.value`);
|
||||
}
|
||||
writeLine();
|
||||
}
|
||||
|
||||
if (loop.state.nonLocalJumps & Jump.Break) {
|
||||
write(`if (${loopResult} === "break") break;`);
|
||||
writeLine();
|
||||
}
|
||||
|
||||
if (loop.state.nonLocalJumps & Jump.Continue) {
|
||||
write(`if (${loopResult} === "continue") continue;`);
|
||||
writeLine();
|
||||
}
|
||||
|
||||
// in case of labeled breaks emit code that either breaks to some known label inside outer loop or delegates jump decision to outer loop
|
||||
emitDispatchTableForLabeledJumps(loopResult, loop.state, convertedLoopState);
|
||||
}
|
||||
|
||||
if (emitAsBlock) {
|
||||
writeLine();
|
||||
decreaseIndent();
|
||||
write("}");
|
||||
}
|
||||
|
||||
function emitDispatchTableForLabeledJumps(loopResultVariable: string, currentLoop: ConvertedLoopState, outerLoop: ConvertedLoopState) {
|
||||
if (!currentLoop.labeledNonLocalBreaks && !currentLoop.labeledNonLocalContinues) {
|
||||
return;
|
||||
}
|
||||
|
||||
write(`switch(${loopResultVariable}) {`);
|
||||
increaseIndent();
|
||||
|
||||
emitDispatchEntriesForLabeledJumps(currentLoop.labeledNonLocalBreaks, /* isBreak */ true, loopResultVariable, outerLoop);
|
||||
emitDispatchEntriesForLabeledJumps(currentLoop.labeledNonLocalContinues, /* isBreak */ false, loopResultVariable, outerLoop);
|
||||
|
||||
decreaseIndent();
|
||||
writeLine();
|
||||
write("}");
|
||||
}
|
||||
|
||||
function emitDispatchEntriesForLabeledJumps(table: Map<string>, isBreak: boolean, loopResultVariable: string, outerLoop: ConvertedLoopState): void {
|
||||
if (!table) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let labelText in table) {
|
||||
let labelMarker = table[labelText];
|
||||
writeLine();
|
||||
write(`case "${labelMarker}": `);
|
||||
// if there are no outer converted loop or outer label in question is located inside outer converted loop
|
||||
// then emit labeled break\continue
|
||||
// otherwise propagate pair 'label -> marker' to outer converted loop and emit 'return labelMarker' so outer loop can later decide what to do
|
||||
if (!outerLoop || (outerLoop.labels && outerLoop.labels[labelText])) {
|
||||
if (isBreak) {
|
||||
write("break ");
|
||||
}
|
||||
else {
|
||||
write("continue ");
|
||||
}
|
||||
write(`${labelText};`);
|
||||
}
|
||||
else {
|
||||
setLabeledJump(outerLoop, isBreak, labelText, labelMarker);
|
||||
write(`return ${loopResultVariable};`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function emitForStatement(node: ForStatement) {
|
||||
emitLoop(node, emitForStatementWorker);
|
||||
}
|
||||
|
||||
function emitForStatementWorker(node: ForStatement, loop: ConvertedLoop) {
|
||||
let endPos = emitToken(SyntaxKind.ForKeyword, node.pos);
|
||||
write(" ");
|
||||
endPos = emitToken(SyntaxKind.OpenParenToken, endPos);
|
||||
@@ -3100,14 +3526,25 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
|
||||
write(";");
|
||||
emitOptional(" ", node.incrementor);
|
||||
write(")");
|
||||
emitEmbeddedStatement(node.statement);
|
||||
|
||||
if (loop) {
|
||||
emitConvertedLoopCall(loop, /* emitAsBlock */ true);
|
||||
}
|
||||
else {
|
||||
emitNormalLoopBody(node, /* emitAsEmbeddedStatement */ true);
|
||||
}
|
||||
}
|
||||
|
||||
function emitForInOrForOfStatement(node: ForInStatement | ForOfStatement) {
|
||||
if (languageVersion < ScriptTarget.ES6 && node.kind === SyntaxKind.ForOfStatement) {
|
||||
return emitDownLevelForOfStatement(node);
|
||||
emitLoop(node, emitDownLevelForOfStatementWorker);
|
||||
}
|
||||
else {
|
||||
emitLoop(node, emitForInOrForOfStatementWorker);
|
||||
}
|
||||
}
|
||||
|
||||
function emitForInOrForOfStatementWorker(node: ForInStatement | ForOfStatement, loop: ConvertedLoop) {
|
||||
let endPos = emitToken(SyntaxKind.ForKeyword, node.pos);
|
||||
write(" ");
|
||||
endPos = emitToken(SyntaxKind.OpenParenToken, endPos);
|
||||
@@ -3130,10 +3567,20 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
|
||||
}
|
||||
emit(node.expression);
|
||||
emitToken(SyntaxKind.CloseParenToken, node.expression.end);
|
||||
emitEmbeddedStatement(node.statement);
|
||||
|
||||
if (loop) {
|
||||
emitConvertedLoopCall(loop, /* emitAsBlock */ true);
|
||||
}
|
||||
else {
|
||||
emitNormalLoopBody(node, /* emitAsEmbeddedStatement */ true);
|
||||
}
|
||||
}
|
||||
|
||||
function emitDownLevelForOfStatement(node: ForOfStatement) {
|
||||
emitLoop(node, emitDownLevelForOfStatementWorker);
|
||||
}
|
||||
|
||||
function emitDownLevelForOfStatementWorker(node: ForOfStatement, loop: ConvertedLoop) {
|
||||
// The following ES6 code:
|
||||
//
|
||||
// for (let v of expr) { }
|
||||
@@ -3263,12 +3710,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
|
||||
emitEnd(node.initializer);
|
||||
write(";");
|
||||
|
||||
if (node.statement.kind === SyntaxKind.Block) {
|
||||
emitLines((<Block>node.statement).statements);
|
||||
if (loop) {
|
||||
writeLine();
|
||||
emitConvertedLoopCall(loop, /* emitAsBlock */ false);
|
||||
}
|
||||
else {
|
||||
writeLine();
|
||||
emit(node.statement);
|
||||
emitNormalLoopBody(node, /* emitAsEmbeddedStatement */ false);
|
||||
}
|
||||
|
||||
writeLine();
|
||||
@@ -3277,12 +3724,63 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
|
||||
}
|
||||
|
||||
function emitBreakOrContinueStatement(node: BreakOrContinueStatement) {
|
||||
if (convertedLoopState) {
|
||||
// check if we can emit break\continue as is
|
||||
// it is possible if either
|
||||
// - break\continue is statement labeled and label is located inside the converted loop
|
||||
// - break\continue is non-labeled and located in non-converted loop\switch statement
|
||||
const jump = node.kind === SyntaxKind.BreakStatement ? Jump.Break : Jump.Continue;
|
||||
const canUseBreakOrContinue =
|
||||
(node.label && convertedLoopState.labels && convertedLoopState.labels[node.label.text]) ||
|
||||
(!node.label && (convertedLoopState.allowedNonLabeledJumps & jump));
|
||||
|
||||
if (!canUseBreakOrContinue) {
|
||||
if (!node.label) {
|
||||
if (node.kind === SyntaxKind.BreakStatement) {
|
||||
convertedLoopState.nonLocalJumps |= Jump.Break;
|
||||
write(`return "break";`);
|
||||
}
|
||||
else {
|
||||
convertedLoopState.nonLocalJumps |= Jump.Continue;
|
||||
write(`return "continue";`);
|
||||
}
|
||||
}
|
||||
else {
|
||||
let labelMarker: string;
|
||||
if (node.kind === SyntaxKind.BreakStatement) {
|
||||
labelMarker = `break-${node.label.text}`;
|
||||
setLabeledJump(convertedLoopState, /* isBreak */ true, node.label.text, labelMarker);
|
||||
}
|
||||
else {
|
||||
labelMarker = `continue-${node.label.text}`;
|
||||
setLabeledJump(convertedLoopState, /* isBreak */ false, node.label.text, labelMarker);
|
||||
}
|
||||
write(`return "${labelMarker}";`);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
emitToken(node.kind === SyntaxKind.BreakStatement ? SyntaxKind.BreakKeyword : SyntaxKind.ContinueKeyword, node.pos);
|
||||
emitOptional(" ", node.label);
|
||||
write(";");
|
||||
}
|
||||
|
||||
function emitReturnStatement(node: ReturnStatement) {
|
||||
if (convertedLoopState) {
|
||||
convertedLoopState.nonLocalJumps |= Jump.Return;
|
||||
write("return { value: ");
|
||||
if (node.expression) {
|
||||
emit(node.expression);
|
||||
}
|
||||
else {
|
||||
write("void 0");
|
||||
}
|
||||
write(" };");
|
||||
return;
|
||||
}
|
||||
|
||||
emitToken(SyntaxKind.ReturnKeyword, node.pos);
|
||||
emitOptional(" ", node.expression);
|
||||
write(";");
|
||||
@@ -3302,7 +3800,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
|
||||
emit(node.expression);
|
||||
endPos = emitToken(SyntaxKind.CloseParenToken, node.expression.end);
|
||||
write(" ");
|
||||
|
||||
let saveAllowedNonLabeledJumps: Jump;
|
||||
if (convertedLoopState) {
|
||||
saveAllowedNonLabeledJumps = convertedLoopState.allowedNonLabeledJumps;
|
||||
// for switch statement allow only non-labeled break
|
||||
convertedLoopState.allowedNonLabeledJumps |= Jump.Break;
|
||||
}
|
||||
emitCaseBlock(node.caseBlock, endPos);
|
||||
if (convertedLoopState) {
|
||||
convertedLoopState.allowedNonLabeledJumps = saveAllowedNonLabeledJumps;
|
||||
}
|
||||
}
|
||||
|
||||
function emitCaseBlock(node: CaseBlock, startPos: number): void {
|
||||
@@ -3383,10 +3891,28 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
|
||||
write(";");
|
||||
}
|
||||
|
||||
function emitLabelledStatement(node: LabeledStatement) {
|
||||
function emitLabelAndColon(node: LabeledStatement): void {
|
||||
emit(node.label);
|
||||
write(": ");
|
||||
}
|
||||
|
||||
function emitLabeledStatement(node: LabeledStatement) {
|
||||
if (!isIterationStatement(node.statement, /* lookInLabeledStatements */ false) || !shouldConvertLoopBody(<IterationStatement>node.statement)) {
|
||||
emitLabelAndColon(node);
|
||||
}
|
||||
|
||||
if (convertedLoopState) {
|
||||
if (!convertedLoopState.labels) {
|
||||
convertedLoopState.labels = {};
|
||||
}
|
||||
convertedLoopState.labels[node.label.text] = node.label.text;
|
||||
}
|
||||
|
||||
emit(node.statement);
|
||||
|
||||
if (convertedLoopState) {
|
||||
convertedLoopState.labels[node.label.text] = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function getContainingModule(node: Node): ModuleDeclaration {
|
||||
@@ -3820,12 +4346,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
|
||||
// for (...) { var <some-uniqie-name> = void 0; }
|
||||
// this is necessary to preserve ES6 semantic in scenarios like
|
||||
// for (...) { let x; console.log(x); x = 1 } // assignment on one iteration should not affect other iterations
|
||||
let isUninitializedLet =
|
||||
let isLetDefinedInLoop =
|
||||
(resolver.getNodeCheckFlags(node) & NodeCheckFlags.BlockScopedBindingInLoop) &&
|
||||
(getCombinedFlagsForIdentifier(<Identifier>node.name) & NodeFlags.Let);
|
||||
|
||||
// NOTE: default initialization should not be added to let bindings in for-in\for-of statements
|
||||
if (isUninitializedLet &&
|
||||
if (isLetDefinedInLoop &&
|
||||
node.parent.parent.kind !== SyntaxKind.ForInStatement &&
|
||||
node.parent.parent.kind !== SyntaxKind.ForOfStatement) {
|
||||
initializer = createVoidZero();
|
||||
@@ -4308,9 +4834,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
|
||||
}
|
||||
|
||||
function emitSignatureAndBody(node: FunctionLikeDeclaration) {
|
||||
const saveConvertedLoopState = convertedLoopState;
|
||||
let saveTempFlags = tempFlags;
|
||||
let saveTempVariables = tempVariables;
|
||||
let saveTempParameters = tempParameters;
|
||||
|
||||
convertedLoopState = undefined;
|
||||
tempFlags = 0;
|
||||
tempVariables = undefined;
|
||||
tempParameters = undefined;
|
||||
@@ -4336,6 +4865,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
|
||||
emitExportMemberAssignment(node);
|
||||
}
|
||||
|
||||
Debug.assert(convertedLoopState === undefined);
|
||||
convertedLoopState = saveConvertedLoopState;
|
||||
|
||||
tempFlags = saveTempFlags;
|
||||
tempVariables = saveTempVariables;
|
||||
tempParameters = saveTempParameters;
|
||||
@@ -4657,15 +5189,21 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
|
||||
}
|
||||
|
||||
function emitConstructor(node: ClassLikeDeclaration, baseTypeElement: ExpressionWithTypeArguments) {
|
||||
const saveConvertedLoopState = convertedLoopState;
|
||||
let saveTempFlags = tempFlags;
|
||||
let saveTempVariables = tempVariables;
|
||||
let saveTempParameters = tempParameters;
|
||||
|
||||
convertedLoopState = undefined;
|
||||
tempFlags = 0;
|
||||
tempVariables = undefined;
|
||||
tempParameters = undefined;
|
||||
|
||||
emitConstructorWorker(node, baseTypeElement);
|
||||
|
||||
Debug.assert(convertedLoopState === undefined);
|
||||
convertedLoopState = saveConvertedLoopState;
|
||||
|
||||
tempFlags = saveTempFlags;
|
||||
tempVariables = saveTempVariables;
|
||||
tempParameters = saveTempParameters;
|
||||
@@ -5003,6 +5541,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
|
||||
let saveTempVariables = tempVariables;
|
||||
let saveTempParameters = tempParameters;
|
||||
let saveComputedPropertyNamesToGeneratedNames = computedPropertyNamesToGeneratedNames;
|
||||
let saveConvertedLoopState = convertedLoopState;
|
||||
|
||||
convertedLoopState = undefined;
|
||||
tempFlags = 0;
|
||||
tempVariables = undefined;
|
||||
tempParameters = undefined;
|
||||
@@ -5030,6 +5571,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
|
||||
});
|
||||
write(";");
|
||||
emitTempDeclarations(/*newLine*/ true);
|
||||
|
||||
Debug.assert(convertedLoopState === undefined);
|
||||
convertedLoopState = saveConvertedLoopState;
|
||||
|
||||
tempFlags = saveTempFlags;
|
||||
tempVariables = saveTempVariables;
|
||||
tempParameters = saveTempParameters;
|
||||
@@ -5711,13 +6256,18 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
|
||||
emitEnd(node.name);
|
||||
write(") ");
|
||||
if (node.body.kind === SyntaxKind.ModuleBlock) {
|
||||
const saveConvertedLoopState = convertedLoopState;
|
||||
let saveTempFlags = tempFlags;
|
||||
let saveTempVariables = tempVariables;
|
||||
convertedLoopState = undefined;
|
||||
tempFlags = 0;
|
||||
tempVariables = undefined;
|
||||
|
||||
emit(node.body);
|
||||
|
||||
Debug.assert(convertedLoopState === undefined);
|
||||
convertedLoopState = saveConvertedLoopState;
|
||||
|
||||
tempFlags = saveTempFlags;
|
||||
tempVariables = saveTempVariables;
|
||||
}
|
||||
@@ -7344,7 +7894,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
|
||||
case SyntaxKind.DefaultClause:
|
||||
return emitCaseOrDefaultClause(<CaseOrDefaultClause>node);
|
||||
case SyntaxKind.LabeledStatement:
|
||||
return emitLabelledStatement(<LabeledStatement>node);
|
||||
return emitLabeledStatement(<LabeledStatement>node);
|
||||
case SyntaxKind.ThrowStatement:
|
||||
return emitThrowStatement(<ThrowStatement>node);
|
||||
case SyntaxKind.TryStatement:
|
||||
|
||||
@@ -1612,6 +1612,7 @@ namespace ts {
|
||||
getReferencedValueDeclaration(reference: Identifier): Declaration;
|
||||
getTypeReferenceSerializationKind(typeName: EntityName): TypeReferenceSerializationKind;
|
||||
isOptionalParameter(node: ParameterDeclaration): boolean;
|
||||
isArgumentsLocalBinding(node: Identifier): boolean;
|
||||
}
|
||||
|
||||
export const enum SymbolFlags {
|
||||
@@ -1756,7 +1757,8 @@ namespace ts {
|
||||
// Values for enum members have been computed, and any errors have been reported for them.
|
||||
EnumValuesComputed = 0x00002000,
|
||||
BlockScopedBindingInLoop = 0x00004000,
|
||||
LexicalModuleMergesWithClass= 0x00008000, // Instantiated lexical module declaration is merged with a previous class declaration.
|
||||
LexicalModuleMergesWithClass = 0x00008000, // Instantiated lexical module declaration is merged with a previous class declaration.
|
||||
LoopWithBlockScopedBindingCapturedInFunction = 0x00010000, // Loop that contains block scoped variable captured in closure
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
|
||||
@@ -657,6 +657,22 @@ namespace ts {
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isIterationStatement(node: Node, lookInLabeledStatements: boolean): boolean {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.ForStatement:
|
||||
case SyntaxKind.ForInStatement:
|
||||
case SyntaxKind.ForOfStatement:
|
||||
case SyntaxKind.DoStatement:
|
||||
case SyntaxKind.WhileStatement:
|
||||
return true;
|
||||
case SyntaxKind.LabeledStatement:
|
||||
return lookInLabeledStatements && isIterationStatement((<LabeledStatement>node).statement, lookInLabeledStatements);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
export function isFunctionBlock(node: Node) {
|
||||
return node && node.kind === SyntaxKind.Block && isFunctionLike(node.parent);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user