propagate back assignments to block scoped binding from the loop body

This commit is contained in:
Vladimir Matveev
2016-02-03 23:48:24 -08:00
parent 2fde7ab8fe
commit dbcfe110f1
31 changed files with 2081 additions and 20 deletions

View File

@@ -7264,6 +7264,15 @@ namespace ts {
// mark iteration statement as containing block-scoped binding captured in some function
getNodeLinks(current).flags |= NodeCheckFlags.LoopWithCapturedBlockScopedBinding;
}
// mark variables that are declared in loop initializer and reassigned inside the body of ForStatement.
// if body of ForStatement will be converted to function then we'll need a extra machinery to propagate reassigned values back.
if (container.kind === SyntaxKind.ForStatement &&
getAncestor(symbol.valueDeclaration, SyntaxKind.VariableDeclarationList).parent === container &&
isAssignedInBodyOfForStatement(node, <ForStatement>container)) {
getNodeLinks(symbol.valueDeclaration).flags |= NodeCheckFlags.NeedsLoopOutParameter;
}
// set 'declared inside loop' bit on the block-scoped binding
getNodeLinks(symbol.valueDeclaration).flags |= NodeCheckFlags.BlockScopedBindingInLoop;
}
@@ -7273,6 +7282,41 @@ namespace ts {
}
}
function isAssignedInBodyOfForStatement(node: Identifier, container: ForStatement): boolean {
let current: Node = node;
// skip parenthesized nodes
while (current.parent.kind === SyntaxKind.ParenthesizedExpression) {
current = current.parent;
}
// check if node is used as LHS in some assignment expression
let isAssigned = false;
if (current.parent.kind === SyntaxKind.BinaryExpression) {
isAssigned = (<BinaryExpression>current.parent).left === current && isAssignmentOperator((<BinaryExpression>current.parent).operatorToken.kind);
}
if ((current.parent.kind === SyntaxKind.PrefixUnaryExpression || current.parent.kind === SyntaxKind.PostfixUnaryExpression)) {
const expr = <PrefixUnaryExpression | PostfixUnaryExpression>current.parent;
isAssigned = expr.operator === SyntaxKind.PlusPlusToken || expr.operator === SyntaxKind.MinusMinusToken;
}
if (!isAssigned) {
return false;
}
// at this point we know that node is the target of assignment
// now check that modification happens inside the statement part of the ForStatement
while (current !== container) {
if (current === container.statement) {
return true;
}
else {
current = current.parent;
}
}
return false;
}
function captureLexicalThis(node: Node, container: Node): void {
getNodeLinks(node).flags |= NodeCheckFlags.LexicalThis;
if (container.kind === SyntaxKind.PropertyDeclaration || container.kind === SyntaxKind.Constructor) {

View File

@@ -287,6 +287,54 @@ namespace ts {
_i = 0x10000000, // Use/preference flag for '_i'
}
const enum CopyDirection {
ToOriginal,
ToOutParameter
}
/**
* 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: string;
}
// targetSourceFile is when users only want one file in entire project to be emitted. This is used in compileOnSave feature
export function emitFiles(resolver: EmitResolver, host: EmitHost, targetSourceFile: SourceFile): EmitResult {
// emit output for the __extends helper function
@@ -419,6 +467,11 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
* for (var x;;) loop(x);
*/
hoistedLocalVariables?: Identifier[];
/**
* List of loop out parameters - detailed descripion can be found in the comment to LoopOutParameter
*/
loopOutParameters?: LoopOutParameter[];
}
function setLabeledJump(state: ConvertedLoopState, isBreak: boolean, labelText: string, labelMarker: string): void {
@@ -2968,11 +3021,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
}
let loopParameters: string[];
let loopOutParameters: LoopOutParameter[];
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);
processVariableDeclaration(varDeclaration.name);
}
}
@@ -2982,14 +3036,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
writeLine();
write(`var ${functionName} = function(${paramList})`);
if (!bodyIsBlock) {
write(" {");
writeLine();
increaseIndent();
}
const convertedOuterLoopState = convertedLoopState;
convertedLoopState = {};
convertedLoopState = { loopOutParameters };
if (convertedOuterLoopState) {
// convertedOuterLoopState !== undefined means that this converted loop is nested in another converted loop.
@@ -3013,16 +3061,38 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
}
}
emitEmbeddedStatement(node.statement);
write(" {");
writeLine();
increaseIndent();
if (!bodyIsBlock) {
decreaseIndent();
writeLine();
write("}");
if (bodyIsBlock) {
emitLines((<Block>node.statement).statements);
}
write(";");
else {
emit(node.statement);
}
writeLine();
// end of loop body -> copy out parameter
copyLoopOutParameters(convertedLoopState, CopyDirection.ToOutParameter, /*emitAsStatements*/true);
decreaseIndent();
writeLine();
write("};");
writeLine();
if (loopOutParameters) {
// declare variables to hold out params for loop body
write(`var `);
for (let i = 0; i < loopOutParameters.length; i++) {
if (i !== 0) {
write(", ");
}
write(loopOutParameters[i].outParamName);
}
write(";");
writeLine();
}
if (convertedLoopState.argumentsName) {
// if alias for arguments is set
if (convertedOuterLoopState) {
@@ -3086,14 +3156,21 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
return { functionName, paramList, state: currentLoopState };
function collectNames(name: Identifier | BindingPattern): void {
function processVariableDeclaration(name: Identifier | BindingPattern): void {
if (name.kind === SyntaxKind.Identifier) {
const nameText = isNameOfNestedBlockScopedRedeclarationOrCapturedBinding(<Identifier>name) ? getGeneratedNameForNode(name) : (<Identifier>name).text;
const nameText = isNameOfNestedBlockScopedRedeclarationOrCapturedBinding(<Identifier>name)
? getGeneratedNameForNode(name)
: (<Identifier>name).text;
loopParameters.push(nameText);
if (resolver.getNodeCheckFlags(name.parent) & NodeCheckFlags.NeedsLoopOutParameter) {
const reassignedVariable = { originalName: <Identifier>name, outParamName: makeUniqueName(`out_${nameText}`) };
(loopOutParameters || (loopOutParameters = [])).push(reassignedVariable);
}
}
else {
for (const element of (<BindingPattern>name).elements) {
collectNames(element.name);
processVariableDeclaration(element.name);
}
}
}
@@ -3124,6 +3201,28 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
}
}
function copyLoopOutParameters(state: ConvertedLoopState, copyDirection: CopyDirection, emitAsStatements: boolean) {
if (state.loopOutParameters) {
for (const outParam of state.loopOutParameters) {
if (copyDirection === CopyDirection.ToOriginal) {
emitIdentifier(outParam.originalName);
write(` = ${outParam.outParamName}`);
}
else {
write(`${outParam.outParamName} = `);
emitIdentifier(outParam.originalName);
}
if (emitAsStatements) {
write(";");
writeLine();
}
else {
write(", ");
}
}
}
}
function emitConvertedLoopCall(loop: ConvertedLoop, emitAsBlock: boolean): void {
if (emitAsBlock) {
write(" {");
@@ -3138,12 +3237,18 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
!loop.state.labeledNonLocalBreaks &&
!loop.state.labeledNonLocalContinues;
copyLoopOutParameters(loop.state, CopyDirection.ToOutParameter, /*emitAsStatements*/ true);
writeLine();
const loopResult = makeUniqueName("state");
if (!isSimpleLoop) {
write(`var ${loopResult} = `);
}
write(`${loop.functionName}(${loop.paramList});`);
writeLine();
copyLoopOutParameters(loop.state, CopyDirection.ToOriginal, /*emitAsStatements*/ true);
if (!isSimpleLoop) {
// for non simple loops we need to store result returned from converted loop function and use it to do dispatching
@@ -3463,14 +3568,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
(!node.label && (convertedLoopState.allowedNonLabeledJumps & jump));
if (!canUseBreakOrContinue) {
write ("return ");
// explicit exit from loop -> copy out parameters
copyLoopOutParameters(convertedLoopState, CopyDirection.ToOutParameter, /*emitAsStatements*/ false);
if (!node.label) {
if (node.kind === SyntaxKind.BreakStatement) {
convertedLoopState.nonLocalJumps |= Jump.Break;
write(`return "break";`);
write(`"break";`);
}
else {
convertedLoopState.nonLocalJumps |= Jump.Continue;
write(`return "continue";`);
write(`"continue";`);
}
}
else {
@@ -3483,7 +3591,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
labelMarker = `continue-${node.label.text}`;
setLabeledJump(convertedLoopState, /*isBreak*/ false, node.label.text, labelMarker);
}
write(`return "${labelMarker}";`);
write(`"${labelMarker}";`);
}
return;

View File

@@ -2044,6 +2044,7 @@ namespace ts {
HasSeenSuperCall = 0x00080000, // Set during the binding when encounter 'super'
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
}
/* @internal */