LibJS: Introduce the CanonicalizeKeyedCollectionKey AO

This is an editorial change in the ECMA-262 spec. See:
https://github.com/tc39/ecma262/commit/30257dd
This commit is contained in:
Timothy Flynn 2024-07-09 15:24:28 -04:00 committed by Andreas Kling
parent d4a7cfb68f
commit 55b4ef7915
Notes: sideshowbarker 2024-07-17 11:33:34 +09:00
7 changed files with 85 additions and 34 deletions

View file

@ -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

View file

@ -15,6 +15,7 @@
#include <LibJS/Runtime/FunctionObject.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/Iterator.h>
#include <LibJS/Runtime/KeyedCollections.h>
#include <LibJS/Runtime/PrivateEnvironment.h>
#include <LibJS/Runtime/Value.h>
@ -276,9 +277,8 @@ ThrowCompletionOr<GroupsType> group_by(VM& vm, Value items, Value callback_funct
// i. Assert: keyCoercion is zero.
static_assert(IsSame<KeyType, void>);
// 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);
}

View file

@ -0,0 +1,22 @@
/*
* Copyright (c) 2024, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/KeyedCollections.h>
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;
}
}

View file

@ -0,0 +1,15 @@
/*
* Copyright (c) 2024, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibJS/Runtime/Value.h>
namespace JS {
Value canonicalize_keyed_collection_key(Value);
}

View file

@ -7,6 +7,7 @@
#include <AK/HashMap.h>
#include <AK/TypeCasts.h>
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/KeyedCollections.h>
#include <LibJS/Runtime/MapIterator.h>
#include <LibJS/Runtime/MapPrototype.h>
@ -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);

View file

@ -9,6 +9,7 @@
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/FunctionObject.h>
#include <LibJS/Runtime/Iterator.h>
#include <LibJS/Runtime/KeyedCollections.h>
#include <LibJS/Runtime/SetIterator.h>
#include <LibJS/Runtime/SetPrototype.h>
#include <LibJS/Runtime/ValueInlines.h>
@ -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));
}

View file

@ -25,21 +25,20 @@ struct ValueTraits : public Traits<Value> {
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);
}
};