From 61566e2f4f93d58a187d281f524b57475d5deddf Mon Sep 17 00:00:00 2001 From: nerix Date: Sat, 1 Jul 2023 17:31:25 +0200 Subject: [PATCH] Add string literal suffixes to create `QString`s, `QByteArray`s, and `QLatin1String(View)`s at compile time (#4706) --- src/CMakeLists.txt | 1 + src/common/Literals.hpp | 172 ++++++++++++++++++++++++++++++++++++++++ tests/CMakeLists.txt | 1 + tests/src/Literals.cpp | 41 ++++++++++ 4 files changed, 215 insertions(+) create mode 100644 src/common/Literals.hpp create mode 100644 tests/src/Literals.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a1a00e4909b..c095aa00f77 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -33,6 +33,7 @@ set(SOURCE_FILES common/Env.hpp common/LinkParser.cpp common/LinkParser.hpp + common/Literals.hpp common/Modes.cpp common/Modes.hpp common/NetworkCommon.cpp diff --git a/src/common/Literals.hpp b/src/common/Literals.hpp new file mode 100644 index 00000000000..fdf9822a36b --- /dev/null +++ b/src/common/Literals.hpp @@ -0,0 +1,172 @@ +#pragma once + +#include + +/// This namespace defines the string suffixes _s, _ba, and _L1 used to create Qt types at compile-time. +/// They're easier to use comapred to their corresponding macros. +/// +/// * u"foobar"_s creates a QString (like QStringLiteral). The u prefix is required. +/// +/// * "foobar"_ba creates a QByteArray (like QByteArrayLiteral). +/// +/// * "foobar"_L1 creates a QLatin1String(-View). +namespace chatterino::literals { + +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + +// This makes sure that the backing data never causes allocation after compilation. +// It's essentially the QStringLiteral macro inlined. +// +// From desktop-app/lib_base +// https://github.com/desktop-app/lib_base/blob/f904c60987115a4b514a575b23009ff25de0fafa/base/basic_types.h#L63-L152 +// And qt/qtbase (5.15) +// https://github.com/qt/qtbase/blob/29400a683f96867133b28299c0d0bd6bcf40df35/src/corelib/text/qstringliteral.h#L64-L104 +namespace detail { + // NOLINTBEGIN(modernize-avoid-c-arrays) + // NOLINTBEGIN(cppcoreguidelines-avoid-c-arrays) + // NOLINTBEGIN(cppcoreguidelines-avoid-const-or-ref-data-members) + + template + struct LiteralResolver { + template + constexpr LiteralResolver(const char16_t (&text)[N], + std::index_sequence /*seq*/) + : utf16Text{text[I]...} + { + } + template + constexpr LiteralResolver(const char (&text)[N], + std::index_sequence /*seq*/) + : latin1Text{text[I]...} + , latin1(true) + { + } + constexpr LiteralResolver(const char16_t (&text)[N]) + : LiteralResolver(text, std::make_index_sequence{}) + { + } + constexpr LiteralResolver(const char (&text)[N]) + : LiteralResolver(text, std::make_index_sequence{}) + { + } + + const char16_t utf16Text[N]{}; + const char latin1Text[N]{}; + size_t length = N; + bool latin1 = false; + }; + + template + struct StaticStringData { + template + constexpr StaticStringData(const char16_t (&text)[N], + std::index_sequence /*seq*/) + : data Q_STATIC_STRING_DATA_HEADER_INITIALIZER(N - 1) + , text{text[I]...} + { + } + QArrayData data; + char16_t text[N]; + + QStringData *pointer() + { + Q_ASSERT(data.ref.isStatic()); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast) + return static_cast(&data); + } + }; + + template + struct StaticByteArrayData { + template + constexpr StaticByteArrayData(const char (&text)[N], + std::index_sequence /*seq*/) + : data Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER(N - 1) + , text{text[I]...} + { + } + QByteArrayData data; + char text[N]; + + QByteArrayData *pointer() + { + Q_ASSERT(data.ref.isStatic()); + return &data; + } + }; + + // NOLINTEND(cppcoreguidelines-avoid-const-or-ref-data-members) + // NOLINTEND(cppcoreguidelines-avoid-c-arrays) + // NOLINTEND(modernize-avoid-c-arrays) + +} // namespace detail + +template +inline QString operator""_s() noexcept +{ + static_assert(R.length > 0); // always has a terminating null + static_assert(!R.latin1, "QString literals must be made up of 16bit " + "characters. Forgot a u\"\"?"); + + static auto literal = detail::StaticStringData( + R.utf16Text, std::make_index_sequence{}); + return QString{QStringDataPtr{literal.pointer()}}; +}; + +template +inline QByteArray operator""_ba() noexcept +{ + static_assert(R.length > 0); // always has a terminating null + static_assert(R.latin1, "QByteArray literals must be made up of 8bit " + "characters. Misplaced u\"\"?"); + + static auto literal = detail::StaticByteArrayData( + R.latin1Text, std::make_index_sequence{}); + return QByteArray{QByteArrayDataPtr{literal.pointer()}}; +}; + +#elif QT_VERSION < QT_VERSION_CHECK(6, 4, 0) + +// The operators were added in 6.4, but their implementation works in any 6.x version. +// +// NOLINTBEGIN(cppcoreguidelines-pro-type-const-cast) +inline QString operator""_s(const char16_t *str, size_t size) noexcept +{ + return QString( + QStringPrivate(nullptr, const_cast(str), qsizetype(size))); +} + +inline QByteArray operator""_ba(const char *str, size_t size) noexcept +{ + return QByteArray( + QByteArrayData(nullptr, const_cast(str), qsizetype(size))); +} +// NOLINTEND(cppcoreguidelines-pro-type-const-cast) + +#else + +inline QString operator""_s(const char16_t *str, size_t size) noexcept +{ + return Qt::Literals::StringLiterals::operator""_s(str, size); +} + +inline QByteArray operator""_ba(const char *str, size_t size) noexcept +{ + return Qt::Literals::StringLiterals::operator""_ba(str, size); +} + +#endif + +constexpr inline QLatin1String operator""_L1(const char *str, + size_t size) noexcept +{ +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + using SizeType = int; +#else + using SizeType = qsizetype; +#endif + + return QLatin1String{str, static_cast(size)}; +} + +} // namespace chatterino::literals diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 06c254ba9ac..0a684b92b0f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -30,6 +30,7 @@ set(test_SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/Filters.cpp ${CMAKE_CURRENT_LIST_DIR}/src/LinkParser.cpp ${CMAKE_CURRENT_LIST_DIR}/src/InputCompletion.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/Literals.cpp # Add your new file above this line! ) diff --git a/tests/src/Literals.cpp b/tests/src/Literals.cpp new file mode 100644 index 00000000000..77607b73977 --- /dev/null +++ b/tests/src/Literals.cpp @@ -0,0 +1,41 @@ +#include "common/Literals.hpp" + +#include + +using namespace chatterino::literals; + +// These tests ensure that the behavior of the suffixes is the same across Qt versions. + +TEST(Literals, String) +{ + ASSERT_EQ(u""_s, QStringLiteral("")); + ASSERT_EQ(u"1"_s, QStringLiteral("1")); + ASSERT_EQ(u"12"_s, QStringLiteral("12")); + ASSERT_EQ(u"123"_s, QStringLiteral("123")); +} + +TEST(Literals, Latin1String) +{ + ASSERT_EQ(""_L1, QLatin1String("")); + ASSERT_EQ("1"_L1, QLatin1String("1")); + ASSERT_EQ("12"_L1, QLatin1String("12")); + ASSERT_EQ("123"_L1, QLatin1String("123")); + + ASSERT_EQ(""_L1, u""_s); + ASSERT_EQ("1"_L1, u"1"_s); + ASSERT_EQ("12"_L1, u"12"_s); + ASSERT_EQ("123"_L1, u"123"_s); + + ASSERT_EQ(QString(""_L1), u""_s); + ASSERT_EQ(QString("1"_L1), u"1"_s); + ASSERT_EQ(QString("12"_L1), u"12"_s); + ASSERT_EQ(QString("123"_L1), u"123"_s); +} + +TEST(Literals, ByteArray) +{ + ASSERT_EQ(""_ba, QByteArrayLiteral("")); + ASSERT_EQ("1"_ba, QByteArrayLiteral("1")); + ASSERT_EQ("12"_ba, QByteArrayLiteral("12")); + ASSERT_EQ("123"_ba, QByteArrayLiteral("123")); +}