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()};