[generators] Implement Generator.prototype.return.

Note: This is currently only used by yield*, we still need to support it in
other places (such as for-of loops).  It can be used manually of course.

(This CL does not touch the full-codegen implementation of yield* because that
code is already dead.  The yield* desugaring already supports return and doesn't
need to be touched.)

BUG=v8:3566
LOG=y

Review URL: https://codereview.chromium.org/1639343005

Cr-Commit-Position: refs/heads/master@{#33744}
This commit is contained in:
neis 2016-02-04 09:13:40 -08:00 committed by Commit bot
parent 813f48ff7e
commit dbd8640813
18 changed files with 415 additions and 37 deletions

View File

@ -2564,6 +2564,7 @@ void AstGraphBuilder::VisitCallRuntime(CallRuntime* expr) {
// TODO(mstarzinger): This bailout is a gigantic hack, the owner is ashamed.
if (function->function_id == Runtime::kInlineGeneratorNext ||
function->function_id == Runtime::kInlineGeneratorReturn ||
function->function_id == Runtime::kInlineGeneratorThrow) {
ast_context()->ProduceValue(jsgraph()->TheHoleConstant());
return SetStackOverflow();

View File

@ -1937,8 +1937,17 @@ void FullCodeGenerator::VisitYield(Yield* expr) {
__ jmp(&suspend);
__ bind(&continuation);
// When we arrive here, the stack top is the resume mode and
// result_register() holds the input value (the argument given to the
// respective resume operation).
__ RecordGeneratorContinuation();
__ jmp(&resume);
__ pop(r1);
__ cmp(r1, Operand(Smi::FromInt(JSGeneratorObject::RETURN)));
__ b(ne, &resume);
__ push(result_register());
EmitCreateIteratorResult(true);
EmitUnwindBeforeReturn();
EmitReturnSequence();
__ bind(&suspend);
VisitForAccumulatorValue(expr->generator_object());
@ -2160,6 +2169,7 @@ void FullCodeGenerator::EmitGeneratorResume(Expression *generator,
__ add(r3, r3, r2);
__ mov(r2, Operand(Smi::FromInt(JSGeneratorObject::kGeneratorExecuting)));
__ str(r2, FieldMemOperand(r1, JSGeneratorObject::kContinuationOffset));
__ Push(Smi::FromInt(resume_mode)); // Consumed in continuation.
__ Jump(r3);
}
__ bind(&slow_resume);
@ -2174,6 +2184,7 @@ void FullCodeGenerator::EmitGeneratorResume(Expression *generator,
__ push(r2);
__ b(&push_operand_holes);
__ bind(&call_resume);
__ Push(Smi::FromInt(resume_mode)); // Consumed in continuation.
DCHECK(!result_register().is(r1));
__ Push(r1, result_register());
__ Push(Smi::FromInt(resume_mode));

View File

@ -4343,8 +4343,17 @@ void FullCodeGenerator::VisitYield(Yield* expr) {
// looks at its pos(). Is it possible to do something more efficient here,
// perhaps using Adr?
__ Bind(&continuation);
// When we arrive here, the stack top is the resume mode and
// result_register() holds the input value (the argument given to the
// respective resume operation).
__ RecordGeneratorContinuation();
__ B(&resume);
__ Pop(x1);
__ Cmp(x1, Smi::FromInt(JSGeneratorObject::RETURN));
__ B(ne, &resume);
__ Push(result_register());
EmitCreateIteratorResult(true);
EmitUnwindBeforeReturn();
EmitReturnSequence();
__ Bind(&suspend);
VisitForAccumulatorValue(expr->generator_object());
@ -4571,6 +4580,7 @@ void FullCodeGenerator::EmitGeneratorResume(Expression *generator,
__ Mov(x12, Smi::FromInt(JSGeneratorObject::kGeneratorExecuting));
__ Str(x12, FieldMemOperand(generator_object,
JSGeneratorObject::kContinuationOffset));
__ Push(Smi::FromInt(resume_mode)); // Consumed in continuation.
__ Br(x10);
__ Bind(&slow_resume);
@ -4581,6 +4591,7 @@ void FullCodeGenerator::EmitGeneratorResume(Expression *generator,
__ PushMultipleTimes(the_hole, operand_stack_size);
__ Mov(x10, Smi::FromInt(resume_mode));
__ Push(Smi::FromInt(resume_mode)); // Consumed in continuation.
__ Push(generator_object, result_register(), x10);
__ CallRuntime(Runtime::kResumeJSGeneratorObject);
// Not reached: the runtime call returns elsewhere.

View File

@ -633,6 +633,13 @@ void FullCodeGenerator::EmitGeneratorNext(CallRuntime* expr) {
}
void FullCodeGenerator::EmitGeneratorReturn(CallRuntime* expr) {
ZoneList<Expression*>* args = expr->arguments();
DCHECK(args->length() == 2);
EmitGeneratorResume(args->at(0), args->at(1), JSGeneratorObject::RETURN);
}
void FullCodeGenerator::EmitGeneratorThrow(CallRuntime* expr) {
ZoneList<Expression*>* args = expr->arguments();
DCHECK(args->length() == 2);

View File

@ -496,6 +496,7 @@ class FullCodeGenerator: public AstVisitor {
F(GetSuperConstructor) \
F(FastOneByteArrayJoin) \
F(GeneratorNext) \
F(GeneratorReturn) \
F(GeneratorThrow) \
F(DebugBreakInOptimizedCode) \
F(ClassOf) \

View File

@ -1846,8 +1846,17 @@ void FullCodeGenerator::VisitYield(Yield* expr) {
__ jmp(&suspend);
__ bind(&continuation);
// When we arrive here, the stack top is the resume mode and
// result_register() holds the input value (the argument given to the
// respective resume operation).
__ RecordGeneratorContinuation();
__ jmp(&resume);
__ pop(ebx);
__ cmp(ebx, Immediate(Smi::FromInt(JSGeneratorObject::RETURN)));
__ j(not_equal, &resume);
__ push(result_register());
EmitCreateIteratorResult(true);
EmitUnwindBeforeReturn();
EmitReturnSequence();
__ bind(&suspend);
VisitForAccumulatorValue(expr->generator_object());
@ -2060,6 +2069,7 @@ void FullCodeGenerator::EmitGeneratorResume(Expression *generator,
__ add(edx, ecx);
__ mov(FieldOperand(ebx, JSGeneratorObject::kContinuationOffset),
Immediate(Smi::FromInt(JSGeneratorObject::kGeneratorExecuting)));
__ Push(Smi::FromInt(resume_mode)); // Consumed in continuation.
__ jmp(edx);
__ bind(&slow_resume);
}
@ -2073,6 +2083,7 @@ void FullCodeGenerator::EmitGeneratorResume(Expression *generator,
__ push(ecx);
__ jmp(&push_operand_holes);
__ bind(&call_resume);
__ Push(Smi::FromInt(resume_mode)); // Consumed in continuation.
__ push(ebx);
__ push(result_register());
__ Push(Smi::FromInt(resume_mode));

View File

@ -1935,8 +1935,17 @@ void FullCodeGenerator::VisitYield(Yield* expr) {
__ jmp(&suspend);
__ bind(&continuation);
// When we arrive here, the stack top is the resume mode and
// result_register() holds the input value (the argument given to the
// respective resume operation).
__ RecordGeneratorContinuation();
__ jmp(&resume);
__ pop(a1);
__ Branch(&resume, ne, a1,
Operand(Smi::FromInt(JSGeneratorObject::RETURN)));
__ push(result_register());
EmitCreateIteratorResult(true);
EmitUnwindBeforeReturn();
EmitReturnSequence();
__ bind(&suspend);
VisitForAccumulatorValue(expr->generator_object());
@ -2152,6 +2161,7 @@ void FullCodeGenerator::EmitGeneratorResume(Expression *generator,
__ Addu(a3, a3, Operand(a2));
__ li(a2, Operand(Smi::FromInt(JSGeneratorObject::kGeneratorExecuting)));
__ sw(a2, FieldMemOperand(a1, JSGeneratorObject::kContinuationOffset));
__ Push(Smi::FromInt(resume_mode)); // Consumed in continuation.
__ Jump(a3);
__ bind(&slow_resume);
}
@ -2165,6 +2175,7 @@ void FullCodeGenerator::EmitGeneratorResume(Expression *generator,
__ push(a2);
__ Branch(&push_operand_holes);
__ bind(&call_resume);
__ Push(Smi::FromInt(resume_mode)); // Consumed in continuation.
DCHECK(!result_register().is(a1));
__ Push(a1, result_register());
__ Push(Smi::FromInt(resume_mode));

View File

@ -1936,8 +1936,17 @@ void FullCodeGenerator::VisitYield(Yield* expr) {
__ jmp(&suspend);
__ bind(&continuation);
// When we arrive here, the stack top is the resume mode and
// result_register() holds the input value (the argument given to the
// respective resume operation).
__ RecordGeneratorContinuation();
__ jmp(&resume);
__ pop(a1);
__ Branch(&resume, ne, a1,
Operand(Smi::FromInt(JSGeneratorObject::RETURN)));
__ push(result_register());
EmitCreateIteratorResult(true);
EmitUnwindBeforeReturn();
EmitReturnSequence();
__ bind(&suspend);
VisitForAccumulatorValue(expr->generator_object());
@ -2154,6 +2163,7 @@ void FullCodeGenerator::EmitGeneratorResume(Expression *generator,
__ Daddu(a3, a3, Operand(a2));
__ li(a2, Operand(Smi::FromInt(JSGeneratorObject::kGeneratorExecuting)));
__ sd(a2, FieldMemOperand(a1, JSGeneratorObject::kContinuationOffset));
__ Push(Smi::FromInt(resume_mode)); // Consumed in continuation.
__ Jump(a3);
__ bind(&slow_resume);
}
@ -2167,6 +2177,7 @@ void FullCodeGenerator::EmitGeneratorResume(Expression *generator,
__ push(a2);
__ Branch(&push_operand_holes);
__ bind(&call_resume);
__ Push(Smi::FromInt(resume_mode)); // Consumed in continuation.
DCHECK(!result_register().is(a1));
__ Push(a1, result_register());
__ Push(Smi::FromInt(resume_mode));

View File

@ -1868,8 +1868,17 @@ void FullCodeGenerator::VisitYield(Yield* expr) {
__ jmp(&suspend);
__ bind(&continuation);
// When we arrive here, the stack top is the resume mode and
// result_register() holds the input value (the argument given to the
// respective resume operation).
__ RecordGeneratorContinuation();
__ jmp(&resume);
__ Pop(rbx);
__ SmiCompare(rbx, Smi::FromInt(JSGeneratorObject::RETURN));
__ j(not_equal, &resume);
__ Push(result_register());
EmitCreateIteratorResult(true);
EmitUnwindBeforeReturn();
EmitReturnSequence();
__ bind(&suspend);
VisitForAccumulatorValue(expr->generator_object());
@ -2017,8 +2026,8 @@ void FullCodeGenerator::VisitYield(Yield* expr) {
}
void FullCodeGenerator::EmitGeneratorResume(Expression *generator,
Expression *value,
void FullCodeGenerator::EmitGeneratorResume(
Expression* generator, Expression* value,
JSGeneratorObject::ResumeMode resume_mode) {
// The value stays in rax, and is ultimately read by the resumed generator, as
// if CallRuntime(Runtime::kSuspendJSGeneratorObject) returned it. Or it
@ -2083,6 +2092,7 @@ void FullCodeGenerator::EmitGeneratorResume(Expression *generator,
__ addp(rdx, rcx);
__ Move(FieldOperand(rbx, JSGeneratorObject::kContinuationOffset),
Smi::FromInt(JSGeneratorObject::kGeneratorExecuting));
__ Push(Smi::FromInt(resume_mode)); // Consumed in continuation.
__ jmp(rdx);
__ bind(&slow_resume);
}
@ -2096,6 +2106,7 @@ void FullCodeGenerator::EmitGeneratorResume(Expression *generator,
__ Push(rcx);
__ jmp(&push_operand_holes);
__ bind(&call_resume);
__ Push(Smi::FromInt(resume_mode)); // Consumed in continuation.
__ Push(rbx);
__ Push(result_register());
__ Push(Smi::FromInt(resume_mode));

View File

@ -39,7 +39,28 @@ function GeneratorObjectNext(value) {
return %_GeneratorNext(this, value);
} else if (continuation == 0) {
// Generator is already closed.
return { value: void 0, done: true };
return %_CreateIterResultObject(UNDEFINED, true);
} else {
// Generator is running.
throw MakeTypeError(kGeneratorRunning);
}
}
function GeneratorObjectReturn(value) {
if (!IS_GENERATOR(this)) {
throw MakeTypeError(kIncompatibleMethodReceiver,
'[Generator].prototype.return', this);
}
var continuation = %GeneratorGetContinuation(this);
if (continuation > 0) {
// Generator is suspended.
DEBUG_PREPARE_STEP_IN_IF_STEPPING(this);
return %_GeneratorReturn(this, value);
} else if (continuation == 0) {
// Generator is already closed.
return %_CreateIterResultObject(value, true);
} else {
// Generator is running.
throw MakeTypeError(kGeneratorRunning);
@ -56,6 +77,7 @@ function GeneratorObjectThrow(exn) {
var continuation = %GeneratorGetContinuation(this);
if (continuation > 0) {
// Generator is suspended.
DEBUG_PREPARE_STEP_IN_IF_STEPPING(this);
return %_GeneratorThrow(this, exn);
} else if (continuation == 0) {
// Generator is already closed.
@ -68,9 +90,11 @@ function GeneratorObjectThrow(exn) {
// ----------------------------------------------------------------------------
// Both Runtime_GeneratorNext and Runtime_GeneratorThrow are supported by
// neither Crankshaft nor TurboFan, disable optimization of wrappers here.
// None of the three resume operations (Runtime_GeneratorNext,
// Runtime_GeneratorReturn, Runtime_GeneratorThrow) is supported by
// Crankshaft or TurboFan. Disable optimization of wrappers here.
%NeverOptimizeFunction(GeneratorObjectNext);
%NeverOptimizeFunction(GeneratorObjectReturn);
%NeverOptimizeFunction(GeneratorObjectThrow);
// Set up non-enumerable functions on the generator prototype object.
@ -78,6 +102,7 @@ var GeneratorObjectPrototype = GeneratorFunctionPrototype.prototype;
utils.InstallFunctions(GeneratorObjectPrototype,
DONT_ENUM,
["next", GeneratorObjectNext,
"return", GeneratorObjectReturn,
"throw", GeneratorObjectThrow]);
%AddNamedProperty(GeneratorObjectPrototype, "constructor",

View File

@ -7271,7 +7271,7 @@ class JSGeneratorObject: public JSObject {
static const int kSize = kOperandStackOffset + kPointerSize;
// Resume mode, for use by runtime functions.
enum ResumeMode { NEXT, THROW };
enum ResumeMode { NEXT, RETURN, THROW };
private:
DISALLOW_IMPLICIT_CONSTRUCTORS(JSGeneratorObject);

View File

@ -5869,9 +5869,6 @@ Expression* ParserTraits::RewriteYieldStar(
Statement* skip = factory->NewEmptyStatement(nopos);
enum { kNext, kReturn, kThrow };
// TODO(neis): Use JSGenerator::ResumeMode once extended with RETURN.
// Forward definition for break/continue statements.
WhileStatement* loop = factory->NewWhileStatement(nullptr, nopos);
@ -5892,7 +5889,7 @@ Expression* ParserTraits::RewriteYieldStar(
Statement* initialize_mode;
{
Expression* mode_proxy = factory->NewVariableProxy(var_mode);
Expression* knext = factory->NewSmiLiteral(kNext, nopos);
Expression* knext = factory->NewSmiLiteral(JSGeneratorObject::NEXT, nopos);
Expression* assignment =
factory->NewAssignment(Token::ASSIGN, mode_proxy, knext, nopos);
initialize_mode = factory->NewExpressionStatement(assignment, nopos);
@ -6087,7 +6084,8 @@ Expression* ParserTraits::RewriteYieldStar(
Statement* set_mode_return;
{
Expression* mode_proxy = factory->NewVariableProxy(var_mode);
Expression* kreturn = factory->NewSmiLiteral(kReturn, nopos);
Expression* kreturn =
factory->NewSmiLiteral(JSGeneratorObject::RETURN, nopos);
Expression* assignment =
factory->NewAssignment(Token::ASSIGN, mode_proxy, kreturn, nopos);
set_mode_return = factory->NewExpressionStatement(assignment, nopos);
@ -6108,7 +6106,7 @@ Expression* ParserTraits::RewriteYieldStar(
Statement* set_mode_next;
{
Expression* mode_proxy = factory->NewVariableProxy(var_mode);
Expression* knext = factory->NewSmiLiteral(kNext, nopos);
Expression* knext = factory->NewSmiLiteral(JSGeneratorObject::NEXT, nopos);
Expression* assignment =
factory->NewAssignment(Token::ASSIGN, mode_proxy, knext, nopos);
set_mode_next = factory->NewExpressionStatement(assignment, nopos);
@ -6119,7 +6117,8 @@ Expression* ParserTraits::RewriteYieldStar(
Statement* set_mode_throw;
{
Expression* mode_proxy = factory->NewVariableProxy(var_mode);
Expression* kthrow = factory->NewSmiLiteral(kThrow, nopos);
Expression* kthrow =
factory->NewSmiLiteral(JSGeneratorObject::THROW, nopos);
Expression* assignment =
factory->NewAssignment(Token::ASSIGN, mode_proxy, kthrow, nopos);
set_mode_throw = factory->NewExpressionStatement(assignment, nopos);
@ -6208,9 +6207,11 @@ Expression* ParserTraits::RewriteYieldStar(
case_throw->Add(factory->NewBreakStatement(switch_mode, nopos), zone);
auto cases = new (zone) ZoneList<CaseClause*>(3, zone);
Expression* knext = factory->NewSmiLiteral(kNext, nopos);
Expression* kreturn = factory->NewSmiLiteral(kReturn, nopos);
Expression* kthrow = factory->NewSmiLiteral(kThrow, nopos);
Expression* knext = factory->NewSmiLiteral(JSGeneratorObject::NEXT, nopos);
Expression* kreturn =
factory->NewSmiLiteral(JSGeneratorObject::RETURN, nopos);
Expression* kthrow =
factory->NewSmiLiteral(JSGeneratorObject::THROW, nopos);
cases->Add(factory->NewCaseClause(knext, case_next, nopos), zone);
cases->Add(factory->NewCaseClause(kreturn, case_return, nopos), zone);
cases->Add(factory->NewCaseClause(kthrow, case_throw, nopos), zone);

View File

@ -87,9 +87,9 @@ RUNTIME_FUNCTION(Runtime_SuspendJSGeneratorObject) {
// called if the suspended activation had operands on the stack, stack handlers
// needing rewinding, or if the resume should throw an exception. The fast path
// is handled directly in FullCodeGenerator::EmitGeneratorResume(), which is
// inlined into GeneratorNext and GeneratorThrow. EmitGeneratorResumeResume is
// called in any case, as it needs to reconstruct the stack frame and make space
// for arguments and operands.
// inlined into GeneratorNext, GeneratorReturn, and GeneratorThrow.
// EmitGeneratorResume is called in any case, as it needs to reconstruct the
// stack frame and make space for arguments and operands.
RUNTIME_FUNCTION(Runtime_ResumeJSGeneratorObject) {
SealHandleScope shs(isolate);
DCHECK(args.length() == 3);
@ -125,7 +125,10 @@ RUNTIME_FUNCTION(Runtime_ResumeJSGeneratorObject) {
JSGeneratorObject::ResumeMode resume_mode =
static_cast<JSGeneratorObject::ResumeMode>(resume_mode_int);
switch (resume_mode) {
// Note: this looks like NEXT and RETURN are the same but RETURN receives
// special treatment in the generator code (to which we return here).
case JSGeneratorObject::NEXT:
case JSGeneratorObject::RETURN:
return value;
case JSGeneratorObject::THROW:
return isolate->Throw(value);
@ -213,15 +216,25 @@ RUNTIME_FUNCTION(Runtime_GeneratorGetSourcePosition) {
}
// Optimization for the following three functions is disabled in
// js/generator.js and compiler/ast-graph-builder.cc.
RUNTIME_FUNCTION(Runtime_GeneratorNext) {
UNREACHABLE(); // Optimization disabled in SetUpGenerators().
return NULL;
UNREACHABLE();
return nullptr;
}
RUNTIME_FUNCTION(Runtime_GeneratorReturn) {
UNREACHABLE();
return nullptr;
}
RUNTIME_FUNCTION(Runtime_GeneratorThrow) {
UNREACHABLE(); // Optimization disabled in SetUpGenerators().
return NULL;
UNREACHABLE();
return nullptr;
}
} // namespace internal
} // namespace v8

View File

@ -262,6 +262,7 @@ namespace internal {
F(GeneratorGetContinuation, 1, 1) \
F(GeneratorGetSourcePosition, 1, 1) \
F(GeneratorNext, 2, 1) \
F(GeneratorReturn, 2, 1) \
F(GeneratorThrow, 2, 1)

View File

@ -99,7 +99,7 @@ function TestGeneratorObjectPrototype() {
assertSame(GeneratorObjectPrototype,
Object.getPrototypeOf((function*(){yield 1}).prototype));
var expected_property_names = ["next", "throw", "constructor"];
var expected_property_names = ["next", "return", "throw", "constructor"];
var found_property_names =
Object.getOwnPropertyNames(GeneratorObjectPrototype);

View File

@ -22,18 +22,25 @@
}
{
x = g();
let x = g();
assertEquals({value: 1, done: false}, x.next(1));
assertEquals({value: 2, done: false}, x.next(2));
assertEquals({value: 3, done: true}, x.next(3));
}
{
x = g();
let x = g();
assertEquals({value: 1, done: false}, x.next(1));
assertEquals({value: 2, done: false}, x.throw(2));
assertEquals({value: 3, done: true}, x.next(3));
}
{
let x = g();
assertEquals({value: 1, done: false}, x.next(1));
assertEquals({value: 2, done: false}, x.return(2));
assertEquals({value: 3, done: true}, x.next(3));
}
}
@ -53,18 +60,25 @@
}
{
x = g();
let x = g();
assertEquals({value: 1, done: false}, x.next(1));
assertEquals({value: undefined, done: false}, x.next(2));
assertEquals({value: 3, done: true}, x.next(3));
}
{
x = g();
let x = g();
assertEquals({value: 1, done: false}, x.next(1));
assertEquals({value: undefined, done: false}, x.next(2));
assertEquals({value: 42, done: true}, x.throw(42));
}
{
let x = g();
assertEquals({value: 1, done: false}, x.next(1));
assertEquals({value: undefined, done: false}, x.next(2));
assertEquals({value: 42, done: true}, x.return(42));
}
}

View File

@ -0,0 +1,252 @@
// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
{ // yield in try-catch
let g = function*() {
try {yield 1} catch (error) {assertEquals("caught", error)}
};
assertThrowsEquals(() => g().throw("not caught"), "not caught");
{
let x = g();
assertEquals({value: 1, done: false}, x.next());
assertEquals({value: undefined, done: true}, x.throw("caught"));
}
{
let x = g();
assertEquals({value: 1, done: false}, x.next());
assertEquals({value: undefined, done: true}, x.next());
assertThrowsEquals(() => x.throw("not caught"), "not caught");
}
}
{ // return that doesn't close
let g = function*() { try {return 42} finally {yield 43} };
{
let x = g();
assertEquals({value: 43, done: false}, x.next());
assertEquals({value: 42, done: true}, x.next());
}
}
{ // return that doesn't close
let x;
let g = function*() { try {return 42} finally {x.throw(666)} };
{
x = g();
assertThrows(() => x.next(), TypeError); // still executing
}
}
{ // yield in try-finally, finally clause performs return
let g = function*() { try {yield 42} finally {return 13} };
{ // "return" closes at suspendedStart
let x = g();
assertEquals({value: 666, done: true}, x.return(666));
assertEquals({value: undefined, done: true}, x.next(42));
assertThrowsEquals(() => x.throw(43), 43);
assertEquals({value: 42, done: true}, x.return(42));
}
{ // "throw" closes at suspendedStart
let x = g();
assertThrowsEquals(() => x.throw(666), 666);
assertEquals({value: undefined, done: true}, x.next(42));
assertEquals({value: 43, done: true}, x.return(43));
assertThrowsEquals(() => x.throw(44), 44);
}
{ // "next" closes at suspendedYield
let x = g();
assertEquals({value: 42, done: false}, x.next());
assertEquals({value: 13, done: true}, x.next(666));
assertEquals({value: undefined, done: true}, x.next(666));
assertThrowsEquals(() => x.throw(666), 666);
}
{ // "return" closes at suspendedYield
let x = g();
assertEquals({value: 42, done: false}, x.next());
assertEquals({value: 13, done: true}, x.return(666));
assertEquals({value: undefined, done: true}, x.next(666));
assertEquals({value: 666, done: true}, x.return(666));
}
{ // "throw" closes at suspendedYield
let x = g();
assertEquals({value: 42, done: false}, x.next());
assertEquals({value: 13, done: true}, x.throw(666));
assertThrowsEquals(() => x.throw(666), 666);
assertEquals({value: undefined, done: true}, x.next(666));
}
}
{ // yield in try-finally, finally clause doesn't perform return
let g = function*() { try {yield 42} finally {13} };
{ // "return" closes at suspendedStart
let x = g();
assertEquals({value: 666, done: true}, x.return(666));
assertEquals({value: undefined, done: true}, x.next(42));
assertThrowsEquals(() => x.throw(43), 43);
assertEquals({value: 42, done: true}, x.return(42));
}
{ // "throw" closes at suspendedStart
let x = g();
assertThrowsEquals(() => x.throw(666), 666);
assertEquals({value: undefined, done: true}, x.next(42));
assertEquals({value: 43, done: true}, x.return(43));
assertThrowsEquals(() => x.throw(44), 44);
}
{ // "next" closes at suspendedYield
let x = g();
assertEquals({value: 42, done: false}, x.next());
assertEquals({value: undefined, done: true}, x.next(666));
assertEquals({value: undefined, done: true}, x.next(666));
assertThrowsEquals(() => x.throw(666), 666);
assertEquals({value: 42, done: true}, x.return(42));
}
{ // "return" closes at suspendedYield
let x = g();
assertEquals({value: 42, done: false}, x.next());
assertEquals({value: 666, done: true}, x.return(666));
assertEquals({value: undefined, done: true}, x.next(666));
assertThrowsEquals(() => x.throw(44), 44);
assertEquals({value: 42, done: true}, x.return(42));
}
{ // "throw" closes at suspendedYield
let x = g();
assertEquals({value: 42, done: false}, x.next());
assertThrowsEquals(() => x.throw(666), 666);
assertEquals({value: undefined, done: true}, x.next(666));
assertThrowsEquals(() => x.throw(666), 666);
assertEquals({value: 42, done: true}, x.return(42));
}
}
{ // yield in try-finally, finally clause yields and performs return
let g = function*() { try {yield 42} finally {yield 43; return 13} };
{
let x = g();
assertEquals({value: 42, done: false}, x.next());
assertEquals({value: 43, done: false}, x.return(666));
assertEquals({value: 13, done: true}, x.next());
assertEquals({value: 666, done: true}, x.return(666));
}
{
let x = g();
assertEquals({value: 666, done: true}, x.return(666));
assertEquals({value: undefined, done: true}, x.next());
assertEquals({value: 666, done: true}, x.return(666));
}
}
{ // yield in try-finally, finally clause yields and doesn't perform return
let g = function*() { try {yield 42} finally {yield 43; 13} };
{
let x = g();
assertEquals({value: 42, done: false}, x.next());
assertEquals({value: 43, done: false}, x.return(666));
assertEquals({value: 666, done: true}, x.next());
assertEquals({value: 5, done: true}, x.return(5));
}
{
let x = g();
assertEquals({value: 666, done: true}, x.return(666));
assertEquals({value: undefined, done: true}, x.next());
assertEquals({value: 666, done: true}, x.return(666));
}
}
{ // yield*, finally clause performs return
let h = function*() { try {yield 42} finally {yield 43; return 13} };
let g = function*() { yield 1; yield yield* h(); };
{
let x = g();
assertEquals({value: 1, done: false}, x.next());
assertEquals({value: 42, done: false}, x.next());
assertEquals({value: 43, done: false}, x.next(666));
assertEquals({value: 13, done: false}, x.next());
assertEquals({value: undefined, done: true}, x.next());
}
{
let x = g();
assertEquals({value: 1, done: false}, x.next());
assertEquals({value: 42, done: false}, x.next());
assertEquals({value: 43, done: false}, x.return(666));
assertEquals({value: 13, done: false}, x.next());
assertEquals({value: undefined, done: true}, x.next());
}
{
let x = g();
assertEquals({value: 1, done: false}, x.next());
assertEquals({value: 42, done: false}, x.next());
assertEquals({value: 43, done: false}, x.throw(666));
assertEquals({value: 13, done: false}, x.next());
assertEquals({value: undefined, done: true}, x.next());
}
}
{ // yield*, finally clause does not perform return
let h = function*() { try {yield 42} finally {yield 43; 13} };
let g = function*() { yield 1; yield yield* h(); };
{
let x = g();
assertEquals({value: 1, done: false}, x.next());
assertEquals({value: 42, done: false}, x.next());
assertEquals({value: 43, done: false}, x.next(666));
assertEquals({value: undefined, done: false}, x.next());
assertEquals({value: undefined, done: true}, x.next());
}
{
let x = g();
assertEquals({value: 1, done: false}, x.next());
assertEquals({value: 42, done: false}, x.next());
assertEquals({value: 43, done: false}, x.return(666));
assertEquals({value: 666, done: false}, x.next());
assertEquals({value: undefined, done: true}, x.next());
}
{
let x = g();
assertEquals({value: 1, done: false}, x.next());
assertEquals({value: 42, done: false}, x.next());
assertEquals({value: 43, done: false}, x.throw(666));
assertThrowsEquals(() => x.next(), 666);
}
}

View File

@ -149,9 +149,6 @@
'built-ins/Array/prototype/values/iteration-mutable': [FAIL],
'built-ins/Array/prototype/Symbol.unscopables/value': [FAIL],
# https://code.google.com/p/v8/issues/detail?id=3566
'built-ins/GeneratorPrototype/return/*': [SKIP],
# https://code.google.com/p/v8/issues/detail?id=4248
'language/expressions/compound-assignment/S11.13.2_A5.*': [FAIL],
'language/expressions/compound-assignment/S11.13.2_A6.*': [FAIL],