Repository: LadybirdBrowser/ladybird
File: AK/Utf16View.h (C/C++)

Code before:

    [[nodiscard]] constexpr size_t length_in_code_units()
    {
        if (has_ascii_storage())
            return 1;
        return UnicodeUtils::code_unit_length_for_code_point(**this);
    }

    [[nodiscard]] constexpr bool at_start() const
    {
        if (has_ascii_storage()) {
            return m_iterator.ascii == m_start.ascii;
        } else {
            return m_iterator.utf16 == m_start.utf16;
        }
    }

    [[nodiscard]] constexpr bool done() const
    {
        return this->remaining_code_units() <= 0;
    }

private:
    friend Utf16View;

    constexpr Utf16CodePointIterator(char const* iterator, size_t length, char const* start = nullptr)
        : m_iterator { .ascii = iterator }
        , m_remaining_code_units(length)
        , m_start { .ascii = start ? start : iterator }
    {
    }

    constexpr Utf16CodePointIterator(char16_t const* iterator, size_t length, char16_t const* start = nullptr)
        : m_iterator { .utf16 = iterator }
        , m_remaining_code_units(length)
        , m_start { .utf16 = start ? start : iterator }
    {
        m_remaining_code_units |= 1uz << Detail::UTF16_FLAG;
    }

    constexpr bool has_ascii_storage() const { return m_remaining_code_units >> Detail::UTF16_FLAG == 0; }
    constexpr size_t remaining_code_units() const { return m_remaining_code_units & ~(1uz << Detail::UTF16_FLAG); }

    union {
        char const* ascii;
        char16_t const* utf16;
    } m_iterator { .ascii = nullptr };

    // Just like Utf16StringData, we store whether this string has ASCII or UTF-16 storage by setting the most
    // significant bit of m_remaining_code_units for UTF-16 storage.
    size_t m_remaining_code_units { 0 };

Review comment (bug):
Why are we defaulting to `nullptr`? Aren't all callers passing this pointer in?

Code after:

    union {
        char const* ascii;
        char16_t const* utf16;
    } m_iterator { .ascii = nullptr };

    // Just like Utf16StringData, we store whether this string has ASCII or UTF-16 storage by setting the most
    // significant bit of m_remaining_code_units for UTF-16 storage.
    size_t m_remaining_code_units { 0 };
};

class Utf16View {
public:
    using Iterator = Utf16CodePointIterator;

    Utf16View() = default;
    ~Utf16View() = default;

    constexpr Utf16View(char16_t const* string, size_t length_in_code_units)
        : m_string { .utf16 = string }
        , m_length_in_code_units(length_in_code_units)
    {
        m_length_in_code_units |= 1uz << Detail::UTF16_FLAG;
    }

    consteval Utf16View(StringView string)
        : m_string { .ascii = string.characters_without_null_termination() }
        , m_length_in_code_units(string.length())
    {
        VERIFY(all_of(string, AK::is_ascii));
    }

    ErrorOr<String> to_utf8(AllowLonelySurrogates = AllowLonelySurrogates::Yes) const;
    ErrorOr<ByteString> to_byte_string(AllowLonelySurrogates = AllowLonelySurrogates::Yes) const;

    ALWAYS_INLINE String to_utf8_but_should_be_ported_to_utf16(AllowLonelySurrogates allow_lonely_surrogates = AllowLonelySurrogates::Yes) const
    {
        return MUST(to_utf8(allow_lonely_surrogates));
    }

    Utf16String to_ascii_lowercase() const;
    Utf16String to_ascii_uppercase() const;
    Utf16String to_ascii_titlecase() const;

    [[nodiscard]] constexpr bool has_ascii_storage() const { return m_length_in_code_units >> Detail::UTF16_FLAG == 0; }

    [[nodiscard]] ALWAYS_INLINE ReadonlyBytes bytes() const
    {
        VERIFY(has_ascii_storage());
        return { m_string.ascii, length_in_code_units() };
    }