From b8322aece2eef2c14c95319355e2991cc92f230d Mon Sep 17 00:00:00 2001 From: Marco Bubke Date: Thu, 16 May 2024 18:17:21 +0200 Subject: [PATCH] QmlDesigner: Use error notifier for prototype and extension type name resolving If the prototype or extension has an unresolved id, it needs to be checked, if an exported name belonging to the prototype or extension was updated. In that case the id has to be again resolved. Task-number: QDS-12761 Change-Id: I7a733662cf37e13e8c2db53dec5a4f3e0a9b6ecf Reviewed-by: Tim Jenssen --- src/libs/sqlite/sqlitebasestatement.h | 9 +- src/libs/sqlite/sqliteids.h | 33 +- .../projectstorage/projectstorage.cpp | 355 +++++++++++-- .../projectstorage/projectstorage.h | 75 +-- .../mocks/projectstorageerrornotifiermock.h | 1 + .../tests/printers/gtest-creator-printing.cpp | 5 +- .../projectstorage/projectstorage-test.cpp | 493 ++++++++++++++++-- .../unittests/sqlite/sqlitestatement-test.cpp | 15 + 8 files changed, 846 insertions(+), 140 deletions(-) diff --git a/src/libs/sqlite/sqlitebasestatement.h b/src/libs/sqlite/sqlitebasestatement.h index 3710021ff53..9a4bb0a2a2f 100644 --- a/src/libs/sqlite/sqlitebasestatement.h +++ b/src/libs/sqlite/sqlitebasestatement.h @@ -36,13 +36,6 @@ class DatabaseBackend; enum class Type : char { Invalid, Integer, Float, Text, Blob, Null }; -template -constexpr static std::underlying_type_t to_underlying(Enumeration enumeration) noexcept -{ - static_assert(std::is_enum_v, "to_underlying expect an enumeration"); - return static_cast>(enumeration); -} - class SQLITE_EXPORT BaseStatement { public: @@ -87,7 +80,7 @@ public: template> void bind(int index, Type id) { - if (id) + if (!id.isNull()) bind(index, id.internalId()); else bindNull(index); diff --git a/src/libs/sqlite/sqliteids.h b/src/libs/sqlite/sqliteids.h index 1ffd546d9f2..460cd91415a 100644 --- a/src/libs/sqlite/sqliteids.h +++ b/src/libs/sqlite/sqliteids.h @@ -11,6 +11,13 @@ namespace Sqlite { +template +static constexpr std::underlying_type_t to_underlying(Enumeration enumeration) noexcept +{ + static_assert(std::is_enum_v, "to_underlying expect an enumeration"); + return static_cast>(enumeration); +} + template class BasicId { @@ -27,7 +34,15 @@ public: return id; } - constexpr friend bool compareInvalidAreTrue(BasicId first, BasicId second) + template + static constexpr BasicId createSpecialState(Enumeration specialState) + { + BasicId id; + id.id = ::Sqlite::to_underlying(specialState); + return id; + } + + friend constexpr bool compareInvalidAreTrue(BasicId first, BasicId second) { return first.id == second.id; } @@ -57,6 +72,14 @@ public: constexpr bool isValid() const { return id > 0; } + constexpr bool isNull() const { return id == 0; } + + template + constexpr bool hasSpecialState(Enumeration specialState) const + { + return id == ::Sqlite::to_underlying(specialState); + } + explicit operator bool() const { return isValid(); } explicit operator std::size_t() const { return static_cast(id); } @@ -68,13 +91,13 @@ public: template friend void convertToString(String &string, BasicId id) { - if (id.isValid()) - NanotraceHR::convertToString(string, id.internalId()); + if (id.isNull()) + NanotraceHR::convertToString(string, "invalid null"); else - NanotraceHR::convertToString(string, "invalid"); + NanotraceHR::convertToString(string, id.internalId()); } -private: +protected: InternalIntegerType id = 0; }; diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.cpp b/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.cpp index 8855c3251ca..2f56d569087 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.cpp +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.cpp @@ -7,6 +7,25 @@ namespace QmlDesigner { +enum class SpecialIdState { Unresolved = -1 }; + +constexpr TypeId unresolvedTypeId = TypeId::createSpecialState(SpecialIdState::Unresolved); + +class UnresolvedTypeId : public TypeId +{ +public: + constexpr UnresolvedTypeId() + : TypeId{TypeId::createSpecialState(SpecialIdState::Unresolved)} + {} + + static constexpr UnresolvedTypeId create(DatabaseType idNumber) + { + UnresolvedTypeId id; + id.id = idNumber; + return id; + } +}; + struct ProjectStorage::Statements { Statements(Sqlite::Database &database) @@ -17,9 +36,13 @@ struct ProjectStorage::Statements Sqlite::ReadWriteStatement<1, 2> insertTypeStatement{ "INSERT OR IGNORE INTO types(sourceId, name) VALUES(?1, ?2) RETURNING typeId", database}; Sqlite::WriteStatement<5> updatePrototypeAndExtensionStatement{ - "UPDATE types SET prototypeId=?2, prototypeNameId=?3, extensionId=?4, extensionNameId=?5 " - "WHERE typeId=?1 AND (prototypeId IS NOT ?2 OR extensionId IS NOT ?3 AND prototypeId " - "IS NOT ?4 OR extensionNameId IS NOT ?5)", + "UPDATE types " + "SET prototypeId=?2, prototypeNameId=?3, extensionId=?4, extensionNameId=?5 " + "WHERE typeId=?1 AND ( " + " prototypeId IS NOT ?2 " + " OR extensionId IS NOT ?3 " + " OR prototypeId IS NOT ?4 " + " OR extensionNameId IS NOT ?5)", database}; mutable Sqlite::ReadStatement<1, 1> selectTypeIdByExportedNameStatement{ "SELECT typeId FROM exportedTypeNames WHERE name=?1", database}; @@ -357,13 +380,51 @@ struct ProjectStorage::Statements "SELECT name FROM propertyDeclarations WHERE propertyDeclarationId=?", database}; Sqlite::WriteStatement<2> updatePropertyDeclarationTypeStatement{ "UPDATE propertyDeclarations SET propertyTypeId=?2 WHERE propertyDeclarationId=?1", database}; - Sqlite::ReadWriteStatement<2, 1> updatePrototypeIdToNullStatement{ - "UPDATE types SET prototypeId=NULL WHERE prototypeId=?1 RETURNING " - "typeId, prototypeNameId", + Sqlite::ReadWriteStatement<2, 2> updatePrototypeIdToTypeIdStatement{ + "UPDATE types " + "SET prototypeId=?2 " + "WHERE prototypeId=?1 " + "RETURNING typeId, prototypeNameId", database}; - Sqlite::ReadWriteStatement<2, 1> updateExtensionIdToNullStatement{ - "UPDATE types SET extensionId=NULL WHERE extensionId=?1 RETURNING " - "typeId, extensionNameId", + Sqlite::ReadWriteStatement<2, 2> updateExtensionIdToTypeIdStatement{ + "UPDATE types " + "SET extensionId=?2 " + "WHERE extensionId=?1 " + "RETURNING typeId, extensionNameId", + database}; + Sqlite::ReadStatement<2, 2> selectTypeIdAndPrototypeNameIdForPrototypeIdAndTypeNameStatement{ + "SELECT typeId, prototypeNameId " + "FROM types " + "WHERE prototypeNameId IN ( " + " SELECT importedTypeNameId " + " FROM " + " importedTypeNames WHERE name=?1) " + " AND prototypeId=?2", + database}; + Sqlite::ReadStatement<2, 2> selectTypeIdAndPrototypeNameIdForPrototypeIdAndSourceIdStatement{ + "SELECT typeId , prototypeNameId " + "FROM types " + "WHERE prototypeId=?1 AND sourceId=?2", + database}; + Sqlite::ReadStatement<2, 2> selectTypeIdAndExtensionNameIdForExtensionIdAndSourceIdStatement{ + "SELECT typeId, extensionNameId " + "FROM types " + "WHERE extensionId=?1 AND sourceId=?2", + database}; + Sqlite::ReadWriteStatement<3, 3> updatePrototypeIdAndExtensionIdToTypeIdForSourceIdStatement{ + "UPDATE types " + "SET prototypeId=?2, extensionId=?3 " + "WHERE sourceId=?1 " + "RETURNING typeId, prototypeNameId, extensionNameId", + database}; + Sqlite::ReadStatement<2, 2> selectTypeIdForExtensionIdAndTypeNameStatement{ + "SELECT typeId , prototypeNameId " + "FROM types " + "WHERE extensionNameId IN ( " + " SELECT importedTypeNameId " + " FROM importedTypeNames " + " WHERE name=?1) " + " AND extensionId=?2", database}; Sqlite::WriteStatement<2> updateTypePrototypeStatement{ "UPDATE types SET prototypeId=?2 WHERE typeId=?1", database}; @@ -619,6 +680,8 @@ struct ProjectStorage::Statements "UPDATE types SET defaultPropertyId=NULL WHERE defaultPropertyId=?1", database}; mutable Sqlite::ReadStatement<3, 1> selectInfoTypeByTypeIdStatement{ "SELECT sourceId, traits, annotationTraits FROM types WHERE typeId=?", database}; + mutable Sqlite::ReadStatement<1, 1> selectSourceIdByTypeIdStatement{ + "SELECT sourceId FROM types WHERE typeId=?", database}; mutable Sqlite::ReadStatement<1, 1> selectPrototypeAnnotationTraitsByTypeIdStatement{ "SELECT annotationTraits " "FROM types " @@ -801,23 +864,23 @@ public: auto &sourceIdColumn = typesTable.addColumn("sourceId", Sqlite::StrictColumnType::Integer); auto &typesNameColumn = typesTable.addColumn("name", Sqlite::StrictColumnType::Text); typesTable.addColumn("traits", Sqlite::StrictColumnType::Integer); - auto &prototypeIdColumn = typesTable.addForeignKeyColumn("prototypeId", - typesTable, - Sqlite::ForeignKeyAction::NoAction, - Sqlite::ForeignKeyAction::Restrict); - typesTable.addColumn("prototypeNameId", Sqlite::StrictColumnType::Integer); - auto &extensionIdColumn = typesTable.addForeignKeyColumn("extensionId", - typesTable, - Sqlite::ForeignKeyAction::NoAction, - Sqlite::ForeignKeyAction::Restrict); - typesTable.addColumn("extensionNameId", Sqlite::StrictColumnType::Integer); + auto &prototypeIdColumn = typesTable.addColumn("prototypeId", + Sqlite::StrictColumnType::Integer); + auto &prototypeNameIdColumn = typesTable.addColumn("prototypeNameId", + Sqlite::StrictColumnType::Integer); + auto &extensionIdColumn = typesTable.addColumn("extensionId", + Sqlite::StrictColumnType::Integer); + auto &extensionNameIdColumn = typesTable.addColumn("extensionNameId", + Sqlite::StrictColumnType::Integer); auto &defaultPropertyIdColumn = typesTable.addColumn("defaultPropertyId", Sqlite::StrictColumnType::Integer); typesTable.addColumn("annotationTraits", Sqlite::StrictColumnType::Integer); typesTable.addUniqueIndex({sourceIdColumn, typesNameColumn}); typesTable.addIndex({defaultPropertyIdColumn}); - typesTable.addIndex({prototypeIdColumn}); - typesTable.addIndex({extensionIdColumn}); + typesTable.addIndex({prototypeIdColumn, sourceIdColumn}); + typesTable.addIndex({extensionIdColumn, sourceIdColumn}); + typesTable.addIndex({prototypeNameIdColumn}); + typesTable.addIndex({extensionNameIdColumn}); typesTable.initialize(database); @@ -1131,7 +1194,7 @@ ProjectStorage::ProjectStorage(Database &database, ProjectStorageErrorNotifierInterface &errorNotifier, bool isInitialized) : database{database} - , errorNotifier{errorNotifier} + , errorNotifier{&errorNotifier} , exclusiveTransaction{database} , initializer{std::make_unique(database, isInitialized)} , moduleCache{ModuleStorageAdapter{*this}} @@ -1175,7 +1238,9 @@ void ProjectStorage::synchronize(Storage::Synchronization::SynchronizationPackag package.moduleDependencies, package.updatedModuleDependencySourceIds, package.moduleExportedImports, - package.updatedModuleIds); + package.updatedModuleIds, + relinkablePrototypes, + relinkableExtensions); synchronizeTypes(package.types, updatedTypeIds, insertedAliasPropertyDeclarations, @@ -1223,7 +1288,24 @@ void ProjectStorage::synchronizeDocumentImports(Storage::Imports imports, Source keyValue("source id", sourceId)}; Sqlite::withImmediateTransaction(database, [&] { - synchronizeDocumentImports(imports, {sourceId}, Storage::Synchronization::ImportKind::Import); + AliasPropertyDeclarations relinkableAliasPropertyDeclarations; + PropertyDeclarations relinkablePropertyDeclarations; + Prototypes relinkablePrototypes; + Prototypes relinkableExtensions; + TypeIds deletedTypeIds; + + synchronizeDocumentImports(imports, + {sourceId}, + Storage::Synchronization::ImportKind::Import, + Relink::Yes, + relinkablePrototypes, + relinkableExtensions); + + relink(relinkableAliasPropertyDeclarations, + relinkablePropertyDeclarations, + relinkablePrototypes, + relinkableExtensions, + deletedTypeIds); }); } @@ -2642,19 +2724,29 @@ void ProjectStorage::synchronizeImports(Storage::Imports &imports, Storage::Imports &moduleDependencies, const SourceIds &updatedModuleDependencySourceIds, Storage::Synchronization::ModuleExportedImports &moduleExportedImports, - const ModuleIds &updatedModuleIds) + const ModuleIds &updatedModuleIds, + Prototypes &relinkablePrototypes, + Prototypes &relinkableExtensions) { NanotraceHR::Tracer tracer{"synchronize imports"_t, projectStorageCategory()}; synchromizeModuleExportedImports(moduleExportedImports, updatedModuleIds); NanotraceHR::Tracer importTracer{"synchronize qml document imports"_t, projectStorageCategory()}; - synchronizeDocumentImports(imports, updatedSourceIds, Storage::Synchronization::ImportKind::Import); + synchronizeDocumentImports(imports, + updatedSourceIds, + Storage::Synchronization::ImportKind::Import, + Relink::No, + relinkablePrototypes, + relinkableExtensions); importTracer.end(); NanotraceHR::Tracer moduleDependenciesTracer{"synchronize module depdencies"_t, projectStorageCategory()}; synchronizeDocumentImports(moduleDependencies, updatedModuleDependencySourceIds, - Storage::Synchronization::ImportKind::ModuleDependency); + Storage::Synchronization::ImportKind::ModuleDependency, + Relink::Yes, + relinkablePrototypes, + relinkableExtensions); moduleDependenciesTracer.end(); } @@ -2821,11 +2913,30 @@ void ProjectStorage::handlePrototypes(TypeId prototypeId, Prototypes &relinkable keyValue("type id", prototypeId), keyValue("relinkable prototypes", relinkablePrototypes)}; + auto callback = [&](TypeId typeId, ImportedTypeNameId prototypeNameId) { + if (prototypeNameId) + relinkablePrototypes.emplace_back(typeId, prototypeNameId); + }; + + s->updatePrototypeIdToTypeIdStatement.readCallback(callback, prototypeId, unresolvedTypeId); +} + +void ProjectStorage::handlePrototypesWithExportedTypeNameAndTypeId( + Utils::SmallStringView exportedTypeName, TypeId typeId, Prototypes &relinkablePrototypes) +{ + using NanotraceHR::keyValue; + NanotraceHR::Tracer tracer{"handle invalid prototypes"_t, + projectStorageCategory(), + keyValue("type id", exportedTypeName), + keyValue("relinkable prototypes", relinkablePrototypes)}; + auto callback = [&](TypeId typeId, ImportedTypeNameId prototypeNameId) { relinkablePrototypes.emplace_back(typeId, prototypeNameId); }; - s->updatePrototypeIdToNullStatement.readCallback(callback, prototypeId); + s->selectTypeIdAndPrototypeNameIdForPrototypeIdAndTypeNameStatement.readCallback(callback, + exportedTypeName, + typeId); } void ProjectStorage::handleExtensions(TypeId extensionId, Prototypes &relinkableExtensions) @@ -2836,11 +2947,28 @@ void ProjectStorage::handleExtensions(TypeId extensionId, Prototypes &relinkable keyValue("type id", extensionId), keyValue("relinkable extensions", relinkableExtensions)}; + auto callback = [&](TypeId typeId, ImportedTypeNameId extensionNameId) { + if (extensionNameId) + relinkableExtensions.emplace_back(typeId, extensionNameId); + }; + + s->updateExtensionIdToTypeIdStatement.readCallback(callback, extensionId, unresolvedTypeId); +} + +void ProjectStorage::handleExtensionsWithExportedTypeNameAndTypeId( + Utils::SmallStringView exportedTypeName, TypeId typeId, Prototypes &relinkableExtensions) +{ + using NanotraceHR::keyValue; + NanotraceHR::Tracer tracer{"handle invalid extensions"_t, + projectStorageCategory(), + keyValue("type id", exportedTypeName), + keyValue("relinkable extensions", relinkableExtensions)}; + auto callback = [&](TypeId typeId, ImportedTypeNameId extensionNameId) { relinkableExtensions.emplace_back(typeId, extensionNameId); }; - s->updateExtensionIdToNullStatement.readCallback(callback, extensionId); + s->selectTypeIdForExtensionIdAndTypeNameStatement.readCallback(callback, exportedTypeName, typeId); } void ProjectStorage::deleteType(TypeId typeId, @@ -2927,6 +3055,39 @@ void ProjectStorage::relinkPropertyDeclarations(PropertyDeclarations &relinkable TypeCompare{}); } +template +void ProjectStorage::relinkPrototypes(Prototypes &relinkablePrototypes, + const TypeIds &deletedTypeIds, + Callable updateStatement) +{ + using NanotraceHR::keyValue; + NanotraceHR::Tracer tracer{"relink prototypes"_t, + projectStorageCategory(), + keyValue("relinkable prototypes", relinkablePrototypes), + keyValue("deleted type ids", deletedTypeIds)}; + + std::sort(relinkablePrototypes.begin(), relinkablePrototypes.end()); + relinkablePrototypes.erase(std::unique(relinkablePrototypes.begin(), relinkablePrototypes.end()), + relinkablePrototypes.end()); + + Utils::set_greedy_difference( + relinkablePrototypes.cbegin(), + relinkablePrototypes.cend(), + deletedTypeIds.begin(), + deletedTypeIds.end(), + [&](const Prototype &prototype) { + TypeId prototypeId = fetchTypeId(prototype.prototypeNameId); + + if (!prototypeId) + errorNotifier->typeNameCannotBeResolved(fetchImportedTypeName(prototype.prototypeNameId), + fetchTypeSourceId(prototype.typeId)); + + updateStatement(prototype.typeId, prototypeId); + checkForPrototypeChainCycle(prototype.typeId); + }, + TypeCompare{}); +} + void ProjectStorage::deleteNotUpdatedTypes(const TypeIds &updatedTypeIds, const SourceIds &updatedSourceIds, const TypeIds &typeIdsToBeDeleted, @@ -3141,6 +3302,9 @@ void ProjectStorage::synchronizeExportedTypes(const TypeIds &updatedTypeIds, } catch (const Sqlite::ConstraintPreventsModification &) { throw QmlDesigner::ExportedTypeCannotBeInserted{type.name}; } + + handlePrototypesWithExportedTypeNameAndTypeId(type.name, unresolvedTypeId, relinkablePrototypes); + handleExtensionsWithExportedTypeNameAndTypeId(type.name, unresolvedTypeId, relinkableExtensions); }; auto update = [&](const Storage::Synchronization::ExportedTypeView &view, @@ -3176,6 +3340,7 @@ void ProjectStorage::synchronizeExportedTypes(const TypeIds &updatedTypeIds, relinkableAliasPropertyDeclarations); handlePrototypes(view.typeId, relinkablePrototypes); handleExtensions(view.typeId, relinkableExtensions); + s->deleteExportedTypeNameStatement.write(view.exportedTypeNameId); }; @@ -3491,11 +3656,90 @@ void ProjectStorage::resetRemovedAliasPropertyDeclarationsToNull( PropertyCompare{}); } +void ProjectStorage::handlePrototypesWithSourceIdAndPrototypeId(SourceId sourceId, + TypeId prototypeId, + Prototypes &relinkablePrototypes) +{ + using NanotraceHR::keyValue; + NanotraceHR::Tracer tracer{"handle prototypes with source id and prototype id"_t, + projectStorageCategory(), + keyValue("source id", sourceId), + keyValue("type id", prototypeId)}; + + auto callback = [&](TypeId typeId, ImportedTypeNameId prototypeNameId) { + if (prototypeNameId) + relinkablePrototypes.emplace_back(typeId, prototypeNameId); + }; + + s->selectTypeIdAndPrototypeNameIdForPrototypeIdAndSourceIdStatement.readCallback(callback, + prototypeId, + sourceId); +} + +void ProjectStorage::handlePrototypesAndExtensionsWithSourceId(SourceId sourceId, + TypeId prototypeId, + TypeId extensionId, + Prototypes &relinkablePrototypes, + Prototypes &relinkableExtensions) +{ + using NanotraceHR::keyValue; + NanotraceHR::Tracer tracer{"handle prototypes with source id"_t, + projectStorageCategory(), + keyValue("source id", sourceId), + keyValue("prototype id", prototypeId), + keyValue("extension id", extensionId)}; + + auto callback = + [&](TypeId typeId, ImportedTypeNameId prototypeNameId, ImportedTypeNameId extensionNameId) { + if (prototypeNameId) + relinkablePrototypes.emplace_back(typeId, prototypeNameId); + if (extensionNameId) + relinkableExtensions.emplace_back(typeId, extensionNameId); + }; + + s->updatePrototypeIdAndExtensionIdToTypeIdForSourceIdStatement.readCallback(callback, + sourceId, + prototypeId, + extensionId); +} + +void ProjectStorage::handleExtensionsWithSourceIdAndExtensionId(SourceId sourceId, + TypeId extensionId, + Prototypes &relinkableExtensions) +{ + using NanotraceHR::keyValue; + NanotraceHR::Tracer tracer{"handle prototypes with source id and prototype id"_t, + projectStorageCategory(), + keyValue("source id", sourceId), + keyValue("type id", extensionId)}; + + auto callback = [&](TypeId typeId, ImportedTypeNameId extensionNameId) { + if (extensionNameId) + relinkableExtensions.emplace_back(typeId, extensionNameId); + }; + + s->selectTypeIdAndExtensionNameIdForExtensionIdAndSourceIdStatement.readCallback(callback, + extensionId, + sourceId); +} + ImportId ProjectStorage::insertDocumentImport(const Storage::Import &import, Storage::Synchronization::ImportKind importKind, ModuleId sourceModuleId, - ImportId parentImportId) + ImportId parentImportId, + Relink relink, + Prototypes &relinkablePrototypes, + Prototypes &relinkableExtensions) { + if (relink == Relink::Yes) { + handlePrototypesWithSourceIdAndPrototypeId(import.sourceId, + unresolvedTypeId, + relinkablePrototypes); + handleExtensionsWithSourceIdAndExtensionId(import.sourceId, + unresolvedTypeId, + relinkableExtensions); + } + if (import.version.minor) { return s->insertDocumentImportWithVersionStatement.value(import.sourceId, import.moduleId, @@ -3522,7 +3766,10 @@ ImportId ProjectStorage::insertDocumentImport(const Storage::Import &import, void ProjectStorage::synchronizeDocumentImports(Storage::Imports &imports, const SourceIds &updatedSourceIds, - Storage::Synchronization::ImportKind importKind) + Storage::Synchronization::ImportKind importKind, + Relink relink, + Prototypes &relinkablePrototypes, + Prototypes &relinkableExtensions) { std::sort(imports.begin(), imports.end(), [](auto &&first, auto &&second) { return std::tie(first.sourceId, first.moduleId, first.version) @@ -3559,7 +3806,13 @@ void ProjectStorage::synchronizeDocumentImports(Storage::Imports &imports, keyValue("source id", import.sourceId), keyValue("module id", import.moduleId)}; - auto importId = insertDocumentImport(import, importKind, import.moduleId, ImportId{}); + auto importId = insertDocumentImport(import, + importKind, + import.moduleId, + ImportId{}, + relink, + relinkablePrototypes, + relinkableExtensions); auto callback = [&](ModuleId exportedModuleId, int majorVersion, int minorVersion) { Storage::Import additionImport{exportedModuleId, Storage::Version{majorVersion, minorVersion}, @@ -3579,7 +3832,10 @@ void ProjectStorage::synchronizeDocumentImports(Storage::Imports &imports, auto indirectImportId = insertDocumentImport(additionImport, exportedImportKind, import.moduleId, - importId); + importId, + relink, + relinkablePrototypes, + relinkableExtensions); tracer.end(keyValue("import id", indirectImportId)); }; @@ -3606,6 +3862,13 @@ void ProjectStorage::synchronizeDocumentImports(Storage::Imports &imports, s->deleteDocumentImportStatement.write(view.importId); s->deleteDocumentImportsWithParentImportIdStatement.write(view.sourceId, view.importId); + if (relink == Relink::Yes) { + handlePrototypesAndExtensionsWithSourceId(view.sourceId, + unresolvedTypeId, + unresolvedTypeId, + relinkablePrototypes, + relinkableExtensions); + } }; Sqlite::insertUpdateDelete(range, imports, compareKey, insert, update, remove); @@ -4156,25 +4419,29 @@ void ProjectStorage::checkForAliasChainCycle(PropertyDeclarationId propertyDecla } std::pair ProjectStorage::fetchImportedTypeNameIdAndTypeId( - const Storage::Synchronization::ImportedTypeName &typeName, SourceId sourceId) + const Storage::Synchronization::ImportedTypeName &importedTypeName, SourceId sourceId) { using NanotraceHR::keyValue; NanotraceHR::Tracer tracer{"fetch imported type name id and type id"_t, projectStorageCategory(), - keyValue("imported type name", typeName), + keyValue("imported type name", importedTypeName), keyValue("source id", sourceId)}; TypeId typeId; ImportedTypeNameId typeNameId; - if (!std::visit([](auto &&typeName_) -> bool { return typeName_.name.isEmpty(); }, typeName)) { - typeNameId = fetchImportedTypeNameId(typeName, sourceId); + auto typeName = std::visit([](auto &&importedTypeName) { return importedTypeName.name; }, + importedTypeName); + if (!typeName.empty()) { + typeNameId = fetchImportedTypeNameId(importedTypeName, sourceId); typeId = fetchTypeId(typeNameId); tracer.end(keyValue("type id", typeId), keyValue("type name id", typeNameId)); - if (!typeId) - throw TypeNameDoesNotExists{fetchImportedTypeName(typeNameId), sourceId}; + if (!typeId) { + errorNotifier->typeNameCannotBeResolved(typeName, sourceId); + return {unresolvedTypeId, typeNameId}; + } } return {typeId, typeNameId}; @@ -4323,6 +4590,11 @@ Utils::SmallString ProjectStorage::fetchImportedTypeName(ImportedTypeNameId type return s->selectNameFromImportedTypeNamesStatement.value(typeNameId); } +SourceId ProjectStorage::fetchTypeSourceId(TypeId typeId) const +{ + return s->selectSourceIdByTypeIdStatement.value(typeId); +} + TypeId ProjectStorage::fetchTypeId(ImportedTypeNameId typeNameId, Storage::Synchronization::TypeNameKind kind) const { @@ -4334,9 +4606,10 @@ TypeId ProjectStorage::fetchTypeId(ImportedTypeNameId typeNameId, TypeId typeId; if (kind == Storage::Synchronization::TypeNameKind::Exported) { - typeId = s->selectTypeIdForImportedTypeNameNamesStatement.value(typeNameId); + typeId = s->selectTypeIdForImportedTypeNameNamesStatement.value(typeNameId); } else { - typeId = s->selectTypeIdForQualifiedImportedTypeNameNamesStatement.value(typeNameId); + typeId = s->selectTypeIdForQualifiedImportedTypeNameNamesStatement.value( + typeNameId); } tracer.end(keyValue("type id", typeId)); diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h index a5773448621..54d91015968 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h @@ -39,6 +39,8 @@ class ProjectStorage final : public ProjectStorageInterface using Database = Sqlite::Database; friend Storage::Info::CommonTypeCache; + enum class Relink { No, Yes }; + public: ProjectStorage(Database &database, ProjectStorageErrorNotifierInterface &errorNotifier, @@ -49,6 +51,11 @@ public: void synchronizeDocumentImports(Storage::Imports imports, SourceId sourceId) override; + void setErrorNotifier(ProjectStorageErrorNotifierInterface &errorNotifier) + { + this->errorNotifier = &errorNotifier; + } + void addObserver(ProjectStorageObserver *observer) override; void removeObserver(ProjectStorageObserver *observer) override; @@ -443,6 +450,11 @@ private: return first.typeId < second.typeId; } + friend bool operator==(Prototype first, Prototype second) + { + return first.typeId == second.typeId; + } + template friend void convertToString(String &string, const Prototype &prototype) { @@ -575,7 +587,9 @@ private: Storage::Imports &moduleDependencies, const SourceIds &updatedModuleDependencySourceIds, Storage::Synchronization::ModuleExportedImports &moduleExportedImports, - const ModuleIds &updatedModuleIds); + const ModuleIds &updatedModuleIds, + Prototypes &relinkablePrototypes, + Prototypes &relinkableExtensions); void synchromizeModuleExportedImports( Storage::Synchronization::ModuleExportedImports &moduleExportedImports, @@ -593,9 +607,14 @@ private: PropertyDeclarations &relinkablePropertyDeclarations); void handlePrototypes(TypeId prototypeId, Prototypes &relinkablePrototypes); + void handlePrototypesWithExportedTypeNameAndTypeId(Utils::SmallStringView exportedTypeName, + TypeId typeId, + Prototypes &relinkablePrototypes); void handleExtensions(TypeId extensionId, Prototypes &relinkableExtensions); - + void handleExtensionsWithExportedTypeNameAndTypeId(Utils::SmallStringView exportedTypeName, + TypeId typeId, + Prototypes &relinkableExtensions); void deleteType(TypeId typeId, AliasPropertyDeclarations &relinkableAliasPropertyDeclarations, PropertyDeclarations &relinkablePropertyDeclarations, @@ -611,32 +630,7 @@ private: template void relinkPrototypes(Prototypes &relinkablePrototypes, const TypeIds &deletedTypeIds, - Callable updateStatement) - { - using NanotraceHR::keyValue; - NanotraceHR::Tracer tracer{"relink prototypes"_t, - projectStorageCategory(), - keyValue("relinkable prototypes", relinkablePrototypes), - keyValue("deleted type ids", deletedTypeIds)}; - - std::sort(relinkablePrototypes.begin(), relinkablePrototypes.end()); - - Utils::set_greedy_difference( - relinkablePrototypes.cbegin(), - relinkablePrototypes.cend(), - deletedTypeIds.begin(), - deletedTypeIds.end(), - [&](const Prototype &prototype) { - TypeId prototypeId = fetchTypeId(prototype.prototypeNameId); - - if (!prototypeId) - throw TypeNameDoesNotExists{fetchImportedTypeName(prototype.prototypeNameId)}; - - updateStatement(prototype.typeId, prototypeId); - checkForPrototypeChainCycle(prototype.typeId); - }, - TypeCompare{}); - } + Callable updateStatement); void deleteNotUpdatedTypes(const TypeIds &updatedTypeIds, const SourceIds &updatedSourceIds, @@ -751,14 +745,32 @@ private: Storage::Synchronization::Types &types, AliasPropertyDeclarations &relinkableAliasPropertyDeclarations); + void handlePrototypesWithSourceIdAndPrototypeId(SourceId sourceId, + TypeId prototypeId, + Prototypes &relinkablePrototypes); + void handlePrototypesAndExtensionsWithSourceId(SourceId sourceId, + TypeId prototypeId, + TypeId extensionId, + Prototypes &relinkablePrototypes, + Prototypes &relinkableExtensions); + void handleExtensionsWithSourceIdAndExtensionId(SourceId sourceId, + TypeId extensionId, + Prototypes &relinkableExtensions); + ImportId insertDocumentImport(const Storage::Import &import, Storage::Synchronization::ImportKind importKind, ModuleId sourceModuleId, - ImportId parentImportId); + ImportId parentImportId, + Relink forceRelink, + Prototypes &relinkablePrototypes, + Prototypes &relinkableExtensions); void synchronizeDocumentImports(Storage::Imports &imports, const SourceIds &updatedSourceIds, - Storage::Synchronization::ImportKind importKind); + Storage::Synchronization::ImportKind importKind, + Relink forceRelink, + Prototypes &relinkablePrototypes, + Prototypes &relinkableExtensions); static Utils::PathString createJson(const Storage::Synchronization::ParameterDeclarations ¶meters); @@ -904,6 +916,7 @@ private: TypeId fetchTypeId(ImportedTypeNameId typeNameId) const; Utils::SmallString fetchImportedTypeName(ImportedTypeNameId typeNameId) const; + SourceId fetchTypeSourceId(TypeId typeId) const; TypeId fetchTypeId(ImportedTypeNameId typeNameId, Storage::Synchronization::TypeNameKind kind) const; @@ -970,7 +983,7 @@ private: public: Database &database; - ProjectStorageErrorNotifierInterface &errorNotifier; + ProjectStorageErrorNotifierInterface *errorNotifier = nullptr; // cannot be null Sqlite::ExclusiveNonThrowingDestructorTransaction exclusiveTransaction; std::unique_ptr initializer; mutable ModuleCache moduleCache{ModuleStorageAdapter{*this}}; diff --git a/tests/unit/tests/mocks/projectstorageerrornotifiermock.h b/tests/unit/tests/mocks/projectstorageerrornotifiermock.h index 28443300cc1..730c70a66ab 100644 --- a/tests/unit/tests/mocks/projectstorageerrornotifiermock.h +++ b/tests/unit/tests/mocks/projectstorageerrornotifiermock.h @@ -9,6 +9,7 @@ class ProjectStorageErrorNotifierMock : public QmlDesigner::ProjectStorageErrorNotifierInterface { +public: MOCK_METHOD(void, typeNameCannotBeResolved, (Utils::SmallStringView typeName, QmlDesigner::SourceId souceId), diff --git a/tests/unit/tests/printers/gtest-creator-printing.cpp b/tests/unit/tests/printers/gtest-creator-printing.cpp index 0cbef7b1b7f..8ca65f4526e 100644 --- a/tests/unit/tests/printers/gtest-creator-printing.cpp +++ b/tests/unit/tests/printers/gtest-creator-printing.cpp @@ -833,8 +833,9 @@ std::ostream &operator<<(std::ostream &out, const Type &type) { using std::operator<<; using Utils::operator<<; - return out << "( typename: \"" << type.typeName << "\", prototype: " << type.prototype << ", " - << type.prototypeId << ", " << type.traits << ", source: " << type.sourceId + return out << "( typename: \"" << type.typeName << "\", prototype: {\"" << type.prototype + << "\", " << type.prototypeId << "}, " << "\", extension: {\"" << type.extension + << "\", " << type.extensionId << "}, " << type.traits << ", source: " << type.sourceId << ", exports: " << type.exportedTypes << ", properties: " << type.propertyDeclarations << ", functions: " << type.functionDeclarations << ", signals: " << type.signalDeclarations << ", changeLevel: " << type.changeLevel diff --git a/tests/unit/tests/unittests/projectstorage/projectstorage-test.cpp b/tests/unit/tests/unittests/projectstorage/projectstorage-test.cpp index c59971fec1c..fc806d73a95 100644 --- a/tests/unit/tests/unittests/projectstorage/projectstorage-test.cpp +++ b/tests/unit/tests/unittests/projectstorage/projectstorage-test.cpp @@ -205,6 +205,23 @@ MATCHER_P4(IsInfoPropertyDeclaration, && propertyDeclaration.traits == traits; } +auto IsUnresolvedTypeId() +{ + return Property(&QmlDesigner::TypeId::internalId, -1); +} + +template +auto IsPrototypeId(const Matcher &matcher) +{ + return Field(&Storage::Synchronization::Type::prototypeId, matcher); +} + +template +auto IsExtensionId(const Matcher &matcher) +{ + return Field(&Storage::Synchronization::Type::extensionId, matcher); +} + class HasNameMatcher { public: @@ -286,6 +303,8 @@ protected: static void TearDownTestSuite() { staticData.reset(); } + ProjectStorage() { storage.setErrorNotifier(errorNotifierMock); } + ~ProjectStorage() { storage.resetForTestsOnly(); } template @@ -312,6 +331,32 @@ protected: storage.fetchSourceId(sourceContextId3, "bar"); } + auto createVerySimpleSynchronizationPackage() + { + SynchronizationPackage package; + + package.types.push_back(Storage::Synchronization::Type{ + "QQuickItem", + Storage::Synchronization::ImportedType{}, + Storage::Synchronization::ImportedType{}, + TypeTraitsKind::Reference, + sourceId1, + {Storage::Synchronization::ExportedType{qtQuickModuleId, "Item"}, + Storage::Synchronization::ExportedType{qtQuickNativeModuleId, "QQuickItem"}}}); + package.types.push_back(Storage::Synchronization::Type{ + "QObject", + Storage::Synchronization::ImportedType{}, + Storage::Synchronization::ImportedType{}, + TypeTraitsKind::Reference, + sourceId2, + {Storage::Synchronization::ExportedType{qmlModuleId, "Object"}, + Storage::Synchronization::ExportedType{qmlNativeModuleId, "QObject"}}}); + + package.updatedSourceIds = {sourceId1, sourceId2}; + + return package; + } + auto createSimpleSynchronizationPackage() { SynchronizationPackage package; @@ -1142,7 +1187,7 @@ protected: inline static std::unique_ptr staticData; Sqlite::Database &database = staticData->database; QmlDesigner::ProjectStorage &storage = staticData->storage; - ProjectStorageErrorNotifierMock &errorNotifierMock = staticData->errorNotifierMock; + NiceMock errorNotifierMock; QmlDesigner::SourcePathCache sourcePathCache{storage}; QmlDesigner::SourcePathView path1{"/path1/to"}; QmlDesigner::SourcePathView path2{"/path2/to"}; @@ -1486,22 +1531,193 @@ TEST_F(ProjectStorage, synchronize_types_adds_new_types_with_exported_extension_ "QQuickItem")))))); } -TEST_F(ProjectStorage, synchronize_types_adds_new_types_throws_with_wrong_prototype_name) +TEST_F(ProjectStorage, + synchronize_types_adds_unknown_prototype_which_notifies_about_unresolved_type_name) { auto package{createSimpleSynchronizationPackage()}; package.types[0].prototype = Storage::Synchronization::ImportedType{"Objec"}; - ASSERT_THROW(storage.synchronize(std::move(package)), QmlDesigner::TypeNameDoesNotExists); + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("Objec"), sourceId1)); + + storage.synchronize(std::move(package)); } -TEST_F(ProjectStorage, synchronize_types_adds_new_types_throws_with_wrong_extension_name) +TEST_F(ProjectStorage, synchronize_types_adds_unknown_prototype_as_unresolved_type_id) +{ + auto package{createSimpleSynchronizationPackage()}; + package.types[0].prototype = Storage::Synchronization::ImportedType{"Objec"}; + + storage.synchronize(std::move(package)); + + ASSERT_THAT(storage.fetchTypeByTypeId(fetchTypeId(sourceId1, "QQuickItem")), + IsPrototypeId(IsUnresolvedTypeId())); +} + +TEST_F(ProjectStorage, synchronize_types_updates_unresolved_prototype_after_exported_type_name_is_added) +{ + auto package{createSimpleSynchronizationPackage()}; + package.types[0].prototype = Storage::Synchronization::ImportedType{"Objec"}; + storage.synchronize(package); + package.types[1].exportedTypes.emplace_back(qmlNativeModuleId, "Objec"); + + storage.synchronize(std::move(package)); + + ASSERT_THAT(storage.fetchTypeByTypeId(fetchTypeId(sourceId1, "QQuickItem")), + IsPrototypeId(fetchTypeId(sourceId2, "QObject"))); +} + +TEST_F(ProjectStorage, + synchronize_types_updates_prototype_to_unresolved_after_exported_type_name_is_removed) +{ + auto package{createSimpleSynchronizationPackage()}; + package.types[0].prototype = Storage::Synchronization::ImportedType{"Objec"}; + package.types[1].exportedTypes.emplace_back(qmlNativeModuleId, "Objec"); + storage.synchronize(package); + package.types[1].exportedTypes.pop_back(); + + storage.synchronize(std::move(package)); + + ASSERT_THAT(storage.fetchTypeByTypeId(fetchTypeId(sourceId1, "QQuickItem")), + IsPrototypeId(IsUnresolvedTypeId())); +} + +TEST_F(ProjectStorage, + synchronize_types_updates_unresolved_prototype_indirectly_after_exported_type_name_is_added) +{ + auto package{createSimpleSynchronizationPackage()}; + package.types[0].prototype = Storage::Synchronization::ImportedType{"Objec"}; + storage.synchronize(package); + package.types[1].exportedTypes.emplace_back(qmlNativeModuleId, "Objec"); + package.types.erase(package.types.begin()); + package.updatedSourceIds = {sourceId2}; + + storage.synchronize(std::move(package)); + + ASSERT_THAT(storage.fetchTypeByTypeId(fetchTypeId(sourceId1, "QQuickItem")), + IsPrototypeId(fetchTypeId(sourceId2, "QObject"))); +} + +TEST_F(ProjectStorage, + synchronize_types_updates_prototype_indirectly_to_unresolved_after_exported_type_name_is_removed) +{ + auto package{createSimpleSynchronizationPackage()}; + package.types[0].prototype = Storage::Synchronization::ImportedType{"Objec"}; + package.types[1].exportedTypes.emplace_back(qmlNativeModuleId, "Objec"); + storage.synchronize(package); + package.types[1].exportedTypes.pop_back(); + package.types.erase(package.types.begin()); + package.updatedSourceIds = {sourceId2}; + + storage.synchronize(std::move(package)); + + ASSERT_THAT(storage.fetchTypeByTypeId(fetchTypeId(sourceId1, "QQuickItem")), + IsPrototypeId(IsUnresolvedTypeId())); +} + +TEST_F(ProjectStorage, + synchronize_types_updates_prototype_indirectly_to_unresolved_after_exported_type_name_is_removed_notifies_type_name_cannot_be_resolved) +{ + auto package{createSimpleSynchronizationPackage()}; + package.types[0].prototype = Storage::Synchronization::ImportedType{"Objec"}; + package.types[1].exportedTypes.emplace_back(qmlNativeModuleId, "Objec"); + storage.synchronize(package); + package.types[1].exportedTypes.pop_back(); + package.types.erase(package.types.begin()); + package.updatedSourceIds = {sourceId2}; + + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("Objec"), sourceId1)); + + storage.synchronize(std::move(package)); +} + +TEST_F(ProjectStorage, synchronize_types_updates_unresolved_extension_after_exported_type_name_is_added) +{ + auto package{createSimpleSynchronizationPackage()}; + package.types[0].extension = Storage::Synchronization::ImportedType{"Objec"}; + storage.synchronize(package); + package.types[1].exportedTypes.emplace_back(qmlNativeModuleId, "Objec"); + + storage.synchronize(std::move(package)); + + ASSERT_THAT(storage.fetchTypeByTypeId(fetchTypeId(sourceId1, "QQuickItem")), + IsExtensionId(fetchTypeId(sourceId2, "QObject"))); +} + +TEST_F(ProjectStorage, + synchronize_types_updates_extension_to_unresolved_after_exported_type_name_is_removed) +{ + auto package{createSimpleSynchronizationPackage()}; + package.types[0].extension = Storage::Synchronization::ImportedType{"Objec"}; + package.types[1].exportedTypes.emplace_back(qmlNativeModuleId, "Objec"); + storage.synchronize(package); + package.types[1].exportedTypes.pop_back(); + + storage.synchronize(std::move(package)); + + ASSERT_THAT(storage.fetchTypeByTypeId(fetchTypeId(sourceId1, "QQuickItem")), + IsExtensionId(IsUnresolvedTypeId())); +} + +TEST_F(ProjectStorage, + synchronize_types_updates_unresolved_extension_indirectly_after_exported_type_name_is_added) +{ + auto package{createSimpleSynchronizationPackage()}; + package.types[0].extension = Storage::Synchronization::ImportedType{"Objec"}; + storage.synchronize(package); + package.types[1].exportedTypes.emplace_back(qmlNativeModuleId, "Objec"); + package.types.erase(package.types.begin()); + package.updatedSourceIds = {sourceId2}; + + storage.synchronize(std::move(package)); + + ASSERT_THAT(storage.fetchTypeByTypeId(fetchTypeId(sourceId1, "QQuickItem")), + IsExtensionId(fetchTypeId(sourceId2, "QObject"))); +} + +TEST_F(ProjectStorage, + synchronize_types_updates_invalid_extension_indirectly_after_exported_type_name_is_removed) +{ + auto package{createSimpleSynchronizationPackage()}; + package.types[0].extension = Storage::Synchronization::ImportedType{"Objec"}; + package.types[1].exportedTypes.emplace_back(qmlNativeModuleId, "Objec"); + storage.synchronize(package); + package.types[1].exportedTypes.pop_back(); + package.types.erase(package.types.begin()); + package.updatedSourceIds = {sourceId2}; + + storage.synchronize(std::move(package)); + + ASSERT_THAT(storage.fetchTypeByTypeId(fetchTypeId(sourceId1, "QQuickItem")), + IsExtensionId(IsUnresolvedTypeId())); +} + +TEST_F(ProjectStorage, + synchronize_types_updates_extension_indirectly_to_unresolved_after_exported_type_name_is_removed_notifies_type_name_cannot_be_resolved) +{ + auto package{createSimpleSynchronizationPackage()}; + package.types[0].extension = Storage::Synchronization::ImportedType{"Objec"}; + package.types[1].exportedTypes.emplace_back(qmlNativeModuleId, "Objec"); + storage.synchronize(package); + package.types[1].exportedTypes.pop_back(); + package.types.erase(package.types.begin()); + package.updatedSourceIds = {sourceId2}; + + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("Objec"), sourceId1)); + + storage.synchronize(std::move(package)); +} + +TEST_F(ProjectStorage, synchronize_types_adds_extension_which_notifies_about_unresolved_type_name) { auto package{createSimpleSynchronizationPackage()}; package.types[0].extension = Storage::Synchronization::ImportedType{"Objec"}; - ASSERT_THROW(storage.synchronize(std::move(package)), QmlDesigner::TypeNameDoesNotExists); + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("Objec"), sourceId1)); + + storage.synchronize(std::move(package)); } + TEST_F(ProjectStorage, synchronize_types_adds_new_types_with_missing_module) { auto package{createSimpleSynchronizationPackage()}; @@ -1774,7 +1990,7 @@ TEST_F(ProjectStorage, synchronize_types_add_qualified_extension) IsExportedType(qtQuickNativeModuleId, "QQuickItem")))))); } -TEST_F(ProjectStorage, synchronize_types_throws_for_missing_prototype) +TEST_F(ProjectStorage, synchronize_types_notifies_cannot_resolve_for_missing_prototype) { auto package{createSimpleSynchronizationPackage()}; package.types = {Storage::Synchronization::Type{ @@ -1786,10 +2002,12 @@ TEST_F(ProjectStorage, synchronize_types_throws_for_missing_prototype) {Storage::Synchronization::ExportedType{qtQuickModuleId, "Item"}, Storage::Synchronization::ExportedType{qtQuickNativeModuleId, "QQuickItem"}}}}; - ASSERT_THROW(storage.synchronize(package), QmlDesigner::TypeNameDoesNotExists); + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("QObject"), sourceId1)); + + storage.synchronize(package); } -TEST_F(ProjectStorage, synchronize_types_throws_for_missing_extension) +TEST_F(ProjectStorage, synchronize_types_notifies_cannot_resolve_for_missing_extension) { auto package{createSimpleSynchronizationPackage()}; package.types = {Storage::Synchronization::Type{ @@ -1801,7 +2019,9 @@ TEST_F(ProjectStorage, synchronize_types_throws_for_missing_extension) {Storage::Synchronization::ExportedType{qtQuickModuleId, "Item"}, Storage::Synchronization::ExportedType{qtQuickNativeModuleId, "QQuickItem"}}}}; - ASSERT_THROW(storage.synchronize(package), QmlDesigner::TypeNameDoesNotExists); + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("QObject"), sourceId1)); + + storage.synchronize(package); } TEST_F(ProjectStorage, synchronize_types_throws_for_invalid_module) @@ -3670,7 +3890,8 @@ TEST_F(ProjectStorage, change_extension_type_module_id) TypeTraitsKind::Reference))); } -TEST_F(ProjectStorage, change_qualified_prototype_type_module_id_throws) +TEST_F(ProjectStorage, + change_qualified_prototype_type_module_id_notifies_that_type_name_cannot_be_resolved) { auto package{createSimpleSynchronizationPackage()}; package.types[0].prototype = Storage::Synchronization::QualifiedImportedType{ @@ -3678,12 +3899,12 @@ TEST_F(ProjectStorage, change_qualified_prototype_type_module_id_throws) storage.synchronize(package); package.types[1].exportedTypes[0].moduleId = qtQuickModuleId; - ASSERT_THROW(storage.synchronize( - SynchronizationPackage{importsSourceId2, {package.types[1]}, {sourceId2}}), - QmlDesigner::TypeNameDoesNotExists); + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("Object"), sourceId1)); + + storage.synchronize(SynchronizationPackage{importsSourceId2, {package.types[1]}, {sourceId2}}); } -TEST_F(ProjectStorage, change_qualified_extension_type_module_id_throws) +TEST_F(ProjectStorage, change_qualified_extension_type_module_id_notifies_cannot_resolve) { auto package{createSimpleSynchronizationPackage()}; std::swap(package.types.front().extension, package.types.front().prototype); @@ -3692,9 +3913,9 @@ TEST_F(ProjectStorage, change_qualified_extension_type_module_id_throws) storage.synchronize(package); package.types[1].exportedTypes[0].moduleId = qtQuickModuleId; - ASSERT_THROW(storage.synchronize( - SynchronizationPackage{importsSourceId2, {package.types[1]}, {sourceId2}}), - QmlDesigner::TypeNameDoesNotExists); + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("Object"), sourceId1)); + + storage.synchronize(SynchronizationPackage{importsSourceId2, {package.types[1]}, {sourceId2}}); } TEST_F(ProjectStorage, change_qualified_prototype_type_module_id) @@ -3798,9 +4019,9 @@ TEST_F(ProjectStorage, change_prototype_type_name_throws_for_wrong_native_protot package.types[1].exportedTypes[2].name = "QObject3"; package.types[1].typeName = "QObject3"; - ASSERT_THROW(storage.synchronize( - SynchronizationPackage{importsSourceId2, {package.types[1]}, {sourceId2}}), - QmlDesigner::TypeNameDoesNotExists); + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("QObject"), sourceId1)); + + storage.synchronize(SynchronizationPackage{importsSourceId2, {package.types[1]}, {sourceId2}}); } TEST_F(ProjectStorage, change_extension_type_name_throws_for_wrong_native_extension_type_name) @@ -3813,9 +4034,9 @@ TEST_F(ProjectStorage, change_extension_type_name_throws_for_wrong_native_extens package.types[1].exportedTypes[2].name = "QObject3"; package.types[1].typeName = "QObject3"; - ASSERT_THROW(storage.synchronize( - SynchronizationPackage{importsSourceId2, {package.types[1]}, {sourceId2}}), - QmlDesigner::TypeNameDoesNotExists); + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("QObject"), sourceId1)); + + storage.synchronize(SynchronizationPackage{importsSourceId2, {package.types[1]}, {sourceId2}}); } TEST_F(ProjectStorage, throw_for_prototype_chain_cycles) @@ -4117,23 +4338,29 @@ TEST_F(ProjectStorage, qualified_extension) TypeTraitsKind::Reference))); } -TEST_F(ProjectStorage, qualified_prototype_upper_down_the_module_chain_throws) +TEST_F(ProjectStorage, + qualified_prototype_upper_down_the_module_chain_notifies_type_name_cannot_be_resolved) { auto package{createSimpleSynchronizationPackage()}; package.types[0].prototype = Storage::Synchronization::QualifiedImportedType{ "Object", Storage::Import{qtQuickModuleId, Storage::Version{}, sourceId1}}; - ASSERT_THROW(storage.synchronize(package), QmlDesigner::TypeNameDoesNotExists); + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("Object"), sourceId1)); + + storage.synchronize(package); } -TEST_F(ProjectStorage, qualified_extension_upper_down_the_module_chain_throws) +TEST_F(ProjectStorage, + qualified_extension_upper_down_the_module_chain_notifies_type_name_cannot_be_resolved) { auto package{createSimpleSynchronizationPackage()}; std::swap(package.types.front().extension, package.types.front().prototype); package.types[0].extension = Storage::Synchronization::QualifiedImportedType{ "Object", Storage::Import{qtQuickModuleId, Storage::Version{}, sourceId1}}; - ASSERT_THROW(storage.synchronize(package), QmlDesigner::TypeNameDoesNotExists); + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("Object"), sourceId1)); + + storage.synchronize(package); } TEST_F(ProjectStorage, qualified_prototype_upper_in_the_module_chain) @@ -4189,7 +4416,7 @@ TEST_F(ProjectStorage, qualified_extension_upper_in_the_module_chain) TypeTraitsKind::Reference))); } -TEST_F(ProjectStorage, qualified_prototype_with_wrong_version_throws) +TEST_F(ProjectStorage, qualified_prototype_with_wrong_version_notifies_type_name_cannot_be_resolved) { auto package{createSimpleSynchronizationPackage()}; package.types[0].prototype = Storage::Synchronization::QualifiedImportedType{ @@ -4205,10 +4432,12 @@ TEST_F(ProjectStorage, qualified_prototype_with_wrong_version_throws) package.imports.emplace_back(qtQuickModuleId, Storage::Version{}, sourceId3); package.updatedSourceIds.push_back(sourceId3); - ASSERT_THROW(storage.synchronize(package), QmlDesigner::TypeNameDoesNotExists); + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("Object"), sourceId1)); + + storage.synchronize(package); } -TEST_F(ProjectStorage, qualified_extension_with_wrong_version_throws) +TEST_F(ProjectStorage, qualified_extension_with_wrong_version_notifies_type_name_cannot_be_resolved) { auto package{createSimpleSynchronizationPackage()}; std::swap(package.types.front().extension, package.types.front().prototype); @@ -4225,7 +4454,9 @@ TEST_F(ProjectStorage, qualified_extension_with_wrong_version_throws) package.imports.emplace_back(qtQuickModuleId, Storage::Version{}, sourceId3); package.updatedSourceIds.push_back(sourceId3); - ASSERT_THROW(storage.synchronize(package), QmlDesigner::TypeNameDoesNotExists); + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("Object"), sourceId1)); + + storage.synchronize(package); } TEST_F(ProjectStorage, qualified_prototype_with_version) @@ -4338,23 +4569,29 @@ TEST_F(ProjectStorage, qualified_extension_with_version_in_the_proto_type_chain) TypeTraitsKind::Reference))); } -TEST_F(ProjectStorage, qualified_prototype_with_version_down_the_proto_type_chain_throws) +TEST_F(ProjectStorage, + qualified_prototype_with_version_down_the_proto_type_chain_notifies_type_name_cannot_be_resolved) { auto package{createSimpleSynchronizationPackage()}; package.types[0].prototype = Storage::Synchronization::QualifiedImportedType{ "Object", Storage::Import{qtQuickModuleId, Storage::Version{2}, sourceId1}}; - ASSERT_THROW(storage.synchronize(package), QmlDesigner::TypeNameDoesNotExists); + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("Object"), sourceId1)); + + storage.synchronize(package); } -TEST_F(ProjectStorage, qualified_extension_with_version_down_the_proto_type_chain_throws) +TEST_F(ProjectStorage, + qualified_extension_with_version_down_the_proto_type_chain_notifies_type_name_cannot_be_resolved) { auto package{createSimpleSynchronizationPackage()}; std::swap(package.types.front().extension, package.types.front().prototype); package.types[0].extension = Storage::Synchronization::QualifiedImportedType{ "Object", Storage::Import{qtQuickModuleId, Storage::Version{2}, sourceId1}}; - ASSERT_THROW(storage.synchronize(package), QmlDesigner::TypeNameDoesNotExists); + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("Object"), sourceId1)); + + storage.synchronize(package); } TEST_F(ProjectStorage, qualified_property_declaration_type_name) @@ -4659,7 +4896,7 @@ TEST_F(ProjectStorage, fetch_by_major_version_and_minor_version_for_qualified_im } TEST_F(ProjectStorage, - fetch_by_major_version_and_minor_version_for_imported_type_if_minor_version_is_not_exported_throws) + fetch_by_major_version_and_minor_version_for_imported_type_if_minor_version_is_not_exported_notifies_type_name_cannot_be_resolved) { auto package{createSynchronizationPackageWithVersions()}; storage.synchronize(package); @@ -4673,12 +4910,13 @@ TEST_F(ProjectStorage, Storage::Version{}}}}; Storage::Import import{qmlModuleId, Storage::Version{1, 1}, sourceId2}; - ASSERT_THROW(storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}), - QmlDesigner::TypeNameDoesNotExists); + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("Object"), sourceId2)); + + storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}); } TEST_F(ProjectStorage, - fetch_by_major_version_and_minor_version_for_qualified_imported_type_if_minor_version_is_not_exported_throws) + fetch_by_major_version_and_minor_version_for_qualified_imported_type_if_minor_version_is_not_exported_notifies_type_name_cannot_be_resolved) { auto package{createSynchronizationPackageWithVersions()}; storage.synchronize(package); @@ -4691,11 +4929,12 @@ TEST_F(ProjectStorage, sourceId2, {Storage::Synchronization::ExportedType{qtQuickModuleId, "Item", Storage::Version{}}}}; - ASSERT_THROW(storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}), - QmlDesigner::TypeNameDoesNotExists); + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("Object"), sourceId2)); + + storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}); } -TEST_F(ProjectStorage, fetch_low_minor_version_for_imported_type_throws) +TEST_F(ProjectStorage, fetch_low_minor_version_for_imported_type_notifies_type_name_cannot_be_resolved) { auto package{createSynchronizationPackageWithVersions()}; storage.synchronize(package); @@ -4709,11 +4948,13 @@ TEST_F(ProjectStorage, fetch_low_minor_version_for_imported_type_throws) Storage::Version{}}}}; Storage::Import import{qmlModuleId, Storage::Version{1, 1}, sourceId2}; - ASSERT_THROW(storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}), - QmlDesigner::TypeNameDoesNotExists); + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("Obj"), sourceId2)); + + storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}); } -TEST_F(ProjectStorage, fetch_low_minor_version_for_qualified_imported_type_throws) +TEST_F(ProjectStorage, + fetch_low_minor_version_for_qualified_imported_type_notifies_type_name_cannot_be_resolved) { auto package{createSynchronizationPackageWithVersions()}; storage.synchronize(package); @@ -4726,8 +4967,9 @@ TEST_F(ProjectStorage, fetch_low_minor_version_for_qualified_imported_type_throw sourceId2, {Storage::Synchronization::ExportedType{qtQuickModuleId, "Item", Storage::Version{}}}}; - ASSERT_THROW(storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}), - QmlDesigner::TypeNameDoesNotExists); + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("Obj"), sourceId2)); + + storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}); } TEST_F(ProjectStorage, fetch_higher_minor_version_for_imported_type) @@ -4775,7 +5017,8 @@ TEST_F(ProjectStorage, fetch_higher_minor_version_for_qualified_imported_type) TypeTraitsKind::Reference))); } -TEST_F(ProjectStorage, fetch_different_major_version_for_imported_type_throws) +TEST_F(ProjectStorage, + fetch_different_major_version_for_imported_type_notifies_type_name_cannot_be_resolved) { auto package{createSynchronizationPackageWithVersions()}; storage.synchronize(package); @@ -4789,11 +5032,13 @@ TEST_F(ProjectStorage, fetch_different_major_version_for_imported_type_throws) Storage::Version{}}}}; Storage::Import import{qmlModuleId, Storage::Version{3, 1}, sourceId2}; - ASSERT_THROW(storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}), - QmlDesigner::TypeNameDoesNotExists); + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("Obj"), sourceId2)); + + storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}); } -TEST_F(ProjectStorage, fetch_different_major_version_for_qualified_imported_type_throws) +TEST_F(ProjectStorage, + fetch_different_major_version_for_qualified_imported_type_notifies_type_name_cannot_be_resolved) { auto package{createSynchronizationPackageWithVersions()}; storage.synchronize(package); @@ -4806,8 +5051,9 @@ TEST_F(ProjectStorage, fetch_different_major_version_for_qualified_imported_type sourceId2, {Storage::Synchronization::ExportedType{qtQuickModuleId, "Item", Storage::Version{}}}}; - ASSERT_THROW(storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}), - QmlDesigner::TypeNameDoesNotExists); + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("Obj"), sourceId2)); + + storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}); } TEST_F(ProjectStorage, fetch_other_type_by_different_version_for_imported_type) @@ -7248,6 +7494,46 @@ TEST_F(ProjectStorage, synchronize_document_imports_adds_import) ASSERT_TRUE(storage.importId(imports.back())); } +TEST_F(ProjectStorage, + synchronize_document_imports_removes_import_notifies_that_type_name_cannot_be_resolved) +{ + auto package{createVerySimpleSynchronizationPackage()}; + package.imports.emplace_back(qmlModuleId, Storage::Version{}, sourceId1); + package.types[0].prototype = Storage::Synchronization::ImportedType{"Object"}; + storage.synchronize(package); + + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("Object"), sourceId1)); + + storage.synchronizeDocumentImports({}, sourceId1); +} + +TEST_F(ProjectStorage, synchronize_document_imports_removes_import_which_makes_prototype_unresolved) +{ + auto package{createVerySimpleSynchronizationPackage()}; + package.imports.emplace_back(qmlModuleId, Storage::Version{}, sourceId1); + package.types[0].prototype = Storage::Synchronization::ImportedType{"Object"}; + storage.synchronize(package); + + storage.synchronizeDocumentImports({}, sourceId1); + + ASSERT_THAT(storage.fetchTypeByTypeId(fetchTypeId(sourceId1, "QQuickItem")), + IsPrototypeId(IsUnresolvedTypeId())); +} + +TEST_F(ProjectStorage, synchronize_document_imports_adds_import_which_makes_prototype_resolved) +{ + auto package{createVerySimpleSynchronizationPackage()}; + package.types[0].prototype = Storage::Synchronization::ImportedType{"Object"}; + storage.synchronize(package); + Storage::Imports imports; + imports.emplace_back(qmlModuleId, Storage::Version{}, sourceId1); + + storage.synchronizeDocumentImports(imports, sourceId1); + + ASSERT_THAT(storage.fetchTypeByTypeId(fetchTypeId(sourceId1, "QQuickItem")), + IsPrototypeId(fetchTypeId(sourceId2, "QObject"))); +} + TEST_F(ProjectStorage, get_exported_type_names) { auto package{createSimpleSynchronizationPackage()}; @@ -7996,4 +8282,105 @@ TEST_F(ProjectStorage, get_no_hair_ids_for_invalid_type_id) ASSERT_THAT(heirIds, IsEmpty()); } + +TEST_F(ProjectStorage, + removed_document_import_notifies_for_prototypes_that_type_name_cannot_be_resolved) +{ + auto package{createVerySimpleSynchronizationPackage()}; + package.moduleDependencies.emplace_back(qmlNativeModuleId, Storage::Version{}, sourceId1); + package.types[0].prototype = Storage::Synchronization::ImportedType{"QObject"}; + storage.synchronize(package); + package.moduleDependencies.clear(); + package.types.clear(); + package.updatedSourceIds.clear(); + package.updatedModuleDependencySourceIds = {sourceId1}; + + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("QObject"), sourceId1)); + + storage.synchronize(package); +} + +TEST_F(ProjectStorage, + removed_document_import_notifies_for_extensions_that_type_name_cannot_be_resolved) +{ + auto package{createVerySimpleSynchronizationPackage()}; + package.moduleDependencies.emplace_back(qmlNativeModuleId, Storage::Version{}, sourceId1); + package.types[0].extension = Storage::Synchronization::ImportedType{"QObject"}; + storage.synchronize(package); + package.moduleDependencies.clear(); + package.types.clear(); + package.updatedSourceIds.clear(); + package.updatedModuleDependencySourceIds = {sourceId1}; + + EXPECT_CALL(errorNotifierMock, typeNameCannotBeResolved(Eq("QObject"), sourceId1)); + + storage.synchronize(package); +} + +TEST_F(ProjectStorage, removed_document_import_changes_prototype_to_unresolved) +{ + auto package{createVerySimpleSynchronizationPackage()}; + package.moduleDependencies.emplace_back(qmlNativeModuleId, Storage::Version{}, sourceId1); + package.types[0].prototype = Storage::Synchronization::ImportedType{"QObject"}; + storage.synchronize(package); + package.moduleDependencies.clear(); + package.types.clear(); + package.updatedSourceIds.clear(); + package.updatedModuleDependencySourceIds = {sourceId1}; + + storage.synchronize(package); + + ASSERT_THAT(storage.fetchTypeByTypeId(fetchTypeId(sourceId1, "QQuickItem")), + Field(&Storage::Synchronization::Type::prototypeId, IsUnresolvedTypeId())); +} + +TEST_F(ProjectStorage, removed_document_import_changes_extension_to_unresolved) +{ + auto package{createVerySimpleSynchronizationPackage()}; + package.moduleDependencies.emplace_back(qmlNativeModuleId, Storage::Version{}, sourceId1); + package.types[0].extension = Storage::Synchronization::ImportedType{"QObject"}; + storage.synchronize(package); + package.moduleDependencies.clear(); + package.types.clear(); + package.updatedSourceIds.clear(); + package.updatedModuleDependencySourceIds = {sourceId1}; + + storage.synchronize(package); + + ASSERT_THAT(storage.fetchTypeByTypeId(fetchTypeId(sourceId1, "QQuickItem")), + Field(&Storage::Synchronization::Type::extensionId, IsUnresolvedTypeId())); +} + +TEST_F(ProjectStorage, added_document_import_fixes_unresolved_prototype) +{ + auto package{createVerySimpleSynchronizationPackage()}; + package.types[0].prototype = Storage::Synchronization::ImportedType{"QObject"}; + storage.synchronize(package); + package.moduleDependencies.emplace_back(qmlNativeModuleId, Storage::Version{}, sourceId1); + package.types.clear(); + package.updatedSourceIds.clear(); + package.updatedModuleDependencySourceIds = {sourceId1}; + + storage.synchronize(package); + + ASSERT_THAT(storage.fetchTypeByTypeId(fetchTypeId(sourceId1, "QQuickItem")), + IsPrototypeId(fetchTypeId(sourceId2, "QObject"))); +} + +TEST_F(ProjectStorage, added_document_import_fixes_unresolved_extension) +{ + auto package{createVerySimpleSynchronizationPackage()}; + package.types[0].extension = Storage::Synchronization::ImportedType{"QObject"}; + storage.synchronize(package); + package.moduleDependencies.emplace_back(qmlNativeModuleId, Storage::Version{}, sourceId1); + package.types.clear(); + package.updatedSourceIds.clear(); + package.updatedModuleDependencySourceIds = {sourceId1}; + + storage.synchronize(package); + + ASSERT_THAT(storage.fetchTypeByTypeId(fetchTypeId(sourceId1, "QQuickItem")), + IsExtensionId(fetchTypeId(sourceId2, "QObject"))); +} + } // namespace diff --git a/tests/unit/tests/unittests/sqlite/sqlitestatement-test.cpp b/tests/unit/tests/unittests/sqlite/sqlitestatement-test.cpp index f533c651e96..24b20176eaa 100644 --- a/tests/unit/tests/unittests/sqlite/sqlitestatement-test.cpp +++ b/tests/unit/tests/unittests/sqlite/sqlitestatement-test.cpp @@ -269,6 +269,21 @@ TEST_F(SqliteStatement, bind_int_id) ASSERT_THAT(readStatement.fetchIntValue(0), 42); } +TEST_F(SqliteStatement, bind_special_state_id) +{ + enum class SpecialIdState { Unresolved = -1 }; + constexpr TestIntId unresolvedTypeId = TestIntId::createSpecialState(SpecialIdState::Unresolved); + SqliteTestStatement<0, 1> statement("INSERT INTO test VALUES ('id', 323, ?)", database); + + statement.bind(1, unresolvedTypeId); + statement.next(); + + SqliteTestStatement<1, 1> readStatement("SELECT value FROM test WHERE name='id'", database); + readStatement.next(); + ASSERT_THAT(readStatement.fetchType(0), Sqlite::Type::Integer); + ASSERT_THAT(readStatement.fetchIntValue(0), -1); +} + TEST_F(SqliteStatement, bind_invalid_long_long_id_to_null) { TestLongLongId id;