commit ee59ccae6549ed060a176932442c9e3735a8761c
parent 59e2d6aa313559cb8e9cacdebbb4a0be23bd069e
Author: Meg Ford <meg387@gmail.com>
Date: Tue, 16 Dec 2025 17:14:30 +0000
Bug 2000092 - Add initial implementation for IteratorChunks. r=dminor,arai
Differential Revision: https://phabricator.services.mozilla.com/D274493
Diffstat:
3 files changed, 122 insertions(+), 1 deletion(-)
diff --git a/js/public/friend/ErrorNumbers.msg b/js/public/friend/ErrorNumbers.msg
@@ -839,6 +839,7 @@ MSG_DEF(JSMSG_BAD_WEAKREF_TARGET, 0, JSEXN_TYPEERR, "cann
// Iterator Helpers
MSG_DEF(JSMSG_NEGATIVE_LIMIT, 0, JSEXN_RANGEERR, "Iterator limits cannot be negative")
+MSG_DEF(JSMSG_INVALID_CHUNKSIZE, 0, JSEXN_RANGEERR, "Iterator chunkSize must be an integer between 1 and 2**32-1 inclusive")
MSG_DEF(JSMSG_ITERATOR_ZIP_INVALID_OPTION_TYPE, 2, JSEXN_TYPEERR, "invalid type for \"{0}\" option: {1}")
MSG_DEF(JSMSG_ITERATOR_ZIP_INVALID_OPTION_VALUE, 2, JSEXN_TYPEERR, "invalid value for \"{0}\" option: {1}")
MSG_DEF(JSMSG_ITERATOR_ZIP_STRICT_OPEN_ITERATOR, 0, JSEXN_TYPEERR, "too few iterator results for \"strict\" mode")
diff --git a/js/src/builtin/Iterator.js b/js/src/builtin/Iterator.js
@@ -1929,7 +1929,67 @@ function IteratorRange(start, end, optionOrStep) {
* https://tc39.es/proposal-iterator-chunking/#sec-iterator.prototype.chunks
*/
function IteratorChunks(chunkSize) {
- return false;
+ // Step 1. Let O be the this value.
+ var iterator = this;
+
+ // Step 2. If O is not an Object, throw a TypeError exception.
+ if (!IsObject(iterator)) {
+ ThrowTypeError(JSMSG_OBJECT_REQUIRED, iterator === null ? "null" : typeof iterator);
+ }
+
+ // Step 3. Let iterated be the Iterator Record
+ // { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }.
+
+ // Step 4. If chunkSize is not an integral Number in the inclusive interval
+ // from 1𝔽 to 𝔽(2**32 - 1), then
+ if (!Number_isInteger(chunkSize) || (chunkSize < 1 || chunkSize > (2 ** 32) - 1)) {
+ // Step 4.a. Let error be ThrowCompletion(a newly created RangeError object).
+ // Step 4.b. Return ? IteratorClose(iterated, error).
+ try {
+ IteratorClose(iterator);
+ } catch {}
+ ThrowRangeError(JSMSG_INVALID_CHUNKSIZE);
+ }
+
+ // Step 5. Set iterated to ? GetIteratorDirect(O).
+ var nextMethod = iterator.next;
+
+ // Step 6. Let closure be a new Abstract Closure with ...
+ // (Handled in IteratorChunksGenerator.)
+
+ // Step 7. Let result be CreateIteratorFromClosure(
+ // closure, "Iterator Helper", %IteratorHelperPrototype%,
+ // « [[UnderlyingIterators]] »
+ // ).
+ var result = NewIteratorHelper();
+ var generator = IteratorChunksGenerator(iterator, nextMethod, chunkSize);
+
+ // Step 8. Set result.[[UnderlyingIterators]] to « iterated ».
+ UnsafeSetReservedSlot(
+ result,
+ ITERATOR_HELPER_GENERATOR_SLOT,
+ generator
+ );
+ UnsafeSetReservedSlot(
+ result,
+ ITERATOR_HELPER_UNDERLYING_ITERATOR_SLOT,
+ iterator
+ );
+
+ // Step 9. Return result.
+ return result;
+}
+
+/**
+ * Iterator.prototype.chunks ( chunkSize )
+ *
+ * Abstract closure definition.
+ *
+ * https://tc39.es/proposal-iterator-chunking/#sec-iterator.prototype.chunks
+ */
+/* eslint-disable-next-line require-yield */
+function* IteratorChunksGenerator(iterator, nextMethod, chunkSize) {
+ IteratorClose(iterator);
}
/**
diff --git a/js/src/tests/non262/Iterator/prototype/chunking/chunking.js b/js/src/tests/non262/Iterator/prototype/chunking/chunking.js
@@ -0,0 +1,60 @@
+// |reftest| shell-option(--enable-iterator-chunking) skip-if(!Iterator.prototype.hasOwnProperty('chunks'))
+
+/*---
+features: [Iterator.chunks]
+---*/
+
+// Invalid parameter types
+assertThrowsInstanceOf(() => Iterator.prototype.chunks('1'), RangeError);
+assertThrowsInstanceOf(() => Iterator.prototype.chunks(null), RangeError);
+assertThrowsInstanceOf(() => Iterator.prototype.chunks(undefined), RangeError);
+assertThrowsInstanceOf(() => Iterator.prototype.chunks({}), RangeError);
+assertThrowsInstanceOf(() => Iterator.prototype.chunks([]), RangeError);
+assertThrowsInstanceOf(() => Iterator.prototype.chunks(true), RangeError);
+assertThrowsInstanceOf(() => Iterator.prototype.chunks(Symbol()), RangeError);
+assertThrowsInstanceOf(() => Iterator.prototype.chunks(() => {}), RangeError);
+assertThrowsInstanceOf(() => Iterator.prototype.chunks(10n), RangeError);
+assertThrowsInstanceOf(() => Iterator.prototype.chunks(-10n), RangeError);
+assertThrowsInstanceOf(() => Iterator.prototype.chunks(BigInt(10)), RangeError);
+assertThrowsInstanceOf(() => Iterator.prototype.chunks(BigInt(-10)), RangeError);
+
+// NaN and Infinity tests
+assertThrowsInstanceOf(() => Iterator.prototype.chunks(NaN), RangeError);
+assertThrowsInstanceOf(() => Iterator.prototype.chunks(Infinity), RangeError);
+assertThrowsInstanceOf(() => Iterator.prototype.chunks(-Infinity), RangeError);
+
+// Out of range values
+assertThrowsInstanceOf(() => Iterator.prototype.chunks(0), RangeError);
+assertThrowsInstanceOf(() => Iterator.prototype.chunks(3.25), RangeError);
+assertThrowsInstanceOf(() => Iterator.prototype.chunks(-1), RangeError);
+assertThrowsInstanceOf(() => Iterator.prototype.chunks(2 ** 32), RangeError);
+assertThrowsInstanceOf(() => Iterator.prototype.chunks(2 ** 32 + 1), RangeError);
+
+// Verify no side effect happens if you pass a non-number value
+var toPrimitiveCalled = false;
+var valueOfCalled = false;
+var toStringCalled = false;
+const testToPrimitiveObj = {
+ get [Symbol.toPrimitive]() {
+ toPrimitiveCalled = true;
+ }
+};
+const testValueOfObj = {
+ get valueOf() {
+ valueOfCalled = true;
+ }
+};
+const testToStringObj = {
+ get toString() {
+ toStringCalled = true;
+ }
+};
+assertThrowsInstanceOf(() =>[1, 2, 3, 4][Symbol.iterator]().chunks(testToPrimitiveObj), RangeError);
+assertEq(toPrimitiveCalled, false);
+assertThrowsInstanceOf(() =>[1, 2, 3, 4][Symbol.iterator]().chunks(testValueOfObj), RangeError);
+assertEq(valueOfCalled, false);
+assertThrowsInstanceOf(() =>[1, 2, 3, 4][Symbol.iterator]().chunks(testToStringObj), RangeError);
+assertEq(toStringCalled, false);
+
+if (typeof reportCompare === 'function')
+ reportCompare(0, 0);