diff --git a/AK/ByteString.cpp b/AK/ByteString.cpp index 0e8b30636cd..0319293d877 100644 --- a/AK/ByteString.cpp +++ b/AK/ByteString.cpp @@ -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; } diff --git a/AK/StringUtils.cpp b/AK/StringUtils.cpp index f2b47817cf2..a8edf9a870e 100644 --- a/AK/StringUtils.cpp +++ b/AK/StringUtils.cpp @@ -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; diff --git a/AK/StringView.cpp b/AK/StringView.cpp index 0e68c003909..0e93b444a09 100644 --- a/AK/StringView.cpp +++ b/AK/StringView.cpp @@ -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::split_view(char const separator, SplitBehavior split_behavior) const diff --git a/AK/StringView.h b/AK/StringView.h index 4590905f5a4..d3c57d7f40c 100644 --- a/AK/StringView.h +++ b/AK/StringView.h @@ -7,6 +7,7 @@ #pragma once #include +#include #include #include #include @@ -28,6 +29,7 @@ public: , m_length(length) { if !consteval { + VERIFY(characters != nullptr); VERIFY(!Checked::addition_would_overflow(reinterpret_cast(characters), length)); } } @@ -36,6 +38,7 @@ public: : m_characters(reinterpret_cast(characters)) , m_length(length) { + VERIFY(characters != nullptr); VERIFY(!Checked::addition_would_overflow(reinterpret_cast(characters), length)); } @@ -43,6 +46,7 @@ public: : m_characters(reinterpret_cast(bytes.data())) , m_length(bytes.size()) { + VERIFY(bytes.data() != nullptr); } StringView(LIFETIME_BOUND ByteBuffer const&); @@ -58,7 +62,6 @@ public: template 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; + + explicit constexpr StringView(Badge>, nullptr_t) + : m_characters(nullptr) + , m_length(0) + { + } + + char const* m_characters { "" }; size_t m_length { 0 }; }; +template<> +struct SentinelOptionalTraits { + static constexpr StringView sentinel_value() { return StringView(Badge> {}, nullptr); } + static constexpr bool is_sentinel(StringView const& value) { return value.m_characters == nullptr; } +}; + +template<> +class Optional : public SentinelOptional { +public: + using SentinelOptional::SentinelOptional; +}; + template<> struct Traits : public DefaultTraits { using PeekType = StringView; diff --git a/AK/Utf8View.h b/AK/Utf8View.h index cd7442804c0..cb7eaee2a85 100644 --- a/AK/Utf8View.h +++ b/AK/Utf8View.h @@ -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) const; diff --git a/Tests/AK/TestStringUtils.cpp b/Tests/AK/TestStringUtils.cpp index 93caf5b9575..807acf0ed87 100644 --- a/Tests/AK/TestStringUtils.cpp +++ b/Tests/AK/TestStringUtils.cpp @@ -29,19 +29,6 @@ TEST_CASE(hash_compatible) static_assert(AK::Concepts::HashCompatible); } -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)); diff --git a/Tests/AK/TestStringView.cpp b/Tests/AK/TestStringView.cpp index f9744ab7ed4..028aa898503 100644 --- a/Tests/AK/TestStringView.cpp +++ b/Tests/AK/TestStringView.cpp @@ -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 missing; + EXPECT(!missing.has_value()); + + Optional empty = ""sv; + EXPECT(empty.has_value()); + EXPECT(empty->is_empty()); + EXPECT_EQ(empty.value(), ""sv); + + Optional 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";