diff options
author | Ulf Hermann <ulf.hermann@qt.io> | 2020-01-21 13:23:34 +0100 |
---|---|---|
committer | Ulf Hermann <ulf.hermann@qt.io> | 2020-01-30 10:20:53 +0100 |
commit | ed080c64ae21d22115d16780a5903d7cc4fd1546 (patch) | |
tree | 54aba89909f54d2848188dda5ad25e091e7bbf85 | |
parent | daf1f3f9ced82f139adbd7fd55b9288aa661b3e6 (diff) |
Core: Add a QTypeRevision class
QTypeRevision is needed to encode the value of the new two-argument
Q_REVISION(major, minor) macros. Those, in turn are necessary because
the minor version resets to 0, and we need to take the major version
into account when stating revisions for Qt classes.
Task-number: QTBUG-71278
Change-Id: I63eff6eab7d6e4f8f32b359a216767c98947a106
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Reviewed-by: Paul Wicking <paul.wicking@qt.io>
Reviewed-by: Simon Hausmann <simon.hausmann@qt.io>
Reviewed-by: Lars Knoll <lars.knoll@qt.io>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
Reviewed-by: Shawn Rutledge <shawn.rutledge@qt.io>
-rw-r--r-- | src/corelib/tools/qversionnumber.cpp | 196 | ||||
-rw-r--r-- | src/corelib/tools/qversionnumber.h | 115 | ||||
-rw-r--r-- | tests/auto/corelib/tools/qversionnumber/tst_qversionnumber.cpp | 115 |
3 files changed, 425 insertions, 1 deletions
diff --git a/src/corelib/tools/qversionnumber.cpp b/src/corelib/tools/qversionnumber.cpp index 58e3c15560..0883896751 100644 --- a/src/corelib/tools/qversionnumber.cpp +++ b/src/corelib/tools/qversionnumber.cpp @@ -556,5 +556,199 @@ uint qHash(const QVersionNumber &key, uint seed) return seed; } -QT_END_NAMESPACE +/*! + \class QTypeRevision + \inmodule QtCore + \since 6.0 + \brief The QTypeRevision class contains a lightweight representation of + a version number with two 8-bit segments, major and minor, either + of which can be unknown. + + Use this class to describe revisions of a type. Compatible revisions can be + expressed as increments of the minor version. Breaking changes can be + expressed as increments of the major version. The return values of + \l QMetaMethod::revision() and \l QMetaProperty::revision() can be passed to + \l QTypeRevision::fromEncodedVersion(). The resulting major and minor versions + specify in which Qt versions the properties and methods were added. + + \sa QMetaMethod::revision(), QMetaProperty::revision() +*/ + +/*! + \fn template<typename Integer> static bool QTypeRevision::isValidSegment(Integer segment) + + Returns true if the given number can be used as either major or minor + version in a QTypeRevision. Valid segments need to be \c {>= 0} and \c {< 255}. +*/ + +/*! + \fn QTypeRevision::QTypeRevision() + + Produces an invalid revision. + + \sa isValid() +*/ + +/*! + \fn template <typename Major, typename Minor> static QTypeRevision QTypeRevision::fromVersion(Major majorVersion, Minor minorVersion) + + Produces a QTypeRevision from the given \a majorVersion and \a minorVersion, + both of which need to be a valid segments. + + \sa isValidSegment() +*/ + +/*! + \fn template <typename Major> static QTypeRevision QTypeRevision::fromMajorVersion(Major majorVersion) + + Produces a QTypeRevision from the given \a majorVersion with an invalid minor + version. \a majorVersion needs to be a valid segment. + + \sa isValidSegment() +*/ + +/*! + \fn template <typename Minor> static QTypeRevision QTypeRevision::fromMinorVersion(Minor minorVersion) + + Produces a QTypeRevision from the given \a minorVersion with an invalid major + version. \a minorVersion needs to be a valid segment. + + \sa isValidSegment() +*/ + +/*! + \fn template <typename Integer> static QTypeRevision QTypeRevision::fromEncodedVersion(Integer value) + + Produces a QTypeRevision from the given \a value. \a value encodes both the + minor and major versions in the least significant and second least + significant byte, respectively. + + \a value must not have any bits outside the least significant two bytes set. + \c Integer needs to be at least 16 bits wide, and must not have a sign bit + in the least significant 16 bits. + + \sa toEncodedVersion() +*/ + +/*! + \fn static QTypeRevision QTypeRevision::zero() + + Produces a QTypeRevision with major and minor version \c{0}. +*/ + +/*! + \fn bool QTypeRevision::hasMajorVersion() const + + Returns true if the major version is known, otherwise false. + + \sa majorVersion(), hasMinorVersion() +*/ + +/*! + \fn quint8 QTypeRevision::majorVersion() const + + Returns the major version encoded in the revision. + + \sa hasMajorVersion(), minorVersion() +*/ + +/*! + \fn bool QTypeRevision::hasMinorVersion() const + + Returns true if the minor version is known, otherwise false. + + \sa minorVersion(), hasMajorVersion() +*/ +/*! + \fn quint8 QTypeRevision::minorVersion() const + + Returns the minor version encoded in the revision. + + \sa hasMinorVersion(), majorVersion() +*/ + +/*! + \fn bool QTypeRevision::isValid() const + + Returns true if the major version or the minor version is known, + otherwise false. + + \sa hasMajorVersion(), hasMinorVersion() +*/ + +/*! + \fn template<typename Integer> Integer QTypeRevision::toEncodedVersion() const + + Transforms the revision into an integer value, encoding the minor + version into the least significant byte, and the major version into + the second least significant byte. + + \c Integer needs to be at least 16 bits wide, and must not have a sign bit + in the least significant 16 bits. + + \sa fromEncodedVersion() +*/ + +#ifndef QT_NO_DATASTREAM +/*! + \fn QDataStream& operator<<(QDataStream &out, const QTypeRevision &revision) + \relates QTypeRevision + \since 6.0 + + Writes the revision \a revision to stream \a out. + */ +QDataStream& operator<<(QDataStream &out, const QTypeRevision &revision) +{ + return out << revision.toEncodedVersion<quint16>(); +} + +/*! + \fn QDataStream& operator>>(QDataStream &in, QTypeRevision &revision) + \relates QTypeRevision + \since 6.0 + + Reads a revision from stream \a in and stores it in \a revision. + */ +QDataStream& operator>>(QDataStream &in, QTypeRevision &revision) +{ + quint16 value; + in >> value; + revision = QTypeRevision::fromEncodedVersion(value); + return in; +} +#endif + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug debug, const QTypeRevision &revision) +{ + QDebugStateSaver saver(debug); + if (revision.hasMajorVersion()) { + if (revision.hasMinorVersion()) + debug.nospace() << revision.majorVersion() << '.' << revision.minorVersion(); + else + debug.nospace().noquote() << revision.majorVersion() << ".x"; + } else { + if (revision.hasMinorVersion()) + debug << revision.minorVersion(); + else + debug.noquote() << "invalid"; + } + return debug; +} +#endif + +/*! + \fn uint qHash(const QTypeRevision &key, uint seed) + \relates QHash + \since 6.0 + + Returns the hash value for the \a key, using \a seed to seed the + calculation. +*/ +uint qHash(const QTypeRevision &key, uint seed) +{ + return qHash(key.toEncodedVersion<quint16>(), seed); +} + +QT_END_NAMESPACE diff --git a/src/corelib/tools/qversionnumber.h b/src/corelib/tools/qversionnumber.h index d43b86ba51..9fd4242859 100644 --- a/src/corelib/tools/qversionnumber.h +++ b/src/corelib/tools/qversionnumber.h @@ -309,8 +309,123 @@ Q_REQUIRED_RESULT inline bool operator==(const QVersionNumber &lhs, const QVersi Q_REQUIRED_RESULT inline bool operator!=(const QVersionNumber &lhs, const QVersionNumber &rhs) noexcept { return QVersionNumber::compare(lhs, rhs) != 0; } +class QTypeRevision; +Q_CORE_EXPORT uint qHash(const QTypeRevision &key, uint seed = 0); + +#ifndef QT_NO_DATASTREAM +Q_CORE_EXPORT QDataStream& operator<<(QDataStream &out, const QTypeRevision &revision); +Q_CORE_EXPORT QDataStream& operator>>(QDataStream &in, QTypeRevision &revision); +#endif + +class QTypeRevision +{ +public: + template<typename Integer> + using if_valid_segment_type = typename std::enable_if< + std::is_integral<Integer>::value, bool>::type; + + template<typename Integer> + using if_valid_value_type = typename std::enable_if< + std::is_integral<Integer>::value + && (sizeof(Integer) > sizeof(quint16) + || (sizeof(Integer) == sizeof(quint16) + && !std::is_signed<Integer>::value)), bool>::type; + + template<typename Integer, if_valid_segment_type<Integer> = true> + static constexpr bool isValidSegment(Integer segment) + { + return segment >= Integer(0) && segment < Integer(SegmentUnknown); + } + + static constexpr bool isValidSegment(qint8 segment) { return segment >= 0; } + + template<typename Major, typename Minor, + if_valid_segment_type<Major> = true, + if_valid_segment_type<Minor> = true> + static constexpr QTypeRevision fromVersion(Major majorVersion, Minor minorVersion) + { + Q_ASSERT(isValidSegment(majorVersion)); + Q_ASSERT(isValidSegment(minorVersion)); + return QTypeRevision(quint8(majorVersion), quint8(minorVersion)); + } + + template<typename Major, if_valid_segment_type<Major> = true> + static constexpr QTypeRevision fromMajorVersion(Major majorVersion) + { + Q_ASSERT(isValidSegment(majorVersion)); + return QTypeRevision(quint8(majorVersion), SegmentUnknown); + } + + template<typename Minor, if_valid_segment_type<Minor> = true> + static constexpr QTypeRevision fromMinorVersion(Minor minorVersion) + { + Q_ASSERT(isValidSegment(minorVersion)); + return QTypeRevision(SegmentUnknown, quint8(minorVersion)); + } + + template<typename Integer, if_valid_value_type<Integer> = true> + static constexpr QTypeRevision fromEncodedVersion(Integer value) + { + Q_ASSERT((value & ~Integer(0xffff)) == Integer(0)); + return QTypeRevision((value & Integer(0xff00)) >> 8, value & Integer(0xff)); + } + + static constexpr QTypeRevision zero() { return QTypeRevision(0, 0); } + + constexpr QTypeRevision() = default; + + constexpr bool hasMajorVersion() const { return m_majorVersion != SegmentUnknown; } + constexpr quint8 majorVersion() const { return m_majorVersion; } + + constexpr bool hasMinorVersion() const { return m_minorVersion != SegmentUnknown; } + constexpr quint8 minorVersion() const { return m_minorVersion; } + + constexpr bool isValid() const { return hasMajorVersion() || hasMinorVersion(); } + + template<typename Integer, if_valid_value_type<Integer> = true> + constexpr Integer toEncodedVersion() const + { + return Integer(m_majorVersion << 8) | Integer(m_minorVersion); + } + +private: + enum { SegmentUnknown = quint8(~0U) }; + +#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN + constexpr QTypeRevision(quint8 major, quint8 minor) + : m_minorVersion(minor), m_majorVersion(major) {} + + quint8 m_minorVersion = SegmentUnknown; + quint8 m_majorVersion = SegmentUnknown; +#else + constexpr QTypeRevision(quint8 major, quint8 minor) + : m_majorVersion(major), m_minorVersion(minor) {} + + quint8 m_majorVersion = SegmentUnknown; + quint8 m_minorVersion = SegmentUnknown; +#endif +}; + +inline constexpr bool operator==(QTypeRevision lhs, QTypeRevision rhs) +{ + return lhs.toEncodedVersion<quint16>() == rhs.toEncodedVersion<quint16>(); +} + +inline constexpr bool operator!=(QTypeRevision lhs, QTypeRevision rhs) +{ + return lhs.toEncodedVersion<quint16>() != rhs.toEncodedVersion<quint16>(); +} + +Q_STATIC_ASSERT(sizeof(QTypeRevision) == 2); +Q_DECLARE_TYPEINFO(QTypeRevision, Q_MOVABLE_TYPE); + +#ifndef QT_NO_DEBUG_STREAM +Q_CORE_EXPORT QDebug operator<<(QDebug, const QTypeRevision &revision); +#endif + QT_END_NAMESPACE Q_DECLARE_METATYPE(QVersionNumber) +Q_DECLARE_METATYPE(QTypeRevision) #endif //QVERSIONNUMBER_H diff --git a/tests/auto/corelib/tools/qversionnumber/tst_qversionnumber.cpp b/tests/auto/corelib/tools/qversionnumber/tst_qversionnumber.cpp index 7c4d1071ce..63a6618dab 100644 --- a/tests/auto/corelib/tools/qversionnumber/tst_qversionnumber.cpp +++ b/tests/auto/corelib/tools/qversionnumber/tst_qversionnumber.cpp @@ -81,6 +81,9 @@ private slots: void serialize(); void moveSemantics(); void qtVersion(); + void qPropertyRevision_data(); + void qPropertyRevision(); + void qPropertyRevisionTypes(); }; void tst_QVersionNumber::singleInstanceData() @@ -645,6 +648,118 @@ void tst_QVersionNumber::qtVersion() QCOMPARE(v.toString(), QString(qVersion())); } +template<typename Integer> +void compileTestRevisionMajorMinor() +{ + const Integer major = 8; + const Integer minor = 4; + + const QTypeRevision r2 = QTypeRevision::fromVersion(major, minor); + QCOMPARE(r2.majorVersion(), 8); + QCOMPARE(r2.minorVersion(), 4); + + const QTypeRevision r3 = QTypeRevision::fromMajorVersion(major); + QCOMPARE(r3.majorVersion(), 8); + QVERIFY(!r3.hasMinorVersion()); + + const QTypeRevision r4 = QTypeRevision::fromMinorVersion(minor); + QVERIFY(!r4.hasMajorVersion()); + QCOMPARE(r4.minorVersion(), 4); +} + + +template<typename Integer> +void compileTestRevision() +{ + if (std::is_signed<Integer>::value) + compileTestRevision<typename QIntegerForSize<sizeof(Integer) / 2>::Signed>(); + else + compileTestRevision<typename QIntegerForSize<sizeof(Integer) / 2>::Unsigned>(); + + const Integer value = 0x0510; + const QTypeRevision r = QTypeRevision::fromEncodedVersion(value); + + QCOMPARE(r.majorVersion(), 5); + QCOMPARE(r.minorVersion(), 16); + QCOMPARE(r.toEncodedVersion<Integer>(), value); + + compileTestRevisionMajorMinor<Integer>(); +} + +template<> +void compileTestRevision<qint16>() +{ + compileTestRevisionMajorMinor<quint8>(); +} + +template<> +void compileTestRevision<quint8>() +{ + compileTestRevisionMajorMinor<quint8>(); +} + +template<> +void compileTestRevision<qint8>() +{ + compileTestRevisionMajorMinor<qint8>(); +} + +void tst_QVersionNumber::qPropertyRevision_data() +{ + QTest::addColumn<QTypeRevision>("revision"); + QTest::addColumn<bool>("valid"); + QTest::addColumn<int>("major"); + QTest::addColumn<int>("minor"); + + QTest::addRow("Qt revision") << QTypeRevision::fromVersion(QT_VERSION_MAJOR, QT_VERSION_MINOR) + << true << QT_VERSION_MAJOR << QT_VERSION_MINOR; + QTest::addRow("invalid") << QTypeRevision() << false << 0xff << 0xff; + QTest::addRow("major") << QTypeRevision::fromMajorVersion(6) << true << 6 << 0xff; + QTest::addRow("minor") << QTypeRevision::fromMinorVersion(15) << true << 0xff << 15; + QTest::addRow("zero") << QTypeRevision::fromVersion(0, 0) << true << 0 << 0; + + // We're intentionally not testing negative numbers. + // There are asserts against negative numbers in QTypeRevision. + // You must not pass them as major or minor versions, or values. +} + +void tst_QVersionNumber::qPropertyRevision() +{ + const QTypeRevision other = QTypeRevision::fromVersion(127, 128); + + QFETCH(QTypeRevision, revision); + + QFETCH(bool, valid); + QFETCH(int, major); + QFETCH(int, minor); + + QCOMPARE(revision.isValid(), valid); + QCOMPARE(revision.majorVersion(), major); + QCOMPARE(revision.minorVersion(), minor); + + QCOMPARE(revision.hasMajorVersion(), QTypeRevision::isValidSegment(major)); + QCOMPARE(revision.hasMinorVersion(), QTypeRevision::isValidSegment(minor)); + + const QTypeRevision copy = QTypeRevision::fromEncodedVersion(revision.toEncodedVersion<int>()); + QCOMPARE(copy, revision); + + QVERIFY(revision != other); + QVERIFY(copy != other); +} + +void tst_QVersionNumber::qPropertyRevisionTypes() +{ + compileTestRevision<quint64>(); + compileTestRevision<qint64>(); + + QVERIFY(!QTypeRevision::isValidSegment(0xff)); + QVERIFY(!QTypeRevision::isValidSegment(-1)); + + const QTypeRevision maxRevision = QTypeRevision::fromVersion(254, 254); + QVERIFY(maxRevision.hasMajorVersion()); + QVERIFY(maxRevision.hasMinorVersion()); +} + QTEST_APPLESS_MAIN(tst_QVersionNumber) #include "tst_qversionnumber.moc" |