Revision control
++ // Copyright (C) 2020 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:significant
++
++ #ifndef QJSPRIMITIVEVALUE_H
++ #define QJSPRIMITIVEVALUE_H
++
++ #include <QtQml/qtqmlglobal.h>
++ #include <QtQml/qjsnumbercoercion.h>
++
++ #include <QtCore/qstring.h>
++ #include <QtCore/qnumeric.h>
++ #include <QtCore/qvariant.h>
++
++ #include <variant>
++ #include <cmath>
++
++ QT_BEGIN_NAMESPACE
++
++ namespace QV4 { struct ExecutionEngine; }
++
++ struct QJSPrimitiveUndefined {};
++ struct QJSPrimitiveNull {};
++
++ class QJSPrimitiveValue
++ {
++ template<typename Concrete>
++ struct StringNaNOperators
++ {
++ static constexpr double op(const QString &, QJSPrimitiveUndefined)
++ {
++ return std::numeric_limits<double>::quiet_NaN();
++ }
++
++ static constexpr double op(QJSPrimitiveUndefined, const QString &)
++ {
++ return std::numeric_limits<double>::quiet_NaN();
++ }
++
++ static double op(const QString &lhs, QJSPrimitiveNull) { return op(lhs, 0); }
++ static double op(QJSPrimitiveNull, const QString &rhs) { return op(0, rhs); }
++
++ template<typename T>
++ static double op(const QString &lhs, T rhs)
++ {
++ return Concrete::op(numberFromString(lhs).toDouble(), rhs);
++ }
++
++ template<typename T>
++ static double op(T lhs, const QString &rhs)
++ {
++ return Concrete::op(lhs, numberFromString(rhs).toDouble());
++ }
++
++ static double op(const QString &lhs, const QString &rhs)
++ {
++ return Concrete::op(numberFromString(lhs).toDouble(), numberFromString(rhs).toDouble());
++ }
++ };
++
++ struct AddOperators {
++ static constexpr double op(double lhs, double rhs) { return lhs + rhs; }
++ static bool opOverflow(int lhs, int rhs, int *result)
++ {
++ return qAddOverflow(lhs, rhs, result);
++ }
++
++ template<typename T>
++ static QString op(const QString &lhs, T rhs)
++ {
++ return lhs + QJSPrimitiveValue(rhs).toString();
++ }
++
++ template<typename T>
++ static QString op(T lhs, const QString &rhs)
++ {
++ return QJSPrimitiveValue(lhs).toString() + rhs;
++ }
++
++ static QString op(const QString &lhs, const QString &rhs) { return lhs + rhs; }
++ };
++
++ struct SubOperators : private StringNaNOperators<SubOperators> {
++ static constexpr double op(double lhs, double rhs) { return lhs - rhs; }
++ static bool opOverflow(int lhs, int rhs, int *result)
++ {
++ return qSubOverflow(lhs, rhs, result);
++ }
++
++ using StringNaNOperators::op;
++ };
++
++ struct MulOperators : private StringNaNOperators<MulOperators> {
++ static constexpr double op(double lhs, double rhs) { return lhs * rhs; }
++ static bool opOverflow(int lhs, int rhs, int *result)
++ {
++ // compare mul_int32 in qv4math_p.h
++ auto hadOverflow = qMulOverflow(lhs, rhs, result);
++ if (((lhs < 0) ^ (rhs < 0)) && (*result == 0))
++ return true; // result must be negative 0, does not fit into int
++ return hadOverflow;
++ }
++
++ using StringNaNOperators::op;
++ };
++
++ struct DivOperators : private StringNaNOperators<DivOperators> {
++ static constexpr double op(double lhs, double rhs) {
++ // Without is_iec559, we don't get proper JS semantics
++ #ifndef Q_OS_INTEGRITY
++ static_assert(std::numeric_limits<double>::is_iec559);
++ #endif
++ QT_WARNING_PUSH
++ // divide by zero: not an issue with iec559
++ QT_WARNING_DISABLE_MSVC(4723)
++ return lhs / rhs;
++ QT_WARNING_POP
++ }
++ static constexpr bool opOverflow(int, int, int *)
++ {
++ return true;
++ }
++
++ using StringNaNOperators::op;
++ };
++
++ public:
++ enum Type : quint8 {
++ Undefined,
++ Null,
++ Boolean,
++ Integer,
++ Double,
++ String
++ };
++
++ constexpr Type type() const { return Type(d.type()); }
++
++ // Prevent casting from Type to int
++ QJSPrimitiveValue(Type) = delete;
++
++ Q_IMPLICIT constexpr QJSPrimitiveValue() noexcept = default;
++ Q_IMPLICIT constexpr QJSPrimitiveValue(QJSPrimitiveUndefined undefined) noexcept : d(undefined) {}
++ Q_IMPLICIT constexpr QJSPrimitiveValue(QJSPrimitiveNull null) noexcept : d(null) {}
++ Q_IMPLICIT constexpr QJSPrimitiveValue(bool value) noexcept : d(value) {}
++ Q_IMPLICIT constexpr QJSPrimitiveValue(int value) noexcept : d(value) {}
++ Q_IMPLICIT constexpr QJSPrimitiveValue(double value) noexcept : d(value) {}
++ Q_IMPLICIT QJSPrimitiveValue(QString string) noexcept : d(std::move(string)) {}
++
++ explicit QJSPrimitiveValue(const QMetaType type, const void *value) noexcept
++ {
++ switch (type.id()) {
++ case QMetaType::UnknownType:
++ d = QJSPrimitiveUndefined();
++ break;
++ case QMetaType::Nullptr:
++ d = QJSPrimitiveNull();
++ break;
++ case QMetaType::Bool:
++ d = *static_cast<const bool *>(value);
++ break;
++ case QMetaType::Int:
++ d = *static_cast<const int *>(value);
++ break;
++ case QMetaType::Double:
++ d = *static_cast<const double *>(value);
++ break;
++ case QMetaType::QString:
++ d = *static_cast<const QString *>(value);
++ break;
++ default:
++ // Unsupported. Remains undefined.
++ break;
++ }
++ }
++
++ explicit QJSPrimitiveValue(QMetaType type) noexcept
++ {
++ switch (type.id()) {
++ case QMetaType::UnknownType:
++ d = QJSPrimitiveUndefined();
++ break;
++ case QMetaType::Nullptr:
++ d = QJSPrimitiveNull();
++ break;
++ case QMetaType::Bool:
++ d = false;
++ break;
++ case QMetaType::Int:
++ d = 0;
++ break;
++ case QMetaType::Double:
++ d = 0.0;
++ break;
++ case QMetaType::QString:
++ d = QString();
++ break;
++ default:
++ // Unsupported. Remains undefined.
++ break;
++ }
++ }
++
++ explicit QJSPrimitiveValue(const QVariant &variant) noexcept
++ : QJSPrimitiveValue(variant.metaType(), variant.data())
++ {
++ }
++
++ constexpr QMetaType metaType() const { return d.metaType(); }
++ constexpr void *data() { return d.data(); }
++ constexpr const void *data() const { return d.data(); }
++ constexpr const void *constData() const { return d.data(); }
++
++ template<Type type>
++ QJSPrimitiveValue to() const {
++ if constexpr (type == Undefined)
++ return QJSPrimitiveUndefined();
++ if constexpr (type == Null)
++ return QJSPrimitiveNull();
++ if constexpr (type == Boolean)
++ return toBoolean();
++ if constexpr (type == Integer)
++ return toInteger();
++ if constexpr (type == Double)
++ return toDouble();
++ if constexpr (type == String)
++ return toString();
++
++ Q_UNREACHABLE_RETURN(QJSPrimitiveUndefined());
++ }
++
++ constexpr bool toBoolean() const
++ {
++ switch (type()) {
++ case Undefined: return false;
++ case Null: return false;
++ case Boolean: return asBoolean();
++ case Integer: return asInteger() != 0;
++ case Double: {
++ const double v = asDouble();
++ return !QJSNumberCoercion::equals(v, 0) && !std::isnan(v);
++ }
++ case String: return !asString().isEmpty();
++ }
++
++ // GCC 8.x does not treat __builtin_unreachable() as constexpr
++ #if !defined(Q_CC_GNU_ONLY) || (Q_CC_GNU >= 900)
++ Q_UNREACHABLE_RETURN(false);
++ #else
++ return false;
++ #endif
++ }
++
++ constexpr int toInteger() const
++ {
++ switch (type()) {
++ case Undefined: return 0;
++ case Null: return 0;
++ case Boolean: return asBoolean();
++ case Integer: return asInteger();
++ case Double: return QJSNumberCoercion::toInteger(asDouble());
++ case String: return numberFromString(asString()).toInteger();
++ }
++
++ // GCC 8.x does not treat __builtin_unreachable() as constexpr
++ #if !defined(Q_CC_GNU_ONLY) || (Q_CC_GNU >= 900)
++ Q_UNREACHABLE_RETURN(0);
++ #else
++ return 0;
++ #endif
++ }
++
++ constexpr double toDouble() const
++ {
++ switch (type()) {
++ case Undefined: return std::numeric_limits<double>::quiet_NaN();
++ case Null: return 0;
++ case Boolean: return asBoolean();
++ case Integer: return asInteger();
++ case Double: return asDouble();
++ case String: return numberFromString(asString()).toDouble();
++ }
++
++ // GCC 8.x does not treat __builtin_unreachable() as constexpr
++ #if !defined(Q_CC_GNU_ONLY) || (Q_CC_GNU >= 900)
++ Q_UNREACHABLE_RETURN({});
++ #else
++ return {};
++ #endif
++ }
++
++ QString toString() const
++ {
++ switch (type()) {
++ case Undefined: return QStringLiteral("undefined");
++ case Null: return QStringLiteral("null");
++ case Boolean: return asBoolean() ? QStringLiteral("true") : QStringLiteral("false");
++ case Integer: return QString::number(asInteger());
++ case Double: {
++ const double result = asDouble();
++ if (std::isnan(result))
++ return QStringLiteral("NaN");
++ if (std::isfinite(result))
++ return toString(result);
++ if (result > 0)
++ return QStringLiteral("Infinity");
++ return QStringLiteral("-Infinity");
++ }
++ case String: return asString();
++ }
++
++ Q_UNREACHABLE_RETURN(QString());
++ }
++
++ QVariant toVariant() const
++ {
++ switch (type()) {
++ case Undefined: return QVariant();
++ case Null: return QVariant::fromValue<std::nullptr_t>(nullptr);
++ case Boolean: return QVariant(asBoolean());
++ case Integer: return QVariant(asInteger());
++ case Double: return QVariant(asDouble());
++ case String: return QVariant(asString());
++ }
++
++ Q_UNREACHABLE_RETURN(QVariant());
++ }
++
++ friend inline QJSPrimitiveValue operator+(const QJSPrimitiveValue &lhs,
++ const QJSPrimitiveValue &rhs)
++ {
++ return operate<AddOperators>(lhs, rhs);
++ }
++
++ friend inline QJSPrimitiveValue operator-(const QJSPrimitiveValue &lhs,
++ const QJSPrimitiveValue &rhs)
++ {
++ return operate<SubOperators>(lhs, rhs);
++ }
++
++ friend inline QJSPrimitiveValue operator*(const QJSPrimitiveValue &lhs,
++ const QJSPrimitiveValue &rhs)
++ {
++ return operate<MulOperators>(lhs, rhs);
++ }
++
++ friend inline QJSPrimitiveValue operator/(const QJSPrimitiveValue &lhs,
++ const QJSPrimitiveValue &rhs)
++ {
++ return operate<DivOperators>(lhs, rhs);
++ }
++
++ friend inline QJSPrimitiveValue operator%(const QJSPrimitiveValue &lhs,
++ const QJSPrimitiveValue &rhs)
++ {
++ switch (lhs.type()) {
++ case Null:
++ case Boolean:
++ case Integer:
++ switch (rhs.type()) {
++ case Boolean:
++ case Integer: {
++ const int leftInt = lhs.toInteger();
++ const int rightInt = rhs.toInteger();
++ if (leftInt >= 0 && rightInt > 0)
++ return leftInt % rightInt;
++ Q_FALLTHROUGH();
++ }
++ case Undefined:
++ case Null:
++ case Double:
++ case String:
++ break;
++ }
++ Q_FALLTHROUGH();
++ case Undefined:
++ case Double:
++ case String:
++ break;
++ }
++
++ return std::fmod(lhs.toDouble(), rhs.toDouble());
++ }
++
++ QJSPrimitiveValue &operator++()
++ {
++ // ++a is modeled as a -= (-1) to avoid the potential string concatenation
++ return (*this = operate<SubOperators>(*this, -1));
++ }
++
++ QJSPrimitiveValue operator++(int)
++ {
++ // a++ is modeled as a -= (-1) to avoid the potential string concatenation
++ QJSPrimitiveValue other = operate<SubOperators>(*this, -1);
++ std::swap(other, *this);
++ return +other; // We still need to coerce the original value.
++ }
++
++ QJSPrimitiveValue &operator--()
++ {
++ return (*this = operate<SubOperators>(*this, 1));
++ }
++
++ QJSPrimitiveValue operator--(int)
++ {
++ QJSPrimitiveValue other = operate<SubOperators>(*this, 1);
++ std::swap(other, *this);
++ return +other; // We still need to coerce the original value.
++ }
++
++ QJSPrimitiveValue operator+()
++ {
++ // +a is modeled as a -= 0. That should force it to number.
++ return (*this = operate<SubOperators>(*this, 0));
++ }
++
++ QJSPrimitiveValue operator-()
++ {
++ return (*this = operate<MulOperators>(*this, -1));
++ }
++
++ constexpr bool strictlyEquals(const QJSPrimitiveValue &other) const
++ {
++ const Type myType = type();
++ const Type otherType = other.type();
++
++ if (myType != otherType) {
++ // int -> double promotion is OK in strict mode
++ if (myType == Double && otherType == Integer)
++ return strictlyEquals(double(other.asInteger()));
++ if (myType == Integer && otherType == Double)
++ return QJSPrimitiveValue(double(asInteger())).strictlyEquals(other);
++ return false;
++ }
++
++ switch (myType) {
++ case Undefined:
++ case Null:
++ return true;
++ case Boolean:
++ return asBoolean() == other.asBoolean();
++ case Integer:
++ return asInteger() == other.asInteger();
++ case Double: {
++ const double l = asDouble();
++ const double r = other.asDouble();
++ if (std::isnan(l) || std::isnan(r))
++ return false;
++ if (qIsNull(l) && qIsNull(r))
++ return true;
++ return QJSNumberCoercion::equals(l, r);
++ }
++ case String:
++ return asString() == other.asString();
++ }
++
++ return false;
++ }
++
++ // Loose operator==, in contrast to strict ===
++ constexpr bool equals(const QJSPrimitiveValue &other) const
++ {
++ const Type myType = type();
++ const Type otherType = other.type();
++
++ if (myType == otherType)
++ return strictlyEquals(other);
++
++ switch (myType) {
++ case Undefined:
++ return otherType == Null;
++ case Null:
++ return otherType == Undefined;
++ case Boolean:
++ return QJSPrimitiveValue(int(asBoolean())).equals(other);
++ case Integer:
++ // prefer rhs bool -> int promotion over promoting both to double
++ return otherType == Boolean
++ ? QJSPrimitiveValue(asInteger()).equals(int(other.asBoolean()))
++ : QJSPrimitiveValue(double(asInteger())).equals(other);
++ case Double:
++ // Promote the other side to double (or recognize lhs as undefined/null)
++ return other.equals(*this);
++ case String:
++ return numberFromString(asString()).parsedEquals(other);
++ }
++
++ return false;
++ }
++
++ friend constexpr inline bool operator==(const QJSPrimitiveValue &lhs, const
++ QJSPrimitiveValue &rhs)
++ {
++ return lhs.strictlyEquals(rhs);
++ }
++
++ friend constexpr inline bool operator!=(const QJSPrimitiveValue &lhs,
++ const QJSPrimitiveValue &rhs)
++ {
++ return !lhs.strictlyEquals(rhs);
++ }
++
++ friend constexpr inline bool operator<(const QJSPrimitiveValue &lhs,
++ const QJSPrimitiveValue &rhs)
++ {
++ switch (lhs.type()) {
++ case Undefined:
++ return false;
++ case Null: {
++ switch (rhs.type()) {
++ case Undefined: return false;
++ case Null: return false;
++ case Boolean: return 0 < int(rhs.asBoolean());
++ case Integer: return 0 < rhs.asInteger();
++ case Double: return double(0) < rhs.asDouble();
++ case String: return double(0) < rhs.toDouble();
++ }
++ break;
++ }
++ case Boolean: {
++ switch (rhs.type()) {
++ case Undefined: return false;
++ case Null: return int(lhs.asBoolean()) < 0;
++ case Boolean: return lhs.asBoolean() < rhs.asBoolean();
++ case Integer: return int(lhs.asBoolean()) < rhs.asInteger();
++ case Double: return double(lhs.asBoolean()) < rhs.asDouble();
++ case String: return double(lhs.asBoolean()) < rhs.toDouble();
++ }
++ break;
++ }
++ case Integer: {
++ switch (rhs.type()) {
++ case Undefined: return false;
++ case Null: return lhs.asInteger() < 0;
++ case Boolean: return lhs.asInteger() < int(rhs.asBoolean());
++ case Integer: return lhs.asInteger() < rhs.asInteger();
++ case Double: return double(lhs.asInteger()) < rhs.asDouble();
++ case String: return double(lhs.asInteger()) < rhs.toDouble();
++ }
++ break;
++ }
++ case Double: {
++ switch (rhs.type()) {
++ case Undefined: return false;
++ case Null: return lhs.asDouble() < double(0);
++ case Boolean: return lhs.asDouble() < double(rhs.asBoolean());
++ case Integer: return lhs.asDouble() < double(rhs.asInteger());
++ case Double: return lhs.asDouble() < rhs.asDouble();
++ case String: return lhs.asDouble() < rhs.toDouble();
++ }
++ break;
++ }
++ case String: {
++ switch (rhs.type()) {
++ case Undefined: return false;
++ case Null: return lhs.toDouble() < double(0);
++ case Boolean: return lhs.toDouble() < double(rhs.asBoolean());
++ case Integer: return lhs.toDouble() < double(rhs.asInteger());
++ case Double: return lhs.toDouble() < rhs.asDouble();
++ case String: return lhs.asString() < rhs.asString();
++ }
++ break;
++ }
++ }
++
++ return false;
++ }
++
++ friend constexpr inline bool operator>(const QJSPrimitiveValue &lhs, const QJSPrimitiveValue &rhs)
++ {
++ return rhs < lhs;
++ }
++
++ friend constexpr inline bool operator<=(const QJSPrimitiveValue &lhs, const QJSPrimitiveValue &rhs)
++ {
++ if (lhs.type() == String) {
++ if (rhs.type() == String)
++ return lhs.asString() <= rhs.asString();
++ else
++ return numberFromString(lhs.asString()) <= rhs;
++ }
++ if (rhs.type() == String)
++ return lhs <= numberFromString(rhs.asString());
++
++ if (lhs.isNanOrUndefined() || rhs.isNanOrUndefined())
++ return false;
++ return !(lhs > rhs);
++ }
++
++ friend constexpr inline bool operator>=(const QJSPrimitiveValue &lhs, const QJSPrimitiveValue &rhs)
++ {
++ if (lhs.type() == String) {
++ if (rhs.type() == String)
++ return lhs.asString() >= rhs.asString();
++ else
++ return numberFromString(lhs.asString()) >= rhs;
++ }
++ if (rhs.type() == String)
++ return lhs >= numberFromString(rhs.asString());
++
++ if (lhs.isNanOrUndefined() || rhs.isNanOrUndefined())
++ return false;
++ return !(lhs < rhs);
++ }
++
++ private:
++ friend class QJSManagedValue;
++ friend class QJSValue;
++ friend struct QV4::ExecutionEngine;
++
++ constexpr bool asBoolean() const { return d.getBool(); }
++ constexpr int asInteger() const { return d.getInt(); }
++ constexpr double asDouble() const { return d.getDouble(); }
++ QString asString() const { return d.getString(); }
++
++ constexpr bool parsedEquals(const QJSPrimitiveValue &other) const
++ {
++ return type() != Undefined && equals(other);
++ }
++
++ static QJSPrimitiveValue numberFromString(const QString &string)
++ {
++ bool ok;
++ const int intValue = string.toInt(&ok);
++ if (ok)
++ return intValue;
++
++ const double doubleValue = string.toDouble(&ok);
++ if (ok)
++ return doubleValue;
++ if (string.isEmpty())
++ return 0;
++ if (string == QStringLiteral("Infinity"))
++ return std::numeric_limits<double>::infinity();
++ if (string == QStringLiteral("-Infinity"))
++ return -std::numeric_limits<double>::infinity();
++ if (string == QStringLiteral("NaN"))
++ return std::numeric_limits<double>::quiet_NaN();
++ return QJSPrimitiveUndefined();
++ }
++
++ static Q_QML_EXPORT QString toString(double d);
++
++ template<typename Operators, typename Lhs, typename Rhs>
++ static QJSPrimitiveValue operateOnIntegers(const QJSPrimitiveValue &lhs,
++ const QJSPrimitiveValue &rhs)
++ {
++ int result;
++ if (Operators::opOverflow(lhs.d.get<Lhs>(), rhs.d.get<Rhs>(), &result))
++ return Operators::op(lhs.d.get<Lhs>(), rhs.d.get<Rhs>());
++ return result;
++ }
++
++ template<typename Operators>
++ static QJSPrimitiveValue operate(const QJSPrimitiveValue &lhs, const QJSPrimitiveValue &rhs)
++ {
++ switch (lhs.type()) {
++ case Undefined:
++ switch (rhs.type()) {
++ case Undefined: return std::numeric_limits<double>::quiet_NaN();
++ case Null: return std::numeric_limits<double>::quiet_NaN();
++ case Boolean: return std::numeric_limits<double>::quiet_NaN();
++ case Integer: return std::numeric_limits<double>::quiet_NaN();
++ case Double: return std::numeric_limits<double>::quiet_NaN();
++ case String: return Operators::op(QJSPrimitiveUndefined(), rhs.asString());
++ }
++ break;
++ case Null:
++ switch (rhs.type()) {
++ case Undefined: return std::numeric_limits<double>::quiet_NaN();
++ case Null: return operateOnIntegers<Operators, int, int>(0, 0);
++ case Boolean: return operateOnIntegers<Operators, int, bool>(0, rhs);
++ case Integer: return operateOnIntegers<Operators, int, int>(0, rhs);
++ case Double: return Operators::op(0, rhs.asDouble());
++ case String: return Operators::op(QJSPrimitiveNull(), rhs.asString());
++ }
++ break;
++ case Boolean:
++ switch (rhs.type()) {
++ case Undefined: return std::numeric_limits<double>::quiet_NaN();
++ case Null: return operateOnIntegers<Operators, bool, int>(lhs, 0);
++ case Boolean: return operateOnIntegers<Operators, bool, bool>(lhs, rhs);
++ case Integer: return operateOnIntegers<Operators, bool, int>(lhs, rhs);
++ case Double: return Operators::op(lhs.asBoolean(), rhs.asDouble());
++ case String: return Operators::op(lhs.asBoolean(), rhs.asString());
++ }
++ break;
++ case Integer:
++ switch (rhs.type()) {
++ case Undefined: return std::numeric_limits<double>::quiet_NaN();
++ case Null: return operateOnIntegers<Operators, int, int>(lhs, 0);
++ case Boolean: return operateOnIntegers<Operators, int, bool>(lhs, rhs);
++ case Integer: return operateOnIntegers<Operators, int, int>(lhs, rhs);
++ case Double: return Operators::op(lhs.asInteger(), rhs.asDouble());
++ case String: return Operators::op(lhs.asInteger(), rhs.asString());
++ }
++ break;
++ case Double:
++ switch (rhs.type()) {
++ case Undefined: return std::numeric_limits<double>::quiet_NaN();
++ case Null: return Operators::op(lhs.asDouble(), 0);
++ case Boolean: return Operators::op(lhs.asDouble(), rhs.asBoolean());
++ case Integer: return Operators::op(lhs.asDouble(), rhs.asInteger());
++ case Double: return Operators::op(lhs.asDouble(), rhs.asDouble());
++ case String: return Operators::op(lhs.asDouble(), rhs.asString());
++ }
++ break;
++ case String:
++ switch (rhs.type()) {
++ case Undefined: return Operators::op(lhs.asString(), QJSPrimitiveUndefined());
++ case Null: return Operators::op(lhs.asString(), QJSPrimitiveNull());
++ case Boolean: return Operators::op(lhs.asString(), rhs.asBoolean());
++ case Integer: return Operators::op(lhs.asString(), rhs.asInteger());
++ case Double: return Operators::op(lhs.asString(), rhs.asDouble());
++ case String: return Operators::op(lhs.asString(), rhs.asString());
++ }
++ break;
++ }
++
++ Q_UNREACHABLE_RETURN(QJSPrimitiveUndefined());
++ }
++
++ constexpr bool isNanOrUndefined() const
++ {
++ switch (type()) {
++ case Undefined: return true;
++ case Null: return false;
++ case Boolean: return false;
++ case Integer: return false;
++ case Double: return std::isnan(asDouble());
++ case String: return false;
++ }
++ // GCC 8.x does not treat __builtin_unreachable() as constexpr
++ #if !defined(Q_CC_GNU_ONLY) || (Q_CC_GNU >= 900)
++ Q_UNREACHABLE_RETURN(false);
++ #else
++ return false;
++ #endif
++ }
++
++ struct QJSPrimitiveValuePrivate
++ {
++ // Can't be default because QString has a non-trivial ctor.
++ constexpr QJSPrimitiveValuePrivate() noexcept {}
++
++ Q_IMPLICIT constexpr QJSPrimitiveValuePrivate(QJSPrimitiveUndefined) noexcept {}
++ Q_IMPLICIT constexpr QJSPrimitiveValuePrivate(QJSPrimitiveNull) noexcept
++ : m_type(Null) {}
++ Q_IMPLICIT constexpr QJSPrimitiveValuePrivate(bool b) noexcept
++ : m_bool(b), m_type(Boolean) {}
++ Q_IMPLICIT constexpr QJSPrimitiveValuePrivate(int i) noexcept
++ : m_int(i), m_type(Integer) {}
++ Q_IMPLICIT constexpr QJSPrimitiveValuePrivate(double d) noexcept
++ : m_double(d), m_type(Double) {}
++ Q_IMPLICIT QJSPrimitiveValuePrivate(QString s) noexcept
++ : m_string(std::move(s)), m_type(String) {}
++
++ constexpr QJSPrimitiveValuePrivate(const QJSPrimitiveValuePrivate &other) noexcept
++ : m_type(other.m_type)
++ {
++ // Not copy-and-swap since swap() would be much more complicated.
++ if (!assignSimple(other))
++ new (&m_string) QString(other.m_string);
++ }
++
++ constexpr QJSPrimitiveValuePrivate(QJSPrimitiveValuePrivate &&other) noexcept
++ : m_type(other.m_type)
++ {
++ // Not move-and-swap since swap() would be much more complicated.
++ if (!assignSimple(other))
++ new (&m_string) QString(std::move(other.m_string));
++ }
++
++ constexpr QJSPrimitiveValuePrivate &operator=(const QJSPrimitiveValuePrivate &other) noexcept
++ {
++ if (this == &other)
++ return *this;
++
++ if (m_type == String) {
++ if (other.m_type == String) {
++ m_type = other.m_type;
++ m_string = other.m_string;
++ return *this;
++ }
++ m_string.~QString();
++ }
++
++ m_type = other.m_type;
++ if (!assignSimple(other))
++ new (&m_string) QString(other.m_string);
++ return *this;
++ }
++
++ constexpr QJSPrimitiveValuePrivate &operator=(QJSPrimitiveValuePrivate &&other) noexcept
++ {
++ if (this == &other)
++ return *this;
++
++ if (m_type == String) {
++ if (other.m_type == String) {
++ m_type = other.m_type;
++ m_string = std::move(other.m_string);
++ return *this;
++ }
++ m_string.~QString();
++ }
++
++ m_type = other.m_type;
++ if (!assignSimple(other))
++ new (&m_string) QString(std::move(other.m_string));
++ return *this;
++ }
++
++ ~QJSPrimitiveValuePrivate()
++ {
++ if (m_type == String)
++ m_string.~QString();
++ }
++
++ constexpr Type type() const noexcept { return m_type; }
++ constexpr bool getBool() const noexcept { return m_bool; }
++ constexpr int getInt() const noexcept { return m_int; }
++ constexpr double getDouble() const noexcept { return m_double; }
++ QString getString() const noexcept { return m_string; }
++
++ template<typename T>
++ constexpr T get() const noexcept {
++ if constexpr (std::is_same_v<T, QJSPrimitiveUndefined>)
++ return QJSPrimitiveUndefined();
++ else if constexpr (std::is_same_v<T, QJSPrimitiveNull>)
++ return QJSPrimitiveNull();
++ else if constexpr (std::is_same_v<T, bool>)
++ return getBool();
++ else if constexpr (std::is_same_v<T, int>)
++ return getInt();
++ else if constexpr (std::is_same_v<T, double>)
++ return getDouble();
++ else if constexpr (std::is_same_v<T, QString>)
++ return getString();
++
++ // GCC 8.x does not treat __builtin_unreachable() as constexpr
++ #if !defined(Q_CC_GNU_ONLY) || (Q_CC_GNU >= 900)
++ Q_UNREACHABLE_RETURN(T());
++ #else
++ return T();
++ #endif
++ }
++
++ constexpr QMetaType metaType() const noexcept {
++ switch (m_type) {
++ case Undefined:
++ return QMetaType();
++ case Null:
++ return QMetaType::fromType<std::nullptr_t>();
++ case Boolean:
++ return QMetaType::fromType<bool>();
++ case Integer:
++ return QMetaType::fromType<int>();
++ case Double:
++ return QMetaType::fromType<double>();
++ case String:
++ return QMetaType::fromType<QString>();
++ }
++
++ // GCC 8.x does not treat __builtin_unreachable() as constexpr
++ #if !defined(Q_CC_GNU_ONLY) || (Q_CC_GNU >= 900)
++ Q_UNREACHABLE_RETURN(QMetaType());
++ #else
++ return QMetaType();
++ #endif
++ }
++
++ constexpr void *data() noexcept {
++ switch (m_type) {
++ case Undefined:
++ case Null:
++ return nullptr;
++ case Boolean:
++ return &m_bool;
++ case Integer:
++ return &m_int;
++ case Double:
++ return &m_double;
++ case String:
++ return &m_string;
++ }
++
++ // GCC 8.x does not treat __builtin_unreachable() as constexpr
++ #if !defined(Q_CC_GNU_ONLY) || (Q_CC_GNU >= 900)
++ Q_UNREACHABLE_RETURN(nullptr);
++ #else
++ return nullptr;
++ #endif
++ }
++
++ constexpr const void *data() const noexcept {
++ switch (m_type) {
++ case Undefined:
++ case Null:
++ return nullptr;
++ case Boolean:
++ return &m_bool;
++ case Integer:
++ return &m_int;
++ case Double:
++ return &m_double;
++ case String:
++ return &m_string;
++ }
++
++ // GCC 8.x does not treat __builtin_unreachable() as constexpr
++ #if !defined(Q_CC_GNU_ONLY) || (Q_CC_GNU >= 900)
++ Q_UNREACHABLE_RETURN(nullptr);
++ #else
++ return nullptr;
++ #endif
++ }
++
++ private:
++ constexpr bool assignSimple(const QJSPrimitiveValuePrivate &other) noexcept
++ {
++ switch (other.m_type) {
++ case Undefined:
++ case Null:
++ return true;
++ case Boolean:
++ m_bool = other.m_bool;
++ return true;
++ case Integer:
++ m_int = other.m_int;
++ return true;
++ case Double:
++ m_double = other.m_double;
++ return true;
++ case String:
++ return false;
++ }
++
++ // GCC 8.x does not treat __builtin_unreachable() as constexpr
++ #if !defined(Q_CC_GNU_ONLY) || (Q_CC_GNU >= 900)
++ Q_UNREACHABLE_RETURN(false);
++ #else
++ return false;
++ #endif
++ }
++
++ union {
++ bool m_bool;
++ int m_int;
++ double m_double;
++ QString m_string;
++
++ // Dummy value to trigger initialization of the whole storage.
++ // We don't want to see maybe-uninitialized warnings every time we access m_string.
++ std::byte m_zeroInitialize[sizeof(QString)] = {};
++ };
++
++ Type m_type = Undefined;
++ };
++
++ QJSPrimitiveValuePrivate d;
++ };
++
++ namespace QQmlPrivate {
++ // TODO: Make this constexpr once std::isnan is constexpr.
++ inline double jsExponentiate(double base, double exponent)
++ {
++ constexpr double qNaN = std::numeric_limits<double>::quiet_NaN();
++ constexpr double inf = std::numeric_limits<double>::infinity();
++
++ if (qIsNull(exponent))
++ return 1.0;
++
++ if (std::isnan(exponent))
++ return qNaN;
++
++ if (QJSNumberCoercion::equals(base, 1.0) || QJSNumberCoercion::equals(base, -1.0))
++ return std::isinf(exponent) ? qNaN : std::pow(base, exponent);
++
++ if (!qIsNull(base))
++ return std::pow(base, exponent);
++
++ if (std::copysign(1.0, base) > 0.0)
++ return exponent < 0.0 ? inf : std::pow(base, exponent);
++
++ if (exponent < 0.0)
++ return QJSNumberCoercion::equals(std::fmod(-exponent, 2.0), 1.0) ? -inf : inf;
++
++ return QJSNumberCoercion::equals(std::fmod(exponent, 2.0), 1.0)
++ ? std::copysign(0, -1.0)
++ : 0.0;
++ }
++ }
++
++ QT_END_NAMESPACE
++
++ #endif // QJSPRIMITIVEVALUE_H