diff options
-rw-r--r-- | src/qmlcompiler/qqmljsimportvisitor.cpp | 18 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljslogger.cpp | 7 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljslogger_p.h | 1 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljsscope.cpp | 34 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljsscope_p.h | 7 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljstypedescriptionreader.cpp | 2 | ||||
-rw-r--r-- | tests/auto/qml/qmltc_qprocess/CMakeLists.txt | 10 | ||||
-rw-r--r-- | tests/auto/qml/qmltc_qprocess/cpptypes/testtype.h | 52 | ||||
-rw-r--r-- | tests/auto/qml/qmltc_qprocess/data/singletonUncreatable.qml | 5 | ||||
-rw-r--r-- | tests/auto/qml/qmltc_qprocess/data/uncreatable.qml | 23 | ||||
-rw-r--r-- | tests/auto/qml/qmltc_qprocess/tst_qmltc_qprocess.cpp | 22 | ||||
-rw-r--r-- | tools/qmltc/qmltcvisitor.cpp | 5 |
12 files changed, 171 insertions, 15 deletions
diff --git a/src/qmlcompiler/qqmljsimportvisitor.cpp b/src/qmlcompiler/qqmljsimportvisitor.cpp index 4cc7849ce4..84cb2b4c63 100644 --- a/src/qmlcompiler/qqmljsimportvisitor.cpp +++ b/src/qmlcompiler/qqmljsimportvisitor.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "qqmljsimportvisitor_p.h" +#include "qqmljsmetatypes_p.h" #include "qqmljsresourcefilemapper_p.h" #include <QtCore/qfileinfo.h> @@ -1339,13 +1340,24 @@ bool QQmlJSImportVisitor::visit(UiObjectDefinition *definition) const QTypeRevision revision = QQmlJSScope::resolveTypes( m_currentScope, m_rootScopeImports, &m_usedTypes); - if (isRoot) { - if (auto base = m_currentScope->baseType(); - base && base->internalName() == u"QQmlComponent") { + if (auto base = m_currentScope->baseType(); base) { + if (isRoot && base->internalName() == u"QQmlComponent") { m_logger->log(u"Qml top level type cannot be 'Component'."_s, qmlTopLevelComponent, definition->qualifiedTypeNameId->identifierToken, true, true); return false; } + if (base->isSingleton() && m_currentScope->isComposite()) { + m_logger->log(u"Singleton Type %1 is not creatable."_s.arg( + m_currentScope->baseTypeName()), + qmlUncreatableType, definition->qualifiedTypeNameId->identifierToken, + true, true); + + } else if (!base->isCreatable()) { + // composite type m_currentScope is allowed to be uncreatable, but it cannot be the base of anything else + m_logger->log(u"Type %1 is not creatable."_s.arg(m_currentScope->baseTypeName()), + qmlUncreatableType, definition->qualifiedTypeNameId->identifierToken, + true, true); + } } if (m_nextIsInlineComponent) { Q_ASSERT(std::holds_alternative<InlineComponentNameType>(m_currentRootName)); diff --git a/src/qmlcompiler/qqmljslogger.cpp b/src/qmlcompiler/qqmljslogger.cpp index d5e8c219fe..976f706a9b 100644 --- a/src/qmlcompiler/qqmljslogger.cpp +++ b/src/qmlcompiler/qqmljslogger.cpp @@ -56,6 +56,8 @@ const LoggerWarningId qmlInvalidLintDirective { "invalid-lint-directive" }; const LoggerWarningId qmlUseProperFunction { "use-proper-function" }; const LoggerWarningId qmlAccessSingleton { "access-singleton-via-object" }; const LoggerWarningId qmlTopLevelComponent { "top-level-component" }; +const LoggerWarningId qmlUncreatableType { "uncreatable-type" }; + const QList<QQmlJSLogger::Category> &QQmlJSLogger::defaultCategories() { @@ -172,7 +174,10 @@ const QList<QQmlJSLogger::Category> &QQmlJSLogger::defaultCategories() QStringLiteral("Warn if a singleton is accessed via an object"), QtWarningMsg }, QQmlJSLogger::Category { qmlTopLevelComponent.name().toString(), QStringLiteral("TopLevelComponent"), - QStringLiteral("Fail when a top level Component are encountered"), QtWarningMsg } + QStringLiteral("Fail when a top level Component are encountered"), QtWarningMsg }, + QQmlJSLogger::Category { + qmlUncreatableType.name().toString(), QStringLiteral("UncreatableType"), + QStringLiteral("Warn if uncreatable types are created"), QtWarningMsg } }; return cats; diff --git a/src/qmlcompiler/qqmljslogger_p.h b/src/qmlcompiler/qqmljslogger_p.h index f91518041d..34be098832 100644 --- a/src/qmlcompiler/qqmljslogger_p.h +++ b/src/qmlcompiler/qqmljslogger_p.h @@ -130,6 +130,7 @@ extern const Q_QMLCOMPILER_PRIVATE_EXPORT LoggerWarningId qmlInvalidLintDirectiv extern const Q_QMLCOMPILER_PRIVATE_EXPORT LoggerWarningId qmlUseProperFunction; extern const Q_QMLCOMPILER_PRIVATE_EXPORT LoggerWarningId qmlAccessSingleton; extern const Q_QMLCOMPILER_PRIVATE_EXPORT LoggerWarningId qmlTopLevelComponent; +extern const Q_QMLCOMPILER_PRIVATE_EXPORT LoggerWarningId qmlUncreatableType; struct Message : public QQmlJS::DiagnosticMessage { diff --git a/src/qmlcompiler/qqmljsscope.cpp b/src/qmlcompiler/qqmljsscope.cpp index eda3b28e60..3ce732a742 100644 --- a/src/qmlcompiler/qqmljsscope.cpp +++ b/src/qmlcompiler/qqmljsscope.cpp @@ -1196,4 +1196,38 @@ QQmlJSScope::InlineComponentOrDocumentRootName QQmlJSScope::enclosingInlineCompo return RootDocumentNameType(); } +/*! + \internal + Returns true if the current type is creatable by checking all the required base classes. + "Uncreatability" is only inherited from base types for composite types (in qml) and not for non-composite types (c++). + + For the exact definition: + A type is uncreatable if and only if one of its composite base type or its first non-composite base type matches + following criteria: + \list + \li the base type is a singleton, or + \li the base type is an attached type, or + \li the base type is a C++ type with the QML_UNCREATABLE or QML_ANONYMOUS macro, or + \li the base type is a type without default constructor (in that case, it really needs QML_UNCREATABLE or QML_ANONYMOUS) + \endlist + */ +bool QQmlJSScope::isCreatable() const +{ + auto isCreatableNonRecursive = [](const QQmlJSScope *scope) { + return scope->hasCreatableFlag() && !scope->isSingleton() && scope->scopeType() == QMLScope; + }; + + for (const QQmlJSScope* scope = this; scope; scope = scope->baseType().get()) { + if (!scope->isComposite()) { + // just check the first nonComposite (c++) base for isCreatableNonRecursive() and then stop + return isCreatableNonRecursive(scope); + } else { + // check all composite (qml) bases for isCreatableNonRecursive(). + if (isCreatableNonRecursive(scope)) + return true; + } + } + // no uncreatable bases found + return false; +} QT_END_NAMESPACE diff --git a/src/qmlcompiler/qqmljsscope_p.h b/src/qmlcompiler/qqmljsscope_p.h index 0745acd668..70b51fec5f 100644 --- a/src/qmlcompiler/qqmljsscope_p.h +++ b/src/qmlcompiler/qqmljsscope_p.h @@ -508,7 +508,8 @@ public: } bool isSingleton() const { return m_flags & Singleton; } - bool isCreatable() const { return m_flags & Creatable; } + bool isCreatable() const; + bool hasCreatableFlag() const { return m_flags & Creatable; } /*! * \internal * @@ -522,7 +523,7 @@ public: bool isWrappedInImplicitComponent() const { return m_flags & WrappedInImplicitComponent; } bool extensionIsNamespace() const { return m_flags & HasExtensionNamespace; } void setIsSingleton(bool v) { m_flags.setFlag(Singleton, v); } - void setIsCreatable(bool v) { m_flags.setFlag(Creatable, v); } + void setCreatableFlag(bool v) { m_flags.setFlag(Creatable, v); } void setIsComposite(bool v) { m_flags.setFlag(Composite, v); } void setIsScript(bool v) { m_flags.setFlag(Script, v); } void setHasCustomParser(bool v) @@ -758,7 +759,7 @@ private: QString m_extensionTypeName; QQmlJSScope::WeakConstPtr m_extensionType; - Flags m_flags; + Flags m_flags = Creatable; // all types are marked as creatable by default. AccessSemantics m_semantics = AccessSemantics::Reference; QQmlJS::SourceLocation m_sourceLocation; diff --git a/src/qmlcompiler/qqmljstypedescriptionreader.cpp b/src/qmlcompiler/qqmljstypedescriptionreader.cpp index fe884b39ec..8c6368dfde 100644 --- a/src/qmlcompiler/qqmljstypedescriptionreader.cpp +++ b/src/qmlcompiler/qqmljstypedescriptionreader.cpp @@ -212,7 +212,7 @@ void QQmlJSTypeDescriptionReader::readComponent(UiObjectDefinition *ast) } else if (name == QLatin1String("isSingleton")) { scope->setIsSingleton(readBoolBinding(script)); } else if (name == QLatin1String("isCreatable")) { - scope->setIsCreatable(readBoolBinding(script)); + scope->setCreatableFlag(readBoolBinding(script)); } else if (name == QLatin1String("isComposite")) { scope->setIsComposite(readBoolBinding(script)); } else if (name == QLatin1String("hasCustomParser")) { diff --git a/tests/auto/qml/qmltc_qprocess/CMakeLists.txt b/tests/auto/qml/qmltc_qprocess/CMakeLists.txt index 5644a622a2..04d2084d4f 100644 --- a/tests/auto/qml/qmltc_qprocess/CMakeLists.txt +++ b/tests/auto/qml/qmltc_qprocess/CMakeLists.txt @@ -9,6 +9,11 @@ qt_internal_add_test(tst_qmltc_qprocess Qt::Qml Qt::QuickTestUtilsPrivate ) + +# special setup for singleton files: +set_source_files_properties(data/SingletonThing.qml data/singletonUncreatable.qml + PROPERTIES QT_QML_SINGLETON_TYPE true) + qt6_add_qml_module(tst_qmltc_qprocess VERSION 1.0 URI QmltcQProcessTests @@ -23,6 +28,8 @@ qt6_add_qml_module(tst_qmltc_qprocess data/invalidAliasRevision.qml data/ComponentType.qml data/inlineComponentWithEnum.qml + data/singletonUncreatable.qml + data/uncreatable.qml ) set(common_libraries @@ -37,9 +44,6 @@ set(common_libraries target_include_directories(tst_qmltc_qprocess PUBLIC cpptypes/) target_link_libraries(tst_qmltc_qprocess PUBLIC ${common_libraries}) -# special setup for singleton files: -set_source_files_properties(SingletonThing.qml PROPERTIES QT_QML_SINGLETON_TYPE true) - add_dependencies(tst_qmltc_qprocess Qt::qmltc) # fetch --resource arguments manually (mimics the logic of qmltc compilation diff --git a/tests/auto/qml/qmltc_qprocess/cpptypes/testtype.h b/tests/auto/qml/qmltc_qprocess/cpptypes/testtype.h index a6fdb4a51a..6082870c76 100644 --- a/tests/auto/qml/qmltc_qprocess/cpptypes/testtype.h +++ b/tests/auto/qml/qmltc_qprocess/cpptypes/testtype.h @@ -3,12 +3,15 @@ #ifndef TESTTYPE_H #define TESTTYPE_H + +#include <QtQmlIntegration/qqmlintegration.h> #include <QtCore/qobject.h> #include <QtQml/qqmlregistration.h> class TypeWithVersionedAlias : public QObject { Q_OBJECT + QML_UNCREATABLE("") QML_ELEMENT QString m_readAndWrite; @@ -17,4 +20,53 @@ public: Q_PROPERTY(QString notExisting MEMBER m_readAndWrite REVISION(6, 0)); Q_PROPERTY(QString existing MEMBER m_readAndWrite REVISION(1, 0)); }; + +class UncreatableType : public QObject +{ + Q_OBJECT + QML_ELEMENT + QML_UNCREATABLE("") +}; + +class NoDefaultConstructorType : public QObject +{ + Q_OBJECT + QML_ELEMENT + NoDefaultConstructorType() = delete; +}; + +class SingletonType : public QObject +{ + Q_OBJECT + QML_ELEMENT + QML_SINGLETON +}; + +class NotSingletonType : public SingletonType +{ + Q_OBJECT + QML_ELEMENT +}; + +class NormalTypeAttached : public QObject +{ + Q_OBJECT + QML_ANONYMOUS +public: + NormalTypeAttached(QObject* parent): QObject(parent) {} +}; + +class NormalType : public QObject +{ + Q_OBJECT + QML_ELEMENT + QML_ATTACHED(NormalTypeAttached) + + static NormalTypeAttached *qmlAttachedProperties(QObject *object) { + return new NormalTypeAttached(object); + } +}; + + + #endif // TESTTYPE_H diff --git a/tests/auto/qml/qmltc_qprocess/data/singletonUncreatable.qml b/tests/auto/qml/qmltc_qprocess/data/singletonUncreatable.qml new file mode 100644 index 0000000000..d48a4ee186 --- /dev/null +++ b/tests/auto/qml/qmltc_qprocess/data/singletonUncreatable.qml @@ -0,0 +1,5 @@ +pragma Singleton + +UncreatableType { + +} diff --git a/tests/auto/qml/qmltc_qprocess/data/uncreatable.qml b/tests/auto/qml/qmltc_qprocess/data/uncreatable.qml new file mode 100644 index 0000000000..aad6ef3421 --- /dev/null +++ b/tests/auto/qml/qmltc_qprocess/data/uncreatable.qml @@ -0,0 +1,23 @@ +import QtQuick + +Item { + // Illegal cases: + UncreatableType {} + SingletonThing {} + SingletonType {} + + component A: SingletonThing {} + component AA: A {} + component AAA: AA {} + AAA {} + + component B: SingletonType {} + component BB: B {} + component BBB: BB {} + BBB {} + + // Legal cases, where qmltc should not crash + property SingletonThing myQmlSingleton + property SingletonType myCppSingleton + NotSingletonType {} // ok because a non composite type inheriting from a singleton does not become a singleton! +} diff --git a/tests/auto/qml/qmltc_qprocess/tst_qmltc_qprocess.cpp b/tests/auto/qml/qmltc_qprocess/tst_qmltc_qprocess.cpp index ae01f4697f..ea30e3c83f 100644 --- a/tests/auto/qml/qmltc_qprocess/tst_qmltc_qprocess.cpp +++ b/tests/auto/qml/qmltc_qprocess/tst_qmltc_qprocess.cpp @@ -189,9 +189,25 @@ void tst_qmltc_qprocess::inlineComponent() void tst_qmltc_qprocess::singleton() { - const auto errors = runQmltc(u"SingletonThing.qml"_s, false); - QEXPECT_FAIL("", "qmltc does not support singletons at the moment", Continue); - QVERIFY(!errors.contains(u"Singleton types are not supported"_s)); + { + const auto errors = runQmltc(u"singletonUncreatable.qml"_s, false); + QVERIFY(errors.contains("singletonUncreatable.qml:3:1: Type UncreatableType is not " + "creatable. [uncreatable-type]")); + } + { + const auto errors = runQmltc(u"uncreatable.qml"_s, false); + QVERIFY(errors.contains( + "uncreatable.qml:5:5: Type UncreatableType is not creatable. [uncreatable-type]")); + QVERIFY(errors.contains("uncreatable.qml:6:5: Singleton Type SingletonThing is not " + "creatable. [uncreatable-type]")); + QVERIFY(errors.contains("uncreatable.qml:7:5: Singleton Type SingletonType is not " + "creatable. [uncreatable-type]")); + QVERIFY(errors.contains("uncreatable.qml:9:18: Singleton Type SingletonThing is not " + "creatable. [uncreatable-type]")); + QVERIFY(errors.contains("uncreatable.qml:14:18: Singleton Type SingletonType is not " + "creatable. [uncreatable-type]")); + QVERIFY(!errors.contains("NotSingletonType")); + } } void tst_qmltc_qprocess::warningsAsErrors() diff --git a/tools/qmltc/qmltcvisitor.cpp b/tools/qmltc/qmltcvisitor.cpp index 3143316652..c777076d0c 100644 --- a/tools/qmltc/qmltcvisitor.cpp +++ b/tools/qmltc/qmltcvisitor.cpp @@ -103,7 +103,10 @@ void QmltcVisitor::findCppIncludes() // look in type's base type auto base = type->baseType(); - Q_ASSERT(base || !type->isComposite()); + if (!base && type->isComposite()) + // in this case, qqmljsimportvisitor would have already print an error message + // about the missing type, so just return silently without crashing + return; if (!base || visitType(base)) return; addCppInclude(base); |