diff options
| author | Ulf Hermann <ulf.hermann@qt.io> | 2023-06-07 09:40:00 +0200 |
|---|---|---|
| committer | Ulf Hermann <ulf.hermann@qt.io> | 2023-06-09 09:35:32 +0200 |
| commit | 1d482737860b22f7419d0e57d993bb3247f4a014 (patch) | |
| tree | f6f3d3e83ad0669823ff1dc0a0463eb14634a8cf | |
| parent | 183a00da764264a04048dc64ff690e128ba6f68e (diff) | |
QML: Allow coercing variant objects to their own type
This allows passing unregistered value types through QML.
Task-number: QTBUG-114340
Change-Id: I6fa5adadf2d406d2d5f3a83fb922e9d547e7ead9
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
(cherry picked from commit cd16a5ffb9d798b08dba0cdfbef045b4b2dd0f08)
| -rw-r--r-- | src/qml/jsruntime/qv4engine.cpp | 82 | ||||
| -rw-r--r-- | tests/auto/qml/qqmllanguage/data/unregisteredValueTypeConversion.qml | 6 | ||||
| -rw-r--r-- | tests/auto/qml/qqmllanguage/testtypes.cpp | 2 | ||||
| -rw-r--r-- | tests/auto/qml/qqmllanguage/testtypes.h | 17 | ||||
| -rw-r--r-- | tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp | 13 |
5 files changed, 87 insertions, 33 deletions
diff --git a/src/qml/jsruntime/qv4engine.cpp b/src/qml/jsruntime/qv4engine.cpp index 8de97a4aa4..dc26d6a93a 100644 --- a/src/qml/jsruntime/qv4engine.cpp +++ b/src/qml/jsruntime/qv4engine.cpp @@ -2632,42 +2632,58 @@ bool ExecutionEngine::metaTypeFromJS(const Value &value, QMetaType metaType, voi return true; const bool isPointer = (metaType.flags() & QMetaType::IsPointer); - if (value.as<QV4::VariantObject>() && isPointer) { - const QByteArray pointedToTypeName = QByteArray(metaType.name()).chopped(1); - const QMetaType valueType = QMetaType::fromName(pointedToTypeName); - QVariant &var = value.as<QV4::VariantObject>()->d()->data(); - if (valueType == var.metaType()) { - // We have T t, T* is requested, so return &t. - *reinterpret_cast<void* *>(data) = var.data(); + const QV4::VariantObject *variantObject = value.as<QV4::VariantObject>(); + if (variantObject) { + // Actually a reference, because we're poking it for its data() below and we want + // the _original_ data, not some copy. + const QVariant &var = variantObject->d()->data(); + + if (var.metaType() == metaType) { + metaType.destruct(data); + metaType.construct(data, var.data()); return true; - } else if (Object *o = value.objectValue()) { - // Look in the prototype chain. - QV4::Scope scope(o->engine()); - QV4::ScopedObject proto(scope, o->getPrototypeOf()); - while (proto) { - bool canCast = false; - if (QV4::VariantObject *vo = proto->as<QV4::VariantObject>()) { - const QVariant &v = vo->d()->data(); - canCast = (metaType == v.metaType()); - } - else if (proto->as<QV4::QObjectWrapper>()) { - QV4::ScopedObject p(scope, proto.getPointer()); - if (QObject *qobject = qtObjectFromJS(p)) { - if (const QMetaObject *metaObject = metaType.metaObject()) - canCast = metaObject->cast(qobject) != nullptr; - else - canCast = qobject->qt_metacast(pointedToTypeName); + } + + if (isPointer) { + const QByteArray pointedToTypeName = QByteArray(metaType.name()).chopped(1); + const QMetaType valueType = QMetaType::fromName(pointedToTypeName); + + if (valueType == var.metaType()) { + // ### Qt7: Remove this. Returning pointers to potentially gc'd data is crazy. + // We have T t, T* is requested, so return &t. + *reinterpret_cast<const void **>(data) = var.data(); + return true; + } else if (Object *o = value.objectValue()) { + // Look in the prototype chain. + QV4::Scope scope(o->engine()); + QV4::ScopedObject proto(scope, o->getPrototypeOf()); + while (proto) { + bool canCast = false; + if (QV4::VariantObject *vo = proto->as<QV4::VariantObject>()) { + const QVariant &v = vo->d()->data(); + canCast = (metaType == v.metaType()); } + else if (proto->as<QV4::QObjectWrapper>()) { + QV4::ScopedObject p(scope, proto.getPointer()); + if (QObject *qobject = qtObjectFromJS(p)) { + if (const QMetaObject *metaObject = metaType.metaObject()) + canCast = metaObject->cast(qobject) != nullptr; + else + canCast = qobject->qt_metacast(pointedToTypeName); + } + } + if (canCast) { + const QMetaType varType = var.metaType(); + if (varType.flags() & QMetaType::IsPointer) { + *reinterpret_cast<const void **>(data) + = *reinterpret_cast<void *const *>(var.data()); + } else { + *reinterpret_cast<const void **>(data) = var.data(); + } + return true; + } + proto = proto->getPrototypeOf(); } - if (canCast) { - const QMetaType varType = var.metaType(); - if (varType.flags() & QMetaType::IsPointer) - *reinterpret_cast<void* *>(data) = *reinterpret_cast<void* *>(var.data()); - else - *reinterpret_cast<void* *>(data) = var.data(); - return true; - } - proto = proto->getPrototypeOf(); } } } else if (value.isNull() && isPointer) { diff --git a/tests/auto/qml/qqmllanguage/data/unregisteredValueTypeConversion.qml b/tests/auto/qml/qqmllanguage/data/unregisteredValueTypeConversion.qml new file mode 100644 index 0000000000..ecf2130f5a --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/unregisteredValueTypeConversion.qml @@ -0,0 +1,6 @@ +import QtQml +import Test + +UnregisteredValueTypeHandler { + Component.onCompleted: consume(produce()) +} diff --git a/tests/auto/qml/qqmllanguage/testtypes.cpp b/tests/auto/qml/qqmllanguage/testtypes.cpp index e775eb165c..87241921c9 100644 --- a/tests/auto/qml/qqmllanguage/testtypes.cpp +++ b/tests/auto/qml/qqmllanguage/testtypes.cpp @@ -149,6 +149,8 @@ void registerTypes() qmlRegisterTypesAndRevisions<BaseValueType>("ValueTypes", 1); qmlRegisterTypesAndRevisions<DerivedValueType>("ValueTypes", 1); qmlRegisterTypesAndRevisions<GetterObject>("Test", 1); + + qmlRegisterTypesAndRevisions<UnregisteredValueTypeHandler>("Test", 1); } QVariant myCustomVariantTypeConverter(const QString &data) diff --git a/tests/auto/qml/qqmllanguage/testtypes.h b/tests/auto/qml/qqmllanguage/testtypes.h index 977c274f6c..3b4f612127 100644 --- a/tests/auto/qml/qqmllanguage/testtypes.h +++ b/tests/auto/qml/qqmllanguage/testtypes.h @@ -2421,4 +2421,21 @@ public: }; +struct UnregisteredValueBaseType +{ + int foo = 12; +}; + +class UnregisteredValueTypeHandler: public QObject +{ + Q_OBJECT + QML_ELEMENT +public: + int consumed = 0; + +public slots: + UnregisteredValueBaseType produce() { return UnregisteredValueBaseType(); } + void consume(UnregisteredValueBaseType) { ++consumed; } +}; + #endif // TESTTYPES_H diff --git a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp index a55876a750..ff5eeb9d7a 100644 --- a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp +++ b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp @@ -410,6 +410,7 @@ private slots: void longConversion(); void objectMethodClone(); + void unregisteredValueTypeConversion(); private: QQmlEngine engine; @@ -7910,6 +7911,18 @@ void tst_qqmllanguage::objectMethodClone() QTRY_COMPARE(o->property("doneClicks").toInt(), 2); } +void tst_qqmllanguage::unregisteredValueTypeConversion() +{ + QQmlEngine e; + QQmlComponent c(&e, testFileUrl("unregisteredValueTypeConversion.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + UnregisteredValueTypeHandler *handler = qobject_cast<UnregisteredValueTypeHandler *>(o.data()); + Q_ASSERT(handler); + QCOMPARE(handler->consumed, 1); +} + QTEST_MAIN(tst_qqmllanguage) #include "tst_qqmllanguage.moc" |
