diff options
-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()) { |