diff options
| author | Ulf Hermann <ulf.hermann@qt.io> | 2022-03-25 10:56:41 +0100 |
|---|---|---|
| committer | Ulf Hermann <ulf.hermann@qt.io> | 2022-03-28 10:28:35 +0200 |
| commit | 3fd49c82cf4eccd24c5141d9dcb8a41722e70fc2 (patch) | |
| tree | 7df63f092d4cc256c78597316371bb89eacbf9b3 | |
| parent | 664aa0c1893e03d4de44959f188f06082bcf5c0c (diff) | |
Respect invokable toString() methods
We should not invoke the base toString() method if there is an override
in a more specific type. This involves lowering the priority of generic
JS lookups when resolving scope properties, in favor of context and
scope lookup.
[ChangeLog][QtQml] You can now override the JavaScript toString() method
by providing a Q_INVOKABLE method of the same name in your QObject-based
C++ classes.
Fixes: QTBUG-87697
Change-Id: I6190111f4c28e54ce76c391c69c4a921e290e612
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
| -rw-r--r-- | src/qml/jsruntime/qv4qmlcontext.cpp | 18 | ||||
| -rw-r--r-- | src/qml/jsruntime/qv4qobjectwrapper.cpp | 33 | ||||
| -rw-r--r-- | tests/auto/qml/qqmlengine/tst_qqmlengine.cpp | 38 |
3 files changed, 71 insertions, 18 deletions
diff --git a/src/qml/jsruntime/qv4qmlcontext.cpp b/src/qml/jsruntime/qv4qmlcontext.cpp index e3069fdc06..d1b923fa36 100644 --- a/src/qml/jsruntime/qv4qmlcontext.cpp +++ b/src/qml/jsruntime/qv4qmlcontext.cpp @@ -150,13 +150,7 @@ ReturnedValue QQmlContextWrapper::getPropertyAndBase(const QQmlContextWrapper *r return Object::virtualGet(resource, id, receiver, hasProperty); } - bool hasProp = false; - ScopedValue result(scope, Object::virtualGet(resource, id, receiver, &hasProp)); - if (hasProp) { - if (hasProperty) - *hasProperty = hasProp; - return result->asReturnedValue(); - } + ScopedValue result(scope); // It's possible we could delay the calculation of the "actual" context (in the case // of sub contexts) until it is definitely needed. @@ -360,6 +354,16 @@ ReturnedValue QQmlContextWrapper::getPropertyAndBase(const QQmlContextWrapper *r lookup = nullptr; } + // Do the generic JS lookup late. + // The scope, context, types etc should be able to override it. + bool hasProp = false; + result = Object::virtualGet(resource, id, receiver, &hasProp); + if (hasProp) { + if (hasProperty) + *hasProperty = hasProp; + return result->asReturnedValue(); + } + // Do a lookup in the global object here to avoid expressionContext->unresolvedNames becoming // true if we access properties of the global object. if (originalLookup) { diff --git a/src/qml/jsruntime/qv4qobjectwrapper.cpp b/src/qml/jsruntime/qv4qobjectwrapper.cpp index 5d29cab79b..8f7f65027c 100644 --- a/src/qml/jsruntime/qv4qobjectwrapper.cpp +++ b/src/qml/jsruntime/qv4qobjectwrapper.cpp @@ -2211,24 +2211,35 @@ void Heap::QObjectMethod::ensureMethodsCache() ReturnedValue QObjectMethod::method_toString(ExecutionEngine *engine) const { - QString result; - if (const QMetaObject *metaObject = d()->metaObject()) { + const auto encode = [engine](const QString &result) { + return engine->newString(result)->asReturnedValue(); + }; - result += QString::fromUtf8(metaObject->className()) + - QLatin1String("(0x") + QString::number((quintptr)d()->object(),16); + if (const QMetaObject *metaObject = d()->metaObject()) { + if (QObject *qobject = d()->object()) { + const int id = metaObject->indexOfMethod("toString()"); + if (id >= 0) { + const QMetaMethod method = metaObject->method(id); + const QMetaType returnType = method.returnMetaType(); + QVariant result(returnType); + method.invoke(qobject, QGenericReturnArgument(returnType.name(), result.data())); + return engine->fromVariant(result); + } - if (d()->object()) { - QString objectName = d()->object()->objectName(); + QString result; + result += QString::fromUtf8(metaObject->className()) + + QLatin1String("(0x") + QString::number(quintptr(qobject), 16); + QString objectName = qobject->objectName(); if (!objectName.isEmpty()) result += QLatin1String(", \"") + objectName + QLatin1Char('\"'); + result += QLatin1Char(')'); + return encode(result); + } else { + return encode(QString::fromUtf8(metaObject->className()) + QLatin1String("(0x0)")); } - - result += QLatin1Char(')'); - } else { - result = QLatin1String("null"); } - return engine->newString(result)->asReturnedValue(); + return encode(QLatin1String("null")); } ReturnedValue QObjectMethod::method_destroy(ExecutionEngine *engine, const Value *args, int argc) const diff --git a/tests/auto/qml/qqmlengine/tst_qqmlengine.cpp b/tests/auto/qml/qqmlengine/tst_qqmlengine.cpp index 635ea40d05..596b53f948 100644 --- a/tests/auto/qml/qqmlengine/tst_qqmlengine.cpp +++ b/tests/auto/qml/qqmlengine/tst_qqmlengine.cpp @@ -93,6 +93,7 @@ private slots: void attachedObjectAsObject(); void listPropertyAsQJSValue(); void stringToColor(); + void qobjectToString(); public slots: QObject *createAQObjectForOwnershipTest () @@ -1554,6 +1555,43 @@ void tst_qqmlengine::stringToColor() QCOMPARE(color, variant); } +class WithToString : public QObject +{ + Q_OBJECT + QML_ELEMENT +public: + Q_INVOKABLE QString toString() const { return QStringLiteral("things"); } +}; + +class WithToNumber : public QObject +{ + Q_OBJECT + QML_ELEMENT +public: + Q_INVOKABLE int toString() const { return 4; } +}; + +void tst_qqmlengine::qobjectToString() +{ + qmlRegisterTypesAndRevisions<WithToString>("WithToString", 1); + qmlRegisterTypesAndRevisions<WithToNumber>("WithToString", 1); + QQmlEngine engine; + QQmlComponent c(&engine); + c.setData(R"( + import WithToString + import QtQml + + WithToString { + id: self + property QtObject weird: WithToNumber {} + objectName: toString() + ' ' + self.toString() + ' ' + weird.toString() + } + )", QUrl()); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QCOMPARE(o->objectName(), QStringLiteral("things things 4")); +} + QTEST_MAIN(tst_qqmlengine) #include "tst_qqmlengine.moc" |
