diff --git a/src/corelib/compat/removed_api.cpp b/src/corelib/compat/removed_api.cpp index 1e05433d926..4bc62380605 100644 --- a/src/corelib/compat/removed_api.cpp +++ b/src/corelib/compat/removed_api.cpp @@ -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 diff --git a/src/corelib/kernel/qmetaobject.cpp b/src/corelib/kernel/qmetaobject.cpp index 9e02098ccad..278d8405acc 100644 --- a/src/corelib/kernel/qmetaobject.cpp +++ b/src/corelib/kernel/qmetaobject.cpp @@ -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 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 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 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 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 { +/*! + 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 QMetaEnum::keysToValue64(const char *keys) const +{ + if (!mobj || !keys) + return std::nullopt; + + EnumExtendMode mode = enumExtendMode(*this); + auto lookup = [&] (QByteArrayView key) -> std::optional { 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 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 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])); diff --git a/src/corelib/kernel/qmetaobject.h b/src/corelib/kernel/qmetaobject.h index 91f287a8d3c..57dbd0cfbed 100644 --- a/src/corelib/kernel/qmetaobject.h +++ b/src/corelib/kernel/qmetaobject.h @@ -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 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 keyToValue64(const char *key) const; + std::optional 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 quint64 value_helper(uint index, Args...) const noexcept; const QMetaObject *mobj; Data data; diff --git a/src/corelib/kernel/qmetatype.cpp b/src/corelib/kernel/qmetatype.cpp index 13026548ef0..3de52c154bc 100644 --- a/src/corelib/kernel/qmetatype.cpp +++ b/src/corelib/kernel/qmetatype.cpp @@ -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(ll)); + const QByteArray keys = en.valueToKeys(ll); if (toType.id() == QMetaType::QString) *static_cast(to) = QString::fromUtf8(keys); else *static_cast(to) = keys; } else { - const char *key = en.valueToKey(static_cast(ll)); + const char *key = en.valueToKey(ll); if (toType.id() == QMetaType::QString) *static_cast(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(from)->toUtf8() : *static_cast(from); - value = en.keysToValue(keys.constData(), &ok); + if (auto v = en.keysToValue64(keys.constData())) { + ok = true; + value = *v; + } } } #endif diff --git a/tests/auto/corelib/kernel/qmetaenum/tst_qmetaenum.cpp b/tests/auto/corelib/kernel/qmetaenum/tst_qmetaenum.cpp index 83dd8c62988..500c8aff820 100644 --- a/tests/auto/corelib/kernel/qmetaenum/tst_qmetaenum.cpp +++ b/tests/auto/corelib/kernel/qmetaenum/tst_qmetaenum.cpp @@ -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(); 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()); + + meta = QMetaEnum::fromType(); + 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()); + + meta = QMetaEnum::fromType(); + 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()); + + meta = QMetaEnum::fromType(); + 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()); meta = QMetaEnum::fromType(); 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()); + + meta = QMetaEnum::fromType(); + 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()); + + meta = QMetaEnum::fromType(); + 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()); } Q_DECLARE_METATYPE(Qt::WindowFlags) +template quint64 toUnderlying(E e, std::enable_if_t, bool> = true) +{ + // keep signedness if it's just an enum + return qToUnderlying(e); +} + +template quint64 toUnderlying(QFlags f) +{ + // force to unsigned so we zero-extend if it's QFlags + return typename QIntegerForSizeof::Unsigned(f); +} + +void tst_QMetaEnum::keyToValue_data() +{ + QTest::addColumn("me"); + QTest::addColumn("key"); + QTest::addColumn("value"); + QTest::addColumn("success"); + + QByteArray notfoundkey = QByteArray::fromRawData("Foobar", 6); + auto addNotFoundRow = [&](auto value) { + QMetaEnum me = QMetaEnum::fromType(); + + 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(); + 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("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() + << (quint64(Qt::Window) | Q_UINT64_C(0x1'0000'0000)) + << QByteArray(); + + QTest::newRow("INT_MIN-as-enum") + << QMetaEnum::fromType() + << quint64(SuperValue2) + << QByteArrayLiteral("SuperValue2"); + QTest::newRow("mangled-INT_MIN-as-enum") + << QMetaEnum::fromType() + << quint64(uint(SuperValue2)) + << QByteArray(); + + QTest::newRow("INT_MIN-as-flags") << QMetaEnum::fromType() << quint64(uint(Flag2)) << QByteArrayLiteral("Flag2"); + QTest::newRow("mangled-INT_MIN-as-flags") + << QMetaEnum::fromType() + << quint64(Flag2) + << QByteArray(); + + QTest::newRow("Flag32_2") + << QMetaEnum::fromType() + << quint64(uint(Flag32_2)) + << QByteArrayLiteral("Flag32_2"); + QTest::newRow("mangled-Flag32_2") + << QMetaEnum::fromType() + << quint64(int(Flag32_2)) + << QByteArray(); + + QTest::newRow("Flag64_all") + << QMetaEnum::fromType() + << 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); - bool ok = false; - QCOMPARE(uint(me.keysToValue(expected, &ok)), flags); - QVERIFY(ok); + if (!expected.isEmpty()) { + bool ok = false; + 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::Value); diff --git a/tests/auto/corelib/kernel/qmetaobject/tst_qmetaobject.cpp b/tests/auto/corelib/kernel/qmetaobject/tst_qmetaobject.cpp index 2220e194fd5..6b2b98e0eac 100644 --- a/tests/auto/corelib/kernel/qmetaobject/tst_qmetaobject.cpp +++ b/tests/auto/corelib/kernel/qmetaobject/tst_qmetaobject.cpp @@ -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); } diff --git a/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp b/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp index d79e0e82f8a..a510a0a680c 100644 --- a/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp +++ b/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp @@ -5371,10 +5371,6 @@ template static void testVariantMetaEnum() QVariant strVar = string; QVERIFY(strVar.canConvert()); - // unary + to silence gcc warning - if ((+static_cast(value) > INT_MAX) || (+static_cast(value) < INT_MIN)) { - QEXPECT_FAIL("", "QMetaEnum api uses 'int' as return type QTBUG-27451", Abort); - } QCOMPARE(strVar.value(), value); strVar = string.toLatin1(); QVERIFY(strVar.canConvert()); diff --git a/tests/auto/tools/moc/CMakeLists.txt b/tests/auto/tools/moc/CMakeLists.txt index d4491b103b8..13adde4c9c5 100644 --- a/tests/auto/tools/moc/CMakeLists.txt +++ b/tests/auto/tools/moc/CMakeLists.txt @@ -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 diff --git a/tests/auto/tools/moc/allmocs_baseline_in.json b/tests/auto/tools/moc/allmocs_baseline_in.json index 0a984307aa7..f3b7cd472cc 100644 --- a/tests/auto/tools/moc/allmocs_baseline_in.json +++ b/tests/auto/tools/moc/allmocs_baseline_in.json @@ -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": [ { diff --git a/tests/auto/tools/moc/qflags64object.h b/tests/auto/tools/moc/qflags64object.h new file mode 100644 index 00000000000..778e1838591 --- /dev/null +++ b/tests/auto/tools/moc/qflags64object.h @@ -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 + +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 diff --git a/tests/auto/tools/moc/tst_moc.cpp b/tests/auto/tools/moc/tst_moc.cpp index e37335cecf2..74e4803e8f4 100644 --- a/tests/auto/tools/moc/tst_moc.cpp +++ b/tests/auto/tools/moc/tst_moc.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2020 The Qt Company Ltd. // Copyright (C) 2020 Olivier Goffart +// Copyright (C) 2024 Intel Corporation. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include @@ -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> 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(); + QVERIFY(!me.isFlag()); + QVERIFY(me.is64Bit()); + QVERIFY(!me.isScoped()); + checkEnum(me, "LargeEnum", values, + QMetaType::fromType()); + + if (QTest::currentTestFailed()) return; + + me = QMetaEnum::fromType(); + QVERIFY(me.isFlag()); + QVERIFY(me.is64Bit()); + QVERIFY(!me.isScoped()); + QCOMPARE(me.enumName(), "LargeFlag"); + checkEnum(me, "LargeFlags", values, + QMetaType::fromType()); + + if (QTest::currentTestFailed()) return; + + me = QMetaEnum::fromType(); + QVERIFY(me.isFlag()); + QVERIFY(me.is64Bit()); + QVERIFY(me.isScoped()); + QCOMPARE(me.enumName(), "ScopedLargeFlag"); + checkEnum(me, "ScopedLargeFlags", values, + QMetaType::fromType()); } class EnumFromNamespaceClass : public QObject @@ -4277,6 +4320,7 @@ void tst_Moc::cxx17Namespaces() QCOMPARE(QMetaEnum::fromType().name(), "NamEn"); QCOMPARE(QMetaEnum::fromType().keyCount(), 1); QCOMPARE(QMetaEnum::fromType().value(0), 4); + QCOMPARE(*QMetaEnum::fromType().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().name(), "GadEn"); QCOMPARE(QMetaEnum::fromType().keyCount(), 1); QCOMPARE(QMetaEnum::fromType().value(0), 3); + QCOMPARE(*QMetaEnum::fromType().value64(0), 3U); } void tst_Moc::cxxAttributes()