/* * Copyright (c) 2020, Andreas Kling * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once #include #include #include #include #include namespace JS { class PropertyKey { AK_MAKE_DEFAULT_COPYABLE(PropertyKey); AK_MAKE_DEFAULT_MOVABLE(PropertyKey); public: enum class StringMayBeNumber { Yes, No, }; static ThrowCompletionOr from_value(VM& vm, Value value) { VERIFY(!value.is_empty()); 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_byte_string(vm)); } PropertyKey() = delete; template PropertyKey(T index) : m_data(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()) { m_data = DeprecatedFlyString { ByteString::number(index) }; return; } } } PropertyKey(DeprecatedFlyString string, StringMayBeNumber string_may_be_number = StringMayBeNumber::Yes) : m_data { try_coerce_into_number(move(string), string_may_be_number) } { } PropertyKey(ByteString const& string) : PropertyKey(DeprecatedFlyString(string)) { } template PropertyKey(char const (&chars)[N]) : PropertyKey(DeprecatedFlyString(chars)) { } PropertyKey(GC::Ref symbol) : m_data { symbol } { } PropertyKey(StringOrSymbol const& string_or_symbol) : m_data { string_or_symbol.is_string() ? Variant, u32> { string_or_symbol.as_string() } : Variant, u32> { const_cast(string_or_symbol.as_symbol()) } } { } bool is_number() const { return m_data.has(); } bool is_string() const { return m_data.has(); } bool is_symbol() const { return m_data.has>(); } u32 as_number() const { return m_data.get(); } DeprecatedFlyString const& as_string() const { return m_data.get(); } Symbol const* as_symbol() const { return m_data.get>(); } ByteString to_string() const { VERIFY(!is_symbol()); if (is_string()) return as_string(); return ByteString::number(as_number()); } StringOrSymbol to_string_or_symbol() const { VERIFY(!is_number()); if (is_string()) return StringOrSymbol(as_string()); return StringOrSymbol(as_symbol()); } private: friend Traits; static Variant try_coerce_into_number(DeprecatedFlyString string, StringMayBeNumber string_may_be_number) { if (string_may_be_number != StringMayBeNumber::Yes) return string; if (string.is_empty()) return string; if (string.starts_with("0"sv) && string.length() != 1) return string; auto property_index = string.to_number(TrimWhitespace::No); if (!property_index.has_value() || property_index.value() >= NumericLimits::max()) return string; return property_index.release_value(); } Variant> m_data; }; } namespace AK { template<> struct Traits : public DefaultTraits { static unsigned hash(JS::PropertyKey const& name) { return name.m_data.visit( [](DeprecatedFlyString const& string) { return string.hash(); }, [](GC::Root const& symbol) { return ptr_hash(symbol.ptr()); }, [](u32 const& number) { return int_hash(number); }); } static bool equals(JS::PropertyKey const& a, JS::PropertyKey const& b) { return a.m_data == b.m_data; } }; 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_or_symbol().to_display_string()); } }; }