aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/qml/qml/qqml.h3
-rw-r--r--src/qmlcompiler/qqmljsscope.cpp36
-rw-r--r--src/qmltyperegistrar/qmltypesclassdescription.cpp9
-rw-r--r--src/qmltyperegistrar/qmltypescreator.cpp22
-rw-r--r--tests/auto/qml/qmllint/data/StaticTest/multi.qmltypes41
-rw-r--r--tests/auto/qml/qmllint/data/StaticTest/qmldir2
-rw-r--r--tests/auto/qml/qmllint/data/multiExtension.qml6
-rw-r--r--tests/auto/qml/qmllint/tst_qmllint.cpp1
-rw-r--r--tests/auto/qml/qmltyperegistrar/tst_qmltyperegistrar.cpp10
-rw-r--r--tests/auto/qml/qmltyperegistrar/tst_qmltyperegistrar.h46
-rw-r--r--tests/auto/qml/qqmllanguage/testtypes.h62
-rw-r--r--tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp21
-rw-r--r--tools/qmllint/checkidentifiers.cpp27
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()) {