aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--sources/pyside6/libpyside/pyside.cpp7
-rw-r--r--sources/pyside6/libpyside/pysideqobject.h3
-rw-r--r--sources/pyside6/tests/pysidetest/CMakeLists.txt1
-rw-r--r--sources/pyside6/tests/pysidetest/multiple_inheritance_test.py127
-rw-r--r--sources/shiboken6/generator/shiboken/cppgenerator.cpp19
-rw-r--r--sources/shiboken6/libshiboken/bindingmanager.cpp61
-rw-r--r--sources/shiboken6/libshiboken/bindingmanager.h3
-rw-r--r--sources/shiboken6/tests/samplebinding/multi_cpp_inheritance_test.py15
8 files changed, 223 insertions, 13 deletions
diff --git a/sources/pyside6/libpyside/pyside.cpp b/sources/pyside6/libpyside/pyside.cpp
index 42b7725cc..29a3d2884 100644
--- a/sources/pyside6/libpyside/pyside.cpp
+++ b/sources/pyside6/libpyside/pyside.cpp
@@ -299,7 +299,8 @@ static bool _setProperty(PyObject *qObj, PyObject *name, PyObject *value, bool *
return true;
}
-bool fillQtProperties(PyObject *qObj, const QMetaObject *metaObj, PyObject *kwds)
+bool fillQtProperties(PyObject *qObj, const QMetaObject *metaObj,
+ PyObject *kwds, bool allowErrors)
{
PyObject *key, *value;
@@ -331,6 +332,10 @@ bool fillQtProperties(PyObject *qObj, const QMetaObject *metaObj, PyObject *kwds
return false;
}
}
+ if (allowErrors) {
+ PyErr_Clear();
+ continue;
+ }
if (!accept) {
PyErr_Format(PyExc_AttributeError, "'%s' is not a Qt property or a signal",
propName.constData());
diff --git a/sources/pyside6/libpyside/pysideqobject.h b/sources/pyside6/libpyside/pysideqobject.h
index 2bd9b74ca..07453bc12 100644
--- a/sources/pyside6/libpyside/pysideqobject.h
+++ b/sources/pyside6/libpyside/pysideqobject.h
@@ -22,7 +22,8 @@ namespace PySide
/// \param metaObj QMetaObject of \p qObj.
/// \param kwds key->value dictonary.
/// \return True if everything goes well, false with a Python error set otherwise.
-PYSIDE_API bool fillQtProperties(PyObject *qObj, const QMetaObject *metaObj, PyObject *kwds);
+PYSIDE_API bool fillQtProperties(PyObject *qObj, const QMetaObject *metaObj,
+ PyObject *kwds, bool allowErrors);
PYSIDE_API void initDynamicMetaObject(PyTypeObject *type, const QMetaObject *base,
std::size_t cppObjSize);
diff --git a/sources/pyside6/tests/pysidetest/CMakeLists.txt b/sources/pyside6/tests/pysidetest/CMakeLists.txt
index 797b72c45..f64cea904 100644
--- a/sources/pyside6/tests/pysidetest/CMakeLists.txt
+++ b/sources/pyside6/tests/pysidetest/CMakeLists.txt
@@ -145,6 +145,7 @@ PYSIDE_TEST(iterable_test.py)
PYSIDE_TEST(list_signal_test.py)
PYSIDE_TEST(mixin_signal_slots_test.py)
PYSIDE_TEST(modelview_test.py)
+PYSIDE_TEST(multiple_inheritance_test.py)
PYSIDE_TEST(new_inherited_functions_test.py)
PYSIDE_TEST(notify_id.py)
PYSIDE_TEST(properties_test.py)
diff --git a/sources/pyside6/tests/pysidetest/multiple_inheritance_test.py b/sources/pyside6/tests/pysidetest/multiple_inheritance_test.py
new file mode 100644
index 000000000..6e48ac735
--- /dev/null
+++ b/sources/pyside6/tests/pysidetest/multiple_inheritance_test.py
@@ -0,0 +1,127 @@
+# Copyright (C) 2023 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 import QtCore, QtGui, QtWidgets
+
+
+def xprint(*args, **kw):
+ if "-v" in sys.argv:
+ print(*args, **kw)
+
+# This is the original testcase of PYSIDE-1564
+class Age(object):
+ def __init__(self, age=0, **kwds):
+ super().__init__(**kwds)
+
+ self.age = age
+
+class Person(QtCore.QObject, Age):
+ def __init__(self, name, **kwds):
+ super().__init__(**kwds)
+
+ self.name = name
+
+
+class OriginalMultipleInheritanceTest(unittest.TestCase):
+
+ def testIt(self):
+ xprint()
+ p = Person("Joe", age=38)
+ xprint(f"p.age = {p.age}")
+ # This would crash if MI does not work.
+
+# More tests follow:
+
+# mro ('C', 'A', 'QObject', 'Object', 'B', 'object')
+class A(QtCore.QObject):
+ def __init__(self, anna=77, **kw):
+ xprint(f'A: before init kw = {kw}')
+ super().__init__(**kw)
+ xprint('A: after init')
+
+class B:
+ def __init__(self, otto=6, age=7, **kw):
+ xprint(f'B: before init kw = {kw}')
+ if "killme" in kw:
+ raise AssertionError("asdf")
+ super().__init__(**kw)
+ self.age = age
+ xprint('B: after init')
+
+class C(A, B):
+ def __init__(self, **kw):
+ xprint(f'C: before init kw = {kw}')
+ super().__init__(**kw)
+ xprint('C: after init')
+
+# mro ('F', 'D', 'QCursor', 'E', 'QLabel', 'QFrame', 'QWidget', 'QObject', 'QPaintDevice', 'Object', 'object')
+class D(QtGui.QCursor):
+ def __init__(self, anna=77, **kw):
+ xprint(f'D: before init kw = {kw}')
+ super().__init__(**kw)
+ xprint('D: after init')
+
+class E:
+ def __init__(self, age=7, **kw):
+ xprint(f'E: before init kw = {kw}')
+ super().__init__(**kw)
+ self.age = age
+ xprint('E: after init')
+
+class F(D, E, QtWidgets.QLabel):
+ def __init__(self, **kw):
+ xprint(f'F: before init kw = {kw}')
+ super().__init__(**kw)
+ xprint('F: after init')
+
+# mro ('I', 'G', 'QTextDocument', 'H', 'QLabel', 'QFrame', 'QWidget', 'QObject', 'QPaintDevice', 'Object', 'object')
+# Similar, but this time we want to reach `H` without support from `super`.
+class G(QtGui.QTextDocument):
+ pass
+
+class H:
+ def __init__(self, age=7, **kw):
+ xprint(f'H: before init kw = {kw}')
+ super().__init__(**kw)
+ self.age = age
+ xprint('H: after init')
+
+class I(G, H, QtWidgets.QLabel):
+ pass
+
+
+class AdditionalMultipleInheritanceTest(UsesQApplication):
+
+ def testABC(self):
+ xprint()
+ res = C(otto=3, anna=5)
+ self.assertEqual(res.age, 7)
+ xprint()
+ with self.assertRaises(AssertionError):
+ res=C(killme=42)
+ xprint()
+
+ def testDEF(self):
+ xprint()
+ res = F(anna=5)
+ self.assertEqual(res.age, 7)
+ xprint()
+
+ def testGHI(self):
+ xprint()
+ res = I(age=7)
+ self.assertEqual(res.age, 7)
+ xprint()
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/sources/shiboken6/generator/shiboken/cppgenerator.cpp b/sources/shiboken6/generator/shiboken/cppgenerator.cpp
index 63fc65f19..8563aec8e 100644
--- a/sources/shiboken6/generator/shiboken/cppgenerator.cpp
+++ b/sources/shiboken6/generator/shiboken/cppgenerator.cpp
@@ -2297,11 +2297,12 @@ void CppGenerator::writeMethodWrapperPreamble(TextStream &s,const OverloadData &
initPythonArguments = minArgs != maxArgs || maxArgs > 1;
}
- if (needsArgumentErrorHandling(overloadData)) {
- s << R"(Shiboken::AutoDecRef errInfo{};
-static const char fullName[] = ")" << fullPythonFunctionName(rfunc, true)
- << "\";\nSBK_UNUSED(fullName)\n";
- }
+ if (needsArgumentErrorHandling(overloadData))
+ s << "Shiboken::AutoDecRef errInfo{};\n";
+
+ s << "static const char fullName[] = \"" << fullPythonFunctionName(rfunc, true)
+ << "\";\nSBK_UNUSED(fullName)\n";
+
if (maxArgs > 0) {
s << "int overloadId = -1;\n"
<< PYTHON_TO_CPPCONVERSION_STRUCT << ' ' << PYTHON_TO_CPP_VAR;
@@ -2389,6 +2390,12 @@ void CppGenerator::writeConstructorWrapper(TextStream &s, const OverloadData &ov
if (overloadData.maxArgs() > 0)
writeOverloadedFunctionDecisor(s, overloadData, errorReturn);
+ // Handles Python Multiple Inheritance
+ QString pre = needsMetaObject ? u"bool usesPyMI = "_s : u""_s;
+ s << "\n// PyMI support\n"
+ << pre << "Shiboken::callInheritedInit(self, args, kwds, fullName);\n"
+ << "if (PyErr_Occurred())\n" << indent << errorReturn << outdent << "\n";
+
writeFunctionCalls(s, overloadData, classContext, errorReturn);
s << '\n';
@@ -2423,7 +2430,7 @@ void CppGenerator::writeConstructorWrapper(TextStream &s, const OverloadData &ov
<< "PySide::Signal::updateSourceObject(self);\n"
<< "metaObject = cptr->metaObject(); // <- init python qt properties\n"
<< "if (!errInfo.isNull() && PyDict_Check(errInfo.object())) {\n" << indent
- << "if (!PySide::fillQtProperties(self, metaObject, errInfo))\n" << indent
+ << "if (!PySide::fillQtProperties(self, metaObject, errInfo, usesPyMI))\n" << indent
<< "return " << returnErrorWrongArguments(overloadData, errorReturn) << ";\n"
<< outdent << outdent
<< "};\n";
diff --git a/sources/shiboken6/libshiboken/bindingmanager.cpp b/sources/shiboken6/libshiboken/bindingmanager.cpp
index 01e073020..ed8b6743a 100644
--- a/sources/shiboken6/libshiboken/bindingmanager.cpp
+++ b/sources/shiboken6/libshiboken/bindingmanager.cpp
@@ -379,5 +379,66 @@ void BindingManager::visitAllPyObjects(ObjectVisitor visitor, void *data)
}
}
+static bool isPythonType(PyTypeObject *type)
+{
+ // This is a type which should be called by multiple inheritance.
+ // It is either a pure Python type or a derived PySide type.
+ return !ObjectType::checkType(type) || ObjectType::isUserType(type);
+}
+
+bool callInheritedInit(PyObject *self, PyObject *args, PyObject *kwds,
+ const char *fullName)
+{
+ using Shiboken::AutoDecRef;
+
+ static PyObject *const _init = String::createStaticString("__init__");
+
+ // A native C++ self cannot have multiple inheritance.
+ if (!Object::isUserType(self))
+ return false;
+
+ auto *startType = Py_TYPE(self);
+ auto *mro = startType->tp_mro;
+ Py_ssize_t idx, n = PyTuple_GET_SIZE(mro);
+ auto classNameLen = std::strrchr(fullName, '.') - fullName;
+ /* No need to check the last one: it's gonna be skipped anyway. */
+ for (idx = 0; idx + 1 < n; ++idx) {
+ auto *lookType = reinterpret_cast<PyTypeObject *>(PyTuple_GET_ITEM(mro, idx));
+ const char *lookName = lookType->tp_name;
+ auto lookLen = long(std::strlen(lookName));
+ if (std::strncmp(lookName, fullName, classNameLen) == 0 && lookLen == classNameLen)
+ break;
+ }
+ // We are now at the first non-Python class `QObject`.
+ // mro: ('C', 'A', 'QObject', 'Object', 'B', 'object')
+ // We want to catch class `B` and call its `__init__`.
+ for (idx += 1; idx + 1 < n; ++idx) {
+ auto *t = reinterpret_cast<PyTypeObject *>(PyTuple_GET_ITEM(mro, idx));
+ if (isPythonType(t))
+ break;
+ }
+ if (idx >= n)
+ return false;
+
+ auto *obSubType = PyTuple_GET_ITEM(mro, idx);
+ auto *subType = reinterpret_cast<PyTypeObject *>(obSubType);
+ if (subType == &PyBaseObject_Type)
+ return false;
+ const Py_ssize_t nargs = PyTuple_GET_SIZE(args);
+ AutoDecRef func(PyObject_GetAttr(obSubType, _init));
+ AutoDecRef newArgs(PyTuple_New(1 + nargs));
+ auto *newArgsOb = newArgs.object();
+ Py_INCREF(self);
+ PyTuple_SET_ITEM(newArgsOb, 0, self);
+ for (idx = 0; idx < nargs; ++idx) {
+ auto *ob = PyTuple_GET_ITEM(args, idx);
+ Py_INCREF(ob);
+ PyTuple_SET_ITEM(newArgsOb, 1 + idx, ob);
+ }
+ // Note: This can fail, so please always check the error status.
+ AutoDecRef result(PyObject_Call(func, newArgs, kwds));
+ return true;
+}
+
} // namespace Shiboken
diff --git a/sources/shiboken6/libshiboken/bindingmanager.h b/sources/shiboken6/libshiboken/bindingmanager.h
index e299dad96..955c0a1f4 100644
--- a/sources/shiboken6/libshiboken/bindingmanager.h
+++ b/sources/shiboken6/libshiboken/bindingmanager.h
@@ -67,6 +67,9 @@ private:
BindingManagerPrivate *m_d;
};
+LIBSHIBOKEN_API bool callInheritedInit(PyObject *self, PyObject *args, PyObject *kwds,
+ const char *fullName);
+
} // namespace Shiboken
#endif // BINDINGMANAGER_H
diff --git a/sources/shiboken6/tests/samplebinding/multi_cpp_inheritance_test.py b/sources/shiboken6/tests/samplebinding/multi_cpp_inheritance_test.py
index e655b8051..6fd735379 100644
--- a/sources/shiboken6/tests/samplebinding/multi_cpp_inheritance_test.py
+++ b/sources/shiboken6/tests/samplebinding/multi_cpp_inheritance_test.py
@@ -74,11 +74,16 @@ class MultipleCppDerivedReverseTest(unittest.TestCase):
self.assertEqual(s.objectName(), "Hi")
def testComplexInstanciation(self):
- c = ComplexUseCaseReverse("Hi")
- c.setObjectName(c)
- self.assertEqual(c.objectName(), "Hi")
- c.setX(2);
- self.assertEqual(c, Point(2, 0))
+ # PYSIDE-1564: This test can no longer work because of this MRO:
+ # ('ComplexUseCaseReverse', 'Point', 'SimpleUseCase2', 'SimpleUseCase',
+ # 'ObjectType', 'Str', 'Object', 'object')
+ # By multiple inheritance Point would be called first but has no argument.
+ with self.assertRaises(TypeError):
+ c = ComplexUseCaseReverse("Hi")
+ # c.setObjectName(c)
+ # self.assertEqual(c.objectName(), "Hi")
+ # c.setX(2);
+ # self.assertEqual(c, Point(2, 0))
if __name__ == '__main__':
unittest.main()