From 8c451bba7aa6474c8aaec01b8fb02201e0237835 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Fri, 16 Jun 2023 15:03:33 +0200 Subject: [PATCH] QML: Allow conversion of symbols to QVariant We produce their descriptiveString or simply a QVariant containing a QJSValue, depending on whether we're supposed to convert objects without equivalent C++ type or not. Pick-to: 6.6 6.5 6.2 Fixes: QTBUG-113854 Change-Id: I22b6038c936d860fdd8aa227f9dfe704e3265a77 Reviewed-by: Fabian Kosmale --- src/qml/jsapi/qjsvalue.cpp | 3 +- src/qml/jsruntime/qv4engine.cpp | 32 +++++++++++++++------- src/qml/jsruntime/qv4engine_p.h | 2 +- tests/auto/qml/qjsengine/tst_qjsengine.cpp | 15 ++++++++++ 4 files changed, 40 insertions(+), 12 deletions(-) diff --git a/src/qml/jsapi/qjsvalue.cpp b/src/qml/jsapi/qjsvalue.cpp index 0aaa471e23..dd3bb98002 100644 --- a/src/qml/jsapi/qjsvalue.cpp +++ b/src/qml/jsapi/qjsvalue.cpp @@ -163,10 +163,11 @@ /*! \enum QJSValue::ObjectConversionBehavior - This enum is used to specify how JavaScript objects without an equivalent + This enum is used to specify how JavaScript objects and symbols without an equivalent native Qt type should be treated when converting to QVariant. \value ConvertJSObjects A best-effort, possibly lossy, conversion is attempted. + Symbols are converted to QString. \value RetainJSObjects The value is retained as QJSValue wrapped in QVariant. */ diff --git a/src/qml/jsruntime/qv4engine.cpp b/src/qml/jsruntime/qv4engine.cpp index 77664c00c6..6efe2a8597 100644 --- a/src/qml/jsruntime/qv4engine.cpp +++ b/src/qml/jsruntime/qv4engine.cpp @@ -1478,7 +1478,9 @@ QQmlError ExecutionEngine::catchExceptionAsQmlError() // Variant conversion code typedef QSet V4ObjectSet; -static QVariant toVariant(const QV4::Value &value, QMetaType typeHint, bool createJSValueForObjects, V4ObjectSet *visitedObjects); +static QVariant toVariant( + const QV4::Value &value, QMetaType typeHint, bool createJSValueForObjectsAndSymbols, + V4ObjectSet *visitedObjects); static QObject *qtObjectFromJS(const QV4::Value &value); static QVariant objectToVariant(const QV4::Object *o, V4ObjectSet *visitedObjects = nullptr); static bool convertToNativeQObject(const QV4::Value &value, QMetaType targetType, void **result); @@ -1491,7 +1493,7 @@ static QV4::ReturnedValue variantToJS(QV4::ExecutionEngine *v4, const QVariant & } static QVariant toVariant( - const QV4::Value &value, QMetaType metaType, bool createJSValueForObjects, + const QV4::Value &value, QMetaType metaType, bool createJSValueForObjectsAndSymbols, V4ObjectSet *visitedObjects) { Q_ASSERT (!value.isEmpty()); @@ -1634,6 +1636,9 @@ static QVariant toVariant( return *ld->d()->locale; #endif if (const QV4::DateObject *d = value.as()) { + // NOTE: since we convert QTime to JS Date, + // round trip will change the variant type (to QDateTime)! + if (metaType == QMetaType::fromType()) return DateObject::dateTimeToDate(d->toQDateTime()); @@ -1649,7 +1654,11 @@ static QVariant toVariant( return d->toQUrl(); if (const ArrayBuffer *d = value.as()) return d->asByteArray(); - // NOTE: since we convert QTime to JS Date, round trip will change the variant type (to QDateTime)! + if (const Symbol *symbol = value.as()) { + return createJSValueForObjectsAndSymbols + ? QVariant::fromValue(QJSValuePrivate::fromReturnedValue(symbol->asReturnedValue())) + : symbol->descriptiveString(); + } const QV4::Object *object = value.as(); Q_ASSERT(object); @@ -1667,16 +1676,17 @@ static QVariant toVariant( return result; } - if (createJSValueForObjects) + if (createJSValueForObjectsAndSymbols) return QVariant::fromValue(QJSValuePrivate::fromReturnedValue(o->asReturnedValue())); return objectToVariant(o, visitedObjects); } -QVariant ExecutionEngine::toVariant(const Value &value, QMetaType typeHint, bool createJSValueForObjects) +QVariant ExecutionEngine::toVariant( + const Value &value, QMetaType typeHint, bool createJSValueForObjectsAndSymbols) { - return ::toVariant(value, typeHint, createJSValueForObjects, nullptr); + return ::toVariant(value, typeHint, createJSValueForObjectsAndSymbols, nullptr); } static QVariant objectToVariant(const QV4::Object *o, V4ObjectSet *visitedObjects) @@ -1707,7 +1717,8 @@ static QVariant objectToVariant(const QV4::Object *o, V4ObjectSet *visitedObject int length = a->getLength(); for (int ii = 0; ii < length; ++ii) { v = a->get(ii); - list << ::toVariant(v, QMetaType {}, /*createJSValueForObjects*/false, visitedObjects); + list << ::toVariant(v, QMetaType {}, /*createJSValueForObjectsAndSymbols*/false, + visitedObjects); } result = list; @@ -1725,7 +1736,7 @@ static QVariant objectToVariant(const QV4::Object *o, V4ObjectSet *visitedObject QString key = name->toQStringNoThrow(); map.insert(key, ::toVariant( val, /*type hint*/ QMetaType {}, - /*createJSValueForObjects*/false, visitedObjects)); + /*createJSValueForObjectsAndSymbols*/false, visitedObjects)); } result = map; @@ -2589,7 +2600,8 @@ bool ExecutionEngine::metaTypeFromJS(const Value &value, QMetaType metaType, voi const QV4::ArrayObject *a = value.as(); if (a) { *reinterpret_cast(data) = ExecutionEngine::toVariant( - *a, /*typeHint*/QMetaType{}, /*createJSValueForObjects*/false).toList(); + *a, /*typeHint*/QMetaType{}, /*createJSValueForObjectsAndSymbols*/false) + .toList(); return true; } break; @@ -2605,7 +2617,7 @@ bool ExecutionEngine::metaTypeFromJS(const Value &value, QMetaType metaType, voi case QMetaType::QVariant: if (value.as()) { *reinterpret_cast(data) = ExecutionEngine::toVariant( - value, /*typeHint*/QMetaType{}, /*createJSValueForObjects*/false); + value, /*typeHint*/QMetaType{}, /*createJSValueForObjectsAndSymbols*/false); } else if (value.isNull()) { *reinterpret_cast(data) = QVariant::fromValue(nullptr); } else if (value.isUndefined()) { diff --git a/src/qml/jsruntime/qv4engine_p.h b/src/qml/jsruntime/qv4engine_p.h index b4fe8602f1..8590f330c4 100644 --- a/src/qml/jsruntime/qv4engine_p.h +++ b/src/qml/jsruntime/qv4engine_p.h @@ -663,7 +663,7 @@ public: // variant conversions static QVariant toVariant( - const QV4::Value &value, QMetaType typeHint, bool createJSValueForObjects = true); + const QV4::Value &value, QMetaType typeHint, bool createJSValueForObjectsAndSymbols = true); QV4::ReturnedValue fromVariant(const QVariant &); QV4::ReturnedValue fromVariant( const QVariant &variant, Heap::Object *parent, int property, uint flags); diff --git a/tests/auto/qml/qjsengine/tst_qjsengine.cpp b/tests/auto/qml/qjsengine/tst_qjsengine.cpp index 8cf8096a13..9a9ef1e9ac 100644 --- a/tests/auto/qml/qjsengine/tst_qjsengine.cpp +++ b/tests/auto/qml/qjsengine/tst_qjsengine.cpp @@ -304,6 +304,8 @@ private slots: void callWithSpreadOnElement(); void spreadNoOverflow(); + void symbolToVariant(); + public: Q_INVOKABLE QJSValue throwingCppMethod1(); Q_INVOKABLE void throwingCppMethod2(); @@ -6083,6 +6085,19 @@ void tst_QJSEngine::spreadNoOverflow() QCOMPARE(result.errorType(), QJSValue::RangeError); } +void tst_QJSEngine::symbolToVariant() +{ + QJSEngine engine; + const QJSValue val = engine.newSymbol("asymbol"); + QCOMPARE(val.toVariant(), QStringLiteral("Symbol(asymbol)")); + + const QVariant retained = val.toVariant(QJSValue::RetainJSObjects); + QCOMPARE(retained.metaType(), QMetaType::fromType()); + QVERIFY(retained.value().strictlyEquals(val)); + + QCOMPARE(val.toVariant(QJSValue::ConvertJSObjects), QStringLiteral("Symbol(asymbol)")); +} + QTEST_MAIN(tst_QJSEngine) #include "tst_qjsengine.moc"