diff --git a/Userland/Libraries/LibJS/CMakeLists.txt b/Userland/Libraries/LibJS/CMakeLists.txt index 87c1d13182e..82bb1dbfe99 100644 --- a/Userland/Libraries/LibJS/CMakeLists.txt +++ b/Userland/Libraries/LibJS/CMakeLists.txt @@ -148,6 +148,7 @@ set(SOURCES Runtime/IteratorPrototype.cpp Runtime/JSONObject.cpp Runtime/JobCallback.cpp + Runtime/KeyedCollections.cpp Runtime/Map.cpp Runtime/MapConstructor.cpp Runtime/MapIterator.cpp diff --git a/Userland/Libraries/LibJS/Runtime/AbstractOperations.h b/Userland/Libraries/LibJS/Runtime/AbstractOperations.h index f4bc039fab7..38be85f9057 100644 --- a/Userland/Libraries/LibJS/Runtime/AbstractOperations.h +++ b/Userland/Libraries/LibJS/Runtime/AbstractOperations.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -276,9 +277,8 @@ ThrowCompletionOr group_by(VM& vm, Value items, Value callback_funct // i. Assert: keyCoercion is zero. static_assert(IsSame); - // ii. If key is -0𝔽, set key to +0𝔽. - if (key.value().is_negative_zero()) - key = Value(0); + // ii. Set key to CanonicalizeKeyedCollectionKey(key). + key = canonicalize_keyed_collection_key(key.value()); add_value_to_keyed_group(vm, groups, make_handle(key.release_value()), value); } diff --git a/Userland/Libraries/LibJS/Runtime/KeyedCollections.cpp b/Userland/Libraries/LibJS/Runtime/KeyedCollections.cpp new file mode 100644 index 00000000000..82f84d4e4fb --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/KeyedCollections.cpp @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2024, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +namespace JS { + +// 24.5.1 CanonicalizeKeyedCollectionKey ( key ), https://tc39.es/ecma262/#sec-canonicalizekeyedcollectionkey +Value canonicalize_keyed_collection_key(Value key) +{ + // 1. If key is -0𝔽, return +0𝔽. + if (key.is_negative_zero()) + return Value { 0.0 }; + + // 2. Return key. + return key; +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/KeyedCollections.h b/Userland/Libraries/LibJS/Runtime/KeyedCollections.h new file mode 100644 index 00000000000..f7f6b11d18c --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/KeyedCollections.h @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2024, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace JS { + +Value canonicalize_keyed_collection_key(Value); + +} diff --git a/Userland/Libraries/LibJS/Runtime/MapPrototype.cpp b/Userland/Libraries/LibJS/Runtime/MapPrototype.cpp index 43a6fbc1a88..f0be4140f0e 100644 --- a/Userland/Libraries/LibJS/Runtime/MapPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/MapPrototype.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -66,11 +67,14 @@ JS_DEFINE_NATIVE_FUNCTION(MapPrototype::delete_) // 2. Perform ? RequireInternalSlot(M, [[MapData]]). auto map = TRY(typed_this_object(vm)); + // 3. Set key to CanonicalizeKeyedCollectionKey(key). + key = canonicalize_keyed_collection_key(key); + // 3. For each Record { [[Key]], [[Value]] } p of M.[[MapData]], do - // a. If p.[[Key]] is not empty and SameValueZero(p.[[Key]], key) is true, then - // i. Set p.[[Key]] to empty. - // ii. Set p.[[Value]] to empty. - // iii. Return true. + // a. If p.[[Key]] is not empty and SameValue(p.[[Key]], key) is true, then + // i. Set p.[[Key]] to empty. + // ii. Set p.[[Value]] to empty. + // iii. Return true. // 4. Return false. return Value(map->map_remove(key)); } @@ -131,11 +135,13 @@ JS_DEFINE_NATIVE_FUNCTION(MapPrototype::get) // 2. Perform ? RequireInternalSlot(M, [[MapData]]). auto map = TRY(typed_this_object(vm)); + // 3. Set key to CanonicalizeKeyedCollectionKey(key). + key = canonicalize_keyed_collection_key(key); + // 3. For each Record { [[Key]], [[Value]] } p of M.[[MapData]], do - // a. If p.[[Key]] is not empty and SameValueZero(p.[[Key]], key) is true, return p.[[Value]]. - auto result = map->map_get(key); - if (result.has_value()) - return result.value(); + // a. If p.[[Key]] is not empty and SameValue(p.[[Key]], key) is true, return p.[[Value]]. + if (auto result = map->map_get(key); result.has_value()) + return result.release_value(); // 4. Return undefined. return js_undefined(); @@ -150,8 +156,11 @@ JS_DEFINE_NATIVE_FUNCTION(MapPrototype::has) // 2. Perform ? RequireInternalSlot(M, [[MapData]]). auto map = TRY(typed_this_object(vm)); + // 3. Set key to CanonicalizeKeyedCollectionKey(key). + key = canonicalize_keyed_collection_key(key); + // 3. For each Record { [[Key]], [[Value]] } p of M.[[MapData]], do - // a. If p.[[Key]] is not empty and SameValueZero(p.[[Key]], key) is true, return true. + // a. If p.[[Key]] is not empty and SameValue(p.[[Key]], key) is true, return true. // 4. Return false. return map->map_has(key); } @@ -178,14 +187,13 @@ JS_DEFINE_NATIVE_FUNCTION(MapPrototype::set) // 2. Perform ? RequireInternalSlot(M, [[MapData]]). auto map = TRY(typed_this_object(vm)); - // 4. If key is -0𝔽, set key to +0𝔽. - if (key.is_negative_zero()) - key = Value(0); + // 3. Set key to CanonicalizeKeyedCollectionKey(key). + key = canonicalize_keyed_collection_key(key); - // 3. For each Record { [[Key]], [[Value]] } p of M.[[MapData]], do - // a. If p.[[Key]] is not empty and SameValueZero(p.[[Key]], key) is true, then - // i. Set p.[[Value]] to value. - // ii. Return M. + // 4. For each Record { [[Key]], [[Value]] } p of M.[[MapData]], do + // a. If p.[[Key]] is not empty and SameValue(p.[[Key]], key) is true, then + // i. Set p.[[Value]] to value. + // ii. Return M. // 5. Let p be the Record { [[Key]]: key, [[Value]]: value }. // 6. Append p to M.[[MapData]]. map->map_set(key, value); diff --git a/Userland/Libraries/LibJS/Runtime/SetPrototype.cpp b/Userland/Libraries/LibJS/Runtime/SetPrototype.cpp index 844ad69cf31..0cdbabb13b0 100644 --- a/Userland/Libraries/LibJS/Runtime/SetPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/SetPrototype.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -62,12 +63,11 @@ JS_DEFINE_NATIVE_FUNCTION(SetPrototype::add) // 2. Perform ? RequireInternalSlot(S, [[SetData]]). auto set = TRY(typed_this_object(vm)); - // 4. If value is -0𝔽, set value to +0𝔽. - if (value.is_negative_zero()) - value = Value(0); + // 3. Set value to CanonicalizeKeyedCollectionKey(value). + value = canonicalize_keyed_collection_key(value); - // 3. For each element e of S.[[SetData]], do - // a. If e is not empty and SameValueZero(e, value) is true, then + // 4. For each element e of S.[[SetData]], do + // a. If e is not empty and SameValue(e, value) is true, then // i. Return S. // 5. Append value to S.[[SetData]]. set->set_add(value); @@ -100,11 +100,14 @@ JS_DEFINE_NATIVE_FUNCTION(SetPrototype::delete_) // 2. Perform ? RequireInternalSlot(S, [[SetData]]). auto set = TRY(typed_this_object(vm)); - // 3. For each element e of S.[[SetData]], do - // a. If e is not empty and SameValueZero(e, value) is true, then + // 3. Set value to CanonicalizeKeyedCollectionKey(value). + value = canonicalize_keyed_collection_key(value); + + // 4. For each element e of S.[[SetData]], do + // a. If e is not empty and SameValue(e, value) is true, then // i. Replace the element of S.[[SetData]] whose value is e with an element whose value is empty. // ii. Return true. - // 4. Return false. + // 5. Return false. return Value(set->set_remove(value)); } @@ -165,9 +168,12 @@ JS_DEFINE_NATIVE_FUNCTION(SetPrototype::has) // 2. Perform ? RequireInternalSlot(S, [[SetData]]). auto set = TRY(typed_this_object(vm)); - // 3. For each element e of S.[[SetData]], do - // a. If e is not empty and SameValueZero(e, value) is true, return true. - // 4. Return false. + // 3. Set value to CanonicalizeKeyedCollectionKey(value). + value = canonicalize_keyed_collection_key(value); + + // 4. For each element e of S.[[SetData]], do + // a. If e is not empty and SameValue(e, value) is true, return true. + // 5. Return false. return Value(set->set_has(value)); } diff --git a/Userland/Libraries/LibJS/Runtime/ValueTraits.h b/Userland/Libraries/LibJS/Runtime/ValueTraits.h index 471687de055..fb7e3e4bbc0 100644 --- a/Userland/Libraries/LibJS/Runtime/ValueTraits.h +++ b/Userland/Libraries/LibJS/Runtime/ValueTraits.h @@ -25,21 +25,20 @@ struct ValueTraits : public Traits { if (value.is_bigint()) return value.as_bigint().big_integer().hash(); - if (value.is_negative_zero()) - value = Value(0); // In the IEEE 754 standard a NaN value is encoded as any value from 0x7ff0000000000001 to 0x7fffffffffffffff, // with the least significant bits (referred to as the 'payload') carrying some kind of diagnostic information // indicating the source of the NaN. Since ECMA262 does not differentiate between different kinds of NaN values, // Sets and Maps must not differentiate between them either. // This is achieved by replacing any NaN value by a canonical qNaN. - else if (value.is_nan()) + if (value.is_nan()) value = js_nan(); return u64_hash(value.encoded()); // FIXME: Is this the best way to hash pointers, doubles & ints? } + static bool equals(Value const a, Value const b) { - return same_value_zero(a, b); + return same_value(a, b); } };