Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LibJS: Implement the Array Grouping proposal #11412

Merged
merged 4 commits into from
Jan 5, 2022

Conversation

Lubrsi
Copy link
Member

@Lubrsi Lubrsi commented Dec 25, 2021

This proposal went to stage 3 five days ago: tc39/proposal-array-grouping@1e3ae1a

This allows you to group together a bunch of elements in an array into a single named array. These groups are determined by user code in a custom callback function. The resulting groups are then stored into either a plain object or a Map depending on the function you use.

Example from their repo:
Screenshot from 2021-12-25 03-58-45

Copy link
Member

@linusg linusg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vector<Handle<Value>> should be MarkedValueList.

@Lubrsi
Copy link
Member Author

Lubrsi commented Dec 25, 2021

Ah, I forgot to mention I already tried that. MarkedValueList can't be used because it's not moveable, which HashMap and HashTable move internally, even if you pass in a const&.

@IdanHo
Copy link
Member

IdanHo commented Dec 25, 2021

MarkedValueList can't be used because it's not moveable

It is moveable 🤔

@Lubrsi
Copy link
Member Author

Lubrsi commented Dec 25, 2021

See here:

MarkedValueList& operator=(MarkedValueList&&) = delete;

If I change the code to look like this:

diff --git a/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp b/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp
index d9fc772623..bf42088331 100644
--- a/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp
+++ b/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp
@@ -1670,7 +1670,7 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::at)
 
 // 2.3 AddValueToKeyedGroup ( groups, key, value ), https://tc39.es/proposal-array-grouping/#sec-add-value-to-keyed-group
 template<typename GroupsType, typename KeyType>
-static void add_value_to_keyed_group(GroupsType& groups, KeyType key, Handle<Value> value)
+static void add_value_to_keyed_group(GlobalObject& global_object, GroupsType& groups, KeyType key, Value value)
 {
     // 1. For each Record { [[Key]], [[Elements]] } g of groups, do
     //      a. If ! SameValue(g.[[Key]], key) is true, then
@@ -1688,7 +1688,7 @@ static void add_value_to_keyed_group(GroupsType& groups, KeyType key, Handle<Val
     }
 
     // 2. Let group be the Record { [[Key]]: key, [[Elements]]: « value » }.
-    Vector<Handle<Value>> new_elements;
+    MarkedValueList new_elements { global_object.heap() };
     new_elements.append(value);
 
     // 3. Append group as the last element of groups.
@@ -1713,7 +1713,7 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::group_by)
         return vm.throw_completion<TypeError>(global_object, ErrorType::NotAFunction, callback_function.to_string_without_side_effects());
 
     // 5. Let groups be a new empty List.
-    OrderedHashMap<PropertyKey, Vector<Handle<Value>>> groups;
+    OrderedHashMap<PropertyKey, MarkedValueList> groups;
 
     // 4. Let k be 0.
     // 6. Repeat, while k < len
@@ -1729,7 +1729,7 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::group_by)
         auto property_key = TRY(property_key_value.to_property_key(global_object));
 
         // d. Perform ! AddValueToKeyedGroup(groups, propertyKey, kValue).
-        add_value_to_keyed_group(groups, property_key, make_handle(k_value));
+        add_value_to_keyed_group(global_object, groups, property_key, k_value);
 
         // e. Set k to k + 1.
     }
@@ -1740,7 +1740,7 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::group_by)
     // 8. For each Record { [[Key]], [[Elements]] } g of groups, do
     for (auto& group : groups) {
         // a. Let elements be ! CreateArrayFromList(g.[[Elements]]).
-        auto* elements = Array::create_from<Handle<Value>>(global_object, group.value, [](auto& handle) { return handle.value(); });
+        auto* elements = Array::create_from(global_object, group.value);
 
         // b. Perform ! CreateDataPropertyOrThrow(obj, g.[[Key]], elements).
         MUST(object->create_data_property_or_throw(group.key, elements));
@@ -1780,7 +1780,7 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::group_by_to_map)
     };
 
     // 5. Let groups be a new empty List.
-    OrderedHashMap<Handle<Value>, Vector<Handle<Value>>, KeyedGroupTraits> groups;
+    OrderedHashMap<Handle<Value>, MarkedValueList, KeyedGroupTraits> groups;
 
     // 4. Let k be 0.
     // 6. Repeat, while k < len
@@ -1799,7 +1799,7 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::group_by_to_map)
             key = Value(0);
 
         // e. Perform ! AddValueToKeyedGroup(groups, key, kValue).
-        add_value_to_keyed_group(groups, make_handle(key), make_handle(k_value));
+        add_value_to_keyed_group(global_object, groups, make_handle(key), k_value);
 
         // f. Set k to k + 1.
     }
@@ -1810,7 +1810,7 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::group_by_to_map)
     // 8. For each Record { [[Key]], [[Elements]] } g of groups, do
     for (auto& group : groups) {
         // a. Let elements be ! CreateArrayFromList(g.[[Elements]]).
-        auto* elements = Array::create_from<Handle<Value>>(global_object, group.value, [](auto& handle) { return handle.value(); });
+        auto* elements = Array::create_from(global_object, group.value);
 
         // b. Let entry be the Record { [[Key]]: g.[[Key]], [[Value]]: elements }.
         // c. Append entry as the last element of map.[[MapData]].

It seems I can move it as the value parameter in set, but when it moves internally, it hits that deleted function.

-- Configuring done
-- Generating done
-- Build files have been written to: /home/lukew/Desktop/serenity-project/serenity/Build/lagom
ninja: Entering directory `/home/lukew/Desktop/serenity-project/serenity/Build/lagom'
[0/2] Re-checking globbed directories...
[1/2] Re-running CMake...
-- Configuring done
-- Generating done
-- Build files have been written to: /home/lukew/Desktop/serenity-project/serenity/Build/lagom
[0/2] Re-checking globbed directories...
[1/4] Building CXX object CMakeFiles/LagomJS.dir/home/lukew/Desktop/serenity-project/serenity/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp.o
FAILED: CMakeFiles/LagomJS.dir/home/lukew/Desktop/serenity-project/serenity/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp.o 
/usr/bin/ccache /usr/bin/g++  -DLagomJS_EXPORTS -I/home/lukew/Desktop/serenity-project/serenity/Meta/Lagom/../.. -I/home/lukew/Desktop/serenity-project/serenity/Meta/Lagom/../../Userland -I/home/lukew/Desktop/serenity-project/serenity/Meta/Lagom/../../Userland/Libraries -I. -fPIC   -Wno-unknown-warning-option -Wno-literal-suffix -Wno-implicit-const-int-float-conversion -O2 -Wall -Wextra -Werror -fPIC -g -Wno-maybe-uninitialized -fno-exceptions -fno-semantic-interposition -Wno-expansion-to-defined -std=c++2a -MD -MT CMakeFiles/LagomJS.dir/home/lukew/Desktop/serenity-project/serenity/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp.o -MF CMakeFiles/LagomJS.dir/home/lukew/Desktop/serenity-project/serenity/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp.o.d -o CMakeFiles/LagomJS.dir/home/lukew/Desktop/serenity-project/serenity/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp.o -c /home/lukew/Desktop/serenity-project/serenity/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp
In file included from /home/lukew/Desktop/serenity-project/serenity/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp:11:
/home/lukew/Desktop/serenity-project/serenity/Meta/Lagom/../../AK/HashTable.h: In instantiation of ‘AK::ErrorOr<AK::HashSetResult> AK::HashTable<T, TraitsForT, IsOrdered>::try_set(U&&, AK::HashSetExistingEntryBehavior) [with U = AK::HashMap<JS::PropertyKey, JS::MarkedValueList, AK::Traits<JS::PropertyKey>, true>::Entry; T = AK::HashMap<JS::PropertyKey, JS::MarkedValueList, AK::Traits<JS::PropertyKey>, true>::Entry; TraitsForT = AK::HashMap<JS::PropertyKey, JS::MarkedValueList, AK::Traits<JS::PropertyKey>, true>::EntryTraits; bool IsOrdered = true]’:
/home/lukew/Desktop/serenity-project/serenity/Meta/Lagom/../../AK/HashTable.h:315:16:   required from ‘AK::HashSetResult AK::HashTable<T, TraitsForT, IsOrdered>::set(U&&, AK::HashSetExistingEntryBehavior) [with U = AK::HashMap<JS::PropertyKey, JS::MarkedValueList, AK::Traits<JS::PropertyKey>, true>::Entry; T = AK::HashMap<JS::PropertyKey, JS::MarkedValueList, AK::Traits<JS::PropertyKey>, true>::Entry; TraitsForT = AK::HashMap<JS::PropertyKey, JS::MarkedValueList, AK::Traits<JS::PropertyKey>, true>::EntryTraits; bool IsOrdered = true]’
/home/lukew/Desktop/serenity-project/serenity/Meta/Lagom/../../AK/HashMap.h:52:68:   required from ‘AK::HashSetResult AK::HashMap<K, V, KeyTraits, IsOrdered>::set(const K&, V&&) [with K = JS::PropertyKey; V = JS::MarkedValueList; KeyTraits = AK::Traits<JS::PropertyKey>; bool IsOrdered = true]’
/home/lukew/Desktop/serenity-project/serenity/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp:1695:29:   required from ‘void JS::add_value_to_keyed_group(JS::GlobalObject&, GroupsType&, KeyType, JS::Value) [with GroupsType = AK::HashMap<JS::PropertyKey, JS::MarkedValueList, AK::Traits<JS::PropertyKey>, true>; KeyType = JS::PropertyKey]’
/home/lukew/Desktop/serenity-project/serenity/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp:1732:78:   required from here
/home/lukew/Desktop/serenity-project/serenity/Meta/Lagom/../../AK/HashTable.h:288:31: error: use of deleted function ‘AK::HashMap<JS::PropertyKey, JS::MarkedValueList, AK::Traits<JS::PropertyKey>, true>::Entry& AK::HashMap<JS::PropertyKey, JS::MarkedValueList, AK::Traits<JS::PropertyKey>, true>::Entry::operator=(AK::HashMap<JS::PropertyKey, JS::MarkedValueList, AK::Traits<JS::PropertyKey>, true>::Entry&&)’
  288 |             (*bucket->slot()) = forward<U>(value);
      |             ~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~
In file included from /home/lukew/Desktop/serenity-project/serenity/Meta/Lagom/../../Userland/Libraries/LibJS/AST.h:12,
                 from /home/lukew/Desktop/serenity-project/serenity/Meta/Lagom/../../Userland/Libraries/LibJS/Runtime/AbstractOperations.h:11,
                 from /home/lukew/Desktop/serenity-project/serenity/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp:14:
/home/lukew/Desktop/serenity-project/serenity/Meta/Lagom/../../AK/HashMap.h:19:12: note: ‘AK::HashMap<JS::PropertyKey, JS::MarkedValueList, AK::Traits<JS::PropertyKey>, true>::Entry& AK::HashMap<JS::PropertyKey, JS::MarkedValueList, AK::Traits<JS::PropertyKey>, true>::Entry::operator=(AK::HashMap<JS::PropertyKey, JS::MarkedValueList, AK::Traits<JS::PropertyKey>, true>::Entry&&)’ is implicitly deleted because the default definition would be ill-formed:
   19 |     struct Entry {
      |            ^~~~~
/home/lukew/Desktop/serenity-project/serenity/Meta/Lagom/../../AK/HashMap.h:19:12: error: use of deleted function ‘JS::MarkedValueList& JS::MarkedValueList::operator=(JS::MarkedValueList&&)’
In file included from /home/lukew/Desktop/serenity-project/serenity/Meta/Lagom/../../Userland/Libraries/LibJS/Runtime/Object.h:17,
                 from /home/lukew/Desktop/serenity-project/serenity/Meta/Lagom/../../Userland/Libraries/LibJS/Runtime/Environment.h:10,
                 from /home/lukew/Desktop/serenity-project/serenity/Meta/Lagom/../../Userland/Libraries/LibJS/Runtime/Reference.h:10,
                 from /home/lukew/Desktop/serenity-project/serenity/Meta/Lagom/../../Userland/Libraries/LibJS/AST.h:23,
                 from /home/lukew/Desktop/serenity-project/serenity/Meta/Lagom/../../Userland/Libraries/LibJS/Runtime/AbstractOperations.h:11,
                 from /home/lukew/Desktop/serenity-project/serenity/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp:14:
/home/lukew/Desktop/serenity-project/serenity/Meta/Lagom/../../Userland/Libraries/LibJS/Runtime/MarkedValueList.h:25:22: note: declared here
   25 |     MarkedValueList& operator=(MarkedValueList&&) = delete;
      |                      ^~~~~~~~
In file included from /home/lukew/Desktop/serenity-project/serenity/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp:11:
/home/lukew/Desktop/serenity-project/serenity/Meta/Lagom/../../AK/HashTable.h: In instantiation of ‘AK::ErrorOr<AK::HashSetResult> AK::HashTable<T, TraitsForT, IsOrdered>::try_set(U&&, AK::HashSetExistingEntryBehavior) [with U = AK::HashMap<JS::Handle<JS::Value>, JS::MarkedValueList, JS::ArrayPrototype::group_by_to_map(JS::VM&, JS::GlobalObject&)::KeyedGroupTraits, true>::Entry; T = AK::HashMap<JS::Handle<JS::Value>, JS::MarkedValueList, JS::ArrayPrototype::group_by_to_map(JS::VM&, JS::GlobalObject&)::KeyedGroupTraits, true>::Entry; TraitsForT = AK::HashMap<JS::Handle<JS::Value>, JS::MarkedValueList, JS::ArrayPrototype::group_by_to_map(JS::VM&, JS::GlobalObject&)::KeyedGroupTraits, true>::EntryTraits; bool IsOrdered = true]’:
/home/lukew/Desktop/serenity-project/serenity/Meta/Lagom/../../AK/HashTable.h:315:16:   required from ‘AK::HashSetResult AK::HashTable<T, TraitsForT, IsOrdered>::set(U&&, AK::HashSetExistingEntryBehavior) [with U = AK::HashMap<JS::Handle<JS::Value>, JS::MarkedValueList, JS::ArrayPrototype::group_by_to_map(JS::VM&, JS::GlobalObject&)::KeyedGroupTraits, true>::Entry; T = AK::HashMap<JS::Handle<JS::Value>, JS::MarkedValueList, JS::ArrayPrototype::group_by_to_map(JS::VM&, JS::GlobalObject&)::KeyedGroupTraits, true>::Entry; TraitsForT = AK::HashMap<JS::Handle<JS::Value>, JS::MarkedValueList, JS::ArrayPrototype::group_by_to_map(JS::VM&, JS::GlobalObject&)::KeyedGroupTraits, true>::EntryTraits; bool IsOrdered = true]’
/home/lukew/Desktop/serenity-project/serenity/Meta/Lagom/../../AK/HashMap.h:52:68:   required from ‘AK::HashSetResult AK::HashMap<K, V, KeyTraits, IsOrdered>::set(const K&, V&&) [with K = JS::Handle<JS::Value>; V = JS::MarkedValueList; KeyTraits = JS::ArrayPrototype::group_by_to_map(JS::VM&, JS::GlobalObject&)::KeyedGroupTraits; bool IsOrdered = true]’
/home/lukew/Desktop/serenity-project/serenity/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp:1695:29:   required from ‘void JS::add_value_to_keyed_group(JS::GlobalObject&, GroupsType&, KeyType, JS::Value) [with GroupsType = AK::HashMap<JS::Handle<JS::Value>, JS::MarkedValueList, JS::ArrayPrototype::group_by_to_map(JS::VM&, JS::GlobalObject&)::KeyedGroupTraits, true>; KeyType = JS::Handle<JS::Value>]’
/home/lukew/Desktop/serenity-project/serenity/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp:1802:82:   required from here
/home/lukew/Desktop/serenity-project/serenity/Meta/Lagom/../../AK/HashTable.h:288:31: error: use of deleted function ‘AK::HashMap<JS::Handle<JS::Value>, JS::MarkedValueList, JS::ArrayPrototype::group_by_to_map(JS::VM&, JS::GlobalObject&)::KeyedGroupTraits, true>::Entry& AK::HashMap<JS::Handle<JS::Value>, JS::MarkedValueList, JS::ArrayPrototype::group_by_to_map(JS::VM&, JS::GlobalObject&)::KeyedGroupTraits, true>::Entry::operator=(AK::HashMap<JS::Handle<JS::Value>, JS::MarkedValueList, JS::ArrayPrototype::group_by_to_map(JS::VM&, JS::GlobalObject&)::KeyedGroupTraits, true>::Entry&&)’
  288 |             (*bucket->slot()) = forward<U>(value);
      |             ~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~
In file included from /home/lukew/Desktop/serenity-project/serenity/Meta/Lagom/../../Userland/Libraries/LibJS/AST.h:12,
                 from /home/lukew/Desktop/serenity-project/serenity/Meta/Lagom/../../Userland/Libraries/LibJS/Runtime/AbstractOperations.h:11,
                 from /home/lukew/Desktop/serenity-project/serenity/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp:14:
/home/lukew/Desktop/serenity-project/serenity/Meta/Lagom/../../AK/HashMap.h:19:12: note: ‘AK::HashMap<JS::Handle<JS::Value>, JS::MarkedValueList, JS::ArrayPrototype::group_by_to_map(JS::VM&, JS::GlobalObject&)::KeyedGroupTraits, true>::Entry& AK::HashMap<JS::Handle<JS::Value>, JS::MarkedValueList, JS::ArrayPrototype::group_by_to_map(JS::VM&, JS::GlobalObject&)::KeyedGroupTraits, true>::Entry::operator=(AK::HashMap<JS::Handle<JS::Value>, JS::MarkedValueList, JS::ArrayPrototype::group_by_to_map(JS::VM&, JS::GlobalObject&)::KeyedGroupTraits, true>::Entry&&)’ is implicitly deleted because the default definition would be ill-formed:
   19 |     struct Entry {
      |            ^~~~~
/home/lukew/Desktop/serenity-project/serenity/Meta/Lagom/../../AK/HashMap.h:19:12: error: use of deleted function ‘JS::MarkedValueList& JS::MarkedValueList::operator=(JS::MarkedValueList&&)’
In file included from /home/lukew/Desktop/serenity-project/serenity/Meta/Lagom/../../Userland/Libraries/LibJS/Runtime/Object.h:17,
                 from /home/lukew/Desktop/serenity-project/serenity/Meta/Lagom/../../Userland/Libraries/LibJS/Runtime/Environment.h:10,
                 from /home/lukew/Desktop/serenity-project/serenity/Meta/Lagom/../../Userland/Libraries/LibJS/Runtime/Reference.h:10,
                 from /home/lukew/Desktop/serenity-project/serenity/Meta/Lagom/../../Userland/Libraries/LibJS/AST.h:23,
                 from /home/lukew/Desktop/serenity-project/serenity/Meta/Lagom/../../Userland/Libraries/LibJS/Runtime/AbstractOperations.h:11,
                 from /home/lukew/Desktop/serenity-project/serenity/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp:14:
/home/lukew/Desktop/serenity-project/serenity/Meta/Lagom/../../Userland/Libraries/LibJS/Runtime/MarkedValueList.h:25:22: note: declared here
   25 |     MarkedValueList& operator=(MarkedValueList&&) = delete;
      |                      ^~~~~~~~
cc1plus: note: unrecognized command-line option ‘-Wno-implicit-const-int-float-conversion’ may have been intended to silence earlier diagnostics
cc1plus: note: unrecognized command-line option ‘-Wno-unknown-warning-option’ may have been intended to silence earlier diagnostics
ninja: build stopped: subcommand failed.

@Lubrsi
Copy link
Member Author

Lubrsi commented Dec 25, 2021

Ah sorry for the misunderstanding, I meant it's not move-assignable :P (Thanks Andreas on Discord!)

@Lubrsi Lubrsi force-pushed the js-array-grouping branch 3 times, most recently from 1f90a9d to bff98d1 Compare January 3, 2022 21:04
Lubrsi and others added 4 commits January 5, 2022 00:28
This allows you to keep an arbitrary JS::Value alive without having to
hook visit_edges somewhere, e.g. by being a NativeFunction that
overrides visit_edges.

For example, this allows you to store JS::Handle<JS::Value> as the key
of a HashMap. This will be used to keep arbitrary Values alive in
the key of a temporary HashMap in Array.prototype.groupByToMap.

Co-authored-by: Ali Mohammad Pur <mpfard@serenityos.org>
This is required to store a MarkedValueList as the value of a HashMap.
@linusg linusg merged commit 0b9ea71 into SerenityOS:master Jan 5, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants