@@ -287,6 +287,54 @@ namespace ts {
287287 _i = 0x10000000 , // Use/preference flag for '_i'
288288 }
289289
290+ const enum CopyDirection {
291+ ToOriginal ,
292+ ToOutParameter
293+ }
294+
295+ /**
296+ * If loop contains block scoped binding captured in some function then loop body is converted to a function.
297+ * Lexical bindings declared in loop initializer will be passed into the loop body function as parameters,
298+ * however if this binding is modified inside the body - this new value should be propagated back to the original binding.
299+ * This is done by declaring new variable (out parameter holder) outside of the loop for every binding that is reassigned inside the body.
300+ * On every iteration this variable is initialized with value of corresponding binding.
301+ * At every point where control flow leaves the loop either explicitly (break/continue) or implicitly (at the end of loop body)
302+ * we copy the value inside the loop to the out parameter holder.
303+ *
304+ * for (let x;;) {
305+ * let a = 1;
306+ * let b = () => a;
307+ * x++
308+ * if (...) break;
309+ * ...
310+ * }
311+ *
312+ * will be converted to
313+ *
314+ * var out_x;
315+ * var loop = function(x) {
316+ * var a = 1;
317+ * var b = function() { return a; }
318+ * x++;
319+ * if (...) return out_x = x, "break";
320+ * ...
321+ * out_x = x;
322+ * }
323+ * for (var x;;) {
324+ * out_x = x;
325+ * var state = loop(x);
326+ * x = out_x;
327+ * if (state === "break") break;
328+ * }
329+ *
330+ * NOTE: values to out parameters are not copies if loop is abrupted with 'return' - in this case this will end the entire enclosing function
331+ * so nobody can observe this new value.
332+ */
333+ interface LoopOutParameter {
334+ originalName : Identifier ;
335+ outParamName : string ;
336+ }
337+
290338 // targetSourceFile is when users only want one file in entire project to be emitted. This is used in compileOnSave feature
291339 export function emitFiles ( resolver : EmitResolver , host : EmitHost , targetSourceFile : SourceFile ) : EmitResult {
292340 // emit output for the __extends helper function
@@ -419,6 +467,11 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
419467 * for (var x;;) loop(x);
420468 */
421469 hoistedLocalVariables ?: Identifier [ ] ;
470+
471+ /**
472+ * List of loop out parameters - detailed descripion can be found in the comment to LoopOutParameter
473+ */
474+ loopOutParameters ?: LoopOutParameter [ ] ;
422475 }
423476
424477 function setLabeledJump ( state : ConvertedLoopState , isBreak : boolean , labelText : string , labelMarker : string ) : void {
@@ -2944,11 +2997,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
29442997 }
29452998
29462999 let loopParameters : string [ ] ;
3000+ let loopOutParameters : LoopOutParameter [ ] ;
29473001 if ( loopInitializer && ( getCombinedNodeFlags ( loopInitializer ) & NodeFlags . BlockScoped ) ) {
29483002 // if loop initializer contains block scoped variables - they should be passed to converted loop body as parameters
29493003 loopParameters = [ ] ;
29503004 for ( const varDeclaration of loopInitializer . declarations ) {
2951- collectNames ( varDeclaration . name ) ;
3005+ processVariableDeclaration ( varDeclaration . name ) ;
29523006 }
29533007 }
29543008
@@ -2958,14 +3012,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
29583012 writeLine ( ) ;
29593013 write ( `var ${ functionName } = function(${ paramList } )` ) ;
29603014
2961- if ( ! bodyIsBlock ) {
2962- write ( " {" ) ;
2963- writeLine ( ) ;
2964- increaseIndent ( ) ;
2965- }
2966-
29673015 const convertedOuterLoopState = convertedLoopState ;
2968- convertedLoopState = { } ;
3016+ convertedLoopState = { loopOutParameters } ;
29693017
29703018 if ( convertedOuterLoopState ) {
29713019 // convertedOuterLoopState !== undefined means that this converted loop is nested in another converted loop.
@@ -2989,16 +3037,38 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
29893037 }
29903038 }
29913039
2992- emitEmbeddedStatement ( node . statement ) ;
3040+ write ( " {" ) ;
3041+ writeLine ( ) ;
3042+ increaseIndent ( ) ;
29933043
2994- if ( ! bodyIsBlock ) {
2995- decreaseIndent ( ) ;
2996- writeLine ( ) ;
2997- write ( "}" ) ;
3044+ if ( bodyIsBlock ) {
3045+ emitLines ( ( < Block > node . statement ) . statements ) ;
29983046 }
2999- write ( ";" ) ;
3047+ else {
3048+ emit ( node . statement ) ;
3049+ }
3050+
3051+ writeLine ( ) ;
3052+ // end of loop body -> copy out parameter
3053+ copyLoopOutParameters ( convertedLoopState , CopyDirection . ToOutParameter , /*emitAsStatements*/ true ) ;
3054+
3055+ decreaseIndent ( ) ;
3056+ writeLine ( ) ;
3057+ write ( "};" ) ;
30003058 writeLine ( ) ;
30013059
3060+ if ( loopOutParameters ) {
3061+ // declare variables to hold out params for loop body
3062+ write ( `var ` ) ;
3063+ for ( let i = 0 ; i < loopOutParameters . length ; i ++ ) {
3064+ if ( i !== 0 ) {
3065+ write ( ", " ) ;
3066+ }
3067+ write ( loopOutParameters [ i ] . outParamName ) ;
3068+ }
3069+ write ( ";" ) ;
3070+ writeLine ( ) ;
3071+ }
30023072 if ( convertedLoopState . argumentsName ) {
30033073 // if alias for arguments is set
30043074 if ( convertedOuterLoopState ) {
@@ -3062,14 +3132,21 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
30623132
30633133 return { functionName, paramList, state : currentLoopState } ;
30643134
3065- function collectNames ( name : Identifier | BindingPattern ) : void {
3135+ function processVariableDeclaration ( name : Identifier | BindingPattern ) : void {
30663136 if ( name . kind === SyntaxKind . Identifier ) {
3067- const nameText = isNameOfNestedBlockScopedRedeclarationOrCapturedBinding ( < Identifier > name ) ? getGeneratedNameForNode ( name ) : ( < Identifier > name ) . text ;
3137+ const nameText = isNameOfNestedBlockScopedRedeclarationOrCapturedBinding ( < Identifier > name )
3138+ ? getGeneratedNameForNode ( name )
3139+ : ( < Identifier > name ) . text ;
3140+
30683141 loopParameters . push ( nameText ) ;
3142+ if ( resolver . getNodeCheckFlags ( name . parent ) & NodeCheckFlags . NeedsLoopOutParameter ) {
3143+ const reassignedVariable = { originalName : < Identifier > name , outParamName : makeUniqueName ( `out_${ nameText } ` ) } ;
3144+ ( loopOutParameters || ( loopOutParameters = [ ] ) ) . push ( reassignedVariable ) ;
3145+ }
30693146 }
30703147 else {
30713148 for ( const element of ( < BindingPattern > name ) . elements ) {
3072- collectNames ( element . name ) ;
3149+ processVariableDeclaration ( element . name ) ;
30733150 }
30743151 }
30753152 }
@@ -3100,6 +3177,28 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
31003177 }
31013178 }
31023179
3180+ function copyLoopOutParameters ( state : ConvertedLoopState , copyDirection : CopyDirection , emitAsStatements : boolean ) {
3181+ if ( state . loopOutParameters ) {
3182+ for ( const outParam of state . loopOutParameters ) {
3183+ if ( copyDirection === CopyDirection . ToOriginal ) {
3184+ emitIdentifier ( outParam . originalName ) ;
3185+ write ( ` = ${ outParam . outParamName } ` ) ;
3186+ }
3187+ else {
3188+ write ( `${ outParam . outParamName } = ` ) ;
3189+ emitIdentifier ( outParam . originalName ) ;
3190+ }
3191+ if ( emitAsStatements ) {
3192+ write ( ";" ) ;
3193+ writeLine ( ) ;
3194+ }
3195+ else {
3196+ write ( ", " ) ;
3197+ }
3198+ }
3199+ }
3200+ }
3201+
31033202 function emitConvertedLoopCall ( loop : ConvertedLoop , emitAsBlock : boolean ) : void {
31043203 if ( emitAsBlock ) {
31053204 write ( " {" ) ;
@@ -3120,6 +3219,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
31203219 }
31213220
31223221 write ( `${ loop . functionName } (${ loop . paramList } );` ) ;
3222+ writeLine ( ) ;
3223+
3224+ copyLoopOutParameters ( loop . state , CopyDirection . ToOriginal , /*emitAsStatements*/ true ) ;
31233225
31243226 if ( ! isSimpleLoop ) {
31253227 // for non simple loops we need to store result returned from converted loop function and use it to do dispatching
@@ -3138,7 +3240,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
31383240 }
31393241 else {
31403242 // top level converted loop - return unwrapped value
3141- write ( `return ${ loopResult } .value` ) ;
3243+ write ( `return ${ loopResult } .value; ` ) ;
31423244 }
31433245 writeLine ( ) ;
31443246 }
@@ -3439,14 +3541,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
34393541 ( ! node . label && ( convertedLoopState . allowedNonLabeledJumps & jump ) ) ;
34403542
34413543 if ( ! canUseBreakOrContinue ) {
3544+ write ( "return " ) ;
3545+ // explicit exit from loop -> copy out parameters
3546+ copyLoopOutParameters ( convertedLoopState , CopyDirection . ToOutParameter , /*emitAsStatements*/ false ) ;
34423547 if ( ! node . label ) {
34433548 if ( node . kind === SyntaxKind . BreakStatement ) {
34443549 convertedLoopState . nonLocalJumps |= Jump . Break ;
3445- write ( `return "break";` ) ;
3550+ write ( `"break";` ) ;
34463551 }
34473552 else {
34483553 convertedLoopState . nonLocalJumps |= Jump . Continue ;
3449- write ( `return "continue";` ) ;
3554+ write ( `"continue";` ) ;
34503555 }
34513556 }
34523557 else {
@@ -3459,7 +3564,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
34593564 labelMarker = `continue-${ node . label . text } ` ;
34603565 setLabeledJump ( convertedLoopState , /*isBreak*/ false , node . label . text , labelMarker ) ;
34613566 }
3462- write ( `return "${ labelMarker } ";` ) ;
3567+ write ( `"${ labelMarker } ";` ) ;
34633568 }
34643569
34653570 return ;
0 commit comments