aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian Tismer <tismer@stackless.com>2022-09-19 12:00:14 +0200
committerChristian Tismer <tismer@stackless.com>2022-09-26 13:12:00 +0200
commit1f143f0440a32882587d4919d0855c7a3e2cd67c (patch)
treec1d24fff22187a282920eb31daa997f9a37bed10
parentb499d69cf3f5b2a1e74646ec2df4217fc0961713 (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.py9
-rw-r--r--sources/shiboken6/libshiboken/sbkenum.cpp110
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()));