diff options
author | Ulf Hermann <ulf.hermann@qt.io> | 2021-02-01 15:03:19 +0100 |
---|---|---|
committer | Ulf Hermann <ulf.hermann@qt.io> | 2021-02-01 16:51:51 +0100 |
commit | f6fc35b45b449fe7aaca3237f29393a12fc3f90c (patch) | |
tree | bd808ac4fd916f49417f08e0e32105e614c7f0fd | |
parent | 24ec3b3e7fa09900d791dcaedb0820a0b1890336 (diff) |
QmlCompiler: Allow for multiple extensions per object
Previously, the assumption was that each object could only have a single
extension object. As proven by the new qqmllanguage test this is not the
case. Each registered object in the type hierarchy can have its own
extension. Therefore, adjust the algorithms that generate qmltypes and
iterate the extension objects when analyzing them.
This leads us to the realization that anonymous types can in fact
meaningfully carry extensions and implement interfaces. Adapt
qmltyperegistrar accordingly.
For the test to compile, however, we need to realize that the class
declaring interfaces needs to befriend all potential subclass's
QmlInterface structs. Fix that, too. The rabbit hole went deep.
Change-Id: Ia451897e927e03b95c3062e829edf1dfcd216613
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
-rw-r--r-- | src/qml/qml/qqml.h | 3 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljsscope.cpp | 36 | ||||
-rw-r--r-- | src/qmltyperegistrar/qmltypesclassdescription.cpp | 9 | ||||
-rw-r--r-- | src/qmltyperegistrar/qmltypescreator.cpp | 22 | ||||
-rw-r--r-- | tests/auto/qml/qmllint/data/StaticTest/multi.qmltypes | 41 | ||||
-rw-r--r-- | tests/auto/qml/qmllint/data/StaticTest/qmldir | 2 | ||||
-rw-r--r-- | tests/auto/qml/qmllint/data/multiExtension.qml | 6 | ||||
-rw-r--r-- | tests/auto/qml/qmllint/tst_qmllint.cpp | 1 | ||||
-rw-r--r-- | tests/auto/qml/qmltyperegistrar/tst_qmltyperegistrar.cpp | 10 | ||||
-rw-r--r-- | tests/auto/qml/qmltyperegistrar/tst_qmltyperegistrar.h | 46 | ||||
-rw-r--r-- | tests/auto/qml/qqmllanguage/testtypes.h | 62 | ||||
-rw-r--r-- | tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp | 21 | ||||
-rw-r--r-- | tools/qmllint/checkidentifiers.cpp | 27 |
13 files changed, 227 insertions, 59 deletions
diff --git a/src/qml/qml/qqml.h b/src/qml/qml/qqml.h index 61eb5e1ffe..078bd93d25 100644 --- a/src/qml/qml/qqml.h +++ b/src/qml/qml/qqml.h @@ -153,7 +153,8 @@ #define QML_IMPLEMENTS_INTERFACES(INTERFACES) \ Q_INTERFACES(INTERFACES) \ - enum class QmlIsInterface {yes = false}; + enum class QmlIsInterface {yes = false}; \ + template<typename, typename> friend struct QML_PRIVATE_NAMESPACE::QmlInterface; #define QML_UNAVAILABLE \ QML_FOREIGN(QQmlTypeNotAvailable) diff --git a/src/qmlcompiler/qqmljsscope.cpp b/src/qmlcompiler/qqmljsscope.cpp index 11a2b4a619..1fe22ab5a8 100644 --- a/src/qmlcompiler/qqmljsscope.cpp +++ b/src/qmlcompiler/qqmljsscope.cpp @@ -41,20 +41,14 @@ QT_BEGIN_NAMESPACE template<typename Action> static bool searchBaseAndExtensionTypes(const QQmlJSScope *type, const Action &check) { - const QQmlJSScope *nonCompositeBase = nullptr; for (const QQmlJSScope *scope = type; scope; scope = scope->baseType().data()) { - if (check(scope)) - return true; - - if (!nonCompositeBase && !scope->isComposite()) - nonCompositeBase = scope; - } - - if (!nonCompositeBase) - return false; + // Extensions override their base types + for (const QQmlJSScope *extension = scope->extensionType().data(); extension; + extension = extension->baseType().data()) { + if (check(extension)) + return true; + } - for (const QQmlJSScope *scope = nonCompositeBase->extensionType().data(); scope; - scope = scope->baseType().data()) { if (check(scope)) return true; } @@ -263,23 +257,7 @@ void QQmlJSScope::resolveGroupedScopes() return false; }; - const QQmlJSScope *nonCompositeBase = isComposite() ? this : nullptr; - for (const QQmlJSScope *type = this; type; type = type->baseType().data()) { - if (findProperty(type)) - break; - - if (!nonCompositeBase && !type->isComposite()) - nonCompositeBase = type; - } - - if (!childScope->m_baseType && nonCompositeBase && nonCompositeBase != this) { - for (const QQmlJSScope *type = nonCompositeBase->extensionType().data(); type; - type = type->baseType().data()) { - if (findProperty(type)) - break; - } - } - + searchBaseAndExtensionTypes(this, findProperty); childScope->resolveGroupedScopes(); } } diff --git a/src/qmltyperegistrar/qmltypesclassdescription.cpp b/src/qmltyperegistrar/qmltypesclassdescription.cpp index 70a4e49d19..2310b8b3b2 100644 --- a/src/qmltyperegistrar/qmltypesclassdescription.cpp +++ b/src/qmltyperegistrar/qmltypesclassdescription.cpp @@ -112,9 +112,14 @@ void QmlTypesClassDescription::collectLocalAnonymous( const auto classInfos = classDef->value(QLatin1String("classInfos")).toArray(); for (const QJsonValue &classInfo : classInfos) { const QJsonObject obj = classInfo.toObject(); - if (obj[QLatin1String("name")].toString() == QLatin1String("DefaultProperty")) { + const QString name = obj[QStringLiteral("name")].toString(); + const QString value = obj[QStringLiteral("value")].toString(); + + if (name == QStringLiteral("DefaultProperty")) { defaultProp = obj[QLatin1String("value")].toString(); - break; + } else if (name == QStringLiteral("QML.Extended")) { + extensionType = value; + collectRelated(value, types, foreign, defaultRevision); } } diff --git a/src/qmltyperegistrar/qmltypescreator.cpp b/src/qmltyperegistrar/qmltypescreator.cpp index 4dafdc4490..f754679fb9 100644 --- a/src/qmltyperegistrar/qmltypescreator.cpp +++ b/src/qmltyperegistrar/qmltypescreator.cpp @@ -61,6 +61,17 @@ void QmlTypesCreator::writeClassProperties(const QmlTypesClassDescription &colle if (!collector.sequenceValueType.isEmpty()) m_qml.writeScriptBinding(QLatin1String("valueType"), enquote(collector.sequenceValueType)); + if (!collector.extensionType.isEmpty()) + m_qml.writeScriptBinding(QLatin1String("extension"), enquote(collector.extensionType)); + + if (!collector.implementsInterfaces.isEmpty()) { + QStringList interfaces; + for (const QString &interface : collector.implementsInterfaces) + interfaces << enquote(interface); + + m_qml.writeArrayBinding(QLatin1String("interfaces"), interfaces); + } + if (collector.elementName.isEmpty()) return; @@ -96,17 +107,6 @@ void QmlTypesCreator::writeClassProperties(const QmlTypesClassDescription &colle if (!collector.attachedType.isEmpty()) m_qml.writeScriptBinding(QLatin1String("attachedType"), enquote(collector.attachedType)); - - if (!collector.extensionType.isEmpty()) - m_qml.writeScriptBinding(QLatin1String("extension"), enquote(collector.extensionType)); - - if (!collector.implementsInterfaces.isEmpty()) { - QStringList interfaces; - for (const QString &interface : collector.implementsInterfaces) - interfaces << enquote(interface); - - m_qml.writeArrayBinding(QLatin1String("interfaces"), interfaces); - } } void QmlTypesCreator::writeType(const QJsonObject &property, const QString &key, bool isReadonly, diff --git a/tests/auto/qml/qmllint/data/StaticTest/multi.qmltypes b/tests/auto/qml/qmllint/data/StaticTest/multi.qmltypes new file mode 100644 index 0000000000..9a2ed24482 --- /dev/null +++ b/tests/auto/qml/qmllint/data/StaticTest/multi.qmltypes @@ -0,0 +1,41 @@ +import QtQuick.tooling 1.2 + +// This file describes the plugin-supplied types contained in the library. +// It is used for QML tooling purposes only. +// +// This file was auto-generated by qmltyperegistrar. + +Module { + Component { + file: "testtypes.h" + name: "ExtensionA" + accessSemantics: "reference" + prototype: "QObject" + Property { name: "a"; type: "int"; isReadonly: true; read: "a" } + } + Component { + file: "testtypes.h" + name: "ExtensionB" + accessSemantics: "reference" + prototype: "QObject" + Property { name: "b"; type: "int"; isReadonly: true; read: "b" } + } + Component { + file: "testtypes.h" + name: "MultiExtension" + accessSemantics: "reference" + prototype: "MultiExtensionParent" + exports: ["StaticTest/MultiExtension 1.0"] + exportMetaObjectRevisions: [256] + extension: "ExtensionB" + Property { name: "e"; type: "int"; isReadonly: true; read: "e" } + } + Component { + file: "testtypes.h" + name: "MultiExtensionParent" + accessSemantics: "reference" + prototype: "QObject" + extension: "ExtensionA" + Property { name: "p"; type: "int"; isReadonly: true; read: "p" } + } +} diff --git a/tests/auto/qml/qmllint/data/StaticTest/qmldir b/tests/auto/qml/qmllint/data/StaticTest/qmldir new file mode 100644 index 0000000000..4702175b79 --- /dev/null +++ b/tests/auto/qml/qmllint/data/StaticTest/qmldir @@ -0,0 +1,2 @@ +module StaticTest +typeinfo multi.qmltypes diff --git a/tests/auto/qml/qmllint/data/multiExtension.qml b/tests/auto/qml/qmllint/data/multiExtension.qml new file mode 100644 index 0000000000..12b36dbb4f --- /dev/null +++ b/tests/auto/qml/qmllint/data/multiExtension.qml @@ -0,0 +1,6 @@ +import StaticTest + +MultiExtension { + property int t: a + b + e + p +} + diff --git a/tests/auto/qml/qmllint/tst_qmllint.cpp b/tests/auto/qml/qmllint/tst_qmllint.cpp index 895953c2f7..b874d3cc6f 100644 --- a/tests/auto/qml/qmllint/tst_qmllint.cpp +++ b/tests/auto/qml/qmllint/tst_qmllint.cpp @@ -337,6 +337,7 @@ void TestQmllint::cleanQmlCode_data() QTest::newRow("goodAliasObject") << QStringLiteral("goodAliasObject.qml"); QTest::newRow("jsmoduleimport") << QStringLiteral("jsmoduleimport.qml"); QTest::newRow("overridescript") << QStringLiteral("overridescript.qml"); + QTest::newRow("multiExtension") << QStringLiteral("multiExtension.qml"); } void TestQmllint::cleanQmlCode() diff --git a/tests/auto/qml/qmltyperegistrar/tst_qmltyperegistrar.cpp b/tests/auto/qml/qmltyperegistrar/tst_qmltyperegistrar.cpp index f7a29948a0..b752c4d6c1 100644 --- a/tests/auto/qml/qmltyperegistrar/tst_qmltyperegistrar.cpp +++ b/tests/auto/qml/qmltyperegistrar/tst_qmltyperegistrar.cpp @@ -161,4 +161,14 @@ void tst_qmltyperegistrar::metaTypesRegistered() verifyMetaType("Ccc*", "Ccc"); } +void tst_qmltyperegistrar::multiExtensions() +{ + QVERIFY(qmltypesData.contains("name: \"MultiExtension\"")); + QVERIFY(qmltypesData.contains("prototype: \"MultiExtensionParent\"")); + QVERIFY(qmltypesData.contains("name: \"MultiExtensionParent\"")); + QVERIFY(qmltypesData.contains("extension: \"ExtensionA\"")); + QVERIFY(qmltypesData.contains("extension: \"ExtensionB\"")); + QVERIFY(qmltypesData.contains("interfaces: [\"Interface3\"]")); +} + QTEST_MAIN(tst_qmltyperegistrar) diff --git a/tests/auto/qml/qmltyperegistrar/tst_qmltyperegistrar.h b/tests/auto/qml/qmltyperegistrar/tst_qmltyperegistrar.h index 6267868375..40569879ec 100644 --- a/tests/auto/qml/qmltyperegistrar/tst_qmltyperegistrar.h +++ b/tests/auto/qml/qmltyperegistrar/tst_qmltyperegistrar.h @@ -37,10 +37,12 @@ class Interface {}; class Interface2 {}; +class Interface3 {}; QT_BEGIN_NAMESPACE Q_DECLARE_INTERFACE(Interface, "io.qt.bugreports.Interface"); Q_DECLARE_INTERFACE(Interface2, "io.qt.bugreports.Interface2"); +Q_DECLARE_INTERFACE(Interface3, "io.qt.bugreports.Interface3"); QT_END_NAMESPACE @@ -150,6 +152,49 @@ public: DerivedFromForeign(QObject *parent) : QTimeLine(1000, parent) {} }; +class ExtensionA : public QObject +{ + Q_OBJECT + QML_ANONYMOUS + Q_PROPERTY(int a READ a CONSTANT) +public: + ExtensionA(QObject *parent = nullptr) : QObject(parent) {} + int a() const { return 'a'; } +}; + +class ExtensionB : public QObject +{ + Q_OBJECT + QML_ANONYMOUS + Q_PROPERTY(int b READ b CONSTANT) +public: + ExtensionB(QObject *parent = nullptr) : QObject(parent) {} + int b() const { return 'b'; } +}; + +class MultiExtensionParent : public QObject, public Interface3 +{ + Q_OBJECT + QML_ANONYMOUS + QML_EXTENDED(ExtensionA) + QML_IMPLEMENTS_INTERFACES(Interface3) + Q_PROPERTY(int p READ p CONSTANT) +public: + MultiExtensionParent(QObject *parent = nullptr) : QObject(parent) {} + int p() const { return 'p'; } +}; + +class MultiExtension : public MultiExtensionParent +{ + Q_OBJECT + QML_ELEMENT + QML_EXTENDED(ExtensionB) + Q_PROPERTY(int e READ e CONSTANT) +public: + MultiExtension(QObject *parent = nullptr) : MultiExtensionParent(parent) {} + int e() const { return 'e'; } +}; + class tst_qmltyperegistrar : public QObject { Q_OBJECT @@ -170,6 +215,7 @@ private slots: void namespacedElement(); void derivedFromForeign(); void metaTypesRegistered(); + void multiExtensions(); private: QByteArray qmltypesData; diff --git a/tests/auto/qml/qqmllanguage/testtypes.h b/tests/auto/qml/qqmllanguage/testtypes.h index 758b9e2f62..77f09a232c 100644 --- a/tests/auto/qml/qqmllanguage/testtypes.h +++ b/tests/auto/qml/qqmllanguage/testtypes.h @@ -1638,6 +1638,68 @@ private: WrapperSingleton() = default; }; +class ExtensionA : public QObject +{ + Q_OBJECT + QML_ANONYMOUS + Q_PROPERTY(int a READ a CONSTANT) + Q_PROPERTY(int c READ c CONSTANT) + Q_PROPERTY(int d READ d CONSTANT) + Q_PROPERTY(int f READ f CONSTANT) + Q_PROPERTY(int g READ g CONSTANT) +public: + ExtensionA(QObject *parent = nullptr) : QObject(parent) {} + int a() const { return 'a'; } + int c() const { return 11; } + int d() const { return 21; } + int f() const { return 31; } + int g() const { return 41; } +}; + +class ExtensionB : public QObject +{ + Q_OBJECT + QML_ANONYMOUS + Q_PROPERTY(int b READ b CONSTANT) + Q_PROPERTY(int c READ c CONSTANT) + Q_PROPERTY(int d READ d CONSTANT) +public: + ExtensionB(QObject *parent = nullptr) : QObject(parent) {} + int b() const { return 'b'; } + int c() const { return 12; } + int d() const { return 22; } +}; + +class MultiExtensionParent : public QObject +{ + Q_OBJECT + QML_ANONYMOUS + QML_EXTENDED(ExtensionA) + Q_PROPERTY(int p READ p CONSTANT) + Q_PROPERTY(int c READ c CONSTANT) + Q_PROPERTY(int f READ f CONSTANT) +public: + MultiExtensionParent(QObject *parent = nullptr) : QObject(parent) {} + int p() const { return 'p'; } + int c() const { return 13; } + int f() const { return 33; } +}; + +class MultiExtension : public MultiExtensionParent +{ + Q_OBJECT + QML_ELEMENT + QML_EXTENDED(ExtensionB) + Q_PROPERTY(int e READ e CONSTANT) + Q_PROPERTY(int c READ c CONSTANT) + Q_PROPERTY(int g READ g CONSTANT) +public: + MultiExtension(QObject *parent = nullptr) : MultiExtensionParent(parent) {} + int e() const { return 'e'; } + int c() const { return 14; } + int g() const { return 44; } +}; + void registerTypes(); #endif // TESTTYPES_H diff --git a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp index 454ae59ad3..5e40387ceb 100644 --- a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp +++ b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp @@ -347,6 +347,8 @@ private slots: void extendedSingleton(); void qtbug_85932(); + void multiExtension(); + private: QQmlEngine engine; QStringList defaultImportPathList; @@ -6128,6 +6130,25 @@ void tst_qqmllanguage::qtbug_85932() QCOMPARE(allWarnings.at(1).toString(), warning2); } +void tst_qqmllanguage::multiExtension() +{ + QQmlEngine engine; + QQmlComponent c(&engine); + c.setData("import StaticTest\nMultiExtension {}", QUrl()); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QCOMPARE(o->property("a").toInt(), int('a')); + QCOMPARE(o->property("b").toInt(), int('b')); + QCOMPARE(o->property("p").toInt(), int('p')); + QCOMPARE(o->property("e").toInt(), int('e')); + + // Extension properties override base object properties + QCOMPARE(o->property("c").toInt(), 12); + QCOMPARE(o->property("d").toInt(), 22); + QCOMPARE(o->property("f").toInt(), 31); + QCOMPARE(o->property("g").toInt(), 44); +} + QTEST_MAIN(tst_qqmllanguage) #include "tst_qqmllanguage.moc" diff --git a/tools/qmllint/checkidentifiers.cpp b/tools/qmllint/checkidentifiers.cpp index 32a47891d5..6400e5db77 100644 --- a/tools/qmllint/checkidentifiers.cpp +++ b/tools/qmllint/checkidentifiers.cpp @@ -76,19 +76,14 @@ void CheckIdentifiers::printContext( + QLatin1Char('\n'), Normal); } -static bool walkViaParentAndAttachedScopes(QQmlJSScope::ConstPtr rootType, - std::function<bool(QQmlJSScope::ConstPtr)> visit) +template<typename Visitor> +static bool walkRelatedScopes(QQmlJSScope::ConstPtr rootType, const Visitor &visit) { if (rootType.isNull()) return false; std::stack<QQmlJSScope::ConstPtr> stack; stack.push(rootType); - if (!rootType->isComposite()) { - if (auto extension = rootType->extensionType()) - stack.push(extension); - } - while (!stack.empty()) { const auto type = stack.top(); stack.pop(); @@ -96,17 +91,17 @@ static bool walkViaParentAndAttachedScopes(QQmlJSScope::ConstPtr rootType, if (visit(type)) return true; - if (auto superType = type->baseType()) { - stack.push(superType); - if (type->isComposite() && !superType->isComposite()) { - if (auto extension = superType->extensionType()) - stack.push(extension); - } - } - if (auto attachedType = type->attachedType()) stack.push(attachedType); + + if (auto baseType = type->baseType()) + stack.push(baseType); + + // Push extension type last. It overrides the base type. + if (auto extensionType = type->extensionType()) + stack.push(extensionType); } + return false; } @@ -233,7 +228,7 @@ bool CheckIdentifiers::checkMemberAccess(const QVector<FieldMember> &members, rootType = scope; bool typeFound = - walkViaParentAndAttachedScopes(rootType, [&](QQmlJSScope::ConstPtr type) { + walkRelatedScopes(rootType, [&](QQmlJSScope::ConstPtr type) { const auto typeProperties = type->ownProperties(); const auto typeIt = typeProperties.find(access.m_name); if (typeIt != typeProperties.end()) { |