QJSEngine: Provide a method to coerce values in JS fashion
JavaScript has its own type coercion rules. We already have a methods that coerce QVariants, QJSValues and QJSManagedValues to specific types. The new method is a generalization of all of those and can coerce everything to everything (as far as JavaScript can). Change-Id: I9b6877fb40f67b6f2354781bbd4cf18cf996c7b0 Reviewed-by: Sami Shalayel <sami.shalayel@qt.io> Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
This commit is contained in:
parent
d9d2477464
commit
7b29ed6f64
|
@ -833,7 +833,7 @@ bool QJSEngine::convertV2(const QJSValue &value, int type, void *ptr)
|
||||||
return convertV2(value, QMetaType(type), ptr);
|
return convertV2(value, QMetaType(type), ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool convertString(const QString &string, QMetaType metaType, void *ptr)
|
bool QJSEngine::convertString(const QString &string, QMetaType metaType, void *ptr)
|
||||||
{
|
{
|
||||||
// have a string based value without engine. Do conversion manually
|
// have a string based value without engine. Do conversion manually
|
||||||
if (metaType == QMetaType::fromType<bool>()) {
|
if (metaType == QMetaType::fromType<bool>()) {
|
||||||
|
@ -906,20 +906,24 @@ bool QJSEngine::convertV2(const QJSValue &value, QMetaType metaType, void *ptr)
|
||||||
|
|
||||||
bool QJSEngine::convertVariant(const QVariant &value, QMetaType metaType, void *ptr)
|
bool QJSEngine::convertVariant(const QVariant &value, QMetaType metaType, void *ptr)
|
||||||
{
|
{
|
||||||
if (value.metaType() == QMetaType::fromType<QString>())
|
|
||||||
return convertString(value.toString(), metaType, ptr);
|
|
||||||
|
|
||||||
// TODO: We could probably avoid creating a QV4::Value in many cases, but we'd have to
|
// TODO: We could probably avoid creating a QV4::Value in many cases, but we'd have to
|
||||||
// duplicate much of metaTypeFromJS and some methods of QV4::Value itself here.
|
// duplicate much of metaTypeFromJS and some methods of QV4::Value itself here.
|
||||||
return QV4::ExecutionEngine::metaTypeFromJS(handle()->fromVariant(value), metaType, ptr);
|
return QV4::ExecutionEngine::metaTypeFromJS(handle()->fromVariant(value), metaType, ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool QJSEngine::convertMetaType(QMetaType fromType, const void *from, QMetaType toType, void *to)
|
||||||
|
{
|
||||||
|
// TODO: We could probably avoid creating a QV4::Value in many cases, but we'd have to
|
||||||
|
// duplicate much of metaTypeFromJS and some methods of QV4::Value itself here.
|
||||||
|
return QV4::ExecutionEngine::metaTypeFromJS(handle()->fromData(fromType, from), toType, to);
|
||||||
|
}
|
||||||
|
|
||||||
/*! \fn template <typename T> QJSValue QJSEngine::toScriptValue(const T &value)
|
/*! \fn template <typename T> QJSValue QJSEngine::toScriptValue(const T &value)
|
||||||
|
|
||||||
Creates a QJSValue with the given \a value.
|
Creates a QJSValue with the given \a value.
|
||||||
This works with any type \c{T} that has a \c{QMetaType}.
|
This works with any type \c{T} that has a \c{QMetaType}.
|
||||||
|
|
||||||
\sa fromScriptValue()
|
\sa fromScriptValue(), coerceValue()
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*! \fn template <typename T> T QJSEngine::fromScriptValue(const QJSValue &value)
|
/*! \fn template <typename T> T QJSEngine::fromScriptValue(const QJSValue &value)
|
||||||
|
@ -927,7 +931,7 @@ bool QJSEngine::convertVariant(const QVariant &value, QMetaType metaType, void *
|
||||||
Returns the given \a value converted to the template type \c{T}.
|
Returns the given \a value converted to the template type \c{T}.
|
||||||
This works with any type \c{T} that has a \c{QMetaType}.
|
This works with any type \c{T} that has a \c{QMetaType}.
|
||||||
|
|
||||||
\sa toScriptValue()
|
\sa toScriptValue(), coerceValue()
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*! \fn template <typename T> T QJSEngine::fromVariant(const QVariant &value)
|
/*! \fn template <typename T> T QJSEngine::fromVariant(const QVariant &value)
|
||||||
|
@ -939,7 +943,20 @@ bool QJSEngine::convertVariant(const QVariant &value, QMetaType metaType, void *
|
||||||
conversions between JavaScript-equivalent types that are not
|
conversions between JavaScript-equivalent types that are not
|
||||||
performed by qvariant_cast by default.
|
performed by qvariant_cast by default.
|
||||||
|
|
||||||
\sa fromScriptValue() qvariant_cast()
|
\sa coerceValue(), fromScriptValue(), qvariant_cast()
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*! \fn template <typename From, typename To> T QJSEngine::coerceValue(const From &from)
|
||||||
|
|
||||||
|
Returns the given \a from converted to the template type \c{To}.
|
||||||
|
This works with any type \c{T} that has a \c{QMetaType}. The
|
||||||
|
conversion is done in JavaScript semantics. Those differ from
|
||||||
|
qvariant_cast's semantics. There are a number of implicit
|
||||||
|
conversions between JavaScript-equivalent types that are not
|
||||||
|
performed by qvariant_cast by default. This method is a generalization of
|
||||||
|
all the other conversion methods in this class.
|
||||||
|
|
||||||
|
\sa fromVariant(), qvariant_cast(), fromScriptValue(), toScriptValue()
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
|
|
@ -83,8 +83,9 @@ public:
|
||||||
if constexpr (std::is_same_v<T, QVariant>)
|
if constexpr (std::is_same_v<T, QVariant>)
|
||||||
return value;
|
return value;
|
||||||
|
|
||||||
|
const QMetaType sourceType = value.metaType();
|
||||||
const QMetaType targetType = QMetaType::fromType<T>();
|
const QMetaType targetType = QMetaType::fromType<T>();
|
||||||
if (value.metaType() == targetType)
|
if (sourceType == targetType)
|
||||||
return *reinterpret_cast<const T *>(value.constData());
|
return *reinterpret_cast<const T *>(value.constData());
|
||||||
|
|
||||||
if constexpr (std::is_same_v<T,std::remove_const_t<std::remove_pointer_t<T>> const *>) {
|
if constexpr (std::is_same_v<T,std::remove_const_t<std::remove_pointer_t<T>> const *>) {
|
||||||
|
@ -94,16 +95,81 @@ public:
|
||||||
return *reinterpret_cast<const nonConstT *>(value.constData());
|
return *reinterpret_cast<const nonConstT *>(value.constData());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if constexpr (std::is_same_v<T, QJSValue>)
|
||||||
|
return toScriptValue(value);
|
||||||
|
|
||||||
|
if constexpr (std::is_same_v<T, QJSManagedValue>)
|
||||||
|
return toManagedValue(value);
|
||||||
|
|
||||||
|
if (sourceType == QMetaType::fromType<QJSValue>())
|
||||||
|
return fromScriptValue<T>(*reinterpret_cast<const QJSValue *>(value.constData()));
|
||||||
|
|
||||||
|
if (sourceType == QMetaType::fromType<QJSManagedValue>()) {
|
||||||
|
return fromManagedValue<T>(
|
||||||
|
*reinterpret_cast<const QJSManagedValue *>(value.constData()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
T t{};
|
T t{};
|
||||||
if (convertVariant(value, targetType, &t))
|
if (value.metaType() == QMetaType::fromType<QString>()) {
|
||||||
|
if (convertString(value.toString(), targetType, &t))
|
||||||
|
return t;
|
||||||
|
} else if (convertVariant(value, targetType, &t)) {
|
||||||
return t;
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
QMetaType::convert(value.metaType(), value.constData(), targetType, &t);
|
QMetaType::convert(value.metaType(), value.constData(), targetType, &t);
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename From, typename To>
|
||||||
|
inline To coerceValue(const From &from)
|
||||||
|
{
|
||||||
|
if constexpr (std::is_same_v<From, To>)
|
||||||
|
return from;
|
||||||
|
|
||||||
|
if constexpr (std::is_same_v<To, QJSValue>)
|
||||||
|
return toScriptValue(from);
|
||||||
|
|
||||||
|
if constexpr (std::is_same_v<From, QJSValue>)
|
||||||
|
return fromScriptValue<To>(from);
|
||||||
|
|
||||||
|
if constexpr (std::is_same_v<To, QJSManagedValue>)
|
||||||
|
return toManagedValue(from);
|
||||||
|
|
||||||
|
if constexpr (std::is_same_v<From, QJSManagedValue>)
|
||||||
|
return fromManagedValue<To>(from);
|
||||||
|
|
||||||
|
if constexpr (std::is_same_v<From, QVariant>)
|
||||||
|
return fromVariant<To>(from);
|
||||||
|
|
||||||
|
if constexpr (std::is_same_v<To, QVariant>)
|
||||||
|
return QVariant::fromValue(from);
|
||||||
|
|
||||||
|
if constexpr (std::is_same_v<To, std::remove_const_t<std::remove_pointer_t<To>> const *>) {
|
||||||
|
using nonConstTo = std::remove_const_t<std::remove_pointer_t<To>> *;
|
||||||
|
if constexpr (std::is_same_v<From, nonConstTo>)
|
||||||
|
return from;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const QMetaType sourceType = QMetaType::fromType<From>();
|
||||||
|
const QMetaType targetType = QMetaType::fromType<To>();
|
||||||
|
To to{};
|
||||||
|
if constexpr (std::is_same_v<From, QString>) {
|
||||||
|
if (convertString(from, targetType, &to))
|
||||||
|
return to;
|
||||||
|
} else if (convertMetaType(sourceType, &from, targetType, &to)) {
|
||||||
|
return to;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMetaType::convert(sourceType, &from, targetType, &to);
|
||||||
|
return to;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void collectGarbage();
|
void collectGarbage();
|
||||||
|
|
||||||
enum ObjectOwnership { CppOwnership, JavaScriptOwnership };
|
enum ObjectOwnership { CppOwnership, JavaScriptOwnership };
|
||||||
|
@ -148,7 +214,10 @@ private:
|
||||||
static bool convertManaged(const QJSManagedValue &value, QMetaType type, void *ptr);
|
static bool convertManaged(const QJSManagedValue &value, QMetaType type, void *ptr);
|
||||||
static bool convertV2(const QJSValue &value, int type, void *ptr);
|
static bool convertV2(const QJSValue &value, int type, void *ptr);
|
||||||
static bool convertV2(const QJSValue &value, QMetaType metaType, void *ptr);
|
static bool convertV2(const QJSValue &value, QMetaType metaType, void *ptr);
|
||||||
|
static bool convertString(const QString &string, QMetaType metaType, void *ptr);
|
||||||
|
|
||||||
bool convertVariant(const QVariant &value, QMetaType metaType, void *ptr);
|
bool convertVariant(const QVariant &value, QMetaType metaType, void *ptr);
|
||||||
|
bool convertMetaType(QMetaType fromType, const void *from, QMetaType toType, void *to);
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
friend inline T qjsvalue_cast(const QJSValue &);
|
friend inline T qjsvalue_cast(const QJSValue &);
|
||||||
|
|
|
@ -740,13 +740,14 @@ public:
|
||||||
QV4::ReturnedValue callInContext(QV4::Function *function, QObject *self,
|
QV4::ReturnedValue callInContext(QV4::Function *function, QObject *self,
|
||||||
QV4::ExecutionContext *ctxt, int argc, const QV4::Value *argv);
|
QV4::ExecutionContext *ctxt, int argc, const QV4::Value *argv);
|
||||||
|
|
||||||
|
QV4::ReturnedValue fromData(
|
||||||
|
QMetaType type, const void *ptr,
|
||||||
|
Heap::Object *parent = nullptr, int property = -1, uint flags = 0);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
template<int Frames>
|
template<int Frames>
|
||||||
friend struct ExecutionEngineCallDepthRecorder;
|
friend struct ExecutionEngineCallDepthRecorder;
|
||||||
|
|
||||||
QV4::ReturnedValue fromData(
|
|
||||||
QMetaType type, const void *ptr,
|
|
||||||
Heap::Object *parent = nullptr, int property = -1, uint flags = 0);
|
|
||||||
static void initializeStaticMembers();
|
static void initializeStaticMembers();
|
||||||
|
|
||||||
static int s_maxCallDepth;
|
static int s_maxCallDepth;
|
||||||
|
|
|
@ -271,6 +271,8 @@ private slots:
|
||||||
void tdzViolations_data();
|
void tdzViolations_data();
|
||||||
void tdzViolations();
|
void tdzViolations();
|
||||||
|
|
||||||
|
void coerceValue();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Q_INVOKABLE QJSValue throwingCppMethod1();
|
Q_INVOKABLE QJSValue throwingCppMethod1();
|
||||||
Q_INVOKABLE void throwingCppMethod2();
|
Q_INVOKABLE void throwingCppMethod2();
|
||||||
|
@ -5765,6 +5767,48 @@ void tst_QJSEngine::tdzViolations()
|
||||||
QCOMPARE(result3.toString(), u"ReferenceError: b is not defined"_s);
|
QCOMPARE(result3.toString(), u"ReferenceError: b is not defined"_s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class WithToString : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
Q_INVOKABLE int toString() const { return 29; }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UnknownToJS
|
||||||
|
{
|
||||||
|
int thing = 13;
|
||||||
|
};
|
||||||
|
|
||||||
|
void tst_QJSEngine::coerceValue()
|
||||||
|
{
|
||||||
|
const UnknownToJS u;
|
||||||
|
QMetaType::registerConverter<UnknownToJS, QTypeRevision>([](const UnknownToJS &u) {
|
||||||
|
return QTypeRevision::fromMinorVersion(u.thing);
|
||||||
|
});
|
||||||
|
QTypeRevision w;
|
||||||
|
QVERIFY(QMetaType::convert(QMetaType::fromType<UnknownToJS>(),
|
||||||
|
&u, QMetaType::fromType<QTypeRevision>(), &w));
|
||||||
|
QCOMPARE(w, QTypeRevision::fromMinorVersion(13));
|
||||||
|
|
||||||
|
|
||||||
|
QJSEngine engine;
|
||||||
|
WithToString withToString;
|
||||||
|
const int i = 7;
|
||||||
|
const QString a = QStringLiteral("5.25");
|
||||||
|
|
||||||
|
QCOMPARE((engine.coerceValue<int, int>(i)), i);
|
||||||
|
QVERIFY((engine.coerceValue<int, QJSValue>(i)).strictlyEquals(QJSValue(i)));
|
||||||
|
QVERIFY((engine.coerceValue<int, QJSManagedValue>(i)).strictlyEquals(
|
||||||
|
QJSManagedValue(QJSPrimitiveValue(i), &engine)));
|
||||||
|
QCOMPARE((engine.coerceValue<QVariant, int>(QVariant(i))), i);
|
||||||
|
QCOMPARE((engine.coerceValue<int, QVariant>(i)), QVariant(i));
|
||||||
|
QCOMPARE((engine.coerceValue<WithToString *, QString>(&withToString)), QStringLiteral("29"));
|
||||||
|
QCOMPARE((engine.coerceValue<WithToString *, const WithToString *>(&withToString)), &withToString);
|
||||||
|
QCOMPARE((engine.coerceValue<QString, double>(a)), 5.25);
|
||||||
|
QCOMPARE((engine.coerceValue<double, QString>(5.25)), a);
|
||||||
|
QCOMPARE((engine.coerceValue<UnknownToJS, QTypeRevision>(u)), w);
|
||||||
|
}
|
||||||
|
|
||||||
QTEST_MAIN(tst_QJSEngine)
|
QTEST_MAIN(tst_QJSEngine)
|
||||||
|
|
||||||
#include "tst_qjsengine.moc"
|
#include "tst_qjsengine.moc"
|
||||||
|
|
Loading…
Reference in New Issue