From 564e134f5eb33f0b0245280654a86691bc89c218 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Fri, 25 Mar 2016 17:52:50 -0700 Subject: [PATCH 1/5] added support for captured block scoped bindings --- src/compiler/factory.ts | 36 +- src/compiler/printer.ts | 10 +- src/compiler/transformers/destructuring.ts | 2 +- src/compiler/transformers/es6.ts | 777 ++++++++++++++++++++- src/compiler/transformers/module/system.ts | 58 +- src/compiler/utilities.ts | 2 +- src/compiler/visitor.ts | 4 +- 7 files changed, 852 insertions(+), 37 deletions(-) diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 695b999e7bf12..7e4195565f63b 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -482,6 +482,19 @@ namespace ts { return node; } + export function createSwitch(expression: Expression, caseBlock: CaseBlock, location?: TextRange): SwitchStatement { + const node = createNode(SyntaxKind.SwitchStatement, location); + node.expression = parenthesizeExpressionForList(expression); + node.caseBlock = caseBlock; + return node; + } + + export function createCaseBlock(clauses: CaseClause[], location?: TextRange): CaseBlock { + const node = createNode(SyntaxKind.CaseBlock, location); + node.clauses = createNodeArray(clauses); + return node; + } + export function createFor(initializer: ForInitializer, condition: Expression, incrementor: Expression, statement: Statement, location?: TextRange) { const node = createNode(SyntaxKind.ForStatement, location); node.initializer = initializer; @@ -687,6 +700,22 @@ namespace ts { ); } + export function createBreak(label?: Identifier, location?: TextRange): BreakStatement { + const node = createNode(SyntaxKind.BreakStatement, location); + if (label) { + node.label = label; + } + return node; + } + + export function createContinue(label?: Identifier, location?: TextRange): BreakStatement { + const node = createNode(SyntaxKind.ContinueStatement, location); + if (label) { + node.label = label; + } + return node; + } + export function createFunctionApply(func: Expression, thisArg: Expression, argumentsExpression: Expression, location?: TextRange) { return createCall( createPropertyAccess(func, "apply"), @@ -1349,8 +1378,11 @@ namespace ts { return clone; } } - else if (getLeftmostExpression(expression).kind === SyntaxKind.ObjectLiteralExpression) { - return createParen(expression, /*location*/ expression); + else { + const leftmostExpressionKind = getLeftmostExpression(expression).kind; + if (leftmostExpressionKind === SyntaxKind.ObjectLiteralExpression || leftmostExpressionKind === SyntaxKind.FunctionExpression) { + return createParen(expression, /*location*/ expression); + } } return expression; diff --git a/src/compiler/printer.ts b/src/compiler/printer.ts index 194d668686cf5..b55b302a9cec0 100644 --- a/src/compiler/printer.ts +++ b/src/compiler/printer.ts @@ -1751,7 +1751,15 @@ const _super = (function (geti, seti) { } function emitCaseOrDefaultClauseStatements(parentNode: Node, statements: NodeArray) { - if (statements.length === 1 && rangeStartPositionsAreOnSameLine(parentNode, statements[0], currentSourceFile)) { + const emitAsSingleStatement = + statements.length === 1 && + ( + // treat synthesized nodes as located on the same line for emit purposes + nodeIsSynthesized(parentNode) || + nodeIsSynthesized(statements[0]) || + rangeStartPositionsAreOnSameLine(parentNode, statements[0], currentSourceFile) + ); + if (emitAsSingleStatement) { write(" "); emit(statements[0]); } diff --git a/src/compiler/transformers/destructuring.ts b/src/compiler/transformers/destructuring.ts index abe568a80f33a..107ea016692d6 100644 --- a/src/compiler/transformers/destructuring.ts +++ b/src/compiler/transformers/destructuring.ts @@ -165,7 +165,7 @@ namespace ts { const pendingAssignments: Expression[] = []; - flattenDestructuring(context, node, /*value*/ undefined, node, emitAssignment, emitTempVariableAssignment); + flattenDestructuring(context, node, /*value*/ undefined, node, emitAssignment, emitTempVariableAssignment, visitor); const expression = inlineExpressions(pendingAssignments); aggregateTransformFlags(expression); diff --git a/src/compiler/transformers/es6.ts b/src/compiler/transformers/es6.ts index 84213239371cd..b6bb23ba031a1 100644 --- a/src/compiler/transformers/es6.ts +++ b/src/compiler/transformers/es6.ts @@ -10,6 +10,133 @@ namespace ts { /** Enables substitutions for block-scoped bindings. */ BlockScopedBindings = 1 << 1, } + /** + * If loop contains block scoped binding captured in some function then loop body is converted to a function. + * Lexical bindings declared in loop initializer will be passed into the loop body function as parameters, + * however if this binding is modified inside the body - this new value should be propagated back to the original binding. + * This is done by declaring new variable (out parameter holder) outside of the loop for every binding that is reassigned inside the body. + * On every iteration this variable is initialized with value of corresponding binding. + * At every point where control flow leaves the loop either explicitly (break/continue) or implicitly (at the end of loop body) + * we copy the value inside the loop to the out parameter holder. + * + * for (let x;;) { + * let a = 1; + * let b = () => a; + * x++ + * if (...) break; + * ... + * } + * + * will be converted to + * + * var out_x; + * var loop = function(x) { + * var a = 1; + * var b = function() { return a; } + * x++; + * if (...) return out_x = x, "break"; + * ... + * out_x = x; + * } + * for (var x;;) { + * out_x = x; + * var state = loop(x); + * x = out_x; + * if (state === "break") break; + * } + * + * NOTE: values to out parameters are not copies if loop is abrupted with 'return' - in this case this will end the entire enclosing function + * so nobody can observe this new value. + */ + interface LoopOutParameter { + originalName: Identifier; + outParamName: Identifier; + } + + const enum CopyDirection { + ToOriginal, + ToOutParameter + } + + const enum Jump { + Break = 1 << 1, + Continue = 1 << 2, + Return = 1 << 3 + } + + interface ConvertedLoopState { + /* + * set of labels that occurred 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; + /* + * collection of labeled jumps that transfer control outside the converted loop. + * maps store association 'label -> labelMarker' where + * - label - value of label as it appear in code + * - label marker - return value that should be interpreted by calling code as 'jump to