mirror of
https://github.com/LadybirdBrowser/ladybird
synced 2026-04-25 17:25:08 +02:00
AK: Remove public null state from StringView
Now that there are no callers of is_null left, Make default constructed StringViews represent the empty string and disallow null pointers in the public constructors, matching ByteString and String. Keep a private null sentinel only for Optional<StringView>.
This commit is contained in:
committed by
Sam Atkins
parent
7ab9454f51
commit
e47cdc6b63
Notes:
github-actions[bot]
2026-03-31 12:50:02 +00:00
Author: https://github.com/shannonbooth Commit: https://github.com/LadybirdBrowser/ladybird/commit/e47cdc6b63e Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/8694 Reviewed-by: https://github.com/AtkinsSJ ✅ Reviewed-by: https://github.com/gmta ✅
@@ -23,9 +23,6 @@ bool ByteString::operator==(ByteString const& other) const
|
||||
|
||||
bool ByteString::operator==(StringView other) const
|
||||
{
|
||||
if (other.is_null())
|
||||
return is_empty();
|
||||
|
||||
return view() == other;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,9 +29,6 @@ bool matches(StringView str, StringView mask, CaseSensitivity case_sensitivity,
|
||||
match_spans->append({ start, length });
|
||||
};
|
||||
|
||||
if (str.is_null() || mask.is_null())
|
||||
return str.is_null() && mask.is_null();
|
||||
|
||||
if (mask == "*"sv) {
|
||||
record_span(0, str.length());
|
||||
return true;
|
||||
@@ -148,7 +145,7 @@ bool starts_with(StringView str, StringView start, CaseSensitivity case_sensitiv
|
||||
|
||||
bool contains(StringView str, StringView needle, CaseSensitivity case_sensitivity)
|
||||
{
|
||||
if (str.is_null() || needle.is_null() || str.is_empty() || needle.length() > str.length())
|
||||
if (str.is_empty() || needle.length() > str.length())
|
||||
return false;
|
||||
if (needle.is_empty())
|
||||
return true;
|
||||
|
||||
@@ -41,6 +41,7 @@ StringView::StringView(ByteBuffer const& buffer)
|
||||
: m_characters((char const*)buffer.data())
|
||||
, m_length(buffer.size())
|
||||
{
|
||||
VERIFY(buffer.data() != nullptr);
|
||||
}
|
||||
|
||||
Vector<StringView> StringView::split_view(char const separator, SplitBehavior split_behavior) const
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <AK/Assertions.h>
|
||||
#include <AK/Badge.h>
|
||||
#include <AK/Checked.h>
|
||||
#include <AK/Concepts.h>
|
||||
#include <AK/Forward.h>
|
||||
@@ -28,6 +29,7 @@ public:
|
||||
, m_length(length)
|
||||
{
|
||||
if !consteval {
|
||||
VERIFY(characters != nullptr);
|
||||
VERIFY(!Checked<uintptr_t>::addition_would_overflow(reinterpret_cast<uintptr_t>(characters), length));
|
||||
}
|
||||
}
|
||||
@@ -36,6 +38,7 @@ public:
|
||||
: m_characters(reinterpret_cast<char const*>(characters))
|
||||
, m_length(length)
|
||||
{
|
||||
VERIFY(characters != nullptr);
|
||||
VERIFY(!Checked<uintptr_t>::addition_would_overflow(reinterpret_cast<uintptr_t>(characters), length));
|
||||
}
|
||||
|
||||
@@ -43,6 +46,7 @@ public:
|
||||
: m_characters(reinterpret_cast<char const*>(bytes.data()))
|
||||
, m_length(bytes.size())
|
||||
{
|
||||
VERIFY(bytes.data() != nullptr);
|
||||
}
|
||||
|
||||
StringView(LIFETIME_BOUND ByteBuffer const&);
|
||||
@@ -58,7 +62,6 @@ public:
|
||||
template<OneOf<String, FlyString, ByteString, ByteBuffer> StringType>
|
||||
StringView& operator=(StringType&&) = delete;
|
||||
|
||||
[[nodiscard]] constexpr bool is_null() const { return m_characters == nullptr; }
|
||||
[[nodiscard]] constexpr bool is_empty() const { return m_length == 0; }
|
||||
|
||||
[[nodiscard]] constexpr char const* characters_without_null_termination() const { return m_characters; }
|
||||
@@ -253,8 +256,6 @@ public:
|
||||
|
||||
constexpr bool operator==(char const* cstring) const
|
||||
{
|
||||
if (is_null())
|
||||
return cstring == nullptr;
|
||||
if (!cstring)
|
||||
return false;
|
||||
// NOTE: `m_characters` is not guaranteed to be null-terminated, but `cstring` is.
|
||||
@@ -280,12 +281,6 @@ public:
|
||||
if (m_length == 0 && other.m_length == 0)
|
||||
return 0;
|
||||
|
||||
if (m_characters == nullptr)
|
||||
return other.m_characters ? -1 : 0;
|
||||
|
||||
if (other.m_characters == nullptr)
|
||||
return 1;
|
||||
|
||||
size_t rlen = min(m_length, other.m_length);
|
||||
int c = __builtin_memcmp(m_characters, other.m_characters, rlen);
|
||||
if (c == 0) {
|
||||
@@ -358,10 +353,30 @@ public:
|
||||
|
||||
private:
|
||||
friend class ByteString;
|
||||
char const* m_characters { nullptr };
|
||||
friend struct SentinelOptionalTraits<StringView>;
|
||||
|
||||
explicit constexpr StringView(Badge<SentinelOptionalTraits<StringView>>, nullptr_t)
|
||||
: m_characters(nullptr)
|
||||
, m_length(0)
|
||||
{
|
||||
}
|
||||
|
||||
char const* m_characters { "" };
|
||||
size_t m_length { 0 };
|
||||
};
|
||||
|
||||
template<>
|
||||
struct SentinelOptionalTraits<StringView> {
|
||||
static constexpr StringView sentinel_value() { return StringView(Badge<SentinelOptionalTraits<StringView>> {}, nullptr); }
|
||||
static constexpr bool is_sentinel(StringView const& value) { return value.m_characters == nullptr; }
|
||||
};
|
||||
|
||||
template<>
|
||||
class Optional<StringView> : public SentinelOptional<StringView> {
|
||||
public:
|
||||
using SentinelOptional::SentinelOptional;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct Traits<StringView> : public DefaultTraits<StringView> {
|
||||
using PeekType = StringView;
|
||||
|
||||
@@ -110,7 +110,6 @@ public:
|
||||
Utf8View unicode_substring_view(size_t code_point_offset) const { return unicode_substring_view(code_point_offset, length() - code_point_offset); }
|
||||
|
||||
bool is_empty() const { return m_string.is_empty(); }
|
||||
bool is_null() const { return m_string.is_null(); }
|
||||
bool starts_with(Utf8View const&) const;
|
||||
bool contains(u32) const;
|
||||
bool contains_any_of(ReadonlySpan<u32>) const;
|
||||
|
||||
@@ -29,19 +29,6 @@ TEST_CASE(hash_compatible)
|
||||
static_assert(AK::Concepts::HashCompatible<ByteBuffer, StringView>);
|
||||
}
|
||||
|
||||
TEST_CASE(matches_null)
|
||||
{
|
||||
EXPECT(AK::StringUtils::matches(StringView(), StringView()));
|
||||
|
||||
EXPECT(!AK::StringUtils::matches(StringView(), ""sv));
|
||||
EXPECT(!AK::StringUtils::matches(StringView(), "*"sv));
|
||||
EXPECT(!AK::StringUtils::matches(StringView(), "?"sv));
|
||||
EXPECT(!AK::StringUtils::matches(StringView(), "a"sv));
|
||||
|
||||
EXPECT(!AK::StringUtils::matches(""sv, StringView()));
|
||||
EXPECT(!AK::StringUtils::matches("a"sv, StringView()));
|
||||
}
|
||||
|
||||
TEST_CASE(matches_empty)
|
||||
{
|
||||
EXPECT(AK::StringUtils::matches(""sv, ""sv));
|
||||
|
||||
@@ -11,9 +11,8 @@
|
||||
|
||||
TEST_CASE(construct_empty)
|
||||
{
|
||||
EXPECT(StringView().is_null());
|
||||
EXPECT(StringView().is_empty());
|
||||
EXPECT(!StringView().characters_without_null_termination());
|
||||
EXPECT_EQ(StringView().characters_without_null_termination(), "");
|
||||
EXPECT_EQ(StringView().length(), 0u);
|
||||
}
|
||||
|
||||
@@ -21,12 +20,29 @@ TEST_CASE(view_literal)
|
||||
{
|
||||
char const* truth = "cats rule dogs drool";
|
||||
StringView view { truth, strlen(truth) };
|
||||
EXPECT_EQ(view.is_null(), false);
|
||||
EXPECT_EQ(view.characters_without_null_termination(), truth);
|
||||
EXPECT_EQ(view, view);
|
||||
EXPECT_EQ(view, truth);
|
||||
}
|
||||
|
||||
TEST_CASE(optional_string_view)
|
||||
{
|
||||
Optional<StringView> missing;
|
||||
EXPECT(!missing.has_value());
|
||||
|
||||
Optional<StringView> empty = ""sv;
|
||||
EXPECT(empty.has_value());
|
||||
EXPECT(empty->is_empty());
|
||||
EXPECT_EQ(empty.value(), ""sv);
|
||||
|
||||
Optional<StringView> value = "foo"sv;
|
||||
EXPECT(value.has_value());
|
||||
EXPECT_EQ(value.value(), "foo"sv);
|
||||
|
||||
value.clear();
|
||||
EXPECT(!value.has_value());
|
||||
}
|
||||
|
||||
TEST_CASE(compare_views)
|
||||
{
|
||||
ByteString foo1 = "foo";
|
||||
|
||||
Reference in New Issue
Block a user