# HG changeset patch # User Sankha Narayan Guria # Date 1369941230 -19800 # Node ID f669c7354de783bfaa57a6714237eeeed9f46dee # Parent c8e47b184aba8cf8665a7d53ca74889390cb1d0e Bug 869996 - Implements Set.prototype.{keys, values, entries}; r=jorendorff diff --git a/js/src/builtin/MapObject.cpp b/js/src/builtin/MapObject.cpp --- a/js/src/builtin/MapObject.cpp +++ b/js/src/builtin/MapObject.cpp @@ -1033,18 +1033,16 @@ const JSPropertySpec MapObject::properti const JSFunctionSpec MapObject::methods[] = { JS_FN("get", get, 1, 0), JS_FN("has", has, 1, 0), JS_FN("set", set, 2, 0), JS_FN("delete", delete_, 1, 0), JS_FN("keys", keys, 0, 0), JS_FN("values", values, 0, 0), - JS_FN("entries", entries, 0, 0), - JS_FN("iterator", entries, 0, 0), JS_FN("clear", clear, 0, 0), JS_FS_END }; static JSObject * InitClass(JSContext *cx, Handle global, Class *clasp, JSProtoKey key, Native construct, const JSPropertySpec *properties, const JSFunctionSpec *methods) { @@ -1063,17 +1061,30 @@ InitClass(JSContext *cx, Handle global(cx, &obj->asGlobal()); - return InitClass(cx, global, &class_, JSProto_Map, construct, properties, methods); + RootedObject proto(cx, + InitClass(cx, global, &class_, JSProto_Map, construct, properties, methods)); + if (proto) { + // Define the "entries" method. + JSFunction *fun = JS_DefineFunction(cx, proto, "entries", entries, 0, 0); + if (!fun) + return NULL; + + // Define its alias. + RootedValue funval(cx, ObjectValue(*fun)); + if (!JS_DefineProperty(cx, proto, "iterator", funval, NULL, NULL, 0)) + return NULL; + } + return proto; } template static void MarkKey(Range &r, const HashableValue &key, JSTracer *trc) { HashableValue newKey = key.mark(trc); @@ -1414,24 +1425,26 @@ js_InitMapClass(JSContext *cx, HandleObj } /*** SetIterator *********************************************************************************/ class js::SetIteratorObject : public JSObject { public: - enum { TargetSlot, RangeSlot, SlotCount }; + enum { TargetSlot, KindSlot, RangeSlot, SlotCount }; static const JSFunctionSpec methods[]; - static SetIteratorObject *create(JSContext *cx, HandleObject setobj, ValueSet *data); + static SetIteratorObject *create(JSContext *cx, HandleObject setobj, ValueSet *data, + SetObject::IteratorKind kind); static void finalize(FreeOp *fop, JSObject *obj); private: static inline bool is(const Value &v); inline ValueSet::Range *range(); + inline SetObject::IteratorKind kind() const; static bool next_impl(JSContext *cx, CallArgs args); static JSBool next(JSContext *cx, unsigned argc, Value *vp); }; inline js::SetIteratorObject & JSObject::asSetIterator() { JS_ASSERT(isSetIterator()); @@ -1458,16 +1471,24 @@ const JSFunctionSpec SetIteratorObject:: }; inline ValueSet::Range * SetIteratorObject::range() { return static_cast(getSlot(RangeSlot).toPrivate()); } +inline SetObject::IteratorKind +SetIteratorObject::kind() const +{ + int32_t i = getSlot(KindSlot).toInt32(); + JS_ASSERT(i == SetObject::Keys || i == SetObject::Values || i == SetObject::Entries); + return SetObject::IteratorKind(i); +} + bool GlobalObject::initSetIteratorProto(JSContext *cx, Handle global) { JSObject *base = global->getOrCreateIteratorPrototype(cx); if (!base) return false; RootedObject proto(cx, NewObjectWithGivenProto(cx, &SetIteratorClass, base, global)); if (!proto) @@ -1475,33 +1496,35 @@ GlobalObject::initSetIteratorProto(JSCon proto->setSlot(SetIteratorObject::RangeSlot, PrivateValue(NULL)); if (!JS_DefineFunctions(cx, proto, SetIteratorObject::methods)) return false; global->setReservedSlot(SET_ITERATOR_PROTO, ObjectValue(*proto)); return true; } SetIteratorObject * -SetIteratorObject::create(JSContext *cx, HandleObject setobj, ValueSet *data) +SetIteratorObject::create(JSContext *cx, HandleObject setobj, ValueSet *data, + SetObject::IteratorKind kind) { Rooted global(cx, &setobj->global()); Rooted proto(cx, global->getOrCreateSetIteratorPrototype(cx)); if (!proto) return NULL; ValueSet::Range *range = cx->new_(data->all()); if (!range) return NULL; JSObject *iterobj = NewObjectWithGivenProto(cx, &SetIteratorClass, proto, global); if (!iterobj) { js_delete(range); return NULL; } iterobj->setSlot(TargetSlot, ObjectValue(*setobj)); + iterobj->setSlot(KindSlot, Int32Value(int32_t(kind))); iterobj->setSlot(RangeSlot, PrivateValue(range)); return static_cast(iterobj); } void SetIteratorObject::finalize(FreeOp *fop, JSObject *obj) { fop->delete_(obj->asSetIterator().range()); @@ -1521,17 +1544,33 @@ SetIteratorObject::next_impl(JSContext * if (!range) return js_ThrowStopIteration(cx); if (range->empty()) { js_delete(range); thisobj.setReservedSlot(RangeSlot, PrivateValue(NULL)); return js_ThrowStopIteration(cx); } - args.rval().set(range->front().get()); + switch (thisobj.kind()) { + case SetObject::Values: + args.rval().set(range->front().get()); + break; + + case SetObject::Entries: { + Value pair[2] = { range->front().get(), range->front().get() }; + AutoValueArray root(cx, pair, 2); + + JSObject *pairobj = NewDenseCopiedArray(cx, 2, pair); + if (!pairobj) + return false; + args.rval().setObject(*pairobj); + break; + } + } + range->popFront(); return true; } JSBool SetIteratorObject::next(JSContext *cx, unsigned argc, Value *vp) { CallArgs args = CallArgsFromVp(argc, vp); @@ -1571,26 +1610,41 @@ const JSPropertySpec SetObject::properti JS_PSG("size", size, 0), JS_PS_END }; const JSFunctionSpec SetObject::methods[] = { JS_FN("has", has, 1, 0), JS_FN("add", add, 1, 0), JS_FN("delete", delete_, 1, 0), - JS_FN("iterator", iterator, 0, 0), + JS_FN("entries", entries, 0, 0), JS_FN("clear", clear, 0, 0), JS_FS_END }; JSObject * SetObject::initClass(JSContext *cx, JSObject *obj) { Rooted global(cx, &obj->asGlobal()); - return InitClass(cx, global, &class_, JSProto_Set, construct, properties, methods); + RootedObject proto(cx, + InitClass(cx, global, &class_, JSProto_Set, construct, properties, methods)); + if (proto) { + // Define the "values" method. + JSFunction *fun = JS_DefineFunction(cx, proto, "values", values, 0, 0); + if (!fun) + return NULL; + + // Define its aliases. + RootedValue funval(cx, ObjectValue(*fun)); + if (!JS_DefineProperty(cx, proto, "keys", funval, NULL, NULL, 0)) + return NULL; + if (!JS_DefineProperty(cx, proto, "iterator", funval, NULL, NULL, 0)) + return NULL; + } + return proto; } void SetObject::mark(JSTracer *trc, JSObject *obj) { SetObject *setobj = static_cast(obj); if (ValueSet *set = setobj->getData()) { for (ValueSet::Range r = set->all(); !r.empty(); r.popFront()) @@ -1735,32 +1789,51 @@ SetObject::delete_impl(JSContext *cx, Ca JSBool SetObject::delete_(JSContext *cx, unsigned argc, Value *vp) { CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } bool -SetObject::iterator_impl(JSContext *cx, CallArgs args) +SetObject::iterator_impl(JSContext *cx, CallArgs args, IteratorKind kind) { Rooted setobj(cx, &args.thisv().toObject().asSet()); ValueSet &set = *setobj->getData(); - Rooted iterobj(cx, SetIteratorObject::create(cx, setobj, &set)); + Rooted iterobj(cx, SetIteratorObject::create(cx, setobj, &set, kind)); if (!iterobj) return false; args.rval().setObject(*iterobj); return true; } +bool +SetObject::values_impl(JSContext *cx, CallArgs args) +{ + return iterator_impl(cx, args, Values); +} + JSBool -SetObject::iterator(JSContext *cx, unsigned argc, Value *vp) +SetObject::values(JSContext *cx, unsigned argc, Value *vp) { CallArgs args = CallArgsFromVp(argc, vp); - return CallNonGenericMethod(cx, is, iterator_impl, args); + return CallNonGenericMethod(cx, is, values_impl, args); +} + +bool +SetObject::entries_impl(JSContext *cx, CallArgs args) +{ + return iterator_impl(cx, args, Entries); +} + +JSBool +SetObject::entries(JSContext *cx, unsigned argc, Value *vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, is, entries_impl, args); } bool SetObject::clear_impl(JSContext *cx, CallArgs args) { Rooted setobj(cx, &args.thisv().toObject().asSet()); if (!setobj->getData()->clear()) { js_ReportOutOfMemory(cx); diff --git a/js/src/builtin/MapObject.h b/js/src/builtin/MapObject.h --- a/js/src/builtin/MapObject.h +++ b/js/src/builtin/MapObject.h @@ -122,39 +122,44 @@ class MapObject : public JSObject { static bool entries_impl(JSContext *cx, CallArgs args); static JSBool entries(JSContext *cx, unsigned argc, Value *vp); static bool clear_impl(JSContext *cx, CallArgs args); static JSBool clear(JSContext *cx, unsigned argc, Value *vp); }; class SetObject : public JSObject { public: + enum IteratorKind { Values, Entries }; static JSObject *initClass(JSContext *cx, JSObject *obj); static Class class_; private: static const JSPropertySpec properties[]; static const JSFunctionSpec methods[]; ValueSet *getData() { return static_cast(getPrivate()); } static ValueSet & extract(CallReceiver call); static void mark(JSTracer *trc, JSObject *obj); static void finalize(FreeOp *fop, JSObject *obj); static JSBool construct(JSContext *cx, unsigned argc, Value *vp); static bool is(const Value &v); + static bool iterator_impl(JSContext *cx, CallArgs args, IteratorKind kind); + static bool size_impl(JSContext *cx, CallArgs args); static JSBool size(JSContext *cx, unsigned argc, Value *vp); static bool has_impl(JSContext *cx, CallArgs args); static JSBool has(JSContext *cx, unsigned argc, Value *vp); static bool add_impl(JSContext *cx, CallArgs args); static JSBool add(JSContext *cx, unsigned argc, Value *vp); static bool delete_impl(JSContext *cx, CallArgs args); static JSBool delete_(JSContext *cx, unsigned argc, Value *vp); - static bool iterator_impl(JSContext *cx, CallArgs args); - static JSBool iterator(JSContext *cx, unsigned argc, Value *vp); + static bool values_impl(JSContext *cx, CallArgs args); + static JSBool values(JSContext *cx, unsigned argc, Value *vp); + static bool entries_impl(JSContext *cx, CallArgs args); + static JSBool entries(JSContext *cx, unsigned argc, Value *vp); static bool clear_impl(JSContext *cx, CallArgs args); static JSBool clear(JSContext *cx, unsigned argc, Value *vp); }; } /* namespace js */ extern JSObject * js_InitMapClass(JSContext *cx, js::HandleObject obj); diff --git a/js/src/jit-test/tests/collections/Map-values-1.js b/js/src/jit-test/tests/collections/Map-values-1.js --- a/js/src/jit-test/tests/collections/Map-values-1.js +++ b/js/src/jit-test/tests/collections/Map-values-1.js @@ -1,8 +1,12 @@ +// Map.prototype.iterator and .entries are the same function object + +assertEq(Map.prototype.iterator, Map.prototype.entries); + // map.keys(), .values(), and .entries() on an empty map produce empty iterators. var m = Map(); var ki = m.keys(), vi = m.values(), ei = m.entries(); var p = Object.getPrototypeOf(ki) assertEq(Object.getPrototypeOf(vi), p); assertEq(Object.getPrototypeOf(ei), p); diff --git a/js/src/jit-test/tests/collections/Set-values-1.js b/js/src/jit-test/tests/collections/Set-values-1.js new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/collections/Set-values-1.js @@ -0,0 +1,18 @@ +// Set.prototype.keys and .values are the same function object + +assertEq(Set.prototype.keys, Set.prototype.values); + +// set.keys(), .values(), and .entries() on an empty set produce empty iterators + +var s = Set(); +var ki = s.keys(), vi = s.values(), ei = s.entries(); +var p = Object.getPrototypeOf(ki); +assertEq(Object.getPrototypeOf(vi), p); +assertEq(Object.getPrototypeOf(ei), p); + +for (let k of ki) + throw "FAIL"; +for (let v of vi) + throw "FAIL"; +for (let [k, v] of ei) + throw "FAIL"; diff --git a/js/src/jit-test/tests/collections/Set-values-2.js b/js/src/jit-test/tests/collections/Set-values-2.js new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/collections/Set-values-2.js @@ -0,0 +1,19 @@ +// set.keys() and set.values() return iterators over the values, +// and set.entries() return iterator over a pair of values in the set + +load(libdir + "asserts.js"); + +var data = [1, 2, 3, 4]; +var values = [[1, 1], [2, 2], [3, 3], [4, 4]]; +var s = Set(data); + +var ki = s.keys(); +assertEq(ki.next(), 1); +assertEq(ki.next(), 2); +assertEq(ki.next(), 3); +assertEq(ki.next(), 4); +assertThrowsValue(function () { ki.next(); }, StopIteration); + +assertEq([...s.keys()].toSource(), [1, 2, 3, 4].toSource()); +assertEq([...s.values()].toSource(), [1, 2, 3, 4].toSource()); +assertEq([...s.entries()].toSource(), values.toSource());