Skip to content

Commit c9bec0f

Browse files
Merge pull request #317 from Distributive-Network/philippe/for-of-iterable
Expand for-of loop support to iterable types
2 parents f180939 + 676ad7c commit c9bec0f

File tree

2 files changed

+158
-2
lines changed

2 files changed

+158
-2
lines changed

src/PyIterableProxyHandler.cc

+150-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121

2222
const char PyIterableProxyHandler::family = 0;
2323

24+
// TODO merge shared _next code
25+
2426
bool PyIterableProxyHandler::iterable_next(JSContext *cx, unsigned argc, JS::Value *vp) {
2527
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
2628
JS::RootedObject thisObj(cx);
@@ -41,7 +43,7 @@ bool PyIterableProxyHandler::iterable_next(JSContext *cx, unsigned argc, JS::Val
4143
PyErr_Clear();
4244
}
4345
else {
44-
return NULL;
46+
return false;
4547
}
4648
}
4749

@@ -66,10 +68,139 @@ JSMethodDef PyIterableProxyHandler::iterable_methods[] = {
6668
{NULL, NULL, 0}
6769
};
6870

71+
72+
// IterableIterator
73+
74+
enum {
75+
IterableIteratorSlotIterableObject,
76+
IterableIteratorSlotCount
77+
};
78+
79+
static JSClass iterableIteratorClass = {"IterableIterator", JSCLASS_HAS_RESERVED_SLOTS(IterableIteratorSlotCount)};
80+
81+
static bool iterator_next(JSContext *cx, unsigned argc, JS::Value *vp) {
82+
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
83+
JS::RootedObject thisObj(cx);
84+
if (!args.computeThis(cx, &thisObj)) return false;
85+
86+
PyObject *it = JS::GetMaybePtrFromReservedSlot<PyObject>(thisObj, IterableIteratorSlotIterableObject);
87+
88+
JS::RootedObject result(cx, JS_NewPlainObject(cx));
89+
90+
PyObject *(*iternext)(PyObject *) = *Py_TYPE(it)->tp_iternext;
91+
92+
PyObject *item = iternext(it);
93+
94+
if (item == NULL) {
95+
if (PyErr_Occurred()) {
96+
if (PyErr_ExceptionMatches(PyExc_StopIteration) ||
97+
PyErr_ExceptionMatches(PyExc_SystemError)) { // TODO this handles a result like SystemError: Objects/dictobject.c:1778: bad argument to internal function. Why are we getting that?
98+
PyErr_Clear();
99+
}
100+
else {
101+
return false;
102+
}
103+
}
104+
105+
JS::RootedValue done(cx, JS::BooleanValue(true));
106+
if (!JS_SetProperty(cx, result, "done", done)) return false;
107+
args.rval().setObject(*result);
108+
return result;
109+
}
110+
111+
JS::RootedValue done(cx, JS::BooleanValue(false));
112+
if (!JS_SetProperty(cx, result, "done", done)) return false;
113+
114+
JS::RootedValue value(cx, jsTypeFactory(cx, item));
115+
if (!JS_SetProperty(cx, result, "value", value)) return false;
116+
117+
args.rval().setObject(*result);
118+
return true;
119+
}
120+
121+
static JSFunctionSpec iterable_iterator_methods[] = {
122+
JS_FN("next", iterator_next, 0, JSPROP_ENUMERATE),
123+
JS_FS_END
124+
};
125+
126+
static bool IterableIteratorConstructor(JSContext *cx, unsigned argc, JS::Value *vp) {
127+
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
128+
129+
if (!args.isConstructing()) {
130+
JS_ReportErrorASCII(cx, "You must call this constructor with 'new'");
131+
return false;
132+
}
133+
134+
JS::RootedObject thisObj(cx, JS_NewObjectForConstructor(cx, &iterableIteratorClass, args));
135+
if (!thisObj) {
136+
return false;
137+
}
138+
139+
args.rval().setObject(*thisObj);
140+
return true;
141+
}
142+
143+
static bool DefineIterableIterator(JSContext *cx, JS::HandleObject global) {
144+
JS::RootedObject iteratorPrototype(cx);
145+
if (!JS_GetClassPrototype(cx, JSProto_Iterator, &iteratorPrototype)) {
146+
return false;
147+
}
148+
149+
JS::RootedObject protoObj(cx,
150+
JS_InitClass(cx, global,
151+
nullptr, iteratorPrototype,
152+
"IterableIterator",
153+
IterableIteratorConstructor, 0,
154+
nullptr, iterable_iterator_methods,
155+
nullptr, nullptr)
156+
);
157+
158+
return protoObj; // != nullptr
159+
}
160+
161+
static bool iterable_values(JSContext *cx, unsigned argc, JS::Value *vp) {
162+
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
163+
164+
JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv()));
165+
if (!proxy) {
166+
return false;
167+
}
168+
169+
PyObject *self = JS::GetMaybePtrFromReservedSlot<PyObject>(proxy, PyObjectSlot);
170+
171+
JS::RootedObject global(cx, JS::GetNonCCWObjectGlobal(proxy));
172+
173+
JS::RootedValue constructor_val(cx);
174+
if (!JS_GetProperty(cx, global, "IterableIterator", &constructor_val)) return false;
175+
if (!constructor_val.isObject()) {
176+
if (!DefineIterableIterator(cx, global)) {
177+
return false;
178+
}
179+
180+
if (!JS_GetProperty(cx, global, "IterableIterator", &constructor_val)) return false;
181+
if (!constructor_val.isObject()) {
182+
JS_ReportErrorASCII(cx, "IterableIterator is not a constructor");
183+
return false;
184+
}
185+
}
186+
JS::RootedObject constructor(cx, &constructor_val.toObject());
187+
188+
JS::RootedObject obj(cx);
189+
if (!JS::Construct(cx, constructor_val, JS::HandleValueArray::empty(), &obj)) return false;
190+
if (!obj) return false;
191+
192+
JS::SetReservedSlot(obj, IterableIteratorSlotIterableObject, JS::PrivateValue((void *)self));
193+
194+
args.rval().setObject(*obj);
195+
return true;
196+
}
197+
69198
bool PyIterableProxyHandler::getOwnPropertyDescriptor(
70199
JSContext *cx, JS::HandleObject proxy, JS::HandleId id,
71200
JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc
72201
) const {
202+
203+
// see if we're calling a function
73204
if (id.isString()) {
74205
for (size_t index = 0;; index++) {
75206
bool isThatFunction;
@@ -92,6 +223,24 @@ bool PyIterableProxyHandler::getOwnPropertyDescriptor(
92223
}
93224
}
94225

226+
// symbol property
227+
if (id.isSymbol()) {
228+
JS::RootedSymbol rootedSymbol(cx, id.toSymbol());
229+
230+
if (JS::GetSymbolCode(rootedSymbol) == JS::SymbolCode::iterator) {
231+
JSFunction *newFunction = JS_NewFunction(cx, iterable_values, 0, 0, NULL);
232+
if (!newFunction) return false;
233+
JS::RootedObject funObj(cx, JS_GetFunctionObject(newFunction));
234+
desc.set(mozilla::Some(
235+
JS::PropertyDescriptor::Data(
236+
JS::ObjectValue(*funObj),
237+
{JS::PropertyAttribute::Enumerable}
238+
)
239+
));
240+
return true;
241+
}
242+
}
243+
95244
PyObject *attrName = idToKey(cx, id);
96245
PyObject *self = JS::GetMaybePtrFromReservedSlot<PyObject>(proxy, PyObjectSlot);
97246
PyObject *item = PyDict_GetItemWithError(self, attrName);

tests/python/test_arrays.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -2204,4 +2204,11 @@ def test_iter_reentrace_next():
22042204
third = next(result[0])
22052205
assert (False)
22062206
except StopIteration as e:
2207-
assert (True)
2207+
assert (True)
2208+
2209+
def test_iter_for_of():
2210+
myit = iter((1,2))
2211+
result = [None, None]
2212+
pm.eval("""(result, myit) => {let index = 0; for (const value of myit) {result[index++] = value}}""")(result, myit)
2213+
assert result[0] == 1.0
2214+
assert result[1] == 2.0

0 commit comments

Comments
 (0)