Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add string literal suffixes to create QStrings, QByteArrays, and QLatin1String(View)s at compile time #4706

Merged
merged 3 commits into from
Jul 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
172 changes: 172 additions & 0 deletions src/common/Literals.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
#pragma once

#include <QString>

/// 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 <size_t N>
struct LiteralResolver {
template <size_t... I>
constexpr LiteralResolver(const char16_t (&text)[N],
std::index_sequence<I...> /*seq*/)
: utf16Text{text[I]...}
{
}
template <size_t... I>
constexpr LiteralResolver(const char (&text)[N],
std::index_sequence<I...> /*seq*/)
: latin1Text{text[I]...}
, latin1(true)
{
}
constexpr LiteralResolver(const char16_t (&text)[N])
: LiteralResolver(text, std::make_index_sequence<N>{})
{
}
constexpr LiteralResolver(const char (&text)[N])
: LiteralResolver(text, std::make_index_sequence<N>{})
{
}

const char16_t utf16Text[N]{};
const char latin1Text[N]{};
size_t length = N;
bool latin1 = false;
};

template <size_t N>
struct StaticStringData {
template <std::size_t... I>
constexpr StaticStringData(const char16_t (&text)[N],
std::index_sequence<I...> /*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<QStringData *>(&data);
}
};

template <size_t N>
struct StaticByteArrayData {
template <std::size_t... I>
constexpr StaticByteArrayData(const char (&text)[N],
std::index_sequence<I...> /*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 <detail::LiteralResolver R>
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.length>(
R.utf16Text, std::make_index_sequence<R.length>{});
return QString{QStringDataPtr{literal.pointer()}};
};

template <detail::LiteralResolver R>
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.length>(
R.latin1Text, std::make_index_sequence<R.length>{});
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<char16_t *>(str), qsizetype(size)));
}

inline QByteArray operator""_ba(const char *str, size_t size) noexcept
{
return QByteArray(
QByteArrayData(nullptr, const_cast<char *>(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<SizeType>(size)};
}

} // namespace chatterino::literals
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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!
)

Expand Down
41 changes: 41 additions & 0 deletions tests/src/Literals.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#include "common/Literals.hpp"

#include <gtest/gtest.h>

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"));
}