diff options
author | Ulf Hermann <ulf.hermann@qt.io> | 2022-02-16 10:47:04 +0100 |
---|---|---|
committer | Ulf Hermann <ulf.hermann@qt.io> | 2022-02-21 12:53:38 +0100 |
commit | 82d9d8ec445c20056087374812f12d7f6ee27670 (patch) | |
tree | 3046475432d2f6733ac77240004697d658e2cce8 | |
parent | 345cb3b6f38bead32cff74dfbe68fa4bfe13b631 (diff) |
QmlCompiler: Perform QVariant conversion in JavaScript semantics
In JavaScript we have a number of extra conversions not covered by
qvariant_cast. Therefore, add a method to perform a QVariant conversion
in JavaScript semantics to QJSEngine, and use that in the compiler.
Fixes: QTBUG-100883
Change-Id: I8b0bfa0974bc6b339d2601fb373859bc710788c8
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Reviewed-by: Andrei Golubev <andrei.golubev@qt.io>
Reviewed-by: Jarkko Koivikko <jarkko.koivikko@code-q.fi>
(cherry picked from commit d0f4e0c037cf61eb5bb559755ee7c9ce6cf6b7dc)
-rw-r--r-- | src/qml/jsapi/qjsengine.cpp | 140 | ||||
-rw-r--r-- | src/qml/jsapi/qjsengine.h | 28 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljscodegenerator.cpp | 3 | ||||
-rw-r--r-- | tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt | 1 | ||||
-rw-r--r-- | tests/auto/qml/qmlcppcodegen/data/objectInVar.qml | 15 | ||||
-rw-r--r-- | tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp | 19 |
6 files changed, 148 insertions, 58 deletions
diff --git a/src/qml/jsapi/qjsengine.cpp b/src/qml/jsapi/qjsengine.cpp index 7b436645e9..f66488e04c 100644 --- a/src/qml/jsapi/qjsengine.cpp +++ b/src/qml/jsapi/qjsengine.cpp @@ -861,73 +861,87 @@ bool QJSEngine::convertV2(const QJSValue &value, int type, void *ptr) return convertV2(value, QMetaType(type), ptr); } +static bool convertString(const QString &string, QMetaType metaType, void *ptr) +{ + // have a string based value without engine. Do conversion manually + if (metaType == QMetaType::fromType<bool>()) { + *reinterpret_cast<bool*>(ptr) = string.length() != 0; + return true; + } + if (metaType == QMetaType::fromType<QString>()) { + *reinterpret_cast<QString*>(ptr) = string; + return true; + } + if (metaType == QMetaType::fromType<QUrl>()) { + *reinterpret_cast<QUrl *>(ptr) = QUrl(string); + return true; + } + + double d = QV4::RuntimeHelpers::stringToNumber(string); + switch (metaType.id()) { + case QMetaType::Int: + *reinterpret_cast<int*>(ptr) = QV4::Value::toInt32(d); + return true; + case QMetaType::UInt: + *reinterpret_cast<uint*>(ptr) = QV4::Value::toUInt32(d); + return true; + case QMetaType::LongLong: + *reinterpret_cast<qlonglong*>(ptr) = QV4::Value::toInteger(d); + return true; + case QMetaType::ULongLong: + *reinterpret_cast<qulonglong*>(ptr) = QV4::Value::toInteger(d); + return true; + case QMetaType::Double: + *reinterpret_cast<double*>(ptr) = d; + return true; + case QMetaType::Float: + *reinterpret_cast<float*>(ptr) = d; + return true; + case QMetaType::Short: + *reinterpret_cast<short*>(ptr) = QV4::Value::toInt32(d); + return true; + case QMetaType::UShort: + *reinterpret_cast<unsigned short*>(ptr) = QV4::Value::toUInt32(d); + return true; + case QMetaType::Char: + *reinterpret_cast<char*>(ptr) = QV4::Value::toInt32(d); + return true; + case QMetaType::UChar: + *reinterpret_cast<unsigned char*>(ptr) = QV4::Value::toUInt32(d); + return true; + case QMetaType::QChar: + *reinterpret_cast<QChar*>(ptr) = QChar(QV4::Value::toUInt32(d)); + return true; + case QMetaType::Char16: + *reinterpret_cast<char16_t *>(ptr) = QV4::Value::toUInt32(d); + return true; + default: + return false; + } +} + /*! \internal convert \a value to \a type, store the result in \a ptr */ bool QJSEngine::convertV2(const QJSValue &value, QMetaType metaType, void *ptr) { - if (const QString *string = QJSValuePrivate::asQString(&value)) { - // have a string based value without engine. Do conversion manually - if (metaType == QMetaType::fromType<bool>()) { - *reinterpret_cast<bool*>(ptr) = string->length() != 0; - return true; - } - if (metaType == QMetaType::fromType<QString>()) { - *reinterpret_cast<QString*>(ptr) = *string; - return true; - } - if (metaType == QMetaType::fromType<QUrl>()) { - *reinterpret_cast<QUrl *>(ptr) = QUrl(*string); - return true; - } - - double d = QV4::RuntimeHelpers::stringToNumber(*string); - switch (metaType.id()) { - case QMetaType::Int: - *reinterpret_cast<int*>(ptr) = QV4::Value::toInt32(d); - return true; - case QMetaType::UInt: - *reinterpret_cast<uint*>(ptr) = QV4::Value::toUInt32(d); - return true; - case QMetaType::LongLong: - *reinterpret_cast<qlonglong*>(ptr) = QV4::Value::toInteger(d); - return true; - case QMetaType::ULongLong: - *reinterpret_cast<qulonglong*>(ptr) = QV4::Value::toInteger(d); - return true; - case QMetaType::Double: - *reinterpret_cast<double*>(ptr) = d; - return true; - case QMetaType::Float: - *reinterpret_cast<float*>(ptr) = d; - return true; - case QMetaType::Short: - *reinterpret_cast<short*>(ptr) = QV4::Value::toInt32(d); - return true; - case QMetaType::UShort: - *reinterpret_cast<unsigned short*>(ptr) = QV4::Value::toUInt32(d); - return true; - case QMetaType::Char: - *reinterpret_cast<char*>(ptr) = QV4::Value::toInt32(d); - return true; - case QMetaType::UChar: - *reinterpret_cast<unsigned char*>(ptr) = QV4::Value::toUInt32(d); - return true; - case QMetaType::QChar: - *reinterpret_cast<QChar*>(ptr) = QChar(QV4::Value::toUInt32(d)); - return true; - case QMetaType::Char16: - *reinterpret_cast<char16_t *>(ptr) = QV4::Value::toUInt32(d); - return true; - default: - return false; - } - } + if (const QString *string = QJSValuePrivate::asQString(&value)) + return convertString(*string, metaType, ptr); return QV4::ExecutionEngine::metaTypeFromJS(QJSValuePrivate::asReturnedValue(&value), metaType, 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 + // duplicate much of metaTypeFromJS and some methods of QV4::Value itself here. + return QV4::ExecutionEngine::metaTypeFromJS(handle()->fromVariant(value), metaType, ptr); +} + /*! \fn template <typename T> QJSValue QJSEngine::toScriptValue(const T &value) Creates a QJSValue with the given \a value. @@ -944,6 +958,18 @@ bool QJSEngine::convertV2(const QJSValue &value, QMetaType metaType, void *ptr) \sa toScriptValue() */ +/*! \fn template <typename T> T QJSEngine::fromVariant(const QVariant &value) + + Returns the given \a value converted to the template type \c{T}. + 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. + + \sa fromScriptValue() qvariant_cast() +*/ + /*! Throws a run-time error (exception) with the given \a message. diff --git a/src/qml/jsapi/qjsengine.h b/src/qml/jsapi/qjsengine.h index 23578a63b6..700b00bcf6 100644 --- a/src/qml/jsapi/qjsengine.h +++ b/src/qml/jsapi/qjsengine.h @@ -113,6 +113,33 @@ public: return qjsvalue_cast<T>(value); } + template <typename T> + inline T fromVariant(const QVariant &value) + { + if constexpr (std::is_same_v<T, QVariant>) + return value; + + const QMetaType targetType = QMetaType::fromType<T>(); + if (value.metaType() == targetType) + return *reinterpret_cast<const T *>(value.constData()); + + if constexpr (std::is_same_v<T,std::remove_const_t<std::remove_pointer_t<T>> const *>) { + using nonConstT = std::remove_const_t<std::remove_pointer_t<T>> *; + const QMetaType nonConstTargetType = QMetaType::fromType<nonConstT>(); + if (value.metaType() == nonConstTargetType) + return *reinterpret_cast<const nonConstT *>(value.constData()); + } + + { + T t{}; + if (convertVariant(value, QMetaType::fromType<T>(), &t)) + return t; + + QMetaType::convert(value.metaType(), value.constData(), targetType, &t); + return t; + } + } + void collectGarbage(); enum ObjectOwnership { CppOwnership, JavaScriptOwnership }; @@ -157,6 +184,7 @@ private: 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, QMetaType metaType, void *ptr); + bool convertVariant(const QVariant &value, QMetaType metaType, void *ptr); template<typename T> friend inline T qjsvalue_cast(const QJSValue &); diff --git a/src/qmlcompiler/qqmljscodegenerator.cpp b/src/qmlcompiler/qqmljscodegenerator.cpp index 9152479d29..5525dd2e96 100644 --- a/src/qmlcompiler/qqmljscodegenerator.cpp +++ b/src/qmlcompiler/qqmljscodegenerator.cpp @@ -2558,7 +2558,8 @@ QString QQmlJSCodeGenerator::conversion(const QQmlJSScope::ConstPtr &from, if (from == varType) { if (to == m_typeResolver->listPropertyType()) return u"QQmlListReference("_qs + variable + u", aotContext->qmlEngine())"_qs; - return u"qvariant_cast<"_qs + castTargetName(to) + u">("_qs + variable + u')'; + return u"aotContext->engine->fromVariant<"_qs + castTargetName(to) + u">("_qs + + variable + u')'; } if (to == varType) diff --git a/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt b/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt index 34c6ea3542..02e0f06deb 100644 --- a/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt +++ b/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt @@ -92,6 +92,7 @@ set(qml_files noscope.qml notEqualsInt.qml nullAccess.qml + objectInVar.qml outOfBounds.qml overriddenMember.qml ownProperty.qml diff --git a/tests/auto/qml/qmlcppcodegen/data/objectInVar.qml b/tests/auto/qml/qmlcppcodegen/data/objectInVar.qml new file mode 100644 index 0000000000..9177ff2089 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/objectInVar.qml @@ -0,0 +1,15 @@ +pragma Strict +import QtQml + +QtObject { + id: self + + property var thing: self + + function doThing() : bool { + if (self.thing) + return true; + else + return false; + } +} diff --git a/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp b/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp index 48e0bb68c9..46e0b4fb90 100644 --- a/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp +++ b/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp @@ -120,6 +120,7 @@ private slots: void infinities(); void blockComments(); void functionLookup(); + void objectInVar(); }; void tst_QmlCppCodegen::simpleBinding() @@ -1805,6 +1806,24 @@ void tst_QmlCppCodegen::functionLookup() QCOMPARE(result.toString(), QStringLiteral("a99")); } +void tst_QmlCppCodegen::objectInVar() +{ + QQmlEngine engine; + QQmlComponent c(&engine, QUrl(u"qrc:/TestTypes/objectInVar.qml"_qs)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(o); + QCOMPARE(qvariant_cast<QObject*>(o->property("thing")), o.data()); + + bool result = false; + QVERIFY(QMetaObject::invokeMethod(o.data(), "doThing", Q_RETURN_ARG(bool, result))); + QVERIFY(result); + + o->setProperty("thing", QVariant::fromValue<std::nullptr_t>(nullptr)); + QVERIFY(QMetaObject::invokeMethod(o.data(), "doThing", Q_RETURN_ARG(bool, result))); + QVERIFY(!result); +} + void tst_QmlCppCodegen::runInterpreted() { if (qEnvironmentVariableIsSet("QV4_FORCE_INTERPRETER")) |