aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFriedemann Kleint <Friedemann.Kleint@qt.io>2024-05-02 13:32:39 +0200
committerFriedemann Kleint <Friedemann.Kleint@qt.io>2024-05-02 14:57:20 +0200
commitb9a9a890e94d4cd7219735ff2414814d4d7dcf91 (patch)
treef95baecb8d2bc1895341444e0e98f82dab7597f2
parentb782c3054fe28f6f23ecab064a700f1da917d88f (diff)
Fix class hierarchies not working with legacy qmlRegisterType()
In order to work with the new QML registration code, the legacy qmlRegisterType() function set QMetaClassInfo items on the meta object of the QObject type to be registered. This caused the meta object to be recreated in PySide's dynamic meta object builder, breaking the class inheritance information. To fix this, use a separate dummy meta object to provide the information. Amends 91bf9aa10faad14de557136664f58005c935d11c. Pick-to: 6.7 Fixes: PYSIDE-2709 Change-Id: Icbd56759279bc8fcf89705af013db560babe4065 Reviewed-by: Shyamnath Premnadh <Shyamnath.Premnadh@qt.io> Reviewed-by: Cristian Maureira-Fredes <cristian.maureira-fredes@qt.io>
-rw-r--r--sources/pyside6/libpysideqml/pysideqmlregistertype.cpp51
-rw-r--r--sources/pyside6/libpysideqml/pysideqmluncreatable.cpp7
-rw-r--r--sources/pyside6/libpysideqml/pysideqmluncreatable.h3
-rw-r--r--sources/pyside6/tests/QtQml/CMakeLists.txt1
-rw-r--r--sources/pyside6/tests/QtQml/qmlregistertype_test.py53
-rw-r--r--sources/pyside6/tests/QtQml/qmlregistertype_test.qml7
6 files changed, 108 insertions, 14 deletions
diff --git a/sources/pyside6/libpysideqml/pysideqmlregistertype.cpp b/sources/pyside6/libpysideqml/pysideqmlregistertype.cpp
index 618d621bd..223c6eaa3 100644
--- a/sources/pyside6/libpysideqml/pysideqmlregistertype.cpp
+++ b/sources/pyside6/libpysideqml/pysideqmlregistertype.cpp
@@ -28,6 +28,7 @@
#include <QtQml/QJSValue>
#include <QtQml/QQmlListProperty>
#include <private/qqmlmetatype_p.h>
+#include <private/qmetaobjectbuilder_p.h>
#include <memory>
@@ -191,19 +192,15 @@ namespace PySide::Qml {
// Modern (6.7) type registration using RegisterTypeAndRevisions
// and information set to QMetaClassInfo.
-static int qmlRegisterType(PyObject *pyObj, PyObject *pyClassInfoObj,
- const ImportData &importData)
+static int qmlRegisterType(PyObject *pyObj,
+ const ImportData &importData,
+ const QMetaObject *metaObject,
+ const QMetaObject *classInfoMetaObject = nullptr)
{
- using namespace Shiboken;
-
PyTypeObject *pyObjType = reinterpret_cast<PyTypeObject *>(pyObj);
- if (!isQObjectDerived(pyObjType, true))
- return -1;
- const QMetaObject *metaObject = PySide::retrieveMetaObject(pyObjType);
- Q_ASSERT(metaObject);
- const QMetaObject *classInfoMetaObject = pyObj == pyClassInfoObj
- ? metaObject : PySide::retrieveMetaObject(pyClassInfoObj);
+ if (classInfoMetaObject == nullptr)
+ classInfoMetaObject = metaObject;
// Register as simple QObject rather than Qt Quick item.
// Incref the type object, don't worry about decref'ing it because
@@ -270,18 +267,44 @@ static int qmlRegisterType(PyObject *pyObj, PyObject *pyClassInfoObj,
return qmlTypeId;
}
+static int qmlRegisterType(PyObject *pyObj, PyObject *pyClassInfoObj,
+ const ImportData &importData)
+{
+ PyTypeObject *pyObjType = reinterpret_cast<PyTypeObject *>(pyObj);
+ if (!isQObjectDerived(pyObjType, true))
+ return -1;
+
+ const QMetaObject *metaObject = PySide::retrieveMetaObject(pyObjType);
+ Q_ASSERT(metaObject);
+ const QMetaObject *classInfoMetaObject = pyObj == pyClassInfoObj
+ ? metaObject : PySide::retrieveMetaObject(pyClassInfoObj);
+ return qmlRegisterType(pyObj, importData, metaObject, classInfoMetaObject);
+}
+
// Legacy (pre 6.7) compatibility helper for the free register functions.
int qmlRegisterType(PyObject *pyObj, const char *uri, int versionMajor, int versionMinor,
const char *qmlName, const char *noCreationReason,
bool creatable)
{
auto *type = checkTypeObject(pyObj, "qmlRegisterType()");
- if (type == nullptr || !PySide::isQObjectDerived(type, true)
- || !setClassInfo(type, qmlElementKey, qmlName))
+ if (type == nullptr || !PySide::isQObjectDerived(type, true))
return -1;
+
+ const QMetaObject *metaObject = PySide::retrieveMetaObject(type);
+ Q_ASSERT(metaObject);
+
+ // PYSIDE-2709: Use a separate QMetaObject for the class information
+ // as modifying metaObject breaks inheritance.
+ QMetaObjectBuilder classInfobuilder(&QObject::staticMetaObject);
+ classInfobuilder.addClassInfo(qmlElementKey, qmlName);
if (!creatable)
- setUncreatableClassInfo(type, noCreationReason);
- return qmlRegisterType(pyObj, pyObj, {uri, versionMajor, versionMinor});
+ setUncreatableClassInfo(&classInfobuilder, noCreationReason);
+ auto *classInfoMetaObject = classInfobuilder.toMetaObject();
+
+ const int qmlTypeId = qmlRegisterType(pyObj, {uri, versionMajor, versionMinor},
+ metaObject, classInfoMetaObject);
+ free(classInfoMetaObject);
+ return qmlTypeId;
}
// Singleton helpers
diff --git a/sources/pyside6/libpysideqml/pysideqmluncreatable.cpp b/sources/pyside6/libpysideqml/pysideqmluncreatable.cpp
index 55b15ba5b..7c0f6b8ff 100644
--- a/sources/pyside6/libpysideqml/pysideqmluncreatable.cpp
+++ b/sources/pyside6/libpysideqml/pysideqmluncreatable.cpp
@@ -10,6 +10,7 @@
#include <sbkcppstring.h>
#include <QtCore/qbytearray.h>
+#include <private/qmetaobjectbuilder_p.h>
using namespace Qt::StringLiterals;
@@ -109,3 +110,9 @@ void setUncreatableClassInfo(PyTypeObject *type, const QByteArray &reason)
{"QML.Creatable"_ba, "false"_ba},
{"QML.UncreatableReason"_ba, reason} });
}
+
+void setUncreatableClassInfo(QMetaObjectBuilder *builder, const QByteArray &reason)
+{
+ builder->addClassInfo("QML.Creatable", "false");
+ builder->addClassInfo("QML.UncreatableReason", reason);
+}
diff --git a/sources/pyside6/libpysideqml/pysideqmluncreatable.h b/sources/pyside6/libpysideqml/pysideqmluncreatable.h
index 772ad4ccb..8a8adb3c8 100644
--- a/sources/pyside6/libpysideqml/pysideqmluncreatable.h
+++ b/sources/pyside6/libpysideqml/pysideqmluncreatable.h
@@ -8,6 +8,8 @@
#include <QtCore/QByteArray>
+QT_FORWARD_DECLARE_CLASS(QMetaObjectBuilder)
+
// The QmlUncreatable decorator modifies QmlElement to register an uncreatable
// type. Due to the (reverse) execution order of decorators, it needs to follow
// QmlElement.
@@ -19,5 +21,6 @@ extern "C"
void initQmlUncreatable(PyObject *module);
void setUncreatableClassInfo(PyTypeObject *type, const QByteArray &reason);
+void setUncreatableClassInfo(QMetaObjectBuilder *builder, const QByteArray &reason);
#endif // PYSIDEQMLUNCREATABLE_H
diff --git a/sources/pyside6/tests/QtQml/CMakeLists.txt b/sources/pyside6/tests/QtQml/CMakeLists.txt
index 720f0ef99..30bf7e786 100644
--- a/sources/pyside6/tests/QtQml/CMakeLists.txt
+++ b/sources/pyside6/tests/QtQml/CMakeLists.txt
@@ -17,6 +17,7 @@ PYSIDE_TEST(bug_997.py)
PYSIDE_TEST(bug_1029.py)
PYSIDE_TEST(groupedproperty.py)
PYSIDE_TEST(listproperty.py)
+PYSIDE_TEST(qmlregistertype_test.py)
PYSIDE_TEST(qqmlapplicationengine_test.py)
PYSIDE_TEST(qqmlnetwork_test.py)
PYSIDE_TEST(qqmlcomponent_test.py)
diff --git a/sources/pyside6/tests/QtQml/qmlregistertype_test.py b/sources/pyside6/tests/QtQml/qmlregistertype_test.py
new file mode 100644
index 000000000..0042d6fd3
--- /dev/null
+++ b/sources/pyside6/tests/QtQml/qmlregistertype_test.py
@@ -0,0 +1,53 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+import os
+import sys
+import unittest
+
+from pathlib import Path
+sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
+from init_paths import init_test_paths
+init_test_paths(False)
+
+from helper.usesqapplication import UsesQApplication
+
+
+from PySide6.QtCore import QCoreApplication, QObject # noqa: F401
+from PySide6.QtQml import QQmlApplicationEngine, qmlRegisterType
+
+
+class BaseClass(QObject):
+ def __init__(self, p=None):
+ super().__init__(p)
+
+
+class ChildClass(BaseClass):
+ def __init__(self, p=None):
+ super().__init__(p)
+
+
+class TestQmlRegisterType(UsesQApplication):
+ """Test the legacy QML register functions."""
+
+ def test(self):
+ qmlRegisterType(BaseClass, 'test', 1, 0, 'BaseClass')
+ qmlRegisterType(ChildClass, 'test', 1, 0, 'ChildClass')
+ # PYSIDE-2709: qmlRegisterType() would set additional class info
+ # on the meta objects for registration which caused another meta
+ # object to be created, breaking inheritance.
+ child = ChildClass()
+ base = BaseClass()
+ self.assertTrue(child.metaObject().inherits(base.metaObject()))
+
+ engine = QQmlApplicationEngine()
+ file = Path(__file__).resolve().parent / 'qmlregistertype_test.qml'
+
+ engine.load(file)
+ rootObjects = engine.rootObjects()
+ self.assertTrue(rootObjects)
+ self.assertTrue(type(rootObjects[0]), ChildClass)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside6/tests/QtQml/qmlregistertype_test.qml b/sources/pyside6/tests/QtQml/qmlregistertype_test.qml
new file mode 100644
index 000000000..108bb84b1
--- /dev/null
+++ b/sources/pyside6/tests/QtQml/qmlregistertype_test.qml
@@ -0,0 +1,7 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+import test
+
+ChildClass {
+}