QMetaEnum: add support for reading 64-bit flags and enums

The previous commit added support for storing them in the meta
object. This complements by adding support for reading the stored
values.

Task-number: QTBUG-111926
Fixes: QTBUG-27451
Change-Id: I8a96935cf6c742259c9dfffd17e958aa8ed66086
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Thiago Macieira 2024-08-06 21:43:42 -07:00
parent 2effeb25c0
commit d41b87e067
11 changed files with 647 additions and 44 deletions

View File

@ -1258,6 +1258,18 @@ bool QMetaType::isValid() const
}
#include "qmetaobject.h"
const char *QMetaEnum::valueToKey(int value) const
{
return valueToKey(quint64(uint(value)));
}
QByteArray QMetaEnum::valueToKeys(int value) const
{
return valueToKeys(quint64(uint(value)));
}
#include "quuid.h"
bool QUuid::isNull() const noexcept

View File

@ -3158,15 +3158,91 @@ const char *QMetaEnum::key(int index) const
Returns the value with the given \a index; or returns -1 if there
is no such value.
\sa keyCount(), key(), keyToValue()
If this is an enumeration with a 64-bit underlying type (see is64Bit()),
this function returns the low 32-bit portion of the value. Use value64() to
obtain the full value instead.
\sa value64(), keyCount(), key(), keyToValue()
*/
int QMetaEnum::value(int index) const
{
return value64(index).value_or(-1);
}
enum EnumExtendMode {
SignExtend = -1,
ZeroExtend,
Use64Bit = 64
};
Q_DECL_PURE_FUNCTION static inline EnumExtendMode enumExtendMode(const QMetaEnum &e)
{
if (e.is64Bit())
return Use64Bit;
if (e.isFlag())
return ZeroExtend;
if (e.metaType().flags() & QMetaType::IsUnsignedEnumeration)
return ZeroExtend;
return SignExtend;
}
static constexpr bool isEnumValueSuitable(quint64 value, EnumExtendMode mode)
{
if (mode == Use64Bit)
return true; // any value is suitable
// 32-bit QMetaEnum
if (mode == ZeroExtend)
return value == uint(value);
return value == quint64(int(value));
}
template <typename... Mode> inline
quint64 QMetaEnum::value_helper(uint index, Mode... modes) const noexcept
{
static_assert(sizeof...(Mode) < 2);
if constexpr (sizeof...(Mode) == 0) {
return value_helper(index, enumExtendMode(*this));
} else if constexpr (sizeof...(Mode) == 1) {
auto mode = (modes, ...);
quint64 value = mobj->d.data[data.data() + 2U * index + 1];
if (mode > 0)
value |= quint64(mobj->d.data[data.data() + 2U * data.keyCount() + index]) << 32;
else if (mode < 0)
value = int(value); // sign extend to 64-bit
return value;
}
}
/*!
\since 6.9
Returns the value with the given \a index if it exists; or returns a
\c{std::nullopt} if it doesn't.
//! [qmetaenum-32bit-signextend-64bit]
This function always sign-extends the value of 32-bit enumerations to 64
bits, if their underlying type is signed (e.g., \c int, \c short). In most
cases, this is the expected behavior.
A notable exception is for flag values that have bit 31 set, like
0x8000'0000, because some compilers (such as Microsoft Visual Studio), do
not automatically switch to an unsigned underlying type. To avoid this
problem, explicitly specify the underlying type in the \c enum declaration.
\note For QMetaObjects compiled prior to Qt 6.6, this function always
sign-extends.
//! [qmetaenum-32bit-signextend-64bit]
\sa keyCount(), key(), keyToValue(), is64Bit()
*/
std::optional<quint64> QMetaEnum::value64(int index) const
{
if (!mobj)
return 0;
if (index >= 0 && index < int(data.keyCount()))
return mobj->d.data[data.data() + 2 * index + 1];
return -1;
return std::nullopt;
if (index < 0 || index >= int(data.keyCount()))
return std::nullopt;
return value_helper(index);
}
/*!
@ -3198,6 +3274,20 @@ bool QMetaEnum::isScoped() const
return data.flags() & EnumIsScoped;
}
/*!
\since 6.9
Returns \c true if the underlying type of this enumeration is 64 bits wide.
\sa value64()
*/
bool QMetaEnum::is64Bit() const
{
if (!mobj)
return false;
return data.flags() & EnumIs64Bit;
}
/*!
Returns the scope this enumerator was declared in.
@ -3247,25 +3337,44 @@ static bool isScopeMatch(QByteArrayView scope, const QMetaEnum *e)
For flag types, use keysToValue().
\sa valueToKey(), isFlag(), keysToValue()
If this is a 64-bit enumeration (see is64Bit()), this function returns the
low 32-bit portion of the value. Use keyToValue64() to obtain the full value
instead.
\sa keyToValue64, valueToKey(), isFlag(), keysToValue(), is64Bit()
*/
int QMetaEnum::keyToValue(const char *key, bool *ok) const
{
auto value = keyToValue64(key);
if (ok != nullptr)
*ok = false;
if (!mobj || !key)
return -1;
*ok = value.has_value();
return int(value.value_or(-1));
}
/*!
\since 6.9
Returns the integer value of the given enumeration \a key, or \c
std::nullopt if \a key is not defined.
For flag types, use keysToValue64().
\include qmetaobject.cpp qmetaenum-32bit-signextend-64bit
\sa valueToKey(), isFlag(), keysToValue64()
*/
std::optional<quint64> QMetaEnum::keyToValue64(const char *key) const
{
if (!mobj || !key)
return std::nullopt;
const auto [scope, enumKey] = parse_scope(QLatin1StringView(key));
for (int i = 0; i < int(data.keyCount()); ++i) {
if ((!scope || isScopeMatch(*scope, this))
&& enumKey == stringDataView(mobj, mobj->d.data[data.data() + 2 * i])) {
if (ok != nullptr)
*ok = true;
return mobj->d.data[data.data() + 2 * i + 1];
return value_helper(i);
}
}
return -1;
return std::nullopt;
}
/*!
@ -3276,13 +3385,19 @@ int QMetaEnum::keyToValue(const char *key, bool *ok) const
\sa isFlag(), valueToKeys()
*/
const char *QMetaEnum::valueToKey(int value) const
const char *QMetaEnum::valueToKey(quint64 value) const
{
if (!mobj)
return nullptr;
for (int i = 0; i < int(data.keyCount()); ++i)
if (value == (int)mobj->d.data[data.data() + 2 * i + 1])
EnumExtendMode mode = enumExtendMode(*this);
if (!isEnumValueSuitable(value, mode))
return nullptr;
for (int i = 0; i < int(data.keyCount()); ++i) {
if (value == value_helper(i, mode))
return rawStringData(mobj, mobj->d.data[data.data() + 2 * i]);
}
return nullptr;
}
@ -3339,39 +3454,57 @@ static bool parseEnumFlags(QByteArrayView v, QVarLengthArray<QByteArrayView, 10>
If \a keys is not defined, *\a{ok} is set to false; otherwise
*\a{ok} is set to true.
\sa isFlag(), valueToKey(), valueToKeys()
If this is a 64-bit enumeration (see is64Bit()), this function returns the
low 32-bit portion of the value. Use keyToValue64() to obtain the full value
instead.
\sa keysToValue64(), isFlag(), valueToKey(), valueToKeys(), is64Bit()
*/
int QMetaEnum::keysToValue(const char *keys, bool *ok) const
{
auto value = keysToValue64(keys);
if (ok != nullptr)
*ok = false;
if (!mobj || !keys)
return -1;
*ok = value.has_value();
return int(value.value_or(-1));
}
auto lookup = [&] (QByteArrayView key) -> std::optional<int> {
/*!
Returns the value derived from combining together the values of the \a keys
using the OR operator, or \c std::nullopt if \a keys is not defined. Note
that the strings in \a keys must be '|'-separated.
\include qmetaobject.cpp qmetaenum-32bit-signextend-64bit
\sa isFlag(), valueToKey(), valueToKeys()
*/
std::optional<quint64> QMetaEnum::keysToValue64(const char *keys) const
{
if (!mobj || !keys)
return std::nullopt;
EnumExtendMode mode = enumExtendMode(*this);
auto lookup = [&] (QByteArrayView key) -> std::optional<quint64> {
for (int i = data.keyCount() - 1; i >= 0; --i) {
if (key == stringDataView(mobj, mobj->d.data[data.data() + 2*i]))
return mobj->d.data[data.data() + 2*i + 1];
return value_helper(i, mode);
}
return std::nullopt;
};
int value = 0;
quint64 value = 0;
QVarLengthArray<QByteArrayView, 10> list;
const bool r = parseEnumFlags(QByteArrayView{keys}, list);
if (!r)
return -1;
return std::nullopt;
for (const auto &untrimmed : list) {
const auto parsed = parse_scope(untrimmed.trimmed());
if (parsed.scope && !isScopeMatch(*parsed.scope, this))
return -1; // wrong type name in qualified name
return std::nullopt; // wrong type name in qualified name
if (auto thisValue = lookup(parsed.key))
value |= *thisValue;
else
return -1; // no such enumerator
return std::nullopt; // no such enumerator
}
if (ok != nullptr)
*ok = true;
return value;
}
@ -3401,18 +3534,28 @@ void join_reversed(String &s, const Container &c, Separator sep)
Returns a byte array of '|'-separated keys that represents the
given \a value.
\note Passing a 64-bit \a value to an enumeration whose underlying type is
32-bit (that is, if is64Bit() returns \c false) results in an empty string
being returned.
\sa isFlag(), valueToKey(), keysToValue()
*/
QByteArray QMetaEnum::valueToKeys(int value) const
QByteArray QMetaEnum::valueToKeys(quint64 value) const
{
QByteArray keys;
if (!mobj)
return keys;
EnumExtendMode mode = enumExtendMode(*this);
if (!isEnumValueSuitable(value, mode))
return keys;
QVarLengthArray<QByteArrayView, sizeof(int) * CHAR_BIT> parts;
int v = value;
quint64 v = value;
// reverse iterate to ensure values like Qt::Dialog=0x2|Qt::Window are processed first.
for (int i = data.keyCount() - 1; i >= 0; --i) {
int k = mobj->d.data[data.data() + 2 * i + 1];
quint64 k = value_helper(i, mode);
if ((k != 0 && (v & k) == k) || (k == value)) {
v = v & ~k;
parts.push_back(stringDataView(mobj, mobj->d.data[data.data() + 2 * i]));

View File

@ -271,17 +271,25 @@ public:
bool isFlag() const;
bool isScoped() const;
bool is64Bit() const;
int keyCount() const;
const char *key(int index) const;
int value(int index) const;
std::optional<quint64> value64(int index) const;
const char *scope() const;
int keyToValue(const char *key, bool *ok = nullptr) const;
const char *valueToKey(int value) const;
int keysToValue(const char *keys, bool *ok = nullptr) const;
std::optional<quint64> keyToValue64(const char *key) const;
std::optional<quint64> keysToValue64(const char *keys) const;
#if QT_CORE_REMOVED_SINCE(6, 8)
const char *valueToKey(int value) const;
QByteArray valueToKeys(int value) const;
#endif
const char *valueToKey(quint64 value) const;
QByteArray valueToKeys(quint64 value) const;
inline const QMetaObject *enclosingMetaObject() const { return mobj; }
@ -312,6 +320,7 @@ private:
};
QMetaEnum(const QMetaObject *mobj, int index);
template <typename... Args> quint64 value_helper(uint index, Args...) const noexcept;
const QMetaObject *mobj;
Data data;

View File

@ -1982,13 +1982,13 @@ static bool convertFromEnum(QMetaType fromType, const void *from, QMetaType toTy
QMetaEnum en = metaEnumFromType(fromType);
if (en.isValid()) {
if (en.isFlag()) {
const QByteArray keys = en.valueToKeys(static_cast<int>(ll));
const QByteArray keys = en.valueToKeys(ll);
if (toType.id() == QMetaType::QString)
*static_cast<QString *>(to) = QString::fromUtf8(keys);
else
*static_cast<QByteArray *>(to) = keys;
} else {
const char *key = en.valueToKey(static_cast<int>(ll));
const char *key = en.valueToKey(ll);
if (toType.id() == QMetaType::QString)
*static_cast<QString *>(to) = QString::fromUtf8(key);
else
@ -2014,7 +2014,10 @@ static bool convertToEnum(QMetaType fromType, const void *from, QMetaType toType
QByteArray keys = (fromTypeId == QMetaType::QString)
? static_cast<const QString *>(from)->toUtf8()
: *static_cast<const QByteArray *>(from);
value = en.keysToValue(keys.constData(), &ok);
if (auto v = en.keysToValue64(keys.constData())) {
ok = true;
value = *v;
}
}
}
#endif

View File

@ -11,14 +11,49 @@ class tst_QMetaEnum : public QObject
{
Q_OBJECT
public:
// these untyped enums are signed
enum SuperEnum { SuperValue1 = 1, SuperValue2 = INT_MIN };
enum Flag { Flag1 = 1, Flag2 = INT_MIN };
// we must force to ": unsigned" to get cross-platform behavior because
// MSVC always chooses int to back un-fixed enums
enum UnsignedEnum : unsigned { UnsignedValue1 = 1, UnsignedValue2 = 0x8000'0000 };
enum Flag32 : unsigned { Flag32_1 = 1, Flag32_2 = 0x8000'0000 };
enum SignedEnum64 : qint64 {
SignedValue64_0,
SignedValue64_1 = 1,
SignedValue64_Large = Q_INT64_C(1) << 32,
SignedValue64_M1 = -1,
};
enum UnsignedEnum64 : quint64 {
UnsignedValue64_0 = 0,
UnsignedValue64_1 = 1,
UnsignedValue64_Large = Q_UINT64_C(1) << 32,
UnsignedValue64_Max = ~Q_UINT64_C(0),
};
// flags should always be unsigned
enum Flag64 : quint64 {
Flag64_1 = 1,
Flag64_2 = Q_UINT64_C(1) << 31,
Flag64_3 = Q_UINT64_C(1) << 32,
};
Q_DECLARE_FLAGS(Flags, Flag)
Q_DECLARE_FLAGS(Flags32, Flag32)
Q_DECLARE_FLAGS(Flags64, Flag64)
Q_ENUM(SuperEnum)
Q_ENUM(UnsignedEnum)
Q_ENUM(SignedEnum64)
Q_ENUM(UnsignedEnum64)
Q_FLAG(Flags)
Q_FLAG(Flags32)
Q_FLAG(Flags64)
private slots:
void fromType();
void keyToValue_data();
void keyToValue();
void valuesToKeys_data();
void valuesToKeys();
void defaultConstructed();
@ -28,23 +63,194 @@ void tst_QMetaEnum::fromType()
{
QMetaEnum meta = QMetaEnum::fromType<SuperEnum>();
QVERIFY(meta.isValid());
QVERIFY(!meta.is64Bit());
QVERIFY(!meta.isFlag());
QCOMPARE(meta.name(), "SuperEnum");
QCOMPARE(meta.enumName(), "SuperEnum");
QCOMPARE(meta.enclosingMetaObject(), &staticMetaObject);
QCOMPARE(meta.keyCount(), 2);
QCOMPARE(meta.metaType(), QMetaType::fromType<SuperEnum>());
meta = QMetaEnum::fromType<UnsignedEnum>();
QVERIFY(meta.isValid());
QVERIFY(!meta.is64Bit());
QVERIFY(!meta.isFlag());
QCOMPARE(meta.name(), "UnsignedEnum");
QCOMPARE(meta.enumName(), "UnsignedEnum");
QCOMPARE(meta.enclosingMetaObject(), &staticMetaObject);
QCOMPARE(meta.keyCount(), 2);
QCOMPARE(meta.metaType(), QMetaType::fromType<UnsignedEnum>());
meta = QMetaEnum::fromType<SignedEnum64>();
QVERIFY(meta.isValid());
QVERIFY(meta.is64Bit());
QVERIFY(!meta.isFlag());
QCOMPARE(meta.name(), "SignedEnum64");
QCOMPARE(meta.enumName(), "SignedEnum64");
QCOMPARE(meta.enclosingMetaObject(), &staticMetaObject);
QCOMPARE(meta.keyCount(), 4);
QCOMPARE(meta.metaType(), QMetaType::fromType<SignedEnum64>());
meta = QMetaEnum::fromType<UnsignedEnum64>();
QVERIFY(meta.isValid());
QVERIFY(meta.is64Bit());
QVERIFY(!meta.isFlag());
QCOMPARE(meta.name(), "UnsignedEnum64");
QCOMPARE(meta.enumName(), "UnsignedEnum64");
QCOMPARE(meta.enclosingMetaObject(), &staticMetaObject);
QCOMPARE(meta.keyCount(), 4);
QCOMPARE(meta.metaType(), QMetaType::fromType<UnsignedEnum64>());
meta = QMetaEnum::fromType<Flags>();
QVERIFY(meta.isValid());
QVERIFY(!meta.is64Bit());
QVERIFY(meta.isFlag());
QCOMPARE(meta.name(), "Flags");
QCOMPARE(meta.enumName(), "Flag");
QCOMPARE(meta.enclosingMetaObject(), &staticMetaObject);
QCOMPARE(meta.keyCount(), 2);
QCOMPARE(meta.metaType(), QMetaType::fromType<Flags>());
meta = QMetaEnum::fromType<Flags32>();
QVERIFY(meta.isValid());
QVERIFY(!meta.is64Bit());
QVERIFY(meta.isFlag());
QCOMPARE(meta.name(), "Flags32");
QCOMPARE(meta.enumName(), "Flag32");
QCOMPARE(meta.enclosingMetaObject(), &staticMetaObject);
QCOMPARE(meta.keyCount(), 2);
QCOMPARE(meta.metaType(), QMetaType::fromType<Flags32>());
meta = QMetaEnum::fromType<Flags64>();
QVERIFY(meta.isValid());
QVERIFY(meta.is64Bit());
QVERIFY(meta.isFlag());
QCOMPARE(meta.name(), "Flags64");
QCOMPARE(meta.enumName(), "Flag64");
QCOMPARE(meta.enclosingMetaObject(), &staticMetaObject);
QCOMPARE(meta.keyCount(), 3);
QCOMPARE(meta.metaType(), QMetaType::fromType<Flags64>());
}
Q_DECLARE_METATYPE(Qt::WindowFlags)
template <typename E> quint64 toUnderlying(E e, std::enable_if_t<std::is_enum_v<E>, bool> = true)
{
// keep signedness if it's just an enum
return qToUnderlying(e);
}
template <typename E> quint64 toUnderlying(QFlags<E> f)
{
// force to unsigned so we zero-extend if it's QFlags
return typename QIntegerForSizeof<E>::Unsigned(f);
}
void tst_QMetaEnum::keyToValue_data()
{
QTest::addColumn<QMetaEnum>("me");
QTest::addColumn<QByteArray>("key");
QTest::addColumn<quint64>("value");
QTest::addColumn<bool>("success");
QByteArray notfoundkey = QByteArray::fromRawData("Foobar", 6);
auto addNotFoundRow = [&](auto value) {
QMetaEnum me = QMetaEnum::fromType<decltype(value)>();
QTest::addRow("notfound-%s", me.name())
<< me << notfoundkey << quint64(value) << false;
};
auto addRow = [&](const char *name, auto value) {
using T = decltype(value);
QMetaEnum me = QMetaEnum::fromType<T>();
QTest::addRow("%s", name) << me << QByteArray(name) << toUnderlying(value) << true;
if constexpr (sizeof(value) == sizeof(int)) {
// repeat with the upper half negated
quint64 v = toUnderlying(value);
v ^= Q_UINT64_C(0xffff'ffff'0000'0000);
QTest::addRow("mangled-%s", name) << me << notfoundkey << v << false;
}
};
addRow("Window", Qt::Window);
addRow("Dialog", Qt::Dialog);
addRow("WindowFullscreenButtonHint", Qt::WindowFullscreenButtonHint);
addNotFoundRow(SuperEnum(2));
addRow("SuperValue1", SuperValue1);
addRow("SuperValue2", SuperValue2);
addNotFoundRow(Flags(2));
addRow("Flag1", Flags(Flag1));
addRow("Flag2", Flags(Flag2));
addNotFoundRow(UnsignedEnum(2));
addRow("UnsignedValue1", UnsignedValue1);
addRow("UnsignedValue2", UnsignedValue2);
addNotFoundRow(Flags32(2));
addRow("Flag32_1", Flags32(Flag32_1));
addRow("Flag32_2", Flags32(Flag32_2));
addNotFoundRow(SignedEnum64(2));
addRow("SignedValue64_0", SignedValue64_0);
addRow("SignedValue64_1", SignedValue64_1);
addRow("SignedValue64_Large", SignedValue64_Large);
addRow("SignedValue64_M1", SignedValue64_M1);
addNotFoundRow(UnsignedEnum64(2));
addRow("UnsignedValue64_0", UnsignedValue64_0);
addRow("UnsignedValue64_1", UnsignedValue64_1);
addRow("UnsignedValue64_Large", UnsignedValue64_Large);
addRow("UnsignedValue64_Max", UnsignedValue64_Max);
addNotFoundRow(Flags64(std::in_place, 2));
addRow("Flag64_1", Flags64(Flag64_1));
addRow("Flag64_2", Flags64(Flag64_2));
addRow("Flag64_3", Flags64(Flag64_3));
}
void tst_QMetaEnum::keyToValue()
{
QFETCH(QMetaEnum, me);
QFETCH(quint64, value);
QFETCH(QByteArray, key);
QFETCH(bool, success);
if (!key.isEmpty()) {
// look up value of key
if (value == uint(value)) {
bool ok;
int value32 = success ? value : -1;
int result32 = me.keyToValue(key, &ok);
QCOMPARE(ok, success);
QCOMPARE(result32, value32);
result32 = me.keysToValue(key, &ok);
QCOMPARE(ok, success);
QCOMPARE(result32, value32);
}
std::optional value64 = me.keyToValue64(key);
QCOMPARE(value64.has_value(), success);
if (success)
QCOMPARE(*value64, value);
value64 = me.keysToValue64(key);
QCOMPARE(value64.has_value(), success);
if (success)
QCOMPARE(*value64, value);
}
// look up value
QByteArray expected = success ? key : QByteArray();
QByteArray result = me.valueToKey(value);
QCOMPARE(result, expected);
result = me.valueToKeys(value);
QCOMPARE(result, expected);
}
void tst_QMetaEnum::valuesToKeys_data()
{
QTest::addColumn<QMetaEnum>("me");
@ -69,10 +275,43 @@ void tst_QMetaEnum::valuesToKeys_data()
<< quint64(Qt::Tool | Qt::WindowMinMaxButtonsHint | Qt::WindowStaysOnTopHint)
<< QByteArrayLiteral("Tool|WindowMinMaxButtonsHint|WindowStaysOnTopHint");
QTest::newRow("INT_MIN")
// Verify that upper bits set don't cause a mistaken detection
QTest::newRow("upperbits-Window")
<< QMetaEnum::fromType<Qt::WindowFlags>()
<< (quint64(Qt::Window) | Q_UINT64_C(0x1'0000'0000))
<< QByteArray();
QTest::newRow("INT_MIN-as-enum")
<< QMetaEnum::fromType<SuperEnum>()
<< quint64(SuperValue2)
<< QByteArrayLiteral("SuperValue2");
QTest::newRow("mangled-INT_MIN-as-enum")
<< QMetaEnum::fromType<SuperEnum>()
<< quint64(uint(SuperValue2))
<< QByteArray();
QTest::newRow("INT_MIN-as-flags")
<< QMetaEnum::fromType<Flags>()
<< quint64(uint(Flag2))
<< QByteArrayLiteral("Flag2");
QTest::newRow("mangled-INT_MIN-as-flags")
<< QMetaEnum::fromType<Flags>()
<< quint64(Flag2)
<< QByteArray();
QTest::newRow("Flag32_2")
<< QMetaEnum::fromType<Flags32>()
<< quint64(uint(Flag32_2))
<< QByteArrayLiteral("Flag32_2");
QTest::newRow("mangled-Flag32_2")
<< QMetaEnum::fromType<Flags32>()
<< quint64(int(Flag32_2))
<< QByteArray();
QTest::newRow("Flag64_all")
<< QMetaEnum::fromType<Flags64>()
<< quint64(Flag64_1 | Flag64_2 | Flag64_3)
<< QByteArrayLiteral("Flag64_1|Flag64_2|Flag64_3");
}
void tst_QMetaEnum::valuesToKeys()
@ -82,9 +321,12 @@ void tst_QMetaEnum::valuesToKeys()
QFETCH(QByteArray, expected);
QCOMPARE(me.valueToKeys(flags), expected);
if (!expected.isEmpty()) {
bool ok = false;
QCOMPARE(uint(me.keysToValue(expected, &ok)), flags);
QCOMPARE(uint(me.keysToValue(expected, &ok)), uint(flags));
QVERIFY(ok);
QCOMPARE(me.keysToValue64(expected), flags);
}
}
void tst_QMetaEnum::defaultConstructed()
@ -93,7 +335,12 @@ void tst_QMetaEnum::defaultConstructed()
QVERIFY(!e.isValid());
QVERIFY(!e.isScoped());
QVERIFY(!e.isFlag());
QVERIFY(!e.is64Bit());
QCOMPARE(e.name(), QByteArray());
QCOMPARE(e.scope(), QByteArray());
QCOMPARE(e.enclosingMetaObject(), nullptr);
QCOMPARE(e.keyCount(), 0);
QCOMPARE(e.metaType(), QMetaType());
}
static_assert(QtPrivate::IsQEnumHelper<tst_QMetaEnum::SuperEnum>::Value);

View File

@ -2371,16 +2371,22 @@ void tst_QMetaObject::keysToValue()
QVERIFY(me.isValid());
QVERIFY(!me.isFlag());
QCOMPARE(QByteArray(me.scope()), QByteArray("MyNamespace::" + name));
QCOMPARE(me.keyToValue64("MyNamespace::" + name + "::MyEnum2"), 1U);
QCOMPARE(me.keyToValue("MyNamespace::" + name + "::MyEnum2", &ok), 1);
// Fully qualified unscoped enumerator
QCOMPARE(me.keyToValue64("MyNamespace::" + name + "::MyEnum::MyEnum2"), 1U);
QCOMPARE(me.keyToValue("MyNamespace::" + name + "::MyEnum::MyEnum2", &ok), 1);
QCOMPARE(ok, true);
QCOMPARE(me.keyToValue64(name + "::MyEnum2"), std::nullopt);
QCOMPARE(me.keyToValue(name + "::MyEnum2", &ok), -1);
QCOMPARE(ok, false);
QCOMPARE(me.keyToValue64("MyNamespace::MyEnum2"), std::nullopt);
QCOMPARE(me.keyToValue("MyNamespace::MyEnum2", &ok), -1);
QCOMPARE(ok, false);
QCOMPARE(me.keyToValue64("MyEnum2"), 1U);
QCOMPARE(me.keyToValue("MyEnum2", &ok), 1);
QCOMPARE(ok, true);
QCOMPARE(me.keyToValue64("MyEnum"), std::nullopt);
QCOMPARE(me.keyToValue("MyEnum", &ok), -1);
QCOMPARE(ok, false);
QCOMPARE(QLatin1String(me.valueToKey(1)), QLatin1String("MyEnum2"));
@ -2388,12 +2394,16 @@ void tst_QMetaObject::keysToValue()
QMetaEnum me2 = mo->enumerator(mo->indexOfEnumerator("MyAnotherEnum"));
QVERIFY(me2.isValid());
QVERIFY(!me2.isFlag());
QCOMPARE(me2.keyToValue64("MyAnotherEnum1"), 1U);
QCOMPARE(me2.keyToValue("MyAnotherEnum1", &ok), 1);
QCOMPARE(ok, true);
QCOMPARE(me2.keyToValue64("MyAnotherEnum2"), 2U);
QCOMPARE(me2.keyToValue("MyAnotherEnum2", &ok), 2);
QCOMPARE(ok, true);
QCOMPARE(me2.keyToValue64("MyAnotherEnum3"), quint64(-1));
QCOMPARE(me2.keyToValue("MyAnotherEnum3", &ok), -1);
QCOMPARE(ok, true);
QCOMPARE(me2.keyToValue64("MyAnotherEnum"), std::nullopt);
QCOMPARE(me2.keyToValue("MyAnotherEnum", &ok), -1);
QCOMPARE(ok, false);
@ -2401,55 +2411,80 @@ void tst_QMetaObject::keysToValue()
QVERIFY(mf.isValid());
QVERIFY(mf.isFlag());
QCOMPARE(QByteArray(mf.scope()), QByteArray("MyNamespace::" + name));
QCOMPARE(mf.keysToValue64("MyNamespace::" + name + "::MyFlag2"), 2U);
QCOMPARE(mf.keysToValue("MyNamespace::" + name + "::MyFlag2", &ok), 2);
QCOMPARE(ok, true);
// Fully qualified
QCOMPARE(mf.keysToValue64("MyNamespace::" + name + "::MyFlag::MyFlag2"), 2U);
QCOMPARE(mf.keysToValue("MyNamespace::" + name + "::MyFlag::MyFlag2", &ok), 2);
QCOMPARE(ok, true);
QCOMPARE(mf.keysToValue64(name + "::MyFlag2"), std::nullopt);
QCOMPARE(mf.keysToValue(name + "::MyFlag2", &ok), -1);
QCOMPARE(ok, false);
QCOMPARE(mf.keysToValue64("MyNamespace::MyFlag2"), std::nullopt);
QCOMPARE(mf.keysToValue("MyNamespace::MyFlag2", &ok), -1);
QCOMPARE(ok, false);
QCOMPARE(mf.keysToValue64("MyFlag2"), 2U);
QCOMPARE(mf.keysToValue("MyFlag2", &ok), 2);
QCOMPARE(ok, true);
QCOMPARE(mf.keysToValue64("MyFlag"), std::nullopt);
QCOMPARE(mf.keysToValue("MyFlag", &ok), -1);
QCOMPARE(ok, false);
QCOMPARE(QLatin1String(mf.valueToKey(2)), QLatin1String("MyFlag2"));
const QByteArray prefix = "MyNamespace::" + name;
QCOMPARE(mf.keysToValue64(prefix + "::MyFlag1|" + prefix + "::MyFlag2"), 3U);
QCOMPARE(mf.keysToValue(prefix + "::MyFlag1|" + prefix + "::MyFlag2", &ok), 3);
QCOMPARE(ok, true);
// Fully qualified
QCOMPARE(mf.keysToValue64(prefix + "::MyFlag::MyFlag1|" + prefix + "::MyFlag::MyFlag2"), 3U);
QCOMPARE(mf.keysToValue(prefix + "::MyFlag::MyFlag1|" + prefix + "::MyFlag::MyFlag2", &ok), 3);
QCOMPARE(ok, true);
QCOMPARE(mf.keysToValue64(name + "::MyFlag1|" + name + "::MyFlag2"), std::nullopt);
QCOMPARE(mf.keysToValue(name + "::MyFlag1|" + name + "::MyFlag2", &ok), -1);
QCOMPARE(ok, false);
QCOMPARE(mf.keysToValue64("MyNamespace::MyFlag1|MyNamespace::MyFlag2"), std::nullopt);
QCOMPARE(mf.keysToValue("MyNamespace::MyFlag1|MyNamespace::MyFlag2", &ok), -1);
QCOMPARE(ok, false);
QCOMPARE(mf.keysToValue64("MyFlag1|MyFlag2"), 3U);
QCOMPARE(mf.keysToValue("MyFlag1|MyFlag2", &ok), 3);
QCOMPARE(ok, true);
QCOMPARE(mf.keysToValue64("MyFlag2|MyFlag2"), 2U);
QCOMPARE(mf.keysToValue("MyFlag2|MyFlag2", &ok), 2);
QCOMPARE(ok, true);
QCOMPARE(mf.keysToValue64("MyFlag1|MyNamespace::" + name + "::MyFlag2"), 3U);
QCOMPARE(mf.keysToValue("MyFlag1|MyNamespace::" + name + "::MyFlag2", &ok), 3);
QCOMPARE(ok, true);
QCOMPARE(mf.keysToValue64(prefix + "::MyFlag2|" + prefix + "::MyFlag2"), 2U);
QCOMPARE(mf.keysToValue(prefix + "::MyFlag2|" + prefix + "::MyFlag2", &ok), 2);
QCOMPARE(ok, true);
// Fully qualified
QCOMPARE(mf.keysToValue64(prefix + "::MyFlag::MyFlag2|" + prefix + "::MyFlag::MyFlag2"), 2U);
QCOMPARE(mf.keysToValue(prefix + "::MyFlag::MyFlag2|" + prefix + "::MyFlag::MyFlag2", &ok), 2);
QCOMPARE(ok, true);
QCOMPARE(QLatin1String(mf.valueToKeys(3)), QLatin1String("MyFlag1|MyFlag2"));
// Test flags with extra '|'
QTest::ignoreMessage(QtWarningMsg,
QRegularExpression(u"QMetaEnum::keysToValue: malformed keys string, ends with '|'.+"_s));
QCOMPARE(mf.keysToValue64("MyFlag1|MyFlag2|"), std::nullopt);
QTest::ignoreMessage(QtWarningMsg,
QRegularExpression(u"QMetaEnum::keysToValue: malformed keys string, ends with '|'.+"_s));
QCOMPARE(mf.keysToValue("MyFlag1|MyFlag2|", &ok), -1);
QCOMPARE(ok, false);
QTest::ignoreMessage(QtWarningMsg,
QRegularExpression(u"QMetaEnum::keysToValue: malformed keys string, starts with '|'.+"_s));
QCOMPARE(mf.keysToValue64("|MyFlag1|MyFlag2|"), std::nullopt);
QTest::ignoreMessage(QtWarningMsg,
QRegularExpression(u"QMetaEnum::keysToValue: malformed keys string, starts with '|'.+"_s));
QCOMPARE(mf.keysToValue("|MyFlag1|MyFlag2|", &ok), -1);
QCOMPARE(ok, false);
QTest::ignoreMessage(QtWarningMsg,
QRegularExpression(
u"QMetaEnum::keysToValue: malformed keys string, has two consecutive '|'.+"_s));
QCOMPARE(mf.keysToValue64("MyFlag1||MyFlag2"), std::nullopt);
QTest::ignoreMessage(QtWarningMsg,
QRegularExpression(
u"QMetaEnum::keysToValue: malformed keys string, has two consecutive '|'.+"_s));
@ -2458,6 +2493,8 @@ void tst_QMetaObject::keysToValue()
// Test empty string
QTest::ignoreMessage(QtWarningMsg, "QMetaEnum::keysToValue: empty keys string.");
QCOMPARE(mf.keysToValue64(""), std::nullopt);
QTest::ignoreMessage(QtWarningMsg, "QMetaEnum::keysToValue: empty keys string.");
QCOMPARE(mf.keysToValue("", &ok), -1);
QCOMPARE(ok, false);
}

View File

@ -5371,10 +5371,6 @@ template <typename Enum, auto Value> static void testVariantMetaEnum()
QVariant strVar = string;
QVERIFY(strVar.canConvert<Enum>());
// unary + to silence gcc warning
if ((+static_cast<qint64>(value) > INT_MAX) || (+static_cast<qint64>(value) < INT_MIN)) {
QEXPECT_FAIL("", "QMetaEnum api uses 'int' as return type QTBUG-27451", Abort);
}
QCOMPARE(strVar.value<Enum>(), value);
strVar = string.toLatin1();
QVERIFY(strVar.canConvert<Enum>());

View File

@ -38,6 +38,7 @@ set(JSON_HEADERS
plugin_metadata.h
pointery_to_incomplete.h
pure-virtual-signals.h
qflags64object.h
qinvokable.h
qprivateslots.h
qtbug-35657-gadget.h

View File

@ -2071,6 +2071,75 @@
"inputFile": "pure-virtual-signals.h",
"outputRevision": 68
},
{
"classes": [
{
"className": "QEnum64Object",
"enums": [
{
"isClass": false,
"isFlag": false,
"name": "LargeEnum",
"type": "qint64",
"values": [
"Value0",
"ValueMixed",
"ValueMinus1"
]
}
],
"lineNumber": 8,
"object": true,
"qualifiedClassName": "QEnum64Object",
"superClasses": [
{
"access": "public",
"name": "QObject"
}
]
},
{
"className": "QFlags64Object",
"enums": [
{
"alias": "LargeFlag",
"isClass": false,
"isFlag": true,
"name": "LargeFlags",
"type": "qint64",
"values": [
"Value0",
"ValueMixed",
"ValueMinus1"
]
},
{
"alias": "ScopedLargeFlag",
"isClass": true,
"isFlag": true,
"name": "ScopedLargeFlags",
"type": "quint64",
"values": [
"Value0",
"ValueMixed",
"ValueMinus1"
]
}
],
"lineNumber": 20,
"object": true,
"qualifiedClassName": "QFlags64Object",
"superClasses": [
{
"access": "public",
"name": "QObject"
}
]
}
],
"inputFile": "qflags64object.h",
"outputRevision": 68
},
{
"classes": [
{

View File

@ -0,0 +1,41 @@
// Copyright (C) 2024 Intel Corporation.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#ifndef QFLAGS64OBJECT_H
#define QFLAGS64OBJECT_H
#include <QtCore/QObject>
class QEnum64Object : public QObject
{
Q_OBJECT
public:
enum LargeEnum : qint64 {
Value0 = 0,
ValueMixed = Q_INT64_C(0x1122'3344'5566'7788),
ValueMinus1 = -1,
};
Q_ENUM(LargeEnum)
};
class QFlags64Object : public QObject
{
Q_OBJECT
public:
enum LargeFlag : qint64 {
Value0 = 0,
ValueMixed = Q_INT64_C(0x1122'3344'5566'7788),
ValueMinus1 = -1,
};
Q_DECLARE_FLAGS(LargeFlags, LargeFlag)
Q_FLAG(LargeFlags)
enum class ScopedLargeFlag : quint64 {
Value0 = 0,
ValueMixed = Q_UINT64_C(0x1122'3344'5566'7788),
ValueMinus1 = quint64(-1),
};
Q_DECLARE_FLAGS(ScopedLargeFlags, ScopedLargeFlag)
Q_FLAG(ScopedLargeFlags)
};
#endif // QFLAGS64OBJECT_H

View File

@ -1,5 +1,6 @@
// Copyright (C) 2020 The Qt Company Ltd.
// Copyright (C) 2020 Olivier Goffart <ogoffart@woboq.com>
// Copyright (C) 2024 Intel Corporation.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QTest>
@ -49,6 +50,7 @@
#include "non-gadget-parent-class.h"
#include "grand-parent-gadget-class.h"
#include "qflags64object.h"
#include "namespace.h"
#include "cxx17-namespaces.h"
#include "cxx-attributes.h"
@ -861,6 +863,7 @@ private slots:
void gadgetHierarchy();
void optionsFileError_data();
void optionsFileError();
void enumAndFlags64();
void testQNamespace();
void testNestedQNamespace();
void cxx17Namespaces();
@ -4168,11 +4171,51 @@ static void checkEnum(const QMetaEnum &enumerator, const QByteArray &name,
QCOMPARE(enumerator.metaType(), enumType);
for (int i = 0; i < enumerator.keyCount(); ++i) {
QCOMPARE(QByteArray{enumerator.key(i)}, keys[i].first);
QCOMPARE(enumerator.value(i), keys[i].second);
QCOMPARE(enumerator.value(i), int(keys[i].second));
QCOMPARE(*enumerator.value64(i), keys[i].second);
}
// out of range
QVERIFY(!enumerator.key(keys.size()));
QCOMPARE(enumerator.value(keys.size()), -1);
QVERIFY(!enumerator.value64(keys.size()));
}
void tst_Moc::enumAndFlags64()
{
const QList<QPair<QByteArray, quint64>> values = {
{ "Value0", 0 },
{ "ValueMixed", Q_UINT64_C(0x1122'3344'5566'7788) },
{ "ValueMinus1", quint64(-1) },
};
QCOMPARE(QEnum64Object::staticMetaObject.enumeratorCount(), 1);
QCOMPARE(QFlags64Object::staticMetaObject.enumeratorCount(), 2);
QMetaEnum me = QMetaEnum::fromType<QEnum64Object::LargeEnum>();
QVERIFY(!me.isFlag());
QVERIFY(me.is64Bit());
QVERIFY(!me.isScoped());
checkEnum(me, "LargeEnum", values,
QMetaType::fromType<QEnum64Object::LargeEnum>());
if (QTest::currentTestFailed()) return;
me = QMetaEnum::fromType<QFlags64Object::LargeFlags>();
QVERIFY(me.isFlag());
QVERIFY(me.is64Bit());
QVERIFY(!me.isScoped());
QCOMPARE(me.enumName(), "LargeFlag");
checkEnum(me, "LargeFlags", values,
QMetaType::fromType<QFlags64Object::LargeFlags>());
if (QTest::currentTestFailed()) return;
me = QMetaEnum::fromType<QFlags64Object::ScopedLargeFlags>();
QVERIFY(me.isFlag());
QVERIFY(me.is64Bit());
QVERIFY(me.isScoped());
QCOMPARE(me.enumName(), "ScopedLargeFlag");
checkEnum(me, "ScopedLargeFlags", values,
QMetaType::fromType<QFlags64Object::ScopedLargeFlags>());
}
class EnumFromNamespaceClass : public QObject
@ -4277,6 +4320,7 @@ void tst_Moc::cxx17Namespaces()
QCOMPARE(QMetaEnum::fromType<CXX17Namespace::A::B::C::D::NamEn>().name(), "NamEn");
QCOMPARE(QMetaEnum::fromType<CXX17Namespace::A::B::C::D::NamEn>().keyCount(), 1);
QCOMPARE(QMetaEnum::fromType<CXX17Namespace::A::B::C::D::NamEn>().value(0), 4);
QCOMPARE(*QMetaEnum::fromType<CXX17Namespace::A::B::C::D::NamEn>().value64(0), 4U);
QCOMPARE(CXX17Namespace::A::B::C::D::ClassInNamespace::staticMetaObject.className(),
"CXX17Namespace::A::B::C::D::ClassInNamespace");
@ -4285,6 +4329,7 @@ void tst_Moc::cxx17Namespaces()
QCOMPARE(QMetaEnum::fromType<CXX17Namespace::A::B::C::D::ClassInNamespace::GadEn>().name(), "GadEn");
QCOMPARE(QMetaEnum::fromType<CXX17Namespace::A::B::C::D::ClassInNamespace::GadEn>().keyCount(), 1);
QCOMPARE(QMetaEnum::fromType<CXX17Namespace::A::B::C::D::ClassInNamespace::GadEn>().value(0), 3);
QCOMPARE(*QMetaEnum::fromType<CXX17Namespace::A::B::C::D::ClassInNamespace::GadEn>().value64(0), 3U);
}
void tst_Moc::cxxAttributes()