aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorUlf Hermann <ulf.hermann@qt.io>2022-03-25 10:56:41 +0100
committerUlf Hermann <ulf.hermann@qt.io>2022-03-28 10:28:35 +0200
commit3fd49c82cf4eccd24c5141d9dcb8a41722e70fc2 (patch)
tree7df63f092d4cc256c78597316371bb89eacbf9b3
parent664aa0c1893e03d4de44959f188f06082bcf5c0c (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.cpp18
-rw-r--r--src/qml/jsruntime/qv4qobjectwrapper.cpp33
-rw-r--r--tests/auto/qml/qqmlengine/tst_qqmlengine.cpp38
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"