From 3531157611972cbce04b1ffe3acb059bf108f97c Mon Sep 17 00:00:00 2001 From: Marco Bubke Date: Thu, 19 Jun 2025 11:45:00 +0200 Subject: [PATCH] QmlDesigner: Notify error for bases cycle When a cyclic dependency occurs within the code model, the synchronization process halts, leading to inconsistencies in type data across components. This desynchronization can cause unexpected behavior or errors during runtime. Currently, the cycle must be manually resolved by the user to restore proper synchronization. Future improvements may include automated cycle detection and resolution mechanisms. Change-Id: I7365122fab1912b20e1ae34e7ee4fd7137fb637e Reviewed-by: Thomas Hartmann --- .../libs/designercore/include/sourcepathids.h | 2 +- .../projectstorage/projectstorage.cpp | 10 +++++-- .../projectstorageerrornotifierinterface.h | 1 + .../project/projectstorageerrornotifier.cpp | 9 +++++++ .../project/projectstorageerrornotifier.h | 1 + .../mocks/projectstorageerrornotifiermock.h | 4 +++ .../projectstorage/projectstorage-test.cpp | 26 +++++++++++++++++++ 7 files changed, 50 insertions(+), 3 deletions(-) diff --git a/src/plugins/qmldesigner/libs/designercore/include/sourcepathids.h b/src/plugins/qmldesigner/libs/designercore/include/sourcepathids.h index 8cf22df5a10..8c1b1c5a9db 100644 --- a/src/plugins/qmldesigner/libs/designercore/include/sourcepathids.h +++ b/src/plugins/qmldesigner/libs/designercore/include/sourcepathids.h @@ -34,7 +34,7 @@ public: using IsBasicId = std::true_type; using DatabaseType = long long; - constexpr explicit SourceId() = default; + constexpr SourceId() = default; static constexpr SourceId create(DirectoryPathId directoryPathId, FileNameId fileNameId) { diff --git a/src/plugins/qmldesigner/libs/designercore/projectstorage/projectstorage.cpp b/src/plugins/qmldesigner/libs/designercore/projectstorage/projectstorage.cpp index bae87b3a9c7..b66d15c5676 100644 --- a/src/plugins/qmldesigner/libs/designercore/projectstorage/projectstorage.cpp +++ b/src/plugins/qmldesigner/libs/designercore/projectstorage/projectstorage.cpp @@ -132,6 +132,8 @@ struct ProjectStorage::Statements "defaultPropertyId=propertyDeclarationId " "WHERE t.typeId=?", database}; + mutable Sqlite::ReadStatement<2, 1> selectTypeNameAndSourceIdByTypeIdStatement{ + "SELECT name, sourceId FROM types WHERE typeId=?", database}; mutable Sqlite::ReadStatement<5, 1> selectExportedTypesByTypeIdStatement{ "SELECT moduleId, typeId, name, majorVersion, minorVersion " "FROM exportedTypeNames " @@ -4428,9 +4430,13 @@ void ProjectStorage::checkForPrototypeChainCycle(TypeId typeId) const category(), keyValue("type id", typeId)}; - auto callback = [=](TypeId currentTypeId) { - if (typeId == currentTypeId) + auto callback = [&](TypeId currentTypeId) { + if (typeId == currentTypeId) { + auto [name, sourceId] = s->selectTypeNameAndSourceIdByTypeIdStatement + .value>(typeId); + errorNotifier->prototypeCycle(name, sourceId); throw PrototypeChainCycle{}; + } }; s->selectPrototypeAndExtensionIdsStatement.readCallback(callback, typeId); diff --git a/src/plugins/qmldesigner/libs/designercore/projectstorage/projectstorageerrornotifierinterface.h b/src/plugins/qmldesigner/libs/designercore/projectstorage/projectstorageerrornotifierinterface.h index df4efdc89b6..18bd1d03842 100644 --- a/src/plugins/qmldesigner/libs/designercore/projectstorage/projectstorageerrornotifierinterface.h +++ b/src/plugins/qmldesigner/libs/designercore/projectstorage/projectstorageerrornotifierinterface.h @@ -32,6 +32,7 @@ public: = 0; virtual void qmltypesFileMissing(QStringView qmltypesPath) = 0; + virtual void prototypeCycle(Utils::SmallStringView typeName, SourceId typeSourceId) = 0; protected: ~ProjectStorageErrorNotifierInterface() = default; diff --git a/src/plugins/qmldesigner/project/projectstorageerrornotifier.cpp b/src/plugins/qmldesigner/project/projectstorageerrornotifier.cpp index 25646c21a35..b03d68370ff 100644 --- a/src/plugins/qmldesigner/project/projectstorageerrornotifier.cpp +++ b/src/plugins/qmldesigner/project/projectstorageerrornotifier.cpp @@ -89,4 +89,13 @@ void ProjectStorageErrorNotifier::qmltypesFileMissing(QStringView qmltypesPath) qmltypesPath); } +void ProjectStorageErrorNotifier::prototypeCycle(Utils::SmallStringView typeName, SourceId typeSourceId) +{ + const QString typeNameString{typeName}; + + logIssue(ProjectExplorer::Task::Error, + Tr::tr("Prototype cycle detected for type %1 in %2.").arg(typeNameString), + m_pathCache.sourcePath(typeSourceId)); +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/project/projectstorageerrornotifier.h b/src/plugins/qmldesigner/project/projectstorageerrornotifier.h index 03606259d43..3fd4441bd52 100644 --- a/src/plugins/qmldesigner/project/projectstorageerrornotifier.h +++ b/src/plugins/qmldesigner/project/projectstorageerrornotifier.h @@ -28,6 +28,7 @@ public: SourceId qmlDocumentSourceId, SourceId qmldirSourceId) override; void qmltypesFileMissing(QStringView qmltypesPath) override; + void prototypeCycle(Utils::SmallStringView typeName, SourceId typeSourceId) override; private: PathCacheType &m_pathCache; diff --git a/tests/unit/tests/mocks/projectstorageerrornotifiermock.h b/tests/unit/tests/mocks/projectstorageerrornotifiermock.h index e58b27d1a33..0a576075560 100644 --- a/tests/unit/tests/mocks/projectstorageerrornotifiermock.h +++ b/tests/unit/tests/mocks/projectstorageerrornotifiermock.h @@ -33,4 +33,8 @@ public: QmlDesigner::SourceId qmldirSourceId), (override)); MOCK_METHOD(void, qmltypesFileMissing, (QStringView qmltypesPath), (override)); + MOCK_METHOD(void, + prototypeCycle, + (Utils::SmallStringView typeName, QmlDesigner::SourceId typeSourceId), + (override)); }; diff --git a/tests/unit/tests/unittests/projectstorage/projectstorage-test.cpp b/tests/unit/tests/unittests/projectstorage/projectstorage-test.cpp index acf7ead2e30..4004033216b 100644 --- a/tests/unit/tests/unittests/projectstorage/projectstorage-test.cpp +++ b/tests/unit/tests/unittests/projectstorage/projectstorage-test.cpp @@ -4448,6 +4448,32 @@ TEST_F(ProjectStorage, throw_for_prototype_chain_cycles) QmlDesigner::PrototypeChainCycle); } +TEST_F(ProjectStorage, notifies_error_for_prototype_chain_cycles) +{ + auto package{createSimpleSynchronizationPackage()}; + package.types[1].prototype = Storage::Synchronization::ImportedType{"Object2"}; + package.types.push_back(Storage::Synchronization::Type{ + "QObject2", + Storage::Synchronization::ImportedType{"Item"}, + Storage::Synchronization::ImportedType{}, + TypeTraitsKind::Reference, + sourceId3, + {Storage::Synchronization::ExportedType{pathToModuleId, "Object2"}, + Storage::Synchronization::ExportedType{pathToModuleId, "Obj2"}}}); + package.imports.emplace_back(pathToModuleId, Storage::Version{}, sourceId2); + package.imports.emplace_back(qtQuickModuleId, Storage::Version{}, sourceId3); + package.imports.emplace_back(pathToModuleId, Storage::Version{}, sourceId3); + + EXPECT_CALL(errorNotifierMock, prototypeCycle(Eq("QObject2"), sourceId3)); + + EXPECT_ANY_THROW( + storage.synchronize(SynchronizationPackage{package.imports, + package.types, + {sourceId1, sourceId2, sourceId3}, + package.moduleDependencies, + package.updatedModuleDependencySourceIds})); +} + TEST_F(ProjectStorage, throw_for_extension_chain_cycles) { auto package{createSimpleSynchronizationPackage()};