aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorUlf Hermann <ulf.hermann@qt.io>2022-08-02 18:20:18 +0200
committerQt Cherry-pick Bot <cherrypick_bot@qt-project.org>2022-08-29 15:33:26 +0000
commitcbef5bae849d1d8c079e0b5fdd3d63fff5616ab8 (patch)
tree2118cbce68aebb78fe1c7382537d3dd30ac55b4d
parent0e5d277bbf66d6b9a75e22d3c60a8fca5368cf6b (diff)
QmlModels: Fix enum resolution in ListElement
There were two problems: a, We need to allow recursion when querying for enums. Otherwise we cannot find enums in the same document. b, when the enum resolution is done in the validation phase, we cannot query the current document's QQmlType for enums, yet, as it's not finalized. However, the QQmlPropertyValidator has the QQmlPropertyCache we're looking for in that case. c, As a drive-by, fix the excessive conversions between QByteArray and QString. Fixes: QTBUG-95864 Change-Id: If0d2687986e1483a27ce11373a204235b92a6efd Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io> (cherry picked from commit cad5c89a0f0ebb24f17171fdbab8355832f0f415) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
-rw-r--r--src/qml/qml/qqmlcustomparser.cpp63
-rw-r--r--src/qml/qml/qqmlcustomparser_p.h2
-rw-r--r--src/qml/qml/qqmlpropertyvalidator_p.h3
-rw-r--r--src/qmlmodels/qqmllistmodel.cpp6
-rw-r--r--tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp2
-rw-r--r--tests/auto/qml/qqmllistmodel/data/Model.qml9
-rw-r--r--tests/auto/qml/qqmllistmodel/data/enumsInListElement.qml8
-rw-r--r--tests/auto/qml/qqmllistmodel/tst_qqmllistmodel.cpp18
8 files changed, 92 insertions, 19 deletions
diff --git a/src/qml/qml/qqmlcustomparser.cpp b/src/qml/qml/qqmlcustomparser.cpp
index fba805f084..ca3985bd92 100644
--- a/src/qml/qml/qqmlcustomparser.cpp
+++ b/src/qml/qml/qqmlcustomparser.cpp
@@ -1,6 +1,7 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#include "qml/qqmlpropertyvalidator_p.h"
#include "qqmlcustomparser_p.h"
#include <private/qv4compileddata_p.h>
@@ -81,7 +82,7 @@ void QQmlCustomParser::error(const QV4::CompiledData::Location &location, const
A valid \a ok must be provided, or the function will assert.
*/
-int QQmlCustomParser::evaluateEnum(const QByteArray& script, bool *ok) const
+int QQmlCustomParser::evaluateEnum(const QString &script, bool *ok) const
{
Q_ASSERT_X(ok, "QQmlCustomParser::evaluateEnum", "ok must not be a null pointer");
*ok = false;
@@ -91,7 +92,7 @@ int QQmlCustomParser::evaluateEnum(const QByteArray& script, bool *ok) const
// * <TypeName>.<ScopedEnumName>.<EnumValue>
auto nextDot = [&](int dot) {
- const int nextDot = script.indexOf('.', dot + 1);
+ const int nextDot = script.indexOf(u'.', dot + 1);
return (nextDot == script.length() - 1) ? -1 : nextDot;
};
@@ -99,7 +100,7 @@ int QQmlCustomParser::evaluateEnum(const QByteArray& script, bool *ok) const
if (dot == -1)
return -1;
- QString scope = QString::fromUtf8(script.left(dot));
+ const QString scope = script.left(dot);
if (scope != QLatin1String("Qt")) {
if (imports.isNull())
@@ -108,23 +109,35 @@ int QQmlCustomParser::evaluateEnum(const QByteArray& script, bool *ok) const
if (imports.isT1()) {
QQmlImportNamespace *ns = nullptr;
- if (!imports.asT1()->resolveType(scope, &type, nullptr, &ns))
+
+ // Pass &recursionDetected to resolveType because that implicitly allows recursion.
+ // This way we can find the QQmlType of the document we're currently validating.
+ bool recursionDetected = false;
+
+ if (!imports.asT1()->resolveType(
+ scope, &type, nullptr, &ns, nullptr,
+ QQmlType::AnyRegistrationType, &recursionDetected)) {
return -1;
+ }
+
if (!type.isValid() && ns != nullptr) {
dot = nextDot(dot);
- if (dot == -1 || !imports.asT1()->resolveType(QString::fromUtf8(script.left(dot)),
- &type, nullptr, nullptr)) {
+ if (dot == -1 || !imports.asT1()->resolveType(
+ script.left(dot), &type, nullptr, nullptr, nullptr,
+ QQmlType::AnyRegistrationType, &recursionDetected)) {
return -1;
}
}
} else {
- QQmlTypeNameCache::Result result = imports.asT2()->query(scope);
+ // Allow recursion so that we can find enums from the same document.
+ const QQmlTypeNameCache::Result result
+ = imports.asT2()->query<QQmlImport::AllowRecursion>(scope);
if (result.isValid()) {
type = result.type;
} else if (result.importNamespace) {
dot = nextDot(dot);
if (dot != -1)
- type = imports.asT2()->query(QString::fromUtf8(script.left(dot))).type;
+ type = imports.asT2()->query<QQmlImport::AllowRecursion>(script.left(dot)).type;
}
}
@@ -133,19 +146,43 @@ int QQmlCustomParser::evaluateEnum(const QByteArray& script, bool *ok) const
const int dot2 = nextDot(dot);
const bool dot2Valid = (dot2 != -1);
- QByteArray enumValue = script.mid(dot2Valid ? dot2 + 1 : dot + 1);
- QByteArray scopedEnumName = (dot2Valid ? script.mid(dot + 1, dot2 - dot - 1) : QByteArray());
+ const QString enumValue = script.mid(dot2Valid ? dot2 + 1 : dot + 1);
+ const QString scopedEnumName = dot2Valid ? script.mid(dot + 1, dot2 - dot - 1) : QString();
+
+ // If we're currently validating the same document, we won't be able to find its enums using
+ // the QQmlType. However, we do have the property cache already, and that one contains the
+ // enums.
+ const QUrl documentUrl = validator ? validator->documentSourceUrl() : QUrl();
+ if (documentUrl.isValid() && documentUrl == type.sourceUrl()) {
+ const QQmlPropertyCache::ConstPtr rootCache = validator->rootPropertyCache();
+ const int count = rootCache->qmlEnumCount();
+ for (int ii = 0; ii < count; ++ii) {
+ const QQmlEnumData *enumData = rootCache->qmlEnum(ii);
+ if (!scopedEnumName.isEmpty() && scopedEnumName != enumData->name)
+ continue;
+
+ for (int jj = 0; jj < enumData->values.count(); ++jj) {
+ const QQmlEnumValue value = enumData->values.at(jj);
+ if (value.namedValue == enumValue) {
+ *ok = true;
+ return value.value;
+ }
+ }
+ }
+ return -1;
+ }
+
if (!scopedEnumName.isEmpty())
return type.scopedEnumValue(engine, scopedEnumName, enumValue, ok);
else
- return type.enumValue(engine, QHashedCStringRef(enumValue.constData(), enumValue.length()), ok);
+ return type.enumValue(engine, enumValue, ok);
}
- QByteArray enumValue = script.mid(dot + 1);
+ const QString enumValue = script.mid(dot + 1);
const QMetaObject *mo = &Qt::staticMetaObject;
int i = mo->enumeratorCount();
while (i--) {
- int v = mo->enumerator(i).keyToValue(enumValue.constData(), ok);
+ int v = mo->enumerator(i).keyToValue(enumValue.toUtf8().constData(), ok);
if (*ok)
return v;
}
diff --git a/src/qml/qml/qqmlcustomparser_p.h b/src/qml/qml/qqmlcustomparser_p.h
index afb5b810c2..98a9f21ae9 100644
--- a/src/qml/qml/qqmlcustomparser_p.h
+++ b/src/qml/qml/qqmlcustomparser_p.h
@@ -56,7 +56,7 @@ protected:
{ error(object->location, description); }
void error(const QV4::CompiledData::Location &location, const QString& description);
- int evaluateEnum(const QByteArray&, bool *ok) const;
+ int evaluateEnum(const QString &, bool *ok) const;
const QMetaObject *resolveType(const QString&) const;
diff --git a/src/qml/qml/qqmlpropertyvalidator_p.h b/src/qml/qml/qqmlpropertyvalidator_p.h
index bd81b75e78..e1154347ec 100644
--- a/src/qml/qml/qqmlpropertyvalidator_p.h
+++ b/src/qml/qml/qqmlpropertyvalidator_p.h
@@ -32,6 +32,9 @@ public:
QVector<QQmlError> validate();
+ QQmlPropertyCache::ConstPtr rootPropertyCache() const { return propertyCaches.at(0); }
+ QUrl documentSourceUrl() const { return compilationUnit->url(); }
+
private:
QVector<QQmlError> validateObject(
int objectIndex, const QV4::CompiledData::Binding *instantiatingBinding,
diff --git a/src/qmlmodels/qqmllistmodel.cpp b/src/qmlmodels/qqmllistmodel.cpp
index f02449e6c6..4a3d582cb1 100644
--- a/src/qmlmodels/qqmllistmodel.cpp
+++ b/src/qmlmodels/qqmllistmodel.cpp
@@ -2881,9 +2881,8 @@ bool QQmlListModelParser::verifyProperty(const QQmlRefPointer<QV4::ExecutableCom
} else if (binding->type() == QV4::CompiledData::Binding::Type_Script) {
QString scriptStr = compilationUnit->bindingValueAsScriptString(binding);
if (!binding->isFunctionExpression() && !definesEmptyList(scriptStr)) {
- QByteArray script = scriptStr.toUtf8();
bool ok;
- evaluateEnum(script, &ok);
+ evaluateEnum(scriptStr, &ok);
if (!ok) {
error(binding, QQmlListModel::tr("ListElement: cannot use script for property value"));
return false;
@@ -2966,9 +2965,8 @@ bool QQmlListModelParser::applyProperty(
QJSValuePrivate::setValue(&v, result->asReturnedValue());
value.setValue(v);
} else {
- QByteArray script = scriptStr.toUtf8();
bool ok;
- value = evaluateEnum(script, &ok);
+ value = evaluateEnum(scriptStr, &ok);
}
} else {
Q_UNREACHABLE();
diff --git a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp
index 59ba9594f9..e25d623fe5 100644
--- a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp
+++ b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp
@@ -6143,7 +6143,7 @@ void tst_qqmllanguage::qualifiedScopeInCustomParser()
"ListModel {\n"
" ListElement { text: \"a\"; type: BACKEND.EnumTester.FIRST }\n"
"}\n", QUrl());
- QVERIFY(component.isReady());
+ QVERIFY2(component.isReady(), qPrintable(component.errorString()));
QScopedPointer<QObject> obj(component.create());
QVERIFY(!obj.isNull());
}
diff --git a/tests/auto/qml/qqmllistmodel/data/Model.qml b/tests/auto/qml/qqmllistmodel/data/Model.qml
new file mode 100644
index 0000000000..f6aeed5bdd
--- /dev/null
+++ b/tests/auto/qml/qqmllistmodel/data/Model.qml
@@ -0,0 +1,9 @@
+import QtQml.Models
+
+ListModel {
+ enum Choose { Foo, Bar, Baz }
+
+ ListElement { choose: Model.Choose.Foo }
+ ListElement { choose: Model.Choose.Bar }
+ ListElement { choose: Model.Choose.Baz }
+}
diff --git a/tests/auto/qml/qqmllistmodel/data/enumsInListElement.qml b/tests/auto/qml/qqmllistmodel/data/enumsInListElement.qml
new file mode 100644
index 0000000000..e8d594dfd8
--- /dev/null
+++ b/tests/auto/qml/qqmllistmodel/data/enumsInListElement.qml
@@ -0,0 +1,8 @@
+import QtQuick
+
+ListView {
+ width: 180
+ height: 200
+ model: Model {}
+ delegate: Text { text: choose }
+}
diff --git a/tests/auto/qml/qqmllistmodel/tst_qqmllistmodel.cpp b/tests/auto/qml/qqmllistmodel/tst_qqmllistmodel.cpp
index c92956bd49..7f31e43b1e 100644
--- a/tests/auto/qml/qqmllistmodel/tst_qqmllistmodel.cpp
+++ b/tests/auto/qml/qqmllistmodel/tst_qqmllistmodel.cpp
@@ -4,6 +4,7 @@
#include <QtQuick/private/qquickitem_p.h>
#include <QtQuick/private/qquicktext_p.h>
#include <QtQuick/private/qquickanimation_p.h>
+#include <QtQuick/private/qquicklistview_p.h>
#include <QtQml/private/qqmlengine_p.h>
#include <QtQmlModels/private/qqmllistmodel_p.h>
#include <QtQml/private/qqmlexpression_p.h>
@@ -116,6 +117,7 @@ private slots:
void listElementWithTemplateString();
void destroyComponentObject();
void objectOwnershipFlip();
+ void enumsInListElement();
};
bool tst_qqmllistmodel::compareVariantList(const QVariantList &testList, QVariant object)
@@ -1891,6 +1893,22 @@ void tst_qqmllistmodel::objectOwnershipFlip()
QCOMPARE(QJSEngine::objectOwnership(item.data()), QJSEngine::CppOwnership);
}
+void tst_qqmllistmodel::enumsInListElement()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, testFileUrl("enumsInListElement.qml"));
+ QVERIFY2(component.isReady(), qPrintable(component.errorString()));
+ QScopedPointer<QObject> root(component.create());
+ QVERIFY(!root.isNull());
+
+ QQuickListView *listView = qobject_cast<QQuickListView *>(root.data());
+ QVERIFY(listView);
+ QCOMPARE(listView->count(), 3);
+ for (int i = 0; i < 3; ++i) {
+ QCOMPARE(listView->itemAtIndex(i)->property("text"), QVariant(QString::number(i)));
+ }
+}
+
QTEST_MAIN(tst_qqmllistmodel)
#include "tst_qqmllistmodel.moc"