Browse Source

AK: Support creating known short string literals at compile time

In cases where we know a string literal will fit in the short string
storage, we can do so at compile time without needing to handle error
propagation. If the provided string literal is too long, a compilation
error will be emitted due to the failed VERIFY statement being a non-
constant expression.
Timothy Flynn 2 years ago
parent
commit
d48266a420
3 changed files with 45 additions and 16 deletions
  1. 1 6
      AK/String.cpp
  2. 26 2
      AK/String.h
  3. 18 8
      Tests/AK/TestString.cpp

+ 1 - 6
AK/String.cpp

@@ -169,11 +169,6 @@ String::String(NonnullRefPtr<Detail::StringData> data)
 {
 }
 
-String::String(ShortString short_string)
-    : m_short_string(short_string)
-{
-}
-
 String::String(String const& other)
     : m_data(other.m_data)
 {
@@ -207,7 +202,7 @@ String& String::operator=(String const& other)
     return *this;
 }
 
-String::~String()
+void String::destroy_string()
 {
     if (!is_short_string())
         m_data->unref();

+ 26 - 2
AK/String.h

@@ -37,7 +37,11 @@ public:
     String& operator=(String&&);
     String& operator=(String const&);
 
-    ~String();
+    constexpr ~String()
+    {
+        if (!is_constant_evaluated())
+            destroy_string();
+    }
 
     // Creates an empty (zero-length) String.
     String();
@@ -45,6 +49,20 @@ public:
     // Creates a new String from a sequence of UTF-8 encoded code points.
     static ErrorOr<String> from_utf8(StringView);
 
+    // Creates a new String from a short sequence of UTF-8 encoded code points. If the provided string
+    // does not fit in the short string storage, a compilation error will be emitted.
+    static consteval String from_utf8_short_string(StringView string)
+    {
+        VERIFY(string.length() <= MAX_SHORT_STRING_BYTE_COUNT);
+
+        ShortString short_string;
+        for (size_t i = 0; i < string.length(); ++i)
+            short_string.storage[i] = string.characters_without_null_termination()[i];
+        short_string.byte_count_and_short_string_flag = (string.length() << 1) | SHORT_STRING_FLAG;
+
+        return String { short_string };
+    }
+
     // Creates a new String by case-transforming this String. Using these methods require linking LibUnicode into your application.
     ErrorOr<String> to_lowercase(Optional<StringView> const& locale = {}) const;
     ErrorOr<String> to_uppercase(Optional<StringView> const& locale = {}) const;
@@ -160,7 +178,13 @@ private:
     };
 
     explicit String(NonnullRefPtr<Detail::StringData>);
-    explicit String(ShortString);
+
+    explicit constexpr String(ShortString short_string)
+        : m_short_string(short_string)
+    {
+    }
+
+    void destroy_string();
 
     union {
         ShortString m_short_string;

+ 18 - 8
Tests/AK/TestString.cpp

@@ -34,15 +34,25 @@ TEST_CASE(move_assignment)
 TEST_CASE(short_strings)
 {
 #ifdef AK_ARCH_64_BIT
-    auto string = MUST(String::from_utf8("abcdefg"sv));
-    EXPECT_EQ(string.is_short_string(), true);
-    EXPECT_EQ(string.bytes().size(), 7u);
-    EXPECT_EQ(string.bytes_as_string_view(), "abcdefg"sv);
+    auto string1 = MUST(String::from_utf8("abcdefg"sv));
+    EXPECT_EQ(string1.is_short_string(), true);
+    EXPECT_EQ(string1.bytes().size(), 7u);
+    EXPECT_EQ(string1.bytes_as_string_view(), "abcdefg"sv);
+
+    constexpr auto string2 = String::from_utf8_short_string("abcdefg"sv);
+    EXPECT_EQ(string2.is_short_string(), true);
+    EXPECT_EQ(string2.bytes().size(), 7u);
+    EXPECT_EQ(string2, string1);
 #else
-    auto string = MUST(String::from_utf8("abc"sv));
-    EXPECT_EQ(string.is_short_string(), true);
-    EXPECT_EQ(string.bytes().size(), 3u);
-    EXPECT_EQ(string.bytes_as_string_view(), "abc"sv);
+    auto string1 = MUST(String::from_utf8("abc"sv));
+    EXPECT_EQ(string1.is_short_string(), true);
+    EXPECT_EQ(string1.bytes().size(), 3u);
+    EXPECT_EQ(string1.bytes_as_string_view(), "abc"sv);
+
+    constexpr auto string2 = String::from_utf8_short_string("abc"sv);
+    EXPECT_EQ(string2.is_short_string(), true);
+    EXPECT_EQ(string2.bytes().size(), 3u);
+    EXPECT_EQ(string2, string1);
 #endif
 }