Skip to content

Commit 84cc656

Browse files
committed
[JSC] Implement Iterator.prototype.reduce from Iterator Helpers Proposal
https://bugs.webkit.org/show_bug.cgi?id=279726 Reviewed by Yusuke Suzuki. Implement `Iterator.prototype.reduce`[1] from Iterator Helpers Proposal[2]. [1]: https://tc39.es/proposal-iterator-helpers/#sec-iteratorprototype.reduce [2]: https://github.com/tc39/proposal-iterator-helpers * JSTests/stress/iterator-prototype-reduce.js: Added. (assert): (sameArray): (shouldThrow): (throw.new.Error.Iter.prototype.get next): (throw.new.Error.Iter): (throw.new.Error): (sameArray.Iter.prototype.next): (sameArray.Iter): (sameValue.Iter.prototype.next): (sameValue.Iter): (sameValue): (prototype.next): (iter.reduce.Iter.prototype.get next): (iter.reduce.Iter.prototype.get return): (iter.reduce.Iter): (iter.reduce): * JSTests/test262/expectations.yaml: * Source/JavaScriptCore/builtins/JSIteratorPrototype.js: (reduce.wrapper.return.next): (reduce.wrapper.return.get return): (reduce.wrapper.iterator): (reduce): * Source/JavaScriptCore/runtime/JSIteratorPrototype.cpp: (JSC::JSIteratorPrototype::finishCreation): Canonical link: https://commits.webkit.org/283697@main
1 parent 0bcab78 commit 84cc656

File tree

4 files changed

+185
-75
lines changed

4 files changed

+185
-75
lines changed
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
//@ requireOptions("--useIteratorHelpers=1")
2+
3+
function assert(a, text) {
4+
if (!a)
5+
throw new Error(`Failed assertion: ${text}`);
6+
}
7+
8+
function sameValue(a, b) {
9+
if (a !== b)
10+
throw new Error(`Expected ${b} but got ${a}`);
11+
}
12+
13+
function sameArray(a, b) {
14+
sameValue(JSON.stringify(a), JSON.stringify(b));
15+
}
16+
17+
function shouldThrow(fn, error, message) {
18+
try {
19+
fn();
20+
throw new Error('Expected to throw, but succeeded');
21+
} catch (e) {
22+
if (!(e instanceof error))
23+
throw new Error(`Expected to throw ${error.name} but got ${e.name}`);
24+
if (e.message !== message)
25+
throw new Error(`Expected ${error.name} with '${message}' but got '${e.message}'`);
26+
}
27+
}
28+
29+
{
30+
let nextGetCount = 0;
31+
class Iter extends Iterator {
32+
i = 1;
33+
get next() {
34+
nextGetCount++;
35+
return function() {
36+
if (this.i > 5)
37+
return { value: this.i, done: true }
38+
return { value: this.i++, done: false }
39+
};
40+
};
41+
}
42+
const iter = new Iter();
43+
const counters = [];
44+
const sum = iter.reduce((acc, value, i) => {
45+
counters.push(i);
46+
return acc + value
47+
}, 0);
48+
sameValue(nextGetCount, 1);
49+
sameValue(sum, 15);
50+
sameArray(counters, [0, 1, 2, 3, 4]);
51+
}
52+
53+
{
54+
class Iter extends Iterator {
55+
i = 1;
56+
next() {
57+
if (this.i > 1)
58+
return { value: this.i, done: true }
59+
return { value: this.i++, done: false }
60+
}
61+
}
62+
const iter = new Iter();
63+
let reducerCalls = 0;
64+
const initialValue = {};
65+
iter.reduce((acc, value, i) => {
66+
sameValue(acc, initialValue);
67+
reducerCalls++;
68+
}, initialValue);
69+
sameValue(reducerCalls, 1);
70+
}
71+
72+
{
73+
class Iter extends Iterator {
74+
i = 1;
75+
next() {
76+
if (this.i > 5)
77+
return { value: this.i, done: true }
78+
return { value: this.i++, done: false }
79+
}
80+
};
81+
const iter = new Iter();
82+
const counters = [];
83+
const sum = iter.reduce((acc, value, i) => {
84+
counters.push(i);
85+
return acc + value
86+
});
87+
sameValue(sum, 15);
88+
sameArray(counters, [1, 2, 3, 4]);
89+
}
90+
91+
{
92+
class Iter extends Iterator {
93+
next() { return { value: 1, done: true }; }
94+
};
95+
const iter = new Iter();
96+
shouldThrow(() => {
97+
iter.reduce(() => {});
98+
}, TypeError, "Iterator.prototype.reduce requires an initial value or an iterator that is not done.");
99+
}
100+
101+
{
102+
class Iter extends Iterator {
103+
next() { return 3; }
104+
};
105+
const iter = new Iter();
106+
shouldThrow(() => {
107+
iter.reduce(() => {});
108+
}, TypeError, "Iterator result interface is not an object.");
109+
}
110+
111+
{
112+
let nextGetCount = 0;
113+
let returnGetCount = 0;
114+
class Iter extends Iterator {
115+
get next() {
116+
nextGetCount++;
117+
return function () {
118+
return { value: 2, done: false }
119+
}
120+
};
121+
get return() {
122+
returnGetCount++;
123+
return function() {
124+
return {};
125+
};
126+
};
127+
};
128+
const iter = new Iter();
129+
shouldThrow(() => {
130+
iter.reduce(() => {
131+
sameValue(returnGetCount, 0);
132+
sameValue(nextGetCount, 1);
133+
throw new Error("my error");
134+
});
135+
}, Error, "my error");
136+
sameValue(nextGetCount, 1);
137+
sameValue(returnGetCount, 1);
138+
}

JSTests/test262/expectations.yaml

Lines changed: 0 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -457,81 +457,6 @@ test/built-ins/Iterator/prototype/map/underlying-iterator-closed-in-parallel.js:
457457
test/built-ins/Iterator/prototype/map/underlying-iterator-closed.js:
458458
default: "TypeError: iterator.map is not a function. (In 'iterator.map(() => 0)', 'iterator.map' is undefined)"
459459
strict mode: "TypeError: iterator.map is not a function. (In 'iterator.map(() => 0)', 'iterator.map' is undefined)"
460-
test/built-ins/Iterator/prototype/reduce/argument-effect-order.js:
461-
default: "TypeError: undefined is not an object (evaluating 'Iterator.prototype.reduce.call')"
462-
strict mode: "TypeError: undefined is not an object (evaluating 'Iterator.prototype.reduce.call')"
463-
test/built-ins/Iterator/prototype/reduce/callable.js:
464-
default: "TypeError: undefined is not an object (evaluating 'Iterator.prototype.reduce.call')"
465-
strict mode: "TypeError: undefined is not an object (evaluating 'Iterator.prototype.reduce.call')"
466-
test/built-ins/Iterator/prototype/reduce/get-next-method-only-once.js:
467-
default: "TypeError: iterator.reduce is not a function. (In 'iterator.reduce(x => x)', 'iterator.reduce' is undefined)"
468-
strict mode: "TypeError: iterator.reduce is not a function. (In 'iterator.reduce(x => x)', 'iterator.reduce' is undefined)"
469-
test/built-ins/Iterator/prototype/reduce/get-next-method-throws.js:
470-
default: 'Test262Error: Expected a Test262Error but got a TypeError'
471-
strict mode: 'Test262Error: Expected a Test262Error but got a TypeError'
472-
test/built-ins/Iterator/prototype/reduce/is-function.js:
473-
default: 'Test262Error: Expected SameValue(«undefined», «function») to be true'
474-
strict mode: 'Test262Error: Expected SameValue(«undefined», «function») to be true'
475-
test/built-ins/Iterator/prototype/reduce/iterator-already-exhausted-initial-value.js:
476-
default: "TypeError: iterator.reduce is not a function. (In 'iterator.reduce(() => {}, initialValue)', 'iterator.reduce' is undefined)"
477-
strict mode: "TypeError: iterator.reduce is not a function. (In 'iterator.reduce(() => {}, initialValue)', 'iterator.reduce' is undefined)"
478-
test/built-ins/Iterator/prototype/reduce/iterator-already-exhausted-no-initial-value.js:
479-
default: "TypeError: iterator.reduce is not a function. (In 'iterator.reduce(() => {}, 0)', 'iterator.reduce' is undefined)"
480-
strict mode: "TypeError: iterator.reduce is not a function. (In 'iterator.reduce(() => {}, 0)', 'iterator.reduce' is undefined)"
481-
test/built-ins/Iterator/prototype/reduce/iterator-yields-once-initial-value.js:
482-
default: "TypeError: iter.reduce is not a function. (In 'iter.reduce((memo, v, count) => {"
483-
strict mode: "TypeError: iter.reduce is not a function. (In 'iter.reduce((memo, v, count) => {"
484-
test/built-ins/Iterator/prototype/reduce/iterator-yields-once-no-initial-value.js:
485-
default: "TypeError: iter.reduce is not a function. (In 'iter.reduce((memo, v, count) => {"
486-
strict mode: "TypeError: iter.reduce is not a function. (In 'iter.reduce((memo, v, count) => {"
487-
test/built-ins/Iterator/prototype/reduce/length.js:
488-
default: "TypeError: undefined is not an object (evaluating 'Object.getOwnPropertyDescriptor(obj, name)')"
489-
strict mode: "TypeError: undefined is not an object (evaluating 'Object.getOwnPropertyDescriptor(obj, name)')"
490-
test/built-ins/Iterator/prototype/reduce/name.js:
491-
default: "TypeError: undefined is not an object (evaluating 'Object.getOwnPropertyDescriptor(obj, name)')"
492-
strict mode: "TypeError: undefined is not an object (evaluating 'Object.getOwnPropertyDescriptor(obj, name)')"
493-
test/built-ins/Iterator/prototype/reduce/next-method-returns-throwing-done.js:
494-
default: 'Test262Error: Expected a Test262Error but got a TypeError'
495-
strict mode: 'Test262Error: Expected a Test262Error but got a TypeError'
496-
test/built-ins/Iterator/prototype/reduce/next-method-returns-throwing-value-done.js:
497-
default: "TypeError: iterator.reduce is not a function. (In 'iterator.reduce(() => {}, 0)', 'iterator.reduce' is undefined)"
498-
strict mode: "TypeError: iterator.reduce is not a function. (In 'iterator.reduce(() => {}, 0)', 'iterator.reduce' is undefined)"
499-
test/built-ins/Iterator/prototype/reduce/next-method-returns-throwing-value.js:
500-
default: 'Test262Error: Expected a Test262Error but got a TypeError'
501-
strict mode: 'Test262Error: Expected a Test262Error but got a TypeError'
502-
test/built-ins/Iterator/prototype/reduce/next-method-throws.js:
503-
default: 'Test262Error: Expected a Test262Error but got a TypeError'
504-
strict mode: 'Test262Error: Expected a Test262Error but got a TypeError'
505-
test/built-ins/Iterator/prototype/reduce/non-callable-reducer.js:
506-
default: "TypeError: iterator.reduce is not a function. (In 'iterator.reduce(() => {})', 'iterator.reduce' is undefined)"
507-
strict mode: "TypeError: iterator.reduce is not a function. (In 'iterator.reduce(() => {})', 'iterator.reduce' is undefined)"
508-
test/built-ins/Iterator/prototype/reduce/prop-desc.js:
509-
default: 'Test262Error: obj should have an own property reduce'
510-
strict mode: 'Test262Error: obj should have an own property reduce'
511-
test/built-ins/Iterator/prototype/reduce/proto.js:
512-
default: "TypeError: undefined is not an object (evaluating 'Object.getPrototypeOf(Iterator.prototype.reduce)')"
513-
strict mode: "TypeError: undefined is not an object (evaluating 'Object.getPrototypeOf(Iterator.prototype.reduce)')"
514-
test/built-ins/Iterator/prototype/reduce/reducer-args-initial-value.js:
515-
default: "TypeError: iter.reduce is not a function. (In 'iter.reduce((memo, v, count) => {"
516-
strict mode: "TypeError: iter.reduce is not a function. (In 'iter.reduce((memo, v, count) => {"
517-
test/built-ins/Iterator/prototype/reduce/reducer-args-no-initial-value.js:
518-
default: "TypeError: iter.reduce is not a function. (In 'iter.reduce((memo, v, count) => {"
519-
strict mode: "TypeError: iter.reduce is not a function. (In 'iter.reduce((memo, v, count) => {"
520-
test/built-ins/Iterator/prototype/reduce/reducer-memo-can-be-any-type.js:
521-
default: "TypeError: iter.reduce is not a function. (In 'iter.reduce((memo, v, count) => {"
522-
strict mode: "TypeError: iter.reduce is not a function. (In 'iter.reduce((memo, v, count) => {"
523-
test/built-ins/Iterator/prototype/reduce/reducer-this.js:
524-
default: "TypeError: iter.reduce is not a function. (In 'iter.reduce(function (memo, v, count) {"
525-
strict mode: "TypeError: iter.reduce is not a function. (In 'iter.reduce(function (memo, v, count) {"
526-
test/built-ins/Iterator/prototype/reduce/reducer-throws-then-closing-iterator-also-throws.js:
527-
default: 'Test262Error: Expected a Test262Error but got a TypeError'
528-
strict mode: 'Test262Error: Expected a Test262Error but got a TypeError'
529-
test/built-ins/Iterator/prototype/reduce/reducer-throws.js:
530-
default: 'Test262Error: Expected a Test262Error but got a TypeError'
531-
strict mode: 'Test262Error: Expected a Test262Error but got a TypeError'
532-
test/built-ins/Iterator/prototype/reduce/this-plain-iterator.js:
533-
default: "TypeError: undefined is not an object (evaluating 'Iterator.prototype.reduce.call')"
534-
strict mode: "TypeError: undefined is not an object (evaluating 'Iterator.prototype.reduce.call')"
535460
test/built-ins/Iterator/prototype/take/argument-effect-order.js:
536461
default: "TypeError: undefined is not an object (evaluating 'Iterator.prototype.take.call')"
537462
strict mode: "TypeError: undefined is not an object (evaluating 'Iterator.prototype.take.call')"

Source/JavaScriptCore/builtins/JSIteratorPrototype.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,48 @@ function find(predicate)
8888

8989
return @undefined;
9090
}
91+
92+
// https://tc39.es/proposal-iterator-helpers/#sec-iteratorprototype.reduce
93+
function reduce(reducer /*, initialValue */)
94+
{
95+
"use strict";
96+
97+
if (!@isObject(this))
98+
@throwTypeError("Iterator.prototype.reduce requires that |this| be an Object.");
99+
100+
if (!@isCallable(reducer))
101+
@throwTypeError("Iterator.prototype.reduce reducer argument must be a function.");
102+
103+
var initialValue = @argument(1);
104+
105+
var iteratedIterator = this;
106+
var iteratedNextMethod = this.next;
107+
108+
var accumulator;
109+
var counter = 0;
110+
if (initialValue === @undefined) {
111+
var result = iteratedNextMethod.@call(iteratedIterator);
112+
if (!@isObject(result))
113+
@throwTypeError("Iterator result interface is not an object.");
114+
if (result.done)
115+
@throwTypeError("Iterator.prototype.reduce requires an initial value or an iterator that is not done.");
116+
accumulator = result.value;
117+
counter = 1;
118+
} else
119+
accumulator = initialValue;
120+
121+
var wrapper = {
122+
@@iterator: function()
123+
{
124+
return {
125+
next: function() { return iteratedNextMethod.@call(iteratedIterator); },
126+
get return() { return iteratedIterator.return; },
127+
};
128+
},
129+
};
130+
for (var item of wrapper) {
131+
accumulator = reducer(accumulator, item, counter++);
132+
}
133+
134+
return accumulator;
135+
}

Source/JavaScriptCore/runtime/JSIteratorPrototype.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ void JSIteratorPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject)
7575
JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().everyPublicName(), jsIteratorPrototypeEveryCodeGenerator, static_cast<unsigned>(PropertyAttribute::DontEnum));
7676
// https://tc39.es/proposal-iterator-helpers/#sec-iteratorprototype.find
7777
JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().findPublicName(), jsIteratorPrototypeFindCodeGenerator, static_cast<unsigned>(PropertyAttribute::DontEnum));
78+
// https://tc39.es/proposal-iterator-helpers/#sec-iteratorprototype.reduce
79+
JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().reducePublicName(), jsIteratorPrototypeReduceCodeGenerator, static_cast<unsigned>(PropertyAttribute::DontEnum));
7880
}
7981
}
8082

0 commit comments

Comments
 (0)