diff --git a/features.txt b/features.txt index 32b993763d4..e5d773cb284 100644 --- a/features.txt +++ b/features.txt @@ -100,6 +100,10 @@ canonical-tz # https://github.com/tc39/proposal-upsert upsert +# Joint Iteration +# https://github.com/tc39/proposal-joint-iteration +joint-iteration + ## Standard language features # # Language features that have been included in a published version of the diff --git a/test/built-ins/Iterator/zip/basic.js b/test/built-ins/Iterator/zip/basic.js new file mode 100644 index 00000000000..a11102ccbe1 --- /dev/null +++ b/test/built-ins/Iterator/zip/basic.js @@ -0,0 +1,236 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zip +description: > + Basic Iterator.zip test using all three possible modes. +includes: [compareArray.js, propertyHelper.js] +features: [joint-iteration] +---*/ + +// Assert |result| is an object created by CreateIteratorResultObject. +function assertIteratorResult(result, value, done) { + assert.sameValue( + Object.getPrototypeOf(result), + Object.prototype, + "[[Prototype]] of iterator result is Object.prototype" + ); + + assert(Object.isExtensible(result), "iterator result is extensible"); + + var ownKeys = Reflect.ownKeys(result); + assert.sameValue(ownKeys.length, 2, "iterator result has two own properties"); + assert.sameValue(ownKeys[0], "value", "first property is 'value'"); + assert.sameValue(ownKeys[1], "done", "second property is 'done'"); + + verifyProperty(result, "value", { + value: value, + writable: true, + enumerable: true, + configurable: true, + }); + + verifyProperty(result, "done", { + value: done, + writable: true, + enumerable: true, + configurable: true, + }); +} + +// Assert |array| is a packed array with default property attributes. +function assertIsPackedArray(array) { + assert(Array.isArray(array), "array is an array exotic object"); + + assert.sameValue( + Object.getPrototypeOf(array), + Array.prototype, + "[[Prototype]] of array is Array.prototype" + ); + + assert(Object.isExtensible(array), "array is extensible"); + + // Ensure "length" property has its default property attributes. + verifyProperty(array, "length", { + writable: true, + enumerable: false, + configurable: false, + }); + + // Ensure no holes and all elements have the default property attributes. + for (var i = 0; i < array.length; i++) { + verifyProperty(array, i, { + writable: true, + enumerable: true, + configurable: true, + }); + } +} + +// Yield all prefixes of the string |s|. +function* prefixes(s) { + for (var i = 0; i <= s.length; ++i) { + yield s.slice(0, i); + } +} + +// Empty iterable doesn't yield any values. +var empty = { + *[Symbol.iterator]() { + } +}; + +// Yield a single value. +var single = { + *[Symbol.iterator]() { + yield 1000; + } +}; + +// Yield an infinite amount of numbers. +var numbers = { + *[Symbol.iterator]() { + var i = 0; + while (true) { + yield 100 + i++; + } + } +}; + +// |iterables| is an array whose elements are array(-like) objects. Pass it as +// the "iterables" argument to |Iterator.zip|, using |options| as the "options" +// argument. +// +// Then iterate over the returned |Iterator.zip| iterator and check all +// returned iteration values have the expected values. +function test(iterables, options) { + var mode = (options && options.mode) || "shortest"; + var padding = options && options.padding; + + var lengths = iterables.map(function(array) { + return array.length; + }); + + var min = Math.min.apply(null, lengths); + var max = Math.max.apply(null, lengths); + + // Expected number of iterations. + var count; + switch (mode) { + case "shortest": + count = min; + break; + case "longest": + count = max; + break; + case "strict": + count = max; + break; + } + + // Compute padding array when |mode| is "longest". + if (mode === "longest") { + if (padding) { + padding = Iterator.from(padding).take(iterables.length).toArray(); + } else { + padding = []; + } + + // Fill with undefined until there are exactly |iterables.length| elements. + padding = padding.concat(Array(iterables.length - padding.length).fill(undefined)); + assert.sameValue(padding.length, iterables.length); + } + + // Last returned elements array. + var last = null; + + var it = Iterator.zip(iterables, options); + for (var i = 0; i < count; i++) { + // "strict" mode throws an error if number of elements don't match. + if (mode === "strict" && min < max && i === min) { + assert.throws(TypeError, function() { + it.next(); + }); + break; + } + + var result = it.next(); + var value = result.value; + + // Test IteratorResult structure. + assertIteratorResult(result, value, false); + + // Ensure value is a new array. + assert.notSameValue(value, last, "returns a new array"); + last = value; + + // Ensure all array elements have the expected value. + var expected = iterables.map(function(array, k) { + if (i < array.length) { + return array[i]; + } + assert.sameValue(mode, "longest", "padding is only used for 'longest' mode"); + return padding[k]; + }); + assert.compareArray(value, expected); + + // Ensure value is a packed array with default data properties. + // + // This operation is destructive, so it has to happen last. + assertIsPackedArray(value); + } + + // Iterator is closed. + assertIteratorResult(it.next(), undefined, true); +} + +var validOptions = [ + undefined, + {}, + {mode: "shortest"}, + {mode: "longest"}, + {mode: "longest", padding: empty}, + {mode: "longest", padding: single}, + {mode: "longest", padding: numbers}, + {mode: "strict"}, +]; + +for (var options of validOptions) { + // Zip an empty iterable. + var it = Iterator.zip([], options); + assertIteratorResult(it.next(), undefined, true); + + // Zip a single iterator. + for (var prefix of prefixes("abcd")) { + // Split prefix into an array. + test([prefix.split("")], options); + + // Use String wrapper as the iterable. + test([new String(prefix)], options); + } + + // Zip two iterators. + for (var prefix1 of prefixes("abcd")) { + for (var prefix2 of prefixes("efgh")) { + // Split prefixes into arrays. + test([prefix1.split(""), prefix2.split("")], options); + + // Use String wrappers as the iterables. + test([new String(prefix1), new String(prefix2)], options); + } + } + + // Zip three iterators. + for (var prefix1 of prefixes("abcd")) { + for (var prefix2 of prefixes("efgh")) { + for (var prefix3 of prefixes("ijkl")) { + // Split prefixes into arrays. + test([prefix1.split(""), prefix2.split(""), prefix3.split("")], options); + + // Use String wrappers as the iterables. + test([new String(prefix1), new String(prefix2), new String(prefix3)], options); + } + } + } +} diff --git a/test/built-ins/Iterator/zip/is-function.js b/test/built-ins/Iterator/zip/is-function.js new file mode 100644 index 00000000000..fe2167e94c4 --- /dev/null +++ b/test/built-ins/Iterator/zip/is-function.js @@ -0,0 +1,15 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zip +description: > + Iterator.zip is a built-in function +features: [joint-iteration] +---*/ + +assert.sameValue( + typeof Iterator.zip, + "function", + "The value of `typeof Iterator.zip` is 'function'" +); diff --git a/test/built-ins/Iterator/zip/iterables-iteration-after-reading-options.js b/test/built-ins/Iterator/zip/iterables-iteration-after-reading-options.js new file mode 100644 index 00000000000..c24d29a93c8 --- /dev/null +++ b/test/built-ins/Iterator/zip/iterables-iteration-after-reading-options.js @@ -0,0 +1,51 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zip +description: > + Perform iteration of the "iterables" argument after reading all properties. +info: | + Iterator.zip ( iterables [ , options ] ) + ... + 3. Let mode be ? Get(options, "mode"). + ... + 7. If mode is "longest", then + a. Set paddingOption to ? Get(options, "padding"). + ... + 10. Let inputIter be ? GetIterator(iterables, sync). + ... +includes: [compareArray.js] +features: [joint-iteration] +---*/ + +var log = []; + +var iterables = { + [Symbol.iterator]() { + log.push("get iterator"); + return this; + }, + next() { + return {done: true}; + } +}; + +var options = { + get mode() { + log.push("get mode"); + return "longest"; + }, + get padding() { + log.push("get padding"); + return []; + } +}; + +Iterator.zip(iterables, options); + +assert.compareArray(log, [ + "get mode", + "get padding", + "get iterator", +]); diff --git a/test/built-ins/Iterator/zip/iterables-iteration-get-iterator-flattenable-abrupt-completion.js b/test/built-ins/Iterator/zip/iterables-iteration-get-iterator-flattenable-abrupt-completion.js new file mode 100644 index 00000000000..948006fbe2b --- /dev/null +++ b/test/built-ins/Iterator/zip/iterables-iteration-get-iterator-flattenable-abrupt-completion.js @@ -0,0 +1,165 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zip +description: > + Handle abrupt completions during iterables iteration. +info: | + Iterator.zip ( iterables [ , options ] ) + ... + 12. Repeat, while next is not done, + ... + c. If next is not done, then + i. Let iter be Completion(GetIteratorFlattenable(next, reject-strings)). + ii. IfAbruptCloseIterators(iter, the list-concatenation of « inputIter » and iters). + ... + + GetIteratorFlattenable ( obj, primitiveHandling ) + 1. If obj is not an Object, then + a. If primitiveHandling is reject-primitives, throw a TypeError exception. + b. Assert: primitiveHandling is iterate-string-primitives. + c. If obj is not a String, throw a TypeError exception. + 2. Let method be ? GetMethod(obj, %Symbol.iterator%). + 3. If method is undefined, then + a. Let iterator be obj. + 4. Else, + a. Let iterator be ? Call(method, obj). + 5. If iterator is not an Object, throw a TypeError exception. + 6. Return ? GetIteratorDirect(iterator). + + IteratorCloseAll ( iters, completion ) + 1. For each element iter of iters, in reverse List order, do + a. Set completion to Completion(IteratorClose(iter, completion)). + 2. Return ? completion. + + IteratorClose ( iteratorRecord, completion ) + 1. Assert: iteratorRecord.[[Iterator]] is an Object. + 2. Let iterator be iteratorRecord.[[Iterator]]. + 3. Let innerResult be Completion(GetMethod(iterator, "return")). + 4. If innerResult is a normal completion, then + a. Let return be innerResult.[[Value]]. + b. If return is undefined, return ? completion. + c. Set innerResult to Completion(Call(return, iterator)). + 5. If completion is a throw completion, return ? completion. + ... +includes: [compareArray.js] +features: [joint-iteration] +---*/ + +class ExpectedError extends Error {} + +var badIterators = [ + // Throw TypeError in GetIteratorFlattenable because strings are rejected. + { + iterator: "bad iterator", + error: TypeError + }, + + // Throw an error when GetIteratorFlattenable performs GetMethod. + { + iterator: { + get [Symbol.iterator]() { + throw new ExpectedError(); + } + }, + error: ExpectedError, + }, + + // Throw an error when GetIteratorFlattenable performs Call. + { + iterator: { + [Symbol.iterator]() { + throw new ExpectedError(); + } + }, + error: ExpectedError, + }, + + // Throw an error when GetIteratorFlattenable performs GetIteratorDirect. + { + iterator: { + get next() { + throw new ExpectedError(); + } + }, + error: ExpectedError, + }, +]; + +function makeIterables(badIterator) { + var log = []; + + var first = { + next() { + log.push("unexpected call to next method"); + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, first); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("close first iterator"); + + // IteratorClose ignores new exceptions when called with a Throw completion. + throw new Test262Error(); + }, + }; + + var second = { + next() { + log.push("unexpected call to next method"); + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, second); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("close second iterator"); + + // IteratorClose ignores new exceptions when called with a Throw completion. + throw new Test262Error(); + }, + }; + + var elements = [first, second, badIterator]; + var elementsIter = elements.values(); + + var iterables = { + [Symbol.iterator]() { + return this; + }, + next() { + log.push("call next"); + return elementsIter.next(); + }, + return() { + log.push("close iterables iterator"); + + // IteratorClose ignores new exceptions when called with a Throw completion. + throw new Test262Error(); + }, + }; + + return {log, iterables}; +} + +for (var {iterator, error} of badIterators) { + var {log, iterables} = makeIterables(iterator); + + assert.throws(error, function() { + Iterator.zip(iterables); + }); + + // Ensure iterators are closed in the correct order. + assert.compareArray(log, [ + "call next", + "call next", + "call next", + "close second iterator", + "close first iterator", + "close iterables iterator", + ]); +} diff --git a/test/built-ins/Iterator/zip/iterables-iteration-iterator-step-value-abrupt-completion.js b/test/built-ins/Iterator/zip/iterables-iteration-iterator-step-value-abrupt-completion.js new file mode 100644 index 00000000000..a9c15a21bff --- /dev/null +++ b/test/built-ins/Iterator/zip/iterables-iteration-iterator-step-value-abrupt-completion.js @@ -0,0 +1,113 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zip +description: > + Handle abrupt completions during iterables iteration. +info: | + Iterator.zip ( iterables [ , options ] ) + ... + 12. Repeat, while next is not done, + a. Set next to Completion(IteratorStepValue(inputIter)). + b. IfAbruptCloseIterators(next, iters). + ... + + IfAbruptCloseIterators ( value, iteratorRecords ) + 1. Assert: value is a Completion Record. + 2. If value is an abrupt completion, return ? IteratorCloseAll(iteratorRecords, value). + 3. Else, set value to value.[[Value]]. + + IteratorCloseAll ( iters, completion ) + 1. For each element iter of iters, in reverse List order, do + a. Set completion to Completion(IteratorClose(iter, completion)). + 2. Return ? completion. + + IteratorClose ( iteratorRecord, completion ) + 1. Assert: iteratorRecord.[[Iterator]] is an Object. + 2. Let iterator be iteratorRecord.[[Iterator]]. + 3. Let innerResult be Completion(GetMethod(iterator, "return")). + 4. If innerResult is a normal completion, then + a. Let return be innerResult.[[Value]]. + b. If return is undefined, return ? completion. + c. Set innerResult to Completion(Call(return, iterator)). + 5. If completion is a throw completion, return ? completion. + ... +includes: [compareArray.js] +features: [joint-iteration] +---*/ + +class ExpectedError extends Error {} + +var log = []; + +var first = { + next() { + log.push("unexpected call to next method"); + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, first); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("close first iterator"); + + // IteratorClose ignores new exceptions when called with a Throw completion. + throw new Test262Error(); + }, +}; + +var second = { + next() { + log.push("unexpected call to next method"); + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, second); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("close second iterator"); + + // IteratorClose ignores new exceptions when called with a Throw completion. + throw new Test262Error(); + }, +}; + +var elements = [first, second]; +var elementsIter = elements.values(); + +var iterables = { + [Symbol.iterator]() { + return this; + }, + next() { + log.push("call next"); + var result = elementsIter.next(); + if (result.done) { + throw new ExpectedError(); + } + return result; + }, + return() { + // This method shouldn't be called. + log.push("UNEXPECTED - close iterables iterator"); + + // IteratorClose ignores new exceptions when called with a Throw completion. + throw new Test262Error(); + }, +}; + +assert.throws(ExpectedError, function() { + Iterator.zip(iterables); +}); + +// Ensure iterators are closed in the correct order. +assert.compareArray(log, [ + "call next", + "call next", + "call next", + "close second iterator", + "close first iterator", +]); diff --git a/test/built-ins/Iterator/zip/iterables-iteration.js b/test/built-ins/Iterator/zip/iterables-iteration.js new file mode 100644 index 00000000000..a0b44ebc99f --- /dev/null +++ b/test/built-ins/Iterator/zip/iterables-iteration.js @@ -0,0 +1,164 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zip +description: > + Perform iteration of the "iterables" argument. +info: | + Iterator.zip ( iterables [ , options ] ) + ... + 10. Let inputIter be ? GetIterator(iterables, sync). + 11. Let next be not-started. + 12. Repeat, while next is not done, + a. Set next to Completion(IteratorStepValue(inputIter)). + b. IfAbruptCloseIterators(next, iters). + c. If next is not done, then + i. Let iter be Completion(GetIteratorFlattenable(next, reject-strings)). + ii. IfAbruptCloseIterators(iter, the list-concatenation of « inputIter » and iters). + iii. Append iter to iters. + ... + + GetIterator ( obj, kind ) + ... + 2. Else, + a. Let method be ? GetMethod(obj, %Symbol.iterator%). + 3. If method is undefined, throw a TypeError exception. + 4. Return ? GetIteratorFromMethod(obj, method). + + GetIteratorFromMethod ( obj, method ) + 1. Let iterator be ? Call(method, obj). + 2. If iterator is not an Object, throw a TypeError exception. + 3. Return ? GetIteratorDirect(iterator). + + GetIteratorDirect ( obj ) + 1. Let nextMethod be ? Get(obj, "next"). + 2. Let iteratorRecord be the Iterator Record { [[Iterator]]: obj, [[NextMethod]]: nextMethod, [[Done]]: false }. + 3. Return iteratorRecord. + + GetIteratorFlattenable ( obj, primitiveHandling ) + 1. If obj is not an Object, then + a. If primitiveHandling is reject-primitives, throw a TypeError exception. + b. Assert: primitiveHandling is iterate-string-primitives. + c. If obj is not a String, throw a TypeError exception. + 2. Let method be ? GetMethod(obj, %Symbol.iterator%). + 3. If method is undefined, then + a. Let iterator be obj. + 4. Else, + a. Let iterator be ? Call(method, obj). + 5. If iterator is not an Object, throw a TypeError exception. + 6. Return ? GetIteratorDirect(iterator). +includes: [proxyTrapsHelper.js, compareArray.js] +features: [joint-iteration] +---*/ + +// Object implementing Iterator protocol, but throws when calling any Iterator methods. +var throwingIterator = { + next() { + throw new Test262Error(); + }, + return() { + throw new Test262Error(); + } +}; + +var iterableReturningThrowingIterator = { + [Symbol.iterator]() { + return throwingIterator; + } +}; + +// "iterables" argument must be an iterable. +assert.throws(TypeError, function() { + Iterator.zip({}); +}); + +// GetIteratorFlattenable accepts both iterables and iterators. +Iterator.zip([ + throwingIterator, + iterableReturningThrowingIterator, +]); + +// GetIteratorFlattenable rejects non-objects. +var badIterators = [ + undefined, + null, + true, + "", + Symbol(), + 0, + 0n, +]; + +for (var iterator of badIterators) { + assert.throws(TypeError, function() { + Iterator.zip([iterator]); + }); +} + +// GetIterator and GetIteratorFlattenable read properties in the correct order. +var log = []; + +function makeProxyWithGetHandler(name, obj) { + return new Proxy(obj, allowProxyTraps({ + get(target, propertyKey, receiver) { + log.push(`${name}::${String(propertyKey)}`); + return Reflect.get(target, propertyKey, receiver); + } + })); +} + +var elements = [ + // An iterator. + makeProxyWithGetHandler("first", throwingIterator), + + // An iterable. + makeProxyWithGetHandler("second", iterableReturningThrowingIterator), + + // An object without any iteration methods. + makeProxyWithGetHandler("third", {}), +]; + +var elementsIter = elements.values(); + +var iterables = makeProxyWithGetHandler("iterables", { + [Symbol.iterator]() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, iterables); + assert.sameValue(arguments.length, 0); + + return this; + }, + next() { + log.push("call next"); + + // Called with the correct receiver and no arguments. + assert.sameValue(this, iterables); + assert.sameValue(arguments.length, 0); + + return elementsIter.next(); + }, + return() { + throw new Test262Error("unexpected call to return method"); + } +}); + +Iterator.zip(iterables); + +assert.compareArray(log, [ + "iterables::Symbol(Symbol.iterator)", + "iterables::next", + + "call next", + "first::Symbol(Symbol.iterator)", + "first::next", + + "call next", + "second::Symbol(Symbol.iterator)", + + "call next", + "third::Symbol(Symbol.iterator)", + "third::next", + + "call next", +]); diff --git a/test/built-ins/Iterator/zip/iterables-primitive.js b/test/built-ins/Iterator/zip/iterables-primitive.js new file mode 100644 index 00000000000..62cf113cc24 --- /dev/null +++ b/test/built-ins/Iterator/zip/iterables-primitive.js @@ -0,0 +1,50 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zip +description: > + Throws a TypeError when the "iterables" argument is not an object. +info: | + Iterator.zip ( iterables [ , options ] ) + 1. If iterables is not an Object, throw a TypeError exception. + ... +features: [joint-iteration] +---*/ + +var invalidIterables = [ + undefined, + null, + true, + "", + Symbol(), + 0, + 0n, +]; + +// Throws when the "iterables" argument is absent. +assert.throws(TypeError, function() { + Iterator.zip(); +}); + +// Throws a TypeError for invalid iterables values. +for (var iterables of invalidIterables) { + assert.throws(TypeError, function() { + Iterator.zip(iterables); + }); +} + +// Options argument not read when iterables is not an object. +var badOptions = { + get mode() { + throw new Test262Error(); + }, + get padding() { + throw new Test262Error(); + } +}; +for (var iterables of invalidIterables) { + assert.throws(TypeError, function() { + Iterator.zip(iterables, badOptions); + }); +} diff --git a/test/built-ins/Iterator/zip/iterator-zip-iteration-iterator-close-abrupt-completion.js b/test/built-ins/Iterator/zip/iterator-zip-iteration-iterator-close-abrupt-completion.js new file mode 100644 index 00000000000..5e832ae5be0 --- /dev/null +++ b/test/built-ins/Iterator/zip/iterator-zip-iteration-iterator-close-abrupt-completion.js @@ -0,0 +1,116 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zip +description: > + Handle abrupt completion from IteratorStep in IteratorZip. +info: | + Iterator.zip ( iterables [ , options ] ) + ... + 16. Return IteratorZip(iters, mode, padding, finishResults). + + IteratorZip ( iters, mode, padding, finishResults ) + 3. Let closure be a new Abstract Closure with no parameters that captures + iters, iterCount, openIters, mode, padding, and finishResults, and + performs the following steps when called: + ... + b. Repeat, + ... + v. Let completion be Completion(Yield(results)). + vi. If completion is an abrupt completion, then + 1. Return ? IteratorCloseAll(openIters, completion). + ... + + IteratorCloseAll ( iters, completion ) + 1. For each element iter of iters, in reverse List order, do + a. Set completion to Completion(IteratorClose(iter, completion)). + 2. Return ? completion. + + IteratorClose ( iteratorRecord, completion ) + 1. Assert: iteratorRecord.[[Iterator]] is an Object. + 2. Let iterator be iteratorRecord.[[Iterator]]. + 3. Let innerResult be Completion(GetMethod(iterator, "return")). + 4. If innerResult is a normal completion, then + a. Let return be innerResult.[[Value]]. + b. If return is undefined, return ? completion. + c. Set innerResult to Completion(Call(return, iterator)). + 5. If completion is a throw completion, return ? completion. + ... +includes: [compareArray.js] +features: [joint-iteration] +---*/ + +function ExpectedError() {} + +var log = []; + +var first = { + next() { + log.push("call first next"); + return {done: false}; + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, first); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("call first return"); + + // IteratorClose ignores new exceptions when called with a Throw completion. + throw new Test262Error(); + } +}; + +var second = { + next() { + log.push("call second next"); + return {done: false}; + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, second); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("call second return"); + + throw new ExpectedError(); + } +}; + +var third = { + next() { + log.push("call third next"); + return {done: false}; + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, third); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("call third return"); + + return {}; + } +}; + +var it = Iterator.zip([first, second, third]); + +it.next(); + +assert.throws(ExpectedError, function() { + it.return(); +}); + +assert.compareArray(log, [ + "call first next", + "call second next", + "call third next", + + "call third return", + "call second return", + "call first return", +]); diff --git a/test/built-ins/Iterator/zip/iterator-zip-iteration-iterator-step-value-abrupt-completion.js b/test/built-ins/Iterator/zip/iterator-zip-iteration-iterator-step-value-abrupt-completion.js new file mode 100644 index 00000000000..b1972c4e917 --- /dev/null +++ b/test/built-ins/Iterator/zip/iterator-zip-iteration-iterator-step-value-abrupt-completion.js @@ -0,0 +1,145 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zip +description: > + Handle abrupt completion from IteratorStepValue in IteratorZip. +info: | + Iterator.zip ( iterables [ , options ] ) + ... + 16. Return IteratorZip(iters, mode, padding, finishResults). + + IteratorZip ( iters, mode, padding, finishResults ) + 3. Let closure be a new Abstract Closure with no parameters that captures + iters, iterCount, openIters, mode, padding, and finishResults, and + performs the following steps when called: + ... + b. Repeat, + ... + iii. For each integer i such that 0 ≤ i < iterCount, in ascending order, do + ... + 3. Else, + a. Let result be Completion(IteratorStepValue(iter)). + b. If result is an abrupt completion, then + i. Remove iter from openIters. + ii. Return ? IteratorCloseAll(openIters, result). + ... + d. If result is done, then + i. Remove iter from openIters. + ... + + IteratorCloseAll ( iters, completion ) + 1. For each element iter of iters, in reverse List order, do + a. Set completion to Completion(IteratorClose(iter, completion)). + 2. Return ? completion. + + IteratorClose ( iteratorRecord, completion ) + 1. Assert: iteratorRecord.[[Iterator]] is an Object. + 2. Let iterator be iteratorRecord.[[Iterator]]. + 3. Let innerResult be Completion(GetMethod(iterator, "return")). + 4. If innerResult is a normal completion, then + a. Let return be innerResult.[[Value]]. + b. If return is undefined, return ? completion. + c. Set innerResult to Completion(Call(return, iterator)). + 5. If completion is a throw completion, return ? completion. + ... +includes: [compareArray.js] +features: [joint-iteration] +---*/ + +var modes = [ + "shortest", + "longest", + "strict", +]; + +function ExpectedError() {} + +var log = []; + +var first = { + next() { + log.push("call first next"); + throw new ExpectedError(); + }, + return() { + log.push("unexpected call first return"); + } +}; + +var second = { + next() { + log.push("unexpected call second next"); + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, second); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("call second return"); + + // IteratorClose ignores new exceptions when called with a Throw completion. + throw new Test262Error(); + } +}; + +var third = { + next() { + log.push("unexpected call third next"); + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, third); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("call third return"); + + // IteratorClose ignores new exceptions when called with a Throw completion. + throw new Test262Error(); + } +}; + +// Empty iterator to ensure |return| is not called for closed iterators. +var empty = { + next() { + log.push("call empty next"); + return {done: true}; + }, + return() { + log.push("unexpected call empty return"); + } +}; + +for (var mode of modes) { + var it = Iterator.zip([first, second, third], {mode}); + + assert.throws(ExpectedError, function() { + it.next(); + }); + + assert.compareArray(log, [ + "call first next", + "call third return", + "call second return", + ]); + + // Clear log. + log.length = 0; +} + +// This case applies only when mode is "longest". +var it = Iterator.zip([empty, first, second, third], {mode: "longest"}); + +assert.throws(ExpectedError, function() { + it.next(); +}); + +assert.compareArray(log, [ + "call empty next", + "call first next", + "call third return", + "call second return", +]); diff --git a/test/built-ins/Iterator/zip/iterator-zip-iteration-longest-iterator-close-abrupt-completion.js b/test/built-ins/Iterator/zip/iterator-zip-iteration-longest-iterator-close-abrupt-completion.js new file mode 100644 index 00000000000..14e9446ea09 --- /dev/null +++ b/test/built-ins/Iterator/zip/iterator-zip-iteration-longest-iterator-close-abrupt-completion.js @@ -0,0 +1,127 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zip +description: > + Handle abrupt completion from IteratorStep in IteratorZip. +info: | + Iterator.zip ( iterables [ , options ] ) + ... + 16. Return IteratorZip(iters, mode, padding, finishResults). + + IteratorZip ( iters, mode, padding, finishResults ) + 3. Let closure be a new Abstract Closure with no parameters that captures + iters, iterCount, openIters, mode, padding, and finishResults, and + performs the following steps when called: + ... + b. Repeat, + ... + v. Let completion be Completion(Yield(results)). + vi. If completion is an abrupt completion, then + 1. Return ? IteratorCloseAll(openIters, completion). + ... + + IteratorCloseAll ( iters, completion ) + 1. For each element iter of iters, in reverse List order, do + a. Set completion to Completion(IteratorClose(iter, completion)). + 2. Return ? completion. + + IteratorClose ( iteratorRecord, completion ) + 1. Assert: iteratorRecord.[[Iterator]] is an Object. + 2. Let iterator be iteratorRecord.[[Iterator]]. + 3. Let innerResult be Completion(GetMethod(iterator, "return")). + 4. If innerResult is a normal completion, then + a. Let return be innerResult.[[Value]]. + b. If return is undefined, return ? completion. + c. Set innerResult to Completion(Call(return, iterator)). + 5. If completion is a throw completion, return ? completion. + ... +includes: [compareArray.js] +features: [joint-iteration] +---*/ + +function ExpectedError() {} + +var log = []; + +var first = { + next() { + log.push("call first next"); + return {done: false}; + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, first); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("call first return"); + + // IteratorClose ignores new exceptions when called with a Throw completion. + throw new Test262Error(); + } +}; + +var second = { + next() { + log.push("call second next"); + return {done: false}; + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, second); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("call second return"); + + throw new ExpectedError(); + } +}; + +var third = { + next() { + log.push("call third next"); + return {done: false}; + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, third); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("call third return"); + + return {}; + } +}; + +var fourth = { + next() { + log.push("call fourth next"); + return {done: true}; + }, + return() { + log.push("unexpected call fourth return"); + } +}; + +var it = Iterator.zip([first, second, third, fourth], {mode: "longest"}); + +it.next(); + +assert.throws(ExpectedError, function() { + it.return(); +}); + +assert.compareArray(log, [ + "call first next", + "call second next", + "call third next", + "call fourth next", + + "call third return", + "call second return", + "call first return", +]); diff --git a/test/built-ins/Iterator/zip/iterator-zip-iteration-shortest-iterator-close-abrupt-completion.js b/test/built-ins/Iterator/zip/iterator-zip-iteration-shortest-iterator-close-abrupt-completion.js new file mode 100644 index 00000000000..0ea88a04e5a --- /dev/null +++ b/test/built-ins/Iterator/zip/iterator-zip-iteration-shortest-iterator-close-abrupt-completion.js @@ -0,0 +1,125 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zip +description: > + Handle abrupt completion from IteratorCloseAll in IteratorZip. +info: | + Iterator.zip ( iterables [ , options ] ) + ... + 16. Return IteratorZip(iters, mode, padding, finishResults). + + IteratorZip ( iters, mode, padding, finishResults ) + 3. Let closure be a new Abstract Closure with no parameters that captures + iters, iterCount, openIters, mode, padding, and finishResults, and + performs the following steps when called: + ... + b. Repeat, + ... + iii. For each integer i such that 0 ≤ i < iterCount, in ascending order, do + ... + 3. Else, + ... + d. If result is done, then + i. Remove iter from openIters. + ii. If mode is "shortest", then + i. Return ? IteratorCloseAll(openIters, ReturnCompletion(undefined)). + ... + + IteratorCloseAll ( iters, completion ) + 1. For each element iter of iters, in reverse List order, do + a. Set completion to Completion(IteratorClose(iter, completion)). + 2. Return ? completion. + + IteratorClose ( iteratorRecord, completion ) + 1. Assert: iteratorRecord.[[Iterator]] is an Object. + 2. Let iterator be iteratorRecord.[[Iterator]]. + 3. Let innerResult be Completion(GetMethod(iterator, "return")). + 4. If innerResult is a normal completion, then + a. Let return be innerResult.[[Value]]. + b. If return is undefined, return ? completion. + c. Set innerResult to Completion(Call(return, iterator)). + 5. If completion is a throw completion, return ? completion. + ... +includes: [compareArray.js] +features: [joint-iteration] +---*/ + +function ExpectedError() {} + +var log = []; + +var first = { + next() { + log.push("call first next"); + return {done: false}; + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, first); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("call first return"); + + return {}; + } +}; + +var second = { + next() { + log.push("call second next"); + return {done: true}; + }, + return() { + log.push("unexpected call second return"); + } +}; + +var third = { + next() { + log.push("unexpected call third next"); + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, third); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("call third return"); + + // IteratorClose ignores new exceptions when called with a Throw completion. + throw new Test262Error(); + } +}; + +var fourth = { + next() { + log.push("unexpected call fourth next"); + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, fourth); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("call fourth return"); + + throw new ExpectedError(); + } +}; + +var it = Iterator.zip([first, second, third, fourth], {mode: "shortest"}); + +assert.throws(ExpectedError, function() { + it.next(); +}); + +assert.compareArray(log, [ + "call first next", + "call second next", + "call fourth return", + "call third return", + "call first return", +]); diff --git a/test/built-ins/Iterator/zip/iterator-zip-iteration-strict-iterator-close-i-is-not-zero-abrupt-completion.js b/test/built-ins/Iterator/zip/iterator-zip-iteration-strict-iterator-close-i-is-not-zero-abrupt-completion.js new file mode 100644 index 00000000000..883a6dff0b9 --- /dev/null +++ b/test/built-ins/Iterator/zip/iterator-zip-iteration-strict-iterator-close-i-is-not-zero-abrupt-completion.js @@ -0,0 +1,108 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zip +description: > + Handle abrupt completion from IteratorCloseAll in IteratorZip. +info: | + Iterator.zip ( iterables [ , options ] ) + ... + 16. Return IteratorZip(iters, mode, padding, finishResults). + + IteratorZip ( iters, mode, padding, finishResults ) + 3. Let closure be a new Abstract Closure with no parameters that captures + iters, iterCount, openIters, mode, padding, and finishResults, and + performs the following steps when called: + ... + b. Repeat, + ... + iii. For each integer i such that 0 ≤ i < iterCount, in ascending order, do + ... + 3. Else, + ... + d. If result is done, then + i. Remove iter from openIters. + ... + iii. Else if mode is "strict", then + i. If i ≠ 0, then + i. Return ? IteratorCloseAll(openIters, ThrowCompletion(a newly created TypeError object)). + ... + + IteratorCloseAll ( iters, completion ) + 1. For each element iter of iters, in reverse List order, do + a. Set completion to Completion(IteratorClose(iter, completion)). + 2. Return ? completion. + + IteratorClose ( iteratorRecord, completion ) + 1. Assert: iteratorRecord.[[Iterator]] is an Object. + 2. Let iterator be iteratorRecord.[[Iterator]]. + 3. Let innerResult be Completion(GetMethod(iterator, "return")). + 4. If innerResult is a normal completion, then + a. Let return be innerResult.[[Value]]. + b. If return is undefined, return ? completion. + c. Set innerResult to Completion(Call(return, iterator)). + 5. If completion is a throw completion, return ? completion. + ... +includes: [compareArray.js] +features: [joint-iteration] +---*/ + +var log = []; + +var first = { + next() { + log.push("call first next"); + return {done: false}; + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, first); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("call first return"); + + return {}; + } +}; + +var second = { + next() { + log.push("call second next"); + return {done: true}; + }, + return() { + log.push("unexpected call second return"); + } +}; + +var third = { + next() { + log.push("unexpected call third next"); + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, third); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("call third return"); + + // IteratorClose ignores new exceptions when called with a Throw completion. + throw new Test262Error(); + } +}; + +var it = Iterator.zip([first, second, third], {mode: "strict"}); + +assert.throws(TypeError, function() { + it.next(); +}); + +assert.compareArray(log, [ + "call first next", + "call second next", + "call third return", + "call first return", +]); diff --git a/test/built-ins/Iterator/zip/iterator-zip-iteration-strict-iterator-close-i-is-zero-abrupt-completion.js b/test/built-ins/Iterator/zip/iterator-zip-iteration-strict-iterator-close-i-is-zero-abrupt-completion.js new file mode 100644 index 00000000000..63646bd924d --- /dev/null +++ b/test/built-ins/Iterator/zip/iterator-zip-iteration-strict-iterator-close-i-is-zero-abrupt-completion.js @@ -0,0 +1,123 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zip +description: > + Handle abrupt completion from IteratorCloseAll in IteratorZip. +info: | + Iterator.zip ( iterables [ , options ] ) + ... + 16. Return IteratorZip(iters, mode, padding, finishResults). + + IteratorZip ( iters, mode, padding, finishResults ) + 3. Let closure be a new Abstract Closure with no parameters that captures + iters, iterCount, openIters, mode, padding, and finishResults, and + performs the following steps when called: + ... + b. Repeat, + ... + iii. For each integer i such that 0 ≤ i < iterCount, in ascending order, do + ... + 3. Else, + ... + d. If result is done, then + i. Remove iter from openIters. + ... + iii. Else if mode is "strict", then + ... + ii. For each integer k such that 1 ≤ k < iterCount, in ascending order, do + ... + iv. Else, + i. Return ? IteratorCloseAll(openIters, ThrowCompletion(a newly created TypeError object)). + ... + + IteratorCloseAll ( iters, completion ) + 1. For each element iter of iters, in reverse List order, do + a. Set completion to Completion(IteratorClose(iter, completion)). + 2. Return ? completion. + + IteratorClose ( iteratorRecord, completion ) + 1. Assert: iteratorRecord.[[Iterator]] is an Object. + 2. Let iterator be iteratorRecord.[[Iterator]]. + 3. Let innerResult be Completion(GetMethod(iterator, "return")). + 4. If innerResult is a normal completion, then + a. Let return be innerResult.[[Value]]. + b. If return is undefined, return ? completion. + c. Set innerResult to Completion(Call(return, iterator)). + 5. If completion is a throw completion, return ? completion. + ... +includes: [compareArray.js] +features: [joint-iteration] +---*/ + +var log = []; + +var first = { + next() { + log.push("call first next"); + return {done: true}; + }, + return() { + log.push("unexpected call first return"); + } +}; + +var second = { + next() { + log.push("call second next"); + return {done: true}; + }, + return() { + log.push("unexpected call second return"); + } +}; + +var third = { + next() { + log.push("call third next"); + return {done: false}; + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, third); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("call third return"); + + // IteratorClose ignores new exceptions when called with a Throw completion. + throw new Test262Error(); + } +}; + +var fourth = { + next() { + log.push("unexpected call fourth next"); + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, fourth); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("call fourth return"); + + // IteratorClose ignores new exceptions when called with a Throw completion. + throw new Test262Error(); + } +}; + +var it = Iterator.zip([first, second, third, fourth], {mode: "strict"}); + +assert.throws(TypeError, function() { + it.next(); +}); + +assert.compareArray(log, [ + "call first next", + "call second next", + "call third next", + "call fourth return", + "call third return", +]); diff --git a/test/built-ins/Iterator/zip/iterator-zip-iteration-strict-iterator-step-abrupt-completion.js b/test/built-ins/Iterator/zip/iterator-zip-iteration-strict-iterator-step-abrupt-completion.js new file mode 100644 index 00000000000..9ef82da6a69 --- /dev/null +++ b/test/built-ins/Iterator/zip/iterator-zip-iteration-strict-iterator-step-abrupt-completion.js @@ -0,0 +1,125 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zip +description: > + Handle abrupt completion from IteratorStep in IteratorZip. +info: | + Iterator.zip ( iterables [ , options ] ) + ... + 16. Return IteratorZip(iters, mode, padding, finishResults). + + IteratorZip ( iters, mode, padding, finishResults ) + 3. Let closure be a new Abstract Closure with no parameters that captures + iters, iterCount, openIters, mode, padding, and finishResults, and + performs the following steps when called: + ... + b. Repeat, + ... + iii. For each integer i such that 0 ≤ i < iterCount, in ascending order, do + ... + 3. Else, + ... + d. If result is done, then + i. Remove iter from openIters. + ... + iii. Else if mode is "strict", then + ... + ii. For each integer k such that 1 ≤ k < iterCount, in ascending order, do + ... + ii. Let open be Completion(IteratorStep(iters[k])). + iii. If open is an abrupt completion, then + i. Remove iters[k] from openIters. + ii. Return ? IteratorCloseAll(openIters, open). + ... + + IteratorCloseAll ( iters, completion ) + 1. For each element iter of iters, in reverse List order, do + a. Set completion to Completion(IteratorClose(iter, completion)). + 2. Return ? completion. + + IteratorClose ( iteratorRecord, completion ) + 1. Assert: iteratorRecord.[[Iterator]] is an Object. + 2. Let iterator be iteratorRecord.[[Iterator]]. + 3. Let innerResult be Completion(GetMethod(iterator, "return")). + 4. If innerResult is a normal completion, then + a. Let return be innerResult.[[Value]]. + b. If return is undefined, return ? completion. + c. Set innerResult to Completion(Call(return, iterator)). + 5. If completion is a throw completion, return ? completion. + ... +includes: [compareArray.js] +features: [joint-iteration] +---*/ + +function ExpectedError() {} + +var log = []; + +var first = { + next() { + log.push("call first next"); + return {done: true}; + }, + return() { + log.push("unexpected call first return"); + } +}; + +var second = { + next() { + log.push("call second next"); + throw new ExpectedError(); + }, + return() { + log.push("unexpected call second return"); + } +}; + +var third = { + next() { + log.push("unexpected call third next"); + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, third); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("call third return"); + + // IteratorClose ignores new exceptions when called with a Throw completion. + throw new Test262Error(); + } +}; + +var fourth = { + next() { + log.push("unexpected call fourth next"); + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, fourth); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("call fourth return"); + + // IteratorClose ignores new exceptions when called with a Throw completion. + throw new Test262Error(); + } +}; + +var it = Iterator.zip([first, second, third, fourth], {mode: "strict"}); + +assert.throws(ExpectedError, function() { + it.next(); +}); + +assert.compareArray(log, [ + "call first next", + "call second next", + "call fourth return", + "call third return", +]); diff --git a/test/built-ins/Iterator/zip/iterator-zip-iteration.js b/test/built-ins/Iterator/zip/iterator-zip-iteration.js new file mode 100644 index 00000000000..7d4a2d33ae6 --- /dev/null +++ b/test/built-ins/Iterator/zip/iterator-zip-iteration.js @@ -0,0 +1,155 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zip +description: > + Perform iteration in IteratorZip. +info: | + Iterator.zip ( iterables [ , options ] ) + ... + 16. Return IteratorZip(iters, mode, padding, finishResults). + + IteratorZip ( iters, mode, padding, finishResults ) + 3. Let closure be a new Abstract Closure with no parameters that captures + iters, iterCount, openIters, mode, padding, and finishResults, and + performs the following steps when called: + ... + b. Repeat, + ... + iii. For each integer i such that 0 ≤ i < iterCount, in ascending order, do + ... + 3. Else, + a. Let result be Completion(IteratorStepValue(iter)). + ... +includes: [compareArray.js] +features: [joint-iteration] +---*/ + +var modes = [ + "shortest", + "longest", + "strict", +]; + +function makeIterator(log, name, elements) { + var elementsIter = elements.values(); + var iterator = { + next() { + log.push(`call ${name} next`); + + // Called with the correct receiver and no arguments. + assert.sameValue(this, iterator); + assert.sameValue(arguments.length, 0); + + var result = elementsIter.next(); + return { + get done() { + log.push(`get ${name}.result.done`); + return result.done; + }, + get value() { + log.push(`get ${name}.result.value`); + return result.value; + }, + }; + }, + return() { + log.push(`call ${name} return`); + + // Called with the correct receiver and no arguments. + assert.sameValue(this, iterator); + assert.sameValue(arguments.length, 0); + + return { + get done() { + log.push(`unexpected get ${name}.result.done`); + return result.done; + }, + get value() { + log.push(`unexpected get ${name}.result.value`); + return result.value; + }, + }; + } + }; + return iterator; +} + +for (var mode of modes) { + var log = []; + var iterables = [ + makeIterator(log, "first", [1, 2, 3]), + makeIterator(log, "second", [4, 5, 6]), + makeIterator(log, "third", [7, 8, 9]), + ]; + var it = Iterator.zip(iterables, {mode}); + + log.push("start"); + for (var v of it) { + log.push("loop"); + } + + var expected = [ + "start", + + "call first next", + "get first.result.done", + "get first.result.value", + "call second next", + "get second.result.done", + "get second.result.value", + "call third next", + "get third.result.done", + "get third.result.value", + "loop", + + "call first next", + "get first.result.done", + "get first.result.value", + "call second next", + "get second.result.done", + "get second.result.value", + "call third next", + "get third.result.done", + "get third.result.value", + "loop", + + "call first next", + "get first.result.done", + "get first.result.value", + "call second next", + "get second.result.done", + "get second.result.value", + "call third next", + "get third.result.done", + "get third.result.value", + "loop", + ]; + + switch (mode) { + case "shortest": { + expected.push( + "call first next", + "get first.result.done", + "call third return", + "call second return", + ); + break; + } + case "longest": + case "strict": { + expected.push( + "call first next", + "get first.result.done", + "call second next", + "get second.result.done", + "call third next", + "get third.result.done", + ); + break; + } + } + + assert.compareArray(log, expected); +} diff --git a/test/built-ins/Iterator/zip/length.js b/test/built-ins/Iterator/zip/length.js new file mode 100644 index 00000000000..418ca6dce4d --- /dev/null +++ b/test/built-ins/Iterator/zip/length.js @@ -0,0 +1,23 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zip +description: > + Iterator.zip has a "length" property whose value is 1. +info: | + ECMAScript Standard Built-in Objects + + Unless otherwise specified, the length property of a built-in + Function object has the attributes { [[Writable]]: false, [[Enumerable]]: + false, [[Configurable]]: true }. +features: [joint-iteration] +includes: [propertyHelper.js] +---*/ + +verifyProperty(Iterator.zip, "length", { + value: 1, + writable: false, + enumerable: false, + configurable: true, +}); diff --git a/test/built-ins/Iterator/zip/name.js b/test/built-ins/Iterator/zip/name.js new file mode 100644 index 00000000000..e147c5d6279 --- /dev/null +++ b/test/built-ins/Iterator/zip/name.js @@ -0,0 +1,30 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zip +description: > + The "name" property of Iterator.zip +info: | + 17 ECMAScript Standard Built-in Objects + + Every built-in Function object, including constructors, that is not + identified as an anonymous function has a name property whose value is a + String. Unless otherwise specified, this value is the name that is given to + the function in this specification. + + ... + + Unless otherwise specified, the name property of a built-in Function + object, if it exists, has the attributes { [[Writable]]: false, + [[Enumerable]]: false, [[Configurable]]: true }. +features: [joint-iteration] +includes: [propertyHelper.js] +---*/ + +verifyProperty(Iterator.zip, "name", { + value: "zip", + writable: false, + enumerable: false, + configurable: true, +}); diff --git a/test/built-ins/Iterator/zip/non-constructible.js b/test/built-ins/Iterator/zip/non-constructible.js new file mode 100644 index 00000000000..a5f3cdd7414 --- /dev/null +++ b/test/built-ins/Iterator/zip/non-constructible.js @@ -0,0 +1,21 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zip +description: > + Iterator.zip is not constructible. + + Built-in function objects that are not identified as constructors do not + implement the [[Construct]] internal method unless otherwise specified in the + description of a particular function. +features: [joint-iteration] +---*/ + +assert.throws(TypeError, () => { + new Iterator.zip(); +}); + +assert.throws(TypeError, () => { + new Iterator.zip([]); +}); diff --git a/test/built-ins/Iterator/zip/options-mode.js b/test/built-ins/Iterator/zip/options-mode.js new file mode 100644 index 00000000000..b73aac997f5 --- /dev/null +++ b/test/built-ins/Iterator/zip/options-mode.js @@ -0,0 +1,88 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zip +description: > + The "mode" option must be undefined or a valid string mode. +info: | + Iterator.zip ( iterables [ , options ] ) + ... + 3. Let mode be ? Get(options, "mode"). + 4. If mode is undefined, set mode to "shortest". + 5. If mode is not one of "shortest", "longest", or "strict", throw a TypeError exception. + ... +features: [joint-iteration] +---*/ + +var validModes = [ + undefined, + "shortest", + "longest", + "strict", +]; + +var invalidModes = [ + null, + false, + "", + "short", + "long", + "loose", + Symbol(), + 123, + 123n, + {}, +]; + +// Absent "mode" option. +Iterator.zip([], {}); + +// All valid mode values are accepted. +for (var mode of validModes) { + Iterator.zip([], {mode}); +} + +// Throws a TypeError for invalid mode options. +for (var mode of invalidModes) { + assert.throws(TypeError, function() { + Iterator.zip([], {mode}); + }); +} + +// "padding" option is not retrieved when "mode" option is invalid. +for (var mode of invalidModes) { + var options = { + mode, + get padding() { + throw new Test262Error(); + } + }; + assert.throws(TypeError, function() { + Iterator.zip([], options); + }); +} + +// String wrappers are not accepted. +for (var mode of validModes) { + var options = {mode: new String(mode)}; + assert.throws(TypeError, function() { + Iterator.zip([], options); + }); +} + +// Does not call any of `toString`, `valueOf`, `Symbol.toPrimitive`. +var badMode = { + toString() { + throw new Test262Error(); + }, + valueOf() { + throw new Test262Error(); + }, + [Symbol.toPrimitive]() { + throw new Test262Error(); + }, +}; +assert.throws(TypeError, function() { + Iterator.zip([], {mode: badMode}); +}); diff --git a/test/built-ins/Iterator/zip/options-padding.js b/test/built-ins/Iterator/zip/options-padding.js new file mode 100644 index 00000000000..de6434d06c7 --- /dev/null +++ b/test/built-ins/Iterator/zip/options-padding.js @@ -0,0 +1,54 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zip +description: > + The "padding" option must be undefined or an object. +info: | + Iterator.zip ( iterables [ , options ] ) + ... + 6. Let paddingOption be undefined. + 7. If mode is "longest", then + a. Set paddingOption to ? Get(options, "padding"). + b. If paddingOption is not undefined and paddingOption is not an Object, throw a TypeError exception. + ... +features: [joint-iteration] +---*/ + +var validPadding = [ + undefined, + [], +]; + +var invalidPadding = [ + null, + false, + "", + Symbol(), + 123, + 123n, +]; + +// Absent "padding" option. +Iterator.zip([], {mode: "longest"}); + +// All valid padding values are accepted. +for (var padding of validPadding) { + Iterator.zip([], {mode: "longest", padding}); +} + +// Throws a TypeError for invalid padding options. +for (var padding of invalidPadding) { + assert.throws(TypeError, function() { + Iterator.zip([], {mode: "longest", padding}); + }); +} + +// Invalid padding options are ignored when mode is not "longest". +for (var padding of invalidPadding) { + Iterator.zip([], {padding}); + Iterator.zip([], {mode: undefined, padding}); + Iterator.zip([], {mode: "shortest", padding}); + Iterator.zip([], {mode: "strict", padding}); +} diff --git a/test/built-ins/Iterator/zip/options.js b/test/built-ins/Iterator/zip/options.js new file mode 100644 index 00000000000..d523a65e006 --- /dev/null +++ b/test/built-ins/Iterator/zip/options.js @@ -0,0 +1,50 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zip +description: > + The "options" argument can either be undefined or an object. +info: | + Iterator.zip ( iterables [ , options ] ) + ... + 2. Set options to ? GetOptionsObject(options). + ... + + GetOptionsObject ( options ) + 1. If options is undefined, then + a. Return OrdinaryObjectCreate(null). + 2. If options is an Object, then + a. Return options. + 3. Throw a TypeError exception. +features: [joint-iteration] +---*/ + +var validOptions = [ + undefined, + {}, +]; + +var invalidOptions = [ + null, + true, + "", + Symbol(), + 0, + 0n, +]; + +// The "options" argument can also be absent. +Iterator.zip([]); + +// All valid option values are accepted. +for (var options of validOptions) { + Iterator.zip([], options); +} + +// Throws a TypeError for invalid option values. +for (var options of invalidOptions) { + assert.throws(TypeError, function() { + Iterator.zip([], options); + }); +} diff --git a/test/built-ins/Iterator/zip/padding-iteration-get-iterator-abrupt-completion.js b/test/built-ins/Iterator/zip/padding-iteration-get-iterator-abrupt-completion.js new file mode 100644 index 00000000000..0a5c9b0579b --- /dev/null +++ b/test/built-ins/Iterator/zip/padding-iteration-get-iterator-abrupt-completion.js @@ -0,0 +1,144 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zip +description: > + Abrupt completion for GetIterator in "padding" option iteration. +info: | + Iterator.zip ( iterables [ , options ] ) + ... + 14. If mode is "longest", then + ... + b. Else, + i. Let paddingIter be Completion(GetIterator(paddingOption, sync)). + ii. IfAbruptCloseIterators(paddingIter, iters). + ... + + IfAbruptCloseIterators ( value, iteratorRecords ) + 1. Assert: value is a Completion Record. + 2. If value is an abrupt completion, return ? IteratorCloseAll(iteratorRecords, value). + 3. Else, set value to value.[[Value]]. + + IteratorCloseAll ( iters, completion ) + 1. For each element iter of iters, in reverse List order, do + a. Set completion to Completion(IteratorClose(iter, completion)). + 2. Return ? completion. + + IteratorClose ( iteratorRecord, completion ) + 1. Assert: iteratorRecord.[[Iterator]] is an Object. + 2. Let iterator be iteratorRecord.[[Iterator]]. + 3. Let innerResult be Completion(GetMethod(iterator, "return")). + 4. If innerResult is a normal completion, then + a. Let return be innerResult.[[Value]]. + b. If return is undefined, return ? completion. + c. Set innerResult to Completion(Call(return, iterator)). + 5. If completion is a throw completion, return ? completion. + ... +includes: [compareArray.js] +features: [joint-iteration] +---*/ + +var log = []; + +var first = { + next() { + log.push("unexpected call to next method"); + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, first); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("first return"); + + // This exception is ignored. + throw new Test262Error(); + } +}; + +var second = { + next() { + log.push("unexpected call to next method"); + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, second); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("second return"); + + // This exception is ignored. + throw new Test262Error(); + } +}; + +var third = { + next() { + log.push("unexpected call to next method"); + }, + get return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, third); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("third return"); + + // This exception is ignored. + throw new Test262Error(); + } +}; + +function ExpectedError() {} + +// Padding iterator throws from |Symbol.iterator|. +var padding = { + [Symbol.iterator]() { + throw new ExpectedError(); + }, + next() { + log.push("unexpected call to next method"); + }, + return() { + log.push("unexpected call to return method"); + }, +}; + +assert.throws(ExpectedError, function() { + Iterator.zip([first, second, third], {mode: "longest", padding}); +}); + +assert.compareArray(log, [ + "third return", + "second return", + "first return", +]); + +// Clear log +log.length = 0; + +// Padding iterator throws from |next|. +var padding = { + [Symbol.iterator]() { + return this; + }, + next() { + throw new ExpectedError(); + }, + return() { + log.push("unexpected call to return method"); + }, +}; + +assert.throws(ExpectedError, function() { + Iterator.zip([first, second, third], {mode: "longest", padding}); +}); + +assert.compareArray(log, [ + "third return", + "second return", + "first return", +]); diff --git a/test/built-ins/Iterator/zip/padding-iteration-iterator-close-abrupt-completion.js b/test/built-ins/Iterator/zip/padding-iteration-iterator-close-abrupt-completion.js new file mode 100644 index 00000000000..0613da2ff5d --- /dev/null +++ b/test/built-ins/Iterator/zip/padding-iteration-iterator-close-abrupt-completion.js @@ -0,0 +1,124 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zip +description: > + Abrupt completion for IteratorClose in "padding" option iteration. +info: | + Iterator.zip ( iterables [ , options ] ) + ... + 14. If mode is "longest", then + ... + b. Else, + i. Let paddingIter be Completion(GetIterator(paddingOption, sync)). + ... + v. If usingIterator is true, then + 1. Let completion be Completion(IteratorClose(paddingIter, NormalCompletion(unused))). + 2. IfAbruptCloseIterators(completion, iters). + ... + + IfAbruptCloseIterators ( value, iteratorRecords ) + 1. Assert: value is a Completion Record. + 2. If value is an abrupt completion, return ? IteratorCloseAll(iteratorRecords, value). + 3. Else, set value to value.[[Value]]. + + IteratorCloseAll ( iters, completion ) + 1. For each element iter of iters, in reverse List order, do + a. Set completion to Completion(IteratorClose(iter, completion)). + 2. Return ? completion. + + IteratorClose ( iteratorRecord, completion ) + 1. Assert: iteratorRecord.[[Iterator]] is an Object. + 2. Let iterator be iteratorRecord.[[Iterator]]. + 3. Let innerResult be Completion(GetMethod(iterator, "return")). + 4. If innerResult is a normal completion, then + a. Let return be innerResult.[[Value]]. + b. If return is undefined, return ? completion. + c. Set innerResult to Completion(Call(return, iterator)). + 5. If completion is a throw completion, return ? completion. + ... +includes: [compareArray.js] +features: [joint-iteration] +---*/ + +var log = []; + +var first = { + next() { + log.push("unexpected call to next method"); + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, first); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("first return"); + + // This exception is ignored. + throw new Test262Error(); + } +}; + +var second = { + next() { + log.push("unexpected call to next method"); + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, second); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("second return"); + + // This exception is ignored. + throw new Test262Error(); + } +}; + +var third = { + next() { + log.push("unexpected call to next method"); + }, + get return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, third); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("third return"); + + // This exception is ignored. + throw new Test262Error(); + } +}; + +function ExpectedError() {} + +// Padding iterator throws from |Symbol.iterator|. +var padding = { + [Symbol.iterator]() { + return this; + }, + next() { + return {done: false}; + }, + return() { + log.push("padding return"); + + throw new ExpectedError(); + }, +}; + +assert.throws(ExpectedError, function() { + Iterator.zip([first, second, third], {mode: "longest", padding}); +}); + +assert.compareArray(log, [ + "padding return", + "third return", + "second return", + "first return", +]); diff --git a/test/built-ins/Iterator/zip/padding-iteration-iterator-step-value-abrupt-completion.js b/test/built-ins/Iterator/zip/padding-iteration-iterator-step-value-abrupt-completion.js new file mode 100644 index 00000000000..833977eab14 --- /dev/null +++ b/test/built-ins/Iterator/zip/padding-iteration-iterator-step-value-abrupt-completion.js @@ -0,0 +1,187 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zip +description: > + Abrupt completion for IteratorStepValue in "padding" option iteration. +info: | + Iterator.zip ( iterables [ , options ] ) + ... + 14. If mode is "longest", then + ... + b. Else, + i. Let paddingIter be Completion(GetIterator(paddingOption, sync)). + ... + iv. Perform the following steps iterCount times: + 1. If usingIterator is true, then + a. Set next to Completion(IteratorStepValue(paddingIter)). + b. IfAbruptCloseIterators(next, iters). + ... + + IfAbruptCloseIterators ( value, iteratorRecords ) + 1. Assert: value is a Completion Record. + 2. If value is an abrupt completion, return ? IteratorCloseAll(iteratorRecords, value). + 3. Else, set value to value.[[Value]]. + + IteratorCloseAll ( iters, completion ) + 1. For each element iter of iters, in reverse List order, do + a. Set completion to Completion(IteratorClose(iter, completion)). + 2. Return ? completion. + + IteratorClose ( iteratorRecord, completion ) + 1. Assert: iteratorRecord.[[Iterator]] is an Object. + 2. Let iterator be iteratorRecord.[[Iterator]]. + 3. Let innerResult be Completion(GetMethod(iterator, "return")). + 4. If innerResult is a normal completion, then + a. Let return be innerResult.[[Value]]. + b. If return is undefined, return ? completion. + c. Set innerResult to Completion(Call(return, iterator)). + 5. If completion is a throw completion, return ? completion. + ... +includes: [compareArray.js] +features: [joint-iteration] +---*/ + +var log = []; + +var first = { + next() { + log.push("unexpected call to next method"); + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, first); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("first return"); + + // This exception is ignored. + throw new Test262Error(); + } +}; + +var second = { + next() { + log.push("unexpected call to next method"); + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, second); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("second return"); + + // This exception is ignored. + throw new Test262Error(); + } +}; + +var third = { + next() { + log.push("unexpected call to next method"); + }, + get return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, third); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("third return"); + + // This exception is ignored. + throw new Test262Error(); + } +}; + +function ExpectedError() {} + +// Padding iterator throws from |next|. +var padding = { + [Symbol.iterator]() { + return this; + }, + next() { + throw new ExpectedError(); + }, + return() { + log.push("unexpected call to return method"); + }, +}; + +assert.throws(ExpectedError, function() { + Iterator.zip([first, second, third], {mode: "longest", padding}); +}); + +assert.compareArray(log, [ + "third return", + "second return", + "first return", +]); + +// Clear log +log.length = 0; + +// Padding iterator throws from |done|. +var padding = { + [Symbol.iterator]() { + return this; + }, + next() { + return { + get done() { + throw new ExpectedError(); + }, + get value() { + log.push("unexpected access to value"); + }, + }; + }, + return() { + log.push("unexpected call to return method"); + }, +}; + +assert.throws(ExpectedError, function() { + Iterator.zip([first, second, third], {mode: "longest", padding}); +}); + +assert.compareArray(log, [ + "third return", + "second return", + "first return", +]); + + +// Clear log +log.length = 0; + +// Padding iterator throws from |value|. +var padding = { + [Symbol.iterator]() { + return this; + }, + next() { + return { + done: false, + get value() { + throw new ExpectedError(); + }, + }; + }, + return() { + log.push("unexpected call to return method"); + }, +}; + +assert.throws(ExpectedError, function() { + Iterator.zip([first, second, third], {mode: "longest", padding}); +}); + +assert.compareArray(log, [ + "third return", + "second return", + "first return", +]); diff --git a/test/built-ins/Iterator/zip/padding-iteration.js b/test/built-ins/Iterator/zip/padding-iteration.js new file mode 100644 index 00000000000..9797e220d5f --- /dev/null +++ b/test/built-ins/Iterator/zip/padding-iteration.js @@ -0,0 +1,135 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zip +description: > + Perform iteration of the "padding" option. +info: | + Iterator.zip ( iterables [ , options ] ) + ... + 14. If mode is "longest", then + ... + b. Else, + i. Let paddingIter be Completion(GetIterator(paddingOption, sync)). + ... + iii. Let usingIterator be true. + iv. Perform the following steps iterCount times: + 1. If usingIterator is true, then + a. Set next to Completion(IteratorStepValue(paddingIter)). + ... + c. If next is done, then + i. Set usingIterator to false. + d. Else, + i. Append next to padding. + 2. If usingIterator is false, append undefined to padding. + v. If usingIterator is true, then + 1. Let completion be Completion(IteratorClose(paddingIter, NormalCompletion(unused))). + ... + ... + + GetIterator ( obj, kind ) + ... + 2. Else, + a. Let method be ? GetMethod(obj, %Symbol.iterator%). + 3. If method is undefined, throw a TypeError exception. + 4. Return ? GetIteratorFromMethod(obj, method). + + GetIteratorFromMethod ( obj, method ) + 1. Let iterator be ? Call(method, obj). + 2. If iterator is not an Object, throw a TypeError exception. + 3. Return ? GetIteratorDirect(iterator). + + GetIteratorDirect ( obj ) + 1. Let nextMethod be ? Get(obj, "next"). + 2. Let iteratorRecord be the Iterator Record { [[Iterator]]: obj, [[NextMethod]]: nextMethod, [[Done]]: false }. + 3. Return iteratorRecord. +includes: [proxyTrapsHelper.js, compareArray.js] +features: [joint-iteration] +---*/ + +function makeProxyWithGetHandler(log, name, obj) { + return new Proxy(obj, allowProxyTraps({ + get(target, propertyKey, receiver) { + log.push(`${name}::${String(propertyKey)}`); + return Reflect.get(target, propertyKey, receiver); + } + })); +} + +// "padding" option must be an iterable. +assert.throws(TypeError, function() { + Iterator.zip([], { + mode: "longest", + padding: {}, + }); +}); + +for (var n = 0; n <= 5; ++n) { + var iterables = Array(n).fill([]); + + for (var k = 0; k <= n + 2; ++k) { + var elements = Array(k).fill(0); + var elementsIter = elements.values(); + + var log = []; + + var padding = makeProxyWithGetHandler(log, "padding", { + [Symbol.iterator]() { + log.push("call iterator"); + + // Called with the correct receiver and no arguments. + assert.sameValue(this, padding); + assert.sameValue(arguments.length, 0); + + return this; + }, + next() { + log.push("call next"); + + // Called with the correct receiver and no arguments. + assert.sameValue(this, padding); + assert.sameValue(arguments.length, 0); + + return elementsIter.next(); + }, + return() { + log.push("call return"); + + // Called with the correct receiver and no arguments. + assert.sameValue(this, padding); + assert.sameValue(arguments.length, 0); + + return {}; + } + }); + + Iterator.zip(iterables, {mode: "longest", padding}); + + // Property reads and calls from GetIterator. + var expected = [ + "padding::Symbol(Symbol.iterator)", + "call iterator", + "padding::next", + ]; + + // Call the "next" method |n| times until the padding iterator is exhausted. + for (var i = 0; i < Math.min(n, k); ++i) { + expected.push("call next"); + } + + // If |n| is larger than |k|, then there was one final call to the "next" + // method. Otherwise the "return" method was called to close the padding + // iterator. + if (n > k) { + expected.push("call next"); + } else { + expected.push( + "padding::return", + "call return" + ); + } + + assert.compareArray(log, expected); + } +} diff --git a/test/built-ins/Iterator/zip/prop-desc.js b/test/built-ins/Iterator/zip/prop-desc.js new file mode 100644 index 00000000000..7e07c43ac98 --- /dev/null +++ b/test/built-ins/Iterator/zip/prop-desc.js @@ -0,0 +1,23 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zip +description: > + Property descriptor of Iterator.zip +info: | + 17 ECMAScript Standard Built-in Objects + + Every other data property described in clauses 18 through 26 and in Annex B.2 + has the attributes { [[Writable]]: true, [[Enumerable]]: false, + [[Configurable]]: true } unless otherwise specified. +features: [joint-iteration] +includes: [propertyHelper.js] +---*/ + +verifyProperty(Iterator, "zip", { + value: Iterator.zip, + writable: true, + enumerable: false, + configurable: true, +}); diff --git a/test/built-ins/Iterator/zip/proto.js b/test/built-ins/Iterator/zip/proto.js new file mode 100644 index 00000000000..21727bc518f --- /dev/null +++ b/test/built-ins/Iterator/zip/proto.js @@ -0,0 +1,16 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zip +description: > + The value of the [[Prototype]] internal slot of Iterator.zip is the + intrinsic object %FunctionPrototype%. +features: [joint-iteration] +---*/ + +assert.sameValue( + Object.getPrototypeOf(Iterator.zip), + Function.prototype, + "Object.getPrototypeOf(Iterator.zip) must return the value of Function.prototype" +); diff --git a/test/built-ins/Iterator/zip/result-is-iterator.js b/test/built-ins/Iterator/zip/result-is-iterator.js new file mode 100644 index 00000000000..f85d17fafb3 --- /dev/null +++ b/test/built-ins/Iterator/zip/result-is-iterator.js @@ -0,0 +1,20 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zip +description: > + The value of the [[Prototype]] internal slot of the return value of Iterator.zip + is the intrinsic object %IteratorHelperPrototype%. +includes: [wellKnownIntrinsicObjects.js] +features: [joint-iteration] +---*/ + +var iter = Iterator.zip([]); +assert(iter instanceof Iterator, "Iterator.zip([]) must return an Iterator"); + +assert.sameValue( + Object.getPrototypeOf(iter), + getWellKnownIntrinsicObject("%IteratorHelperPrototype%"), + "[[Prototype]] is %IteratorHelperPrototype%" +); diff --git a/test/built-ins/Iterator/zip/suspended-start-iterator-close-calls-next.js b/test/built-ins/Iterator/zip/suspended-start-iterator-close-calls-next.js new file mode 100644 index 00000000000..916ad840981 --- /dev/null +++ b/test/built-ins/Iterator/zip/suspended-start-iterator-close-calls-next.js @@ -0,0 +1,78 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zip +description: > + Generator is closed from suspended-start state and IteratorClose calls next. +info: | + %IteratorHelperPrototype%.return ( ) + ... + 4. If O.[[GeneratorState]] is suspended-start, then + a. Set O.[[GeneratorState]] to completed. + ... + c. Perform ? IteratorCloseAll(O.[[UnderlyingIterators]], ReturnCompletion(undefined)). + d. Return CreateIteratorResultObject(undefined, true). + ... + + IteratorCloseAll ( iters, completion ) + 1. For each element iter of iters, in reverse List order, do + a. Set completion to Completion(IteratorClose(iter, completion)). + 2. Return ? completion. + + IteratorClose ( iteratorRecord, completion ) + ... + 3. Let innerResult be Completion(GetMethod(iterator, "return")). + 4. If innerResult is a normal completion, then + a. Let return be innerResult.[[Value]]. + b. If return is undefined, return ? completion. + c. Set innerResult to Completion(Call(return, iterator)). + ... + + %IteratorHelperPrototype%.next ( ) + 1. Return ? GeneratorResume(this value, undefined, "Iterator Helper"). + + GeneratorResume ( generator, value, generatorBrand ) + 1. Let state be ? GeneratorValidate(generator, generatorBrand). + 2. If state is completed, return CreateIteratorResultObject(undefined, true). + ... + + GeneratorValidate ( generator, generatorBrand ) + ... + 5. Let state be generator.[[GeneratorState]]. + 6. If state is executing, throw a TypeError exception. + 7. Return state. +features: [joint-iteration] +---*/ + +var returnCallCount = 0; + +var underlying = { + next() { + throw new Test262Error("Unexpected call to next"); + }, + return() { + returnCallCount += 1; + + // The generator state is already set to "completed". The generator state is + // not "executing", so `GeneratorValidate` succeeds and `GeneratorResume` + // returns with `CreateIteratorResultObject()`. + var result = it.next(); + assert.sameValue(result.value, undefined); + assert.sameValue(result.done, true); + + return {}; + }, +}; + +var it = Iterator.zip([underlying]); + +assert.sameValue(returnCallCount, 0); + +// This `return()` call sets the generator state to "completed" and then calls +// `IteratorClose()`. +var result = it.return(); +assert.sameValue(result.value, undefined); +assert.sameValue(result.done, true); + +assert.sameValue(returnCallCount, 1); diff --git a/test/built-ins/Iterator/zip/suspended-start-iterator-close-calls-return.js b/test/built-ins/Iterator/zip/suspended-start-iterator-close-calls-return.js new file mode 100644 index 00000000000..20cd09d631b --- /dev/null +++ b/test/built-ins/Iterator/zip/suspended-start-iterator-close-calls-return.js @@ -0,0 +1,82 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zip +description: > + Generator is closed from suspended-start state and IteratorClose calls return. +info: | + %IteratorHelperPrototype%.return ( ) + ... + 4. If O.[[GeneratorState]] is suspended-start, then + a. Set O.[[GeneratorState]] to completed. + ... + c. Perform ? IteratorCloseAll(O.[[UnderlyingIterators]], ReturnCompletion(undefined)). + d. Return CreateIteratorResultObject(undefined, true). + 5. Let C be ReturnCompletion(undefined). + 6. Return ? GeneratorResumeAbrupt(O, C, "Iterator Helper"). + + IteratorCloseAll ( iters, completion ) + 1. For each element iter of iters, in reverse List order, do + a. Set completion to Completion(IteratorClose(iter, completion)). + 2. Return ? completion. + + IteratorClose ( iteratorRecord, completion ) + ... + 3. Let innerResult be Completion(GetMethod(iterator, "return")). + 4. If innerResult is a normal completion, then + a. Let return be innerResult.[[Value]]. + b. If return is undefined, return ? completion. + c. Set innerResult to Completion(Call(return, iterator)). + ... + 8. Return ? completion. + + GeneratorResumeAbrupt ( generator, abruptCompletion, generatorBrand ) + 1. Let state be ? GeneratorValidate(generator, generatorBrand). + 2. If state is suspended-start, then + ... + 3. If state is completed, then + a. If abruptCompletion is a return completion, then + i. Return CreateIteratorResultObject(abruptCompletion.[[Value]], true). + ... + + GeneratorValidate ( generator, generatorBrand ) + ... + 5. Let state be generator.[[GeneratorState]]. + 6. If state is executing, throw a TypeError exception. + 7. Return state. +features: [joint-iteration] +---*/ + +var returnCallCount = 0; + +var underlying = { + next() { + throw new Test262Error("Unexpected call to next"); + }, + return() { + returnCallCount += 1; + + // The generator state is already set to "completed", so this `return()` + // call proceeds to `GeneratorResumeAbrupt`. The generator state is not + // "executing", so `GeneratorValidate` succeeds and `GeneratorResumeAbrupt` + // returns with `CreateIteratorResultObject()`. + var result = it.return(); + assert.sameValue(result.value, undefined); + assert.sameValue(result.done, true); + + return {}; + }, +}; + +var it = Iterator.zip([underlying]); + +assert.sameValue(returnCallCount, 0); + +// This `return()` call sets the generator state to "completed" and then calls +// `IteratorClose()`. +var result = it.return(); +assert.sameValue(result.value, undefined); +assert.sameValue(result.done, true); + +assert.sameValue(returnCallCount, 1); diff --git a/test/built-ins/Iterator/zip/suspended-yield-iterator-close-calls-next.js b/test/built-ins/Iterator/zip/suspended-yield-iterator-close-calls-next.js new file mode 100644 index 00000000000..1a5683bf65a --- /dev/null +++ b/test/built-ins/Iterator/zip/suspended-yield-iterator-close-calls-next.js @@ -0,0 +1,102 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zip +description: > + Generator is closed from suspended-yield state and IteratorClose calls next. +info: | + %IteratorHelperPrototype%.return ( ) + ... + 5. Let C be ReturnCompletion(undefined). + 6. Return ? GeneratorResumeAbrupt(O, C, "Iterator Helper"). + + GeneratorResumeAbrupt ( generator, abruptCompletion, generatorBrand ) + 1. Let state be ? GeneratorValidate(generator, generatorBrand). + ... + 4. Assert: state is suspended-yield. + ... + 8. Set generator.[[GeneratorState]] to executing. + ... + 10. Resume the suspended evaluation of genContext using abruptCompletion as + the result of the operation that suspended it. Let result be the + Completion Record returned by the resumed computation. + ... + + GeneratorValidate ( generator, generatorBrand ) + ... + 5. Let state be generator.[[GeneratorState]]. + 6. If state is executing, throw a TypeError exception. + 7. Return state. + + IteratorZip ( iters, mode, padding, finishResults ) + ... + 3. Let closure be a new Abstract Closure with no parameters that captures + iters, iterCount, openIters, mode, padding, and finishResults, and + performs the following steps when called: + ... + b. Repeat, + ... + v. Let completion be Completion(Yield(results)). + vi. If completion is an abrupt completion, then + 1. Return ? IteratorCloseAll(openIters, completion). + ... + + IteratorCloseAll ( iters, completion ) + 1. For each element iter of iters, in reverse List order, do + a. Set completion to Completion(IteratorClose(iter, completion)). + 2. Return ? completion. + + IteratorClose ( iteratorRecord, completion ) + ... + 3. Let innerResult be Completion(GetMethod(iterator, "return")). + 4. If innerResult is a normal completion, then + a. Let return be innerResult.[[Value]]. + b. If return is undefined, return ? completion. + c. Set innerResult to Completion(Call(return, iterator)). + ... + + %IteratorHelperPrototype%.next ( ) + 1. Return ? GeneratorResume(this value, undefined, "Iterator Helper"). + + GeneratorResume ( generator, value, generatorBrand ) + 1. Let state be ? GeneratorValidate(generator, generatorBrand). + ... +includes: [compareArray.js] +features: [joint-iteration] +---*/ + +var returnCallCount = 0; + +var underlying = { + next() { + return {value: 123, done: false}; + }, + return() { + returnCallCount += 1; + + // The generator state is set to "executing", so this `next()` call throws + // a TypeError when `GeneratorResume` performs `GeneratorValidate`. + assert.throws(TypeError, function() { + it.next(); + }); + + return {}; + }, +}; + +var it = Iterator.zip([underlying]); + +// Move generator into "suspended-yield" state. +var result = it.next(); +assert.compareArray(result.value, [123]); +assert.sameValue(result.done, false); + +assert.sameValue(returnCallCount, 0); + +// This `return()` call continues execution in the suspended generator. +var result = it.return(); +assert.sameValue(result.value, undefined); +assert.sameValue(result.done, true); + +assert.sameValue(returnCallCount, 1); diff --git a/test/built-ins/Iterator/zip/suspended-yield-iterator-close-calls-return.js b/test/built-ins/Iterator/zip/suspended-yield-iterator-close-calls-return.js new file mode 100644 index 00000000000..be55f0128e2 --- /dev/null +++ b/test/built-ins/Iterator/zip/suspended-yield-iterator-close-calls-return.js @@ -0,0 +1,95 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zip +description: > + Generator is closed from suspended-yield state and IteratorClose calls return. +info: | + %IteratorHelperPrototype%.return ( ) + ... + 5. Let C be ReturnCompletion(undefined). + 6. Return ? GeneratorResumeAbrupt(O, C, "Iterator Helper"). + + GeneratorResumeAbrupt ( generator, abruptCompletion, generatorBrand ) + 1. Let state be ? GeneratorValidate(generator, generatorBrand). + ... + 4. Assert: state is suspended-yield. + ... + 8. Set generator.[[GeneratorState]] to executing. + ... + 10. Resume the suspended evaluation of genContext using abruptCompletion as + the result of the operation that suspended it. Let result be the + Completion Record returned by the resumed computation. + ... + + GeneratorValidate ( generator, generatorBrand ) + ... + 5. Let state be generator.[[GeneratorState]]. + 6. If state is executing, throw a TypeError exception. + 7. Return state. + + IteratorZip ( iters, mode, padding, finishResults ) + ... + 3. Let closure be a new Abstract Closure with no parameters that captures + iters, iterCount, openIters, mode, padding, and finishResults, and + performs the following steps when called: + ... + b. Repeat, + ... + v. Let completion be Completion(Yield(results)). + vi. If completion is an abrupt completion, then + 1. Return ? IteratorCloseAll(openIters, completion). + ... + + IteratorCloseAll ( iters, completion ) + 1. For each element iter of iters, in reverse List order, do + a. Set completion to Completion(IteratorClose(iter, completion)). + 2. Return ? completion. + + IteratorClose ( iteratorRecord, completion ) + ... + 3. Let innerResult be Completion(GetMethod(iterator, "return")). + 4. If innerResult is a normal completion, then + a. Let return be innerResult.[[Value]]. + b. If return is undefined, return ? completion. + c. Set innerResult to Completion(Call(return, iterator)). + ... +includes: [compareArray.js] +features: [joint-iteration] +---*/ + +var returnCallCount = 0; + +var underlying = { + next() { + return {value: 123, done: false}; + }, + return() { + returnCallCount += 1; + + // The generator state is set to "executing", so this `return()` call throws + // a TypeError when `GeneratorResumeAbrupt` performs `GeneratorValidate`. + assert.throws(TypeError, function() { + it.return(); + }); + + return {}; + }, +}; + +var it = Iterator.zip([underlying]); + +// Move generator into "suspended-yield" state. +var result = it.next(); +assert.compareArray(result.value, [123]); +assert.sameValue(result.done, false); + +assert.sameValue(returnCallCount, 0); + +// This `return()` call continues execution in the suspended generator. +var result = it.return(); +assert.sameValue(result.value, undefined); +assert.sameValue(result.done, true); + +assert.sameValue(returnCallCount, 1); diff --git a/test/built-ins/Iterator/zipKeyed/basic.js b/test/built-ins/Iterator/zipKeyed/basic.js new file mode 100644 index 00000000000..768f844d520 --- /dev/null +++ b/test/built-ins/Iterator/zipKeyed/basic.js @@ -0,0 +1,270 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zipkeyed +description: > + Basic Iterator.zipKeyed test using all three possible modes. +includes: [compareArray.js, propertyHelper.js] +features: [joint-iteration] +---*/ + +// Assert |result| is an object created by CreateIteratorResultObject. +function assertIteratorResult(result, value, done) { + assert.sameValue( + Object.getPrototypeOf(result), + Object.prototype, + "[[Prototype]] of iterator result is Object.prototype" + ); + + assert(Object.isExtensible(result), "iterator result is extensible"); + + var ownKeys = Reflect.ownKeys(result); + assert.sameValue(ownKeys.length, 2, "iterator result has two own properties"); + assert.sameValue(ownKeys[0], "value", "first property is 'value'"); + assert.sameValue(ownKeys[1], "done", "second property is 'done'"); + + verifyProperty(result, "value", { + value: value, + writable: true, + enumerable: true, + configurable: true, + }); + + verifyProperty(result, "done", { + value: done, + writable: true, + enumerable: true, + configurable: true, + }); +} + +// Assert |actual| is a plain object equal to |expected| with default property attributes. +function assertPlainObject(actual, expected) { + assert.sameValue( + Object.getPrototypeOf(actual), + null, + "[[Prototype]] of actual is null" + ); + + assert(Object.isExtensible(actual), "actual is extensible"); + + var actualKeys = Reflect.ownKeys(actual); + var expectedKeys = Reflect.ownKeys(expected); + + // All expected property keys are present. + assert.compareArray(actualKeys, expectedKeys); + + // All expected property values are equal. + for (var key of expectedKeys) { + assert.sameValue(actual[key], expected[key], "with key: " + String(key)); + } + + // Ensure all properties have the default property attributes. + for (var key of expectedKeys) { + verifyProperty(actual, key, { + writable: true, + enumerable: true, + configurable: true, + }); + } +} + +// Yield all prefixes of the string |s|. +function* prefixes(s) { + for (var i = 0; i <= s.length; ++i) { + yield s.slice(0, i); + } +} + +// Empty object. +var empty = {}; + +// Object with a single property. +var single = { + a: 1000, +}; + +// Object with many properties. +var numbers = new Proxy({}, { + has(target, key) { + if (typeof key === "symbol") { + key = key.description; + } + assert.sameValue(key.length, 1, "unsupported key length"); + return true; + }, + get(target, key, receiver) { + if (typeof key === "symbol") { + key = key.description; + } + assert.sameValue(key.length, 1, "unsupported key length"); + return key.charCodeAt(0); + } +}); + +// |iterables| is an object whose properties are array(-like) objects. Pass it +// as the "iterables" argument to |Iterator.zipKeyed|, using |options| as the +// "options" argument. +// +// Then iterate over the returned |Iterator.zipKeyed| iterator and check all +// returned iteration values have the expected values. +function test(iterables, options) { + var mode = (options && options.mode) || "shortest"; + var padding = options && options.padding; + + // Not Object.entries to allow symbol property keys. + var entries = Reflect.ownKeys(iterables).map(function(key) { + return [key, iterables[key]]; + }); + + var lengths = entries.map(function([key, array]) { + return array.length; + }); + + var min = Math.min.apply(null, lengths); + var max = Math.max.apply(null, lengths); + + // Expected number of iterations. + var count; + switch (mode) { + case "shortest": + count = min; + break; + case "longest": + count = max; + break; + case "strict": + count = max; + break; + } + + // Compute padding array when |mode| is "longest". + if (mode === "longest") { + padding = entries.map(function([key, array]) { + if (padding !== undefined && key in padding) { + return padding[key]; + } + return undefined; + }); + } + + // Last returned results object. + var last = null; + + var it = Iterator.zipKeyed(iterables, options); + for (var i = 0; i < count; i++) { + // "strict" mode throws an error if number of elements don't match. + if (mode === "strict" && min < max && i === min) { + assert.throws(TypeError, function() { + it.next(); + }); + break; + } + + var result = it.next(); + var value = result.value; + + // Test IteratorResult structure. + assertIteratorResult(result, value, false); + + // Ensure value is a new object. + assert.notSameValue(value, last, "returns a new object"); + last = value; + + var expected = Object.fromEntries(entries.map(function([key, array], k) { + if (i < array.length) { + return [key, array[i]]; + } + assert.sameValue(mode, "longest", "padding is only used for 'longest' mode"); + return [key, padding[k]]; + })); + + // Ensure all properties have the expected value. + // Ensure value is a plain with default data properties. + assertPlainObject(value, expected); + } + + // Iterator is closed. + assertIteratorResult(it.next(), undefined, true); +} + +var validOptions = [ + undefined, + {}, + {mode: "shortest"}, + {mode: "longest"}, + {mode: "longest", padding: empty}, + {mode: "longest", padding: single}, + {mode: "longest", padding: numbers}, + {mode: "strict"}, +]; + +for (var options of validOptions) { + // Zip an empty object. + var it = Iterator.zipKeyed({}, options); + assertIteratorResult(it.next(), undefined, true); + + // Zip a single key. + for (var prefix of prefixes("abcd")) { + // Split prefix into an array. + test({ + a: prefix.split(""), + }, options); + test({ + [Symbol("a")]: prefix.split(""), + }, options); + + // Use String wrapper as the iterable. + test({ + a: new String(prefix), + }, options); + } + + // Zip two keys. + for (var prefix1 of prefixes("abcd")) { + for (var prefix2 of prefixes("efgh")) { + // Split prefixes into arrays. + test({ + a: prefix1.split(""), + b: prefix2.split(""), + }, options); + test({ + [Symbol("a")]: prefix1.split(""), + [Symbol("b")]: prefix2.split(""), + }, options); + + // Use String wrappers as the iterables. + test({ + a: new String(prefix1), + b: new String(prefix2), + }, options); + } + } + + // Zip three keys. + for (var prefix1 of prefixes("abcd")) { + for (var prefix2 of prefixes("efgh")) { + for (var prefix3 of prefixes("ijkl")) { + // Split prefixes into arrays. + test({ + a: prefix1.split(""), + b: prefix2.split(""), + c: prefix3.split(""), + }, options); + test({ + [Symbol("a")]: prefix1.split(""), + [Symbol("b")]: prefix2.split(""), + [Symbol("c")]: prefix3.split(""), + }, options); + + // Use String wrappers as the iterables. + test({ + a: new String(prefix1), + b: new String(prefix2), + c: new String(prefix3), + }, options); + } + } + } +} diff --git a/test/built-ins/Iterator/zipKeyed/is-function.js b/test/built-ins/Iterator/zipKeyed/is-function.js new file mode 100644 index 00000000000..840cca95f65 --- /dev/null +++ b/test/built-ins/Iterator/zipKeyed/is-function.js @@ -0,0 +1,15 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zipkeyed +description: > + Iterator.zipKeyed is a built-in function +features: [joint-iteration] +---*/ + +assert.sameValue( + typeof Iterator.zipKeyed, + "function", + "The value of `typeof Iterator.zipKeyed` is 'function'" +); diff --git a/test/built-ins/Iterator/zipKeyed/iterables-iteration-after-reading-options.js b/test/built-ins/Iterator/zipKeyed/iterables-iteration-after-reading-options.js new file mode 100644 index 00000000000..1f94381f020 --- /dev/null +++ b/test/built-ins/Iterator/zipKeyed/iterables-iteration-after-reading-options.js @@ -0,0 +1,49 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zipkeyed +description: > + Perform [[OwnPropertyKeys]] on the "iterables" argument after reading all properties. +info: | + Iterator.zipKeyed ( iterables [ , options ] ) + ... + 3. Let mode be ? Get(options, "mode"). + ... + 7. If mode is "longest", then + a. Set paddingOption to ? Get(options, "padding"). + ... + 10. Let allKeys be ? iterables.[[OwnPropertyKeys]](). + ... +includes: [proxyTrapsHelper.js, compareArray.js] +features: [joint-iteration] +---*/ + +var log = []; + +var iterables = new Proxy({}, allowProxyTraps({ + ownKeys(target) { + log.push("own-keys"); + return Reflect.ownKeys(target); + }, +})); + + +var options = { + get mode() { + log.push("get mode"); + return "longest"; + }, + get padding() { + log.push("get padding"); + return []; + } +}; + +Iterator.zipKeyed(iterables, options); + +assert.compareArray(log, [ + "get mode", + "get padding", + "own-keys", +]); diff --git a/test/built-ins/Iterator/zipKeyed/iterables-iteration-deleted.js b/test/built-ins/Iterator/zipKeyed/iterables-iteration-deleted.js new file mode 100644 index 00000000000..4a855cb782e --- /dev/null +++ b/test/built-ins/Iterator/zipKeyed/iterables-iteration-deleted.js @@ -0,0 +1,51 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zipkeyed +description: > + Deleted properties are skipped in "iterables" iteration. +info: | + Iterator.zipKeyed ( iterables [ , options ] ) + ... + 10. Let allKeys be ? iterables.[[OwnPropertyKeys]](). + 11. Let keys be a new empty List. + 12. For each element key of allKeys, do + a. Let desc be Completion(iterables.[[GetOwnProperty]](key)). + ... + c. If desc is not undefined and desc.[[Enumerable]] is true, then + ... +includes: [compareArray.js] +features: [joint-iteration] +---*/ + +var log = []; + +var iterables = { + get a() { + log.push("get a"); + + // Delete property "b". + delete iterables.b; + + return []; + }, + get b() { + throw new Test262Error("unexpected get b"); + }, + get c() { + log.push("get c"); + + // Add new property "d". + iterables.d = null; + + return []; + }, +}; + +Iterator.zipKeyed(iterables); + +assert.compareArray(log, [ + "get a", + "get c", +]); diff --git a/test/built-ins/Iterator/zipKeyed/iterables-iteration-enumerable.js b/test/built-ins/Iterator/zipKeyed/iterables-iteration-enumerable.js new file mode 100644 index 00000000000..86d2dc2cf4a --- /dev/null +++ b/test/built-ins/Iterator/zipKeyed/iterables-iteration-enumerable.js @@ -0,0 +1,80 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zipkeyed +description: > + Non-enumerable properties are skipped in "iterables" iteration. +info: | + Iterator.zipKeyed ( iterables [ , options ] ) + ... + 10. Let allKeys be ? iterables.[[OwnPropertyKeys]](). + 11. Let keys be a new empty List. + 12. For each element key of allKeys, do + a. Let desc be Completion(iterables.[[GetOwnProperty]](key)). + ... + c. If desc is not undefined and desc.[[Enumerable]] is true, then + ... +includes: [compareArray.js] +features: [joint-iteration] +---*/ + +var log = []; + +var iterables = Object.create(null, { + a: { + enumerable: false, + get() { + throw new Test262Error("unexpected get a"); + } + }, + b: { + enumerable: true, + get() { + log.push("get b"); + + // Change enumerable of property "c". + Object.defineProperty(iterables, "c", { + enumerable: false, + }); + + return []; + } + }, + c: { + enumerable: true, + configurable: true, + get() { + throw new Test262Error("unexpected get c"); + } + }, + d: { + enumerable: true, + get() { + log.push("get d"); + + // Change enumerable of property "e". + Object.defineProperty(iterables, "e", { + enumerable: true, + }); + + return []; + } + }, + e: { + enumerable: false, + configurable: true, + get() { + log.push("get e"); + return []; + } + }, +}); + +Iterator.zipKeyed(iterables); + +assert.compareArray(log, [ + "get b", + "get d", + "get e", +]); diff --git a/test/built-ins/Iterator/zipKeyed/iterables-iteration-get-abrupt-completion.js b/test/built-ins/Iterator/zipKeyed/iterables-iteration-get-abrupt-completion.js new file mode 100644 index 00000000000..9c074806d3d --- /dev/null +++ b/test/built-ins/Iterator/zipKeyed/iterables-iteration-get-abrupt-completion.js @@ -0,0 +1,96 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zipkeyed +description: > + Handle abrupt completions during iterables iteration. +info: | + Iterator.zipKeyed ( iterables [ , options ] ) + ... + 12. For each element key of allKeys, do + ... + 3. If desc is not undefined and desc.[[Enumerable]] is true, then + i. Let value be Completion(Get(iterables, key)). + ii. IfAbruptCloseIterators(value, iters). + ... + + IfAbruptCloseIterators ( value, iteratorRecords ) + 1. Assert: value is a Completion Record. + 2. If value is an abrupt completion, return ? IteratorCloseAll(iteratorRecords, value). + 3. Else, set value to value.[[Value]]. + + IteratorCloseAll ( iters, completion ) + 1. For each element iter of iters, in reverse List order, do + a. Set completion to Completion(IteratorClose(iter, completion)). + 2. Return ? completion. + + IteratorClose ( iteratorRecord, completion ) + 1. Assert: iteratorRecord.[[Iterator]] is an Object. + 2. Let iterator be iteratorRecord.[[Iterator]]. + 3. Let innerResult be Completion(GetMethod(iterator, "return")). + 4. If innerResult is a normal completion, then + a. Let return be innerResult.[[Value]]. + b. If return is undefined, return ? completion. + c. Set innerResult to Completion(Call(return, iterator)). + 5. If completion is a throw completion, return ? completion. + ... +includes: [compareArray.js] +features: [joint-iteration] +---*/ + +class ExpectedError extends Error {} + +var log = []; + +var first = { + next() { + log.push("unexpected call to next method"); + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, first); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("close first iterator"); + + // IteratorClose ignores new exceptions when called with a Throw completion. + throw new Test262Error(); + }, +}; + +var second = { + next() { + log.push("unexpected call to next method"); + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, second); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("close second iterator"); + + // IteratorClose ignores new exceptions when called with a Throw completion. + throw new Test262Error(); + }, +}; + +var iterables = { + first, + second, + get third() { + throw new ExpectedError(); + } +}; + +assert.throws(ExpectedError, function() { + Iterator.zipKeyed(iterables); +}); + +// Ensure iterators are closed in the correct order. +assert.compareArray(log, [ + "close second iterator", + "close first iterator", +]); diff --git a/test/built-ins/Iterator/zipKeyed/iterables-iteration-get-iterator-flattenable-abrupt-completion.js b/test/built-ins/Iterator/zipKeyed/iterables-iteration-get-iterator-flattenable-abrupt-completion.js new file mode 100644 index 00000000000..5c6448ca3b2 --- /dev/null +++ b/test/built-ins/Iterator/zipKeyed/iterables-iteration-get-iterator-flattenable-abrupt-completion.js @@ -0,0 +1,147 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zipkeyed +description: > + Handle abrupt completions during iterables iteration. +info: | + Iterator.zipKeyed ( iterables [ , options ] ) + ... + 12. For each element key of allKeys, do + ... + c. If desc is not undefined and desc.[[Enumerable]] is true, then + ... + iii. If value is not undefined, then + ... + 2. Let iter be Completion(GetIteratorFlattenable(value, reject-strings)). + 3. IfAbruptCloseIterators(iter, iters). + ... + + GetIteratorFlattenable ( obj, primitiveHandling ) + 1. If obj is not an Object, then + a. If primitiveHandling is reject-primitives, throw a TypeError exception. + b. Assert: primitiveHandling is iterate-string-primitives. + c. If obj is not a String, throw a TypeError exception. + 2. Let method be ? GetMethod(obj, %Symbol.iterator%). + 3. If method is undefined, then + a. Let iterator be obj. + 4. Else, + a. Let iterator be ? Call(method, obj). + 5. If iterator is not an Object, throw a TypeError exception. + 6. Return ? GetIteratorDirect(iterator). + + IteratorCloseAll ( iters, completion ) + 1. For each element iter of iters, in reverse List order, do + a. Set completion to Completion(IteratorClose(iter, completion)). + 2. Return ? completion. + + IteratorClose ( iteratorRecord, completion ) + 1. Assert: iteratorRecord.[[Iterator]] is an Object. + 2. Let iterator be iteratorRecord.[[Iterator]]. + 3. Let innerResult be Completion(GetMethod(iterator, "return")). + 4. If innerResult is a normal completion, then + a. Let return be innerResult.[[Value]]. + b. If return is undefined, return ? completion. + c. Set innerResult to Completion(Call(return, iterator)). + 5. If completion is a throw completion, return ? completion. + ... +includes: [compareArray.js] +features: [joint-iteration] +---*/ + +class ExpectedError extends Error {} + +var badIterators = [ + // Throw TypeError in GetIteratorFlattenable because strings are rejected. + { + iterator: "bad iterator", + error: TypeError + }, + + // Throw an error when GetIteratorFlattenable performs GetMethod. + { + iterator: { + get [Symbol.iterator]() { + throw new ExpectedError(); + } + }, + error: ExpectedError, + }, + + // Throw an error when GetIteratorFlattenable performs Call. + { + iterator: { + [Symbol.iterator]() { + throw new ExpectedError(); + } + }, + error: ExpectedError, + }, + + // Throw an error when GetIteratorFlattenable performs GetIteratorDirect. + { + iterator: { + get next() { + throw new ExpectedError(); + } + }, + error: ExpectedError, + }, +]; + +function makeIterables(badIterator) { + var log = []; + + var first = { + next() { + log.push("unexpected call to next method"); + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, first); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("close first iterator"); + + // IteratorClose ignores new exceptions when called with a Throw completion. + throw new Test262Error(); + }, + }; + + var second = { + next() { + log.push("unexpected call to next method"); + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, second); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("close second iterator"); + + // IteratorClose ignores new exceptions when called with a Throw completion. + throw new Test262Error(); + }, + }; + + var iterables = {first, second, badIterator}; + + return {log, iterables}; +} + +for (var {iterator, error} of badIterators) { + var {log, iterables} = makeIterables(iterator); + + assert.throws(error, function() { + Iterator.zipKeyed(iterables); + }); + + // Ensure iterators are closed in the correct order. + assert.compareArray(log, [ + "close second iterator", + "close first iterator", + ]); +} diff --git a/test/built-ins/Iterator/zipKeyed/iterables-iteration-get-own-property-abrupt-completion.js b/test/built-ins/Iterator/zipKeyed/iterables-iteration-get-own-property-abrupt-completion.js new file mode 100644 index 00000000000..8265a917ba3 --- /dev/null +++ b/test/built-ins/Iterator/zipKeyed/iterables-iteration-get-own-property-abrupt-completion.js @@ -0,0 +1,95 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zipkeyed +description: > + Handle abrupt completions during iterables iteration. +info: | + Iterator.zipKeyed ( iterables [ , options ] ) + ... + 12. For each element key of allKeys, do + a. Let desc be Completion(iterables.[[GetOwnProperty]](key)). + b. IfAbruptCloseIterators(desc, iters). + ... + + IfAbruptCloseIterators ( value, iteratorRecords ) + 1. Assert: value is a Completion Record. + 2. If value is an abrupt completion, return ? IteratorCloseAll(iteratorRecords, value). + 3. Else, set value to value.[[Value]]. + + IteratorCloseAll ( iters, completion ) + 1. For each element iter of iters, in reverse List order, do + a. Set completion to Completion(IteratorClose(iter, completion)). + 2. Return ? completion. + + IteratorClose ( iteratorRecord, completion ) + 1. Assert: iteratorRecord.[[Iterator]] is an Object. + 2. Let iterator be iteratorRecord.[[Iterator]]. + 3. Let innerResult be Completion(GetMethod(iterator, "return")). + 4. If innerResult is a normal completion, then + a. Let return be innerResult.[[Value]]. + b. If return is undefined, return ? completion. + c. Set innerResult to Completion(Call(return, iterator)). + 5. If completion is a throw completion, return ? completion. + ... +includes: [compareArray.js] +features: [joint-iteration] +---*/ + +class ExpectedError extends Error {} + +var log = []; + +var first = { + next() { + log.push("unexpected call to next method"); + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, first); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("close first iterator"); + + // IteratorClose ignores new exceptions when called with a Throw completion. + throw new Test262Error(); + }, +}; + +var second = { + next() { + log.push("unexpected call to next method"); + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, second); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("close second iterator"); + + // IteratorClose ignores new exceptions when called with a Throw completion. + throw new Test262Error(); + }, +}; + +var iterables = new Proxy({first, second, third: null}, { + getOwnPropertyDescriptor(target, propertyKey) { + if (propertyKey === "third") { + throw new ExpectedError(); + } + return Reflect.getOwnPropertyDescriptor(target, propertyKey); + } +}); + +assert.throws(ExpectedError, function() { + Iterator.zipKeyed(iterables); +}); + +// Ensure iterators are closed in the correct order. +assert.compareArray(log, [ + "close second iterator", + "close first iterator", +]); diff --git a/test/built-ins/Iterator/zipKeyed/iterables-iteration.js b/test/built-ins/Iterator/zipKeyed/iterables-iteration.js new file mode 100644 index 00000000000..7b960622b6d --- /dev/null +++ b/test/built-ins/Iterator/zipKeyed/iterables-iteration.js @@ -0,0 +1,122 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zipkeyed +description: > + Perform iteration of the "iterables" argument. +info: | + Iterator.zipKeyed ( iterables [ , options ] ) + ... + 10. Let allKeys be ? iterables.[[OwnPropertyKeys]](). + 11. Let keys be a new empty List. + 12. For each element key of allKeys, do + a. Let desc be Completion(iterables.[[GetOwnProperty]](key)). + ... + c. If desc is not undefined and desc.[[Enumerable]] is true, then + i. Let value be Completion(Get(iterables, key)). + ... + iii. If value is not undefined, then + 1. Append key to keys. + 2. Let iter be Completion(GetIteratorFlattenable(value, reject-strings)). + ... + 4. Append iter to iters. + ... +includes: [proxyTrapsHelper.js, compareArray.js] +features: [joint-iteration] +---*/ + +// Object implementing Iterator protocol, but throws when calling any Iterator methods. +var throwingIterator = { + next() { + throw new Test262Error(); + }, + return() { + throw new Test262Error(); + } +}; + +var iterableReturningThrowingIterator = { + [Symbol.iterator]() { + return throwingIterator; + } +}; + +// |undefined| values are ignored. +Iterator.zipKeyed({ + a: undefined, +}); + +// GetIteratorFlattenable accepts both iterables and iterators. +Iterator.zipKeyed({ + a: throwingIterator, + b: iterableReturningThrowingIterator, +}); + +// GetIteratorFlattenable rejects non-objects. +var badIterators = [ + null, + true, + "", + Symbol(), + 0, + 0n, +]; + +for (var iterator of badIterators) { + assert.throws(TypeError, function() { + Iterator.zipKeyed({a: iterator}); + }); +} + +// [[OwnPropertyKeys]], [[GetOwnProperty]], [[Get]], and GetIteratorFlattenable +// read properties in the correct order. +var log = []; + +function makeProxyWithGetHandler(name, obj) { + return new Proxy(obj, allowProxyTraps({ + ownKeys(target) { + log.push(`${name}::[[OwnPropertyKeys]]}`); + return Reflect.ownKeys(target); + }, + getOwnPropertyDescriptor(target, propertyKey) { + log.push(`${name}::[[GetOwnProperty]](${String(propertyKey)})`); + return Reflect.getOwnPropertyDescriptor(target, propertyKey); + }, + get(target, propertyKey, receiver) { + log.push(`${name}::[[Get]](${String(propertyKey)})`); + return Reflect.get(target, propertyKey, receiver); + }, + })); +} + +var iterables = makeProxyWithGetHandler("iterables", { + // An iterator. + a: makeProxyWithGetHandler("first", throwingIterator), + + // An iterable. + b: makeProxyWithGetHandler("second", iterableReturningThrowingIterator), + + // An object without any iteration methods. + c: makeProxyWithGetHandler("third", {}), +}); + +Iterator.zipKeyed(iterables); + +assert.compareArray(log, [ + "iterables::[[OwnPropertyKeys]]}", + + "iterables::[[GetOwnProperty]](a)", + "iterables::[[Get]](a)", + "first::[[Get]](Symbol(Symbol.iterator))", + "first::[[Get]](next)", + + "iterables::[[GetOwnProperty]](b)", + "iterables::[[Get]](b)", + "second::[[Get]](Symbol(Symbol.iterator))", + + "iterables::[[GetOwnProperty]](c)", + "iterables::[[Get]](c)", + "third::[[Get]](Symbol(Symbol.iterator))", + "third::[[Get]](next)", +]); diff --git a/test/built-ins/Iterator/zipKeyed/iterables-primitive.js b/test/built-ins/Iterator/zipKeyed/iterables-primitive.js new file mode 100644 index 00000000000..a0d6ac1f468 --- /dev/null +++ b/test/built-ins/Iterator/zipKeyed/iterables-primitive.js @@ -0,0 +1,50 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zipkeyed +description: > + Throws a TypeError when the "iterables" argument is not an object. +info: | + Iterator.zipKeyed ( iterables [ , options ] ) + 1. If iterables is not an Object, throw a TypeError exception. + ... +features: [joint-iteration] +---*/ + +var invalidIterables = [ + undefined, + null, + true, + "", + Symbol(), + 0, + 0n, +]; + +// Throws when the "iterables" argument is absent. +assert.throws(TypeError, function() { + Iterator.zipKeyed(); +}); + +// Throws a TypeError for invalid iterables values. +for (var iterables of invalidIterables) { + assert.throws(TypeError, function() { + Iterator.zipKeyed(iterables); + }); +} + +// Options argument not read when iterables is not an object. +var badOptions = { + get mode() { + throw new Test262Error(); + }, + get padding() { + throw new Test262Error(); + } +}; +for (var iterables of invalidIterables) { + assert.throws(TypeError, function() { + Iterator.zipKeyed(iterables, badOptions); + }); +} diff --git a/test/built-ins/Iterator/zipKeyed/iterator-zip-iteration-iterator-close-abrupt-completion.js b/test/built-ins/Iterator/zipKeyed/iterator-zip-iteration-iterator-close-abrupt-completion.js new file mode 100644 index 00000000000..a1d24e423c0 --- /dev/null +++ b/test/built-ins/Iterator/zipKeyed/iterator-zip-iteration-iterator-close-abrupt-completion.js @@ -0,0 +1,116 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zipkeyed +description: > + Handle abrupt completion from IteratorStep in IteratorZip. +info: | + Iterator.zipKeyed ( iterables [ , options ] ) + ... + 16. Return IteratorZip(iters, mode, padding, finishResults). + + IteratorZip ( iters, mode, padding, finishResults ) + 3. Let closure be a new Abstract Closure with no parameters that captures + iters, iterCount, openIters, mode, padding, and finishResults, and + performs the following steps when called: + ... + b. Repeat, + ... + v. Let completion be Completion(Yield(results)). + vi. If completion is an abrupt completion, then + 1. Return ? IteratorCloseAll(openIters, completion). + ... + + IteratorCloseAll ( iters, completion ) + 1. For each element iter of iters, in reverse List order, do + a. Set completion to Completion(IteratorClose(iter, completion)). + 2. Return ? completion. + + IteratorClose ( iteratorRecord, completion ) + 1. Assert: iteratorRecord.[[Iterator]] is an Object. + 2. Let iterator be iteratorRecord.[[Iterator]]. + 3. Let innerResult be Completion(GetMethod(iterator, "return")). + 4. If innerResult is a normal completion, then + a. Let return be innerResult.[[Value]]. + b. If return is undefined, return ? completion. + c. Set innerResult to Completion(Call(return, iterator)). + 5. If completion is a throw completion, return ? completion. + ... +includes: [compareArray.js] +features: [joint-iteration] +---*/ + +function ExpectedError() {} + +var log = []; + +var first = { + next() { + log.push("call first next"); + return {done: false}; + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, first); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("call first return"); + + // IteratorClose ignores new exceptions when called with a Throw completion. + throw new Test262Error(); + } +}; + +var second = { + next() { + log.push("call second next"); + return {done: false}; + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, second); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("call second return"); + + throw new ExpectedError(); + } +}; + +var third = { + next() { + log.push("call third next"); + return {done: false}; + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, third); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("call third return"); + + return {}; + } +}; + +var it = Iterator.zipKeyed({first, second, third}); + +it.next(); + +assert.throws(ExpectedError, function() { + it.return(); +}); + +assert.compareArray(log, [ + "call first next", + "call second next", + "call third next", + + "call third return", + "call second return", + "call first return", +]); diff --git a/test/built-ins/Iterator/zipKeyed/iterator-zip-iteration-iterator-step-value-abrupt-completion.js b/test/built-ins/Iterator/zipKeyed/iterator-zip-iteration-iterator-step-value-abrupt-completion.js new file mode 100644 index 00000000000..007acbc1b52 --- /dev/null +++ b/test/built-ins/Iterator/zipKeyed/iterator-zip-iteration-iterator-step-value-abrupt-completion.js @@ -0,0 +1,145 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zipkeyed +description: > + Handle abrupt completion from IteratorStepValue in IteratorZip. +info: | + Iterator.zipKeyed ( iterables [ , options ] ) + ... + 16. Return IteratorZip(iters, mode, padding, finishResults). + + IteratorZip ( iters, mode, padding, finishResults ) + 3. Let closure be a new Abstract Closure with no parameters that captures + iters, iterCount, openIters, mode, padding, and finishResults, and + performs the following steps when called: + ... + b. Repeat, + ... + iii. For each integer i such that 0 ≤ i < iterCount, in ascending order, do + ... + 3. Else, + a. Let result be Completion(IteratorStepValue(iter)). + b. If result is an abrupt completion, then + i. Remove iter from openIters. + ii. Return ? IteratorCloseAll(openIters, result). + ... + d. If result is done, then + i. Remove iter from openIters. + ... + + IteratorCloseAll ( iters, completion ) + 1. For each element iter of iters, in reverse List order, do + a. Set completion to Completion(IteratorClose(iter, completion)). + 2. Return ? completion. + + IteratorClose ( iteratorRecord, completion ) + 1. Assert: iteratorRecord.[[Iterator]] is an Object. + 2. Let iterator be iteratorRecord.[[Iterator]]. + 3. Let innerResult be Completion(GetMethod(iterator, "return")). + 4. If innerResult is a normal completion, then + a. Let return be innerResult.[[Value]]. + b. If return is undefined, return ? completion. + c. Set innerResult to Completion(Call(return, iterator)). + 5. If completion is a throw completion, return ? completion. + ... +includes: [compareArray.js] +features: [joint-iteration] +---*/ + +var modes = [ + "shortest", + "longest", + "strict", +]; + +function ExpectedError() {} + +var log = []; + +var first = { + next() { + log.push("call first next"); + throw new ExpectedError(); + }, + return() { + log.push("unexpected call first return"); + } +}; + +var second = { + next() { + log.push("unexpected call second next"); + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, second); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("call second return"); + + // IteratorClose ignores new exceptions when called with a Throw completion. + throw new Test262Error(); + } +}; + +var third = { + next() { + log.push("unexpected call third next"); + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, third); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("call third return"); + + // IteratorClose ignores new exceptions when called with a Throw completion. + throw new Test262Error(); + } +}; + +// Empty iterator to ensure |return| is not called for closed iterators. +var empty = { + next() { + log.push("call empty next"); + return {done: true}; + }, + return() { + log.push("unexpected call empty return"); + } +}; + +for (var mode of modes) { + var it = Iterator.zipKeyed({first, second, third}, {mode}); + + assert.throws(ExpectedError, function() { + it.next(); + }); + + assert.compareArray(log, [ + "call first next", + "call third return", + "call second return", + ]); + + // Clear log. + log.length = 0; +} + +// This case applies only when mode is "longest". +var it = Iterator.zipKeyed({empty, first, second, third}, {mode: "longest"}); + +assert.throws(ExpectedError, function() { + it.next(); +}); + +assert.compareArray(log, [ + "call empty next", + "call first next", + "call third return", + "call second return", +]); diff --git a/test/built-ins/Iterator/zipKeyed/iterator-zip-iteration-longest-iterator-close-abrupt-completion.js b/test/built-ins/Iterator/zipKeyed/iterator-zip-iteration-longest-iterator-close-abrupt-completion.js new file mode 100644 index 00000000000..f7d7f5d1c0c --- /dev/null +++ b/test/built-ins/Iterator/zipKeyed/iterator-zip-iteration-longest-iterator-close-abrupt-completion.js @@ -0,0 +1,127 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zipkeyed +description: > + Handle abrupt completion from IteratorStep in IteratorZip. +info: | + Iterator.zipKeyed ( iterables [ , options ] ) + ... + 16. Return IteratorZip(iters, mode, padding, finishResults). + + IteratorZip ( iters, mode, padding, finishResults ) + 3. Let closure be a new Abstract Closure with no parameters that captures + iters, iterCount, openIters, mode, padding, and finishResults, and + performs the following steps when called: + ... + b. Repeat, + ... + v. Let completion be Completion(Yield(results)). + vi. If completion is an abrupt completion, then + 1. Return ? IteratorCloseAll(openIters, completion). + ... + + IteratorCloseAll ( iters, completion ) + 1. For each element iter of iters, in reverse List order, do + a. Set completion to Completion(IteratorClose(iter, completion)). + 2. Return ? completion. + + IteratorClose ( iteratorRecord, completion ) + 1. Assert: iteratorRecord.[[Iterator]] is an Object. + 2. Let iterator be iteratorRecord.[[Iterator]]. + 3. Let innerResult be Completion(GetMethod(iterator, "return")). + 4. If innerResult is a normal completion, then + a. Let return be innerResult.[[Value]]. + b. If return is undefined, return ? completion. + c. Set innerResult to Completion(Call(return, iterator)). + 5. If completion is a throw completion, return ? completion. + ... +includes: [compareArray.js] +features: [joint-iteration] +---*/ + +function ExpectedError() {} + +var log = []; + +var first = { + next() { + log.push("call first next"); + return {done: false}; + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, first); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("call first return"); + + // IteratorClose ignores new exceptions when called with a Throw completion. + throw new Test262Error(); + } +}; + +var second = { + next() { + log.push("call second next"); + return {done: false}; + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, second); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("call second return"); + + throw new ExpectedError(); + } +}; + +var third = { + next() { + log.push("call third next"); + return {done: false}; + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, third); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("call third return"); + + return {}; + } +}; + +var fourth = { + next() { + log.push("call fourth next"); + return {done: true}; + }, + return() { + log.push("unexpected call fourth return"); + } +}; + +var it = Iterator.zipKeyed({first, second, third, fourth}, {mode: "longest"}); + +it.next(); + +assert.throws(ExpectedError, function() { + it.return(); +}); + +assert.compareArray(log, [ + "call first next", + "call second next", + "call third next", + "call fourth next", + + "call third return", + "call second return", + "call first return", +]); diff --git a/test/built-ins/Iterator/zipKeyed/iterator-zip-iteration-shortest-iterator-close-abrupt-completion.js b/test/built-ins/Iterator/zipKeyed/iterator-zip-iteration-shortest-iterator-close-abrupt-completion.js new file mode 100644 index 00000000000..cf3b0dbedbd --- /dev/null +++ b/test/built-ins/Iterator/zipKeyed/iterator-zip-iteration-shortest-iterator-close-abrupt-completion.js @@ -0,0 +1,125 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zipkeyed +description: > + Handle abrupt completion from IteratorCloseAll in IteratorZip. +info: | + Iterator.zipKeyed ( iterables [ , options ] ) + ... + 16. Return IteratorZip(iters, mode, padding, finishResults). + + IteratorZip ( iters, mode, padding, finishResults ) + 3. Let closure be a new Abstract Closure with no parameters that captures + iters, iterCount, openIters, mode, padding, and finishResults, and + performs the following steps when called: + ... + b. Repeat, + ... + iii. For each integer i such that 0 ≤ i < iterCount, in ascending order, do + ... + 3. Else, + ... + d. If result is done, then + i. Remove iter from openIters. + ii. If mode is "shortest", then + i. Return ? IteratorCloseAll(openIters, ReturnCompletion(undefined)). + ... + + IteratorCloseAll ( iters, completion ) + 1. For each element iter of iters, in reverse List order, do + a. Set completion to Completion(IteratorClose(iter, completion)). + 2. Return ? completion. + + IteratorClose ( iteratorRecord, completion ) + 1. Assert: iteratorRecord.[[Iterator]] is an Object. + 2. Let iterator be iteratorRecord.[[Iterator]]. + 3. Let innerResult be Completion(GetMethod(iterator, "return")). + 4. If innerResult is a normal completion, then + a. Let return be innerResult.[[Value]]. + b. If return is undefined, return ? completion. + c. Set innerResult to Completion(Call(return, iterator)). + 5. If completion is a throw completion, return ? completion. + ... +includes: [compareArray.js] +features: [joint-iteration] +---*/ + +function ExpectedError() {} + +var log = []; + +var first = { + next() { + log.push("call first next"); + return {done: false}; + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, first); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("call first return"); + + return {}; + } +}; + +var second = { + next() { + log.push("call second next"); + return {done: true}; + }, + return() { + log.push("unexpected call second return"); + } +}; + +var third = { + next() { + log.push("unexpected call third next"); + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, third); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("call third return"); + + // IteratorClose ignores new exceptions when called with a Throw completion. + throw new Test262Error(); + } +}; + +var fourth = { + next() { + log.push("unexpected call fourth next"); + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, fourth); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("call fourth return"); + + throw new ExpectedError(); + } +}; + +var it = Iterator.zipKeyed({first, second, third, fourth}, {mode: "shortest"}); + +assert.throws(ExpectedError, function() { + it.next(); +}); + +assert.compareArray(log, [ + "call first next", + "call second next", + "call fourth return", + "call third return", + "call first return", +]); diff --git a/test/built-ins/Iterator/zipKeyed/iterator-zip-iteration-strict-iterator-close-i-is-not-zero-abrupt-completion.js b/test/built-ins/Iterator/zipKeyed/iterator-zip-iteration-strict-iterator-close-i-is-not-zero-abrupt-completion.js new file mode 100644 index 00000000000..f9dbc90e7f3 --- /dev/null +++ b/test/built-ins/Iterator/zipKeyed/iterator-zip-iteration-strict-iterator-close-i-is-not-zero-abrupt-completion.js @@ -0,0 +1,108 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zipkeyed +description: > + Handle abrupt completion from IteratorCloseAll in IteratorZip. +info: | + Iterator.zipKeyed ( iterables [ , options ] ) + ... + 16. Return IteratorZip(iters, mode, padding, finishResults). + + IteratorZip ( iters, mode, padding, finishResults ) + 3. Let closure be a new Abstract Closure with no parameters that captures + iters, iterCount, openIters, mode, padding, and finishResults, and + performs the following steps when called: + ... + b. Repeat, + ... + iii. For each integer i such that 0 ≤ i < iterCount, in ascending order, do + ... + 3. Else, + ... + d. If result is done, then + i. Remove iter from openIters. + ... + iii. Else if mode is "strict", then + i. If i ≠ 0, then + i. Return ? IteratorCloseAll(openIters, ThrowCompletion(a newly created TypeError object)). + ... + + IteratorCloseAll ( iters, completion ) + 1. For each element iter of iters, in reverse List order, do + a. Set completion to Completion(IteratorClose(iter, completion)). + 2. Return ? completion. + + IteratorClose ( iteratorRecord, completion ) + 1. Assert: iteratorRecord.[[Iterator]] is an Object. + 2. Let iterator be iteratorRecord.[[Iterator]]. + 3. Let innerResult be Completion(GetMethod(iterator, "return")). + 4. If innerResult is a normal completion, then + a. Let return be innerResult.[[Value]]. + b. If return is undefined, return ? completion. + c. Set innerResult to Completion(Call(return, iterator)). + 5. If completion is a throw completion, return ? completion. + ... +includes: [compareArray.js] +features: [joint-iteration] +---*/ + +var log = []; + +var first = { + next() { + log.push("call first next"); + return {done: false}; + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, first); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("call first return"); + + return {}; + } +}; + +var second = { + next() { + log.push("call second next"); + return {done: true}; + }, + return() { + log.push("unexpected call second return"); + } +}; + +var third = { + next() { + log.push("unexpected call third next"); + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, third); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("call third return"); + + // IteratorClose ignores new exceptions when called with a Throw completion. + throw new Test262Error(); + } +}; + +var it = Iterator.zipKeyed({first, second, third}, {mode: "strict"}); + +assert.throws(TypeError, function() { + it.next(); +}); + +assert.compareArray(log, [ + "call first next", + "call second next", + "call third return", + "call first return", +]); diff --git a/test/built-ins/Iterator/zipKeyed/iterator-zip-iteration-strict-iterator-close-i-is-zero-abrupt-completion.js b/test/built-ins/Iterator/zipKeyed/iterator-zip-iteration-strict-iterator-close-i-is-zero-abrupt-completion.js new file mode 100644 index 00000000000..b7998512b64 --- /dev/null +++ b/test/built-ins/Iterator/zipKeyed/iterator-zip-iteration-strict-iterator-close-i-is-zero-abrupt-completion.js @@ -0,0 +1,123 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zipkeyed +description: > + Handle abrupt completion from IteratorCloseAll in IteratorZip. +info: | + Iterator.zipKeyed ( iterables [ , options ] ) + ... + 16. Return IteratorZip(iters, mode, padding, finishResults). + + IteratorZip ( iters, mode, padding, finishResults ) + 3. Let closure be a new Abstract Closure with no parameters that captures + iters, iterCount, openIters, mode, padding, and finishResults, and + performs the following steps when called: + ... + b. Repeat, + ... + iii. For each integer i such that 0 ≤ i < iterCount, in ascending order, do + ... + 3. Else, + ... + d. If result is done, then + i. Remove iter from openIters. + ... + iii. Else if mode is "strict", then + ... + ii. For each integer k such that 1 ≤ k < iterCount, in ascending order, do + ... + iv. Else, + i. Return ? IteratorCloseAll(openIters, ThrowCompletion(a newly created TypeError object)). + ... + + IteratorCloseAll ( iters, completion ) + 1. For each element iter of iters, in reverse List order, do + a. Set completion to Completion(IteratorClose(iter, completion)). + 2. Return ? completion. + + IteratorClose ( iteratorRecord, completion ) + 1. Assert: iteratorRecord.[[Iterator]] is an Object. + 2. Let iterator be iteratorRecord.[[Iterator]]. + 3. Let innerResult be Completion(GetMethod(iterator, "return")). + 4. If innerResult is a normal completion, then + a. Let return be innerResult.[[Value]]. + b. If return is undefined, return ? completion. + c. Set innerResult to Completion(Call(return, iterator)). + 5. If completion is a throw completion, return ? completion. + ... +includes: [compareArray.js] +features: [joint-iteration] +---*/ + +var log = []; + +var first = { + next() { + log.push("call first next"); + return {done: true}; + }, + return() { + log.push("unexpected call first return"); + } +}; + +var second = { + next() { + log.push("call second next"); + return {done: true}; + }, + return() { + log.push("unexpected call second return"); + } +}; + +var third = { + next() { + log.push("call third next"); + return {done: false}; + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, third); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("call third return"); + + // IteratorClose ignores new exceptions when called with a Throw completion. + throw new Test262Error(); + } +}; + +var fourth = { + next() { + log.push("unexpected call fourth next"); + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, fourth); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("call fourth return"); + + // IteratorClose ignores new exceptions when called with a Throw completion. + throw new Test262Error(); + } +}; + +var it = Iterator.zipKeyed({first, second, third, fourth}, {mode: "strict"}); + +assert.throws(TypeError, function() { + it.next(); +}); + +assert.compareArray(log, [ + "call first next", + "call second next", + "call third next", + "call fourth return", + "call third return", +]); diff --git a/test/built-ins/Iterator/zipKeyed/iterator-zip-iteration-strict-iterator-step-abrupt-completion.js b/test/built-ins/Iterator/zipKeyed/iterator-zip-iteration-strict-iterator-step-abrupt-completion.js new file mode 100644 index 00000000000..a3e24dc983a --- /dev/null +++ b/test/built-ins/Iterator/zipKeyed/iterator-zip-iteration-strict-iterator-step-abrupt-completion.js @@ -0,0 +1,125 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zipkeyed +description: > + Handle abrupt completion from IteratorStep in IteratorZip. +info: | + Iterator.zipKeyed ( iterables [ , options ] ) + ... + 16. Return IteratorZip(iters, mode, padding, finishResults). + + IteratorZip ( iters, mode, padding, finishResults ) + 3. Let closure be a new Abstract Closure with no parameters that captures + iters, iterCount, openIters, mode, padding, and finishResults, and + performs the following steps when called: + ... + b. Repeat, + ... + iii. For each integer i such that 0 ≤ i < iterCount, in ascending order, do + ... + 3. Else, + ... + d. If result is done, then + i. Remove iter from openIters. + ... + iii. Else if mode is "strict", then + ... + ii. For each integer k such that 1 ≤ k < iterCount, in ascending order, do + ... + ii. Let open be Completion(IteratorStep(iters[k])). + iii. If open is an abrupt completion, then + i. Remove iters[k] from openIters. + ii. Return ? IteratorCloseAll(openIters, open). + ... + + IteratorCloseAll ( iters, completion ) + 1. For each element iter of iters, in reverse List order, do + a. Set completion to Completion(IteratorClose(iter, completion)). + 2. Return ? completion. + + IteratorClose ( iteratorRecord, completion ) + 1. Assert: iteratorRecord.[[Iterator]] is an Object. + 2. Let iterator be iteratorRecord.[[Iterator]]. + 3. Let innerResult be Completion(GetMethod(iterator, "return")). + 4. If innerResult is a normal completion, then + a. Let return be innerResult.[[Value]]. + b. If return is undefined, return ? completion. + c. Set innerResult to Completion(Call(return, iterator)). + 5. If completion is a throw completion, return ? completion. + ... +includes: [compareArray.js] +features: [joint-iteration] +---*/ + +function ExpectedError() {} + +var log = []; + +var first = { + next() { + log.push("call first next"); + return {done: true}; + }, + return() { + log.push("unexpected call first return"); + } +}; + +var second = { + next() { + log.push("call second next"); + throw new ExpectedError(); + }, + return() { + log.push("unexpected call second return"); + } +}; + +var third = { + next() { + log.push("unexpected call third next"); + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, third); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("call third return"); + + // IteratorClose ignores new exceptions when called with a Throw completion. + throw new Test262Error(); + } +}; + +var fourth = { + next() { + log.push("unexpected call fourth next"); + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, fourth); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("call fourth return"); + + // IteratorClose ignores new exceptions when called with a Throw completion. + throw new Test262Error(); + } +}; + +var it = Iterator.zipKeyed({first, second, third, fourth}, {mode: "strict"}); + +assert.throws(ExpectedError, function() { + it.next(); +}); + +assert.compareArray(log, [ + "call first next", + "call second next", + "call fourth return", + "call third return", +]); diff --git a/test/built-ins/Iterator/zipKeyed/iterator-zip-iteration.js b/test/built-ins/Iterator/zipKeyed/iterator-zip-iteration.js new file mode 100644 index 00000000000..45ff94572ab --- /dev/null +++ b/test/built-ins/Iterator/zipKeyed/iterator-zip-iteration.js @@ -0,0 +1,155 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zipkeyed +description: > + Perform iteration in IteratorZip. +info: | + Iterator.zipKeyed ( iterables [ , options ] ) + ... + 16. Return IteratorZip(iters, mode, padding, finishResults). + + IteratorZip ( iters, mode, padding, finishResults ) + 3. Let closure be a new Abstract Closure with no parameters that captures + iters, iterCount, openIters, mode, padding, and finishResults, and + performs the following steps when called: + ... + b. Repeat, + ... + iii. For each integer i such that 0 ≤ i < iterCount, in ascending order, do + ... + 3. Else, + a. Let result be Completion(IteratorStepValue(iter)). + ... +includes: [compareArray.js] +features: [joint-iteration] +---*/ + +var modes = [ + "shortest", + "longest", + "strict", +]; + +function makeIterator(log, name, elements) { + var elementsIter = elements.values(); + var iterator = { + next() { + log.push(`call ${name} next`); + + // Called with the correct receiver and no arguments. + assert.sameValue(this, iterator); + assert.sameValue(arguments.length, 0); + + var result = elementsIter.next(); + return { + get done() { + log.push(`get ${name}.result.done`); + return result.done; + }, + get value() { + log.push(`get ${name}.result.value`); + return result.value; + }, + }; + }, + return() { + log.push(`call ${name} return`); + + // Called with the correct receiver and no arguments. + assert.sameValue(this, iterator); + assert.sameValue(arguments.length, 0); + + return { + get done() { + log.push(`unexpected get ${name}.result.done`); + return result.done; + }, + get value() { + log.push(`unexpected get ${name}.result.value`); + return result.value; + }, + }; + } + }; + return iterator; +} + +for (var mode of modes) { + var log = []; + var iterables = { + first: makeIterator(log, "first", [1, 2, 3]), + second: makeIterator(log, "second", [4, 5, 6]), + third: makeIterator(log, "third", [7, 8, 9]), + }; + var it = Iterator.zipKeyed(iterables, {mode}); + + log.push("start"); + for (var v of it) { + log.push("loop"); + } + + var expected = [ + "start", + + "call first next", + "get first.result.done", + "get first.result.value", + "call second next", + "get second.result.done", + "get second.result.value", + "call third next", + "get third.result.done", + "get third.result.value", + "loop", + + "call first next", + "get first.result.done", + "get first.result.value", + "call second next", + "get second.result.done", + "get second.result.value", + "call third next", + "get third.result.done", + "get third.result.value", + "loop", + + "call first next", + "get first.result.done", + "get first.result.value", + "call second next", + "get second.result.done", + "get second.result.value", + "call third next", + "get third.result.done", + "get third.result.value", + "loop", + ]; + + switch (mode) { + case "shortest": { + expected.push( + "call first next", + "get first.result.done", + "call third return", + "call second return", + ); + break; + } + case "longest": + case "strict": { + expected.push( + "call first next", + "get first.result.done", + "call second next", + "get second.result.done", + "call third next", + "get third.result.done", + ); + break; + } + } + + assert.compareArray(log, expected); +} diff --git a/test/built-ins/Iterator/zipKeyed/length.js b/test/built-ins/Iterator/zipKeyed/length.js new file mode 100644 index 00000000000..c76a18f0b91 --- /dev/null +++ b/test/built-ins/Iterator/zipKeyed/length.js @@ -0,0 +1,23 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zipkeyed +description: > + Iterator.zipKeyed has a "length" property whose value is 1. +info: | + ECMAScript Standard Built-in Objects + + Unless otherwise specified, the length property of a built-in + Function object has the attributes { [[Writable]]: false, [[Enumerable]]: + false, [[Configurable]]: true }. +features: [joint-iteration] +includes: [propertyHelper.js] +---*/ + +verifyProperty(Iterator.zipKeyed, "length", { + value: 1, + writable: false, + enumerable: false, + configurable: true, +}); diff --git a/test/built-ins/Iterator/zipKeyed/name.js b/test/built-ins/Iterator/zipKeyed/name.js new file mode 100644 index 00000000000..387cc7becea --- /dev/null +++ b/test/built-ins/Iterator/zipKeyed/name.js @@ -0,0 +1,30 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zipkeyed +description: > + The "name" property of Iterator.zipKeyed +info: | + 17 ECMAScript Standard Built-in Objects + + Every built-in Function object, including constructors, that is not + identified as an anonymous function has a name property whose value is a + String. Unless otherwise specified, this value is the name that is given to + the function in this specification. + + ... + + Unless otherwise specified, the name property of a built-in Function + object, if it exists, has the attributes { [[Writable]]: false, + [[Enumerable]]: false, [[Configurable]]: true }. +features: [joint-iteration] +includes: [propertyHelper.js] +---*/ + +verifyProperty(Iterator.zipKeyed, "name", { + value: "zipKeyed", + writable: false, + enumerable: false, + configurable: true, +}); diff --git a/test/built-ins/Iterator/zipKeyed/non-constructible.js b/test/built-ins/Iterator/zipKeyed/non-constructible.js new file mode 100644 index 00000000000..04c3f040654 --- /dev/null +++ b/test/built-ins/Iterator/zipKeyed/non-constructible.js @@ -0,0 +1,21 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zipkeyed +description: > + Iterator.zipKeyed is not constructible. + + Built-in function objects that are not identified as constructors do not + implement the [[Construct]] internal method unless otherwise specified in the + description of a particular function. +features: [joint-iteration] +---*/ + +assert.throws(TypeError, () => { + new Iterator.zipKeyed(); +}); + +assert.throws(TypeError, () => { + new Iterator.zipKeyed({}); +}); diff --git a/test/built-ins/Iterator/zipKeyed/options-mode.js b/test/built-ins/Iterator/zipKeyed/options-mode.js new file mode 100644 index 00000000000..2649bab6925 --- /dev/null +++ b/test/built-ins/Iterator/zipKeyed/options-mode.js @@ -0,0 +1,88 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zipkeyed +description: > + The "mode" option must be undefined or a valid string mode. +info: | + Iterator.zipKeyed ( iterables [ , options ] ) + ... + 3. Let mode be ? Get(options, "mode"). + 4. If mode is undefined, set mode to "shortest". + 5. If mode is not one of "shortest", "longest", or "strict", throw a TypeError exception. + ... +features: [joint-iteration] +---*/ + +var validModes = [ + undefined, + "shortest", + "longest", + "strict", +]; + +var invalidModes = [ + null, + false, + "", + "short", + "long", + "loose", + Symbol(), + 123, + 123n, + {}, +]; + +// Absent "mode" option. +Iterator.zipKeyed({}, {}); + +// All valid mode values are accepted. +for (var mode of validModes) { + Iterator.zipKeyed({}, {mode}); +} + +// Throws a TypeError for invalid mode options. +for (var mode of invalidModes) { + assert.throws(TypeError, function() { + Iterator.zipKeyed({}, {mode}); + }); +} + +// "padding" option is not retrieved when "mode" option is invalid. +for (var mode of invalidModes) { + var options = { + mode, + get padding() { + throw new Test262Error(); + } + }; + assert.throws(TypeError, function() { + Iterator.zipKeyed({}, options); + }); +} + +// String wrappers are not accepted. +for (var mode of validModes) { + var options = {mode: new String(mode)}; + assert.throws(TypeError, function() { + Iterator.zipKeyed({}, options); + }); +} + +// Does not call any of `toString`, `valueOf`, `Symbol.toPrimitive`. +var badMode = { + toString() { + throw new Test262Error(); + }, + valueOf() { + throw new Test262Error(); + }, + [Symbol.toPrimitive]() { + throw new Test262Error(); + }, +}; +assert.throws(TypeError, function() { + Iterator.zipKeyed({}, {mode: badMode}); +}); diff --git a/test/built-ins/Iterator/zipKeyed/options-padding.js b/test/built-ins/Iterator/zipKeyed/options-padding.js new file mode 100644 index 00000000000..d7f92ee4237 --- /dev/null +++ b/test/built-ins/Iterator/zipKeyed/options-padding.js @@ -0,0 +1,54 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zipkeyed +description: > + The "padding" option must be undefined or an object. +info: | + Iterator.zipKeyed ( iterables [ , options ] ) + ... + 6. Let paddingOption be undefined. + 7. If mode is "longest", then + a. Set paddingOption to ? Get(options, "padding"). + b. If paddingOption is not undefined and paddingOption is not an Object, throw a TypeError exception. + ... +features: [joint-iteration] +---*/ + +var validPadding = [ + undefined, + [], +]; + +var invalidPadding = [ + null, + false, + "", + Symbol(), + 123, + 123n, +]; + +// Absent "padding" option. +Iterator.zipKeyed({}, {mode: "longest"}); + +// All valid padding values are accepted. +for (var padding of validPadding) { + Iterator.zipKeyed({}, {mode: "longest", padding}); +} + +// Throws a TypeError for invalid padding options. +for (var padding of invalidPadding) { + assert.throws(TypeError, function() { + Iterator.zipKeyed({}, {mode: "longest", padding}); + }); +} + +// Invalid padding options are ignored when mode is not "longest". +for (var padding of invalidPadding) { + Iterator.zipKeyed({}, {padding}); + Iterator.zipKeyed({}, {mode: undefined, padding}); + Iterator.zipKeyed({}, {mode: "shortest", padding}); + Iterator.zipKeyed({}, {mode: "strict", padding}); +} diff --git a/test/built-ins/Iterator/zipKeyed/options.js b/test/built-ins/Iterator/zipKeyed/options.js new file mode 100644 index 00000000000..ac518b4520f --- /dev/null +++ b/test/built-ins/Iterator/zipKeyed/options.js @@ -0,0 +1,50 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zipkeyed +description: > + The "options" argument can either be undefined or an object. +info: | + Iterator.zipKeyed ( iterables [ , options ] ) + ... + 2. Set options to ? GetOptionsObject(options). + ... + + GetOptionsObject ( options ) + 1. If options is undefined, then + a. Return OrdinaryObjectCreate(null). + 2. If options is an Object, then + a. Return options. + 3. Throw a TypeError exception. +features: [joint-iteration] +---*/ + +var validOptions = [ + undefined, + {}, +]; + +var invalidOptions = [ + null, + true, + "", + Symbol(), + 0, + 0n, +]; + +// The "options" argument can also be absent. +Iterator.zipKeyed({}); + +// All valid option values are accepted. +for (var options of validOptions) { + Iterator.zipKeyed({}, options); +} + +// Throws a TypeError for invalid option values. +for (var options of invalidOptions) { + assert.throws(TypeError, function() { + Iterator.zipKeyed({}, options); + }); +} diff --git a/test/built-ins/Iterator/zipKeyed/padding-iteration-get-abrupt-completion.js b/test/built-ins/Iterator/zipKeyed/padding-iteration-get-abrupt-completion.js new file mode 100644 index 00000000000..e5b73f7581c --- /dev/null +++ b/test/built-ins/Iterator/zipKeyed/padding-iteration-get-abrupt-completion.js @@ -0,0 +1,126 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zipkeyed +description: > + Abrupt completion for Get in "padding" option iteration. +info: | + Iterator.zipKeyed ( iterables [ , options ] ) + ... + 14. If mode is "longest", then + ... + b. Else, + i. For each element key of keys, do + 1. Let value be Completion(Get(paddingOption, key)). + 2. IfAbruptCloseIterators(value, iters). + ... + + IfAbruptCloseIterators ( value, iteratorRecords ) + 1. Assert: value is a Completion Record. + 2. If value is an abrupt completion, return ? IteratorCloseAll(iteratorRecords, value). + 3. Else, set value to value.[[Value]]. + + IteratorCloseAll ( iters, completion ) + 1. For each element iter of iters, in reverse List order, do + a. Set completion to Completion(IteratorClose(iter, completion)). + 2. Return ? completion. + + IteratorClose ( iteratorRecord, completion ) + 1. Assert: iteratorRecord.[[Iterator]] is an Object. + 2. Let iterator be iteratorRecord.[[Iterator]]. + 3. Let innerResult be Completion(GetMethod(iterator, "return")). + 4. If innerResult is a normal completion, then + a. Let return be innerResult.[[Value]]. + b. If return is undefined, return ? completion. + c. Set innerResult to Completion(Call(return, iterator)). + 5. If completion is a throw completion, return ? completion. + ... +includes: [compareArray.js] +features: [joint-iteration] +---*/ + +var log = []; + +var first = { + next() { + log.push("unexpected call to next method"); + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, first); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("first return"); + + // This exception is ignored. + throw new Test262Error(); + } +}; + +var second = { + next() { + log.push("unexpected call to next method"); + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, second); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("second return"); + + // This exception is ignored. + throw new Test262Error(); + } +}; + +var third = { + next() { + log.push("unexpected call to next method"); + }, + get return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, third); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("third return"); + + // This exception is ignored. + throw new Test262Error(); + } +}; + +function ExpectedError() {} + +// Padding object throws from |get second|. +var padding = { + get first() { + log.push("padding first"); + }, + get second() { + log.push("padding second"); + throw new ExpectedError(); + }, + get third() { + log.push("unexpected padding third"); + }, +}; + +assert.throws(ExpectedError, function() { + Iterator.zipKeyed({ + first, + second, + third + }, {mode: "longest", padding}); +}); + +assert.compareArray(log, [ + "padding first", + "padding second", + "third return", + "second return", + "first return", +]); diff --git a/test/built-ins/Iterator/zipKeyed/padding-iteration.js b/test/built-ins/Iterator/zipKeyed/padding-iteration.js new file mode 100644 index 00000000000..5062f787082 --- /dev/null +++ b/test/built-ins/Iterator/zipKeyed/padding-iteration.js @@ -0,0 +1,55 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zipkeyed +description: > + Perform keys iteration on the "padding" option. +info: | + Iterator.zipKeyed ( iterables [ , options ] ) + ... + 14. If mode is "longest", then + ... + b. Else, + i. For each element key of keys, do + 1. Let value be Completion(Get(paddingOption, key)). + ... +includes: [proxyTrapsHelper.js, compareArray.js] +features: [joint-iteration] +---*/ + +function makeKeys(k) { + var str = "abcdefgh"; + assert(k <= str.length, "more than eight keys are unsupported"); + return str.slice(0, k).split(""); +} + +function fromKeys(keys, value) { + return Object.fromEntries(keys.map(function(k) { + return [k, value]; + })); +} + +for (var n = 0; n <= 5; ++n) { + // Create an object with |n| properties. + var keys = makeKeys(n); + var iterables = fromKeys(keys, []); + + for (var k = 0; k <= n + 2; ++k) { + var log = []; + + // Create a padding object with |k| properties. Ensure only [[Get]] is + // called. + var padding = new Proxy(fromKeys(makeKeys(k), undefined), allowProxyTraps({ + get(target, propertyKey, receiver) { + log.push(propertyKey); + return Reflect.get(target, propertyKey, receiver); + }, + })); + + Iterator.zipKeyed(iterables, {mode: "longest", padding}); + + // [[Get]] happened for all keys from |keys|. + assert.compareArray(log, keys); + } +} diff --git a/test/built-ins/Iterator/zipKeyed/prop-desc.js b/test/built-ins/Iterator/zipKeyed/prop-desc.js new file mode 100644 index 00000000000..43328f59c0a --- /dev/null +++ b/test/built-ins/Iterator/zipKeyed/prop-desc.js @@ -0,0 +1,23 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zipkeyed +description: > + Property descriptor of Iterator.zipKeyed +info: | + 17 ECMAScript Standard Built-in Objects + + Every other data property described in clauses 18 through 26 and in Annex B.2 + has the attributes { [[Writable]]: true, [[Enumerable]]: false, + [[Configurable]]: true } unless otherwise specified. +features: [joint-iteration] +includes: [propertyHelper.js] +---*/ + +verifyProperty(Iterator, "zipKeyed", { + value: Iterator.zipKeyed, + writable: true, + enumerable: false, + configurable: true, +}); diff --git a/test/built-ins/Iterator/zipKeyed/proto.js b/test/built-ins/Iterator/zipKeyed/proto.js new file mode 100644 index 00000000000..6cfc0ad12cf --- /dev/null +++ b/test/built-ins/Iterator/zipKeyed/proto.js @@ -0,0 +1,16 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zipkeyed +description: > + The value of the [[Prototype]] internal slot of Iterator.zipKeyed is the + intrinsic object %FunctionPrototype%. +features: [joint-iteration] +---*/ + +assert.sameValue( + Object.getPrototypeOf(Iterator.zipKeyed), + Function.prototype, + "Object.getPrototypeOf(Iterator.zipKeyed) must return the value of Function.prototype" +); diff --git a/test/built-ins/Iterator/zipKeyed/result-is-iterator.js b/test/built-ins/Iterator/zipKeyed/result-is-iterator.js new file mode 100644 index 00000000000..220f4182734 --- /dev/null +++ b/test/built-ins/Iterator/zipKeyed/result-is-iterator.js @@ -0,0 +1,20 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zipkeyed +description: > + The value of the [[Prototype]] internal slot of the return value of Iterator.zipKeyed + is the intrinsic object %IteratorHelperPrototype%. +includes: [wellKnownIntrinsicObjects.js] +features: [joint-iteration] +---*/ + +var iter = Iterator.zipKeyed({}); +assert(iter instanceof Iterator, "Iterator.zipKeyed({}) must return an Iterator"); + +assert.sameValue( + Object.getPrototypeOf(iter), + getWellKnownIntrinsicObject("%IteratorHelperPrototype%"), + "[[Prototype]] is %IteratorHelperPrototype%" +); diff --git a/test/built-ins/Iterator/zipKeyed/results-object-from-array.js b/test/built-ins/Iterator/zipKeyed/results-object-from-array.js new file mode 100644 index 00000000000..107f947c549 --- /dev/null +++ b/test/built-ins/Iterator/zipKeyed/results-object-from-array.js @@ -0,0 +1,51 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zipkeyed +description: > + Calling Iterator.zipKeyed with an array object. +info: | + Iterator.zipKeyed ( iterables [ , options ] ) + ... + 15. Let finishResults be a new Abstract Closure with parameters (results) that captures keys and iterCount and performs the following steps when called: + a. Let obj be OrdinaryObjectCreate(null). + b. For each integer i such that 0 ≤ i < iterCount, in ascending order, do + i. Perform ! CreateDataPropertyOrThrow(obj, keys[i], results[i]). + c. Return obj. + ... +features: [joint-iteration] +---*/ + +var iterables = [ + [1, 2, 3], + [4, 5, 6], +]; + +var it = Iterator.zipKeyed(iterables); + +for (var i = 0; i < iterables[0].length; ++i) { + var results = it.next().value; + + assert.sameValue( + Object.getPrototypeOf(results), + null, + "results prototype is null" + ); + + assert.sameValue( + Reflect.ownKeys(results).length, + iterables.length, + "results has correct number of properties" + ); + + for (var j = 0; j < iterables.length; ++j) { + assert.sameValue( + results[j], + iterables[j][i], + "results property value has the correct value" + ); + } +} + +assert.sameValue(it.next().done, true, "iterator is exhausted"); diff --git a/test/built-ins/Iterator/zipKeyed/results-object-has-default-attributes.js b/test/built-ins/Iterator/zipKeyed/results-object-has-default-attributes.js new file mode 100644 index 00000000000..52046016d1b --- /dev/null +++ b/test/built-ins/Iterator/zipKeyed/results-object-has-default-attributes.js @@ -0,0 +1,106 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zipkeyed +description: > + Returned object has the correct prototype and default property attributes. +info: | + Iterator.zipKeyed ( iterables [ , options ] ) + ... + 15. Let finishResults be a new Abstract Closure with parameters (results) that captures keys and iterCount and performs the following steps when called: + a. Let obj be OrdinaryObjectCreate(null). + b. For each integer i such that 0 ≤ i < iterCount, in ascending order, do + i. Perform ! CreateDataPropertyOrThrow(obj, keys[i], results[i]). + c. Return obj. + ... +includes: [compareArray.js, propertyHelper.js] +features: [joint-iteration] +---*/ + +// Assert |actual| is a plain object equal to |expected| with default property attributes. +function assertPlainObject(actual, expected) { + assert.sameValue( + Object.getPrototypeOf(actual), + null, + "[[Prototype]] of actual is null" + ); + + assert(Object.isExtensible(actual), "actual is extensible"); + + var actualKeys = Reflect.ownKeys(actual); + var expectedKeys = Reflect.ownKeys(expected); + + // All expected property keys are present. + assert.compareArray(actualKeys, expectedKeys); + + // All expected property values are equal. + for (var key of expectedKeys) { + assert.sameValue(actual[key], expected[key], "with key: " + String(key)); + } + + // Ensure all properties have the default property attributes. + for (var key of expectedKeys) { + verifyProperty(actual, key, { + writable: true, + enumerable: true, + configurable: true, + }); + } +} + +var iterables = Object.create(Array.prototype, { + a: { + writable: true, + enumerable: true, + configurable: true, + value: ["A"], + }, + b: { + writable: false, + enumerable: true, + configurable: true, + value: ["B"], + }, + c: { + writable: true, + enumerable: true, + configurable: false, + value: ["C"], + }, + d: { + writable: false, + enumerable: true, + configurable: false, + value: ["D"], + }, + e: { + enumerable: true, + configurable: true, + get() { + return ["E"]; + } + }, + f: { + enumerable: true, + configurable: false, + get() { + return ["F"]; + } + }, +}); + +var it = Iterator.zipKeyed(iterables); + +var results = it.next().value; + +assertPlainObject(results, { + a: "A", + b: "B", + c: "C", + d: "D", + e: "E", + f: "F", +}); + +assert.sameValue(it.next().done, true, "iterator is exhausted"); diff --git a/test/built-ins/Iterator/zipKeyed/results-object-has-no-undefined-iterables-properties.js b/test/built-ins/Iterator/zipKeyed/results-object-has-no-undefined-iterables-properties.js new file mode 100644 index 00000000000..8889ff2baee --- /dev/null +++ b/test/built-ins/Iterator/zipKeyed/results-object-has-no-undefined-iterables-properties.js @@ -0,0 +1,33 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zipkeyed +description: > + Undefined properties from the "iterables" object are not present in the results object +info: | + Iterator.zipKeyed ( iterables [ , options ] ) + ... + 12. For each element key of allKeys, do + a. Let desc be Completion(iterables.[[GetOwnProperty]](key)). + b. IfAbruptCloseIterators(desc, iters). + c. If desc is not undefined and desc.[[Enumerable]] is true, then + ... +features: [joint-iteration] +---*/ + +var iterables = { + a: ["A"], + b: undefined, + c: ["C"], +}; + +var it = Iterator.zipKeyed(iterables); + +var results = it.next().value; + +assert.sameValue("a" in results, true, "property 'a' is present"); +assert.sameValue("b" in results, false, "property 'b' is not present"); +assert.sameValue("c" in results, true, "property 'c' is present"); + +assert.sameValue(it.next().done, true, "iterator is exhausted"); diff --git a/test/built-ins/Iterator/zipKeyed/suspended-start-iterator-close-calls-next.js b/test/built-ins/Iterator/zipKeyed/suspended-start-iterator-close-calls-next.js new file mode 100644 index 00000000000..56032a50a9a --- /dev/null +++ b/test/built-ins/Iterator/zipKeyed/suspended-start-iterator-close-calls-next.js @@ -0,0 +1,78 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zipkeyed +description: > + Generator is closed from suspended-start state and IteratorClose calls next. +info: | + %IteratorHelperPrototype%.return ( ) + ... + 4. If O.[[GeneratorState]] is suspended-start, then + a. Set O.[[GeneratorState]] to completed. + ... + c. Perform ? IteratorCloseAll(O.[[UnderlyingIterators]], ReturnCompletion(undefined)). + d. Return CreateIteratorResultObject(undefined, true). + ... + + IteratorCloseAll ( iters, completion ) + 1. For each element iter of iters, in reverse List order, do + a. Set completion to Completion(IteratorClose(iter, completion)). + 2. Return ? completion. + + IteratorClose ( iteratorRecord, completion ) + ... + 3. Let innerResult be Completion(GetMethod(iterator, "return")). + 4. If innerResult is a normal completion, then + a. Let return be innerResult.[[Value]]. + b. If return is undefined, return ? completion. + c. Set innerResult to Completion(Call(return, iterator)). + ... + + %IteratorHelperPrototype%.next ( ) + 1. Return ? GeneratorResume(this value, undefined, "Iterator Helper"). + + GeneratorResume ( generator, value, generatorBrand ) + 1. Let state be ? GeneratorValidate(generator, generatorBrand). + 2. If state is completed, return CreateIteratorResultObject(undefined, true). + ... + + GeneratorValidate ( generator, generatorBrand ) + ... + 5. Let state be generator.[[GeneratorState]]. + 6. If state is executing, throw a TypeError exception. + 7. Return state. +features: [joint-iteration] +---*/ + +var returnCallCount = 0; + +var underlying = { + next() { + throw new Test262Error("Unexpected call to next"); + }, + return() { + returnCallCount += 1; + + // The generator state is already set to "completed". The generator state is + // not "executing", so `GeneratorValidate` succeeds and `GeneratorResume` + // returns with `CreateIteratorResultObject()`. + var result = it.next(); + assert.sameValue(result.value, undefined); + assert.sameValue(result.done, true); + + return {}; + }, +}; + +var it = Iterator.zipKeyed({a: underlying}); + +assert.sameValue(returnCallCount, 0); + +// This `return()` call sets the generator state to "completed" and then calls +// `IteratorClose()`. +var result = it.return(); +assert.sameValue(result.value, undefined); +assert.sameValue(result.done, true); + +assert.sameValue(returnCallCount, 1); diff --git a/test/built-ins/Iterator/zipKeyed/suspended-start-iterator-close-calls-return.js b/test/built-ins/Iterator/zipKeyed/suspended-start-iterator-close-calls-return.js new file mode 100644 index 00000000000..938786dc792 --- /dev/null +++ b/test/built-ins/Iterator/zipKeyed/suspended-start-iterator-close-calls-return.js @@ -0,0 +1,82 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zipkeyed +description: > + Generator is closed from suspended-start state and IteratorClose calls return. +info: | + %IteratorHelperPrototype%.return ( ) + ... + 4. If O.[[GeneratorState]] is suspended-start, then + a. Set O.[[GeneratorState]] to completed. + ... + c. Perform ? IteratorCloseAll(O.[[UnderlyingIterators]], ReturnCompletion(undefined)). + d. Return CreateIteratorResultObject(undefined, true). + 5. Let C be ReturnCompletion(undefined). + 6. Return ? GeneratorResumeAbrupt(O, C, "Iterator Helper"). + + IteratorCloseAll ( iters, completion ) + 1. For each element iter of iters, in reverse List order, do + a. Set completion to Completion(IteratorClose(iter, completion)). + 2. Return ? completion. + + IteratorClose ( iteratorRecord, completion ) + ... + 3. Let innerResult be Completion(GetMethod(iterator, "return")). + 4. If innerResult is a normal completion, then + a. Let return be innerResult.[[Value]]. + b. If return is undefined, return ? completion. + c. Set innerResult to Completion(Call(return, iterator)). + ... + 8. Return ? completion. + + GeneratorResumeAbrupt ( generator, abruptCompletion, generatorBrand ) + 1. Let state be ? GeneratorValidate(generator, generatorBrand). + 2. If state is suspended-start, then + ... + 3. If state is completed, then + a. If abruptCompletion is a return completion, then + i. Return CreateIteratorResultObject(abruptCompletion.[[Value]], true). + ... + + GeneratorValidate ( generator, generatorBrand ) + ... + 5. Let state be generator.[[GeneratorState]]. + 6. If state is executing, throw a TypeError exception. + 7. Return state. +features: [joint-iteration] +---*/ + +var returnCallCount = 0; + +var underlying = { + next() { + throw new Test262Error("Unexpected call to next"); + }, + return() { + returnCallCount += 1; + + // The generator state is already set to "completed", so this `return()` + // call proceeds to `GeneratorResumeAbrupt`. The generator state is not + // "executing", so `GeneratorValidate` succeeds and `GeneratorResumeAbrupt` + // returns with `CreateIteratorResultObject()`. + var result = it.return(); + assert.sameValue(result.value, undefined); + assert.sameValue(result.done, true); + + return {}; + }, +}; + +var it = Iterator.zipKeyed({a: underlying}); + +assert.sameValue(returnCallCount, 0); + +// This `return()` call sets the generator state to "completed" and then calls +// `IteratorClose()`. +var result = it.return(); +assert.sameValue(result.value, undefined); +assert.sameValue(result.done, true); + +assert.sameValue(returnCallCount, 1); diff --git a/test/built-ins/Iterator/zipKeyed/suspended-yield-iterator-close-calls-next.js b/test/built-ins/Iterator/zipKeyed/suspended-yield-iterator-close-calls-next.js new file mode 100644 index 00000000000..b825a2f3e0b --- /dev/null +++ b/test/built-ins/Iterator/zipKeyed/suspended-yield-iterator-close-calls-next.js @@ -0,0 +1,101 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zipkeyed +description: > + Generator is closed from suspended-yield state and IteratorClose calls next. +info: | + %IteratorHelperPrototype%.return ( ) + ... + 5. Let C be ReturnCompletion(undefined). + 6. Return ? GeneratorResumeAbrupt(O, C, "Iterator Helper"). + + GeneratorResumeAbrupt ( generator, abruptCompletion, generatorBrand ) + 1. Let state be ? GeneratorValidate(generator, generatorBrand). + ... + 4. Assert: state is suspended-yield. + ... + 8. Set generator.[[GeneratorState]] to executing. + ... + 10. Resume the suspended evaluation of genContext using abruptCompletion as + the result of the operation that suspended it. Let result be the + Completion Record returned by the resumed computation. + ... + + GeneratorValidate ( generator, generatorBrand ) + ... + 5. Let state be generator.[[GeneratorState]]. + 6. If state is executing, throw a TypeError exception. + 7. Return state. + + IteratorZip ( iters, mode, padding, finishResults ) + ... + 3. Let closure be a new Abstract Closure with no parameters that captures + iters, iterCount, openIters, mode, padding, and finishResults, and + performs the following steps when called: + ... + b. Repeat, + ... + v. Let completion be Completion(Yield(results)). + vi. If completion is an abrupt completion, then + 1. Return ? IteratorCloseAll(openIters, completion). + ... + + IteratorCloseAll ( iters, completion ) + 1. For each element iter of iters, in reverse List order, do + a. Set completion to Completion(IteratorClose(iter, completion)). + 2. Return ? completion. + + IteratorClose ( iteratorRecord, completion ) + ... + 3. Let innerResult be Completion(GetMethod(iterator, "return")). + 4. If innerResult is a normal completion, then + a. Let return be innerResult.[[Value]]. + b. If return is undefined, return ? completion. + c. Set innerResult to Completion(Call(return, iterator)). + ... + + %IteratorHelperPrototype%.next ( ) + 1. Return ? GeneratorResume(this value, undefined, "Iterator Helper"). + + GeneratorResume ( generator, value, generatorBrand ) + 1. Let state be ? GeneratorValidate(generator, generatorBrand). + ... +features: [joint-iteration] +---*/ + +var returnCallCount = 0; + +var underlying = { + next() { + return {value: 123, done: false}; + }, + return() { + returnCallCount += 1; + + // The generator state is set to "executing", so this `next()` call throws + // a TypeError when `GeneratorResume` performs `GeneratorValidate`. + assert.throws(TypeError, function() { + it.next(); + }); + + return {}; + }, +}; + +var it = Iterator.zipKeyed({a: underlying}); + +// Move generator into "suspended-yield" state. +var result = it.next(); +assert.sameValue(result.value.a, 123); +assert.sameValue(result.done, false); + +assert.sameValue(returnCallCount, 0); + +// This `return()` call continues execution in the suspended generator. +var result = it.return(); +assert.sameValue(result.value, undefined); +assert.sameValue(result.done, true); + +assert.sameValue(returnCallCount, 1); diff --git a/test/built-ins/Iterator/zipKeyed/suspended-yield-iterator-close-calls-return.js b/test/built-ins/Iterator/zipKeyed/suspended-yield-iterator-close-calls-return.js new file mode 100644 index 00000000000..e58f2cf0cfe --- /dev/null +++ b/test/built-ins/Iterator/zipKeyed/suspended-yield-iterator-close-calls-return.js @@ -0,0 +1,94 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zipkeyed +description: > + Generator is closed from suspended-yield state and IteratorClose calls return. +info: | + %IteratorHelperPrototype%.return ( ) + ... + 5. Let C be ReturnCompletion(undefined). + 6. Return ? GeneratorResumeAbrupt(O, C, "Iterator Helper"). + + GeneratorResumeAbrupt ( generator, abruptCompletion, generatorBrand ) + 1. Let state be ? GeneratorValidate(generator, generatorBrand). + ... + 4. Assert: state is suspended-yield. + ... + 8. Set generator.[[GeneratorState]] to executing. + ... + 10. Resume the suspended evaluation of genContext using abruptCompletion as + the result of the operation that suspended it. Let result be the + Completion Record returned by the resumed computation. + ... + + GeneratorValidate ( generator, generatorBrand ) + ... + 5. Let state be generator.[[GeneratorState]]. + 6. If state is executing, throw a TypeError exception. + 7. Return state. + + IteratorZip ( iters, mode, padding, finishResults ) + ... + 3. Let closure be a new Abstract Closure with no parameters that captures + iters, iterCount, openIters, mode, padding, and finishResults, and + performs the following steps when called: + ... + b. Repeat, + ... + v. Let completion be Completion(Yield(results)). + vi. If completion is an abrupt completion, then + 1. Return ? IteratorCloseAll(openIters, completion). + ... + + IteratorCloseAll ( iters, completion ) + 1. For each element iter of iters, in reverse List order, do + a. Set completion to Completion(IteratorClose(iter, completion)). + 2. Return ? completion. + + IteratorClose ( iteratorRecord, completion ) + ... + 3. Let innerResult be Completion(GetMethod(iterator, "return")). + 4. If innerResult is a normal completion, then + a. Let return be innerResult.[[Value]]. + b. If return is undefined, return ? completion. + c. Set innerResult to Completion(Call(return, iterator)). + ... +features: [joint-iteration] +---*/ + +var returnCallCount = 0; + +var underlying = { + next() { + return {value: 123, done: false}; + }, + return() { + returnCallCount += 1; + + // The generator state is set to "executing", so this `return()` call throws + // a TypeError when `GeneratorResumeAbrupt` performs `GeneratorValidate`. + assert.throws(TypeError, function() { + it.return(); + }); + + return {}; + }, +}; + +var it = Iterator.zipKeyed({a: underlying}); + +// Move generator into "suspended-yield" state. +var result = it.next(); +assert.sameValue(result.value.a, 123); +assert.sameValue(result.done, false); + +assert.sameValue(returnCallCount, 0); + +// This `return()` call continues execution in the suspended generator. +var result = it.return(); +assert.sameValue(result.value, undefined); +assert.sameValue(result.done, true); + +assert.sameValue(returnCallCount, 1);