diff options
| author | Dmitrii Akshintsev <[email protected]> | 2025-07-30 17:36:16 +0200 |
|---|---|---|
| committer | Dmitrii Akshintsev <[email protected]> | 2025-09-17 15:31:15 +0200 |
| commit | 29b5743f47088feb3f61cd9e9d1a1e3f6ad542f9 (patch) | |
| tree | 782231ceddadbac0aa3832fd9f5883b3da032a2d | |
| parent | 228c783a233e1dbb789e2d96ab5a537827372ba2 (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.cpp | 19 | ||||
| -rw-r--r-- | src/qml/qml/qqmlpropertycache_p.h | 3 | ||||
| -rw-r--r-- | src/qml/qml/qqmlpropertycachecreator_p.h | 69 | ||||
| -rw-r--r-- | tests/auto/qml/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | tests/auto/qml/qqmlpropertycachecreator/CMakeLists.txt | 18 | ||||
| -rw-r--r-- | tests/auto/qml/qqmlpropertycachecreator/tst_qqmlpropertycachecreator.cpp | 135 | ||||
| -rw-r--r-- | tests/auto/qml/qqmlpropertycachecreator/tst_qqmlpropertycachecreator.h | 14 |
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(); +}; |
