aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDmitrii Akshintsev <[email protected]>2025-07-30 17:36:16 +0200
committerDmitrii Akshintsev <[email protected]>2025-09-17 15:31:15 +0200
commit29b5743f47088feb3f61cd9e9d1a1e3f6ad542f9 (patch)
tree782231ceddadbac0aa3832fd9f5883b3da032a2d
parent228c783a233e1dbb789e2d96ab5a537827372ba2 (diff)
QQmlPropertyCacheCreator extract and expose tryCreateQQmlPropertyData
This patch aims to extract the logic of transforming QV4::CompiledData::Property into QQmlPropertyData. Currently it's arguably a responsibility of QQmlPropertyCacheCreator because it is responsible for the resolution of property type, which is central to the creation of QQmlPropertyData. Such refactoring allows more detailed and robust testing of irProperty -> QQmlPropertyData, which is handy in the context of QTBUG-98320 to make sure that property attributes are propagated correctly to the QQmlPropertyData (and later to the cache). Change-Id: Iffdfd22f515016c61c087414f0c4530e43556091 Task-number: QTBUG-98320 Reviewed-by: Ulf Hermann <[email protected]>
-rw-r--r--src/qml/qml/qqmlpropertycache.cpp19
-rw-r--r--src/qml/qml/qqmlpropertycache_p.h3
-rw-r--r--src/qml/qml/qqmlpropertycachecreator_p.h69
-rw-r--r--tests/auto/qml/CMakeLists.txt1
-rw-r--r--tests/auto/qml/qqmlpropertycachecreator/CMakeLists.txt18
-rw-r--r--tests/auto/qml/qqmlpropertycachecreator/tst_qqmlpropertycachecreator.cpp135
-rw-r--r--tests/auto/qml/qqmlpropertycachecreator/tst_qqmlpropertycachecreator.h14
7 files changed, 220 insertions, 39 deletions
diff --git a/src/qml/qml/qqmlpropertycache.cpp b/src/qml/qml/qqmlpropertycache.cpp
index fb16e51c2b..424a389459 100644
--- a/src/qml/qml/qqmlpropertycache.cpp
+++ b/src/qml/qml/qqmlpropertycache.cpp
@@ -205,25 +205,6 @@ QQmlPropertyCache::Ptr QQmlPropertyCache::copyAndReserve(
return rv;
}
-/*! \internal
-
- \a notifyIndex MUST be in the signal index range (see QObjectPrivate::signalIndex()).
- This is different from QMetaMethod::methodIndex().
-*/
-QQmlPropertyCache::OverrideResult
-QQmlPropertyCache::appendProperty(const QString &name, QQmlPropertyData::Flags flags, int coreIndex,
- QMetaType propType, QTypeRevision version, int notifyIndex)
-{
- QQmlPropertyData data;
- data.setPropType(propType);
- data.setCoreIndex(coreIndex);
- data.setNotifyIndex(notifyIndex);
- data.setFlags(flags);
- data.setTypeVersion(version);
-
- return doAppendPropertyData(name, std::move(data));
-}
-
QQmlPropertyCache::OverrideResult
QQmlPropertyCache::appendAlias(const QString &name, QQmlPropertyData::Flags flags, int coreIndex,
QMetaType propType, QTypeRevision version, int notifyIndex,
diff --git a/src/qml/qml/qqmlpropertycache_p.h b/src/qml/qml/qqmlpropertycache_p.h
index dcd7dfe1a6..bfc1b73390 100644
--- a/src/qml/qml/qqmlpropertycache_p.h
+++ b/src/qml/qml/qqmlpropertycache_p.h
@@ -153,8 +153,6 @@ public:
int propertyCount, int methodCount, int signalCount, int enumCount) const;
enum OverrideResult { NoOverride, InvalidOverride, ValidOverride };
- OverrideResult appendProperty(const QString &, QQmlPropertyData::Flags flags, int coreIndex,
- QMetaType propType, QTypeRevision revision, int notifyIndex);
OverrideResult appendAlias(const QString &, QQmlPropertyData::Flags flags, int coreIndex,
QMetaType propType, QTypeRevision version, int notifyIndex,
int encodedTargetIndex);
@@ -302,6 +300,7 @@ private:
return handleOverride(name, data, findNamedProperty(name));
}
+ // TODO consider making public
OverrideResult doAppendPropertyData(const QString &name, QQmlPropertyData &&data)
{
QQmlPropertyData *old = findNamedProperty(name);
diff --git a/src/qml/qml/qqmlpropertycachecreator_p.h b/src/qml/qml/qqmlpropertycachecreator_p.h
index bf9ba820f8..8faf1c7615 100644
--- a/src/qml/qml/qqmlpropertycachecreator_p.h
+++ b/src/qml/qml/qqmlpropertycachecreator_p.h
@@ -223,6 +223,20 @@ public:
tryDeriveCacheFrom(const CompiledObject *obj, const QQmlPropertyCache::ConstPtr &baseTypeCache,
QByteArray dynamicClassName = QByteArray()) const;
+ /*!
+ \internal
+ Tries to create a QQmlPropertyData based on IR of a property.
+ This involves property type resolution and property flags creation
+
+ \a notifyIndex MUST be in the signal index range (see QObjectPrivate::signalIndex()).
+ This is different from QMetaMethod::methodIndex()
+
+ return error in case of failed type resolution
+ */
+ [[nodiscard]] q23::expected<QQmlPropertyData, QQmlError>
+ tryCreateQQmlPropertyData(const QV4::CompiledData::Property &propertyIR, int coreIndex,
+ int notifyIndex) const;
+
protected:
enum class VMEMetaObjectIsRequired { Maybe, Always };
@@ -248,9 +262,10 @@ private:
[[nodiscard]] q23::expected<PropertyType, QQmlError>
tryResolvePropertyType(const QV4::CompiledData::Property &propertyIR) const;
- [[nodiscard]] QQmlPropertyData::Flags
+ // can be made a free function
+ [[nodiscard]] static QQmlPropertyData::Flags
propertyDataFlags(const QV4::CompiledData::Property &propertyIR,
- const PropertyType &resolvedPropertyType) const;
+ const PropertyType &resolvedPropertyType);
protected:
QQmlTypeLoader *const typeLoader;
@@ -362,7 +377,6 @@ QQmlPropertyCacheCreator<ObjectContainer>::tryDeriveCacheFrom(
obj->signalCount() + obj->propertyCount() + obj->aliasCount(), obj->enumCount());
cache->_dynamicClassName = std::move(dynamicClassName);
- int effectivePropertyIndex = cache->propertyIndexCacheStart;
int effectiveMethodIndex = cache->methodIndexCacheStart;
// For property change signal override detection.
@@ -550,36 +564,54 @@ QQmlPropertyCacheCreator<ObjectContainer>::tryDeriveCacheFrom(
}
// Dynamic properties
- int effectiveSignalIndex = cache->signalHandlerIndexCacheStart;
- int propertyIdx = 0;
+ int propertyIndex = cache->propertyIndexCacheStart;
+ int notifyIndex = cache->signalHandlerIndexCacheStart;
+ int objPropertyIdx = 0;
p = obj->propertiesBegin();
pend = obj->propertiesEnd();
- for (; p != pend; ++p, ++propertyIdx) {
- const auto propertyTypeOrError = tryResolvePropertyType(*p);
- if (!propertyTypeOrError.has_value()) {
- return q23::make_unexpected(propertyTypeOrError.error());
+ for (; p != pend; ++p, ++objPropertyIdx, propertyIndex++, notifyIndex++) {
+ auto propertyDataOrError = tryCreateQQmlPropertyData(*p, propertyIndex, notifyIndex);
+ if (!propertyDataOrError.has_value()) {
+ return q23::make_unexpected(propertyDataOrError.error());
}
- const auto &propertyType = propertyTypeOrError.value();
QString propertyName = stringAt(p->nameIndex());
- if (!obj->hasAliasAsDefaultProperty() && propertyIdx == obj->indexOfDefaultPropertyOrAlias)
+ if (!obj->hasAliasAsDefaultProperty()
+ && objPropertyIdx == obj->indexOfDefaultPropertyOrAlias)
cache->_defaultPropertyName = propertyName;
- const auto overrideResult = cache->appendProperty(
- propertyName, propertyDataFlags(*p, propertyType), effectivePropertyIndex++,
- propertyType.metaType, propertyType.revision, effectiveSignalIndex);
- if (overrideResult == QQmlPropertyCache::OverrideResult::InvalidOverride) {
+ if (cache->doAppendPropertyData(propertyName, std::move(propertyDataOrError).value())
+ == QQmlPropertyCache::OverrideResult::InvalidOverride) {
return q23::make_unexpected(qQmlCompileError(
p->location,
// TODO improve error message
QQmlPropertyCacheCreatorBase::tr("Cannot override FINAL property")));
}
- effectiveSignalIndex++;
}
return cache;
};
template <typename ObjectContainer>
+q23::expected<QQmlPropertyData, QQmlError>
+QQmlPropertyCacheCreator<ObjectContainer>::tryCreateQQmlPropertyData(
+ const QV4::CompiledData::Property &propertyIR, int coreIndex, int notifyIndex) const
+{
+ const auto propertyTypeOrError = tryResolvePropertyType(propertyIR);
+ if (!propertyTypeOrError.has_value()) {
+ return q23::make_unexpected(propertyTypeOrError.error());
+ }
+ const auto propertyType = propertyTypeOrError.value();
+
+ QQmlPropertyData propertyData;
+ propertyData.setPropType(propertyType.metaType);
+ propertyData.setCoreIndex(coreIndex);
+ propertyData.setNotifyIndex(notifyIndex);
+ propertyData.setFlags(QQmlPropertyCacheCreator::propertyDataFlags(propertyIR, propertyType));
+ propertyData.setTypeVersion(propertyType.revision);
+ return propertyData;
+}
+
+template <typename ObjectContainer>
inline QQmlError QQmlPropertyCacheCreator<ObjectContainer>::buildMetaObjectRecursively(int objectIndex, const QQmlBindingInstantiationContext &context, VMEMetaObjectIsRequired isVMERequired)
{
auto isAddressable = [](const QUrl &url) {
@@ -833,6 +865,8 @@ inline auto QQmlPropertyCacheCreator<ObjectContainer>::tryResolvePropertyType(
}
// needs to be resolved
+ // can be extracted and passed to tryResolvePropertyType as a function object to simplify
+ // dependency injection (imports / typeLoader)
Q_ASSERT(!propertyIR.isCommonType());
QQmlType qmltype;
bool selfReference = false;
@@ -857,8 +891,7 @@ inline auto QQmlPropertyCacheCreator<ObjectContainer>::tryResolvePropertyType(
template <typename ObjectContainer>
inline QQmlPropertyData::Flags QQmlPropertyCacheCreator<ObjectContainer>::propertyDataFlags(
- const QV4::CompiledData::Property &propertyIR,
- const PropertyType &resolvedPropertyType) const
+ const QV4::CompiledData::Property &propertyIR, const PropertyType &resolvedPropertyType)
{
QQmlPropertyData::Flags flags;
diff --git a/tests/auto/qml/CMakeLists.txt b/tests/auto/qml/CMakeLists.txt
index 548a06c4cc..72b8977d97 100644
--- a/tests/auto/qml/CMakeLists.txt
+++ b/tests/auto/qml/CMakeLists.txt
@@ -122,6 +122,7 @@ if(QT_FEATURE_private_tests)
add_subdirectory(qqmlopenmetaobject)
add_subdirectory(qqmlproperty)
add_subdirectory(qqmlpropertycache)
+ add_subdirectory(qqmlpropertycachecreator)
add_subdirectory(qqmlpropertymap)
# special case begin
if (TARGET Qt::Sql)
diff --git a/tests/auto/qml/qqmlpropertycachecreator/CMakeLists.txt b/tests/auto/qml/qqmlpropertycachecreator/CMakeLists.txt
new file mode 100644
index 0000000000..22682e90c8
--- /dev/null
+++ b/tests/auto/qml/qqmlpropertycachecreator/CMakeLists.txt
@@ -0,0 +1,18 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
+ cmake_minimum_required(VERSION 3.16)
+ project(tst_qmljsir LANGUAGES CXX)
+ find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST)
+endif()
+
+qt_internal_add_test(tst_qqmlpropertycachecreator
+ SOURCES
+ tst_qqmlpropertycachecreator.h
+ tst_qqmlpropertycachecreator.cpp
+ LIBRARIES
+ Qt::Qml
+ Qt::QmlPrivate
+ #Qt::QuickTestUtilsPrivate
+)
diff --git a/tests/auto/qml/qqmlpropertycachecreator/tst_qqmlpropertycachecreator.cpp b/tests/auto/qml/qqmlpropertycachecreator/tst_qqmlpropertycachecreator.cpp
new file mode 100644
index 0000000000..f4af950c6f
--- /dev/null
+++ b/tests/auto/qml/qqmlpropertycachecreator/tst_qqmlpropertycachecreator.cpp
@@ -0,0 +1,135 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "tst_qqmlpropertycachecreator.h"
+
+#include <QtQml/private/qqmlpropertycachecreator_p.h>
+#include <QtQml/private/qqmltypecompiler_p.h>
+
+// #include <QtQuickTestUtils/private/qmlutils_p.h>
+
+// TODO consider moving to test utils
+class FakeObjectContainer
+{
+public:
+ FakeObjectContainer() = default;
+ FakeObjectContainer(std::unique_ptr<QmlIR::Document> docPtr,
+ QV4::CompiledData::ResolvedTypeReferenceMap resolvedTypes)
+ : m_document(std::move(docPtr)), m_resolvedTypes(std::move(resolvedTypes))
+ {
+ }
+
+ // --- interface used by QQmlPropertyCacheCreator --------
+ using CompiledObject = QmlIR::Object;
+
+ int objectCount() const { return m_document->objects.size(); }
+ QmlIR::Object *objectAt(int index) const { return m_document->objects.at(index); }
+
+ QmlIR::PoolList<QmlIR::Function>::Iterator
+ objectFunctionsBegin(const QmlIR::Object *object) const
+ {
+ return object->functionsBegin();
+ }
+ QmlIR::PoolList<QmlIR::Function>::Iterator objectFunctionsEnd(const QmlIR::Object *object) const
+ {
+ return object->functionsEnd();
+ }
+
+ // used to get IC type name
+ QString stringAt(int) const { return {}; }
+
+ using ListPropertyAssignBehavior = QmlIR::Pragma::ListPropertyAssignBehaviorValue;
+ ListPropertyAssignBehavior listPropertyAssignBehavior() const { return {}; }
+
+ QUrl url() const { return {}; }
+
+ QQmlType qmlTypeForComponent(const QString & = QString()) const { return {}; };
+
+ QV4::ResolvedTypeReference *resolvedType(int id) const { return m_resolvedTypes.value(id); }
+ // -----------------------------------------------------
+private:
+ std::unique_ptr<QmlIR::Document> m_document =
+ std::make_unique<QmlIR::Document>(QString(), QString(), true);
+ QV4::CompiledData::ResolvedTypeReferenceMap m_resolvedTypes;
+};
+
+/* for the non common type proper mocking of typeLoader and imports is needed*/
+static inline auto createQQmlPropertyDataCommonType(const QmlIR::Property &irProperty)
+{
+ QQmlPropertyCacheVector cacheVector;
+ FakeObjectContainer objContainer;
+
+ return QQmlPropertyCacheCreator(&cacheVector, nullptr, nullptr, &objContainer, nullptr, {})
+ .tryCreateQQmlPropertyData(irProperty, -1, -1)
+ .value_or(QQmlPropertyData());
+}
+
+namespace propertyDataComparator {
+using Type = qxp::function_ref<bool(const QQmlPropertyData &, const QQmlPropertyData &)>;
+
+const Type onlyFlags = [](const QQmlPropertyData &lhs, const QQmlPropertyData &rhs) -> bool {
+ return lhs.flags() == rhs.flags();
+};
+} // namespace propertyDataComparator
+
+void tst_qqmlpropertycachecreator::tryCreateQQmlPropertyData_commonType_data()
+{
+ QTest::addColumn<const QmlIR::Property>("irProperty");
+ QTest::addColumn<const QQmlPropertyData>("expectedPropertyData");
+ QTest::addColumn<propertyDataComparator::Type>("comparator");
+
+ const auto defaultIrPropertyAndFlags = []() {
+ QmlIR::Property irProperty = {};
+ irProperty.setCommonType(QV4::CompiledData::CommonType::Var);
+
+ QQmlPropertyData::Flags flags = {};
+ flags.setType(QQmlPropertyData::Flags::VarPropertyType);
+ flags.setIsWritable(true);
+ return std::make_pair(std::move(irProperty), std::move(flags));
+ };
+
+ {
+ auto [irProperty, flags] = defaultIrPropertyAndFlags();
+
+ QQmlPropertyData expectedPropertyData{};
+ expectedPropertyData.setFlags(flags);
+
+ QTest::newRow("property var")
+ << irProperty << expectedPropertyData << propertyDataComparator::onlyFlags;
+ }
+ {
+ auto [irProperty, flags] = defaultIrPropertyAndFlags();
+ irProperty.setIsReadOnly(true);
+ flags.setIsWritable(false);
+
+ QQmlPropertyData expectedPropertyData{};
+ expectedPropertyData.setFlags(flags);
+
+ QTest::newRow("readonly property var")
+ << irProperty << expectedPropertyData << propertyDataComparator::onlyFlags;
+ }
+ {
+ auto [irProperty, flags] = defaultIrPropertyAndFlags();
+ irProperty.setIsFinal(true);
+ flags.setIsFinal(true);
+
+ QQmlPropertyData expectedPropertyData{};
+ expectedPropertyData.setFlags(flags);
+
+ QTest::newRow("final property var")
+ << irProperty << expectedPropertyData << propertyDataComparator::onlyFlags;
+ }
+}
+
+void tst_qqmlpropertycachecreator::tryCreateQQmlPropertyData_commonType()
+{
+ QFETCH(const QmlIR::Property, irProperty);
+ QFETCH(const QQmlPropertyData, expectedPropertyData);
+ QFETCH(propertyDataComparator::Type, comparator);
+
+ const auto actualPropertyData = createQQmlPropertyDataCommonType(irProperty);
+
+ QVERIFY(comparator(actualPropertyData, expectedPropertyData));
+}
+
+QTEST_MAIN(tst_qqmlpropertycachecreator)
diff --git a/tests/auto/qml/qqmlpropertycachecreator/tst_qqmlpropertycachecreator.h b/tests/auto/qml/qqmlpropertycachecreator/tst_qqmlpropertycachecreator.h
new file mode 100644
index 0000000000..788e2b72a3
--- /dev/null
+++ b/tests/auto/qml/qqmlpropertycachecreator/tst_qqmlpropertycachecreator.h
@@ -0,0 +1,14 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QTest>
+
+#include <QtQml/private/qqmlirbuilder_p.h>
+
+class tst_qqmlpropertycachecreator : public QObject
+{
+ Q_OBJECT
+private slots:
+ void tryCreateQQmlPropertyData_commonType_data();
+ void tryCreateQQmlPropertyData_commonType();
+};