aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorUlf Hermann <ulf.hermann@qt.io>2022-02-16 10:47:04 +0100
committerUlf Hermann <ulf.hermann@qt.io>2022-02-21 12:53:38 +0100
commit82d9d8ec445c20056087374812f12d7f6ee27670 (patch)
tree3046475432d2f6733ac77240004697d658e2cce8
parent345cb3b6f38bead32cff74dfbe68fa4bfe13b631 (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.cpp140
-rw-r--r--src/qml/jsapi/qjsengine.h28
-rw-r--r--src/qmlcompiler/qqmljscodegenerator.cpp3
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt1
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/objectInVar.qml15
-rw-r--r--tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp19
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"))