forked from qt-creator/qt-creator
QmlDesigner: Notify error for alias cycle
When a cyclic dependency is introduced through QML aliases, the code model's synchronization mechanism is disrupted. This interruption prevents the model from updating type information consistently across components, resulting in stale or incorrect type data. Such inconsistencies can lead to unpredictable behavior or runtime errors in the application. This commit documents the issue and highlights the need for user intervention to manually resolve the cycle. Change-Id: I3c8283b0bbeba0634463e783ee18d5c99d8c919a Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
@@ -786,6 +786,12 @@ struct ProjectStorage::Statements
|
|||||||
"FROM propertyDeclarations "
|
"FROM propertyDeclarations "
|
||||||
"WHERE propertyDeclarationId=?1 LIMIT 1",
|
"WHERE propertyDeclarationId=?1 LIMIT 1",
|
||||||
database};
|
database};
|
||||||
|
mutable Sqlite::ReadStatement<2, 1> selectPropertyDeclarationNameAndTypeIdForPropertyDeclarationIdStatement{
|
||||||
|
"SELECT name, typeId "
|
||||||
|
"FROM propertyDeclarations "
|
||||||
|
"WHERE propertyDeclarationId=?1 "
|
||||||
|
"LIMIT 1",
|
||||||
|
database};
|
||||||
mutable Sqlite::ReadStatement<1, 1> selectSignalDeclarationNamesForTypeStatement{
|
mutable Sqlite::ReadStatement<1, 1> selectSignalDeclarationNamesForTypeStatement{
|
||||||
"WITH RECURSIVE "
|
"WITH RECURSIVE "
|
||||||
" prototypes(typeId) AS ( "
|
" prototypes(typeId) AS ( "
|
||||||
@@ -4447,9 +4453,17 @@ void ProjectStorage::checkForAliasChainCycle(PropertyDeclarationId propertyDecla
|
|||||||
NanotraceHR::Tracer tracer{"check for alias chain cycle",
|
NanotraceHR::Tracer tracer{"check for alias chain cycle",
|
||||||
category(),
|
category(),
|
||||||
keyValue("property declaration id", propertyDeclarationId)};
|
keyValue("property declaration id", propertyDeclarationId)};
|
||||||
auto callback = [=](PropertyDeclarationId currentPropertyDeclarationId) {
|
auto callback = [&](PropertyDeclarationId currentPropertyDeclarationId) {
|
||||||
if (propertyDeclarationId == currentPropertyDeclarationId)
|
if (propertyDeclarationId == currentPropertyDeclarationId) {
|
||||||
|
auto [propertyName, typeId] = s->selectPropertyDeclarationNameAndTypeIdForPropertyDeclarationIdStatement
|
||||||
|
.value<std::tuple<Utils::SmallString, TypeId>>(
|
||||||
|
propertyDeclarationId);
|
||||||
|
auto [typeName, sourceId] = s->selectTypeNameAndSourceIdByTypeIdStatement
|
||||||
|
.value<std::tuple<Utils::SmallString, SourceId>>(typeId);
|
||||||
|
errorNotifier->aliasCycle(typeName, propertyName, sourceId);
|
||||||
|
|
||||||
throw AliasChainCycle{};
|
throw AliasChainCycle{};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
s->selectPropertyDeclarationIdsForAliasChainStatement.readCallback(callback,
|
s->selectPropertyDeclarationIdsForAliasChainStatement.readCallback(callback,
|
||||||
|
@@ -33,6 +33,10 @@ public:
|
|||||||
|
|
||||||
virtual void qmltypesFileMissing(QStringView qmltypesPath) = 0;
|
virtual void qmltypesFileMissing(QStringView qmltypesPath) = 0;
|
||||||
virtual void prototypeCycle(Utils::SmallStringView typeName, SourceId typeSourceId) = 0;
|
virtual void prototypeCycle(Utils::SmallStringView typeName, SourceId typeSourceId) = 0;
|
||||||
|
virtual void aliasCycle(Utils::SmallStringView typeName,
|
||||||
|
Utils::SmallStringView propertyName,
|
||||||
|
SourceId typeSourceId)
|
||||||
|
= 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
~ProjectStorageErrorNotifierInterface() = default;
|
~ProjectStorageErrorNotifierInterface() = default;
|
||||||
|
@@ -98,4 +98,18 @@ void ProjectStorageErrorNotifier::prototypeCycle(Utils::SmallStringView typeName
|
|||||||
m_pathCache.sourcePath(typeSourceId));
|
m_pathCache.sourcePath(typeSourceId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ProjectStorageErrorNotifier::aliasCycle(Utils::SmallStringView typeName,
|
||||||
|
Utils::SmallStringView propertyName,
|
||||||
|
SourceId typeSourceId)
|
||||||
|
{
|
||||||
|
const QString typeNameString{typeName};
|
||||||
|
const QString propertyNameString{propertyName};
|
||||||
|
|
||||||
|
logIssue(ProjectExplorer::Task::Error,
|
||||||
|
Tr::tr("Alias cycle detected for type %1 and property %2 in %3.")
|
||||||
|
.arg(typeNameString)
|
||||||
|
.arg(propertyNameString),
|
||||||
|
m_pathCache.sourcePath(typeSourceId));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace QmlDesigner
|
} // namespace QmlDesigner
|
||||||
|
@@ -29,6 +29,9 @@ public:
|
|||||||
SourceId qmldirSourceId) override;
|
SourceId qmldirSourceId) override;
|
||||||
void qmltypesFileMissing(QStringView qmltypesPath) override;
|
void qmltypesFileMissing(QStringView qmltypesPath) override;
|
||||||
void prototypeCycle(Utils::SmallStringView typeName, SourceId typeSourceId) override;
|
void prototypeCycle(Utils::SmallStringView typeName, SourceId typeSourceId) override;
|
||||||
|
void aliasCycle(Utils::SmallStringView typeName,
|
||||||
|
Utils::SmallStringView propertyName,
|
||||||
|
SourceId typeSourceId) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
PathCacheType &m_pathCache;
|
PathCacheType &m_pathCache;
|
||||||
|
@@ -37,4 +37,10 @@ public:
|
|||||||
prototypeCycle,
|
prototypeCycle,
|
||||||
(Utils::SmallStringView typeName, QmlDesigner::SourceId typeSourceId),
|
(Utils::SmallStringView typeName, QmlDesigner::SourceId typeSourceId),
|
||||||
(override));
|
(override));
|
||||||
|
MOCK_METHOD(void,
|
||||||
|
aliasCycle,
|
||||||
|
(Utils::SmallStringView typeName,
|
||||||
|
Utils::SmallStringView propertyName,
|
||||||
|
QmlDesigner::SourceId typeSourceId),
|
||||||
|
(override));
|
||||||
};
|
};
|
||||||
|
@@ -4677,7 +4677,7 @@ TEST_F(ProjectStorage, update_aliases_after_change_property_to_alias)
|
|||||||
"objects"))))));
|
"objects"))))));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(ProjectStorage, check_for_proto_type_cycle_throws)
|
TEST_F(ProjectStorage, check_for_alias_type_cycle_throws)
|
||||||
{
|
{
|
||||||
auto package{createSynchronizationPackageWithRecursiveAliases()};
|
auto package{createSynchronizationPackageWithRecursiveAliases()};
|
||||||
package.types[1].propertyDeclarations.clear();
|
package.types[1].propertyDeclarations.clear();
|
||||||
@@ -4688,7 +4688,7 @@ TEST_F(ProjectStorage, check_for_proto_type_cycle_throws)
|
|||||||
ASSERT_THROW(storage.synchronize(package), QmlDesigner::AliasChainCycle);
|
ASSERT_THROW(storage.synchronize(package), QmlDesigner::AliasChainCycle);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(ProjectStorage, check_for_proto_type_cycle_after_update_throws)
|
TEST_F(ProjectStorage, check_for_alias_type_cycle_after_update_throws)
|
||||||
{
|
{
|
||||||
auto package{createSynchronizationPackageWithRecursiveAliases()};
|
auto package{createSynchronizationPackageWithRecursiveAliases()};
|
||||||
storage.synchronize(package);
|
storage.synchronize(package);
|
||||||
@@ -4702,6 +4702,21 @@ TEST_F(ProjectStorage, check_for_proto_type_cycle_after_update_throws)
|
|||||||
QmlDesigner::AliasChainCycle);
|
QmlDesigner::AliasChainCycle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(ProjectStorage, check_for_alias_type_cycle_after_update_notifies_about_error)
|
||||||
|
{
|
||||||
|
auto package{createSynchronizationPackageWithRecursiveAliases()};
|
||||||
|
storage.synchronize(package);
|
||||||
|
package.types[1].propertyDeclarations.clear();
|
||||||
|
package.types[1].propertyDeclarations.push_back(Storage::Synchronization::PropertyDeclaration{
|
||||||
|
"objects", Storage::Synchronization::ImportedType{"AliasItem2"}, "objects"});
|
||||||
|
importsSourceId2.emplace_back(qtQuickModuleId, Storage::Version{}, sourceId2);
|
||||||
|
|
||||||
|
EXPECT_CALL(errorNotifierMock, aliasCycle(Eq("QObject"), Eq("objects"), sourceId2));
|
||||||
|
|
||||||
|
EXPECT_ANY_THROW(storage.synchronize(
|
||||||
|
SynchronizationPackage{importsSourceId2, {package.types[1]}, {sourceId2}}));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(ProjectStorage, qualified_prototype)
|
TEST_F(ProjectStorage, qualified_prototype)
|
||||||
{
|
{
|
||||||
auto package{createSimpleSynchronizationPackage()};
|
auto package{createSimpleSynchronizationPackage()};
|
||||||
|
Reference in New Issue
Block a user