Bug 666396 - Implemement yield*. r=jorendorff, r=Waldo

This commit is contained in:
Andy Wingo 2013-09-19 15:26:26 +02:00
parent dfb080bf26
commit f70b793cab
30 changed files with 826 additions and 69 deletions

View File

@ -273,6 +273,12 @@ EmitJump(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp op, ptrdiff_t off)
return offset;
}
static ptrdiff_t
EmitCall(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp op, uint16_t argc)
{
return Emit3(cx, bce, op, ARGC_HI(argc), ARGC_LO(argc));
}
/* XXX too many "... statement" L10N gaffes below -- fix via js.msg! */
const char js_with_statement_str[] = "with statement";
const char js_finally_block_str[] = "finally block";
@ -1642,10 +1648,10 @@ CheckSideEffects(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, bool
default:
/*
* All of PNK_INC, PNK_DEC, PNK_THROW, and PNK_YIELD have direct
* effects. Of the remaining unary-arity node types, we can't
* easily prove that the operand never denotes an object with a
* toString or valueOf method.
* All of PNK_INC, PNK_DEC, PNK_THROW, PNK_YIELD, and PNK_YIELD_STAR
* have direct effects. Of the remaining unary-arity node types, we
* can't easily prove that the operand never denotes an object with
* a toString or valueOf method.
*/
*answer = true;
return true;
@ -4892,6 +4898,157 @@ EmitReturn(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
return true;
}
static bool
EmitYieldStar(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *iter)
{
JS_ASSERT(bce->sc->isFunctionBox());
JS_ASSERT(bce->sc->asFunctionBox()->isStarGenerator());
if (!EmitTree(cx, bce, iter)) // ITER
return false;
int depth = bce->stackDepth;
JS_ASSERT(depth >= 1);
// Initial send value is undefined.
if (Emit1(cx, bce, JSOP_UNDEFINED) < 0) // ITER RECEIVED
return false;
ptrdiff_t initialSend = -1;
if (EmitBackPatchOp(cx, bce, &initialSend) < 0) // goto initialSend
return false;
// Try prologue. // ITER RESULT
StmtInfoBCE stmtInfo(cx);
PushStatementBCE(bce, &stmtInfo, STMT_TRY, bce->offset());
ptrdiff_t noteIndex = NewSrcNote(cx, bce, SRC_TRY);
if (noteIndex < 0 || Emit1(cx, bce, JSOP_TRY) < 0)
return false;
ptrdiff_t tryStart = bce->offset(); // tryStart:
JS_ASSERT(bce->stackDepth == depth + 1);
// Yield RESULT as-is, without re-boxing.
if (Emit1(cx, bce, JSOP_YIELD) < 0) // ITER RECEIVED
return false;
// Try epilogue.
if (!SetSrcNoteOffset(cx, bce, noteIndex, 0, bce->offset() - tryStart + JSOP_TRY_LENGTH))
return false;
if (NewSrcNote(cx, bce, SRC_HIDDEN) < 0)
return false;
ptrdiff_t subsequentSend = -1;
if (EmitBackPatchOp(cx, bce, &subsequentSend) < 0) // goto subsequentSend
return false;
ptrdiff_t tryEnd = bce->offset(); // tryEnd:
// Catch location.
// THROW? = 'throw' in ITER // ITER
bce->stackDepth = (unsigned) depth;
if (Emit1(cx, bce, JSOP_EXCEPTION) < 0) // ITER EXCEPTION
return false;
if (Emit1(cx, bce, JSOP_SWAP) < 0) // EXCEPTION ITER
return false;
if (Emit1(cx, bce, JSOP_DUP) < 0) // EXCEPTION ITER ITER
return false;
if (!EmitAtomOp(cx, cx->names().throw_, JSOP_STRING, bce)) // EXCEPTION ITER ITER "throw"
return false;
if (Emit1(cx, bce, JSOP_SWAP) < 0) // EXCEPTION ITER "throw" ITER
return false;
if (Emit1(cx, bce, JSOP_IN) < 0) // EXCEPTION ITER THROW?
return false;
// if (THROW?) goto delegate
ptrdiff_t checkThrow = EmitJump(cx, bce, JSOP_IFNE, 0); // EXCEPTION ITER
if (checkThrow < 0)
return false;
if (Emit1(cx, bce, JSOP_POP) < 0) // EXCEPTION
return false;
if (Emit1(cx, bce, JSOP_THROW) < 0) // throw EXCEPTION
return false;
SetJumpOffsetAt(bce, checkThrow); // delegate:
// RESULT = ITER.throw(EXCEPTION) // EXCEPTION ITER
bce->stackDepth = (unsigned) depth + 1;
if (Emit1(cx, bce, JSOP_DUP) < 0) // EXCEPTION ITER ITER
return false;
if (Emit1(cx, bce, JSOP_DUP) < 0) // EXCEPTION ITER ITER ITER
return false;
if (!EmitAtomOp(cx, cx->names().throw_, JSOP_CALLPROP, bce)) // EXCEPTION ITER ITER THROW
return false;
if (Emit1(cx, bce, JSOP_SWAP) < 0) // EXCEPTION ITER THROW ITER
return false;
if (Emit1(cx, bce, JSOP_NOTEARG) < 0) // EXCEPTION ITER THROW ITER
return false;
if (Emit2(cx, bce, JSOP_PICK, (jsbytecode)3) < 0) // ITER THROW ITER EXCEPTION
return false;
if (Emit1(cx, bce, JSOP_NOTEARG) < 0) // ITER THROW ITER EXCEPTION
return false;
if (EmitCall(cx, bce, JSOP_CALL, 1) < 0) // ITER RESULT
return false;
JS_ASSERT(bce->stackDepth == depth + 1);
ptrdiff_t checkResult = -1;
if (EmitBackPatchOp(cx, bce, &checkResult) < 0) // goto checkResult
return false;
// Catch epilogue.
if (!PopStatementBCE(cx, bce))
return false;
// This is a peace offering to ReconstructPCStack. See the note in EmitTry.
if (Emit1(cx, bce, JSOP_NOP) < 0)
return false;
if (!bce->tryNoteList.append(JSTRY_CATCH, depth, tryStart, tryEnd))
return false;
// After the try/catch block: send the received value to the iterator.
if (!BackPatch(cx, bce, initialSend, bce->code().end(), JSOP_GOTO)) // initialSend:
return false;
if (!BackPatch(cx, bce, subsequentSend, bce->code().end(), JSOP_GOTO)) // subsequentSend:
return false;
// Send location.
// result = iter.next(received) // ITER RECEIVED
if (Emit1(cx, bce, JSOP_SWAP) < 0) // RECEIVED ITER
return false;
if (Emit1(cx, bce, JSOP_DUP) < 0) // RECEIVED ITER ITER
return false;
if (Emit1(cx, bce, JSOP_DUP) < 0) // RECEIVED ITER ITER ITER
return false;
if (!EmitAtomOp(cx, cx->names().next, JSOP_CALLPROP, bce)) // RECEIVED ITER ITER NEXT
return false;
if (Emit1(cx, bce, JSOP_SWAP) < 0) // RECEIVED ITER NEXT ITER
return false;
if (Emit1(cx, bce, JSOP_NOTEARG) < 0) // RECEIVED ITER NEXT ITER
return false;
if (Emit2(cx, bce, JSOP_PICK, (jsbytecode)3) < 0) // ITER NEXT ITER RECEIVED
return false;
if (Emit1(cx, bce, JSOP_NOTEARG) < 0) // ITER NEXT ITER RECEIVED
return false;
if (EmitCall(cx, bce, JSOP_CALL, 1) < 0) // ITER RESULT
return false;
JS_ASSERT(bce->stackDepth == depth + 1);
if (!BackPatch(cx, bce, checkResult, bce->code().end(), JSOP_GOTO)) // checkResult:
return false;
// if (!result.done) goto tryStart; // ITER RESULT
if (Emit1(cx, bce, JSOP_DUP) < 0) // ITER RESULT RESULT
return false;
if (!EmitAtomOp(cx, cx->names().done, JSOP_GETPROP, bce)) // ITER RESULT DONE
return false;
// if (!DONE) goto tryStart;
if (EmitJump(cx, bce, JSOP_IFEQ, tryStart - bce->offset()) < 0) // ITER RESULT
return false;
// result.value
if (Emit1(cx, bce, JSOP_SWAP) < 0) // RESULT ITER
return false;
if (Emit1(cx, bce, JSOP_POP) < 0) // RESULT
return false;
if (!EmitAtomOp(cx, cx->names().value, JSOP_GETPROP, bce)) // VALUE
return false;
JS_ASSERT(bce->stackDepth == depth);
return true;
}
static bool
EmitStatementList(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t top)
{
@ -5194,7 +5351,7 @@ EmitCallOrNew(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
}
if (!spread) {
if (Emit3(cx, bce, pn->getOp(), ARGC_HI(argc), ARGC_LO(argc)) < 0)
if (EmitCall(cx, bce, pn->getOp(), argc) < 0)
return false;
} else {
if (Emit1(cx, bce, pn->getOp()) < 0)
@ -5889,6 +6046,10 @@ frontend::EmitTree(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
ok = EmitReturn(cx, bce, pn);
break;
case PNK_YIELD_STAR:
ok = EmitYieldStar(cx, bce, pn->pn_kid);
break;
case PNK_YIELD:
JS_ASSERT(bce->sc->isFunctionBox());
if (bce->sc->asFunctionBox()->isStarGenerator()) {

View File

@ -125,6 +125,7 @@ class UpvarCookie
F(THROW) \
F(DEBUGGER) \
F(YIELD) \
F(YIELD_STAR) \
F(GENEXP) \
F(ARRAYCOMP) \
F(ARRAYPUSH) \

View File

@ -4577,16 +4577,14 @@ Parser<ParseHandler>::yieldExpression()
pc->lastYieldOffset = begin;
bool isDelegatingYield = tokenStream.matchToken(TOK_MUL);
ParseNodeKind kind = tokenStream.matchToken(TOK_MUL) ? PNK_YIELD_STAR : PNK_YIELD;
// ES6 generators require a value.
Node exprNode = assignExpr();
if (!exprNode)
return null();
// FIXME: Plumb isDelegatingYield appropriately.
(void) isDelegatingYield;
return handler.newUnary(PNK_YIELD, JSOP_YIELD, begin, exprNode);
return handler.newUnary(kind, JSOP_NOP, begin, exprNode);
}
case NotGenerator:
@ -4648,7 +4646,7 @@ Parser<ParseHandler>::yieldExpression()
return null();
}
return handler.newUnary(PNK_YIELD, JSOP_YIELD, begin, exprNode);
return handler.newUnary(PNK_YIELD, JSOP_NOP, begin, exprNode);
}
}
@ -6094,7 +6092,7 @@ Parser<FullParseHandler>::generatorExpr(ParseNode *kid)
ParseNode *pn = UnaryNode::create(PNK_YIELD, &handler);
if (!pn)
return null();
pn->setOp(JSOP_YIELD);
pn->setOp(JSOP_NOP);
pn->setInParens(true);
pn->pn_pos = kid->pn_pos;
pn->pn_kid = kid;

View File

@ -0,0 +1,14 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
load(libdir + "asserts.js");
if (typeof assertIteratorResult === 'undefined') {
var assertIteratorResult = function assertIteratorResult(result, value, done) {
assertEq(typeof result, "object");
assertDeepEq(result.value, value);
assertDeepEq(result.done, done);
}
}

View File

@ -46,3 +46,6 @@ check('m().next();', 'declarative', gw.makeDebuggeeValue(g.m));
g.eval('function n() { let (x = 1) { debugger; } }');
check('n()', 'declarative', null);
g.eval('function* o() { debugger; yield true; }');
check('o().next();', 'declarative', gw.makeDebuggeeValue(g.o));

View File

@ -5,10 +5,6 @@ var g = newGlobal();
var dbg = new Debugger;
var gw = dbg.addDebuggee(g);
g.eval('function f(x) { debugger; yield x; }');
g.eval('var g = f(2);');
g.eval('var h = f(3);');
function check(gen, label) {
print("check(" + label + ")");
var hits;
@ -22,5 +18,14 @@ function check(gen, label) {
assertEq(hits, 1);
}
g.eval('function f(x) { debugger; yield x; }');
g.eval('var g = f(2);');
g.eval('var h = f(3);');
check(g.g, 'g.g');
check(g.h, 'g.h');
g.eval('function* f(x) { debugger; yield x; }');
g.eval('var g = f(2);');
g.eval('var h = f(3);');
check(g.g, 'g.g');
check(g.h, 'g.h');

View File

@ -29,3 +29,6 @@ test("new function () { eval('debugger'); }", {type: "eval", generator: false, c
test("function gen() { debugger; yield 1; debugger; }\n" +
"for (var x in gen()) {}\n",
{type: "call", generator: true, constructing: false}, 2);
test("var iter = (function* stargen() { debugger; yield 1; debugger; })();\n" +
"iter.next(); iter.next();",
{type: "call", generator: true, constructing: false}, 2);

View File

@ -0,0 +1,12 @@
// yield is not allowed in eval in a star generator.
load(libdir + 'asserts.js');
var g = newGlobal();
var dbg = new Debugger(g);
dbg.onDebuggerStatement = function (frame) {
assertThrowsInstanceOf(function() { frame.eval('yield 10;') }, SyntaxError);
};
g.eval("(function*g(){ debugger; })()");

View File

@ -0,0 +1,20 @@
// Returning {throw:} from an onPop handler when yielding works and
// does not close the generator-iterator.
load(libdir + "iteration.js");
var g = newGlobal();
var dbg = new Debugger;
var gw = dbg.addDebuggee(g);
dbg.onDebuggerStatement = function handleDebugger(frame) {
frame.onPop = function (c) {
return {throw: "fit"};
};
};
g.eval("function* g() { for (var i = 0; i < 10; i++) { debugger; yield i; } }");
g.eval("var it = g();");
var rv = gw.evalInGlobal("it.next();");
assertEq(rv.throw, "fit");
dbg.enabled = false;
assertIteratorResult(g.it.next(), 1, false);

View File

@ -0,0 +1,19 @@
// Throwing an exception from an onPop handler when yielding terminates the debuggee
// but does not close the generator-iterator.
load(libdir + 'iteration.js')
var g = newGlobal();
var dbg = new Debugger;
var gw = dbg.addDebuggee(g);
dbg.onDebuggerStatement = function handleDebugger(frame) {
frame.onPop = function (c) {
throw "fit";
};
};
g.eval("function* g() { for (var i = 0; i < 10; i++) { debugger; yield i; } }");
g.eval("var it = g();");
assertEq(gw.evalInGlobal("it.next();"), null);
dbg.enabled = false;
assertIteratorResult(g.it.next(), 1, false);

View File

@ -0,0 +1,42 @@
// Each resumption of an ES6 generator gets a fresh frame, whose onPop
// handler fires the next time the generator yields. This is not the
// behavior the spec requests, but it's what we do for the moment, and
// it's good to check that at least we don't crash.
load(libdir + 'iteration.js');
var g = newGlobal();
var dbg = new Debugger(g);
var log;
var debuggerFrames = [];
var poppedFrames = [];
dbg.onDebuggerStatement = function handleDebugger(frame) {
log += 'd';
assertEq(frame.type, "call");
assertEq(debuggerFrames.indexOf(frame), -1);
assertEq(poppedFrames.indexOf(frame), -1);
debuggerFrames.push(frame);
if (frame.eval('i').return % 3 == 0) {
frame.onPop = function handlePop(c) {
log += ')' + c.return.value;
assertEq(debuggerFrames.indexOf(this) != -1, true);
assertEq(poppedFrames.indexOf(this), -1);
poppedFrames.push(this);
};
}
};
g.eval("function* g() { for (var i = 0; i < 10; i++) { debugger; yield i; } }");
log ='';
g.eval("var t = 0, iter = g();");
for (var j = 0; j < 10; j++)
g.eval("t += iter.next().value;");
assertIteratorResult(g.eval("iter.next()"), undefined, true);
assertEq(g.eval("t"), 45);
// FIXME: Should equal this, but see bug 917809.
// assertEq(log, "d)0ddd)3ddd)6ddd)9");
assertEq(log, "d)undefinedddd)undefinedddd)undefinedddd)undefined");

View File

@ -38,5 +38,7 @@ test("var obj = {get x() { return 0; }, set x(v) {}}; debugger;",
"S[SS]");
test("function r(n) { for (var i = 0; i < n; i++) yield i; } debugger;",
"S[S]");
test("function* qux(n) { for (var i = 0; i < n; i++) yield i; } debugger;",
"S[S]");
test("var it = (3 for (p in obj)); debugger;",
"S[S]");

View File

@ -0,0 +1,40 @@
// Setting a breakpoint in a generator function works, and we can
// traverse the stack and evaluate expressions in the context of older
// generator frames.
var g = newGlobal();
var dbg = Debugger(g);
dbg.onDebuggerStatement = function (frame) {
function hit(frame) {
assertEq(frame.generator, true);
assertEq(frame.older.generator, true);
frame.older.eval("q += 16");
}
var s = frame.script;
var offs = s.getLineOffsets(g.line0 + 9);
for (var i = 0; i < offs.length; i++)
s.setBreakpoint(offs[i], {hit: hit});
};
g.eval("line0 = Error().lineNumber;\n" +
"function* g(x) {\n" + // + 1
" var q = 10;\n" + // + 2
" yield* x;\n" + // + 3
" return q;\n" + // + 4
"}\n" + // + 5
"function* range(n) {\n" + // + 6
" debugger;\n" + // + 7
" for (var i = 0; i < n; i++)\n" + // + 8
" yield i;\n" + // + 9 <-- breakpoint
" return;\n" + // so that line 9 only has the yield
"}");
g.eval("var iter = g(range(2))");
g.eval("var first = iter.next().value");
g.eval("var second = iter.next().value");
g.eval("var third = iter.next().value");
assertEq(g.first, 0);
assertEq(g.second, 1);
assertEq(g.third, 42);

View File

@ -49,6 +49,9 @@ test(function () { g.clone(evaluate("(function(x) { return x + 1; })", {compileA
// eval declaring a generator
test(function () { g.eval("function r(n) { for (var i=0;i<n;i++) yield i; }"); });
// eval declaring a star generator
test(function () { g.eval("function* sg(n) { for (var i=0;i<n;i++) yield i; }"); });
// eval with a generator-expression
test(function () { g.eval("var it = (obj[p] for (p in obj));"); });

View File

@ -0,0 +1,20 @@
// |jit-test| debug
// Forced return from a star generator frame.
load(libdir + 'asserts.js')
load(libdir + 'iteration.js')
var g = newGlobal();
g.debuggeeGlobal = this;
g.eval("var dbg = new Debugger(debuggeeGlobal);" +
"dbg.onDebuggerStatement = function () { return {return: '!'}; };");
function* gen() {
yield '1';
debugger; // Force return here. The value is ignored.
yield '2';
}
var iter = gen();
assertIteratorResult(iter.next(), '1', false);
assertEq(iter.next(), '!');
assertThrowsInstanceOf(iter.next.bind(iter), TypeError);

View File

@ -1803,12 +1803,12 @@ NativeMethod(JSContext *cx, unsigned argc, Value *vp)
}
#define JSPROP_ROPERM (JSPROP_READONLY | JSPROP_PERMANENT)
#define JS_METHOD(name, T, impl, len, perms) JS_FN(name, (NativeMethod<T,impl>), len, perms)
#define JS_METHOD(name, T, impl, len, attrs) JS_FN(name, (NativeMethod<T,impl>), len, attrs)
static const JSFunctionSpec star_generator_methods[] = {
JS_FN("iterator", iterator_iterator, 0, 0),
JS_METHOD("next", StarGeneratorObject, star_generator_next, 1, JSPROP_ROPERM),
JS_METHOD("throw", StarGeneratorObject, star_generator_throw, 1, JSPROP_ROPERM),
JS_METHOD("next", StarGeneratorObject, star_generator_next, 1, 0),
JS_METHOD("throw", StarGeneratorObject, star_generator_throw, 1, 0),
JS_FS_END
};

View File

@ -96,6 +96,8 @@ static char const * const callbackNames[] = {
NULL
};
enum YieldKind { Delegating, NotDelegating };
typedef AutoValueVector NodeVector;
/*
@ -601,7 +603,7 @@ class NodeBuilder
bool thisExpression(TokenPos *pos, MutableHandleValue dst);
bool yieldExpression(HandleValue arg, 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);
@ -1242,13 +1244,23 @@ NodeBuilder::thisExpression(TokenPos *pos, MutableHandleValue dst)
}
bool
NodeBuilder::yieldExpression(HandleValue arg, TokenPos *pos, MutableHandleValue dst)
NodeBuilder::yieldExpression(HandleValue arg, YieldKind kind, TokenPos *pos, MutableHandleValue dst)
{
RootedValue cb(cx, callbacks[AST_YIELD_EXPR]);
if (!cb.isNull())
return callback(cb, opt(arg), pos, dst);
RootedValue delegateVal(cx);
return newNode(AST_YIELD_EXPR, pos, "argument", arg, dst);
switch (kind) {
case Delegating:
delegateVal = BooleanValue(true);
break;
case NotDelegating:
delegateVal = BooleanValue(false);
break;
}
if (!cb.isNull())
return callback(cb, opt(arg), delegateVal, pos, dst);
return newNode(AST_YIELD_EXPR, pos, "argument", arg, "delegate", delegateVal, dst);
}
bool
@ -2597,13 +2609,22 @@ ASTSerializer::expression(ParseNode *pn, MutableHandleValue dst)
case PNK_NULL:
return literal(pn, dst);
case PNK_YIELD_STAR:
{
JS_ASSERT(pn->pn_pos.encloses(pn->pn_kid->pn_pos));
RootedValue arg(cx);
return expression(pn->pn_kid, &arg) &&
builder.yieldExpression(arg, Delegating, &pn->pn_pos, dst);
}
case PNK_YIELD:
{
JS_ASSERT_IF(pn->pn_kid, pn->pn_pos.encloses(pn->pn_kid->pn_pos));
RootedValue arg(cx);
return optExpression(pn->pn_kid, &arg) &&
builder.yieldExpression(arg, &pn->pn_pos, dst);
builder.yieldExpression(arg, NotDelegating, &pn->pn_pos, dst);
}
case PNK_ARRAYCOMP:

View File

@ -0,0 +1,38 @@
// This file was written by Andy Wingo <wingo@igalia.com> and originally
// contributed to V8 as generators-objects.js, available here:
//
// http://code.google.com/p/v8/source/browse/branches/bleeding_edge/test/mjsunit/harmony/generators-objects.js
// Test that yield* re-yields received results without re-boxing.
function results(results) {
var i = 0;
function next() {
return results[i++];
}
return { next: next }
}
function* yield_results(expected) {
return yield* results(expected);
}
function collect_results(iter) {
var ret = [];
var result;
do {
result = iter.next();
ret.push(result);
} while (!result.done);
return ret;
}
// We have to put a full result for the end, because the return will re-box.
var expected = [{value: 1}, 13, "foo", {value: 34, done: true}];
// Sanity check.
assertDeepEq(expected, collect_results(results(expected)));
assertDeepEq(expected, collect_results(yield_results(expected)));
if (typeof reportCompare == "function")
reportCompare(true, true);

View File

@ -0,0 +1,49 @@
// Errors accessing next, done, or value don't cause an exception to be
// thrown into the iterator of a yield*.
function* g(n) { for (var i=0; i<n; i++) yield i; }
function* delegate(iter) { return yield* iter; }
var log = "", inner, outer;
// That var is poisoooooon, p-poison poison...
var Poison = new Error;
function log_calls(method) {
return function () {
log += "x"
return method.call(this);
}
}
function poison(receiver, prop) {
Object.defineProperty(receiver, prop, { get: function () { throw Poison } });
}
// Poison inner.next.
inner = g(10);
outer = delegate(inner);
inner.throw = log_calls(inner.throw);
poison(inner, 'next')
assertThrowsValue(outer.next.bind(outer), Poison);
assertEq(log, "");
// Poison result value from inner.
inner = g(10);
outer = delegate(inner);
inner.next = function () { return { done: true, get value() { throw Poison} } };
inner.throw = log_calls(inner.throw);
assertThrowsValue(outer.next.bind(outer), Poison);
assertEq(log, "");
// Poison result done from inner.
inner = g(10);
outer = delegate(inner);
inner.next = function () { return { get done() { throw Poison }, value: 42 } };
inner.throw = log_calls(inner.throw);
assertThrowsValue(outer.next.bind(outer), Poison);
assertEq(log, "");
// mischief managed.
if (typeof reportCompare == "function")
reportCompare(true, true);

View File

@ -0,0 +1,19 @@
// The first call to yield* passes one arg to "next".
function Iter() {
function next() {
if (arguments.length != 1)
throw Error;
return { value: 42, done: true }
}
this.next = next;
}
function* delegate(iter) { return yield* iter; }
var iter = delegate(new Iter());
assertDeepEq(iter.next(), {value:42, done:true});
if (typeof reportCompare == "function")
reportCompare(true, true);

View File

@ -0,0 +1,71 @@
// Test yield* with iter.throw and monkeypatching.
function* g1() { return (yield 1); }
function* g2() { try { yield 1; } catch (e) { yield e; } }
function* delegate(iter) { return yield* iter; }
var GeneratorObjectPrototype = Object.getPrototypeOf(g1).prototype;
var GeneratorObjectPrototype_throw = GeneratorObjectPrototype.throw;
// An uncaught delegated throw.
var inner = g1();
var outer = delegate(inner);
assertIteratorResult(1, false, outer.next());
assertThrowsValue(function () { outer.throw(42) }, 42);
assertThrowsInstanceOf(function () { outer.throw(42) }, TypeError);
// A caught delegated throw.
inner = g2();
outer = delegate(inner);
assertIteratorResult(1, false, outer.next());
assertIteratorResult(42, false, outer.throw(42));
assertThrowsValue(function () { outer.throw(42) }, 42);
assertThrowsInstanceOf(function () { outer.throw(42) }, TypeError);
// What would be an uncaught delegated throw, but with a monkeypatched iterator.
inner = g1();
outer = delegate(inner);
assertIteratorResult(1, false, outer.next());
inner.throw = function(e) { return e*2; };
assertEq(84, outer.throw(42));
assertIteratorResult(undefined, true, outer.next());
// Monkeypatching inner.next.
inner = g1();
outer = delegate(inner);
inner.next = function() { return { value: 13, done: true } };
assertIteratorResult(13, true, outer.next());
// What would be a caught delegated throw, but with a monkeypunched prototype.
inner = g2();
outer = delegate(inner);
assertIteratorResult(1, false, outer.next());
delete GeneratorObjectPrototype.throw;
var outer_throw_42 = GeneratorObjectPrototype_throw.bind(outer, 42);
assertThrowsValue(outer_throw_42, 42);
assertThrowsInstanceOf(outer_throw_42, TypeError);
// Monkeypunch a different throw handler.
inner = g2();
outer = delegate(inner);
outer_throw_42 = GeneratorObjectPrototype_throw.bind(outer, 42);
assertIteratorResult(1, false, outer.next());
GeneratorObjectPrototype.throw = function(e) { return e*2; }
assertEq(84, outer_throw_42());
assertEq(84, outer_throw_42());
// This continues indefinitely.
assertEq(84, outer_throw_42());
assertIteratorResult(undefined, true, outer.next());
// The same, but restoring the original pre-monkey throw.
inner = g2();
outer = delegate(inner);
outer_throw_42 = GeneratorObjectPrototype_throw.bind(outer, 42);
assertIteratorResult(1, false, outer.next());
assertEq(84, outer_throw_42());
assertEq(84, outer_throw_42());
GeneratorObjectPrototype.throw = GeneratorObjectPrototype_throw;
assertIteratorResult(42, false, outer_throw_42());
assertIteratorResult(undefined, true, outer.next());
if (typeof reportCompare == "function")
reportCompare(true, true);

View File

@ -0,0 +1,40 @@
// Test yield* with iter.next and monkeypatching.
function* g(n) { for (var i=0; i<n; i++) yield i; }
function* delegate(iter) { return yield* iter; }
var GeneratorObjectPrototype = Object.getPrototypeOf(g).prototype;
var GeneratorObjectPrototype_next = GeneratorObjectPrototype.next;
// Monkeypatch next on an iterator.
var inner = g(20);
var outer = delegate(inner);
assertIteratorResult(0, false, outer.next());
assertIteratorResult(1, false, outer.next());
inner.next = function() { return 0; };
// 42 yielded directly without re-boxing.
assertEq(0, outer.next());
// Outer generator not terminated.
assertEq(0, outer.next());
// Restore.
inner.next = GeneratorObjectPrototype_next;
assertIteratorResult(2, false, outer.next());
// Repatch.
inner.next = function() { return { value: 42, done: true }; };
assertIteratorResult(42, true, outer.next());
// Monkeypunch next on the prototype.
var inner = g(20);
var outer = delegate(inner);
assertIteratorResult(0, false, outer.next());
assertIteratorResult(1, false, outer.next());
GeneratorObjectPrototype.next = function() { return 0; };
// 42 yielded directly without re-boxing.
assertEq(0, GeneratorObjectPrototype_next.call(outer));
// Outer generator not terminated.
assertEq(0, GeneratorObjectPrototype_next.call(outer));
// Restore.
GeneratorObjectPrototype.next = GeneratorObjectPrototype_next;
assertIteratorResult(2, false, outer.next());
if (typeof reportCompare == "function")
reportCompare(true, true);

View File

@ -0,0 +1,18 @@
// With yield*, inner and outer iterators can be invoked separately.
function* g(n) { for (var i=0; i<n; i++) yield i; }
function* delegate(iter) { return yield* iter; }
var inner = g(20);
var outer1 = delegate(inner);
var outer2 = delegate(inner);
assertIteratorResult(0, false, outer1.next());
assertIteratorResult(1, false, outer2.next());
assertIteratorResult(2, false, inner.next());
assertIteratorResult(3, false, outer1.next());
assertIteratorResult(4, false, outer2.next());
assertIteratorResult(5, false, inner.next());
if (typeof reportCompare == "function")
reportCompare(true, true);

View File

@ -0,0 +1,33 @@
// Test that a deep yield* chain re-yields received results without
// re-boxing.
function results(results) {
var i = 0;
function next() {
return results[i++];
}
return { next: next }
}
function* yield_results(expected, n) {
return yield* n ? yield_results(expected, n - 1) : results(expected);
}
function collect_results(iter) {
var ret = [];
var result;
do {
result = iter.next();
ret.push(result);
} while (!result.done);
return ret;
}
// We have to put a full result for the end, because the return will re-box.
var expected = [{value: 1}, 13, "foo", {value: 34, done: true}];
assertDeepEq(expected, collect_results(results(expected)));
assertDeepEq(expected, collect_results(yield_results(expected, 20)));
if (typeof reportCompare == "function")
reportCompare(true, true);

View File

@ -0,0 +1,49 @@
// Test that each yield* loop just checks "done", and "value" is only
// fetched once at the end.
var log = "";
function collect_results(iter) {
var ret = [];
var result;
do {
result = iter.next();
ret.push(result);
} while (!result.done);
return ret;
}
function Iter(val, count) {
function next() {
return {
get done() { log += "d"; return count-- == 0; },
get value() { log += "v"; return val; }
}
}
this.next = next;
}
function* delegate(iter) { return yield* iter; }
var inner = new Iter(42, 5);
var outer = delegate(inner);
// Five values, and one terminal value.
outer.next();
outer.next();
outer.next();
outer.next();
outer.next();
outer.next();
assertEq(log, "ddddddv");
// Outer's dead, man. Outer's dead.
assertThrowsInstanceOf(outer.next.bind(outer), TypeError);
// No more checking the iterator.
assertEq(log, "ddddddv");
if (typeof reportCompare == "function")
reportCompare(true, true);

View File

@ -0,0 +1,33 @@
// The iteratee of yield* can be a proxy.
function results(results) {
var i = 0;
function next() {
return results[i++];
}
return { next: next }
}
function* yield_results(expected) {
return yield* Proxy(results(expected), {});
}
function collect_results(iter) {
var ret = [];
var result;
do {
result = iter.next();
ret.push(result);
} while (!result.done);
return ret;
}
// We have to put a full result for the end, because the return will re-box.
var expected = [{value: 1}, 13, "foo", {value: 34, done: true}];
// Sanity check.
assertDeepEq(expected, collect_results(results(expected)));
assertDeepEq(expected, collect_results(yield_results(expected)));
if (typeof reportCompare == "function")
reportCompare(true, true);

View File

@ -0,0 +1,44 @@
// Test that yield* can appear in a loop, and alongside yield.
function* countdown(n) {
while (n > 0) {
yield n;
yield* countdown(--n);
}
return 34;
}
function collect_results(iter) {
var ret = [];
var result;
do {
result = iter.next();
ret.push(result);
} while (!result.done);
return ret;
}
var expected = [
// yield in countdown(3), n == 3
{value: 3, done: false},
// yield in yield* countdown(2), n == 2
{value: 2, done: false},
// yield in nested yield* countdown(1), n == 1
{value: 1, done: false},
// countdown(0) yields no values
// second go-through of countdown(2) loop, n == 1
{value: 1, done: false},
// second go-through of countdown(3) loop, n == 2
{value: 2, done: false},
// yield in yield* countdown(1), n == 1
{value: 1, done: false},
// third go-through of countdown(3) loop, n == 1
{value: 1, done: false},
// done
{value: 34, done: true}
];
assertDeepEq(expected, collect_results(countdown(3)));
if (typeof reportCompare == "function")
reportCompare(true, true);

View File

@ -0,0 +1,37 @@
// Test that yield* can appear in a loop, and inside yield.
function* countdown(n) {
while (n > 0) {
yield (yield* countdown(--n));
}
return 34;
}
function collect_results(iter) {
var ret = [];
var result;
do {
result = iter.next();
ret.push(result);
} while (!result.done);
return ret;
}
var expected = [
// Only 34 yielded from the "yield" and the last return make it out.
// Three yields in countdown(3), two in countdown(2), and one in
// countdown(1) (called twice).
{value: 34, done: false},
{value: 34, done: false},
{value: 34, done: false},
{value: 34, done: false},
{value: 34, done: false},
{value: 34, done: false},
{value: 34, done: false},
{value: 34, done: true}, // final
];
assertDeepEq(collect_results(countdown(3)), expected);
if (typeof reportCompare == "function")
reportCompare(true, true);

View File

@ -58,11 +58,9 @@ function TestGenerator(g, expected_values_for_next,
testSend(g);
testThrow(g);
// FIXME: Implement yield*. Bug 907738.
//
// testNext(function*() { return yield* g(); });
// testSend(function*() { return yield* g(); });
// testThrow(function*() { return yield* g(); });
testNext(function*() { return yield* g(); });
testSend(function*() { return yield* g(); });
testThrow(function*() { return yield* g(); });
if (g instanceof GeneratorFunction) {
testNext(function() { return new g(); });
@ -332,39 +330,6 @@ TestGenerator(
"foo",
[42, undefined]);
/* FIXME: Implement yield*. Bug 907738.
// Test that yield* re-yields received results without re-boxing.
function TestDelegatingYield() {
function results(results) {
var i = 0;
function next() {
return results[i++];
}
return { next: next }
}
function* yield_results(expected) {
return yield* results(expected);
}
function collect_results(iter) {
var ret = [];
var result;
do {
result = iter.next();
ret.push(result);
} while (!result.done);
return ret;
}
// We have to put a full result for the end, because the return will re-box.
var expected = [{value: 1}, 13, "foo", {value: 34, done: true}];
// Sanity check.
assertDeepEq(expected, collect_results(results(expected)));
assertDeepEq(expected, collect_results(yield_results(expected)));
}
TestDelegatingYield();
*/
function TestTryCatch(instantiate) {
function* g() { yield 1; try { yield 2; } catch (e) { yield e; } yield 3; }
function Sentinel() {}
@ -425,8 +390,7 @@ function TestTryCatch(instantiate) {
Test6(instantiate(g));
}
TestTryCatch(function (g) { return g(); });
// FIXME: Implement yield*. Bug 907738.
// TestTryCatch(function* (g) { return yield* g(); });
TestTryCatch(function* (g) { return yield* g(); });
function TestTryFinally(instantiate) {
function* g() { yield 1; try { yield 2; } finally { yield 3; } yield 4; }
@ -495,8 +459,7 @@ function TestTryFinally(instantiate) {
Test7(instantiate(g));
}
TestTryFinally(function (g) { return g(); });
// FIXME: Implement yield*. Bug 907738.
// TestTryFinally(function* (g) { return yield* g(); });
TestTryFinally(function* (g) { return yield* g(); });
function TestNestedTry(instantiate) {
function* g() {
@ -586,8 +549,7 @@ function TestNestedTry(instantiate) {
// That's probably enough.
}
TestNestedTry(function (g) { return g(); });
// FIXME: Implement yield*. Bug 907738.
// TestNestedTry(function* (g) { return yield* g(); });
TestNestedTry(function* (g) { return yield* g(); });
function TestRecursion() {
function TestNextRecursion() {

View File

@ -3352,7 +3352,7 @@ default:
switch (tn->kind) {
case JSTRY_CATCH:
JS_ASSERT(*regs.pc == JSOP_ENTERBLOCK);
JS_ASSERT(*regs.pc == JSOP_ENTERBLOCK || *regs.pc == JSOP_EXCEPTION);
/* Catch cannot intercept the closing of a generator. */
if (JS_UNLIKELY(cx->getPendingException().isMagic(JS_GENERATOR_CLOSING)))