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>
This commit is contained in:
Ulf Hermann 2020-01-21 13:23:34 +01:00
parent daf1f3f9ce
commit ed080c64ae
3 changed files with 425 additions and 1 deletions

View File

@ -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

View File

@ -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

View File

@ -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"