diff options
author | Ulf Hermann <ulf.hermann@qt.io> | 2022-08-02 18:20:18 +0200 |
---|---|---|
committer | Qt Cherry-pick Bot <cherrypick_bot@qt-project.org> | 2022-08-29 15:33:26 +0000 |
commit | cbef5bae849d1d8c079e0b5fdd3d63fff5616ab8 (patch) | |
tree | 2118cbce68aebb78fe1c7382537d3dd30ac55b4d | |
parent | 0e5d277bbf66d6b9a75e22d3c60a8fca5368cf6b (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.cpp | 63 | ||||
-rw-r--r-- | src/qml/qml/qqmlcustomparser_p.h | 2 | ||||
-rw-r--r-- | src/qml/qml/qqmlpropertyvalidator_p.h | 3 | ||||
-rw-r--r-- | src/qmlmodels/qqmllistmodel.cpp | 6 | ||||
-rw-r--r-- | tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp | 2 | ||||
-rw-r--r-- | tests/auto/qml/qqmllistmodel/data/Model.qml | 9 | ||||
-rw-r--r-- | tests/auto/qml/qqmllistmodel/data/enumsInListElement.qml | 8 | ||||
-rw-r--r-- | tests/auto/qml/qqmllistmodel/tst_qqmllistmodel.cpp | 18 |
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" |