diff --git a/src/PyIterableProxyHandler.cc b/src/PyIterableProxyHandler.cc index e22483d5..02ee8bc6 100644 --- a/src/PyIterableProxyHandler.cc +++ b/src/PyIterableProxyHandler.cc @@ -21,6 +21,8 @@ const char PyIterableProxyHandler::family = 0; +// TODO merge shared _next code + bool PyIterableProxyHandler::iterable_next(JSContext *cx, unsigned argc, JS::Value *vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); JS::RootedObject thisObj(cx); @@ -41,7 +43,7 @@ bool PyIterableProxyHandler::iterable_next(JSContext *cx, unsigned argc, JS::Val PyErr_Clear(); } else { - return NULL; + return false; } } @@ -66,10 +68,139 @@ JSMethodDef PyIterableProxyHandler::iterable_methods[] = { {NULL, NULL, 0} }; + +// IterableIterator + +enum { + IterableIteratorSlotIterableObject, + IterableIteratorSlotCount +}; + +static JSClass iterableIteratorClass = {"IterableIterator", JSCLASS_HAS_RESERVED_SLOTS(IterableIteratorSlotCount)}; + +static bool iterator_next(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::RootedObject thisObj(cx); + if (!args.computeThis(cx, &thisObj)) return false; + + PyObject *it = JS::GetMaybePtrFromReservedSlot(thisObj, IterableIteratorSlotIterableObject); + + JS::RootedObject result(cx, JS_NewPlainObject(cx)); + + PyObject *(*iternext)(PyObject *) = *Py_TYPE(it)->tp_iternext; + + PyObject *item = iternext(it); + + if (item == NULL) { + if (PyErr_Occurred()) { + if (PyErr_ExceptionMatches(PyExc_StopIteration) || + 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? + PyErr_Clear(); + } + else { + return false; + } + } + + JS::RootedValue done(cx, JS::BooleanValue(true)); + if (!JS_SetProperty(cx, result, "done", done)) return false; + args.rval().setObject(*result); + return result; + } + + JS::RootedValue done(cx, JS::BooleanValue(false)); + if (!JS_SetProperty(cx, result, "done", done)) return false; + + JS::RootedValue value(cx, jsTypeFactory(cx, item)); + if (!JS_SetProperty(cx, result, "value", value)) return false; + + args.rval().setObject(*result); + return true; +} + +static JSFunctionSpec iterable_iterator_methods[] = { + JS_FN("next", iterator_next, 0, JSPROP_ENUMERATE), + JS_FS_END +}; + +static bool IterableIteratorConstructor(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (!args.isConstructing()) { + JS_ReportErrorASCII(cx, "You must call this constructor with 'new'"); + return false; + } + + JS::RootedObject thisObj(cx, JS_NewObjectForConstructor(cx, &iterableIteratorClass, args)); + if (!thisObj) { + return false; + } + + args.rval().setObject(*thisObj); + return true; +} + +static bool DefineIterableIterator(JSContext *cx, JS::HandleObject global) { + JS::RootedObject iteratorPrototype(cx); + if (!JS_GetClassPrototype(cx, JSProto_Iterator, &iteratorPrototype)) { + return false; + } + + JS::RootedObject protoObj(cx, + JS_InitClass(cx, global, + nullptr, iteratorPrototype, + "IterableIterator", + IterableIteratorConstructor, 0, + nullptr, iterable_iterator_methods, + nullptr, nullptr) + ); + + return protoObj; // != nullptr +} + +static bool iterable_values(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + JS::RootedObject global(cx, JS::GetNonCCWObjectGlobal(proxy)); + + JS::RootedValue constructor_val(cx); + if (!JS_GetProperty(cx, global, "IterableIterator", &constructor_val)) return false; + if (!constructor_val.isObject()) { + if (!DefineIterableIterator(cx, global)) { + return false; + } + + if (!JS_GetProperty(cx, global, "IterableIterator", &constructor_val)) return false; + if (!constructor_val.isObject()) { + JS_ReportErrorASCII(cx, "IterableIterator is not a constructor"); + return false; + } + } + JS::RootedObject constructor(cx, &constructor_val.toObject()); + + JS::RootedObject obj(cx); + if (!JS::Construct(cx, constructor_val, JS::HandleValueArray::empty(), &obj)) return false; + if (!obj) return false; + + JS::SetReservedSlot(obj, IterableIteratorSlotIterableObject, JS::PrivateValue((void *)self)); + + args.rval().setObject(*obj); + return true; +} + bool PyIterableProxyHandler::getOwnPropertyDescriptor( JSContext *cx, JS::HandleObject proxy, JS::HandleId id, JS::MutableHandle> desc ) const { + + // see if we're calling a function if (id.isString()) { for (size_t index = 0;; index++) { bool isThatFunction; @@ -92,6 +223,24 @@ bool PyIterableProxyHandler::getOwnPropertyDescriptor( } } + // symbol property + if (id.isSymbol()) { + JS::RootedSymbol rootedSymbol(cx, id.toSymbol()); + + if (JS::GetSymbolCode(rootedSymbol) == JS::SymbolCode::iterator) { + JSFunction *newFunction = JS_NewFunction(cx, iterable_values, 0, 0, NULL); + if (!newFunction) return false; + JS::RootedObject funObj(cx, JS_GetFunctionObject(newFunction)); + desc.set(mozilla::Some( + JS::PropertyDescriptor::Data( + JS::ObjectValue(*funObj), + {JS::PropertyAttribute::Enumerable} + ) + )); + return true; + } + } + PyObject *attrName = idToKey(cx, id); PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); PyObject *item = PyDict_GetItemWithError(self, attrName); diff --git a/tests/python/test_arrays.py b/tests/python/test_arrays.py index 8bf2365c..9dc96a3d 100644 --- a/tests/python/test_arrays.py +++ b/tests/python/test_arrays.py @@ -2204,4 +2204,11 @@ def test_iter_reentrace_next(): third = next(result[0]) assert (False) except StopIteration as e: - assert (True) \ No newline at end of file + assert (True) + +def test_iter_for_of(): + myit = iter((1,2)) + result = [None, None] + pm.eval("""(result, myit) => {let index = 0; for (const value of myit) {result[index++] = value}}""")(result, myit) + assert result[0] == 1.0 + assert result[1] == 2.0 \ No newline at end of file