diff options
| author | Christian Tismer <tismer@stackless.com> | 2022-09-19 12:00:14 +0200 |
|---|---|---|
| committer | Christian Tismer <tismer@stackless.com> | 2022-09-26 13:12:00 +0200 |
| commit | 1f143f0440a32882587d4919d0855c7a3e2cd67c (patch) | |
| tree | c1d24fff22187a282920eb31daa997f9a37bed10 | |
| parent | b499d69cf3f5b2a1e74646ec2df4217fc0961713 (diff) | |
PyEnum: Increase Compatibility by allowing any int as enum value
This patch supplies a _missing_ function to every (Int)?Enum
class that creates the given integer as a nameless enum.
The missing object is created on-demand and kept in the dict
"_sbk_missing_" for re-use.
[ChangeLog][PySide6] New Python Enum/IntEnum now allows for missing
values like the Shiboken enums before, increasing compatibility.
Task-number: PYSIDE-1735
Task-number: PYSIDE-841
Change-Id: I436b536e0a29337663cc3bca5db4322d9cf0de74
Pick-to: 6.3
Reviewed-by: Friedemann Kleint <Friedemann.Kleint@qt.io>
| -rw-r--r-- | sources/pyside6/tests/QtGui/bug_617.py | 9 | ||||
| -rw-r--r-- | sources/shiboken6/libshiboken/sbkenum.cpp | 110 |
2 files changed, 110 insertions, 9 deletions
diff --git a/sources/pyside6/tests/QtGui/bug_617.py b/sources/pyside6/tests/QtGui/bug_617.py index a6487768b..59b1a9a4a 100644 --- a/sources/pyside6/tests/QtGui/bug_617.py +++ b/sources/pyside6/tests/QtGui/bug_617.py @@ -16,12 +16,7 @@ from PySide6.QtGui import QColor class MyEvent(QEvent): def __init__(self): - if sys.pyside63_option_python_enum: - # PYSIDE-1735: Python Enum: We cannot assign arbitrary numbers. - # They must exist as constants in the type. - QEvent.__init__(self, QEvent.Type(1000)) - else: - QEvent.__init__(self, QEvent.Type(999)) + QEvent.__init__(self, QEvent.Type(999)) class Bug617(unittest.TestCase): @@ -32,7 +27,7 @@ class Bug617(unittest.TestCase): def testOutOfBounds(self): e = MyEvent() - self.assertEqual(repr(e.type()), "<Type.User: 1000>" + self.assertEqual(repr(e.type()), "<Type.999: 999>" if sys.pyside63_option_python_enum else "PySide6.QtCore.QEvent.Type(999)") diff --git a/sources/shiboken6/libshiboken/sbkenum.cpp b/sources/shiboken6/libshiboken/sbkenum.cpp index afc78a38f..3b0757417 100644 --- a/sources/shiboken6/libshiboken/sbkenum.cpp +++ b/sources/shiboken6/libshiboken/sbkenum.cpp @@ -975,6 +975,104 @@ static bool is_old_version() return number <= 3008; } +/////////////////////////////////////////////////////////////////////// +// +// Support for Missing Values +// ========================== +// +// Qt enums sometimes use undefined values in enums. +// The enum module handles this by the option "KEEP" for Flag and +// IntFlag. The handling of missing enum values is still strict. +// +// We changed that (also for compatibility with some competitor) +// and provide a `_missing_` function that creates the missing value. +// +// The idea: +// --------- +// We cannot modify the already created class. +// But we can create a one-element class with the new value and +// pretend that this is the already existing class. +// +// We create each constant only once and keep the result in a dict +// "_sbk_missing_". This is similar to a competitor's "_sip_missing_". +// +static PyObject *missing_func(PyObject *self, PyObject *args) +{ + // In order to relax matters to be more compatible with C++, we need + // to create a pseudo-member with that value. + static auto *const _sbk_missing = Shiboken::String::createStaticString("_sbk_missing_"); + static auto *const _name = Shiboken::String::createStaticString("__name__"); + static auto *const _mro = Shiboken::String::createStaticString("__mro__"); + static auto *const _class = Shiboken::String::createStaticString("__class__"); + + PyObject *klass{}, *value{}; + if (!PyArg_UnpackTuple(args, "missing", 2, 2, &klass, &value)) + Py_RETURN_NONE; + if (!PyLong_Check(value)) + Py_RETURN_NONE; + auto *type = reinterpret_cast<PyTypeObject *>(klass); + auto *sbk_missing = PyDict_GetItem(type->tp_dict, _sbk_missing); + if (!sbk_missing) { + sbk_missing = PyDict_New(); + PyDict_SetItem(type->tp_dict, _sbk_missing, sbk_missing); + } + // See if the value is already in the dict. + AutoDecRef val_str(PyObject_CallMethod(value, "__str__", nullptr)); + auto *ret = PyDict_GetItem(sbk_missing, val_str); + if (ret) { + Py_INCREF(ret); + return ret; + } + // No, we must create a new object and insert it into the dict. + AutoDecRef cls_name(PyObject_GetAttr(klass, _name)); + AutoDecRef mro(PyObject_GetAttr(klass, _mro)); + auto *baseClass(PyTuple_GetItem(mro, 1)); + AutoDecRef param(PyDict_New()); + PyDict_SetItem(param, val_str, value); + AutoDecRef fake(PyObject_CallFunctionObjArgs(baseClass, cls_name.object(), param.object(), + nullptr)); + ret = PyObject_GetAttr(fake, val_str); + PyDict_SetItem(sbk_missing, val_str, ret); + // Now the real fake: Pretend that the type is our original type! + PyObject_SetAttr(ret, _class, klass); + return ret; +} + +static struct PyMethodDef dummy_methods[] = { + {"_missing_", reinterpret_cast<PyCFunction>(missing_func), METH_VARARGS|METH_STATIC, nullptr}, + {nullptr, nullptr, 0, nullptr} +}; + +static PyType_Slot dummy_slots[] = { + {Py_tp_base, reinterpret_cast<void *>(&PyType_Type)}, + {Py_tp_methods, reinterpret_cast<void *>(dummy_methods)}, + {0, nullptr} +}; + +static PyType_Spec dummy_spec = { + "1:builtins.EnumType", + 0, + 0, + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, + dummy_slots, +}; + +static PyObject *create_missing_func(PyObject *klass) +{ + // When creating the class, memorize it in the missing function by + // a partial function argument. + static auto *const type = SbkType_FromSpec(&dummy_spec); + static auto *const obType = reinterpret_cast<PyObject *>(type); + static auto *const _missing = Shiboken::String::createStaticString("_missing_"); + static auto *const func = PyObject_GetAttr(obType, _missing); + static auto *const functools = PyImport_ImportModule("_functools"); // builtin + static auto *const _partial = Shiboken::String::createStaticString("partial"); + static auto *const partial = PyObject_GetAttr(functools, _partial); + return PyObject_CallFunctionObjArgs(partial, func, klass, nullptr); +} +// +//////////////////////////////////////////////////////////////////////// + PyTypeObject *morphLastEnumToPython() { /// The Python Enum internal structure is way too complicated. @@ -1014,6 +1112,7 @@ PyTypeObject *morphLastEnumToPython() AutoDecRef PyEnumType(PyObject_GetAttr(PyEnumModule, enumName)); assert(PyEnumType.object()); + bool isFlag = PyObject_IsSubclass(PyEnumType, PyFlag); // Walk the values dict and create a Python enum type. AutoDecRef name(PyUnicode_FromString(lec.name)); @@ -1038,8 +1137,15 @@ PyTypeObject *morphLastEnumToPython() if (PyFlag_KEEP) PyDict_SetItem(callDict, boundary, PyFlag_KEEP); auto *obNewType = PyObject_Call(PyEnumType, callArgs, callDict); - if (!obNewType || PyObject_SetAttr(lec.scopeOrModule, pyName, obNewType) < 0) - return nullptr; + if (!obNewType || PyObject_SetAttr(scopeOrModule, pyName, obNewType) < 0) + return nullptr; + + // For compatibility with Qt enums, provide a permissive missing method for (Int)?Enum. + if (!isFlag) { + AutoDecRef enum_missing(create_missing_func(obNewType)); + PyObject_SetAttrString(obNewType, "_missing_", enum_missing); + } + auto *newType = reinterpret_cast<PyTypeObject *>(obNewType); auto *obEnumType = reinterpret_cast<PyObject *>(enumType); AutoDecRef qual_name(PyObject_GetAttr(obEnumType, PyMagicName::qualname())); |
