1 From: Christian Tismer <tismer@stackless.com>
2 Date: Tue, 14 Feb 2023 14:46:22 +0100
3 Subject: Support running PySide on Python 3.12
5 Builtin types no longer have tp_dict set. We need to
6 use PyType_GetDict, instead. This works without Limited API
9 With some great cheating, this works with Limited API, too.
10 We emulate PyType_GetDict by tp_dict if that is not 0.
11 Otherwise we create an empty dict.
13 Some small changes to Exception handling and longer
14 warm-up in leaking tests were found, too.
17 Task-number: PYSIDE-2230
18 Change-Id: I8a56de6208ec00979255b39b5784dfc9b4b92def
19 Reviewed-by: Friedemann Kleint <Friedemann.Kleint@qt.io>
20 (cherry picked from commit 441ffbd4fc622e67acd81e9c1c6d3a0b0fbcacf0)
22 build_scripts/config.py | 3 +-
23 sources/pyside2/PySide2/support/generate_pyi.py | 8 ++++--
24 sources/pyside2/libpyside/feature_select.cpp | 10 ++++---
25 sources/pyside2/libpyside/pysideproperty.cpp | 4 +--
26 sources/pyside2/libpyside/pysidesignal.cpp | 4 +--
27 sources/pyside2/tests/QtWidgets/bug_662.py | 3 +-
28 sources/pyside2/tests/signals/bug_79.py | 5 ++++
29 sources/shiboken2/libshiboken/pep384impl.cpp | 33 ++++++++++++++++++++++
30 sources/shiboken2/libshiboken/pep384impl.h | 8 ++++++
31 .../shiboken2/libshiboken/signature/signature.cpp | 2 +-
32 .../libshiboken/signature/signature_helper.cpp | 6 ++--
33 .../shibokensupport/signature/errorhandler.py | 6 ++++
34 sources/shiboken2/tests/samplebinding/enum_test.py | 2 +-
35 13 files changed, 78 insertions(+), 16 deletions(-)
37 diff --git a/build_scripts/config.py b/build_scripts/config.py
38 index f2b4c40..5fc23d4 100644
39 --- a/build_scripts/config.py
40 +++ b/build_scripts/config.py
41 @@ -94,7 +94,8 @@ class Config(object):
42 'Programming Language :: Python :: 3.8',
43 'Programming Language :: Python :: 3.9',
44 'Programming Language :: Python :: 3.10',
45 - 'Programming Language :: Python :: 3.11'
46 + 'Programming Language :: Python :: 3.11',
47 + 'Programming Language :: Python :: 3.12',
50 self.setup_script_dir = None
51 diff --git a/sources/pyside2/PySide2/support/generate_pyi.py b/sources/pyside2/PySide2/support/generate_pyi.py
52 index 1956533..fd05b1f 100644
53 --- a/sources/pyside2/PySide2/support/generate_pyi.py
54 +++ b/sources/pyside2/PySide2/support/generate_pyi.py
55 @@ -116,8 +116,12 @@ class Formatter(Writer):
57 def _typevar__repr__(self):
58 return "typing." + self.__name__
59 - typing.TypeVar.__repr__ = _typevar__repr__
61 + # This is no longer necessary for modern typing versions.
62 + # Ignore therefore if the repr is read-only and cannot be changed.
64 + typing.TypeVar.__repr__ = _typevar__repr__
67 # Adding a pattern to substitute "Union[T, NoneType]" by "Optional[T]"
68 # I tried hard to replace typing.Optional by a simple override, but
69 # this became _way_ too much.
70 diff --git a/sources/pyside2/libpyside/feature_select.cpp b/sources/pyside2/libpyside/feature_select.cpp
71 index b9e1470..533c09d 100644
72 --- a/sources/pyside2/libpyside/feature_select.cpp
73 +++ b/sources/pyside2/libpyside/feature_select.cpp
74 @@ -358,7 +358,8 @@ static bool SelectFeatureSetSubtype(PyTypeObject *type, PyObject *select_id)
75 * This is the selector for one sublass. We need to call this for
76 * every subclass until no more subclasses or reaching the wanted id.
78 - if (Py_TYPE(type->tp_dict) == Py_TYPE(PyType_Type.tp_dict)) {
79 + static const auto *pyTypeType_tp_dict = PepType_GetDict(&PyType_Type);
80 + if (Py_TYPE(type->tp_dict) == Py_TYPE(pyTypeType_tp_dict)) {
81 // On first touch, we initialize the dynamic naming.
82 // The dict type will be replaced after the first call.
83 if (!replaceClassDict(type)) {
84 @@ -385,7 +386,8 @@ static inline PyObject *SelectFeatureSet(PyTypeObject *type)
85 * Generated functions call this directly.
86 * Shiboken will assign it via a public hook of `basewrapper.cpp`.
88 - if (Py_TYPE(type->tp_dict) == Py_TYPE(PyType_Type.tp_dict)) {
89 + static const auto *pyTypeType_tp_dict = PepType_GetDict(&PyType_Type);
90 + if (Py_TYPE(type->tp_dict) == Py_TYPE(pyTypeType_tp_dict)) {
91 // We initialize the dynamic features by using our own dict type.
92 if (!replaceClassDict(type))
94 @@ -721,11 +723,11 @@ static bool patch_property_impl()
95 // Turn `__doc__` into a computed attribute without changing writability.
96 auto gsp = property_getset;
97 auto type = &PyProperty_Type;
98 - auto dict = type->tp_dict;
99 + AutoDecRef dict(PepType_GetDict(type));
100 AutoDecRef descr(PyDescr_NewGetSet(type, gsp));
103 - if (PyDict_SetItemString(dict, gsp->name, descr) < 0)
104 + if (PyDict_SetItemString(dict.object(), gsp->name, descr) < 0)
106 // Replace property_descr_get/set by slightly changed versions
108 diff --git a/sources/pyside2/libpyside/pysideproperty.cpp b/sources/pyside2/libpyside/pysideproperty.cpp
109 index 86909d3..d2e2c68 100644
110 --- a/sources/pyside2/libpyside/pysideproperty.cpp
111 +++ b/sources/pyside2/libpyside/pysideproperty.cpp
112 @@ -445,8 +445,8 @@ namespace {
114 static PyObject *getFromType(PyTypeObject *type, PyObject *name)
116 - PyObject *attr = nullptr;
117 - attr = PyDict_GetItem(type->tp_dict, name);
118 + AutoDecRef tpDict(PepType_GetDict(type));
119 + auto *attr = PyDict_GetItem(tpDict.object(), name);
121 PyObject *bases = type->tp_bases;
122 int size = PyTuple_GET_SIZE(bases);
123 diff --git a/sources/pyside2/libpyside/pysidesignal.cpp b/sources/pyside2/libpyside/pysidesignal.cpp
124 index 6824a71..f15d7aa 100644
125 --- a/sources/pyside2/libpyside/pysidesignal.cpp
126 +++ b/sources/pyside2/libpyside/pysidesignal.cpp
127 @@ -670,8 +670,8 @@ void updateSourceObject(PyObject *source)
132 - while (PyDict_Next(objType->tp_dict, &pos, &key, &value)) {
133 + Shiboken::AutoDecRef tpDict(PepType_GetDict(objType));
134 + while (PyDict_Next(tpDict, &pos, &key, &value)) {
135 if (PyObject_TypeCheck(value, PySideSignalTypeF())) {
136 Shiboken::AutoDecRef signalInstance(reinterpret_cast<PyObject *>(PyObject_New(PySideSignalInstance, PySideSignalInstanceTypeF())));
137 instanceInitialize(signalInstance.cast<PySideSignalInstance *>(), key, reinterpret_cast<PySideSignal *>(value), source, 0);
138 diff --git a/sources/pyside2/tests/QtWidgets/bug_662.py b/sources/pyside2/tests/QtWidgets/bug_662.py
139 index 7fb97de..ec0e6f9 100644
140 --- a/sources/pyside2/tests/QtWidgets/bug_662.py
141 +++ b/sources/pyside2/tests/QtWidgets/bug_662.py
142 @@ -40,7 +40,8 @@ from PySide2.QtWidgets import QTextEdit, QApplication
145 class testQTextBlock(unittest.TestCase):
146 - def tesIterator(self):
148 + def testIterator(self):
150 cursor = edit.textCursor()
151 fmt = QTextCharFormat()
152 diff --git a/sources/pyside2/tests/signals/bug_79.py b/sources/pyside2/tests/signals/bug_79.py
153 index ca25fb3..b70c8c5 100644
154 --- a/sources/pyside2/tests/signals/bug_79.py
155 +++ b/sources/pyside2/tests/signals/bug_79.py
156 @@ -60,6 +60,11 @@ class ConnectTest(unittest.TestCase):
158 # if this is no debug build, then we check at least that
159 # we do not crash any longer.
160 + for idx in range(200):
161 + # PYSIDE-2230: Warm-up is necessary before measuring, because
162 + # the code changes the constant parts after some time.
163 + o.selectionModel().destroyed.connect(self.callback)
164 + o.selectionModel().destroyed.disconnect(self.callback)
166 total = gettotalrefcount()
167 for idx in range(1000):
168 diff --git a/sources/shiboken2/libshiboken/pep384impl.cpp b/sources/shiboken2/libshiboken/pep384impl.cpp
169 index d12dae3..fed2716 100644
170 --- a/sources/shiboken2/libshiboken/pep384impl.cpp
171 +++ b/sources/shiboken2/libshiboken/pep384impl.cpp
172 @@ -810,6 +810,39 @@ init_PepRuntime()
173 PepRuntime_38_flag = 1;
176 +#ifdef Py_LIMITED_API
177 +static PyObject *emulatePyType_GetDict(PyTypeObject *type)
179 + if (_PepRuntimeVersion() < 0x030C00 || type->tp_dict) {
180 + auto *res = type->tp_dict;
184 + // PYSIDE-2230: Here we are really cheating. We don't know how to
185 + // access an internal dict, and so we simply pretend
186 + // it were an empty dict. This works great for our types.
187 + // This was an unexpectedly simple solution :D
188 + return PyDict_New();
192 +// PyType_GetDict: replacement for <static type>.tp_dict, which is
193 +// zero for builtin types since 3.12.
194 +PyObject *PepType_GetDict(PyTypeObject *type)
196 +#if !defined(Py_LIMITED_API)
197 +# if PY_VERSION_HEX >= 0x030C0000
198 + return PyType_GetDict(type);
200 + // pre 3.12 fallback code, mimicking the addref-behavior.
201 + Py_XINCREF(type->tp_dict);
202 + return type->tp_dict;
205 + return emulatePyType_GetDict(type);
206 +#endif // Py_LIMITED_API
209 /*****************************************************************************
211 * Module Initialization
212 diff --git a/sources/shiboken2/libshiboken/pep384impl.h b/sources/shiboken2/libshiboken/pep384impl.h
213 index a870d6b..440784e 100644
214 --- a/sources/shiboken2/libshiboken/pep384impl.h
215 +++ b/sources/shiboken2/libshiboken/pep384impl.h
216 @@ -567,6 +567,14 @@ extern LIBSHIBOKEN_API PyObject *PepMapping_Items(PyObject *o);
218 extern LIBSHIBOKEN_API int PepRuntime_38_flag;
220 +/*****************************************************************************
222 + * Runtime support for Python 3.12 incompatibility
226 +LIBSHIBOKEN_API PyObject *PepType_GetDict(PyTypeObject *type);
228 /*****************************************************************************
230 * Module Initialization
231 diff --git a/sources/shiboken2/libshiboken/signature/signature.cpp b/sources/shiboken2/libshiboken/signature/signature.cpp
232 index 191af3d..f817e47 100644
233 --- a/sources/shiboken2/libshiboken/signature/signature.cpp
234 +++ b/sources/shiboken2/libshiboken/signature/signature.cpp
235 @@ -482,7 +482,7 @@ static PyObject *adjustFuncName(const char *func_name)
237 // Find the feature flags
238 auto type = reinterpret_cast<PyTypeObject *>(obtype.object());
239 - auto dict = type->tp_dict;
240 + AutoDecRef dict(PepType_GetDict(type));
241 int id = SbkObjectType_GetReserved(type);
242 id = id < 0 ? 0 : id; // if undefined, set to zero
243 auto lower = id & 0x01;
244 diff --git a/sources/shiboken2/libshiboken/signature/signature_helper.cpp b/sources/shiboken2/libshiboken/signature/signature_helper.cpp
245 index 0246ec6..05eaa14 100644
246 --- a/sources/shiboken2/libshiboken/signature/signature_helper.cpp
247 +++ b/sources/shiboken2/libshiboken/signature/signature_helper.cpp
248 @@ -105,7 +105,8 @@ int add_more_getsets(PyTypeObject *type, PyGetSetDef *gsp, PyObject **doc_descr)
250 assert(PyType_Check(type));
252 - PyObject *dict = type->tp_dict;
253 + AutoDecRef tpDict(PepType_GetDict(type));
254 + auto *dict = tpDict.object();
255 for (; gsp->name != nullptr; gsp++) {
256 PyObject *have_descr = PyDict_GetItemString(dict, gsp->name);
257 if (have_descr != nullptr) {
258 @@ -346,7 +347,8 @@ static int _build_func_to_type(PyObject *obtype)
259 * We also check for hidden methods, see below.
261 auto *type = reinterpret_cast<PyTypeObject *>(obtype);
262 - PyObject *dict = type->tp_dict;
263 + AutoDecRef tpDict(PepType_GetDict(type));
264 + auto *dict = tpDict.object();
265 PyMethodDef *meth = type->tp_methods;
268 diff --git a/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/signature/errorhandler.py b/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/signature/errorhandler.py
269 index 47ab89a..3e1266c 100644
270 --- a/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/signature/errorhandler.py
271 +++ b/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/signature/errorhandler.py
272 @@ -113,6 +113,12 @@ def seterror_argument(args, func_name, info):
273 msg = "{func_name}(): {info}".format(**locals())
276 + if isinstance(info, Exception):
277 + # PYSIDE-2230: Python 3.12 seems to always do normalization.
279 + info = info.args[0]
280 + msg = f"{func_name}(): {info}"
282 if info and type(info) is dict:
283 keyword = tuple(info)[0]
284 msg = "{func_name}(): unsupported keyword '{keyword}'".format(**locals())
285 diff --git a/sources/shiboken2/tests/samplebinding/enum_test.py b/sources/shiboken2/tests/samplebinding/enum_test.py
286 index 0beb720..f2606a4 100644
287 --- a/sources/shiboken2/tests/samplebinding/enum_test.py
288 +++ b/sources/shiboken2/tests/samplebinding/enum_test.py
289 @@ -95,7 +95,7 @@ class EnumTest(unittest.TestCase):
291 def testEnumConstructorWithTooManyParameters(self):
292 '''Calling the constructor of non-extensible enum with the wrong number of parameters.'''
293 - self.assertRaises(TypeError, SampleNamespace.InValue, 13, 14)
294 + self.assertRaises((TypeError, ValueError), SampleNamespace.InValue, 13, 14)
296 def testEnumConstructorWithNonNumberParameter(self):
297 '''Calling the constructor of non-extensible enum with a string.'''