Bug 725907 - for-of improvements, part 4: Add .next() method to iterator objects and make for-of call it. r=bhackett.

This commit is contained in:
Jason Orendorff
2012-07-03 16:34:56 -05:00
parent 15a0684f38
commit b84f93e784
24 changed files with 519 additions and 256 deletions

View File

@@ -51,31 +51,6 @@ using namespace mozilla;
using namespace js;
using namespace js::gc;
Class js::ElementIteratorClass = {
"ElementIterator",
JSCLASS_HAS_RESERVED_SLOTS(ElementIteratorObject::NumSlots),
JS_PropertyStub, /* addProperty */
JS_PropertyStub, /* delProperty */
JS_PropertyStub, /* getProperty */
JS_StrictPropertyStub, /* setProperty */
JS_EnumerateStub,
JS_ResolveStub,
JS_ConvertStub,
NULL, /* finalize */
NULL, /* checkAccess */
NULL, /* call */
NULL, /* construct */
NULL, /* hasInstance */
NULL, /* trace */
{
NULL, /* equality */
NULL, /* outerObject */
NULL, /* innerObject */
NULL, /* iteratorObject */
NULL /* unused */
}
};
static const gc::AllocKind ITERATOR_FINALIZE_KIND = gc::FINALIZE_OBJECT2;
void
@@ -737,6 +712,18 @@ GetIterator(JSContext *cx, HandleObject obj, unsigned flags, Value *vp)
}
JSBool
js_ThrowStopIteration(JSContext *cx)
{
JS_ASSERT(!JS_IsExceptionPending(cx));
RootedValue v(cx);
if (js_FindClassObject(cx, NullPtr(), JSProto_StopIteration, &v))
cx->setPendingException(v);
return JS_FALSE;
}
/*** Iterator objects ****************************************************************************/
static JSBool
Iterator(JSContext *cx, unsigned argc, Value *vp)
{
@@ -757,16 +744,6 @@ Iterator(JSContext *cx, unsigned argc, Value *vp)
return true;
}
JSBool
js_ThrowStopIteration(JSContext *cx)
{
JS_ASSERT(!JS_IsExceptionPending(cx));
RootedValue v(cx);
if (js_FindClassObject(cx, NullPtr(), JSProto_StopIteration, &v))
cx->setPendingException(v);
return JS_FALSE;
}
static bool
IsIterator(const Value &v)
{
@@ -812,7 +789,7 @@ static JSFunctionSpec iterator_methods[] = {
JS_FS_END
};
JSObject *
static JSObject *
iterator_iteratorObject(JSContext *cx, HandleObject obj, JSBool keysonly)
{
return obj;
@@ -861,6 +838,105 @@ Class PropertyIteratorObject::class_ = {
}
};
const uint32_t CLOSED_INDEX = UINT32_MAX;
JSObject *
ElementIteratorObject::create(JSContext *cx, Handle<Value> target)
{
GlobalObject *global = GetCurrentGlobal(cx);
Rooted<JSObject*> proto(cx, global->getOrCreateElementIteratorPrototype(cx));
if (!proto)
return NULL;
JSObject *iterobj = NewObjectWithGivenProto(cx, &ElementIteratorClass, proto, global);
if (iterobj) {
iterobj->setReservedSlot(TargetSlot, target);
iterobj->setReservedSlot(IndexSlot, Int32Value(0));
}
return iterobj;
}
static bool
IsElementIterator(const Value &v)
{
return v.isObject() && v.toObject().isElementIterator();
}
JSBool
ElementIteratorObject::next(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod(cx, IsElementIterator, next_impl, args);
}
bool
ElementIteratorObject::next_impl(JSContext *cx, CallArgs args)
{
JSObject *iterobj = &args.thisv().toObject();
uint32_t i, length;
Value target = iterobj->getReservedSlot(TargetSlot);
Rooted<JSObject*> obj(cx);
// Get target.length.
if (target.isString()) {
length = uint32_t(target.toString()->length());
} else {
obj = ValueToObject(cx, target);
if (!obj)
goto close;
if (!js_GetLengthProperty(cx, obj, &length))
goto close;
}
// Check target.length.
i = uint32_t(iterobj->getReservedSlot(IndexSlot).toInt32());
if (i >= length) {
js_ThrowStopIteration(cx);
goto close;
}
// Get target[i].
JS_ASSERT(i + 1 > i);
if (target.isString()) {
JSString *c = cx->runtime->staticStrings.getUnitStringForElement(cx, target.toString(), i);
if (!c)
goto close;
args.rval().setString(c);
} else {
if (!obj->getElement(cx, obj, i, &args.rval()))
goto close;
}
// On success, bump the index.
iterobj->setReservedSlot(IndexSlot, Int32Value(int32_t(i + 1)));
return true;
close:
// Close the iterator. The TargetSlot will never be used again, so don't keep a
// reference to it.
iterobj->setReservedSlot(TargetSlot, UndefinedValue());
iterobj->setReservedSlot(IndexSlot, Int32Value(int32_t(CLOSED_INDEX)));
return false;
}
Class js::ElementIteratorClass = {
"Iterator",
JSCLASS_IMPLEMENTS_BARRIERS |
JSCLASS_HAS_RESERVED_SLOTS(ElementIteratorObject::NumSlots),
JS_PropertyStub, /* addProperty */
JS_PropertyStub, /* delProperty */
JS_PropertyStub, /* getProperty */
JS_StrictPropertyStub, /* setProperty */
JS_EnumerateStub,
JS_ResolveStub,
JS_ConvertStub,
NULL /* finalize */
};
JSFunctionSpec ElementIteratorObject::methods[] = {
JS_FN("next", next, 0, 0),
JS_FS_END
};
#if JS_HAS_GENERATORS
static JSBool
CloseGenerator(JSContext *cx, JSObject *genobj);
@@ -1103,75 +1179,6 @@ js_SuppressDeletedElements(JSContext *cx, HandleObject obj, uint32_t begin, uint
return SuppressDeletedPropertyHelper(cx, obj, IndexRangePredicate(begin, end));
}
const uint32_t CLOSED_INDEX = UINT32_MAX;
JSObject *
ElementIteratorObject::create(JSContext *cx, HandleObject obj)
{
JS_ASSERT(obj);
JSObject *iterobj = NewObjectWithGivenProto(cx, &ElementIteratorClass, NULL, obj);
if (iterobj) {
iterobj->setReservedSlot(TargetSlot, ObjectValue(*obj));
iterobj->setReservedSlot(IndexSlot, Int32Value(0));
}
return iterobj;
}
inline uint32_t
ElementIteratorObject::getIndex() const
{
return uint32_t(getReservedSlot(IndexSlot).toInt32());
}
inline JSObject *
ElementIteratorObject::getTargetObject() const
{
return &getReservedSlot(TargetSlot).toObject();
}
inline void
ElementIteratorObject::setIndex(uint32_t index)
{
setReservedSlot(IndexSlot, Int32Value(int32_t(index)));
}
bool
ElementIteratorObject::iteratorNext(JSContext *cx, Value *vp)
{
Rooted<ElementIteratorObject*> self(cx, this);
uint32_t i, length;
RootedObject obj(cx, getTargetObject());
if (!js_GetLengthProperty(cx, obj, &length))
goto error;
i = self->getIndex();
if (i >= length) {
self->setIndex(CLOSED_INDEX);
vp->setMagic(JS_NO_ITER_VALUE);
return true;
}
JS_ASSERT(i + 1 > i);
if (!obj->getElement(cx, obj, i, vp))
goto error;
/* On success, bump the index. */
self->setIndex(i + 1);
return true;
error:
self->setIndex(CLOSED_INDEX);
return false;
}
inline js::ElementIteratorObject *
JSObject::asElementIterator()
{
JS_ASSERT(isElementIterator());
return static_cast<js::ElementIteratorObject *>(this);
}
JSBool
js_IteratorMore(JSContext *cx, HandleObject iterobj, Value *rval)
{
@@ -1207,25 +1214,6 @@ js_IteratorMore(JSContext *cx, HandleObject iterobj, Value *rval)
return false;
if ((ni->flags & JSITER_KEYVALUE) && !NewKeyValuePair(cx, id, *rval, rval))
return false;
} else if (iterobj->isElementIterator()) {
/*
* Like native iterators, element iterators do not have a .next
* method, so this fast path is necessary for correctness.
*/
if (!iterobj->asElementIterator()->iteratorNext(cx, rval))
return false;
if (rval->isMagic(JS_NO_ITER_VALUE)) {
cx->iterValue.setMagic(JS_NO_ITER_VALUE);
rval->setBoolean(false);
return true;
}
} else if (iterobj->isProxy()) {
if (!Proxy::iteratorNext(cx, iterobj, rval))
return false;
if (rval->isMagic(JS_NO_ITER_VALUE)) {
rval->setBoolean(false);
return true;
}
} else {
/* Call the iterator object's .next method. */
if (!GetMethod(cx, iterobj, cx->runtime->atomState.nextAtom, 0, rval))
@@ -1314,6 +1302,8 @@ Class js::StopIterationClass = {
NULL /* construct */
};
/*** Generators **********************************************************************************/
#if JS_HAS_GENERATORS
static void
@@ -1756,83 +1746,75 @@ static JSFunctionSpec generator_methods[] = {
#endif /* JS_HAS_GENERATORS */
static bool
InitIteratorClass(JSContext *cx, Handle<GlobalObject*> global)
{
Rooted<JSObject*> iteratorProto(cx,
global->createBlankPrototype(cx, &PropertyIteratorObject::class_));
if (!iteratorProto)
return false;
AutoIdVector blank(cx);
NativeIterator *ni = NativeIterator::allocateIterator(cx, 0, blank);
if (!ni)
return false;
ni->init(NULL, 0 /* flags */, 0, 0);
iteratorProto->asPropertyIterator().setNativeIterator(ni);
RootedFunction ctor(cx, global->createConstructor(cx, Iterator, CLASS_NAME(cx, Iterator), 2));
if (!ctor)
return false;
if (!LinkConstructorAndPrototype(cx, ctor, iteratorProto))
return false;
if (!DefinePropertiesAndBrand(cx, iteratorProto, NULL, iterator_methods))
return false;
return DefineConstructorAndPrototype(cx, global, JSProto_Iterator, ctor, iteratorProto);
}
/* static */ bool
GlobalObject::initGeneratorClass(JSContext *cx, Handle<GlobalObject*> global)
GlobalObject::initIteratorClasses(JSContext *cx, Handle<GlobalObject *> global)
{
Rooted<JSObject*> iteratorProto(cx);
Value iteratorProtoVal = global->getPrototype(JSProto_Iterator);
if (iteratorProtoVal.isObject()) {
iteratorProto = &iteratorProtoVal.toObject();
} else {
iteratorProto = global->createBlankPrototype(cx, &PropertyIteratorObject::class_);
if (!iteratorProto)
return false;
AutoIdVector blank(cx);
NativeIterator *ni = NativeIterator::allocateIterator(cx, 0, blank);
if (!ni)
return false;
ni->init(NULL, 0 /* flags */, 0, 0);
iteratorProto->asPropertyIterator().setNativeIterator(ni);
Rooted<JSFunction*> ctor(cx, global->createConstructor(cx, Iterator, CLASS_NAME(cx, Iterator), 2));
if (!ctor)
return false;
if (!LinkConstructorAndPrototype(cx, ctor, iteratorProto))
return false;
if (!DefinePropertiesAndBrand(cx, iteratorProto, NULL, iterator_methods))
return false;
if (!DefineConstructorAndPrototype(cx, global, JSProto_Iterator, ctor, iteratorProto))
return false;
}
Rooted<JSObject*> proto(cx);
if (global->getSlot(ELEMENT_ITERATOR_PROTO).isUndefined()) {
Class *cls = &ElementIteratorClass;
proto = global->createBlankPrototypeInheriting(cx, cls, *iteratorProto);
if (!proto || !DefinePropertiesAndBrand(cx, proto, NULL, ElementIteratorObject::methods))
return false;
global->setReservedSlot(ELEMENT_ITERATOR_PROTO, ObjectValue(*proto));
}
#if JS_HAS_GENERATORS
RootedObject proto(cx, global->createBlankPrototype(cx, &GeneratorClass));
if (!proto || !DefinePropertiesAndBrand(cx, proto, NULL, generator_methods))
return false;
global->setReservedSlot(GENERATOR_PROTO, ObjectValue(*proto));
if (global->getSlot(GENERATOR_PROTO).isUndefined()) {
proto = global->createBlankPrototype(cx, &GeneratorClass);
if (!proto || !DefinePropertiesAndBrand(cx, proto, NULL, generator_methods))
return false;
global->setReservedSlot(GENERATOR_PROTO, ObjectValue(*proto));
}
#endif
if (global->getPrototype(JSProto_StopIteration).isUndefined()) {
proto = global->createBlankPrototype(cx, &StopIterationClass);
if (!proto || !proto->freeze(cx))
return false;
/* This should use a non-JSProtoKey'd slot, but this is easier for now. */
if (!DefineConstructorAndPrototype(cx, global, JSProto_StopIteration, proto, proto))
return false;
MarkStandardClassInitializedNoProto(global, &StopIterationClass);
}
return true;
}
static JSObject *
InitStopIterationClass(JSContext *cx, Handle<GlobalObject*> global)
{
RootedObject proto(cx, global->createBlankPrototype(cx, &StopIterationClass));
if (!proto || !proto->freeze(cx))
return NULL;
/* This should use a non-JSProtoKey'd slot, but this is easier for now. */
if (!DefineConstructorAndPrototype(cx, global, JSProto_StopIteration, proto, proto))
return NULL;
MarkStandardClassInitializedNoProto(global, &StopIterationClass);
return proto;
}
JSObject *
js_InitIteratorClasses(JSContext *cx, JSObject *obj)
{
JS_ASSERT(obj->isNative());
Rooted<GlobalObject*> global(cx, &obj->asGlobal());
/*
* Bail if Iterator has already been initialized. We test for Iterator
* rather than for StopIteration because if js_InitIteratorClasses recurs,
* as happens when the StopIteration object is frozen, initializing the
* Iterator class a second time will assert.
*/
RootedObject iter(cx);
if (!js_GetClassObject(cx, global, JSProto_Iterator, &iter))
if (!GlobalObject::initIteratorClasses(cx, global))
return NULL;
if (iter)
return iter;
if (!InitIteratorClass(cx, global) || !GlobalObject::initGeneratorClass(cx, global))
return NULL;
return InitStopIterationClass(cx, global);
return global->getIteratorPrototype();
}