aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.pyside6.md2
-rw-r--r--README.pyside6_addons.md2
-rw-r--r--README.pyside6_essentials.md2
-rw-r--r--README.pyside6_examples.md2
-rw-r--r--coin/instructions/find_path_to_msvc_compiler.yaml7
-rw-r--r--doc/changelogs/changes-6.7.161
-rw-r--r--examples/quickcontrols/filesystemexplorer/main.py2
-rw-r--r--sources/pyside6/PySide6/QtGui/typesystem_gui_common.xml2
-rw-r--r--sources/pyside6/PySide6/QtQuick/typesystem_quick.xml2
-rw-r--r--sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml4
-rw-r--r--sources/pyside6/libpyside/pyside.cpp2
-rw-r--r--sources/pyside6/libpyside/pysidesignal.cpp4
-rw-r--r--sources/pyside6/libpysideqml/pysideqmllistproperty.cpp2
-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/QtGui/qbrush_test.py10
-rw-r--r--sources/pyside6/tests/QtQml/CMakeLists.txt1
-rw-r--r--sources/pyside6/tests/QtQml/listproperty.py87
-rw-r--r--sources/pyside6/tests/QtQml/listproperty.qml50
-rw-r--r--sources/pyside6/tests/QtQml/qmlregistertype_test.py53
-rw-r--r--sources/pyside6/tests/QtQml/qmlregistertype_test.qml7
-rw-r--r--sources/shiboken6/ApiExtractor/abstractmetabuilder.cpp106
-rw-r--r--sources/shiboken6/ApiExtractor/abstractmetatype.cpp1
-rw-r--r--sources/shiboken6/ApiExtractor/messages.cpp15
-rw-r--r--sources/shiboken6/ApiExtractor/messages.h4
-rw-r--r--sources/shiboken6/doc/typediscovery.rst145
-rw-r--r--sources/shiboken6/doc/typesystem.rst1
-rw-r--r--sources/shiboken6/doc/typesystem_converters.rst58
-rw-r--r--sources/shiboken6/doc/typesystem_specifying_types.rst33
-rw-r--r--sources/shiboken6/generator/shiboken/cppgenerator.cpp85
-rw-r--r--sources/shiboken6/generator/shiboken/cppgenerator.h3
-rw-r--r--sources/shiboken6/libshiboken/basewrapper.cpp267
-rw-r--r--sources/shiboken6/libshiboken/basewrapper.h45
-rw-r--r--sources/shiboken6/libshiboken/basewrapper_p.h105
-rw-r--r--sources/shiboken6/libshiboken/bindingmanager.cpp73
-rw-r--r--sources/shiboken6/libshiboken/bindingmanager.h6
-rw-r--r--sources/shiboken6/libshiboken/pep384impl.cpp8
-rw-r--r--sources/shiboken6/libshiboken/sbknumpy.cpp4
-rw-r--r--sources/shiboken6/tests/libother/othermultiplederived.h2
-rw-r--r--sources/shiboken6/tests/otherbinding/typediscovery_test.py17
41 files changed, 898 insertions, 443 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/PySide6/QtGui/typesystem_gui_common.xml b/sources/pyside6/PySide6/QtGui/typesystem_gui_common.xml
index dd17952f0..3eee63ba4 100644
--- a/sources/pyside6/PySide6/QtGui/typesystem_gui_common.xml
+++ b/sources/pyside6/PySide6/QtGui/typesystem_gui_common.xml
@@ -2572,7 +2572,7 @@
</extra-includes>
</primitive-type>
- <object-type name="QWindow" delete-in-main-thread="true">
+ <object-type name="QWindow" delete-in-main-thread="true" polymorphic-base="true">
<enum-type name="AncestorMode"/>
<enum-type name="Visibility"/>
<modify-function signature="raise()" rename="raise_"/>
diff --git a/sources/pyside6/PySide6/QtQuick/typesystem_quick.xml b/sources/pyside6/PySide6/QtQuick/typesystem_quick.xml
index 0a24123f4..61aed6005 100644
--- a/sources/pyside6/PySide6/QtQuick/typesystem_quick.xml
+++ b/sources/pyside6/PySide6/QtQuick/typesystem_quick.xml
@@ -53,7 +53,7 @@
<object-type name="QQuickImageResponse"/>
<object-type name="QQuickTransform"/>
- <object-type name="QQuickItem" delete-in-main-thread="true">
+ <object-type name="QQuickItem" delete-in-main-thread="true" polymorphic-base="true">
<value-type name="UpdatePaintNodeData"/>
<enum-type name="Flag" flags="Flags"/>
<enum-type name="ItemChange"/>
diff --git a/sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml b/sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml
index 0669c18f6..774f9dd64 100644
--- a/sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml
+++ b/sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml
@@ -1205,7 +1205,7 @@
<modify-function signature="getColor(const QColor&amp;,QWidget*,const QString&amp;,QFlags&lt;QColorDialog::ColorDialogOption>)" allow-thread="yes"/>
</object-type>
- <object-type name="QLayout">
+ <object-type name="QLayout" polymorphic-base="true">
<inject-code class="native" position="beginning" file="../glue/qtwidgets.cpp"
snippet="qwidget-retrieveobjectname"/>
<inject-code class="native" position="beginning" file="../glue/qtwidgets.cpp" snippet="qlayout-help-functions"/>
@@ -1980,7 +1980,7 @@
<modify-function signature="removeItemWidget(QListWidgetItem*)" allow-thread="yes"/>
</object-type>
- <object-type name="QWidget" delete-in-main-thread="true">
+ <object-type name="QWidget" delete-in-main-thread="true" polymorphic-base="true">
<!-- see QWindow::nativeEvent(), QAbstractNativeEventFilter::nativeEventFilter() -->
<inject-code class="native" position="beginning" file="../glue/qtwidgets.cpp" snippet="qwidget-addaction-glue"/>
<inject-code class="native" position="beginning">
diff --git a/sources/pyside6/libpyside/pyside.cpp b/sources/pyside6/libpyside/pyside.cpp
index d5e815a42..cff74c260 100644
--- a/sources/pyside6/libpyside/pyside.cpp
+++ b/sources/pyside6/libpyside/pyside.cpp
@@ -738,7 +738,7 @@ PyObject *getWrapperForQObject(QObject *cppSelf, PyTypeObject *sbk_type)
}
}
- pyOut = Shiboken::Object::newObject(sbk_type, cppSelf, false, false, typeName(cppSelf));
+ pyOut = Shiboken::Object::newObjectWithHeuristics(sbk_type, cppSelf, false, typeName(cppSelf));
return pyOut;
}
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/QtGui/qbrush_test.py b/sources/pyside6/tests/QtGui/qbrush_test.py
index 800e6f072..69262328b 100644
--- a/sources/pyside6/tests/QtGui/qbrush_test.py
+++ b/sources/pyside6/tests/QtGui/qbrush_test.py
@@ -13,7 +13,7 @@ from init_paths import init_test_paths
init_test_paths(False)
from PySide6.QtCore import Qt
-from PySide6.QtGui import QColor, QBrush
+from PySide6.QtGui import QColor, QBrush, QConicalGradient
from helper.usesqapplication import UsesQApplication
@@ -30,6 +30,14 @@ class Constructor(UsesQApplication):
obj = QBrush(Qt.blue)
self.assertEqual(obj.color(), Qt.blue)
+ def testGradient(self):
+ """Test type discovery on class hierarchies with non-virtual
+ destructors by specifying a polymorphic-id-expression without
+ polymorphic-name-function."""
+ gradient = QConicalGradient()
+ brush = QBrush(gradient)
+ self.assertEqual(type(brush.gradient()), type(gradient))
+
if __name__ == '__main__':
unittest.main()
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 89d636964..29566a272 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-&gt;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-&gt;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..6c9cc5fec 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)
{
@@ -1589,6 +1590,27 @@ void CppGenerator::writeEnumConverterFunctions(TextStream &s, const AbstractMeta
s << '\n';
}
+static void writePointerToPythonConverter(TextStream &c,
+ const AbstractMetaClassCPtr &metaClass,
+ const QString &typeName,
+ const QString &cpythonType)
+{
+ c << "auto *pyOut = reinterpret_cast<PyObject *>(Shiboken::BindingManager::instance().retrieveWrapper(cppIn));\n"
+ << "if (pyOut) {\n" << indent
+ << "Py_INCREF(pyOut);\nreturn pyOut;\n" << outdent
+ << "}\n"
+ << "auto *tCppIn = reinterpret_cast<const " << typeName << R"( *>(cppIn);
+const char *typeName = )";
+
+ const QString nameFunc = metaClass->typeEntry()->polymorphicNameFunction();
+ if (nameFunc.isEmpty())
+ c << "typeid(*tCppIn).name();\n";
+ else
+ c << nameFunc << "(tCppIn);\n";
+ c << "return Shiboken::Object::newObjectForPointer("
+ << cpythonType << ", const_cast<void *>(cppIn), false, typeName);\n";
+}
+
void CppGenerator::writeConverterFunctions(TextStream &s, const AbstractMetaClassCPtr &metaClass,
const GeneratorContext &classContext) const
{
@@ -1634,30 +1656,7 @@ void CppGenerator::writeConverterFunctions(TextStream &s, const AbstractMetaClas
c << "return PySide::getWrapperForQObject(reinterpret_cast<"
<< typeName << " *>(const_cast<void *>(cppIn)), " << cpythonType << ");\n";
} else {
- c << "auto *pyOut = reinterpret_cast<PyObject *>(Shiboken::BindingManager::instance().retrieveWrapper(cppIn));\n"
- << "if (pyOut) {\n" << indent
- << "Py_INCREF(pyOut);\nreturn pyOut;\n" << outdent
- << "}\n"
- << "bool changedTypeName = false;\n"
- << "auto *tCppIn = reinterpret_cast<const " << typeName << R"( *>(cppIn);
-const char *typeName = )";
-
- const QString nameFunc = metaClass->typeEntry()->polymorphicNameFunction();
- if (nameFunc.isEmpty())
- c << "typeid(*tCppIn).name();\n";
- 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;
-}
-)"
- << "PyObject *result = Shiboken::Object::newObject(" << cpythonType
- << R"(, const_cast<void *>(cppIn), false, /* exactType */ changedTypeName, typeName);
-if (changedTypeName)
- delete [] typeName;
-return result;)";
+ writePointerToPythonConverter(c, metaClass, typeName, cpythonType);
}
std::swap(targetTypeName, sourceTypeName);
writeCppToPythonFunction(s, c.toString(), sourceTypeName, targetTypeName);
@@ -4260,6 +4259,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 +4272,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 +5476,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,
@@ -5804,7 +5828,7 @@ void CppGenerator::writeTypeDiscoveryFunction(TextStream &s,
} else if (metaClass->isPolymorphic()) {
const auto &ancestors = metaClass->allTypeSystemAncestors();
for (const auto &ancestor : ancestors) {
- if (ancestor->baseClass())
+ if (ancestor->baseClass() && !ancestor->typeEntry()->isPolymorphicBase())
continue;
if (ancestor->isPolymorphic()) {
s << "if (instanceType == Shiboken::SbkType< " << m_gsp
@@ -6337,6 +6361,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 +6500,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..c11fbbfb2 100644
--- a/sources/shiboken6/libshiboken/basewrapper.cpp
+++ b/sources/shiboken6/libshiboken/basewrapper.cpp
@@ -37,7 +37,73 @@ namespace {
void _destroyParentInfo(SbkObject *obj, bool keepReference);
}
-static void callDestructor(const Shiboken::DtorAccumulatorVisitor::DestructorEntries &dts)
+namespace Shiboken
+{
+// Walk through the first level of non-user-type Sbk base classes relevant for
+// C++ object allocation. Return true from the predicate to terminate.
+template <class Predicate>
+bool walkThroughBases(PyTypeObject *currentType, Predicate predicate)
+{
+ PyObject *bases = currentType->tp_bases;
+ const Py_ssize_t numBases = PyTuple_Size(bases);
+ bool result = false;
+ for (Py_ssize_t i = 0; !result && i < numBases; ++i) {
+ auto type = reinterpret_cast<PyTypeObject *>(PyTuple_GetItem(bases, i));
+ if (PyType_IsSubtype(type, SbkObject_TypeF()) != 0) {
+ result = PepType_SOTP(type)->is_user_type
+ ? walkThroughBases(type, predicate) : predicate(type);
+ }
+ }
+ return result;
+}
+
+int getTypeIndexOnHierarchy(PyTypeObject *baseType, PyTypeObject *desiredType)
+{
+ int index = -1;
+ walkThroughBases(baseType, [&index, desiredType](PyTypeObject *node) {
+ ++index;
+ return PyType_IsSubtype(node, desiredType) != 0;
+ });
+ return index;
+}
+
+int getNumberOfCppBaseClasses(PyTypeObject *baseType)
+{
+ int count = 0;
+ walkThroughBases(baseType, [&count](PyTypeObject *) {
+ ++count;
+ return false;
+ });
+ return count;
+}
+
+std::vector<PyTypeObject *> getCppBaseClasses(PyTypeObject *baseType)
+{
+ std::vector<PyTypeObject *> cppBaseClasses;
+ walkThroughBases(baseType, [&cppBaseClasses](PyTypeObject *node) {
+ cppBaseClasses.push_back(node);
+ return false;
+ });
+ return cppBaseClasses;
+}
+
+using DestructorEntries = std::vector<DestructorEntry>;
+
+DestructorEntries getDestructorEntries(SbkObject *o)
+{
+ DestructorEntries result;
+ void **cptrs = o->d->cptr;
+ walkThroughBases(Py_TYPE(o), [&result, cptrs](PyTypeObject *node) {
+ auto *sotp = PepType_SOTP(node);
+ auto index = result.size();
+ result.push_back(DestructorEntry{sotp->cpp_dtor,
+ cptrs[index]});
+ return false;
+ });
+ return result;
+}
+
+static void callDestructor(const DestructorEntries &dts)
{
for (const auto &e : dts) {
Shiboken::ThreadStateSaver threadSaver;
@@ -46,6 +112,8 @@ static void callDestructor(const Shiboken::DtorAccumulatorVisitor::DestructorEnt
}
}
+} // namespace Shiboken
+
extern "C"
{
@@ -129,31 +197,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 +234,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)
@@ -370,9 +408,8 @@ static void SbkDeallocWrapperCommon(PyObject *pyObj, bool canDelete)
if (sotp->delete_in_main_thread && Shiboken::currentThreadId() != Shiboken::mainThreadId()) {
auto &bindingManager = Shiboken::BindingManager::instance();
if (sotp->is_multicpp) {
- Shiboken::DtorAccumulatorVisitor visitor(sbkObj);
- Shiboken::walkThroughClassHierarchy(Py_TYPE(pyObj), &visitor);
- for (const auto &e : visitor.entries())
+ const auto entries = Shiboken::getDestructorEntries(sbkObj);
+ for (const auto &e : entries)
bindingManager.addToDeletionInMainThread(e);
} else {
Shiboken::DestructorEntry e{sotp->cpp_dtor, sbkObj->d->cptr[0]};
@@ -390,10 +427,9 @@ static void SbkDeallocWrapperCommon(PyObject *pyObj, bool canDelete)
if (canDelete) {
if (sotp->is_multicpp) {
- Shiboken::DtorAccumulatorVisitor visitor(sbkObj);
- Shiboken::walkThroughClassHierarchy(Py_TYPE(pyObj), &visitor);
+ const auto entries = Shiboken::getDestructorEntries(sbkObj);
Shiboken::Object::deallocData(sbkObj, true);
- callDestructor(visitor.entries());
+ callDestructor(entries);
} else {
void *cptr = sbkObj->d->cptr[0];
Shiboken::Object::deallocData(sbkObj, true);
@@ -730,52 +766,9 @@ void _destroyParentInfo(SbkObject *obj, bool keepReference)
namespace Shiboken
{
-bool walkThroughClassHierarchy(PyTypeObject *currentType, HierarchyVisitor *visitor)
-{
- PyObject *bases = currentType->tp_bases;
- Py_ssize_t numBases = PyTuple_GET_SIZE(bases);
- bool result = false;
- for (int i = 0; !result && i < numBases; ++i) {
- auto type = reinterpret_cast<PyTypeObject *>(PyTuple_GET_ITEM(bases, i));
- if (PyType_IsSubtype(type, reinterpret_cast<PyTypeObject *>(SbkObject_TypeF()))) {
- result = PepType_SOTP(type)->is_user_type
- ? walkThroughClassHierarchy(type, visitor) : visitor->visit(type);
- }
- }
- return result;
-}
// Wrapper metatype and base type ----------------------------------------------------------
-HierarchyVisitor::HierarchyVisitor() = default;
-HierarchyVisitor::~HierarchyVisitor() = default;
-
-bool BaseCountVisitor::visit(PyTypeObject *)
-{
- m_count++;
- return false;
-}
-
-bool BaseAccumulatorVisitor::visit(PyTypeObject *node)
-{
- m_bases.push_back(node);
- return false;
-}
-
-bool GetIndexVisitor::visit(PyTypeObject *node)
-{
- m_index++;
- return PyType_IsSubtype(node, m_desiredType);
-}
-
-bool DtorAccumulatorVisitor::visit(PyTypeObject *node)
-{
- auto *sotp = PepType_SOTP(node);
- m_entries.push_back(DestructorEntry{sotp->cpp_dtor,
- m_pyObject->d->cptr[m_entries.size()]});
- return false;
-}
-
void _initMainThreadId(); // helper.cpp
namespace Conversions { void init(); }
@@ -871,20 +864,6 @@ PyObject *checkInvalidArgumentCount(Py_ssize_t numArgs, Py_ssize_t minArgs, Py_s
return result;
}
-class FindBaseTypeVisitor : public HierarchyVisitor
-{
-public:
- explicit FindBaseTypeVisitor(PyTypeObject *typeToFind) : m_typeToFind(typeToFind) {}
-
- bool visit(PyTypeObject *node) override
- {
- return node == m_typeToFind;
- }
-
-private:
- PyTypeObject *m_typeToFind;
-};
-
std::vector<SbkObject *> splitPyObject(PyObject *pyObj)
{
std::vector<SbkObject *> result;
@@ -925,8 +904,8 @@ bool isUserType(PyTypeObject *type)
bool canCallConstructor(PyTypeObject *myType, PyTypeObject *ctorType)
{
- FindBaseTypeVisitor visitor(ctorType);
- if (!walkThroughClassHierarchy(myType, &visitor)) {
+ auto findBasePred = [ctorType](PyTypeObject *type) { return type == ctorType; };
+ if (!walkThroughBases(myType, findBasePred)) {
PyErr_Format(PyExc_TypeError, "%s isn't a direct base class of %s", ctorType->tp_name, myType->tp_name);
return false;
}
@@ -1007,11 +986,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;
@@ -1081,6 +1055,26 @@ bool hasSpecialCastFunction(PyTypeObject *sbkType)
return d != nullptr && d->mi_specialcast != nullptr;
}
+// Find whether base is a direct single line base class of type
+// (no multiple inheritance), that is, a C++ pointer cast can safely be done.
+static bool isDirectAncestor(PyTypeObject *type, PyTypeObject *base)
+{
+ if (type == base)
+ return true;
+ if (PyTuple_Size(type->tp_bases) == 0)
+ return false;
+ auto *sbkObjectType = SbkObject_TypeF();
+ auto *firstBase = reinterpret_cast<PyTypeObject *>(PyTuple_GetItem(type->tp_bases, 0));
+ return firstBase != sbkObjectType
+ && PyType_IsSubtype(type, sbkObjectType) != 0
+ && isDirectAncestor(firstBase, base);
+}
+
+bool canDowncastTo(PyTypeObject *baseType, PyTypeObject *targetType)
+{
+ return isDirectAncestor(targetType, baseType);
+}
+
} // namespace ObjectType
@@ -1169,9 +1163,7 @@ void callCppDestructors(SbkObject *pyObj)
PyTypeObject *type = Py_TYPE(pyObj);
auto *sotp = PepType_SOTP(type);
if (sotp->is_multicpp) {
- Shiboken::DtorAccumulatorVisitor visitor(pyObj);
- Shiboken::walkThroughClassHierarchy(type, &visitor);
- callDestructor(visitor.entries());
+ callDestructor(getDestructorEntries(pyObj));
} else {
Shiboken::ThreadStateSaver threadSaver;
threadSaver.save();
@@ -1452,25 +1444,66 @@ SbkObject *findColocatedChild(SbkObject *wrapper,
return nullptr;
}
+// Legacy, for compatibility only.
PyObject *newObject(PyTypeObject *instanceType,
void *cptr,
bool hasOwnership,
bool isExactType,
const char *typeName)
{
- // Try to find the exact type of cptr.
- if (!isExactType) {
- if (PyTypeObject *exactType = ObjectType::typeForTypeName(typeName)) {
- instanceType = exactType;
- } else {
- auto resolved = BindingManager::instance().findDerivedType(cptr, instanceType);
- if (resolved.first != nullptr) {
- instanceType = resolved.first;
- cptr = resolved.second;
- }
+ return isExactType
+ ? newObjectForType(instanceType, cptr, hasOwnership)
+ : newObjectWithHeuristics(instanceType, cptr, hasOwnership, typeName);
+}
+
+static PyObject *newObjectWithHeuristicsHelper(PyTypeObject *instanceType,
+ PyTypeObject *exactType,
+ void *cptr,
+ bool hasOwnership)
+{
+ // Try to find the exact type of cptr. For hierarchies with
+ // non-virtual destructors, typeid() will return the base name.
+ // Try type discovery in these cases.
+ if (exactType == nullptr || exactType == instanceType) {
+ auto resolved = BindingManager::instance().findDerivedType(cptr, instanceType);
+ if (resolved.first != nullptr) {
+ exactType = resolved.first;
+ cptr = resolved.second;
}
}
+ return newObjectForType(exactType != nullptr ? exactType : instanceType,
+ cptr, hasOwnership);
+}
+
+PyObject *newObjectForPointer(PyTypeObject *instanceType,
+ void *cptr,
+ bool hasOwnership,
+ const char *typeName)
+{
+ // Try to find the exact type of cptr.
+ PyTypeObject *exactType = ObjectType::typeForTypeName(typeName);
+ // PYSIDE-868: In case of multiple inheritance, (for example,
+ // a function returning a QPaintDevice * from a QWidget *),
+ // use instance type to avoid pointer offset errors.
+ return exactType != nullptr && !Shiboken::ObjectType::canDowncastTo(instanceType, exactType)
+ ? newObjectForType(instanceType, cptr, hasOwnership)
+ : newObjectWithHeuristicsHelper(instanceType, exactType, cptr, hasOwnership);
+}
+
+
+PyObject *newObjectWithHeuristics(PyTypeObject *instanceType,
+ void *cptr,
+ bool hasOwnership,
+ const char *typeName)
+{
+ return newObjectWithHeuristicsHelper(instanceType,
+ ObjectType::typeForTypeName(typeName),
+ cptr, hasOwnership);
+}
+
+PyObject *newObjectForType(PyTypeObject *instanceType, void *cptr, bool hasOwnership)
+{
bool shouldCreate = true;
bool shouldRegister = true;
SbkObject *self = nullptr;
diff --git a/sources/shiboken6/libshiboken/basewrapper.h b/sources/shiboken6/libshiboken/basewrapper.h
index 4835c4810..ec5545aea 100644
--- a/sources/shiboken6/libshiboken/basewrapper.h
+++ b/sources/shiboken6/libshiboken/basewrapper.h
@@ -265,6 +265,14 @@ LIBSHIBOKEN_API PyTypeObject *typeForTypeName(const char *typeName);
* \since 5.12
*/
LIBSHIBOKEN_API bool hasSpecialCastFunction(PyTypeObject *sbkType);
+
+/// Returns whether a C++ pointer of \p baseType can be safely downcast
+/// to \p targetType (base is a direct, single line base class of targetType).
+/// (is a direct, single-line inheritance)
+/// \param baseType Python type of base class
+/// \param targetType Python type of derived class
+/// \since 6.8
+LIBSHIBOKEN_API bool canDowncastTo(PyTypeObject *baseType, PyTypeObject *targetType);
}
namespace Object {
@@ -297,7 +305,8 @@ LIBSHIBOKEN_API SbkObject *findColocatedChild(SbkObject *wrapper,
const PyTypeObject *instanceType);
/**
- * Bind a C++ object to Python.
+ * Bind a C++ object to Python. Forwards to
+ * newObjectWithHeuristics(), newObjectForType() depending on \p isExactType.
* \param instanceType equivalent Python type for the C++ object.
* \param hasOwnership if true, Python will try to delete the underlying C++ object when there's no more refs.
* \param isExactType if false, Shiboken will use some heuristics to detect the correct Python type of this C++
@@ -311,6 +320,40 @@ LIBSHIBOKEN_API PyObject *newObject(PyTypeObject *instanceType,
bool isExactType = false,
const char *typeName = nullptr);
+/// Bind a C++ object to Python for polymorphic pointers. Calls
+/// newObjectWithHeuristics() with an additional check for multiple
+/// inheritance, in which case it will fall back to instanceType.
+/// \param instanceType Equivalent Python type for the C++ object.
+/// \param hasOwnership if true, Python will try to delete the underlying C++ object
+/// when there's no more refs.
+/// \param typeName If non-null, this will be used as helper to find the correct
+/// Python type for this object (obtained by typeid().name().
+LIBSHIBOKEN_API PyObject *newObjectForPointer(PyTypeObject *instanceType,
+ void *cptr,
+ bool hasOwnership = true,
+ const char *typeName = nullptr);
+
+/// Bind a C++ object to Python using some heuristics to detect the correct
+/// Python type of this C++ object. In any case \p instanceType must be provided;
+/// it'll be used as search starting point and as fallback.
+/// \param instanceType Equivalent Python type for the C++ object.
+/// \param hasOwnership if true, Python will try to delete the underlying C++ object
+/// C++ object when there are no more references.
+/// when there's no more refs.
+/// \param typeName If non-null, this will be used as helper to find the correct
+/// Python type for this object (obtained by typeid().name().
+LIBSHIBOKEN_API PyObject *newObjectWithHeuristics(PyTypeObject *instanceType,
+ void *cptr,
+ bool hasOwnership = true,
+ const char *typeName = nullptr);
+
+/// Bind a C++ object to Python using the given type.
+/// \param instanceType Equivalent Python type for the C++ object.
+/// \param hasOwnership if true, Python will try to delete the underlying
+/// C++ object when there are no more references.
+LIBSHIBOKEN_API PyObject *newObjectForType(PyTypeObject *instanceType,
+ void *cptr, bool hasOwnership = true);
+
/**
* Changes the valid flag of a PyObject, invalid objects will raise an exception when someone tries to access it.
*/
diff --git a/sources/shiboken6/libshiboken/basewrapper_p.h b/sources/shiboken6/libshiboken/basewrapper_p.h
index 526bf9fa3..fb9140793 100644
--- a/sources/shiboken6/libshiboken/basewrapper_p.h
+++ b/sources/shiboken6/libshiboken/basewrapper_p.h
@@ -114,7 +114,8 @@ struct SbkObjectTypePrivate
/// True if this type holds two or more C++ instances, e.g.: a Python class which inherits from two C++ classes.
unsigned int is_multicpp : 1;
- /// True if this type was defined by the user.
+ /// True if this type was defined by the user (a class written in Python inheriting
+ /// a class provided by a Shiboken binding).
unsigned int is_user_type : 1;
/// Tells is the type is a value type or an object-type, see BEHAVIOUR_ *constants.
unsigned int type_behaviour : 2;
@@ -142,107 +143,7 @@ struct DestructorEntry
**/
std::vector<SbkObject *> splitPyObject(PyObject *pyObj);
-/**
-* Visitor class used by walkOnClassHierarchy function.
-*/
-class HierarchyVisitor
-{
-public:
- HierarchyVisitor(const HierarchyVisitor &) = delete;
- HierarchyVisitor(HierarchyVisitor &&) = delete;
- HierarchyVisitor &operator=(const HierarchyVisitor &) = delete;
- HierarchyVisitor &operator=(HierarchyVisitor &&) = delete;
-
- HierarchyVisitor();
- virtual ~HierarchyVisitor();
-
- virtual bool visit(PyTypeObject *node) = 0; // return true to terminate
-};
-
-class BaseCountVisitor : public HierarchyVisitor
-{
-public:
- bool visit(PyTypeObject *) override;
-
- int count() const { return m_count; }
-
-private:
- int m_count = 0;
-};
-
-class BaseAccumulatorVisitor : public HierarchyVisitor
-{
-public:
- using Result = std::vector<PyTypeObject *>;
-
- bool visit(PyTypeObject *node) override;
-
- Result bases() const { return m_bases; }
-
-private:
- Result m_bases;
-};
-
-class GetIndexVisitor : public HierarchyVisitor
-{
-public:
- explicit GetIndexVisitor(PyTypeObject *desiredType) : m_desiredType(desiredType) {}
-
- bool visit(PyTypeObject *node) override;
-
- int index() const { return m_index; }
-
-private:
- int m_index = -1;
- PyTypeObject *m_desiredType;
-};
-
-/// Collect destructors and C++ instances of each C++ object held by a Python
-/// object
-class DtorAccumulatorVisitor : public HierarchyVisitor
-{
-public:
- explicit DtorAccumulatorVisitor(SbkObject *pyObj) : m_pyObject(pyObj) {}
-
- bool visit(PyTypeObject *node) override;
-
- using DestructorEntries = std::vector<DestructorEntry>;
-
- const DestructorEntries &entries() const { return m_entries; }
-
-private:
- DestructorEntries m_entries;
- SbkObject *m_pyObject;
-};
-
-/// \internal Internal function used to walk on classes inheritance trees.
-/**
-* Walk on class hierarchy using a DFS algorithm.
-* For each pure Shiboken type found, HierarchyVisitor::visit is called and the algorithm
-* considers all children of this type as visited.
-*/
-bool walkThroughClassHierarchy(PyTypeObject *currentType, HierarchyVisitor *visitor);
-
-inline int getTypeIndexOnHierarchy(PyTypeObject *baseType, PyTypeObject *desiredType)
-{
- GetIndexVisitor visitor(desiredType);
- walkThroughClassHierarchy(baseType, &visitor);
- return visitor.index();
-}
-
-inline int getNumberOfCppBaseClasses(PyTypeObject *baseType)
-{
- BaseCountVisitor visitor;
- walkThroughClassHierarchy(baseType, &visitor);
- return visitor.count();
-}
-
-inline std::vector<PyTypeObject *> getCppBaseClasses(PyTypeObject *baseType)
-{
- BaseAccumulatorVisitor visitor;
- walkThroughClassHierarchy(baseType, &visitor);
- return visitor.bases();
-}
+int getNumberOfCppBaseClasses(PyTypeObject *baseType);
namespace Object
{
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
diff --git a/sources/shiboken6/tests/libother/othermultiplederived.h b/sources/shiboken6/tests/libother/othermultiplederived.h
index a8e265388..cd9910687 100644
--- a/sources/shiboken6/tests/libother/othermultiplederived.h
+++ b/sources/shiboken6/tests/libother/othermultiplederived.h
@@ -10,7 +10,7 @@
class ObjectType;
-class LIBOTHER_API OtherMultipleDerived : public MDerived1
+class LIBOTHER_API OtherMultipleDerived : public OtherBase, public MDerived1
{
public:
// this will use CppCopier from other module (bug#142)
diff --git a/sources/shiboken6/tests/otherbinding/typediscovery_test.py b/sources/shiboken6/tests/otherbinding/typediscovery_test.py
index 791d3bdce..39dc5cf0f 100644
--- a/sources/shiboken6/tests/otherbinding/typediscovery_test.py
+++ b/sources/shiboken6/tests/otherbinding/typediscovery_test.py
@@ -13,7 +13,8 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
from shiboken_paths import init_paths
init_paths()
-from sample import Abstract, Base1, Derived
+from sample import (Abstract, Base1, Derived,
+ MDerived1, SonOfMDerived1, MDerived3)
from other import OtherMultipleDerived
@@ -32,14 +33,18 @@ class TypeDiscoveryTest(unittest.TestCase):
def testMultipleInheritance(self):
obj = OtherMultipleDerived.createObject("Base1")
self.assertEqual(type(obj), Base1)
- # PYSIDE-868: In case of multiple inheritance, a factory
- # function will return the base class wrapper.
+ # PYSIDE-868: In case of single line direct inheritance,
+ # a factory function will return the class wrapper
+ # of the derived class.
obj = OtherMultipleDerived.createObject("MDerived1")
- self.assertEqual(type(obj), Base1)
+ self.assertEqual(type(obj), MDerived1)
obj = OtherMultipleDerived.createObject("SonOfMDerived1")
- self.assertEqual(type(obj), Base1)
+ self.assertEqual(type(obj), SonOfMDerived1)
obj = OtherMultipleDerived.createObject("MDerived3")
- self.assertEqual(type(obj), Base1)
+ self.assertEqual(type(obj), MDerived3)
+ # PYSIDE-868: OtherMultipleDerived inherits
+ # OtherBase, Base1. In this case, a factory
+ # function will return the base class wrapper.
obj = OtherMultipleDerived.createObject("OtherMultipleDerived")
self.assertEqual(type(obj), Base1)