Skip to content

Commit 63ddf56

Browse files
committed
[GR-58995] Implement iterator-sequencing proposal.
PullRequest: js/3383
2 parents 067af6c + defb678 commit 63ddf56

File tree

10 files changed

+265
-31
lines changed

10 files changed

+265
-31
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ See [release calendar](https://www.graalvm.org/release-calendar/) for release da
2323
* Implemented the `TextDecoder` and `TextEncoder` APIs of the [WHATWG Encoding Standard](https://encoding.spec.whatwg.org/). They are available behind the experimental option (`--js.text-encoding`).
2424
* Implemented the [`RegExp.escape`](https://github.com/tc39/proposal-regex-escaping) proposal. It is available in ECMAScript staging mode (`--js.ecmascript-version=staging`).
2525
* Implemented the [Regular Expression Pattern Modifiers](https://github.com/tc39/proposal-regexp-modifiers) proposal.
26+
* Implemented the [Iterator Sequencing](https://github.com/tc39/proposal-iterator-sequencing) proposal. It is available in ECMAScript staging mode (`--js.ecmascript-version=staging`).
2627

2728
## Version 24.1.0
2829
* ECMAScript 2024 mode/features enabled by default.

graal-js/mx.graal-js/mx_graal_js.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#
22
# ----------------------------------------------------------------------------------------------------
33
#
4-
# Copyright (c) 2007, 2024, Oracle and/or its affiliates. All rights reserved.
4+
# Copyright (c) 2007, 2025, Oracle and/or its affiliates. All rights reserved.
55
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
66
#
77
# This code is free software; you can redistribute it and/or modify it
@@ -47,7 +47,7 @@
4747
TEST262_REPO = "https://" + "github.com/tc39/test262.git"
4848

4949
# Git revision of Test262 to checkout
50-
TEST262_REV = "42d83277b7d73ea7b6e28a9ddedf8f4b2cee0d50"
50+
TEST262_REV = "d0bda4d26f16558698b3f11deba74f7cc4f8afda"
5151

5252
# Git repository of V8
5353
TESTV8_REPO = "https://" + "github.com/v8/v8.git"

graal-js/src/com.oracle.truffle.js.test.external/src/com/oracle/truffle/js/test/external/test262/Test262Runnable.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -207,6 +207,7 @@ public class Test262Runnable extends TestRunnable {
207207
"async-functions",
208208
"async-iteration",
209209
"caller",
210+
"canonical-tz",
210211
"change-array-by-copy",
211212
"class",
212213
"class-fields-private",
@@ -241,6 +242,7 @@ public class Test262Runnable extends TestRunnable {
241242
"import.meta",
242243
"intl-normative-optional",
243244
"iterator-helpers",
245+
"iterator-sequencing",
244246
"json-modules",
245247
"json-parse-with-source",
246248
"json-superset",
@@ -296,6 +298,7 @@ public class Test262Runnable extends TestRunnable {
296298
"RegExp.escape",
297299
"ShadowRealm",
298300
"decorators",
301+
"iterator-sequencing",
299302
"json-parse-with-source",
300303
"promise-try",
301304
"uint8array-base64",
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl.
6+
*/
7+
8+
/**
9+
* Tests Iterator.concat() a.k.a. iterator-sequencing proposal.
10+
*
11+
* @option ecmascript-version=staging
12+
*/
13+
14+
load("./iteratorhelper_common.js");
15+
16+
{
17+
let iter = Iterator.concat(["a", "b"], ["c", "d"], ["e"]);
18+
19+
assertIterResult({done: false, value: "a"}, iter.next());
20+
assertIterResult({done: false, value: "b"}, iter.next());
21+
assertIterResult({done: false, value: "c"}, iter.next());
22+
assertIterResult({done: false, value: "d"}, iter.next());
23+
assertIterResult({done: false, value: "e"}, iter.next());
24+
assertIterResult({done: true, value: undefined}, iter.next());
25+
assertIterResult({done: true, value: undefined}, iter.next());
26+
27+
// generator state: completed
28+
assertIterResult({done: true, value: undefined}, iter.return());
29+
30+
assertIterResult({done: true, value: undefined}, iter.next());
31+
}
32+
33+
{
34+
let iter = Iterator.concat(["a", "b"], ["c", "d"], ["e"]);
35+
36+
assertIterResult({done: false, value: "a"}, iter.next());
37+
assertIterResult({done: false, value: "b"}, iter.next());
38+
assertIterResult({done: false, value: "c"}, iter.next());
39+
40+
// generator state: suspended-yield
41+
assertIterResult({done: true, value: undefined}, iter.return());
42+
43+
assertIterResult({done: true, value: undefined}, iter.next());
44+
assertIterResult({done: true, value: undefined}, iter.next());
45+
}
46+
47+
{
48+
let iter = Iterator.concat(["a", "b"], ["c", "d"], ["e"]);
49+
50+
// generator state: suspended-start
51+
assertIterResult({done: true, value: undefined}, iter.return());
52+
53+
assertIterResult({done: true, value: undefined}, iter.next());
54+
assertIterResult({done: true, value: undefined}, iter.next());
55+
}
56+
57+
{
58+
let iter = Iterator.concat(["a", "b"], ["c", "d"], Iterator.from(["e"]));
59+
60+
assertSameContent(["a", "b", "c", "d", "e"], Array.from(iter));
61+
}
62+
63+
{
64+
let iter = Iterator.concat(["a", "b"], Iterator.concat(["c", "d"], ["e"]));
65+
66+
assertSameContent(["a", "b", "c", "d", "e"], [...iter]);
67+
}
68+
69+
{
70+
let iter = Iterator.concat(["a", "b"], ["c", "d"], ["e"]);
71+
72+
let items = [];
73+
for (let item of iter) {
74+
items.push(item);
75+
if (item === "c") {
76+
break;
77+
}
78+
}
79+
assertSameContent(["a", "b", "c"], items);
80+
81+
assertIterResult({done: true, value: undefined}, iter.next());
82+
}

graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/IteratorFunctionBuiltins.java

Lines changed: 130 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -40,18 +40,34 @@
4040
*/
4141
package com.oracle.truffle.js.builtins;
4242

43+
import com.oracle.truffle.api.dsl.Cached;
44+
import com.oracle.truffle.api.dsl.ImportStatic;
4345
import com.oracle.truffle.api.dsl.Specialization;
44-
import com.oracle.truffle.js.builtins.IteratorFunctionBuiltinsFactory.JSIteratorFromNodeGen;
46+
import com.oracle.truffle.api.frame.VirtualFrame;
47+
import com.oracle.truffle.api.profiles.InlinedBranchProfile;
48+
import com.oracle.truffle.js.builtins.IteratorPrototypeBuiltins.IteratorArgs;
49+
import com.oracle.truffle.js.builtins.IteratorPrototypeBuiltins.IteratorFromGeneratorNode;
4550
import com.oracle.truffle.js.nodes.access.GetIteratorFlattenableNode;
51+
import com.oracle.truffle.js.nodes.access.GetIteratorFromMethodNode;
52+
import com.oracle.truffle.js.nodes.access.GetMethodNode;
53+
import com.oracle.truffle.js.nodes.access.IsObjectNode;
54+
import com.oracle.truffle.js.nodes.access.IteratorCompleteNode;
55+
import com.oracle.truffle.js.nodes.access.IteratorNextNode;
56+
import com.oracle.truffle.js.nodes.access.IteratorValueNode;
4657
import com.oracle.truffle.js.nodes.binary.InstanceofNode.OrdinaryHasInstanceNode;
4758
import com.oracle.truffle.js.nodes.function.JSBuiltin;
4859
import com.oracle.truffle.js.nodes.function.JSBuiltinNode;
60+
import com.oracle.truffle.js.runtime.Errors;
61+
import com.oracle.truffle.js.runtime.JSConfig;
4962
import com.oracle.truffle.js.runtime.JSContext;
5063
import com.oracle.truffle.js.runtime.JSRealm;
64+
import com.oracle.truffle.js.runtime.Symbol;
5165
import com.oracle.truffle.js.runtime.builtins.BuiltinEnum;
5266
import com.oracle.truffle.js.runtime.builtins.JSIterator;
67+
import com.oracle.truffle.js.runtime.builtins.JSIteratorHelperObject;
5368
import com.oracle.truffle.js.runtime.builtins.JSWrapForValidIterator;
5469
import com.oracle.truffle.js.runtime.objects.IteratorRecord;
70+
import com.oracle.truffle.js.runtime.objects.Undefined;
5571

5672
/**
5773
* Contains builtins for {@linkplain JSIterator} function (constructor).
@@ -65,7 +81,9 @@ public final class IteratorFunctionBuiltins extends JSBuiltinsContainer.SwitchEn
6581
}
6682

6783
public enum IteratorFunction implements BuiltinEnum<IteratorFunction> {
68-
from(1);
84+
from(1),
85+
86+
concat(0);
6987

7088
private final int length;
7189

@@ -77,13 +95,23 @@ public enum IteratorFunction implements BuiltinEnum<IteratorFunction> {
7795
public int getLength() {
7896
return length;
7997
}
98+
99+
@Override
100+
public int getECMAScriptVersion() {
101+
if (this == concat) {
102+
return JSConfig.StagingECMAScriptVersion;
103+
}
104+
return BuiltinEnum.super.getECMAScriptVersion();
105+
}
80106
}
81107

82108
@Override
83109
protected Object createNode(JSContext context, JSBuiltin builtin, boolean construct, boolean newTarget, IteratorFunction builtinEnum) {
84110
switch (builtinEnum) {
85111
case from:
86-
return JSIteratorFromNodeGen.create(context, builtin, args().fixedArgs(1).createArgumentNodes(context));
112+
return IteratorFunctionBuiltinsFactory.JSIteratorFromNodeGen.create(context, builtin, args().fixedArgs(1).createArgumentNodes(context));
113+
case concat:
114+
return IteratorFunctionBuiltinsFactory.IteratorConcatNodeGen.create(context, builtin, args().varArgs().createArgumentNodes(context));
87115
}
88116

89117
return null;
@@ -94,7 +122,7 @@ public abstract static class JSIteratorFromNode extends JSBuiltinNode {
94122

95123
@Child private OrdinaryHasInstanceNode ordinaryHasInstanceNode;
96124

97-
public JSIteratorFromNode(JSContext context, JSBuiltin builtin) {
125+
protected JSIteratorFromNode(JSContext context, JSBuiltin builtin) {
98126
super(context, builtin);
99127
this.getIteratorFlattenableNode = GetIteratorFlattenableNode.create(false, false, context);
100128
this.ordinaryHasInstanceNode = OrdinaryHasInstanceNode.create(context);
@@ -114,4 +142,101 @@ protected Object iteratorFrom(Object arg) {
114142
}
115143

116144
}
145+
146+
private record Iterable(Object openMethod, Object iterable) {
147+
}
148+
149+
static final class ConcatArgs extends IteratorArgs {
150+
151+
private final Iterable[] iterables;
152+
private int iterableIndex;
153+
boolean innerAlive;
154+
IteratorRecord innerIterator;
155+
156+
ConcatArgs(Iterable[] iterables) {
157+
super(null);
158+
this.iterables = iterables;
159+
}
160+
}
161+
162+
@ImportStatic({Symbol.class})
163+
public abstract static class IteratorConcatNode extends IteratorFromGeneratorNode<ConcatArgs> {
164+
165+
protected IteratorConcatNode(JSContext context, JSBuiltin builtin) {
166+
super(context, builtin, JSContext.BuiltinFunctionKey.IteratorConcat,
167+
c -> createIteratorFromGeneratorFunctionImpl(c, IteratorConcatNextNode.create(c)));
168+
}
169+
170+
@Specialization
171+
protected final Object iteratorConcat(Object[] items,
172+
@Cached IsObjectNode isObjectNode,
173+
@Cached(parameters = {"getContext()", "SYMBOL_ITERATOR"}) GetMethodNode getIteratorMethodNode,
174+
@Cached InlinedBranchProfile errorBranch) {
175+
Iterable[] iterables = new Iterable[items.length];
176+
for (int i = 0; i < items.length; i++) {
177+
Object item = items[i];
178+
if (!isObjectNode.executeBoolean(item)) {
179+
errorBranch.enter(this);
180+
throw Errors.createTypeErrorNotAnObject(item);
181+
}
182+
Object method = getIteratorMethodNode.executeWithTarget(item);
183+
if (method == Undefined.instance) {
184+
errorBranch.enter(this);
185+
throw Errors.createTypeErrorNotIterable(item, this);
186+
}
187+
iterables[i] = new Iterable(method, item);
188+
}
189+
return createIteratorHelperObject(new ConcatArgs(iterables));
190+
}
191+
}
192+
193+
protected abstract static class IteratorConcatNextNode extends IteratorFromGeneratorNode.IteratorFromGeneratorImplNode<ConcatArgs> {
194+
195+
protected IteratorConcatNextNode(JSContext context) {
196+
super(context);
197+
}
198+
199+
@Specialization
200+
protected final Object next(VirtualFrame frame, JSIteratorHelperObject thisObj,
201+
@Cached GetIteratorFromMethodNode getIteratorFromMethodNode,
202+
@Cached IteratorNextNode iteratorNextNode,
203+
@Cached IteratorCompleteNode iteratorCompleteNode,
204+
@Cached IteratorValueNode iteratorValueNode) {
205+
ConcatArgs args = getArgs(thisObj);
206+
var iterables = args.iterables;
207+
int iterableIndex = args.iterableIndex;
208+
209+
while (iterableIndex < iterables.length) {
210+
IteratorRecord iterator = args.innerIterator;
211+
if (!args.innerAlive) {
212+
var iterable = iterables[iterableIndex];
213+
args.innerIterator = iterator = getIteratorFromMethodNode.execute(this, iterable.iterable, iterable.openMethod);
214+
args.innerAlive = true;
215+
}
216+
217+
assert args.innerAlive && iterator != null;
218+
Object result = iteratorNextNode.execute(iterator);
219+
boolean done = iteratorCompleteNode.execute(result);
220+
if (done) {
221+
iteratorValueNode.execute(result);
222+
args.innerAlive = false;
223+
args.innerIterator = null;
224+
args.iterableIndex = ++iterableIndex;
225+
} else {
226+
return generatorYield(thisObj, result);
227+
// Note: Abrupt completion is handled by IteratorHelperReturnNode.
228+
}
229+
}
230+
return createResultDone(frame, thisObj);
231+
}
232+
233+
@Override
234+
public IteratorFromGeneratorNode.IteratorFromGeneratorImplNode<ConcatArgs> copyUninitialized() {
235+
return create(context);
236+
}
237+
238+
static IteratorConcatNextNode create(JSContext context) {
239+
return IteratorFunctionBuiltinsFactory.IteratorConcatNextNodeGen.create(context);
240+
}
241+
}
117242
}

0 commit comments

Comments
 (0)