/* * Copyright (c) 2020, Andreas Kling * Copyright (c) 2025, Aliaksandr Kalenik * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once #include #include #include #include namespace JS { class PropertyKey { public: enum class StringMayBeNumber { Yes, No, }; static ThrowCompletionOr from_value(VM& vm, Value value) { VERIFY(!value.is_special_empty_value()); if (value.is_symbol()) return PropertyKey { value.as_symbol() }; if (value.is_integral_number() && value.as_double() >= 0 && value.as_double() < NumericLimits::max()) return static_cast(value.as_double()); return TRY(value.to_string(vm)); } static constexpr uintptr_t NORMAL_STRING_FLAG = 0; static constexpr uintptr_t SHORT_STRING_FLAG = 1; static constexpr uintptr_t SYMBOL_FLAG = 2; static constexpr uintptr_t NUMBER_FLAG = 3; bool is_string() const { return (m_bits & 3) == NORMAL_STRING_FLAG || (m_bits & 3) == SHORT_STRING_FLAG; } bool is_number() const { return (m_bits & 3) == NUMBER_FLAG; } bool is_symbol() const { return (m_bits & 3) == SYMBOL_FLAG; } PropertyKey() = delete; PropertyKey(PropertyKey const& other) { if (other.is_string()) new (&m_string) FlyString(other.m_string); else m_bits = other.m_bits; } PropertyKey(PropertyKey&& other) noexcept { if (other.is_string()) new (&m_string) FlyString(move(other.m_string)); else m_bits = exchange(other.m_bits, 0); } template PropertyKey(T index) { // FIXME: Replace this with requires(IsUnsigned)? // Needs changes in various places using `int` (but not actually being in the negative range) VERIFY(index >= 0); if constexpr (NumericLimits::max() >= NumericLimits::max()) { if (index >= NumericLimits::max()) { new (&m_string) FlyString { String::number(index) }; return; } } m_number = static_cast(index) << 2 | NUMBER_FLAG; } PropertyKey(FlyString string, StringMayBeNumber string_may_be_number = StringMayBeNumber::Yes) { if (string_may_be_number == StringMayBeNumber::Yes) { auto view = string.bytes_as_string_view(); if (!view.is_empty() && !(view[0] == '0' && view.length() > 1)) { auto property_index = view.to_number(TrimWhitespace::No); if (property_index.has_value() && property_index.value() < NumericLimits::max()) { m_number = static_cast(property_index.release_value()) << 2 | NUMBER_FLAG; return; } } } new (&m_string) FlyString(move(string)); } PropertyKey(String const& string) : PropertyKey(FlyString(string)) { } PropertyKey(GC::Ref symbol) { m_bits = reinterpret_cast(symbol.ptr()) | SYMBOL_FLAG; } PropertyKey& operator=(PropertyKey const& other) { if (this != &other) { if (is_string()) m_string.~FlyString(); new (this) PropertyKey(other); } return *this; } PropertyKey& operator=(PropertyKey&& other) noexcept { if (this != &other) { if (is_string()) m_string.~FlyString(); new (this) PropertyKey(move(other)); } return *this; } ~PropertyKey() { if (is_string()) m_string.~FlyString(); } u32 as_number() const { VERIFY(is_number()); return m_number >> 2; } FlyString const& as_string() const { VERIFY(is_string()); return m_string; } Symbol const* as_symbol() const { VERIFY(is_symbol()); return reinterpret_cast(m_bits & ~3ULL); } Value to_value(VM& vm) const { if (is_string()) return Value { PrimitiveString::create(vm, as_string()) }; if (is_symbol()) return Value { as_symbol() }; return Value { PrimitiveString::create(vm, String::number(as_number())) }; } String to_string() const { if (is_string()) return as_string().to_string(); if (is_symbol()) return MUST(as_symbol()->descriptive_string()); return String::number(as_number()); } void visit_edges(Cell::Visitor& visitor) const { if (is_symbol()) visitor.visit(const_cast(as_symbol())); } bool operator==(PropertyKey const& other) const { if (is_string()) return other.is_string() && m_string == other.m_string; if (is_symbol()) return other.is_symbol() && as_symbol() == other.as_symbol(); if (other.is_number()) return as_number() == other.as_number(); return false; } private: friend Traits; union { FlyString m_string; u64 m_number; Symbol const* m_symbol; uintptr_t m_bits; }; }; static_assert(sizeof(PropertyKey) == sizeof(uintptr_t)); } namespace AK { template<> struct Traits : public DefaultTraits { static unsigned hash(JS::PropertyKey const& name) { if (name.is_string()) return name.as_string().hash(); if (name.is_symbol()) return ptr_hash(name.as_symbol()); if (name.is_number()) return int_hash(name.as_number()); VERIFY_NOT_REACHED(); } static bool equals(JS::PropertyKey const& a, JS::PropertyKey const& b) { if (a.is_string()) return b.is_string() && a.as_string() == b.as_string(); if (a.is_symbol()) return b.is_symbol() && a.as_symbol() == b.as_symbol(); if (a.is_number()) return b.is_number() && a.as_number() == b.as_number(); VERIFY_NOT_REACHED(); } }; template<> struct Formatter : Formatter { ErrorOr format(FormatBuilder& builder, JS::PropertyKey const& property_key) { if (property_key.is_number()) return builder.put_u64(property_key.as_number()); return builder.put_string(property_key.to_string()); } }; }