@@ -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 {
@@ -2968,11 +3021,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
29683021 }
29693022
29703023 let loopParameters : string [ ] ;
3024+ let loopOutParameters : LoopOutParameter [ ] ;
29713025 if ( loopInitializer && ( getCombinedNodeFlags ( loopInitializer ) & NodeFlags . BlockScoped ) ) {
29723026 // if loop initializer contains block scoped variables - they should be passed to converted loop body as parameters
29733027 loopParameters = [ ] ;
29743028 for ( const varDeclaration of loopInitializer . declarations ) {
2975- collectNames ( varDeclaration . name ) ;
3029+ processVariableDeclaration ( varDeclaration . name ) ;
29763030 }
29773031 }
29783032
@@ -2982,14 +3036,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
29823036 writeLine ( ) ;
29833037 write ( `var ${ functionName } = function(${ paramList } )` ) ;
29843038
2985- if ( ! bodyIsBlock ) {
2986- write ( " {" ) ;
2987- writeLine ( ) ;
2988- increaseIndent ( ) ;
2989- }
2990-
29913039 const convertedOuterLoopState = convertedLoopState ;
2992- convertedLoopState = { } ;
3040+ convertedLoopState = { loopOutParameters } ;
29933041
29943042 if ( convertedOuterLoopState ) {
29953043 // 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
30133061 }
30143062 }
30153063
3016- emitEmbeddedStatement ( node . statement ) ;
3064+ write ( " {" ) ;
3065+ writeLine ( ) ;
3066+ increaseIndent ( ) ;
30173067
3018- if ( ! bodyIsBlock ) {
3019- decreaseIndent ( ) ;
3020- writeLine ( ) ;
3021- write ( "}" ) ;
3068+ if ( bodyIsBlock ) {
3069+ emitLines ( ( < Block > node . statement ) . statements ) ;
30223070 }
3023- write ( ";" ) ;
3071+ else {
3072+ emit ( node . statement ) ;
3073+ }
3074+
3075+ writeLine ( ) ;
3076+ // end of loop body -> copy out parameter
3077+ copyLoopOutParameters ( convertedLoopState , CopyDirection . ToOutParameter , /*emitAsStatements*/ true ) ;
3078+
3079+ decreaseIndent ( ) ;
3080+ writeLine ( ) ;
3081+ write ( "};" ) ;
30243082 writeLine ( ) ;
30253083
3084+ if ( loopOutParameters ) {
3085+ // declare variables to hold out params for loop body
3086+ write ( `var ` ) ;
3087+ for ( let i = 0 ; i < loopOutParameters . length ; i ++ ) {
3088+ if ( i !== 0 ) {
3089+ write ( ", " ) ;
3090+ }
3091+ write ( loopOutParameters [ i ] . outParamName ) ;
3092+ }
3093+ write ( ";" ) ;
3094+ writeLine ( ) ;
3095+ }
30263096 if ( convertedLoopState . argumentsName ) {
30273097 // if alias for arguments is set
30283098 if ( convertedOuterLoopState ) {
@@ -3086,14 +3156,21 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
30863156
30873157 return { functionName, paramList, state : currentLoopState } ;
30883158
3089- function collectNames ( name : Identifier | BindingPattern ) : void {
3159+ function processVariableDeclaration ( name : Identifier | BindingPattern ) : void {
30903160 if ( name . kind === SyntaxKind . Identifier ) {
3091- const nameText = isNameOfNestedBlockScopedRedeclarationOrCapturedBinding ( < Identifier > name ) ? getGeneratedNameForNode ( name ) : ( < Identifier > name ) . text ;
3161+ const nameText = isNameOfNestedBlockScopedRedeclarationOrCapturedBinding ( < Identifier > name )
3162+ ? getGeneratedNameForNode ( name )
3163+ : ( < Identifier > name ) . text ;
3164+
30923165 loopParameters . push ( nameText ) ;
3166+ if ( resolver . getNodeCheckFlags ( name . parent ) & NodeCheckFlags . NeedsLoopOutParameter ) {
3167+ const reassignedVariable = { originalName : < Identifier > name , outParamName : makeUniqueName ( `out_${ nameText } ` ) } ;
3168+ ( loopOutParameters || ( loopOutParameters = [ ] ) ) . push ( reassignedVariable ) ;
3169+ }
30933170 }
30943171 else {
30953172 for ( const element of ( < BindingPattern > name ) . elements ) {
3096- collectNames ( element . name ) ;
3173+ processVariableDeclaration ( element . name ) ;
30973174 }
30983175 }
30993176 }
@@ -3124,6 +3201,28 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
31243201 }
31253202 }
31263203
3204+ function copyLoopOutParameters ( state : ConvertedLoopState , copyDirection : CopyDirection , emitAsStatements : boolean ) {
3205+ if ( state . loopOutParameters ) {
3206+ for ( const outParam of state . loopOutParameters ) {
3207+ if ( copyDirection === CopyDirection . ToOriginal ) {
3208+ emitIdentifier ( outParam . originalName ) ;
3209+ write ( ` = ${ outParam . outParamName } ` ) ;
3210+ }
3211+ else {
3212+ write ( `${ outParam . outParamName } = ` ) ;
3213+ emitIdentifier ( outParam . originalName ) ;
3214+ }
3215+ if ( emitAsStatements ) {
3216+ write ( ";" ) ;
3217+ writeLine ( ) ;
3218+ }
3219+ else {
3220+ write ( ", " ) ;
3221+ }
3222+ }
3223+ }
3224+ }
3225+
31273226 function emitConvertedLoopCall ( loop : ConvertedLoop , emitAsBlock : boolean ) : void {
31283227 if ( emitAsBlock ) {
31293228 write ( " {" ) ;
@@ -3144,6 +3243,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
31443243 }
31453244
31463245 write ( `${ loop . functionName } (${ loop . paramList } );` ) ;
3246+ writeLine ( ) ;
3247+
3248+ copyLoopOutParameters ( loop . state , CopyDirection . ToOriginal , /*emitAsStatements*/ true ) ;
31473249
31483250 if ( ! isSimpleLoop ) {
31493251 // for non simple loops we need to store result returned from converted loop function and use it to do dispatching
@@ -3463,14 +3565,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
34633565 ( ! node . label && ( convertedLoopState . allowedNonLabeledJumps & jump ) ) ;
34643566
34653567 if ( ! canUseBreakOrContinue ) {
3568+ write ( "return " ) ;
3569+ // explicit exit from loop -> copy out parameters
3570+ copyLoopOutParameters ( convertedLoopState , CopyDirection . ToOutParameter , /*emitAsStatements*/ false ) ;
34663571 if ( ! node . label ) {
34673572 if ( node . kind === SyntaxKind . BreakStatement ) {
34683573 convertedLoopState . nonLocalJumps |= Jump . Break ;
3469- write ( `return "break";` ) ;
3574+ write ( `"break";` ) ;
34703575 }
34713576 else {
34723577 convertedLoopState . nonLocalJumps |= Jump . Continue ;
3473- write ( `return "continue";` ) ;
3578+ write ( `"continue";` ) ;
34743579 }
34753580 }
34763581 else {
@@ -3483,7 +3588,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
34833588 labelMarker = `continue-${ node . label . text } ` ;
34843589 setLabeledJump ( convertedLoopState , /*isBreak*/ false , node . label . text , labelMarker ) ;
34853590 }
3486- write ( `return "${ labelMarker } ";` ) ;
3591+ write ( `"${ labelMarker } ";` ) ;
34873592 }
34883593
34893594 return ;
0 commit comments