summaryrefslogtreecommitdiff
path: root/Userland
diff options
context:
space:
mode:
Diffstat (limited to 'Userland')
-rw-r--r--Userland/Libraries/LibJS/Runtime/Set.cpp4
-rw-r--r--Userland/Libraries/LibJS/Runtime/Set.h19
-rw-r--r--Userland/Libraries/LibJS/Runtime/SetIterator.cpp2
-rw-r--r--Userland/Libraries/LibJS/Runtime/SetIterator.h2
-rw-r--r--Userland/Libraries/LibJS/Runtime/SetIteratorPrototype.cpp4
-rw-r--r--Userland/Libraries/LibJS/Runtime/SetPrototype.cpp15
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Set/Set.prototype.forEach.js103
-rw-r--r--Userland/Utilities/js.cpp5
8 files changed, 133 insertions, 21 deletions
diff --git a/Userland/Libraries/LibJS/Runtime/Set.cpp b/Userland/Libraries/LibJS/Runtime/Set.cpp
index dfaaf0b28a..32a11c0148 100644
--- a/Userland/Libraries/LibJS/Runtime/Set.cpp
+++ b/Userland/Libraries/LibJS/Runtime/Set.cpp
@@ -15,6 +15,7 @@ Set* Set::create(GlobalObject& global_object)
Set::Set(Object& prototype)
: Object(prototype)
+ , m_values(*prototype.global_object().map_prototype())
{
}
@@ -25,8 +26,7 @@ Set::~Set()
void Set::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
- for (auto& value : m_values)
- visitor.visit(value);
+ static_cast<Object&>(m_values).visit_edges(visitor);
}
}
diff --git a/Userland/Libraries/LibJS/Runtime/Set.h b/Userland/Libraries/LibJS/Runtime/Set.h
index ea06df52ed..63056ae8fe 100644
--- a/Userland/Libraries/LibJS/Runtime/Set.h
+++ b/Userland/Libraries/LibJS/Runtime/Set.h
@@ -6,8 +6,8 @@
#pragma once
-#include <AK/HashTable.h>
#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/Map.h>
#include <LibJS/Runtime/Object.h>
#include <LibJS/Runtime/Value.h>
@@ -22,13 +22,24 @@ public:
explicit Set(Object& prototype);
virtual ~Set() override;
- OrderedHashTable<Value, ValueTraits> const& values() const { return m_values; };
- OrderedHashTable<Value, ValueTraits>& values() { return m_values; };
+ // NOTE: Unlike what the spec says, we implement Sets using an underlying map,
+ // so all the functions below do not directly implement the operations as
+ // defined by the specification.
+
+ void set_clear() { m_values.map_clear(); }
+ bool set_remove(Value const& value) { return m_values.map_remove(value); }
+ bool set_has(Value const& key) const { return m_values.map_has(key); }
+ void set_add(Value const& key) { m_values.map_set(key, js_undefined()); }
+ size_t set_size() const { return m_values.map_size(); }
+
+ auto begin() const { return m_values.begin(); }
+ auto begin() { return m_values.begin(); }
+ auto end() const { return m_values.end(); }
private:
virtual void visit_edges(Visitor& visitor) override;
- OrderedHashTable<Value, ValueTraits> m_values;
+ Map m_values;
};
}
diff --git a/Userland/Libraries/LibJS/Runtime/SetIterator.cpp b/Userland/Libraries/LibJS/Runtime/SetIterator.cpp
index 2ae33407cf..5eb99ab187 100644
--- a/Userland/Libraries/LibJS/Runtime/SetIterator.cpp
+++ b/Userland/Libraries/LibJS/Runtime/SetIterator.cpp
@@ -18,7 +18,7 @@ SetIterator::SetIterator(Set& set, Object::PropertyKind iteration_kind, Object&
: Object(prototype)
, m_set(set)
, m_iteration_kind(iteration_kind)
- , m_iterator(set.values().begin())
+ , m_iterator(static_cast<Set const&>(set).begin())
{
}
diff --git a/Userland/Libraries/LibJS/Runtime/SetIterator.h b/Userland/Libraries/LibJS/Runtime/SetIterator.h
index cc85c3d6c5..7a4da942fc 100644
--- a/Userland/Libraries/LibJS/Runtime/SetIterator.h
+++ b/Userland/Libraries/LibJS/Runtime/SetIterator.h
@@ -33,7 +33,7 @@ private:
Set& m_set;
bool m_done { false };
Object::PropertyKind m_iteration_kind;
- OrderedHashTable<Value, ValueTraits>::Iterator m_iterator;
+ Map::ConstIterator m_iterator;
};
}
diff --git a/Userland/Libraries/LibJS/Runtime/SetIteratorPrototype.cpp b/Userland/Libraries/LibJS/Runtime/SetIteratorPrototype.cpp
index 2fb811b752..ee07df8bf6 100644
--- a/Userland/Libraries/LibJS/Runtime/SetIteratorPrototype.cpp
+++ b/Userland/Libraries/LibJS/Runtime/SetIteratorPrototype.cpp
@@ -41,7 +41,7 @@ JS_DEFINE_NATIVE_FUNCTION(SetIteratorPrototype::next)
return create_iterator_result_object(global_object, js_undefined(), true);
auto& set = set_iterator->set();
- if (set_iterator->m_iterator == set.values().end()) {
+ if (set_iterator->m_iterator == set.end()) {
set_iterator->m_done = true;
return create_iterator_result_object(global_object, js_undefined(), true);
}
@@ -49,7 +49,7 @@ JS_DEFINE_NATIVE_FUNCTION(SetIteratorPrototype::next)
auto iteration_kind = set_iterator->iteration_kind();
VERIFY(iteration_kind != Object::PropertyKind::Key);
- auto value = *set_iterator->m_iterator;
+ auto value = (*set_iterator->m_iterator).key;
++set_iterator->m_iterator;
if (iteration_kind == Object::PropertyKind::Value)
return create_iterator_result_object(global_object, value, false);
diff --git a/Userland/Libraries/LibJS/Runtime/SetPrototype.cpp b/Userland/Libraries/LibJS/Runtime/SetPrototype.cpp
index 6add2986f0..72fc509de2 100644
--- a/Userland/Libraries/LibJS/Runtime/SetPrototype.cpp
+++ b/Userland/Libraries/LibJS/Runtime/SetPrototype.cpp
@@ -52,7 +52,7 @@ JS_DEFINE_NATIVE_FUNCTION(SetPrototype::add)
auto value = vm.argument(0);
if (value.is_negative_zero())
value = Value(0);
- set->values().set(value, AK::HashSetExistingEntryBehavior::Keep);
+ set->set_add(value);
return set;
}
@@ -60,7 +60,7 @@ JS_DEFINE_NATIVE_FUNCTION(SetPrototype::add)
JS_DEFINE_NATIVE_FUNCTION(SetPrototype::clear)
{
auto* set = TRY(typed_this_object(global_object));
- set->values().clear();
+ set->set_clear();
return js_undefined();
}
@@ -68,7 +68,7 @@ JS_DEFINE_NATIVE_FUNCTION(SetPrototype::clear)
JS_DEFINE_NATIVE_FUNCTION(SetPrototype::delete_)
{
auto* set = TRY(typed_this_object(global_object));
- return Value(set->values().remove(vm.argument(0)));
+ return Value(set->set_remove(vm.argument(0)));
}
// 24.2.3.5 Set.prototype.entries ( ), https://tc39.es/ecma262/#sec-set.prototype.entries
@@ -86,8 +86,8 @@ JS_DEFINE_NATIVE_FUNCTION(SetPrototype::for_each)
if (!vm.argument(0).is_function())
return vm.throw_completion<TypeError>(global_object, ErrorType::NotAFunction, vm.argument(0).to_string_without_side_effects());
auto this_value = vm.this_value(global_object);
- for (auto& value : set->values())
- TRY(call(global_object, vm.argument(0).as_function(), vm.argument(1), value, value, this_value));
+ for (auto& entry : *set)
+ TRY(call(global_object, vm.argument(0).as_function(), vm.argument(1), entry.key, entry.key, this_value));
return js_undefined();
}
@@ -95,8 +95,7 @@ JS_DEFINE_NATIVE_FUNCTION(SetPrototype::for_each)
JS_DEFINE_NATIVE_FUNCTION(SetPrototype::has)
{
auto* set = TRY(typed_this_object(global_object));
- auto& values = set->values();
- return Value(values.find(vm.argument(0)) != values.end());
+ return Value(set->set_has(vm.argument(0)));
}
// 24.2.3.10 Set.prototype.values ( ), https://tc39.es/ecma262/#sec-set.prototype.values
@@ -111,7 +110,7 @@ JS_DEFINE_NATIVE_FUNCTION(SetPrototype::values)
JS_DEFINE_NATIVE_FUNCTION(SetPrototype::size_getter)
{
auto* set = TRY(typed_this_object(global_object));
- return Value(set->values().size());
+ return Value(set->set_size());
}
}
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Set/Set.prototype.forEach.js b/Userland/Libraries/LibJS/Tests/builtins/Set/Set.prototype.forEach.js
index ab677dcd2a..5259bf7c37 100644
--- a/Userland/Libraries/LibJS/Tests/builtins/Set/Set.prototype.forEach.js
+++ b/Userland/Libraries/LibJS/Tests/builtins/Set/Set.prototype.forEach.js
@@ -46,3 +46,106 @@ describe("normal behavior", () => {
});
});
});
+
+describe("modification during iteration", () => {
+ test("adding items during forEach also get visited", () => {
+ const set = new Set([1, 2]);
+ const visited = [];
+ set.forEach(val => {
+ if (val <= 2) set.add(4 * val);
+
+ visited.push(val);
+ });
+ expect(set).toHaveSize(4);
+
+ expect(visited).toEqual([1, 2, 4, 8]);
+ });
+
+ test("removing an item before it is visited means it doesn't get visited", () => {
+ const set = new Set([1, 2, 3]);
+ const visited = [];
+ set.forEach(val => {
+ visited.push(val);
+ if (val === 1) {
+ expect(set.delete(2)).toBeTrue();
+ } else {
+ expect(val).toBe(3);
+ expect(set.delete(2)).toBeFalse();
+ }
+ });
+ expect(set).toHaveSize(2);
+ expect(visited).toEqual([1, 3]);
+ });
+
+ test("removing an item after it was visited and adding it again means it gets visited twice", () => {
+ const set = new Set([1, 2, 3]);
+ const visited = [];
+ set.forEach(val => {
+ visited.push(val);
+ if (val === 2) {
+ expect(set.delete(1)).toBeTrue();
+ } else if (val === 3) {
+ expect(set).toHaveSize(2);
+ set.add(1);
+ expect(set).toHaveSize(3);
+ }
+ });
+ expect(set).toHaveSize(3);
+ expect(visited).toEqual([1, 2, 3, 1]);
+ });
+
+ test("adding a new item and removing it before it gets visited means it never gets visited", () => {
+ const set = new Set([1, 2]);
+ const visited = [];
+ set.forEach(val => {
+ visited.push(val);
+ if (val === 1) {
+ set.add(3);
+ expect(set).toHaveSize(3);
+ } else if (val === 2) {
+ expect(set).toHaveSize(3);
+ expect(set.delete(3)).toBeTrue();
+ expect(set).toHaveSize(2);
+ }
+ expect(val).not.toBe(3);
+ });
+ expect(set).toHaveSize(2);
+ expect(visited).toEqual([1, 2]);
+ });
+
+ test("removing and adding in the same iterations", () => {
+ const set = new Set([1, 2, 3]);
+ const visited = [];
+ let first = true;
+ set.forEach(val => {
+ visited.push(val);
+ if (val === 1 && first) {
+ expect(set.delete(1)).toBeTrue();
+ set.add(1);
+ }
+
+ first = false;
+ });
+ expect(set).toHaveSize(3);
+
+ expect(visited).toEqual([1, 2, 3, 1]);
+ });
+
+ test("removing and readding the same item means it can get visited n times", () => {
+ let n = 3;
+
+ const set = new Set([1, 2]);
+
+ const visited = [];
+ set.forEach(val => {
+ visited.push(val);
+ if (n-- > 0) {
+ expect(set.delete(val)).toBeTrue();
+ set.add(val);
+ }
+ });
+
+ expect(set).toHaveSize(2);
+ expect(visited).toEqual([1, 2, 1, 2, 1]);
+ });
+});
diff --git a/Userland/Utilities/js.cpp b/Userland/Utilities/js.cpp
index 066bc1d0ef..51c1994690 100644
--- a/Userland/Utilities/js.cpp
+++ b/Userland/Utilities/js.cpp
@@ -375,13 +375,12 @@ static void print_map(JS::Object const& object, HashTable<JS::Object*>& seen_obj
static void print_set(JS::Object const& object, HashTable<JS::Object*>& seen_objects)
{
auto& set = static_cast<JS::Set const&>(object);
- auto& values = set.values();
print_type("Set");
js_out(" {{");
bool first = true;
- for (auto& value : values) {
+ for (auto& entry : set) {
print_separator(first);
- print_value(value, seen_objects);
+ print_value(entry.key, seen_objects);
}
if (!first)
js_out(" ");