Bug 1065450 - Make Reflect.parse properly handle new-style array comprehensions and generator expressions. r=Waldo.
authorJason Orendorff <jorendorff@mozilla.com>
Tue, 30 Sep 2014 09:17:12 -0500
changeset 232592 a5d074e91f7a83d50b8db3ec55d734300e8b763b
parent 232591 bccecd00aa530539ace17f51f6af0134de4ce7b3
child 232593 90eec7edf8c0e4cfaf0fe15bf3f887f63a24eba0
push id4187
push userbhearsum@mozilla.com
push dateFri, 28 Nov 2014 15:29:12 +0000
treeherdermozilla-beta@f23cc6a30c11 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersWaldo
bugs1065450
milestone35.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 1065450 - Make Reflect.parse properly handle new-style array comprehensions and generator expressions. r=Waldo.
js/src/frontend/ParseNode.h
js/src/jsreflect.cpp
js/src/tests/js1_8_5/extensions/reflect-parse.js
--- a/js/src/frontend/ParseNode.h
+++ b/js/src/frontend/ParseNode.h
@@ -768,18 +768,18 @@ class ParseNode
     /* Return true if this node appears in a Directive Prologue. */
     bool isDirectivePrologueMember() const { return pn_prologue; }
 
 #ifdef JS_HAS_GENERATOR_EXPRS
     ParseNode *generatorExpr() const {
         MOZ_ASSERT(isKind(PNK_GENEXP));
         ParseNode *callee = this->pn_head;
         ParseNode *body = callee->pn_body;
-        MOZ_ASSERT(body->isKind(PNK_LEXICALSCOPE));
-        return body->pn_expr;
+        MOZ_ASSERT(body->isKind(PNK_LEXICALSCOPE) || body->isKind(PNK_FOR));
+        return body;
     }
 #endif
 
     inline void markAsAssigned();
 
     /*
      * Compute a pointer to the last element in a singly-linked list. NB: list
      * must be non-empty for correct PN_LAST usage -- this is asserted!
--- a/js/src/jsreflect.cpp
+++ b/js/src/jsreflect.cpp
@@ -665,20 +665,20 @@ class NodeBuilder
     bool thisExpression(TokenPos *pos, MutableHandleValue dst);
 
     bool yieldExpression(HandleValue arg, YieldKind kind, TokenPos *pos, MutableHandleValue dst);
 
     bool comprehensionBlock(HandleValue patt, HandleValue src, bool isForEach, bool isForOf, TokenPos *pos,
                             MutableHandleValue dst);
 
     bool comprehensionExpression(HandleValue body, NodeVector &blocks, HandleValue filter,
-                                 TokenPos *pos, MutableHandleValue dst);
+                                 bool isLegacy, TokenPos *pos, MutableHandleValue dst);
 
     bool generatorExpression(HandleValue body, NodeVector &blocks, HandleValue filter,
-                             TokenPos *pos, MutableHandleValue dst);
+                             bool isLegacy, TokenPos *pos, MutableHandleValue dst);
 
     bool letExpression(NodeVector &head, HandleValue expr, TokenPos *pos, MutableHandleValue dst);
 
     /*
      * declarations
      */
 
     bool variableDeclaration(NodeVector &elts, VarDeclKind kind, TokenPos *pos,
@@ -1406,49 +1406,59 @@ NodeBuilder::comprehensionBlock(HandleVa
                    "right", src,
                    "each", isForEachVal,
                    "of", isForOfVal,
                    dst);
 }
 
 bool
 NodeBuilder::comprehensionExpression(HandleValue body, NodeVector &blocks, HandleValue filter,
-                                     TokenPos *pos, MutableHandleValue dst)
+                                     bool isLegacy, TokenPos *pos, MutableHandleValue dst)
 {
     RootedValue array(cx);
     if (!newArray(blocks, &array))
         return false;
 
+    RootedValue style(cx);
+    if (!atomValue(isLegacy ? "legacy" : "modern", &style))
+        return false;
+
     RootedValue cb(cx, callbacks[AST_COMP_EXPR]);
     if (!cb.isNull())
-        return callback(cb, body, array, opt(filter), pos, dst);
+        return callback(cb, body, array, opt(filter), style, pos, dst);
 
     return newNode(AST_COMP_EXPR, pos,
                    "body", body,
                    "blocks", array,
                    "filter", filter,
+                   "style", style,
                    dst);
 }
 
 bool
 NodeBuilder::generatorExpression(HandleValue body, NodeVector &blocks, HandleValue filter,
-                                 TokenPos *pos, MutableHandleValue dst)
+                                 bool isLegacy, TokenPos *pos, MutableHandleValue dst)
 {
     RootedValue array(cx);
     if (!newArray(blocks, &array))
         return false;
 
+    RootedValue style(cx);
+    if (!atomValue(isLegacy ? "legacy" : "modern", &style))
+        return false;
+
     RootedValue cb(cx, callbacks[AST_GENERATOR_EXPR]);
     if (!cb.isNull())
-        return callback(cb, body, array, opt(filter), pos, dst);
+        return callback(cb, body, array, opt(filter), style, pos, dst);
 
     return newNode(AST_GENERATOR_EXPR, pos,
                    "body", body,
                    "blocks", array,
                    "filter", filter,
+                   "style", style,
                    dst);
 }
 
 bool
 NodeBuilder::letExpression(NodeVector &head, HandleValue expr, TokenPos *pos,
                            MutableHandleValue dst)
 {
     RootedValue array(cx);
@@ -2572,33 +2582,38 @@ bool
 ASTSerializer::comprehensionBlock(ParseNode *pn, MutableHandleValue dst)
 {
     LOCAL_ASSERT(pn->isArity(PN_BINARY));
 
     ParseNode *in = pn->pn_left;
 
     LOCAL_ASSERT(in && (in->isKind(PNK_FORIN) || in->isKind(PNK_FOROF)));
 
-    bool isForEach = pn->pn_iflags & JSITER_FOREACH;
+    bool isForEach = in->isKind(PNK_FORIN) && (pn->pn_iflags & JSITER_FOREACH);
     bool isForOf = in->isKind(PNK_FOROF);
 
     RootedValue patt(cx), src(cx);
     return pattern(in->pn_kid2, nullptr, &patt) &&
            expression(in->pn_kid3, &src) &&
            builder.comprehensionBlock(patt, src, isForEach, isForOf, &in->pn_pos, dst);
 }
 
 bool
 ASTSerializer::comprehension(ParseNode *pn, MutableHandleValue dst)
 {
-    LOCAL_ASSERT(pn->isKind(PNK_FOR));
+    // There are two array comprehension flavors.
+    // 1. The kind that was in ES4 for a while: [z for (x in y)]
+    // 2. The kind that was in ES6 for a while: [for (x of y) z]
+    // They have slightly different parse trees and scoping.
+    bool isLegacy = pn->isKind(PNK_LEXICALSCOPE);
+    ParseNode *next = isLegacy ? pn->pn_expr : pn;
+    LOCAL_ASSERT(next->isKind(PNK_FOR));
 
     NodeVector blocks(cx);
 
-    ParseNode *next = pn;
     while (next->isKind(PNK_FOR)) {
         RootedValue block(cx);
         if (!comprehensionBlock(next, &block) || !blocks.append(block))
             return false;
         next = next->pn_right;
     }
 
     RootedValue filter(cx, MagicValue(JS_SERIALIZE_NO_NODE));
@@ -2613,27 +2628,31 @@ ASTSerializer::comprehension(ParseNode *
         return builder.arrayExpression(empty, &pn->pn_pos, dst);
     }
 
     LOCAL_ASSERT(next->isKind(PNK_ARRAYPUSH));
 
     RootedValue body(cx);
 
     return expression(next->pn_kid, &body) &&
-           builder.comprehensionExpression(body, blocks, filter, &pn->pn_pos, dst);
+           builder.comprehensionExpression(body, blocks, filter, isLegacy, &pn->pn_pos, dst);
 }
 
 bool
 ASTSerializer::generatorExpression(ParseNode *pn, MutableHandleValue dst)
 {
-    LOCAL_ASSERT(pn->isKind(PNK_FOR));
+    // Just as there are two kinds of array comprehension (see
+    // ASTSerializer::comprehension), there are legacy and modern generator
+    // expression.
+    bool isLegacy = pn->isKind(PNK_LEXICALSCOPE);
+    ParseNode *next = isLegacy ? pn->pn_expr : pn;
+    LOCAL_ASSERT(next->isKind(PNK_FOR));
 
     NodeVector blocks(cx);
 
-    ParseNode *next = pn;
     while (next->isKind(PNK_FOR)) {
         RootedValue block(cx);
         if (!comprehensionBlock(next, &block) || !blocks.append(block))
             return false;
         next = next->pn_right;
     }
 
     RootedValue filter(cx, MagicValue(JS_SERIALIZE_NO_NODE));
@@ -2646,17 +2665,17 @@ ASTSerializer::generatorExpression(Parse
 
     LOCAL_ASSERT(next->isKind(PNK_SEMI) &&
                  next->pn_kid->isKind(PNK_YIELD) &&
                  next->pn_kid->pn_kid);
 
     RootedValue body(cx);
 
     return expression(next->pn_kid->pn_kid, &body) &&
-           builder.generatorExpression(body, blocks, filter, &pn->pn_pos, dst);
+           builder.generatorExpression(body, blocks, filter, isLegacy, &pn->pn_pos, dst);
 }
 
 bool
 ASTSerializer::expression(ParseNode *pn, MutableHandleValue dst)
 {
     JS_CHECK_RECURSION(cx, return false);
     switch (pn->getKind()) {
       case PNK_FUNCTION:
@@ -2993,19 +3012,17 @@ ASTSerializer::expression(ParseNode *pn,
                builder.yieldExpression(arg, NotDelegating, &pn->pn_pos, dst);
       }
 
       case PNK_ARRAYCOMP:
         MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_head->pn_pos));
 
         /* NB: it's no longer the case that pn_count could be 2. */
         LOCAL_ASSERT(pn->pn_count == 1);
-        LOCAL_ASSERT(pn->pn_head->isKind(PNK_LEXICALSCOPE));
-
-        return comprehension(pn->pn_head->pn_expr, dst);
+        return comprehension(pn->pn_head, dst);
 
       case PNK_LET:
         return let(pn, true, dst);
 
       default:
         LOCAL_NOT_REACHED("unexpected expression type");
     }
 }
--- a/js/src/tests/js1_8_5/extensions/reflect-parse.js
+++ b/js/src/tests/js1_8_5/extensions/reflect-parse.js
@@ -94,18 +94,18 @@ function callExpr(callee, args) Pattern(
 function arrExpr(elts) Pattern({ type: "ArrayExpression", elements: elts })
 function objExpr(elts) Pattern({ type: "ObjectExpression", properties: elts })
 function computedName(elts) Pattern({ type: "ComputedName", name: elts })
 function templateLit(elts) Pattern({ type: "TemplateLiteral", elements: elts })
 function taggedTemplate(tagPart, templatePart) Pattern({ type: "TaggedTemplate", callee: tagPart,
                 arguments : templatePart })
 function template(raw, cooked, ...args) Pattern([{ type: "CallSiteObject", raw: raw, cooked:
 cooked}, ...args])
-function compExpr(body, blocks, filter) Pattern({ type: "ComprehensionExpression", body: body, blocks: blocks, filter: filter })
-function genExpr(body, blocks, filter) Pattern({ type: "GeneratorExpression", body: body, blocks: blocks, filter: filter })
+function compExpr(body, blocks, filter, style) Pattern({ type: "ComprehensionExpression", body, blocks, filter, style })
+function genExpr(body, blocks, filter, style) Pattern({ type: "GeneratorExpression", body, blocks, filter, style })
 function graphExpr(idx, body) Pattern({ type: "GraphExpression", index: idx, expression: body })
 function letExpr(head, body) Pattern({ type: "LetExpression", head: head, body: body })
 function idxExpr(idx) Pattern({ type: "GraphIndexExpression", index: idx })
 
 function compBlock(left, right) Pattern({ type: "ComprehensionBlock", left: left, right: right, each: false, of: false })
 function compEachBlock(left, right) Pattern({ type: "ComprehensionBlock", left: left, right: right, each: true, of: false })
 function compOfBlock(left, right) Pattern({ type: "ComprehensionBlock", left: left, right: right, each: false, of: true })
 
@@ -376,17 +376,17 @@ assertExpr("({['__proto__']:q, __proto__
 
 // Bug 571617: eliminate constant-folding
 assertExpr("2 + 3", binExpr("+", lit(2), lit(3)));
 
 // Bug 632026: constant-folding
 assertExpr("typeof(0?0:a)", unExpr("typeof", condExpr(lit(0), lit(0), ident("a"))));
 
 // Bug 632029: constant-folding
-assertExpr("[x for each (x in y) if (false)]", compExpr(ident("x"), [compEachBlock(ident("x"), ident("y"))], lit(false)));
+assertExpr("[x for each (x in y) if (false)]", compExpr(ident("x"), [compEachBlock(ident("x"), ident("y"))], lit(false), "legacy"));
 
 // Bug 632056: constant-folding
 program([exprStmt(ident("f")),
          ifStmt(lit(1),
                 funDecl(ident("f"), [], blockStmt([])),
                 null)]).assert(Reflect.parse("f; if (1) function f(){}"));
 
 // Bug 924688: computed property names
@@ -809,124 +809,156 @@ assertExpr("({ get x() { return 42 } })"
 assertExpr("({ set x(v) { return 42 } })",
            objExpr([ { key: ident("x"),
                        value: funExpr(null, [ident("v")], blockStmt([returnStmt(lit(42))])),
                        kind: "set" } ]));
 
 // comprehensions
 
 assertExpr("[ x         for (x in foo)]",
-           compExpr(ident("x"), [compBlock(ident("x"), ident("foo"))], null));
+           compExpr(ident("x"), [compBlock(ident("x"), ident("foo"))], null, "legacy"));
 assertExpr("[ [x,y]     for (x in foo) for (y in bar)]",
-           compExpr(arrExpr([ident("x"), ident("y")]), [compBlock(ident("x"), ident("foo")), compBlock(ident("y"), ident("bar"))], null));
+           compExpr(arrExpr([ident("x"), ident("y")]), [compBlock(ident("x"), ident("foo")), compBlock(ident("y"), ident("bar"))], null, "legacy"));
 assertExpr("[ [x,y,z] for (x in foo) for (y in bar) for (z in baz)]",
            compExpr(arrExpr([ident("x"), ident("y"), ident("z")]),
                     [compBlock(ident("x"), ident("foo")), compBlock(ident("y"), ident("bar")), compBlock(ident("z"), ident("baz"))],
-                    null));
+                    null,
+                    "legacy"));
 
 assertExpr("[ x         for (x in foo) if (p)]",
-           compExpr(ident("x"), [compBlock(ident("x"), ident("foo"))], ident("p")));
+           compExpr(ident("x"), [compBlock(ident("x"), ident("foo"))], ident("p"), "legacy"));
 assertExpr("[ [x,y]     for (x in foo) for (y in bar) if (p)]",
-           compExpr(arrExpr([ident("x"), ident("y")]), [compBlock(ident("x"), ident("foo")), compBlock(ident("y"), ident("bar"))], ident("p")));
+           compExpr(arrExpr([ident("x"), ident("y")]), [compBlock(ident("x"), ident("foo")), compBlock(ident("y"), ident("bar"))], ident("p"), "legacy"));
 assertExpr("[ [x,y,z] for (x in foo) for (y in bar) for (z in baz) if (p) ]",
            compExpr(arrExpr([ident("x"), ident("y"), ident("z")]),
                     [compBlock(ident("x"), ident("foo")), compBlock(ident("y"), ident("bar")), compBlock(ident("z"), ident("baz"))],
-                    ident("p")));
+                    ident("p"),
+                    "legacy"));
 
 assertExpr("[ x         for each (x in foo)]",
-           compExpr(ident("x"), [compEachBlock(ident("x"), ident("foo"))], null));
+           compExpr(ident("x"), [compEachBlock(ident("x"), ident("foo"))], null, "legacy"));
 assertExpr("[ [x,y]     for each (x in foo) for each (y in bar)]",
-           compExpr(arrExpr([ident("x"), ident("y")]), [compEachBlock(ident("x"), ident("foo")), compEachBlock(ident("y"), ident("bar"))], null));
+           compExpr(arrExpr([ident("x"), ident("y")]), [compEachBlock(ident("x"), ident("foo")), compEachBlock(ident("y"), ident("bar"))], null, "legacy"));
 assertExpr("[ [x,y,z] for each (x in foo) for each (y in bar) for each (z in baz)]",
            compExpr(arrExpr([ident("x"), ident("y"), ident("z")]),
                     [compEachBlock(ident("x"), ident("foo")), compEachBlock(ident("y"), ident("bar")), compEachBlock(ident("z"), ident("baz"))],
-                    null));
+                    null,
+                    "legacy"));
 
 assertExpr("[ x         for each (x in foo) if (p)]",
-           compExpr(ident("x"), [compEachBlock(ident("x"), ident("foo"))], ident("p")));
+           compExpr(ident("x"), [compEachBlock(ident("x"), ident("foo"))], ident("p"), "legacy"));
 assertExpr("[ [x,y]     for each (x in foo) for each (y in bar) if (p)]",
-           compExpr(arrExpr([ident("x"), ident("y")]), [compEachBlock(ident("x"), ident("foo")), compEachBlock(ident("y"), ident("bar"))], ident("p")));
+           compExpr(arrExpr([ident("x"), ident("y")]), [compEachBlock(ident("x"), ident("foo")), compEachBlock(ident("y"), ident("bar"))], ident("p"), "legacy"));
 assertExpr("[ [x,y,z] for each (x in foo) for each (y in bar) for each (z in baz) if (p) ]",
            compExpr(arrExpr([ident("x"), ident("y"), ident("z")]),
                     [compEachBlock(ident("x"), ident("foo")), compEachBlock(ident("y"), ident("bar")), compEachBlock(ident("z"), ident("baz"))],
-                    ident("p")));
+                    ident("p"),
+                    "legacy"));
+
+// Comprehension expressions using for-of can be written in two different styles.
+function assertLegacyAndModernArrayComp(expr, body, blocks, filter) {
+    assertExpr(expr, compExpr(body, blocks, filter, "legacy"));
+
+    // Transform the legacy comprehension to a modern comprehension and test it
+    // that way too.
+    let match = expr.match(/^\[(.*?) for (.*)\]$/);
+    assertEq(match !== null, true);
+    let expr2 = "[for " + match[2] + " " + match[1] + "]";
+    assertExpr(expr2, compExpr(body, blocks, filter, "modern"));
+}
 
-assertExpr("[ x         for (x of foo)]",
-           compExpr(ident("x"), [compOfBlock(ident("x"), ident("foo"))], null));
-assertExpr("[ [x,y]     for (x of foo) for (y of bar)]",
-           compExpr(arrExpr([ident("x"), ident("y")]), [compOfBlock(ident("x"), ident("foo")), compOfBlock(ident("y"), ident("bar"))], null));
-assertExpr("[ [x,y,z] for (x of foo) for (y of bar) for (z of baz)]",
-           compExpr(arrExpr([ident("x"), ident("y"), ident("z")]),
-                    [compOfBlock(ident("x"), ident("foo")), compOfBlock(ident("y"), ident("bar")), compOfBlock(ident("z"), ident("baz"))],
-                    null));
+assertLegacyAndModernArrayComp("[ x         for (x of foo)]",
+                               ident("x"), [compOfBlock(ident("x"), ident("foo"))], null);
+assertLegacyAndModernArrayComp("[ [x,y]     for (x of foo) for (y of bar)]",
+                               arrExpr([ident("x"), ident("y")]), [compOfBlock(ident("x"), ident("foo")), compOfBlock(ident("y"), ident("bar"))], null);
+assertLegacyAndModernArrayComp("[ [x,y,z] for (x of foo) for (y of bar) for (z of baz)]",
+                               arrExpr([ident("x"), ident("y"), ident("z")]),
+                               [compOfBlock(ident("x"), ident("foo")), compOfBlock(ident("y"), ident("bar")), compOfBlock(ident("z"), ident("baz"))],
+                               null);
 
-assertExpr("[ x         for (x of foo) if (p)]",
-           compExpr(ident("x"), [compOfBlock(ident("x"), ident("foo"))], ident("p")));
-assertExpr("[ [x,y]     for (x of foo) for (y of bar) if (p)]",
-           compExpr(arrExpr([ident("x"), ident("y")]), [compOfBlock(ident("x"), ident("foo")), compOfBlock(ident("y"), ident("bar"))], ident("p")));
-assertExpr("[ [x,y,z] for (x of foo) for (y of bar) for (z of baz) if (p) ]",
-           compExpr(arrExpr([ident("x"), ident("y"), ident("z")]),
-                    [compOfBlock(ident("x"), ident("foo")), compOfBlock(ident("y"), ident("bar")), compOfBlock(ident("z"), ident("baz"))],
-                    ident("p")));
+assertLegacyAndModernArrayComp("[ x         for (x of foo) if (p)]",
+                               ident("x"), [compOfBlock(ident("x"), ident("foo"))], ident("p"));
+assertLegacyAndModernArrayComp("[ [x,y]     for (x of foo) for (y of bar) if (p)]",
+                               arrExpr([ident("x"), ident("y")]), [compOfBlock(ident("x"), ident("foo")), compOfBlock(ident("y"), ident("bar"))], ident("p"));
+assertLegacyAndModernArrayComp("[ [x,y,z] for (x of foo) for (y of bar) for (z of baz) if (p) ]",
+                               arrExpr([ident("x"), ident("y"), ident("z")]),
+                               [compOfBlock(ident("x"), ident("foo")), compOfBlock(ident("y"), ident("bar")), compOfBlock(ident("z"), ident("baz"))],
+                               ident("p"));
 
 // generator expressions
 
 assertExpr("( x         for (x in foo))",
-           genExpr(ident("x"), [compBlock(ident("x"), ident("foo"))], null));
+           genExpr(ident("x"), [compBlock(ident("x"), ident("foo"))], null, "legacy"));
 assertExpr("( [x,y]     for (x in foo) for (y in bar))",
-           genExpr(arrExpr([ident("x"), ident("y")]), [compBlock(ident("x"), ident("foo")), compBlock(ident("y"), ident("bar"))], null));
+           genExpr(arrExpr([ident("x"), ident("y")]), [compBlock(ident("x"), ident("foo")), compBlock(ident("y"), ident("bar"))], null, "legacy"));
 assertExpr("( [x,y,z] for (x in foo) for (y in bar) for (z in baz))",
            genExpr(arrExpr([ident("x"), ident("y"), ident("z")]),
                    [compBlock(ident("x"), ident("foo")), compBlock(ident("y"), ident("bar")), compBlock(ident("z"), ident("baz"))],
-                   null));
+                   null,
+                   "legacy"));
 
 assertExpr("( x         for (x in foo) if (p))",
-           genExpr(ident("x"), [compBlock(ident("x"), ident("foo"))], ident("p")));
+           genExpr(ident("x"), [compBlock(ident("x"), ident("foo"))], ident("p"), "legacy"));
 assertExpr("( [x,y]     for (x in foo) for (y in bar) if (p))",
-           genExpr(arrExpr([ident("x"), ident("y")]), [compBlock(ident("x"), ident("foo")), compBlock(ident("y"), ident("bar"))], ident("p")));
+           genExpr(arrExpr([ident("x"), ident("y")]), [compBlock(ident("x"), ident("foo")), compBlock(ident("y"), ident("bar"))], ident("p"), "legacy"));
 assertExpr("( [x,y,z] for (x in foo) for (y in bar) for (z in baz) if (p) )",
            genExpr(arrExpr([ident("x"), ident("y"), ident("z")]),
                    [compBlock(ident("x"), ident("foo")), compBlock(ident("y"), ident("bar")), compBlock(ident("z"), ident("baz"))],
-                   ident("p")));
+                   ident("p"),
+                   "legacy"));
 
 assertExpr("( x         for each (x in foo))",
-           genExpr(ident("x"), [compEachBlock(ident("x"), ident("foo"))], null));
+           genExpr(ident("x"), [compEachBlock(ident("x"), ident("foo"))], null, "legacy"));
 assertExpr("( [x,y]     for each (x in foo) for each (y in bar))",
-           genExpr(arrExpr([ident("x"), ident("y")]), [compEachBlock(ident("x"), ident("foo")), compEachBlock(ident("y"), ident("bar"))], null));
+           genExpr(arrExpr([ident("x"), ident("y")]), [compEachBlock(ident("x"), ident("foo")), compEachBlock(ident("y"), ident("bar"))], null, "legacy"));
 assertExpr("( [x,y,z] for each (x in foo) for each (y in bar) for each (z in baz))",
            genExpr(arrExpr([ident("x"), ident("y"), ident("z")]),
                    [compEachBlock(ident("x"), ident("foo")), compEachBlock(ident("y"), ident("bar")), compEachBlock(ident("z"), ident("baz"))],
-                   null));
+                   null,
+                   "legacy"));
 
 assertExpr("( x         for each (x in foo) if (p))",
-           genExpr(ident("x"), [compEachBlock(ident("x"), ident("foo"))], ident("p")));
+           genExpr(ident("x"), [compEachBlock(ident("x"), ident("foo"))], ident("p"), "legacy"));
 assertExpr("( [x,y]     for each (x in foo) for each (y in bar) if (p))",
-           genExpr(arrExpr([ident("x"), ident("y")]), [compEachBlock(ident("x"), ident("foo")), compEachBlock(ident("y"), ident("bar"))], ident("p")));
+           genExpr(arrExpr([ident("x"), ident("y")]), [compEachBlock(ident("x"), ident("foo")), compEachBlock(ident("y"), ident("bar"))], ident("p"), "legacy"));
 assertExpr("( [x,y,z] for each (x in foo) for each (y in bar) for each (z in baz) if (p) )",
            genExpr(arrExpr([ident("x"), ident("y"), ident("z")]),
                    [compEachBlock(ident("x"), ident("foo")), compEachBlock(ident("y"), ident("bar")), compEachBlock(ident("z"), ident("baz"))],
-                   ident("p")));
+                   ident("p"),
+                   "legacy"));
+
+// Generator expressions using for-of can be written in two different styles.
+function assertLegacyAndModernGenExpr(expr, body, blocks, filter) {
+    assertExpr(expr, genExpr(body, blocks, filter, "legacy"));
+
+    // Transform the legacy genexpr to a modern genexpr and test it that way
+    // too.
+    let match = expr.match(/^\((.*?) for (.*)\)$/);
+    assertEq(match !== null, true);
+    let expr2 = "(for " + match[2] + " " + match[1] + ")";
+    assertExpr(expr2, genExpr(body, blocks, filter, "modern"));
+}
 
-assertExpr("( x         for (x of foo))",
-           genExpr(ident("x"), [compOfBlock(ident("x"), ident("foo"))], null));
-assertExpr("( [x,y]     for (x of foo) for (y of bar))",
-           genExpr(arrExpr([ident("x"), ident("y")]), [compOfBlock(ident("x"), ident("foo")), compOfBlock(ident("y"), ident("bar"))], null));
-assertExpr("( [x,y,z] for (x of foo) for (y of bar) for (z of baz))",
-           genExpr(arrExpr([ident("x"), ident("y"), ident("z")]),
-                   [compOfBlock(ident("x"), ident("foo")), compOfBlock(ident("y"), ident("bar")), compOfBlock(ident("z"), ident("baz"))],
-                   null));
+assertLegacyAndModernGenExpr("( x         for (x of foo))",
+                             ident("x"), [compOfBlock(ident("x"), ident("foo"))], null);
+assertLegacyAndModernGenExpr("( [x,y]     for (x of foo) for (y of bar))",
+                             arrExpr([ident("x"), ident("y")]), [compOfBlock(ident("x"), ident("foo")), compOfBlock(ident("y"), ident("bar"))], null);
+assertLegacyAndModernGenExpr("( [x,y,z] for (x of foo) for (y of bar) for (z of baz))",
+                             arrExpr([ident("x"), ident("y"), ident("z")]),
+                             [compOfBlock(ident("x"), ident("foo")), compOfBlock(ident("y"), ident("bar")), compOfBlock(ident("z"), ident("baz"))],
+                             null);
 
-assertExpr("( x         for (x of foo) if (p))",
-           genExpr(ident("x"), [compOfBlock(ident("x"), ident("foo"))], ident("p")));
-assertExpr("( [x,y]     for (x of foo) for (y of bar) if (p))",
-           genExpr(arrExpr([ident("x"), ident("y")]), [compOfBlock(ident("x"), ident("foo")), compOfBlock(ident("y"), ident("bar"))], ident("p")));
-assertExpr("( [x,y,z] for (x of foo) for (y of bar) for (z of baz) if (p) )",
-           genExpr(arrExpr([ident("x"), ident("y"), ident("z")]),
-                   [compOfBlock(ident("x"), ident("foo")), compOfBlock(ident("y"), ident("bar")), compOfBlock(ident("z"), ident("baz"))],
-                   ident("p")));
+assertLegacyAndModernGenExpr("( x         for (x of foo) if (p))",
+                             ident("x"), [compOfBlock(ident("x"), ident("foo"))], ident("p"));
+assertLegacyAndModernGenExpr("( [x,y]     for (x of foo) for (y of bar) if (p))",
+                             arrExpr([ident("x"), ident("y")]), [compOfBlock(ident("x"), ident("foo")), compOfBlock(ident("y"), ident("bar"))], ident("p"));
+assertLegacyAndModernGenExpr("( [x,y,z] for (x of foo) for (y of bar) for (z of baz) if (p) )",
+                             arrExpr([ident("x"), ident("y"), ident("z")]),
+                             [compOfBlock(ident("x"), ident("foo")), compOfBlock(ident("y"), ident("bar")), compOfBlock(ident("z"), ident("baz"))],
+                             ident("p"));
 
 // NOTE: it would be good to test generator expressions both with and without upvars, just like functions above.
 
 
 // let expressions
 
 assertExpr("(let (x=1) x)", letExpr([{ id: ident("x"), init: lit(1) }], ident("x")));
 assertExpr("(let (x=1,y=2) y)", letExpr([{ id: ident("x"), init: lit(1) },
@@ -1056,17 +1088,19 @@ assertGlobalExpr("(let (x) x)", 20, { le
 
 assertGlobalStmt("switch (x) { case y: }", switchStmt(ident("x"), [1]), { switchCase: function() 1 });
 assertGlobalStmt("try { } catch (e) { }", 2, { tryStatement: (function(b, g, u, f) u), catchClause: function() 2 });
 assertGlobalStmt("try { } catch (e if e instanceof A) { } catch (e if e instanceof B) { }", [2, 2], { tryStatement: (function(b, g, u, f) g), catchClause: function() 2 });
 assertGlobalStmt("try { } catch (e) { }", tryStmt(blockStmt([]), [], 2, null), { catchClause: function() 2 });
 assertGlobalStmt("try { } catch (e if e instanceof A) { } catch (e if e instanceof B) { }",
                  tryStmt(blockStmt([]), [2, 2], null, null),
                  { catchClause: function() 2 });
-assertGlobalExpr("[x for (y in z) for (x in y)]", compExpr(ident("x"), [3, 3], null), { comprehensionBlock: function() 3 });
+assertGlobalExpr("[x for (y in z) for (x in y)]",
+                 compExpr(ident("x"), [3, 3], null, "legacy"),
+                 { comprehensionBlock: function() 3 });
 
 assertGlobalExpr("({ x: y } = z)", aExpr("=", 1, ident("z")), { objectPattern: function() 1 });
 assertGlobalExpr("({ x: y } = z)", aExpr("=", objPatt([2]), ident("z")), { propertyPattern: function() 2 });
 assertGlobalExpr("[ x ] = y", aExpr("=", 3, ident("y")), { arrayPattern: function() 3 });
 
 // Ensure that exceptions thrown by builder methods propagate.
 var thrown = false;
 try {