Bug 1216623 - Part 2: In `for (let ...)` loops, evaluate initializers in the scope of the variables being initialized. r=Waldo.
authorJason Orendorff <jorendorff@mozilla.com>
Tue, 20 Oct 2015 11:52:01 -0500
changeset 305158 ce0741b494a70915a451fda9378a20816319ee3c
parent 305157 02922ca741fd7f1cb3a742b9afcc15c1f5e49083
child 305159 ca648290f7de761db027f1a8465d138b1f7a654f
push id5513
push userraliiev@mozilla.com
push dateMon, 25 Jan 2016 13:55:34 +0000
treeherdermozilla-beta@5ee97dd05b5c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersWaldo
bugs1216623
milestone45.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1216623 - Part 2: In `for (let ...)` loops, evaluate initializers in the scope of the variables being initialized. r=Waldo.
js/src/builtin/ReflectParse.cpp
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/BytecodeEmitter.h
js/src/frontend/FoldConstants.cpp
js/src/frontend/FullParseHandler.h
js/src/frontend/NameFunctions.cpp
js/src/frontend/ParseNode.cpp
js/src/frontend/ParseNode.h
js/src/frontend/Parser.cpp
js/src/jit-test/tests/basic/bug646968-3.js
js/src/jit-test/tests/basic/bug646968-4.js
js/src/jit-test/tests/basic/bug646968-6.js
js/src/jit-test/tests/basic/letLegacyForOfOrInScope.js
js/src/jit-test/tests/basic/testLet.js
js/src/tests/ecma_6/LexicalEnvironment/bug-1216623.js
js/src/tests/ecma_6/LexicalEnvironment/for-loop.js
js/src/tests/js1_8_5/reflect-parse/destructuring-variable-declarations.js
--- a/js/src/builtin/ReflectParse.cpp
+++ b/js/src/builtin/ReflectParse.cpp
@@ -2486,18 +2486,19 @@ ASTSerializer::tryStatement(ParseNode* p
 bool
 ASTSerializer::forInit(ParseNode* pn, MutableHandleValue dst)
 {
     if (!pn) {
         dst.setMagic(JS_SERIALIZE_NO_NODE);
         return true;
     }
 
-    return (pn->isKind(PNK_VAR))
-           ? variableDeclaration(pn, false, dst)
+    bool lexical = pn->isKind(PNK_LET) || pn->isKind(PNK_CONST);
+    return (lexical || pn->isKind(PNK_VAR))
+           ? variableDeclaration(pn, lexical, dst)
            : expression(pn, dst);
 }
 
 bool
 ASTSerializer::forOf(ParseNode* loop, ParseNode* head, HandleValue var, HandleValue stmt,
                          MutableHandleValue dst)
 {
     RootedValue expr(cx);
@@ -2636,42 +2637,41 @@ ASTSerializer::statement(ParseNode* pn, 
         MOZ_ASSERT_IF(head->pn_kid1, head->pn_pos.encloses(head->pn_kid1->pn_pos));
         MOZ_ASSERT_IF(head->pn_kid2, head->pn_pos.encloses(head->pn_kid2->pn_pos));
         MOZ_ASSERT_IF(head->pn_kid3, head->pn_pos.encloses(head->pn_kid3->pn_pos));
 
         RootedValue stmt(cx);
         if (!statement(pn->pn_right, &stmt))
             return false;
 
-        if (head->isKind(PNK_FORIN)) {
+        if (head->isKind(PNK_FORIN) || head->isKind(PNK_FOROF)) {
             RootedValue var(cx);
-            return (!head->pn_kid1
-                    ? pattern(head->pn_kid2, &var)
-                    : head->pn_kid1->isKind(PNK_LEXICALSCOPE)
-                    ? variableDeclaration(head->pn_kid1->pn_expr, true, &var)
-                    : variableDeclaration(head->pn_kid1, false, &var)) &&
-                forIn(pn, head, var, stmt, dst);
-        }
-
-        if (head->isKind(PNK_FOROF)) {
-            RootedValue var(cx);
-            return (!head->pn_kid1
-                    ? pattern(head->pn_kid2, &var)
-                    : head->pn_kid1->isKind(PNK_LEXICALSCOPE)
-                    ? variableDeclaration(head->pn_kid1->pn_expr, true, &var)
-                    : variableDeclaration(head->pn_kid1, false, &var)) &&
-                forOf(pn, head, var, stmt, dst);
+            if (!head->pn_kid1) {
+                if (!pattern(head->pn_kid2, &var))
+                    return false;
+            } else if (head->pn_kid1->isKind(PNK_LEXICALSCOPE)) {
+                if (!variableDeclaration(head->pn_kid1->pn_expr, true, &var))
+                    return false;
+            } else {
+                if (!variableDeclaration(head->pn_kid1,
+                                         head->pn_kid1->isKind(PNK_LET) ||
+                                         head->pn_kid1->isKind(PNK_CONST),
+                                         &var))
+                {
+                    return false;
+                }
+            }
+            if (head->isKind(PNK_FORIN))
+                return forIn(pn, head, var, stmt, dst);
+            return forOf(pn, head, var, stmt, dst);
         }
 
         RootedValue init(cx), test(cx), update(cx);
 
-        return forInit(head->pn_kid1 && !head->pn_kid1->isKind(PNK_FRESHENBLOCK)
-                       ? head->pn_kid1
-                       : nullptr,
-                       &init) &&
+        return forInit(head->pn_kid1, &init) &&
                optExpression(head->pn_kid2, &test) &&
                optExpression(head->pn_kid3, &update) &&
                builder.forStatement(init, test, update, stmt, &pn->pn_pos, dst);
       }
 
       case PNK_BREAK:
       case PNK_CONTINUE:
       {
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -2318,17 +2318,16 @@ BytecodeEmitter::checkSideEffects(ParseN
 
       case PNK_ARGSBODY:
         *answer = true;
         return true;
 
       case PNK_FORIN:           // by PNK_FOR
       case PNK_FOROF:           // by PNK_FOR
       case PNK_FORHEAD:         // by PNK_FOR
-      case PNK_FRESHENBLOCK:    // by PNK_FOR
       case PNK_CLASSMETHOD:     // by PNK_CLASS
       case PNK_CLASSNAMES:      // by PNK_CLASS
       case PNK_CLASSMETHODLIST: // by PNK_CLASS
       case PNK_IMPORT_SPEC_LIST: // by PNK_IMPORT
       case PNK_IMPORT_SPEC:      // by PNK_IMPORT
       case PNK_EXPORT_BATCH_SPEC:// by PNK_EXPORT
       case PNK_EXPORT_SPEC_LIST: // by PNK_EXPORT
       case PNK_EXPORT_SPEC:      // by PNK_EXPORT
@@ -5284,16 +5283,25 @@ BytecodeEmitter::emitIterator()
         return false;
     checkTypeSet(JSOP_CALL);
     return true;
 }
 
 bool
 BytecodeEmitter::emitForInOrOfVariables(ParseNode* pn, bool* letDecl)
 {
+    // ES6 specifies that loop variables get a fresh binding in each iteration.
+    // This is currently implemented for C-style for(;;) loops, but not
+    // for-in/of loops, though a similar approach should work. See bug 449811.
+    //
+    // In `for (let x in/of EXPR)`, ES6 specifies that EXPR is evaluated in a
+    // scope containing an uninitialized `x`. If EXPR accesses `x`, we should
+    // get a ReferenceError due to the TDZ violation. This is not yet
+    // implemented. See bug 1069480.
+
     *letDecl = pn->isKind(PNK_LEXICALSCOPE);
     MOZ_ASSERT_IF(*letDecl, pn->isLexical());
 
     // If the left part is 'var x', emit code to define x if necessary using a
     // prologue opcode, but do not emit a pop. If it is 'let x', enterBlockScope
     // will initialize let bindings in emitForOf and emitForIn with
     // undefineds.
     //
@@ -5313,17 +5321,16 @@ BytecodeEmitter::emitForInOrOfVariables(
                 return false;
         }
         emittingForInit = false;
     }
 
     return true;
 }
 
-
 bool
 BytecodeEmitter::emitForOf(StmtType type, ParseNode* pn, ptrdiff_t top)
 {
     MOZ_ASSERT(type == StmtType::FOR_OF_LOOP || type == StmtType::SPREAD);
     MOZ_ASSERT_IF(type == StmtType::FOR_OF_LOOP, pn && pn->pn_left->isKind(PNK_FOROF));
     MOZ_ASSERT_IF(type == StmtType::SPREAD, !pn);
 
     ParseNode* forHead = pn ? pn->pn_left : nullptr;
@@ -5586,47 +5593,68 @@ BytecodeEmitter::emitForIn(ParseNode* pn
     if (letDecl) {
         if (!leaveNestedScope(&letStmt))
             return false;
     }
 
     return true;
 }
 
-bool
-BytecodeEmitter::emitNormalFor(ParseNode* pn, ptrdiff_t top)
+/* C-style `for (init; cond; update) ...` loop. */
+bool
+BytecodeEmitter::emitCStyleFor(ParseNode* pn, ptrdiff_t top)
 {
     LoopStmtInfo stmtInfo(cx);
     pushLoopStatement(&stmtInfo, StmtType::FOR_LOOP, top);
 
     ParseNode* forHead = pn->pn_left;
     ParseNode* forBody = pn->pn_right;
 
-    /* C-style for (init; cond; update) ... loop. */
+    // If the head of this for-loop declared any lexical variables, the parser
+    // wrapped this PNK_FOR node in a PNK_LEXICALSCOPE representing the
+    // implicit scope of those variables. By the time we get here, we have
+    // already entered that scope. So far, so good.
+    //
+    // ### Scope freshening
+    //
+    // Each iteration of a `for (let V...)` loop creates a fresh loop variable
+    // binding for V, even if the loop is a C-style `for(;;)` loop:
+    //
+    //     var funcs = [];
+    //     for (let i = 0; i < 2; i++)
+    //         funcs.push(function() { return i; });
+    //     assertEq(funcs[0](), 0);  // the two closures capture...
+    //     assertEq(funcs[1](), 1);  // ...two different `i` bindings
+    //
+    // This is implemented by "freshening" the implicit block -- changing the
+    // scope chain to a fresh clone of the instantaneous block object -- each
+    // iteration, just before evaluating the "update" in for(;;) loops.
+    //
+    // No freshening occurs in `for (const ...;;)` as there's no point: you
+    // can't reassign consts. This is observable through the Debugger API. (The
+    // ES6 spec also skips cloning the environment in this case.)
     bool forLoopRequiresFreshening = false;
     if (ParseNode* init = forHead->pn_kid1) {
-        if (init->isKind(PNK_FRESHENBLOCK)) {
-            // The loop's init declaration was hoisted into an enclosing lexical
-            // scope node.  Note that the block scope must be freshened each
-            // iteration.
-            forLoopRequiresFreshening = true;
-        } else {
-            emittingForInit = true;
-            if (!updateSourceCoordNotes(init->pn_pos.begin))
-                return false;
-            if (!emitTree(init))
-                return false;
-            emittingForInit = false;
-
-            if (!init->isKind(PNK_VAR) && !init->isKind(PNK_LET) && !init->isKind(PNK_CONST)) {
-                // 'init' is an expression, not a declaration. emitTree left
-                // its value on the stack.
-                if (!emit1(JSOP_POP))
-                    return false;
-            }
+        forLoopRequiresFreshening = init->isKind(PNK_LET);
+
+        // Emit the `init` clause, whether it's an expression or a variable
+        // declaration. (The loop variables were hoisted into an enclosing
+        // scope, but we still need to emit code for the initializers.)
+        emittingForInit = true;
+        if (!updateSourceCoordNotes(init->pn_pos.begin))
+            return false;
+        if (!emitTree(init))
+            return false;
+        emittingForInit = false;
+
+        if (!init->isKind(PNK_VAR) && !init->isKind(PNK_LET) && !init->isKind(PNK_CONST)) {
+            // 'init' is an expression, not a declaration. emitTree left its
+            // value on the stack.
+            if (!emit1(JSOP_POP))
+                return false;
         }
     }
 
     /*
      * NB: the SRC_FOR note has offsetBias 1 (JSOP_NOP_LENGTH).
      * Use tmp to hold the biased srcnote "top" offset, which differs
      * from the top local variable by the length of the JSOP_GOTO
      * emitted in between tmp and top if this loop has a condition.
@@ -5744,17 +5772,17 @@ BytecodeEmitter::emitFor(ParseNode* pn, 
 {
     if (pn->pn_left->isKind(PNK_FORIN))
         return emitForIn(pn, top);
 
     if (pn->pn_left->isKind(PNK_FOROF))
         return emitForOf(StmtType::FOR_OF_LOOP, pn, top);
 
     MOZ_ASSERT(pn->pn_left->isKind(PNK_FORHEAD));
-    return emitNormalFor(pn, top);
+    return emitCStyleFor(pn, top);
 }
 
 MOZ_NEVER_INLINE bool
 BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto)
 {
     FunctionBox* funbox = pn->pn_funbox;
     RootedFunction fun(cx, funbox->function());
     MOZ_ASSERT_IF(fun->isInterpretedLazy(), fun->lazyScript());
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -581,17 +581,17 @@ struct BytecodeEmitter
     bool emitSelfHostedCallFunction(ParseNode* pn);
     bool emitSelfHostedResumeGenerator(ParseNode* pn);
     bool emitSelfHostedForceInterpreter(ParseNode* pn);
 
     bool emitDo(ParseNode* pn);
     bool emitFor(ParseNode* pn, ptrdiff_t top);
     bool emitForIn(ParseNode* pn, ptrdiff_t top);
     bool emitForInOrOfVariables(ParseNode* pn, bool* letDecl);
-    bool emitNormalFor(ParseNode* pn, ptrdiff_t top);
+    bool emitCStyleFor(ParseNode* pn, ptrdiff_t top);
     bool emitWhile(ParseNode* pn, ptrdiff_t top);
 
     bool emitBreak(PropertyName* label);
     bool emitContinue(PropertyName* label);
 
     bool emitDefaultsAndDestructuring(ParseNode* pn);
     bool emitLexicalInitialization(ParseNode* pn, JSOp globalDefOp);
 
--- a/js/src/frontend/FoldConstants.cpp
+++ b/js/src/frontend/FoldConstants.cpp
@@ -403,17 +403,16 @@ ContainsHoistedDeclaration(ExclusiveCont
       case PNK_GENEXP:
       case PNK_ARRAYCOMP:
       case PNK_ARGSBODY:
       case PNK_CATCHLIST:
       case PNK_CATCH:
       case PNK_FORIN:
       case PNK_FOROF:
       case PNK_FORHEAD:
-      case PNK_FRESHENBLOCK:
       case PNK_CLASSMETHOD:
       case PNK_CLASSMETHODLIST:
       case PNK_CLASSNAMES:
       case PNK_NEWTARGET:
       case PNK_POSHOLDER:
       case PNK_SUPERCALL:
         MOZ_CRASH("ContainsHoistedDeclaration should have indicated false on "
                   "some parent node without recurring to test this node");
@@ -1705,17 +1704,16 @@ Fold(ExclusiveContext* cx, ParseNode** p
       case PNK_DEBUGGER:
       case PNK_BREAK:
       case PNK_CONTINUE:
       case PNK_TEMPLATE_STRING:
       case PNK_THIS:
       case PNK_GENERATOR:
       case PNK_EXPORT_BATCH_SPEC:
       case PNK_OBJECT_PROPERTY_NAME:
-      case PNK_FRESHENBLOCK:
       case PNK_POSHOLDER:
         MOZ_ASSERT(pn->isArity(PN_NULLARY));
         return true;
 
       case PNK_TYPEOFNAME:
         MOZ_ASSERT(pn->isArity(PN_UNARY));
         MOZ_ASSERT(pn->pn_kid->isKind(PNK_NAME));
         MOZ_ASSERT(!pn->pn_kid->maybeExpr());
--- a/js/src/frontend/FullParseHandler.h
+++ b/js/src/frontend/FullParseHandler.h
@@ -566,20 +566,16 @@ class FullParseHandler
 
     ParseNode* newForHead(ParseNodeKind kind, ParseNode* pn1, ParseNode* pn2, ParseNode* pn3,
                           const TokenPos& pos)
     {
         MOZ_ASSERT(kind == PNK_FORIN || kind == PNK_FOROF || kind == PNK_FORHEAD);
         return new_<TernaryNode>(kind, JSOP_NOP, pn1, pn2, pn3, pos);
     }
 
-    ParseNode* newFreshenBlock(const TokenPos& pos) {
-        return new_<NullaryNode>(PNK_FRESHENBLOCK, pos);
-    }
-
     ParseNode* newSwitchStatement(uint32_t begin, ParseNode* discriminant, ParseNode* caseList) {
         TokenPos pos(begin, caseList->pn_pos.end);
         return new_<BinaryNode>(PNK_SWITCH, JSOP_NOP, pos, discriminant, caseList);
     }
 
     ParseNode* newCaseOrDefault(uint32_t begin, ParseNode* expr, ParseNode* body) {
         TokenPos pos(begin, body->pn_pos.end);
         return new_<BinaryNode>(expr ? PNK_CASE : PNK_DEFAULT, JSOP_NOP, pos, expr, body);
--- a/js/src/frontend/NameFunctions.cpp
+++ b/js/src/frontend/NameFunctions.cpp
@@ -369,17 +369,16 @@ class NameResolver
           case PNK_THIS:
           case PNK_ELISION:
           case PNK_GENERATOR:
           case PNK_NUMBER:
           case PNK_BREAK:
           case PNK_CONTINUE:
           case PNK_DEBUGGER:
           case PNK_EXPORT_BATCH_SPEC:
-          case PNK_FRESHENBLOCK:
           case PNK_OBJECT_PROPERTY_NAME:
           case PNK_POSHOLDER:
             MOZ_ASSERT(cur->isArity(PN_NULLARY));
             break;
 
           case PNK_TYPEOFNAME:
             MOZ_ASSERT(cur->isArity(PN_UNARY));
             MOZ_ASSERT(cur->pn_kid->isKind(PNK_NAME));
--- a/js/src/frontend/ParseNode.cpp
+++ b/js/src/frontend/ParseNode.cpp
@@ -209,17 +209,16 @@ PushNodeChildren(ParseNode* pn, NodeStac
       case PNK_ELISION:
       case PNK_GENERATOR:
       case PNK_NUMBER:
       case PNK_BREAK:
       case PNK_CONTINUE:
       case PNK_DEBUGGER:
       case PNK_EXPORT_BATCH_SPEC:
       case PNK_OBJECT_PROPERTY_NAME:
-      case PNK_FRESHENBLOCK:
       case PNK_POSHOLDER:
         MOZ_ASSERT(pn->isArity(PN_NULLARY));
         MOZ_ASSERT(!pn->isUsed(), "handle non-trivial cases separately");
         MOZ_ASSERT(!pn->isDefn(), "handle non-trivial cases separately");
         return PushResult::Recyclable;
 
       // Nodes with a single non-null child.
       case PNK_TYPEOFNAME:
--- a/js/src/frontend/ParseNode.h
+++ b/js/src/frontend/ParseNode.h
@@ -160,17 +160,16 @@ class PackedScopeCoordinate
     F(EXPORT_FROM) \
     F(EXPORT_DEFAULT) \
     F(EXPORT_SPEC_LIST) \
     F(EXPORT_SPEC) \
     F(EXPORT_BATCH_SPEC) \
     F(FORIN) \
     F(FOROF) \
     F(FORHEAD) \
-    F(FRESHENBLOCK) \
     F(ARGSBODY) \
     F(SPREAD) \
     F(MUTATEPROTO) \
     F(CLASS) \
     F(CLASSMETHOD) \
     F(CLASSMETHODLIST) \
     F(CLASSNAMES) \
     F(NEWTARGET) \
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -4058,17 +4058,18 @@ Parser<FullParseHandler>::pushLetScope(H
     if (!ForEachLetDef(tokenStream, pc, blockObj, AddLetDecl(stmt->blockid)))
         return null();
 
     return pn;
 }
 
 template <>
 SyntaxParseHandler::Node
-Parser<SyntaxParseHandler>::pushLetScope(HandleStaticBlockObject blockObj, AutoPushStmtInfoPC& stmt)
+Parser<SyntaxParseHandler>::pushLetScope(HandleStaticBlockObject blockObj,
+                                         AutoPushStmtInfoPC& stmt)
 {
     JS_ALWAYS_FALSE(abortIfSyntaxParser());
     return SyntaxParseHandler::NodeFailure;
 }
 
 template <typename ParseHandler>
 typename ParseHandler::Node
 Parser<ParseHandler>::blockStatement(YieldHandling yieldHandling)
@@ -5182,29 +5183,47 @@ Parser<FullParseHandler>::forStatement(Y
                 if (!report(ParseWarning, pc->sc->strict(), null(), JSMSG_DEPRECATED_FOR_EACH))
                     return null();
             }
         }
     }
 
     MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_AFTER_FOR);
 
-    /*
-     * True if we have 'for (var/let/const ...)'.
-     */
+    // True if we have 'for (var/let/const ...)'.
     bool isForDecl = false;
 
+    // The next three variables are used to implement `for (let/const ...)`.
+    //
+    // We generate an implicit block, wrapping the whole loop, to store loop
+    // variables declared this way. Note that if the loop uses `for (var...)`
+    // instead, those variables go on some existing enclosing scope, so no
+    // implicit block scope is created.
+    //
+    // All three variables remain null/none if the loop is any other form.
+    //
+    // blockObj is the static block object for the implicit block scope.
+    RootedStaticBlockObject blockObj(context);
+
+    // letStmt is the BLOCK StmtInfo for the implicit block.
+    //
+    // Caution: `letStmt.emplace()` creates some Rooted objects. Rooteds must
+    // be created/destroyed in FIFO order. Therefore adding a Rooted in this
+    // function, between this point and the .emplace() call below, would trip
+    // assertions.
+    Maybe<AutoPushStmtInfoPC> letStmt;
+
+    // The PNK_LEXICALSCOPE node containing blockObj's ObjectBox.
+    ParseNode* forLetImpliedBlock = nullptr;
+
     // True if a 'let' token at the head is parsed as an identifier instead of
     // as starting a declaration.
     bool letIsIdentifier = false;
 
-    /* Non-null when isForDecl is true for a 'for (let ...)' statement. */
-    RootedStaticBlockObject blockObj(context);
-
-    /* Set to 'x' in 'for (x ;... ;...)' or 'for (x in ...)'. */
+    // Set to 'x' in 'for (x; ...; ...)' or 'for (x in ...)'.
     ParseNode* pn1;
 
     TokenStream::Modifier modifier = TokenStream::Operand;
     {
         TokenKind tt;
         if (!tokenStream.peekToken(&tt, TokenStream::Operand))
             return null();
         if (tt == TOK_SEMI) {
@@ -5244,19 +5263,29 @@ Parser<FullParseHandler>::forStatement(Y
                     bool constDecl = tt == TOK_CONST;
                     isForDecl = true;
                     blockObj = StaticBlockObject::create(context);
                     if (!blockObj)
                         return null();
 
                     // Initialize the enclosing scope manually for the call to
                     // |variables| below.
+                    blockObj = StaticBlockObject::create(context);
+                    if (!blockObj)
+                        return null();
                     blockObj->initEnclosingScopeFromParser(pc->innermostStaticScope());
+                    letStmt.emplace(*this, StmtType::BLOCK);
+                    forLetImpliedBlock = pushLetScope(blockObj, *letStmt);
+                    if (!forLetImpliedBlock)
+                        return null();
+                    (*letStmt)->isForLetBlock = true;
+
+                    MOZ_ASSERT(CurrentLexicalStaticBlock(pc) == blockObj);
                     pn1 = variables(yieldHandling, constDecl ? PNK_CONST : PNK_LET, InForInit,
-                                    nullptr, blockObj, DontHoistVars);
+                                    nullptr, blockObj, HoistVars);
                 } else {
                     pn1 = expr(InProhibited, yieldHandling, TripledotProhibited);
                 }
             } else {
                 // Pass |InProhibited| when parsing an expression so that |in|
                 // isn't parsed in a RelationalExpression as a binary operator.
                 // In this context, |in| is part of a for-in loop -- *not* part
                 // of a binary expression.
@@ -5264,70 +5293,20 @@ Parser<FullParseHandler>::forStatement(Y
             }
             if (!pn1)
                 return null();
             modifier = TokenStream::None;
         }
     }
 
     MOZ_ASSERT_IF(isForDecl, pn1->isArity(PN_LIST));
-    MOZ_ASSERT(!!blockObj == (isForDecl && (pn1->isOp(JSOP_DEFLET) || pn1->isOp(JSOP_DEFCONST))));
-
-    // If the head of a for-loop declares any lexical variables, we generate an
-    // implicit block to store them. We implement this by desugaring. These:
-    //
-    //     for (let/const <bindings>; <test>; <update>) <stmt>
-    //     for (let <pattern> in <expr>) <stmt>
-    //     for (let <pattern> of <expr>) <stmt>
-    //
-    // transform into roughly the same parse trees as these (using deprecated
-    // let-block syntax):
-    //
-    //     let (<bindings>) { for (; <test>; <update>) <stmt> }
-    //     let (<pattern>) { for (<pattern> in <expr>) <stmt> }
-    //     let (<pattern>) { for (<pattern> of <expr>) <stmt> }
-    //
-    // This desugaring is not ES6 compliant. Initializers in the head of a
-    // let-block are evaluated *outside* the scope of the variables being
-    // initialized. ES6 mandates that they be evaluated in the same scope,
-    // triggering used-before-initialization temporal dead zone errors as
-    // necessary. See bug 1216623 on scoping and bug 1069480 on TDZ.
-    //
-    // Additionally, in ES6, each iteration of a for-loop creates a fresh
-    // binding of the loop variables. For example:
-    //
-    //     var funcs = [];
-    //     for (let i = 0; i < 2; i++)
-    //         funcs.push(function() { return i; });
-    //     assertEq(funcs[0](), 0);  // the two closures capture...
-    //     assertEq(funcs[1](), 1);  // ...two different `i` bindings
-    //
-    // These semantics are implemented by "freshening" the implicit block --
-    // changing the scope chain to a fresh clone of the instantaneous block
-    // object -- each iteration, just before evaluating the "update" in
-    // for(;;) loops. We don't implement this freshening for for-in/of loops
-    // yet: bug 449811.
-    //
-    // No freshening occurs in `for (const ...;;)` as there's no point: you
-    // can't reassign consts. This is observable through the Debugger API. (The
-    // ES6 spec also skips cloning the environment in this case.)
-    //
-    // If the for-loop head includes a lexical declaration, then we create an
-    // implicit block scope, and:
-    //
-    //   * forLetImpliedBlock is the node for the implicit block scope.
-    //   * forLetDecl is the node for the decl 'let/const <pattern>'.
-    //
-    // Otherwise both are null.
-    ParseNode* forLetImpliedBlock = nullptr;
-    ParseNode* forLetDecl = nullptr;
+    MOZ_ASSERT(letStmt.isSome() == (isForDecl && (pn1->isOp(JSOP_DEFLET) || pn1->isOp(JSOP_DEFCONST))));
 
     // If there's an |in| keyword here, it's a for-in loop, by dint of careful
     // parsing of |pn1|.
-    Maybe<AutoPushStmtInfoPC> letStmt; /* used if blockObj != nullptr. */
     ParseNode* pn2;      /* forHead->pn_kid2 */
     ParseNode* pn3;      /* forHead->pn_kid3 */
     ParseNodeKind headKind = PNK_FORHEAD;
     if (pn1) {
         bool isForIn, isForOf;
         if (!matchInOrOf(&isForIn, &isForOf))
             return null();
 
@@ -5388,52 +5367,35 @@ Parser<FullParseHandler>::forStatement(Y
                 // loop isn't valid ES6 and has never been permitted in
                 // SpiderMonkey.
                 report(ParseError, false, pn2, JSMSG_INVALID_FOR_INOF_DECL_WITH_INIT,
                        headKind == PNK_FOROF ? "of" : "in");
                 return null();
             }
         } else {
             /* Not a declaration. */
-            MOZ_ASSERT(!blockObj);
+            MOZ_ASSERT(!letStmt);
             pn2 = pn1;
             pn1 = nullptr;
 
             if (!checkAndMarkAsAssignmentLhs(pn2, PlainAssignment))
                 return null();
         }
 
         pn3 = (headKind == PNK_FOROF)
               ? assignExpr(InAllowed, yieldHandling, TripledotProhibited)
               : expr(InAllowed, yieldHandling, TripledotProhibited);
         if (!pn3)
             return null();
         modifier = TokenStream::None;
 
-        if (blockObj) {
-            /*
-             * Now that the pn3 has been parsed, push the let scope. To hold
-             * the blockObj for the emitter, wrap the PNK_LEXICALSCOPE node
-             * created by pushLetScope around the for's initializer. This also
-             * serves to indicate the let-decl to the emitter.
-             */
-            letStmt.emplace(*this, StmtType::BLOCK);
-            ParseNode* block = pushLetScope(blockObj, *letStmt);
-            if (!block)
-                return null();
-            (*letStmt)->isForLetBlock = true;
-            block->pn_expr = pn1;
-            block->pn_pos = pn1->pn_pos;
-            pn1 = block;
-        }
-
         if (isForDecl) {
             /*
              * pn2 is part of a declaration. Make a copy that can be passed to
-             * EmitAssignment. Take care to do this after pushLetScope.
+             * BytecodeEmitter::emitAssignment.
              */
             pn2 = cloneLeftHandSide(pn2);
             if (!pn2)
                 return null();
         }
 
         ParseNodeKind kind2 = pn2->getKind();
         MOZ_ASSERT(kind2 != PNK_ASSIGN, "forStatement TOK_ASSIGN");
@@ -5445,57 +5407,23 @@ Parser<FullParseHandler>::forStatement(Y
     } else {
         if (isForEach) {
             reportWithOffset(ParseError, false, begin, JSMSG_BAD_FOR_EACH_LOOP);
             return null();
         }
 
         MOZ_ASSERT(headKind == PNK_FORHEAD);
 
-        if (blockObj) {
+        if (letStmt) {
             // Ensure here that the previously-unchecked assignment mandate for
             // const declarations holds.
             if (!checkForHeadConstInitializers(pn1)) {
                 report(ParseError, false, nullptr, JSMSG_BAD_CONST_DECL);
                 return null();
             }
-
-            // Desugar
-            //
-            //   for (let INIT; TEST; UPDATE) STMT
-            //
-            // into
-            //
-            //   let (INIT) { for (; TEST; UPDATE) STMT }
-            //
-            // to provide a block scope for INIT.
-            letStmt.emplace(*this, StmtType::BLOCK);
-            forLetImpliedBlock = pushLetScope(blockObj, *letStmt);
-            if (!forLetImpliedBlock)
-                return null();
-            (*letStmt)->isForLetBlock = true;
-
-            forLetDecl = pn1;
-
-            // The above transformation isn't enough to implement |INIT|
-            // scoping, because each loop iteration must see separate bindings
-            // of |INIT|.  We handle this by replacing the block on the scope
-            // chain with a new block, copying the old one's contents, each
-            // iteration.  We supply a special PNK_FRESHENBLOCK node as the
-            // |let INIT| node for |for(let INIT;;)| loop heads to distinguish
-            // such nodes from *actual*, non-desugared use of the above syntax.
-            // (We don't do this for PNK_CONST nodes because the spec says no
-            // freshening happens -- observable with the Debugger API.)
-            if (pn1->isKind(PNK_CONST)) {
-                pn1 = nullptr;
-            } else {
-                pn1 = handler.newFreshenBlock(pn1->pn_pos);
-                if (!pn1)
-                    return null();
-            }
         }
 
         /* Parse the loop condition or null into pn2. */
         MUST_MATCH_TOKEN_MOD(TOK_SEMI, modifier, JSMSG_SEMI_AFTER_FOR_INIT);
         TokenKind tt;
         if (!tokenStream.peekToken(&tt, TokenStream::Operand))
             return null();
         if (tt == TOK_SEMI) {
@@ -5537,17 +5465,17 @@ Parser<FullParseHandler>::forStatement(Y
 
     ParseNode* forLoop = handler.newForStatement(begin, forHead, body, iflags);
     if (!forLoop)
         return null();
 
     if (forLetImpliedBlock) {
         forLetImpliedBlock->pn_expr = forLoop;
         forLetImpliedBlock->pn_pos = forLoop->pn_pos;
-        return handler.newLetBlock(forLetDecl, forLetImpliedBlock, forLoop->pn_pos);
+        return forLetImpliedBlock;
     }
     return forLoop;
 }
 
 template <>
 SyntaxParseHandler::Node
 Parser<SyntaxParseHandler>::forStatement(YieldHandling yieldHandling)
 {
@@ -8288,16 +8216,17 @@ Parser<ParseHandler>::comprehensionFor(G
     TokenPos headPos(begin, pos().end);
 
     AutoPushStmtInfoPC stmtInfo(*this, StmtType::BLOCK);
     BindData<ParseHandler> data(context);
 
     RootedStaticBlockObject blockObj(context, StaticBlockObject::create(context));
     if (!blockObj)
         return null();
+
     // Initialize the enclosing scope manually for the call to |bind|
     // below, which is before the call to |pushLetScope|.
     blockObj->initEnclosingScopeFromParser(pc->innermostStaticScope());
 
     data.initLexical(DontHoistVars, JSOP_DEFLET, blockObj, JSMSG_TOO_MANY_LOCALS);
     Node decls = handler.newList(PNK_LET, lhs);
     if (!decls)
         return null();
--- a/js/src/jit-test/tests/basic/bug646968-3.js
+++ b/js/src/jit-test/tests/basic/bug646968-3.js
@@ -1,16 +1,16 @@
-var s, x = 0;
+var s, v = "NOPE";
 
 s = '';
-for (let x = x; x < 3; x++)
+for (let v = 0, x = v; x < 3; x++)
     s += x;
 assertEq(s, '012');
 
 s = '';
-for (let x = eval('x'); x < 3; x++)
+for (let v = 0, x = eval('v'); x < 3; x++)
     s += x;
 assertEq(s, '012');
 
 s = ''
-for (let x = function () { with ({}) return x; }(); x < 3; x++)
+for (let v = 0, x = function () { with ({}) return v; }(); x < 3; x++)
     s += x;
 assertEq(s, '012');
--- a/js/src/jit-test/tests/basic/bug646968-4.js
+++ b/js/src/jit-test/tests/basic/bug646968-4.js
@@ -1,4 +1,8 @@
-var s = '', x = {a: 1, b: 2, c: 3};
+// Scoping: `x` in the head of a `for (let x...)` loop refers to the loop variable.
+
+// For now, this means it evaluates to undefined. It ought to throw a
+// ReferenceError instead, but the TDZ isn't implemented here (bug 1069480).
+var s = "", x = {a: 1, b: 2, c: 3};
 for (let x in eval('x'))
     s += x;
-assertEq(s, 'abc');
+assertEq(s, "");
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/bug646968-6.js
@@ -0,0 +1,16 @@
+// In `for (let x = EXPR; ;)`, if `x` appears within EXPR, it refers to the
+// loop variable. Actually doing this is typically a TDZ error.
+
+load(libdir + "asserts.js");
+
+assertThrowsInstanceOf(() => {
+    for (let x = x; null.foo; null.foo++) {}
+}, ReferenceError);
+
+assertThrowsInstanceOf(() => {
+    for (let x = eval('x'); null.foo; null.foo++) {}
+}, ReferenceError);
+
+assertThrowsInstanceOf(() => {
+    for (let x = function () { with ({}) return x; }(); null.foo; null.foo++) {}
+}, ReferenceError);
deleted file mode 100644
--- a/js/src/jit-test/tests/basic/letLegacyForOfOrInScope.js
+++ /dev/null
@@ -1,5 +0,0 @@
-var x = "foobar";
-{ for (let x of x) assertEq(x.length, 1, "second x refers to outer x"); }
-
-var x = "foobar";
-{ for (let x in x) assertEq(x.length, 1, "second x refers to outer x"); }
--- a/js/src/jit-test/tests/basic/testLet.js
+++ b/js/src/jit-test/tests/basic/testLet.js
@@ -1,14 +1,15 @@
 var otherGlobal = newGlobal();
 
 function test(str, arg, result)
 {
     arg = arg || 'ponies';
-    result = result || 'ponies';
+    if (arguments.length < 3)
+        result = 'ponies';
 
     var fun = new Function('x', str);
 
     var got = fun.toSource();
     var expect = '(function anonymous(x) {\n' + str + '\n})';
     if (got !== expect) {
         print("GOT:    " + got);
         print("EXPECT: " + expect);
@@ -131,33 +132,27 @@ test('this.y = x;if (x) {let y = 1;retur
 
 // for(;;)
 test('for (;;) {return x;}');
 test('for (let y = 1;;) {return x;}');
 test('for (let y = 1;; ++y) {return x;}');
 test('for (let y = 1; ++y;) {return x;}');
 test('for (let [[a, [b, c]]] = [[x, []]];;) {return a;}');
 test('var sum = 0;for (let y = x; y < 4; ++y) {sum += y;}return sum;', 1, 6);
-test('var sum = 0;for (let x = x, y = 10; x < 4; ++x) {sum += x;}return sum;', 1, 6);
-test('var sum = 0;for (let x = x; x < 4; ++x) {sum += x;}return x;', 1, 1);
-test('var sum = 0;for (let x = eval("x"); x < 4; ++x) {sum += x;}return sum;', 1, 6);
-test('var sum = 0;for (let x = x; eval("x") < 4; ++x) {sum += eval("x");}return sum;', 1, 6);
-test('var sum = 0;for (let x = eval("x"); eval("x") < 4; ++x) {sum += eval("x");}return sum;', 1, 6);
+test('var sum = 0;for (let x = 1; eval("x") < 4; ++x) {sum += eval("x");}return sum;', 1, 6);
 test('for (var y = 1;;) {return x;}');
 test('for (var y = 1;; ++y) {return x;}');
 test('for (var y = 1; ++y;) {return x;}');
 test('for (var X = 1, [y, z] = x, a = x; z < 4; ++z) {return X + y;}', [2,3], 3);
 test('var sum = 0;for (var y = x; y < 4; ++y) {sum += y;}return sum;', 1, 6);
 test('var sum = 0;for (var X = x, y = 10; X < 4; ++X) {sum += X;}return sum;', 1, 6);
 test('var sum = 0;for (var X = x; X < 4; ++X) {sum += X;}return x;', 1, 1);
 test('var sum = 0;for (var X = eval("x"); X < 4; ++X) {sum += X;}return sum;', 1, 6);
 test('var sum = 0;for (var X = x; eval("X") < 4; ++X) {sum += eval("X");}return sum;', 1, 6);
 test('var sum = 0;for (var X = eval("x"); eval("X") < 4; ++X) {sum += eval("X");}return sum;', 1, 6);
-test('try {for (let x = eval("throw x");;) {}} catch (e) {return e;}');
-test('try {for (let x = x + "s"; eval("throw x");) {}} catch (e) {return e;}', 'ponie');
 test('for (let y = x;;) {let x;return y;}');
 test('for (let y = x;;) {let y;return x;}');
 test('for (let y;;) {let y;return x;}');
 test('for (let a = x;;) {let c = x, d = x;return c;}');
 test('for (let [a, b] = x;;) {let c = x, d = x;return c;}');
 test('for (let [a] = (1, [x]);;) {return a;}');
 test('for (let [a] = (1, x, 1, x);;) {return a;}', ['ponies']);
 isParseError('for (let x = 1, x = 2;;) {}');
@@ -165,38 +160,41 @@ isParseError('for (let [x, y] = a, {a:x}
 isParseError('for (let [x, y, x] = a;;) {}');
 isParseError('for (let [x, [y, [x]]] = a;;) {}');
 
 // for(in)
 test('for (let i in x) {return x;}');
 test('for (let i in x) {let y;return x;}');
 test('for each (let [a, b] in x) {let y;return x;}');
 test('for (let i in x) {let i = x;return i;}');
-test('for each (let [x, y] in x) {return x + y;}', [['ponies', '']]);
-test('for each (let [{0: x, 1: y}, z] in x) {return x + y + z;}', [[['po','nies'], '']]);
 test('var s = "";for (let a in x) {for (let b in x) {s += a + b;}}return s;', [1,2], '00011011');
 test('var res = "";for (let i in x) {res += x[i];}return res;');
 test('var res = "";for (var i in x) {res += x[i];}return res;');
-test('for each (let {x: y, y: x} in [{x: x, y: x}]) {return y;}');
-test('for (let x in eval("x")) {return x;}', {ponies:true});
-test('for (let x in x) {return eval("x");}', {ponies:true});
-test('for (let x in eval("x")) {return eval("x");}', {ponies:true});
+isParseError('for ((let (x = {y: true}) x).y in eval("x")) {return eval("x");}');
 test('for (let i in x) {break;}return x;');
 test('for (let i in x) {break;}return eval("x");');
-test('for (let x in x) {break;}return x;');
-test('for (let x in x) {break;}return eval("x");');
 test('a:for (let i in x) {for (let j in x) {break a;}}return x;');
 test('a:for (let i in x) {for (let j in x) {break a;}}return eval("x");');
 test('var j;for (let i in x) {j = i;break;}return j;', {ponies:true});
-test('try {for (let x in eval("throw x")) {}} catch (e) {return e;}');
-test('try {for each (let x in x) {eval("throw x");}} catch (e) {return e;}', ['ponies']);
 isParseError('for (let [x, x] in o) {}');
 isParseError('for (let [x, y, x] in o) {}');
 isParseError('for (let [x, [y, [x]]] in o) {}');
 
+// for(let ... in ...) scoping bugs (bug 1069480)
+test('for each (let [x, y] in x) {return x + y;}', [['ponies', '']], undefined);
+test('for each (let [{0: x, 1: y}, z] in x) {return x + y + z;}', [[['po','nies'], '']], undefined);
+test('for (let x in eval("x")) {return x;}', {ponies:true}, undefined);
+test('for (let x in x) {return eval("x");}', {ponies:true}, undefined);
+test('for (let x in eval("x")) {return eval("x");}', {ponies:true}, undefined);
+test('for (let x in x) {break;}return x;');
+test('for (let x in x) {break;}return eval("x");');
+test('try {for (let x in eval("throw x")) {}} catch (e) {return e;}', undefined, undefined);
+test('try {for each (let x in x) {eval("throw x");}} catch (e) {return e;}', ['ponies'], undefined);
+test('for each (let {x: y, y: x} in [{x: x, y: x}]) {return y;}', undefined, undefined);
+
 // genexps
 test('return (i for (i in x)).next();', {ponies:true});
 test('return (eval("i") for (i in x)).next();', {ponies:true});
 test('return (eval("i") for (i in eval("x"))).next();', {ponies:true});
 test('try {return (eval("throw i") for (i in x)).next();} catch (e) {return e;}', {ponies:true});
 
 // array comprehension
 test('return [i for (i in x)][0];', {ponies:true});
@@ -217,11 +215,18 @@ isReferenceError('inner(); let x; functi
 isReferenceError('inner(); let x; function inner() { function innerer() { x++; } innerer(); }');
 isReferenceError('let x; var inner = function () { y++; }; inner(); let y;');
 isReferenceError('let x = x;');
 isReferenceError('let [x] = [x];');
 isReferenceError('let {x} = {x:x};');
 isReferenceError('switch (x) {case 3:let x;break;default:if (x === undefined) {return "ponies";}}');
 isReferenceError('let x = function() {} ? x() : function() {}');
 isReferenceError('(function() { let x = (function() { return x }()); }())');
+isReferenceError('var sum = 0;for (let x = x, y = 10; x < 4; ++x) {sum += x;}return sum;');
+isReferenceError('var sum = 0;for (let x = x; x < 4; ++x) {sum += x;}return x;');
+isReferenceError('var sum = 0;for (let x = eval("x"); x < 4; ++x) {sum += x;}return sum;');
+isReferenceError('var sum = 0;for (let x = x; eval("x") < 4; ++x) {sum += eval("x");}return sum;');
+isReferenceError('var sum = 0;for (let x = eval("x"); eval("x") < 4; ++x) {sum += eval("x");}return sum;');
+isReferenceError('for (let x = eval("throw x");;) {}');
+isReferenceError('for (let x = x + "s"; eval("throw x");) {}');
 
 // redecl with function statements
 isParseError('let a; function a() {}');
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/LexicalEnvironment/bug-1216623.js
@@ -0,0 +1,19 @@
+// Scoping in the head of for(let;;) statements.
+
+let x = 0;
+for (let i = 0, a = () => i; i < 4; i++) {
+  assertEq(i, x++);
+  assertEq(a(), 0);
+}
+assertEq(x, 4);
+
+x = 11;
+let q = 0;
+for (let {[++q]: r} = [0, 11, 22], s = () => r; r < 13; r++) {
+  assertEq(r, x++);
+  assertEq(s(), 11);
+}
+assertEq(x, 13);
+assertEq(q, 1);
+
+reportCompare(0, 0);
--- a/js/src/tests/ecma_6/LexicalEnvironment/for-loop.js
+++ b/js/src/tests/ecma_6/LexicalEnvironment/for-loop.js
@@ -73,21 +73,17 @@ assertEq(funcs[6](), 6);
 assertEq(funcs[7](), 7);
 assertEq(funcs[8](), 8);
 assertEq(funcs[9](), 9);
 
 var outer = "OUTER V IGNORE";
 var save;
 for (let outer = (save = function() { return outer; }); ; )
   break;
-assertEq(save(), "OUTER V IGNORE",
-         "this is actually a bug: fix for(;;) loops to evaluate init RHSes " +
-         "in the block scope containing all the LHS bindings!");
-
-
+assertEq(save(), save);
 
 var funcs = [];
 function t(i, name, expect)
 {
   assertEq(funcs[i].name, name);
   assertEq(funcs[i](), expect);
 }
 
--- a/js/src/tests/js1_8_5/reflect-parse/destructuring-variable-declarations.js
+++ b/js/src/tests/js1_8_5/reflect-parse/destructuring-variable-declarations.js
@@ -18,19 +18,19 @@ function testVarPatternCombinations(make
         assertBlockDecl("let " + pattSrcs[i].join(",") + ";", letDecl(pattPatts[i]));
 
         assertDecl("const " + constSrcs[i].join(",") + ";", constDecl(constPatts[i]));
 
         // variable declarations in for-loop heads
         assertStmt("for (var " + pattSrcs[i].join(",") + "; foo; bar);",
                    forStmt(varDecl(pattPatts[i]), ident("foo"), ident("bar"), emptyStmt));
         assertStmt("for (let " + pattSrcs[i].join(",") + "; foo; bar);",
-                   letStmt(pattPatts[i], forStmt(null, ident("foo"), ident("bar"), emptyStmt)));
+                   forStmt(letDecl(pattPatts[i]), ident("foo"), ident("bar"), emptyStmt));
         assertStmt("for (const " + constSrcs[i].join(",") + "; foo; bar);",
-                   letStmt(constPatts[i], forStmt(null, ident("foo"), ident("bar"), emptyStmt)));
+                   forStmt(constDecl(constPatts[i]), ident("foo"), ident("bar"), emptyStmt));
     }
 }
 
 testVarPatternCombinations(n => ("{a" + n + ":x" + n + "," + "b" + n + ":y" + n + "," + "c" + n + ":z" + n + "} = 0"),
                            n => ({ id: objPatt([assignProp("a" + n, ident("x" + n)),
                                                 assignProp("b" + n, ident("y" + n)),
                                                 assignProp("c" + n, ident("z" + n))]),
                                    init: lit(0) }));