diff options
32 files changed, 659 insertions, 231 deletions
diff --git a/README.pyside6.md b/README.pyside6.md index 8c70b1c8f..9178660d5 100644 --- a/README.pyside6.md +++ b/README.pyside6.md @@ -88,7 +88,7 @@ and [join our community](https://wiki.qt.io/Qt_for_Python#Community)! ### Licensing -PySide6 is available under both Open Source (LGPLv3/GPLv2) and commercial +PySide6 is available under both Open Source (LGPLv3/GPLv3) and commercial license. Using PyPi is the recommended installation source, because the content of the wheels is valid for both cases. For more information, refer to the [Qt Licensing page](https://www.qt.io/licensing/). diff --git a/README.pyside6_addons.md b/README.pyside6_addons.md index 3247a550d..e6044c4a4 100644 --- a/README.pyside6_addons.md +++ b/README.pyside6_addons.md @@ -65,7 +65,7 @@ and [join our community](https://wiki.qt.io/Qt_for_Python#Community)! ### Licensing -PySide6 is available under both Open Source (LGPLv3/GPLv2) and commercial +PySide6 is available under both Open Source (LGPLv3/GPLv3) and commercial license. Using PyPi is the recommended installation source, because the content of the wheels is valid for both cases. For more information, refer to the [Qt Licensing page](https://www.qt.io/licensing/). diff --git a/README.pyside6_essentials.md b/README.pyside6_essentials.md index ef0376e43..7f96c19b1 100644 --- a/README.pyside6_essentials.md +++ b/README.pyside6_essentials.md @@ -51,7 +51,7 @@ and [join our community](https://wiki.qt.io/Qt_for_Python#Community)! ### Licensing -PySide6 is available under both Open Source (LGPLv3/GPLv2) and commercial +PySide6 is available under both Open Source (LGPLv3/GPLv3) and commercial license. Using PyPi is the recommended installation source, because the content of the wheels is valid for both cases. For more information, refer to the [Qt Licensing page](https://www.qt.io/licensing/). diff --git a/README.pyside6_examples.md b/README.pyside6_examples.md index ffa7d83d5..15e318151 100644 --- a/README.pyside6_examples.md +++ b/README.pyside6_examples.md @@ -28,7 +28,7 @@ and [join our community](https://wiki.qt.io/Qt_for_Python#Community)! ### Licensing -PySide6 is available under both Open Source (LGPLv3/GPLv2) and commercial +PySide6 is available under both Open Source (LGPLv3/GPLv3) and commercial licenses. Using PyPi is the recommended installation source, because the content of the wheels is valid for both cases. For more information, refer to the [Qt Licensing page](https://www.qt.io/licensing/). diff --git a/coin/instructions/find_path_to_msvc_compiler.yaml b/coin/instructions/find_path_to_msvc_compiler.yaml index 8f9bed82f..015a86eab 100644 --- a/coin/instructions/find_path_to_msvc_compiler.yaml +++ b/coin/instructions/find_path_to_msvc_compiler.yaml @@ -18,6 +18,13 @@ instructions: condition: property property: host.compiler equals_value: MSVC2019 + - type: EnvironmentVariable + variableName: VC_SCRIPT + variableValue: "\\Program Files\\Microsoft Visual Studio\\2022\\Professional\\VC\\Auxiliary\\Build\\vcvarsall.bat" + enable_if: + condition: property + property: host.compiler + equals_value: MSVC2022 - type: WriteFile fileContents: "call \"{{.Env.VC_SCRIPT}}\" {{.Env.TARGET_ARCHITECTURE}} \r\ncmd /c %*" filename: "c:\\users\\qt\\MSVC.bat" diff --git a/doc/changelogs/changes-6.7.1 b/doc/changelogs/changes-6.7.1 new file mode 100644 index 000000000..66263ed80 --- /dev/null +++ b/doc/changelogs/changes-6.7.1 @@ -0,0 +1,61 @@ +Qt for Python 6.7.1 is a bug-fix release. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +https://doc.qt.io/qtforpython/ + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* PySide6 * +**************************************************************************** + + - [PYSIDE-487] A number of missing classes have been added. + - [PYSIDE-2629] Tooling: pyside6-qsb, pyside6-balsam and pyside6-balsamui + have been added. + - [PYSIDE-2644] QtAsyncio: An issue with tasks with loop not cancelling + has been fixed. + - [PYSIDE-2663] A crash when browsing https://outlook.com has been fixed. + - [PYSIDE-2665] A syntax error in the pyi-files has been fixed. + - [PYSIDE-2668] The comparison of QOperatingSystemVersion.OSType has been + fixed. + - [PYSIDE-2675] Lazy Load: An issue with polymorphic classes has been + fixed. + - [PYSIDE-2676] A crash with Python 3.12 when creating classes from + meta classes has been fixed. + - [PYSIDE-2685] An error in the pyi-files related to the import of + NoneType has been fixed. + - [PYSIDE-2686] Missing imports for types of return values + have been added to the pyi-files. + - [PYSIDE-2698] A crash when querying the size of QtQml.ListProperty + has been fixed and documentation for QtQml.ListProperty + has been added. + - [PYSIDE-2705] Warnings about failures of QObject.disconnect() can + now be suppressed. + - [PYSIDE-2709] A bug using legacy qmlRegisterType() for class hierarchies + has been fixed. + - [QTBUG-123997] Multimedia: The renaming of the namespace QAudio to + QtAudio has been undone following a revert in Qt. + +**************************************************************************** +* Shiboken6 * +**************************************************************************** + + - [PYSIDE-2590] An attribute for global inline namespace scopes has been + added. + - [PYSIDE-2602] Generate Python override code for added virtuals + - [PYSIDE-2602] Support for virtual functions with return type + modifications has been added and the function + QWebEnginePage.javaScriptPrompt() + has been fixed accordingly. + - [PYSIDE-2675] A code snippet placeholder for the base class for + polymorphic-id-expressions has been added, fixing + a potentially undefined behavior when using the + derived classes. diff --git a/examples/quickcontrols/filesystemexplorer/main.py b/examples/quickcontrols/filesystemexplorer/main.py index 1477023e9..8fad951cb 100644 --- a/examples/quickcontrols/filesystemexplorer/main.py +++ b/examples/quickcontrols/filesystemexplorer/main.py @@ -22,7 +22,7 @@ if __name__ == '__main__': app.setOrganizationName("QtProject") app.setApplicationName("File System Explorer") app.setApplicationVersion(qVersion()) - app.setWindowIcon(QIcon("FileSystemModule/icons/app_icon.svg")) + app.setWindowIcon(QIcon(sys.path[0] + "/FileSystemModule/icons/app_icon.svg")) parser = QCommandLineParser() parser.setApplicationDescription("Qt Filesystemexplorer Example") diff --git a/sources/pyside6/libpyside/pysidesignal.cpp b/sources/pyside6/libpyside/pysidesignal.cpp index c93cbadfb..774837e5b 100644 --- a/sources/pyside6/libpyside/pysidesignal.cpp +++ b/sources/pyside6/libpyside/pysidesignal.cpp @@ -664,10 +664,10 @@ static PyObject *signalInstanceGetItem(PyObject *self, PyObject *key) static inline void warnDisconnectFailed(PyObject *aSlot, const QByteArray &signature) { if (PyErr_Occurred() != nullptr) { // avoid "%S" invoking str() when an error is set. - PyErr_WarnFormat(PyExc_RuntimeError, 0, "Failed to disconnect (%s) from signal \"%s\".", + PyErr_WarnFormat(PyExc_RuntimeWarning, 0, "Failed to disconnect (%s) from signal \"%s\".", Py_TYPE(aSlot)->tp_name, signature.constData()); } else { - PyErr_WarnFormat(PyExc_RuntimeError, 0, "Failed to disconnect (%S) from signal \"%s\".", + PyErr_WarnFormat(PyExc_RuntimeWarning, 0, "Failed to disconnect (%S) from signal \"%s\".", aSlot, signature.constData()); } } diff --git a/sources/pyside6/libpysideqml/pysideqmllistproperty.cpp b/sources/pyside6/libpysideqml/pysideqmllistproperty.cpp index 97d6ce91b..75bb5af96 100644 --- a/sources/pyside6/libpysideqml/pysideqmllistproperty.cpp +++ b/sources/pyside6/libpysideqml/pysideqmllistproperty.cpp @@ -169,7 +169,7 @@ qsizetype propListCount(QQmlListProperty<QObject> *propList) return 0; } - int cppResult = 0; + qsizetype cppResult = 0; auto *converter = Shiboken::Conversions::PrimitiveTypeConverter<qsizetype>(); if (auto *pythonToCpp = Shiboken::Conversions::isPythonToCppConvertible(converter, retVal)) pythonToCpp(retVal, &cppResult); 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/listproperty.py b/sources/pyside6/tests/QtQml/listproperty.py index 8916aefe5..884600d29 100644 --- a/sources/pyside6/tests/QtQml/listproperty.py +++ b/sources/pyside6/tests/QtQml/listproperty.py @@ -7,11 +7,25 @@ import unittest from pathlib import Path sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) -from init_paths import init_test_paths +from init_paths import init_test_paths # noqa: E402 init_test_paths(False) -from PySide6.QtCore import QObject -from PySide6.QtQml import ListProperty +from helper.usesqapplication import UsesQApplication # noqa: E402, F401 + +from PySide6.QtCore import QObject, QUrl, Property, qInstallMessageHandler # noqa: E402 +from PySide6.QtQml import ListProperty, QmlElement # noqa: E402 +from PySide6.QtQuick import QQuickView # noqa: E402 + + +QML_IMPORT_NAME = "test.ListPropertyTest" +QML_IMPORT_MAJOR_VERSION = 1 + +output_messages = [] + + +def message_handler(mode, context, message): + global output_messages + output_messages.append(f"{message}") class InheritsQObject(QObject): @@ -22,7 +36,46 @@ def dummyFunc(): pass -class TestListProperty(unittest.TestCase): +@QmlElement +class Person(QObject): + def __init__(self, parent=None): + super().__init__(parent=None) + self._name = '' + self._friends = [] + + def appendFriend(self, friend): + self._friends.append(friend) + + def friendCount(self): + return len(self._friends) + + def friend(self, index): + return self._friends[index] + + def removeLastItem(self): + if len(self._friends) > 0: + self._friends.pop() + + def replace(self, index, friend): + if 0 <= index < len(self._friends): + self._friends[index] = friend + + def clear(self): + self._friends.clear() + + @Property(str, final=True) + def name(self): + return self._name + + @name.setter + def name(self, value): + self._name = value + + friends = ListProperty(QObject, append=appendFriend, count=friendCount, at=friend, + removeLast=removeLastItem, replace=replace, clear=clear) + + +class TestListProperty(UsesQApplication): def testIt(self): # Verify that type checking works properly @@ -31,7 +84,7 @@ class TestListProperty(unittest.TestCase): try: ListProperty(QObject) ListProperty(InheritsQObject) - except: + except Exception: type_check_error = True self.assertFalse(type_check_error) @@ -47,21 +100,37 @@ class TestListProperty(unittest.TestCase): method_check_error = False try: - ListProperty(QObject, append=None, at=None, count=None, replace=None, clear=None, removeLast=None) # Explicitly setting None + ListProperty(QObject, append=None, at=None, count=None, replace=None, clear=None, + removeLast=None) # Explicitly setting None ListProperty(QObject, append=dummyFunc) ListProperty(QObject, count=dummyFunc, at=dummyFunc) - except: + except Exception: method_check_error = True self.assertFalse(method_check_error) try: - ListPropery(QObject, append=QObject()) - except: + ListProperty(QObject, append=QObject()) + except Exception: method_check_error = True self.assertTrue(method_check_error) + def testListPropParameters(self): + global output_messages + qInstallMessageHandler(message_handler) + view = QQuickView() + file = Path(__file__).resolve().parent / 'listproperty.qml' + self.assertTrue(file.is_file()) + view.setSource(QUrl.fromLocalFile(file)) + view.show() + self.assertEqual(output_messages[0], "List length: 3") + self.assertEqual(output_messages[1], "First element: Alice") + self.assertEqual(output_messages[2], "Removing last item: Charlie") + self.assertEqual(output_messages[3], "Replacing last item: Bob") + self.assertEqual(output_messages[4], "Replaced last item: David") + self.assertEqual(output_messages[5], "List length after clearing: 0") + if __name__ == '__main__': unittest.main() diff --git a/sources/pyside6/tests/QtQml/listproperty.qml b/sources/pyside6/tests/QtQml/listproperty.qml new file mode 100644 index 000000000..7b71e30ba --- /dev/null +++ b/sources/pyside6/tests/QtQml/listproperty.qml @@ -0,0 +1,50 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick 2.0 +import test.ListPropertyTest + +Rectangle { + width: 360 + height: 360 + + Person { + id: person + friends: [ + Person{ + name: "Alice" + }, + Person{ + name: "Bob" + }, + Person{ + name: "Charlie" + } + ] + } + + Person{ + id: david + name: "David" + } + + Component.onCompleted: { + // Access the length of the list + console.log("List length: " + person.friends.length); + + // Access the first element of the list + console.log("First element: " + person.friends[0].name); + + // Remove the last item of the list + console.log("Removing last item: " + person.friends.pop().name); + + // Repalce the last item of the list + console.log("Replacing last item: " + person.friends[person.friends.length - 1].name); + person.friends[person.friends.length - 1] = david; + console.log("Replaced last item: " + person.friends[person.friends.length - 1].name); + + // Clear the list + person.friends = []; + console.log("List length after clearing: " + person.friends.length); + } +} 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 { +} diff --git a/sources/shiboken6/ApiExtractor/abstractmetabuilder.cpp b/sources/shiboken6/ApiExtractor/abstractmetabuilder.cpp index 50ef1b94a..22f6267a7 100644 --- a/sources/shiboken6/ApiExtractor/abstractmetabuilder.cpp +++ b/sources/shiboken6/ApiExtractor/abstractmetabuilder.cpp @@ -1574,7 +1574,8 @@ bool AbstractMetaBuilderPrivate::setupInheritance(const AbstractMetaClassPtr &me &info, &baseContainerType); if (templ) { setupInheritance(templ); - inheritTemplate(metaClass, templ, info); + if (!inheritTemplate(metaClass, templ, info)) + return false; metaClass->typeEntry()->setBaseContainerType(templ->typeEntry()); return true; } @@ -3143,54 +3144,81 @@ AbstractMetaClassPtr return result; } + +static std::optional<AbstractMetaType> + inheritTemplateParameter(const AbstractMetaClassPtr &subclass, + const AbstractMetaClassCPtr &templateClass, + const TypeInfo &info, QString *errorMessage) +{ + QString typeName = info.qualifiedName().join("::"_L1); + TypeDatabase *typeDb = TypeDatabase::instance(); + TypeEntryPtr t; + // Check for a non-type template integer parameter, that is, for a base + // "template <int R, int C> Matrix<R, C>" and subclass + // "typedef Matrix<2,3> Matrix2x3;". If so, create dummy entries of + // EnumValueTypeEntry for the integer values encountered on the fly. + if (isNumber(typeName)) { + t = typeDb->findType(typeName); + if (!t) { + auto parent = typeSystemTypeEntry(subclass->typeEntry()); + t = TypeDatabase::instance()->addConstantValueTypeEntry(typeName, parent); + } + } else { + QStringList possibleNames; + possibleNames << subclass->qualifiedCppName() + "::"_L1 + typeName; + possibleNames << templateClass->qualifiedCppName() + "::"_L1 + typeName; + if (subclass->enclosingClass()) + possibleNames << subclass->enclosingClass()->qualifiedCppName() + "::"_L1 + typeName; + possibleNames << typeName; + + for (const QString &possibleName : std::as_const(possibleNames)) { + t = typeDb->findType(possibleName); + if (t) + break; + } + } + + if (!t) { + *errorMessage = msgIgnoringTemplateParameter(typeName, + "The corresponding type was not found in the typesystem."); + return std::nullopt; + } + + if (t->isContainer()) { + *errorMessage = msgIgnoringTemplateParameter(typeName, + "Template inheritance from nested containers is not supported"); + return std::nullopt; + } + AbstractMetaType result(t); + result.setConstant(info.isConstant()); + result.setReferenceType(info.referenceType()); + result.setIndirectionsV(info.indirectionsV()); + result.decideUsagePattern(); + return result; +} + bool AbstractMetaBuilderPrivate::inheritTemplate(const AbstractMetaClassPtr &subclass, const AbstractMetaClassCPtr &templateClass, const TypeInfo &info) { AbstractMetaTypeList templateTypes; + QString errorMessage; for (const TypeInfo &i : info.instantiations()) { - QString typeName = i.qualifiedName().join(u"::"_s); - TypeDatabase *typeDb = TypeDatabase::instance(); - TypeEntryPtr t; - // Check for a non-type template integer parameter, that is, for a base - // "template <int R, int C> Matrix<R, C>" and subclass - // "typedef Matrix<2,3> Matrix2x3;". If so, create dummy entries of - // EnumValueTypeEntry for the integer values encountered on the fly. - if (isNumber(typeName)) { - t = typeDb->findType(typeName); - if (!t) { - auto parent = typeSystemTypeEntry(subclass->typeEntry()); - t = TypeDatabase::instance()->addConstantValueTypeEntry(typeName, parent); - } - } else { - QStringList possibleNames; - possibleNames << subclass->qualifiedCppName() + u"::"_s + typeName; - possibleNames << templateClass->qualifiedCppName() + u"::"_s + typeName; - if (subclass->enclosingClass()) - possibleNames << subclass->enclosingClass()->qualifiedCppName() + u"::"_s + typeName; - possibleNames << typeName; - - for (const QString &possibleName : std::as_const(possibleNames)) { - t = typeDb->findType(possibleName); - if (t) - break; - } - } - - if (t) { - AbstractMetaType temporaryType(t); - temporaryType.setConstant(i.isConstant()); - temporaryType.setReferenceType(i.referenceType()); - temporaryType.setIndirectionsV(i.indirectionsV()); - temporaryType.decideUsagePattern(); - templateTypes << temporaryType; + const auto typeO = inheritTemplateParameter(subclass, templateClass, i, &errorMessage); + if (typeO.has_value()) { + templateTypes.append(typeO.value()); } else { - qCWarning(lcShiboken).noquote().nospace() - << "Ignoring template parameter " << typeName << " from " - << info.toString() << ". The corresponding type was not found in the typesystem."; + errorMessage = msgInheritTemplateIssue(subclass, info, errorMessage); + qCWarning(lcShiboken, "%s", qPrintable(errorMessage)); } } + if (templateTypes.isEmpty()) { + errorMessage = msgInheritTemplateIssue(subclass, info, + "No template parameters could be inherited"_L1); + qCWarning(lcShiboken, "%s", qPrintable(errorMessage)); + return false; + } return inheritTemplate(subclass, templateClass, templateTypes); } diff --git a/sources/shiboken6/ApiExtractor/abstractmetatype.cpp b/sources/shiboken6/ApiExtractor/abstractmetatype.cpp index dcfc74bbb..3ec07509d 100644 --- a/sources/shiboken6/ApiExtractor/abstractmetatype.cpp +++ b/sources/shiboken6/ApiExtractor/abstractmetatype.cpp @@ -543,6 +543,7 @@ void AbstractMetaType::decideUsagePattern() pattern = ObjectPattern; } setTypeUsagePattern(pattern); + Q_ASSERT(pattern != ContainerPattern || !d->m_instantiations.isEmpty()); } bool AbstractMetaTypeData::hasTemplateChildren() const diff --git a/sources/shiboken6/ApiExtractor/messages.cpp b/sources/shiboken6/ApiExtractor/messages.cpp index f9f46f520..b1f0b240e 100644 --- a/sources/shiboken6/ApiExtractor/messages.cpp +++ b/sources/shiboken6/ApiExtractor/messages.cpp @@ -481,6 +481,21 @@ QString msgCannotFindTypeEntryForSmartPointer(const QString &t, const QString &s + u"\" for instantiation of \""_s +smartPointerType + u"\"."_s; } +QString msgInheritTemplateIssue(const AbstractMetaClassPtr &subclass, + const TypeInfo &info, + const QString &what) +{ + return "While inheriting template "_L1 + subclass->name() + + " from "_L1 + info.toString() + ": "_L1 + what; +} + +QString msgIgnoringTemplateParameter(const QString &typeName, + const char *why) +{ + return "Ignoring template parameter "_L1 + typeName + + ": "_L1 + QLatin1StringView(why); +} + QString msgInvalidSmartPointerType(const TypeInfo &i) { return u"Invalid smart pointer type \""_s +i.toString() + u"\"."_s; diff --git a/sources/shiboken6/ApiExtractor/messages.h b/sources/shiboken6/ApiExtractor/messages.h index 2899cbdfa..e3f582b49 100644 --- a/sources/shiboken6/ApiExtractor/messages.h +++ b/sources/shiboken6/ApiExtractor/messages.h @@ -126,6 +126,10 @@ QString msgUnableToTranslateType(const TypeInfo &typeInfo, QString msgCannotFindTypeEntry(const QString &t); QString msgCannotFindTypeEntryForSmartPointer(const QString &t, const QString &smartPointerType); +QString msgInheritTemplateIssue(const AbstractMetaClassPtr &subclass, + const TypeInfo &info, const QString &what); +QString msgIgnoringTemplateParameter(const QString &typeName, + const char *why); QString msgInvalidSmartPointerType(const TypeInfo &i); QString msgCannotFindSmartPointerInstantion(const TypeInfo &i); diff --git a/sources/shiboken6/doc/typediscovery.rst b/sources/shiboken6/doc/typediscovery.rst new file mode 100644 index 000000000..76d3adf7b --- /dev/null +++ b/sources/shiboken6/doc/typediscovery.rst @@ -0,0 +1,145 @@ +.. _typediscovery: + +************** +Type Discovery +************** + +When converting objects which are part of a class hierarchy from a pointer to a +base class, it is expected to get the Python type of the actual, most derived +type, as opposed to C++ which requires a cast for this: + +.. code-block:: python + + def event(self, event): + if event.type() == QEvent.Type.MousePress: + self.do_things(event.position()) + ... + + +.. code-block:: c++ + + bool event(QEvent *event) override + { + if (event->type() == QEvent::MousePress) { + auto *mouseEvent = static_cast<QMouseEvent *>(event); + doThings(mouseEvent->position()); + ... + } + +The process of determining the type of the event is called `type discovery`. + +Shiboken generates code to automatically detect the type. First, it tries to +find a converter for the name obtained by ``typeid(*pointer).name()``. This +should normally work as this name is registered by the binding. If that fails, +it starts walking a type inheritance graph built up in libshiboken to find the +most derived class by using a cast function (``dynamic_cast<>`` by default) to +check. + +For normal class hierarchies with virtual destructors, no special handling +is required since ``typeid()`` usually detects the proper class name. + +Multiple inheritance +==================== + +In case of multiple inheritance in C++, the conversion to the derived class is +not done in case it is not a single-line direct inheritance. For example, in +Qt, the class ``QWidget`` inherits both ``QObject`` (base of the ``QObject`` +hierarchy) and ``QPaintDevice``. + +When calling a function returning a ``QPaintDevice *``, for example +``QPainter.device()``, a Python type representing ``QPaintDevice`` is returned +instead of the underlying widget type. This restriction exists because the +underlying pointer in C++ is a pointer to a ``QPaintDevice *`` and differs from +the pointer to the ``QWidget``. + +Hierarchies of classes with non-virtual destructors +=================================================== + +There are some hierarchies of value-ish C++ classes that do not have virtual +destructors. This makes type discovery based on ``typeid()`` and +``dynamic_cast<>`` impossible. + +Examples in Qt are the ``QStyleOption``-derived or the ``QGradient`` +-derived classes. + +For such classes, some attributes need to be specified on the type entries: + +Primarily, a :ref:`polymorphic-id-expression` attribute +must be specified to be used as a check replacing ``dynamic_cast<>``. + +In addition, a :ref:`polymorphic-name-function` attribute can be specified. +This replaces the type name guess obtained by ``typeid()`` and is mainly a hint +to speed things up by skipping the checks for each type in the inheritance +graph. + +A :ref:`polymorphic-base` attribute identifies the base class of a hierarchy. +It should be given in case the base class inherits from another class to +prevent the logic from going below the base class. + +Using type discovery attributes for class hierarchies with virtual destructors +============================================================================== + +It is possible to use :ref:`polymorphic-id-expression` and +:ref:`polymorphic-name-function` for normal class hierarchies with virtual +destructors as well since they basically replace ``typeid()`` and +``dynamic_cast<>``. This makes sense if expressions can be specified that are +faster than the checks on virtual tables. + +Specifying :ref:`polymorphic-base` can also make sense for generating special +cast functions in case of multiple inheritance. For example, in Qt, +``QWindow``, ``QLayout``, ``QWidget`` are base classes of hierarchies. Since +they all inherit from ``QObject``, indicating the base classes prevents +the logic from using ``QObject`` as a base class. + +.. _typediscovery-attributes: + +Type discovery attributes reference +=================================== + +The following attributes related to type discovery may be be specified on the +:ref:`object-type` or :ref:`value-type` elements: + +.. _polymorphic-id-expression: + +polymorphic-id-expression ++++++++++++++++++++++++++ + +The **polymorphic-id-expression** attribute specifies an expression checking +whether a base class pointer is of the matching type. For example, in a +``virtual eventHandler(BaseEvent *e)`` function, this is used to construct a +Python wrapper matching the derived class (for example, a ``MouseEvent`` or +similar). The attribute value may contain placeholders: + +%1 + Fully qualified class name + +%B + Fully qualified name of the base class (found by base class + search or as indicated by **polymorphic-base**). + +To check for a class inheriting ``BaseEvent``, specify: + +.. code-block:: xml + + <object-type name="MouseEvent" + polymorphic-id-expression="%B->type() == BaseEvent::MouseEvent"/> + +.. _polymorphic-name-function: + +polymorphic-name-function ++++++++++++++++++++++++++ + +The **polymorphic-name-function** attribute specifies the name of a function +returning the type name of a derived class on the base class type entry. +Normally, ``typeid(ptr).name()`` is used for this. + +The function is expected to return ``const char *``. + +.. _polymorphic-base: + +polymorphic-base +++++++++++++++++ + +The boolean **polymorphic-base** attribute indicates whether the class is the +base class of a class hierarchy. It is used for the *%B* placeholder in +**polymorphic-id-expression** and for cast operations in multiple inheritance. diff --git a/sources/shiboken6/doc/typesystem.rst b/sources/shiboken6/doc/typesystem.rst index e1e4fdda2..26f929801 100644 --- a/sources/shiboken6/doc/typesystem.rst +++ b/sources/shiboken6/doc/typesystem.rst @@ -65,3 +65,4 @@ Extra options and Python caveats typesystem_solving_compilation.rst typesystem_specialfunctions.rst + typediscovery.rst diff --git a/sources/shiboken6/doc/typesystem_converters.rst b/sources/shiboken6/doc/typesystem_converters.rst index 7bdabc49c..ab6fba930 100644 --- a/sources/shiboken6/doc/typesystem_converters.rst +++ b/sources/shiboken6/doc/typesystem_converters.rst @@ -233,61 +233,3 @@ Variables & Functions **%CHECKTYPE[CPPTYPE]** Replaced by a |project| type checking function for a Python variable. The C++ type is indicated by ``CPPTYPE``. - - -.. _oldconverters: - -Converting The Old Converters -============================= - -If you use |project| for your bindings, and has defined some type conversions -using the ``Shiboken::Converter`` template, then you must update your converters -to the new scheme. - -Previously your conversion rules were declared in one line, like this: - - -.. code-block:: xml - - <primitive-type name="Complex" target-lang-api-name="PyComplex"> - <include file-name="complex.h" location="global"/> - <conversion-rule file="complex_conversions.h"/> - </primitive-type> - - -And implemented in a separate C++ file, like this: - - -.. code-block:: c++ - - namespace Shiboken { - template<> struct Converter<Complex> - { - static inline bool checkType(PyObject* pyObj) { - return PyComplex_Check(pyObj); - } - static inline bool isConvertible(PyObject* pyObj) { - return PyComplex_Check(pyObj); - } - static inline PyObject* toPython(void* cppobj) { - return toPython(*reinterpret_cast<Complex*>(cppobj)); - } - static inline PyObject* toPython(const Complex& cpx) { - return PyComplex_FromDoubles(cpx.real(), cpx.imag()); - } - static inline Complex toCpp(PyObject* pyobj) { - double real = PyComplex_RealAsDouble(pyobj); - double imag = PyComplex_ImagAsDouble(pyobj); - return Complex(real, imag); - } - }; - } - - -In this case, the parts of the implementation that will be used in the new -conversion-rule are the ones in the two last method -``static inline PyObject* toPython(const Complex& cpx)`` and -``static inline Complex toCpp(PyObject* pyobj)``. The ``isConvertible`` method -is gone, and the ``checkType`` is now an attribute of the :ref:`add-conversion <add-conversion>` -tag. Refer back to the first example in this page and you will be able to -correlate the above template with the new scheme of conversion rule definition. diff --git a/sources/shiboken6/doc/typesystem_specifying_types.rst b/sources/shiboken6/doc/typesystem_specifying_types.rst index 66e68ae2b..f65b79bb4 100644 --- a/sources/shiboken6/doc/typesystem_specifying_types.rst +++ b/sources/shiboken6/doc/typesystem_specifying_types.rst @@ -536,37 +536,8 @@ type system has this attribute set, the heuristics will be applied to all classes. In shiboken 7, it will be mandatory to set the attribute. -The *optional* **polymorphic-id-expression** attribute specifies an -expression checking whether a base class pointer is of the matching -type. For example, in a ``virtual eventHandler(BaseEvent *e)`` -function, this is used to construct a Python wrapper matching -the derived class (for example, a ``MouseEvent`` or similar). -The attribute value may contain placeholders: - -%1 - Fully qualified class name - -%B - Fully qualified name of the base class (found by base class - search or as indicated by **polymorphic-base**). - -To check for a class inheriting ``BaseEvent``, specify: - -.. code-block:: xml - - <object-type name="MouseEvent" - polymorphic-id-expression="%B->type() == BaseEvent::MouseEvent"/> - -The *optional* **polymorphic-name-function** specifies the name of a -function returning the type name of a derived class on the base class -type entry. Normally, ``typeid(ptr).name()`` is used for this. -However, this fails if the type hierarchy does not have virtual functions. -In this case, a function is required which typically decides depending -on some type enumeration. - -The *optional* **polymorphic-base** attribute indicates -whether the class is the base class of a class hierarchy -(used for the *%B* placeholder in **polymorphic-id-expression**). +For the *optional* **polymorphic-id-expression**, **polymorphic-name-function** +and **polymorphic-base** attributes, see :ref:`typediscovery-attributes`. interface-type ^^^^^^^^^^^^^^ diff --git a/sources/shiboken6/generator/shiboken/cppgenerator.cpp b/sources/shiboken6/generator/shiboken/cppgenerator.cpp index ad4071c35..84d3c93d3 100644 --- a/sources/shiboken6/generator/shiboken/cppgenerator.cpp +++ b/sources/shiboken6/generator/shiboken/cppgenerator.cpp @@ -61,6 +61,7 @@ static const char shibokenErrorsOccurred[] = "Shiboken::Errors::occurred() != nu static constexpr auto virtualMethodStaticReturnVar = "result"_L1; static constexpr auto sbkObjectTypeF = "SbkObject_TypeF()"_L1; +static const char initInheritanceFunction[] = "initInheritance"; static QString mangleName(QString name) { @@ -1638,7 +1639,7 @@ void CppGenerator::writeConverterFunctions(TextStream &s, const AbstractMetaClas << "if (pyOut) {\n" << indent << "Py_INCREF(pyOut);\nreturn pyOut;\n" << outdent << "}\n" - << "bool changedTypeName = false;\n" + << "bool exactType = false;\n" << "auto *tCppIn = reinterpret_cast<const " << typeName << R"( *>(cppIn); const char *typeName = )"; @@ -1648,15 +1649,11 @@ const char *typeName = )"; else c << nameFunc << "(tCppIn);\n"; c << R"(auto *sbkType = Shiboken::ObjectType::typeForTypeName(typeName); -if (sbkType != nullptr && Shiboken::ObjectType::hasSpecialCastFunction(sbkType)) { - typeName = Shiboken::typeNameOf(typeid(*tCppIn).name()); - changedTypeName = true; -} +if (sbkType != nullptr && Shiboken::ObjectType::hasSpecialCastFunction(sbkType)) + exactType = true; )" << "PyObject *result = Shiboken::Object::newObject(" << cpythonType - << R"(, const_cast<void *>(cppIn), false, /* exactType */ changedTypeName, typeName); -if (changedTypeName) - delete [] typeName; + << R"(, const_cast<void *>(cppIn), false, exactType, typeName); return result;)"; } std::swap(targetTypeName, sourceTypeName); @@ -4260,6 +4257,12 @@ QString CppGenerator::writeContainerConverterInitialization(TextStream &s, return converter; } +QString CppGenerator::typeInitStruct(const TypeEntryCPtr &te) +{ + return cppApiVariableName(te->targetLangPackage()) + u'[' + + getTypeIndexVariableName(te) + u']'; +} + void CppGenerator::writeExtendedConverterInitialization(TextStream &s, const TypeEntryCPtr &externalType, const AbstractMetaClassCList &conversions) @@ -4267,15 +4270,13 @@ void CppGenerator::writeExtendedConverterInitialization(TextStream &s, s << "// Extended implicit conversions for " << externalType->qualifiedTargetLangName() << ".\n"; for (const auto &sourceClass : conversions) { - const QString converterVar = cppApiVariableName(externalType->targetLangPackage()) + u'[' - + getTypeIndexVariableName(externalType) + u']'; QString sourceTypeName = fixedCppTypeName(sourceClass->typeEntry()); QString targetTypeName = fixedCppTypeName(externalType); QString toCpp = pythonToCppFunctionName(sourceTypeName, targetTypeName); QString isConv = convertibleToCppFunctionName(sourceTypeName, targetTypeName); if (!externalType->isPrimitive()) s << cpythonTypeNameExt(externalType) << ";\n"; - writeAddPythonToCppConversion(s, converterVar, toCpp, isConv); + writeAddPythonToCppConversion(s, typeInitStruct(externalType), toCpp, isConv); } } @@ -5473,6 +5474,27 @@ QStringList CppGenerator::pyBaseTypes(const AbstractMetaClassCPtr &metaClass) return result; } +void CppGenerator::writeInitInheritance(TextStream &s) const +{ + s << "static void " << initInheritanceFunction << "()\n{\n" << indent + << "auto &bm = Shiboken::BindingManager::instance();\n" + << sbkUnusedVariableCast("bm"); + for (const auto &cls : api().classes()){ + auto te = cls->typeEntry(); + if (shouldGenerate(te)) { + const auto &baseEntries = pyBaseTypeEntries(cls); + if (!baseEntries.isEmpty()) { + const QString childTypeInitStruct = typeInitStruct(cls->typeEntry()); + for (const auto &baseEntry : baseEntries) { + s << "bm.addClassInheritance(&" << typeInitStruct(baseEntry) << ",\n" + << Pad(' ', 23) << '&' << childTypeInitStruct << ");\n"; + } + } + } + } + s << outdent << "}\n\n"; +} + void CppGenerator::writeClassRegister(TextStream &s, const AbstractMetaClassCPtr &metaClass, const GeneratorContext &classContext, @@ -6337,6 +6359,8 @@ bool CppGenerator::finishGeneration() // PYSIDE-510: Create a signatures string for the introspection feature. writeSignatureStrings(s, signatureStream.toString(), moduleName(), "global functions"); + writeInitInheritance(s); + // Write module init function const QString globalModuleVar = pythonModuleObjectName(); s << "extern \"C\" LIBSHIBOKEN_EXPORT PyObject *PyInit_" @@ -6474,7 +6498,8 @@ bool CppGenerator::finishGeneration() } } - s << "\nif (" << shibokenErrorsOccurred << ") {\n" << indent + s << '\n' << initInheritanceFunction << "();\n" + << "\nif (" << shibokenErrorsOccurred << ") {\n" << indent << "PyErr_Print();\n" << "Py_FatalError(\"can't initialize module " << moduleName() << "\");\n" << outdent << "}\n"; diff --git a/sources/shiboken6/generator/shiboken/cppgenerator.h b/sources/shiboken6/generator/shiboken/cppgenerator.h index 1ad576895..a31c2ca14 100644 --- a/sources/shiboken6/generator/shiboken/cppgenerator.h +++ b/sources/shiboken6/generator/shiboken/cppgenerator.h @@ -395,6 +395,7 @@ private: static void writeSignatureStrings(TextStream &s, const QString &signatures, const QString &arrayName, const char *comment); + void writeInitInheritance(TextStream &s) const; void writeClassRegister(TextStream &s, const AbstractMetaClassCPtr &metaClass, const GeneratorContext &classContext, @@ -480,6 +481,8 @@ private: const AbstractMetaType &type, const ApiExtractorResult &api); void writeSmartPointerConverterInitialization(TextStream &s, const AbstractMetaType &ype) const; + + static QString typeInitStruct(const TypeEntryCPtr &te); static void writeExtendedConverterInitialization(TextStream &s, const TypeEntryCPtr &externalType, const AbstractMetaClassCList &conversions); diff --git a/sources/shiboken6/libshiboken/basewrapper.cpp b/sources/shiboken6/libshiboken/basewrapper.cpp index f34e053ca..0dfd3cff8 100644 --- a/sources/shiboken6/libshiboken/basewrapper.cpp +++ b/sources/shiboken6/libshiboken/basewrapper.cpp @@ -129,31 +129,6 @@ static PyGetSetDef SbkObjectType_tp_getset[] = { static PyTypeObject *createObjectTypeType() { - // PYSIDE-2676: When using the new type extension, we need to use an - // extra meta type that provides the extra size. - // This is a hairy part of Python 3.12 . - // - // The problem here is that we use the type extension both in types - // and also in meta types. This was invisible with extender dicts. - // Please study carefully: - // https://docs.python.org/3/c-api/type.html#c.PyType_Spec.basicsize - - PyType_Slot SbkObjectTypeMeta_Type_slots[] = { - {Py_tp_base, static_cast<void *>(&PyType_Type)}, - {Py_tp_alloc, reinterpret_cast<void *>(PyType_GenericAlloc)}, - {0, nullptr} - }; - - PyType_Spec SbkObjectTypeMeta_Type_spec = { - "1:Shiboken.ObjectTypeMeta", - -long(sizeof(SbkObjectTypePrivate)), - 0, // sizeof(PyMemberDef), not for PyPy without a __len__ defined - Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_TYPE_SUBCLASS, - SbkObjectTypeMeta_Type_slots, - }; - - auto specMeta = &SbkObjectTypeMeta_Type_spec; - PyType_Slot SbkObjectType_Type_slots[] = { {Py_tp_dealloc, reinterpret_cast<void *>(SbkObjectType_tp_dealloc)}, {Py_tp_getattro, reinterpret_cast<void *>(mangled_type_getattro)}, @@ -191,14 +166,9 @@ static PyTypeObject *createObjectTypeType() SbkObjectType_Type_slots, }; - if (_PepRuntimeVersion() >= 0x030C00) { - auto *meta = SbkType_FromSpec(specMeta); - auto spec = &SbkObjectType_Type_spec_312; - return SbkType_FromSpecWithMeta(spec, meta); - } - - auto spec = &SbkObjectType_Type_spec; - return SbkType_FromSpec(spec); + return SbkType_FromSpec(_PepRuntimeVersion() >= 0x030C00 ? + &SbkObjectType_Type_spec_312 : + &SbkObjectType_Type_spec); } PyTypeObject *SbkObjectType_TypeF(void) @@ -1007,11 +977,6 @@ introduceWrapperType(PyObject *enclosingObject, auto *type = SbkType_FromSpecBasesMeta(typeSpec, bases, SbkObjectType_TypeF()); - for (Py_ssize_t i = 0; i < basesSize; ++i) { - auto *st = reinterpret_cast<PyTypeObject *>(PySequence_Fast_GET_ITEM(bases, i)); - BindingManager::instance().addClassInheritance(st, type); - } - auto sotp = PepType_SOTP(type); if (wrapperFlags & DeleteInMainThread) sotp->delete_in_main_thread = 1; diff --git a/sources/shiboken6/libshiboken/bindingmanager.cpp b/sources/shiboken6/libshiboken/bindingmanager.cpp index 60460c6ab..83c927ae5 100644 --- a/sources/shiboken6/libshiboken/bindingmanager.cpp +++ b/sources/shiboken6/libshiboken/bindingmanager.cpp @@ -7,6 +7,7 @@ #include "bindingmanager.h" #include "gilstate.h" #include "helper.h" +#include "sbkmodule.h" #include "sbkstring.h" #include "sbkstaticstrings.h" #include "sbkfeature_base.h" @@ -21,6 +22,29 @@ #include <unordered_map> #include <unordered_set> +// GraphNode for the dependency graph. It keeps a pointer to +// the TypeInitStruct to be able to lazily create the type and hashes +// by the full type name. +struct GraphNode +{ + explicit GraphNode(Shiboken::Module::TypeInitStruct *i) : name(i->fullName), initStruct(i) {} + explicit GraphNode(const char *n) : name(n), initStruct(nullptr) {} // Only for searching + + std::string_view name; + Shiboken::Module::TypeInitStruct *initStruct; + + friend bool operator==(const GraphNode &n1, const GraphNode &n2) { return n1.name == n2.name; } + friend bool operator!=(const GraphNode &n1, const GraphNode &n2) { return n1.name != n2.name; } +}; + +template <> +struct std::hash<GraphNode> { + size_t operator()(const GraphNode &n) const noexcept + { + return std::hash<std::string_view>{}(n.name); + } +}; + namespace Shiboken { @@ -56,28 +80,45 @@ public: } }; -class Graph : public BaseGraph<PyTypeObject *> +class Graph : public BaseGraph<GraphNode> { public: - Graph() = default; + using TypeCptrPair = BindingManager::TypeCptrPair; - BindingManager::TypeCptrPair identifyType(void *cptr, PyTypeObject *type, PyTypeObject *baseType) const; + TypeCptrPair identifyType(void *cptr, PyTypeObject *type, PyTypeObject *baseType) const + { + return identifyType(cptr, GraphNode(type->tp_name), type, baseType); + } bool dumpTypeGraph(const char *fileName) const; + +private: + TypeCptrPair identifyType(void *cptr, const GraphNode &typeNode, PyTypeObject *type, + PyTypeObject *baseType) const; }; -BindingManager::TypeCptrPair Graph::identifyType(void *cptr, PyTypeObject *type, PyTypeObject *baseType) const +Graph::TypeCptrPair Graph::identifyType(void *cptr, + const GraphNode &typeNode, PyTypeObject *type, + PyTypeObject *baseType) const { - auto edgesIt = m_edges.find(type); + assert(typeNode.initStruct != nullptr || type != nullptr); + auto edgesIt = m_edges.find(typeNode); if (edgesIt != m_edges.end()) { const NodeList &adjNodes = edgesIt->second; - for (PyTypeObject *node : adjNodes) { - auto newType = identifyType(cptr, node, baseType); + for (const auto &node : adjNodes) { + auto newType = identifyType(cptr, node, nullptr, baseType); if (newType.first != nullptr) return newType; } } + if (type == nullptr) { + if (typeNode.initStruct->type == nullptr) // Layzily create type + type = Shiboken::Module::get(*typeNode.initStruct); + else + type = typeNode.initStruct->type; + } + auto *sotp = PepType_SOTP(type); if (sotp->type_discovery != nullptr) { if (void *derivedCPtr = sotp->type_discovery(cptr, baseType)) @@ -86,9 +127,8 @@ BindingManager::TypeCptrPair Graph::identifyType(void *cptr, PyTypeObject *type, return {nullptr, nullptr}; } -static void formatDotNode(const char *nameC, std::ostream &file) +static void formatDotNode(std::string_view name, std::ostream &file) { - std::string_view name(nameC); auto lastDot = name.rfind('.'); file << " \"" << name << "\" [ label="; if (lastDot != std::string::npos) { @@ -109,15 +149,15 @@ bool Graph::dumpTypeGraph(const char *fileName) const file << "digraph D {\n"; // Define nodes with short names - for (const auto *node : nodeSet()) - formatDotNode(node->tp_name, file); + for (const auto &node : nodeSet()) + formatDotNode(node.name, file); // Write edges for (const auto &p : m_edges) { - auto *node1 = p.first; + const auto &node1 = p.first; const NodeList &nodeList = p.second; - for (const PyTypeObject *node2 : nodeList) - file << " \"" << node2->tp_name << "\" -> \"" << node1->tp_name << "\"\n"; + for (const auto &node2 : nodeList) + file << " \"" << node2.name << "\" -> \"" << node1.name << "\"\n"; } file << "}\n"; return true; @@ -380,9 +420,10 @@ PyObject *BindingManager::getOverride(const void *cptr, return nullptr; } -void BindingManager::addClassInheritance(PyTypeObject *parent, PyTypeObject *child) +void BindingManager::addClassInheritance(Module::TypeInitStruct *parent, + Module::TypeInitStruct *child) { - m_d->classHierarchy.addEdge(parent, child); + m_d->classHierarchy.addEdge(GraphNode(parent), GraphNode(child)); } BindingManager::TypeCptrPair BindingManager::findDerivedType(void *cptr, PyTypeObject *type) const diff --git a/sources/shiboken6/libshiboken/bindingmanager.h b/sources/shiboken6/libshiboken/bindingmanager.h index 281de2c73..54c4e486a 100644 --- a/sources/shiboken6/libshiboken/bindingmanager.h +++ b/sources/shiboken6/libshiboken/bindingmanager.h @@ -15,6 +15,10 @@ struct SbkObject; namespace Shiboken { +namespace Module { +struct TypeInitStruct; +} + struct DestructorEntry; using ObjectVisitor = void (*)(SbkObject *, void *); @@ -40,7 +44,7 @@ public: SbkObject *retrieveWrapper(const void *cptr); PyObject *getOverride(const void *cptr, PyObject *nameCache[], const char *methodName); - void addClassInheritance(PyTypeObject *parent, PyTypeObject *child); + void addClassInheritance(Module::TypeInitStruct *parent, Module::TypeInitStruct *child); /// Try to find the correct type of cptr via type discovery knowing that it's at least /// of type \p type. If a derived class is found, it returns a cptr cast to the type /// (which may be different in case of multiple inheritance. diff --git a/sources/shiboken6/libshiboken/pep384impl.cpp b/sources/shiboken6/libshiboken/pep384impl.cpp index 4b3759456..f926107e2 100644 --- a/sources/shiboken6/libshiboken/pep384impl.cpp +++ b/sources/shiboken6/libshiboken/pep384impl.cpp @@ -1009,9 +1009,12 @@ long _PepRuntimeVersion() SbkObjectTypePrivate *PepType_SOTP(PyTypeObject *type) { + // PYSIDE-2676: Use the meta type explicitly. + // A derived type would fail the offset calculation. + static auto *meta = SbkObjectType_TypeF(); assert(SbkObjectType_Check(type)); auto *obType = reinterpret_cast<PyObject *>(type); - void *data = PyObject_GetTypeData(obType, Py_TYPE(obType)); + void *data = PyObject_GetTypeData(obType, meta); return reinterpret_cast<SbkObjectTypePrivate *>(data); } @@ -1061,11 +1064,12 @@ static thread_local SbkObjectTypePrivate *SOTP_value{}; SbkObjectTypePrivate *PepType_SOTP(PyTypeObject *type) { + static auto *meta = SbkObjectType_TypeF(); static bool use_312 = _PepRuntimeVersion() >= 0x030C00; assert(SbkObjectType_Check(type)); if (use_312) { auto *obType = reinterpret_cast<PyObject *>(type); - void *data = PepObject_GetTypeData(obType, Py_TYPE(obType)); + void *data = PepObject_GetTypeData(obType, meta); return reinterpret_cast<SbkObjectTypePrivate *>(data); } if (type == SOTP_key) diff --git a/sources/shiboken6/libshiboken/sbknumpy.cpp b/sources/shiboken6/libshiboken/sbknumpy.cpp index 2e1c64d73..b6422e73f 100644 --- a/sources/shiboken6/libshiboken/sbknumpy.cpp +++ b/sources/shiboken6/libshiboken/sbknumpy.cpp @@ -29,10 +29,8 @@ static void initNumPy() // Expanded from macro "import_array" in __multiarray_api.h // Make sure to read about the magic defines PY_ARRAY_UNIQUE_SYMBOL etc., // when changing this or spreading the code over several source files. - if (_import_array() < 0) { + if (_import_array() < 0) PyErr_Print(); - PyErr_Clear(); - } } #endif // HAVE_NUMPY |