Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright (C) 2026 Taras Mankovski. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-generator.prototype.return
description: >
When generator.return() is called while paused in nested try blocks,
yields in both inner and outer finally blocks should suspend the generator.
info: |
27.5.3.2 Generator.prototype.return ( value )

When return triggers nested finally blocks, each yield in each finally
block causes the generator to suspend. The generator completes only
after all finally blocks have finished executing.
features: [generators]
---*/

function* nestedCleanup() {
try {
try {
yield "work";
} finally {
yield "inner-cleanup";
}
} finally {
yield "outer-cleanup";
}
}

var gen = nestedCleanup();
var result;

result = gen.next();
assert.sameValue(result.value, "work", "r1.value");
assert.sameValue(result.done, false, "r1.done");

result = gen.return("cancelled");
assert.sameValue(
result.value,
"inner-cleanup",
"r2.value (inner finally yield)",
);
assert.sameValue(result.done, false, "r2.done (suspended at inner finally)");

result = gen.next();
assert.sameValue(
result.value,
"outer-cleanup",
"r3.value (outer finally yield)",
);
assert.sameValue(result.done, false, "r3.done (suspended at outer finally)");

result = gen.next();
assert.sameValue(result.value, "cancelled", "r4.value (return value)");
assert.sameValue(result.done, true, "r4.done (completed)");
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (C) 2026 Taras Mankovski. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-generator.prototype.return
description: >
When generator.return() triggers a finally block that contains its own
return statement, the finally's return value overrides the original.
info: |
27.5.3.2 Generator.prototype.return ( value )

If a finally block contains a return statement, that return value
replaces the original return value from generator.return().
features: [generators]
---*/

function* genWithOverride() {
try {
yield "work";
} finally {
return "cleanup-override";
}
}

var gen = genWithOverride();
gen.next();

var result = gen.return("cancelled");

assert.sameValue(
result.value,
"cleanup-override",
"Finally return overrides generator.return() value",
);
assert.sameValue(result.done, true, "Generator is done");
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (C) 2026 Taras Mankovski. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-generator.prototype.return
description: >
When generator.return() triggers a finally block that yields and then
throws, the yield suspends normally and the throw occurs on next resume.
info: |
27.5.3.2 Generator.prototype.return ( value )
If a finally block yields and then throws, the generator suspends at
the yield. When resumed with next(), execution continues and the
exception is thrown.
features: [generators]
---*/

function* genWithSuspendingThrowingCleanup() {
try {
yield "work";
} finally {
yield "cleanup";
throw new Error("cleanup-failed-after-yield");
}
}

var gen = genWithSuspendingThrowingCleanup();
var result;

result = gen.next();
assert.sameValue(result.value, "work", "r1.value");

result = gen.return("cancelled");
assert.sameValue(result.value, "cleanup", "r2.value (yield in finally)");
assert.sameValue(result.done, false, "r2.done (suspended at yield)");

var caught;
try {
gen.next();
} catch (e) {
caught = e.message;
}

assert.sameValue(
caught,
"cleanup-failed-after-yield",
"Exception after yield should propagate on resume",
);

result = gen.next();
assert.sameValue(result.value, undefined, "Generator completion value");
assert.sameValue(result.done, true, "Generator should be completed");
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (C) 2026 Taras Mankovski. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-generator.prototype.return
description: >
When generator.return() triggers a finally block that throws before
yielding, the exception propagates to the caller.
info: |
27.5.3.2 Generator.prototype.return ( value )

If a finally block throws an exception before any yield, that exception
replaces the return completion and propagates to the caller.
features: [generators]
---*/

function* genWithThrowingCleanup() {
try {
yield "work";
} finally {
throw new Error("cleanup-failed");
}
}

var gen = genWithThrowingCleanup();
gen.next();

var caught;
try {
gen.return("cancelled");
} catch (e) {
caught = e.message;
}

assert.sameValue(
caught,
"cleanup-failed",
"Exception from finally should propagate",
);

var result = gen.next();
assert.sameValue(result.value, undefined, "Generator completion value");
assert.sameValue(result.done, true, "Generator should be completed");
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright (C) 2026 Taras Mankovski. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-generator.prototype.return
description: >
When generator.return() triggers a finally block with yield, the yield
can receive a value from the subsequent next() call, just like normal yields.
info: |
27.5.3.2 Generator.prototype.return ( value )

Yields in finally blocks during return behave like normal yields -
they can receive values passed to next().
features: [generators]
---*/

var cleanupInput;

function* cleanupNeedsAck() {
try {
yield "work";
} finally {
cleanupInput = yield "cleanup-1";
yield "cleanup-2";
}
}

var gen = cleanupNeedsAck();
var result;

result = gen.next();
assert.sameValue(result.value, "work", "r1.value");

result = gen.return("cancelled");
assert.sameValue(result.value, "cleanup-1", "r2.value");
assert.sameValue(result.done, false, "r2.done");

result = gen.next("ack");
assert.sameValue(result.value, "cleanup-2", "r3.value");
assert.sameValue(result.done, false, "r3.done");

result = gen.next();
assert.sameValue(result.value, "cancelled", "r4.value");
assert.sameValue(result.done, true, "r4.done");

assert.sameValue(
cleanupInput,
"ack",
"yield in finally received value from next()",
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright (C) 2026 Taras Mankovski. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-generator.prototype.return
description: >
When generator.return() is called on an outer generator delegating via
yield*, and the inner generator has a yield in its finally block, the
generator suspends at the inner finally yield.
info: |
27.5.3.2 Generator.prototype.return ( value )

When return is called on a generator that is delegating via yield*,
the return is forwarded to the inner generator. If the inner generator
has a finally block with yield, it suspends there.
features: [generators]
---*/

var cleanupOrder = [];

function* inner() {
try {
yield "inner-work";
return "inner-done";
} finally {
yield "inner-cleanup";
cleanupOrder.push("inner");
}
}

function* outer() {
try {
var result = yield* inner();
return result;
} finally {
yield "outer-cleanup";
cleanupOrder.push("outer");
}
}

var gen = outer();
var result;

result = gen.next();
assert.sameValue(
result.value,
"inner-work",
"First yield from inner generator",
);
assert.sameValue(result.done, false, "First result done");

result = gen.return("cancelled");
assert.sameValue(
result.value,
"inner-cleanup",
"Should yield from inner finally",
);
assert.sameValue(result.done, false, "Should suspend at inner finally yield");

result = gen.next();
assert.sameValue(
result.value,
"outer-cleanup",
"Should yield from outer finally",
);
assert.sameValue(result.done, false, "Should suspend at outer finally yield");

result = gen.next();
assert.sameValue(result.value, "cancelled", "Should return original return value");
assert.sameValue(result.done, true, "Should be done after all finally blocks");
assert.sameValue(
cleanupOrder.join(","),
"inner,outer",
"Cleanup order should be inner then outer",
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright (C) 2026 Taras Mankovski. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-generator.prototype.return
description: >
When generator.return() triggers a finally block containing yield*,
the delegated generator's values are yielded before completion.
info: |
27.5.3.2 Generator.prototype.return ( value )

yield* in a finally block during return delegates to the inner generator.
Each value from the inner generator is yielded before the outer generator
completes with the return value.
features: [generators]
---*/

function* delegatedCleanup() {
yield "cleanup-1";
yield "cleanup-2";
}

function* withYieldStarCleanup() {
try {
yield "work";
} finally {
yield* delegatedCleanup();
}
}

var gen = withYieldStarCleanup();
var result;

result = gen.next();
assert.sameValue(result.value, "work", "r1.value");

result = gen.return("cancelled");
assert.sameValue(result.value, "cleanup-1", "r2.value (first delegated yield)");
assert.sameValue(result.done, false, "r2.done");

result = gen.next();
assert.sameValue(
result.value,
"cleanup-2",
"r3.value (second delegated yield)",
);
assert.sameValue(result.done, false, "r3.done");

result = gen.next();
assert.sameValue(result.value, "cancelled", "r4.value (return value)");
assert.sameValue(result.done, true, "r4.done");
Loading