From ecdb4ed275a0869dc668d73d774735575d43a0a3 Mon Sep 17 00:00:00 2001 From: Fabian Kosmale Date: Thu, 2 Jan 2020 14:42:12 +0100 Subject: Enable conversion from QJSValues containing arrays to container types We started to convert containers to QJSValues, so that we could use them as JavaScript arrays. Unfortunately, this would then lead to a type missmatch when those same values where to be stored in a property of the container type. This commit fixes this by converting them back to the original type. Fixes: QTBUG-80916 Change-Id: I30a3b03e17c34b171d4a6881dfd7801c13e94d80 Reviewed-by: Ulf Hermann --- src/qml/jsruntime/qv4engine.cpp | 25 +++++++++++++++++++- .../data/sequenceConversion.write.error.qml | 2 +- .../auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp | 9 ++++---- .../qml/qqmllanguage/data/arrayToContainer.qml | 7 ++++++ tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp | 27 ++++++++++++++++++++++ 5 files changed, 63 insertions(+), 7 deletions(-) create mode 100644 tests/auto/qml/qqmllanguage/data/arrayToContainer.qml diff --git a/src/qml/jsruntime/qv4engine.cpp b/src/qml/jsruntime/qv4engine.cpp index c81661033a..81f55673cd 100644 --- a/src/qml/jsruntime/qv4engine.cpp +++ b/src/qml/jsruntime/qv4engine.cpp @@ -1508,12 +1508,35 @@ static QVariant toVariant(QV4::ExecutionEngine *e, const QV4::Value &value, int return QVariant::fromValue(QV4::JsonObject::toJsonArray(a)); } + QVariant retn; #if QT_CONFIG(qml_sequence_object) bool succeeded = false; - QVariant retn = QV4::SequencePrototype::toVariant(value, typeHint, &succeeded); + retn = QV4::SequencePrototype::toVariant(value, typeHint, &succeeded); if (succeeded) return retn; #endif + retn = QVariant(typeHint, QMetaType::create(typeHint)); + auto retnAsIterable = retn.value(); + if (retnAsIterable._iteratorCapabilities & QtMetaTypePrivate::ContainerIsAppendable) { + auto const length = a->getLength(); + QV4::ScopedValue arrayValue(scope); + for (qint64 i = 0; i < length; ++i) { + arrayValue = a->get(i); + QVariant asVariant = toVariant(e, arrayValue, retnAsIterable._metaType_id, false, visitedObjects); + auto originalType = asVariant.userType(); + bool couldConvert = asVariant.convert(retnAsIterable._metaType_id); + if (!couldConvert) { + qWarning() << QLatin1String("Could not convert array value at position %1 from %2 to %3") + .arg(QString::number(i), + QMetaType::typeName(originalType), + QMetaType::typeName(retnAsIterable._metaType_id)); + // create default constructed value + asVariant = QVariant(retnAsIterable._metaType_id, nullptr); + } + retnAsIterable.append(asVariant.constData()); + } + return retn; + } } if (value.isUndefined()) diff --git a/tests/auto/qml/qqmlecmascript/data/sequenceConversion.write.error.qml b/tests/auto/qml/qqmlecmascript/data/sequenceConversion.write.error.qml index 75beafd1ee..c2c8c1b52b 100644 --- a/tests/auto/qml/qqmlecmascript/data/sequenceConversion.write.error.qml +++ b/tests/auto/qml/qqmlecmascript/data/sequenceConversion.write.error.qml @@ -12,7 +12,7 @@ Item { function performTest() { // we have NOT registered QList as a type - var pointList = [ Qt.point(7,7), Qt.point(8,8), Qt.point(9,9) ]; + var pointList = [ Qt.point(7,7), "hello world", Qt.point(8,8), Qt.point(9,9) ]; msco.pointListProperty = pointList; // error. } } diff --git a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp index 370ef5f065..26232ac1a3 100644 --- a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp +++ b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp @@ -5792,13 +5792,12 @@ void tst_qqmlecmascript::sequenceConversionWrite() MySequenceConversionObject *seq = object->findChild("msco"); QVERIFY(seq != nullptr); - // we haven't registered QList as a sequence type, so writing shouldn't work. - QString warningOne = qmlFile.toString() + QLatin1String(":16: Error: Cannot assign QJSValue to QList"); - QTest::ignoreMessage(QtWarningMsg, warningOne.toLatin1().constData()); - + // Behavior change in 5.14: due to added auto-magical conversions, it is possible to assign to + // QList, even though it is not a registered sequence type + QTest::ignoreMessage(QtMsgType::QtWarningMsg, QRegularExpression("Could not convert array value at position 1 from QString to QPoint")); QMetaObject::invokeMethod(object, "performTest"); - QList pointList; pointList << QPoint(1, 2) << QPoint(3, 4) << QPoint(5, 6); // original values, shouldn't have changed + QList pointList; pointList << QPoint(7, 7) << QPoint(0,0) << QPoint(8, 8) << QPoint(9, 9); // original values, shouldn't have changed QCOMPARE(seq->pointListProperty(), pointList); delete object; diff --git a/tests/auto/qml/qqmllanguage/data/arrayToContainer.qml b/tests/auto/qml/qqmllanguage/data/arrayToContainer.qml new file mode 100644 index 0000000000..ee400eb41f --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/arrayToContainer.qml @@ -0,0 +1,7 @@ +import QtQml 2.14 +import qt.test 1.0 + +TestItem { + property var vector + positions: vector +} diff --git a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp index fae74f1f25..4a8ce77f92 100644 --- a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp +++ b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp @@ -303,6 +303,8 @@ private slots: void typeWrapperToVariant(); + void arrayToContainer(); + private: QQmlEngine engine; QStringList defaultImportPathList; @@ -5238,6 +5240,31 @@ void tst_qqmllanguage::typeWrapperToVariant() QVERIFY(target); } +class TestItem : public QObject +{ + Q_OBJECT + Q_PROPERTY( QVector positions MEMBER m_points ) + +public: + TestItem() = default; + QVector< QPointF > m_points; +}; + + +Q_DECLARE_METATYPE(QVector); +void tst_qqmllanguage::arrayToContainer() +{ + QQmlEngine engine; + qmlRegisterType("qt.test", 1, 0, "TestItem"); + QVector points { QPointF (2.0, 3.0) }; + engine.rootContext()->setContextProperty("test", QVariant::fromValue(points)); + QQmlComponent component(&engine, testFileUrl("arrayToContainer.qml")); + VERIFY_ERRORS(0); + QScopedPointer root(qobject_cast(component.createWithInitialProperties( {{"vector", QVariant::fromValue(points)}} ))); + QVERIFY(root); + QCOMPARE(root->m_points.at(0), QPointF (2.0, 3.0) ); +} + QTEST_MAIN(tst_qqmllanguage) #include "tst_qqmllanguage.moc" -- cgit v1.2.3