diff options
Diffstat (limited to 'sources/shiboken6/libshiboken/sbkenum.cpp')
-rw-r--r-- | sources/shiboken6/libshiboken/sbkenum.cpp | 979 |
1 files changed, 334 insertions, 645 deletions
diff --git a/sources/shiboken6/libshiboken/sbkenum.cpp b/sources/shiboken6/libshiboken/sbkenum.cpp index a2e6401ca..d39369979 100644 --- a/sources/shiboken6/libshiboken/sbkenum.cpp +++ b/sources/shiboken6/libshiboken/sbkenum.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2018 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt for Python. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "sbkenum.h" #include "sbkstring.h" @@ -47,717 +11,442 @@ #include "sbkpython.h" #include "signature.h" -#include <string.h> #include <cstring> #include <vector> +#include <sstream> -#define SbkEnumType_Check(o) (Py_TYPE(Py_TYPE(o)) == SbkEnumType_TypeF()) -typedef PyObject *(*enum_func)(PyObject *, PyObject *); +using namespace Shiboken; extern "C" { -struct SbkEnumTypePrivate -{ - SbkConverter **converterPtr; - SbkConverter *converter; - const char *cppName; -}; - struct SbkEnumType { PyTypeObject type; }; -struct SbkEnumObject -{ - PyObject_HEAD - long ob_value; - PyObject *ob_name; -}; - -static PyTypeObject *SbkEnum_TypeF(); // forward - -static PyObject *SbkEnumObject_repr(PyObject *self) -{ - const SbkEnumObject *enumObj = reinterpret_cast<SbkEnumObject *>(self); - if (enumObj->ob_name) - return Shiboken::String::fromFormat("%s.%s", (Py_TYPE(self))->tp_name, PyBytes_AS_STRING(enumObj->ob_name)); - else - return Shiboken::String::fromFormat("%s(%ld)", (Py_TYPE(self))->tp_name, enumObj->ob_value); -} - -static PyObject *SbkEnumObject_name(PyObject *self, void *) -{ - auto *enum_self = reinterpret_cast<SbkEnumObject *>(self); - - if (enum_self->ob_name == nullptr) - Py_RETURN_NONE; - - Py_INCREF(enum_self->ob_name); - return enum_self->ob_name; -} - -static PyObject *SbkEnum_tp_new(PyTypeObject *type, PyObject *args, PyObject *) -{ - long itemValue = 0; - if (!PyArg_ParseTuple(args, "|l:__new__", &itemValue)) - return nullptr; - - if (type == SbkEnum_TypeF()) { - PyErr_Format(PyExc_TypeError, "You cannot use %s directly", type->tp_name); - return nullptr; - } - - SbkEnumObject *self = PyObject_New(SbkEnumObject, type); - if (!self) - return nullptr; - self->ob_value = itemValue; - Shiboken::AutoDecRef item(Shiboken::Enum::getEnumItemFromValue(type, itemValue)); - self->ob_name = item.object() ? SbkEnumObject_name(item, nullptr) : nullptr; - return reinterpret_cast<PyObject *>(self); -} - -static const char *SbkEnum_SignatureStrings[] = { - "Shiboken.Enum(self,itemValue:int=0)", - nullptr}; // Sentinel - -void enum_object_dealloc(PyObject *ob) +// Initialization +static bool _init_enum() { - auto self = reinterpret_cast<SbkEnumObject *>(ob); - Py_XDECREF(self->ob_name); - Sbk_object_dealloc(ob); -} - -static PyObject *_enum_op(enum_func f, PyObject *a, PyObject *b) { - PyObject *valA = a; - PyObject *valB = b; - PyObject *result = nullptr; - bool enumA = false; - bool enumB = false; - - // We are not allowing floats - if (!PyFloat_Check(valA) && !PyFloat_Check(valB)) { - // Check if both variables are SbkEnumObject - if (SbkEnumType_Check(valA)) { - valA = PyLong_FromLong(reinterpret_cast<SbkEnumObject *>(valA)->ob_value); - enumA = true; - } - if (SbkEnumType_Check(valB)) { - valB = PyLong_FromLong(reinterpret_cast<SbkEnumObject *>(valB)->ob_value); - enumB = true; + AutoDecRef shibo(PyImport_ImportModule("shiboken6.Shiboken")); + return !shibo.isNull(); +} + +static PyObject *PyEnumModule{}; +static PyObject *PyEnumMeta{}; +static PyObject *PyEnum{}; +static PyObject *PyIntEnum{}; +static PyObject *PyFlag{}; +static PyObject *PyIntFlag{}; +static PyObject *PyFlag_KEEP{}; + +bool PyEnumMeta_Check(PyObject *ob) +{ + return Py_TYPE(ob) == reinterpret_cast<PyTypeObject *>(PyEnumMeta); +} + +PyTypeObject *getPyEnumMeta() +{ + if (PyEnumMeta) + return reinterpret_cast<PyTypeObject *>(PyEnumMeta); + + static auto *mod = PyImport_ImportModule("enum"); + if (mod) { + PyEnumModule = mod; + PyEnumMeta = PyObject_GetAttrString(mod, "EnumMeta"); + if (PyEnumMeta && PyType_Check(PyEnumMeta)) + PyEnum = PyObject_GetAttrString(mod, "Enum"); + if (PyEnum && PyType_Check(PyEnum)) + PyIntEnum = PyObject_GetAttrString(mod, "IntEnum"); + if (PyIntEnum && PyType_Check(PyIntEnum)) + PyFlag = PyObject_GetAttrString(mod, "Flag"); + if (PyFlag && PyType_Check(PyFlag)) + PyIntFlag = PyObject_GetAttrString(mod, "IntFlag"); + if (PyIntFlag && PyType_Check(PyIntFlag)) { + // KEEP is defined from Python 3.11 on. + PyFlag_KEEP = PyObject_GetAttrString(mod, "KEEP"); + PyErr_Clear(); + return reinterpret_cast<PyTypeObject *>(PyEnumMeta); } } - - // Without an enum we are not supporting the operation - if (!(enumA || enumB)) { - Py_INCREF(Py_NotImplemented); - return Py_NotImplemented; - } else { - result = f(valA, valB); - } - - // Decreasing the reference of the used variables a and b. - if (enumA) - Py_DECREF(valA); - if (enumB) - Py_DECREF(valB); - return result; -} - -/* Notes: - * On Py3k land we use long type when using integer numbers. However, on older - * versions of Python (version 2) we need to convert it to int type, - * respectively. - * - * Thus calling PyInt_FromLong() will result in calling PyLong_FromLong in - * Py3k. - */ -static PyObject *enum_int(PyObject *v) -{ - return PyInt_FromLong(reinterpret_cast<SbkEnumObject *>(v)->ob_value); -} - -static PyObject *enum_and(PyObject *self, PyObject *b) -{ - return _enum_op(PyNumber_And, self, b); -} - -static PyObject *enum_or(PyObject *self, PyObject *b) -{ - return _enum_op(PyNumber_Or, self, b); + Py_FatalError("Python module 'enum' not found"); + return nullptr; } -static PyObject *enum_xor(PyObject *self, PyObject *b) +void init_enum() { - return _enum_op(PyNumber_Xor, self, b); + static bool isInitialized = false; + if (isInitialized) + return; + if (!(isInitialized || _init_enum())) + Py_FatalError("could not init enum"); + + // PYSIDE-1735: Determine whether we should use the old or the new enum implementation. + static PyObject *option = PySys_GetObject("pyside6_option_python_enum"); + if (!option || !PyLong_Check(option)) { + PyErr_Clear(); + option = PyLong_FromLong(1); + } + int ignoreOver{}; + Enum::enumOption = PyLong_AsLongAndOverflow(option, &ignoreOver); + getPyEnumMeta(); + isInitialized = true; } -static int enum_bool(PyObject *v) +// PYSIDE-1735: Helper function supporting QEnum +int enumIsFlag(PyObject *ob_type) { - return (reinterpret_cast<SbkEnumObject *>(v)->ob_value > 0); -} + init_enum(); -static PyObject *enum_add(PyObject *self, PyObject *v) -{ - return _enum_op(PyNumber_Add, self, v); + auto *metatype = Py_TYPE(ob_type); + if (metatype != reinterpret_cast<PyTypeObject *>(PyEnumMeta)) + return -1; + auto *mro = reinterpret_cast<PyTypeObject *>(ob_type)->tp_mro; + const Py_ssize_t n = PyTuple_GET_SIZE(mro); + for (Py_ssize_t idx = 0; idx < n; ++idx) { + auto *sub_type = reinterpret_cast<PyTypeObject *>(PyTuple_GET_ITEM(mro, idx)); + if (sub_type == reinterpret_cast<PyTypeObject *>(PyFlag)) + return 1; + } + return 0; } -static PyObject *enum_subtract(PyObject *self, PyObject *v) -{ - return _enum_op(PyNumber_Subtract, self, v); -} +/////////////////////////////////////////////////////////////////////// +// +// 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 *enum_multiply(PyObject *self, PyObject *v) +static PyObject *missing_func(PyObject * /* self */ , PyObject *args) { - return _enum_op(PyNumber_Multiply, self, v); -} + // 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__"); -static PyObject *enum_richcompare(PyObject *self, PyObject *other, int op) -{ - PyObject *valA = self; - PyObject *valB = other; - PyObject *result = nullptr; - bool enumA = false; - bool enumB = false; - - // We are not allowing floats - if (!PyFloat_Check(valA) && !PyFloat_Check(valB)) { - - // Check if both variables are SbkEnumObject - if (SbkEnumType_Check(valA)) { - valA = PyLong_FromLong(reinterpret_cast<SbkEnumObject *>(valA)->ob_value); - enumA = true; - } - if (SbkEnumType_Check(valB)) { - valB = PyLong_FromLong(reinterpret_cast<SbkEnumObject *>(valB)->ob_value); - enumB =true; - } + 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); + AutoDecRef tpDict(PepType_GetDict(type)); + auto *sbk_missing = PyDict_GetItem(tpDict.object(), _sbk_missing); + if (!sbk_missing) { + sbk_missing = PyDict_New(); + PyDict_SetItem(tpDict.object(), _sbk_missing, sbk_missing); } - - // Without an enum we are not supporting the operation - if (!(enumA || enumB)) { - Py_INCREF(Py_NotImplemented); - return Py_NotImplemented; + // 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; } - result = PyObject_RichCompare(valA, valB, op); - - // Decreasing the reference of the used variables a and b. - if (enumA) - Py_DECREF(valA); - if (enumB) - Py_DECREF(valB); - - return result; -} - -static Py_hash_t enum_hash(PyObject *pyObj) -{ - Py_hash_t val = reinterpret_cast<SbkEnumObject *>(pyObj)->ob_value; - if (val == -1) - val = -2; - return val; -} - -static PyGetSetDef SbkEnumGetSetList[] = { - {const_cast<char *>("name"), &SbkEnumObject_name, nullptr, nullptr, nullptr}, - {nullptr, nullptr, nullptr, nullptr, nullptr} // Sentinel + // 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 void SbkEnumTypeDealloc(PyObject *pyObj); -static PyObject *SbkEnumTypeTpNew(PyTypeObject *metatype, PyObject *args, PyObject *kwds); - -static PyType_Slot SbkEnumType_Type_slots[] = { - {Py_tp_dealloc, (void *)SbkEnumTypeDealloc}, - {Py_tp_base, (void *)&PyType_Type}, - {Py_tp_alloc, (void *)PyType_GenericAlloc}, - {Py_tp_new, (void *)SbkEnumTypeTpNew}, - {Py_tp_free, (void *)PyObject_GC_Del}, +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 SbkEnumType_Type_spec = { - "1:Shiboken.EnumMeta", - 0, // filled in later - sizeof(PyMemberDef), - Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_CHECKTYPES, - SbkEnumType_Type_slots, -}; - -PyTypeObject *SbkEnumType_TypeF(void) -{ - static PyTypeObject *type = nullptr; - if (!type) { - SbkEnumType_Type_spec.basicsize = - PepHeapType_SIZE + sizeof(SbkEnumTypePrivate); - type = reinterpret_cast<PyTypeObject *>(SbkType_FromSpec(&SbkEnumType_Type_spec)); - } - return type; -} +static PyType_Spec dummy_spec = { + "1:builtins.EnumType", + 0, + 0, + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, + dummy_slots, +}; -void SbkEnumTypeDealloc(PyObject *pyObj) +static PyObject *create_missing_func(PyObject *klass) { - auto sbkType = reinterpret_cast<SbkEnumType *>(pyObj); - - PyObject_GC_UnTrack(pyObj); -#ifndef Py_LIMITED_API - Py_TRASHCAN_SAFE_BEGIN(pyObj); -#endif - if (PepType_SETP(sbkType)->converter) { - Shiboken::Conversions::deleteConverter(PepType_SETP(sbkType)->converter); - } -#ifndef Py_LIMITED_API - Py_TRASHCAN_SAFE_END(pyObj); -#endif - if (PepRuntime_38_flag) { - // PYSIDE-939: Handling references correctly. - // This was not needed before Python 3.8 (Python issue 35810) - Py_DECREF(Py_TYPE(pyObj)); - } -} - -PyObject *SbkEnumTypeTpNew(PyTypeObject *metatype, PyObject *args, PyObject *kwds) -{ - auto type_new = reinterpret_cast<newfunc>(PyType_GetSlot(&PyType_Type, Py_tp_new)); - auto newType = reinterpret_cast<SbkEnumType *>(type_new(metatype, args, kwds)); - if (!newType) - return nullptr; - return reinterpret_cast<PyObject *>(newType); + // 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 partial = Pep_GetPartialFunction(); + return PyObject_CallFunctionObjArgs(partial, func, klass, nullptr); } +// +//////////////////////////////////////////////////////////////////////// } // extern "C" -/////////////////////////////////////////////////////////////// -// -// PYSIDE-15: Pickling Support for Qt Enum objects -// This works very well and fixes the issue. -// -extern "C" { +namespace Shiboken::Enum { -static PyObject *enum_unpickler = nullptr; +int enumOption{}; -// Pickling: reduce the Qt Enum object -static PyObject *enum___reduce__(PyObject *obj) +bool check(PyObject *pyObj) { init_enum(); - return Py_BuildValue("O(Ni)", - enum_unpickler, - Py_BuildValue("s", Py_TYPE(obj)->tp_name), - PyInt_AS_LONG(obj)); -} -} // extern "C" - -namespace Shiboken { namespace Enum { + static PyTypeObject *meta = getPyEnumMeta(); + return Py_TYPE(Py_TYPE(pyObj)) == reinterpret_cast<PyTypeObject *>(meta); +} -// Unpickling: rebuild the Qt Enum object -PyObject *unpickleEnum(PyObject *enum_class_name, PyObject *value) +PyObject *getEnumItemFromValue(PyTypeObject *enumType, EnumValueType itemValue) { - Shiboken::AutoDecRef parts(PyObject_CallMethod(enum_class_name, - const_cast<char *>("split"), const_cast<char *>("s"), ".")); - if (parts.isNull()) - return nullptr; - PyObject *top_name = PyList_GetItem(parts, 0); // borrowed ref - if (top_name == nullptr) - return nullptr; - PyObject *module = PyImport_GetModule(top_name); - if (module == nullptr) { - PyErr_Format(PyExc_ImportError, "could not import module %.200s", - Shiboken::String::toCString(top_name)); + init_enum(); + + auto *obEnumType = reinterpret_cast<PyObject *>(enumType); + AutoDecRef val2members(PyObject_GetAttrString(obEnumType, "_value2member_map_")); + if (val2members.isNull()) { + PyErr_Clear(); return nullptr; } - Shiboken::AutoDecRef cur_thing(module); - int len = PyList_Size(parts); - for (int idx = 1; idx < len; ++idx) { - PyObject *name = PyList_GetItem(parts, idx); // borrowed ref - PyObject *thing = PyObject_GetAttr(cur_thing, name); - if (thing == nullptr) { - PyErr_Format(PyExc_ImportError, "could not import Qt Enum type %.200s", - Shiboken::String::toCString(enum_class_name)); - return nullptr; - } - cur_thing.reset(thing); - } - PyObject *klass = cur_thing; - return PyObject_CallFunctionObjArgs(klass, value, nullptr); -} - -} // namespace Enum -} // namespace Shiboken - -extern "C" { - -// Initialization -static bool _init_enum() -{ - Shiboken::AutoDecRef shibo(PyImport_ImportModule("shiboken6.Shiboken")); - auto mod = shibo.object(); - // publish Shiboken.Enum so that the signature gets initialized - if (PyObject_SetAttrString(mod, "Enum", reinterpret_cast<PyObject *>(SbkEnum_TypeF())) < 0) - return false; - if (InitSignatureStrings(SbkEnum_TypeF(), SbkEnum_SignatureStrings) < 0) - return false; - enum_unpickler = PyObject_GetAttrString(mod, "_unpickle_enum"); - if (enum_unpickler == nullptr) - return false; - return true; + AutoDecRef ob_value(PyLong_FromLongLong(itemValue)); + auto *result = PyDict_GetItem(val2members, ob_value); + Py_XINCREF(result); + return result; } -void init_enum() +PyObject *newItem(PyTypeObject *enumType, EnumValueType itemValue, + const char *itemName) { - static bool is_initialized = false; - if (!(is_initialized || enum_unpickler || _init_enum())) - Py_FatalError("could not load enum pickling helper function"); - is_initialized = true; -} - -static PyMethodDef SbkEnumObject_Methods[] = { - {const_cast<char *>("__reduce__"), reinterpret_cast<PyCFunction>(enum___reduce__), - METH_NOARGS, nullptr}, - {nullptr, nullptr, 0, nullptr} // Sentinel -}; - -} // extern "C" + init_enum(); -// -/////////////////////////////////////////////////////////////// + auto *obEnumType = reinterpret_cast<PyObject *>(enumType); + if (!itemName) + return PyObject_CallFunction(obEnumType, "L", itemValue); -namespace Shiboken { + static PyObject *const _member_map_ = String::createStaticString("_member_map_"); + AutoDecRef tpDict(PepType_GetDict(enumType)); + auto *member_map = PyDict_GetItem(tpDict.object(), _member_map_); + if (!(member_map && PyDict_Check(member_map))) + return nullptr; + auto *result = PyDict_GetItemString(member_map, itemName); + Py_XINCREF(result); + return result; +} -class DeclaredEnumTypes +EnumValueType getValue(PyObject *enumItem) { -public: - DeclaredEnumTypes(const DeclaredEnumTypes &) = delete; - DeclaredEnumTypes(DeclaredEnumTypes &&) = delete; - DeclaredEnumTypes &operator=(const DeclaredEnumTypes &) = delete; - DeclaredEnumTypes &operator=(DeclaredEnumTypes &&) = delete; - - DeclaredEnumTypes(); - ~DeclaredEnumTypes(); - static DeclaredEnumTypes &instance(); - void addEnumType(PyTypeObject *type); - -private: - std::vector<PyTypeObject *> m_enumTypes; -}; + init_enum(); -namespace Enum { + assert(Enum::check(enumItem)); -bool check(PyObject *pyObj) -{ - return Py_TYPE(Py_TYPE(pyObj)) == SbkEnumType_TypeF(); + AutoDecRef pyValue(PyObject_GetAttrString(enumItem, "value")); + return PyLong_AsLongLong(pyValue); } -PyObject *getEnumItemFromValue(PyTypeObject *enumType, long itemValue) +void setTypeConverter(PyTypeObject *type, SbkConverter *converter) { - PyObject *key, *value; - Py_ssize_t pos = 0; - PyObject *values = PyDict_GetItem(enumType->tp_dict, Shiboken::PyName::values()); - - while (PyDict_Next(values, &pos, &key, &value)) { - auto *obj = reinterpret_cast<SbkEnumObject *>(value); - if (obj->ob_value == itemValue) { - Py_INCREF(value); - return value; - } - } - return nullptr; + auto *enumType = reinterpret_cast<SbkEnumType *>(type); + PepType_SETP(enumType)->converter = converter; } -static PyTypeObject *createEnum(const char *fullName, const char *cppName, - PyTypeObject *flagsType) +static PyTypeObject *createEnumForPython(PyObject *scopeOrModule, + const char *fullName, + PyObject *pyEnumItems) { - PyTypeObject *enumType = newTypeWithName(fullName, cppName, flagsType); - if (PyType_Ready(enumType) < 0) { - Py_XDECREF(enumType); - return nullptr; - } - return enumType; -} + const char *colon = strchr(fullName, ':'); + assert(colon); + int package_level = atoi(fullName); + const char *mod = colon + 1; -PyTypeObject *createGlobalEnum(PyObject *module, const char *name, const char *fullName, const char *cppName, PyTypeObject *flagsType) -{ - PyTypeObject *enumType = createEnum(fullName, cppName, flagsType); - if (enumType && PyModule_AddObject(module, name, reinterpret_cast<PyObject *>(enumType)) < 0) { - Py_DECREF(enumType); - return nullptr; + const char *qual = mod; + for (int idx = package_level; idx > 0; --idx) { + const char *dot = strchr(qual, '.'); + if (!dot) + break; + qual = dot + 1; } - if (flagsType && PyModule_AddObject(module, PepType_GetNameStr(flagsType), - reinterpret_cast<PyObject *>(flagsType)) < 0) { - Py_DECREF(enumType); - return nullptr; + int mlen = qual - mod - 1; + AutoDecRef module(Shiboken::String::fromCString(mod, mlen)); + AutoDecRef qualname(Shiboken::String::fromCString(qual)); + const char *dot = strrchr(qual, '.'); + AutoDecRef name(Shiboken::String::fromCString(dot ? dot + 1 : qual)); + + static PyObject *enumName = String::createStaticString("IntEnum"); + if (PyType_Check(scopeOrModule)) { + // For global objects, we have no good solution, yet where to put the int info. + auto type = reinterpret_cast<PyTypeObject *>(scopeOrModule); + auto *sotp = PepType_SOTP(type); + if (!sotp->enumFlagsDict) + initEnumFlagsDict(type); + enumName = PyDict_GetItem(sotp->enumTypeDict, name); } - return enumType; -} -PyTypeObject *createScopedEnum(SbkObjectType *scope, const char *name, const char *fullName, const char *cppName, PyTypeObject *flagsType) -{ - PyTypeObject *enumType = createEnum(fullName, cppName, flagsType); - if (enumType && PyDict_SetItemString(reinterpret_cast<PyTypeObject *>(scope)->tp_dict, name, - reinterpret_cast<PyObject *>(enumType)) < 0) { - Py_DECREF(enumType); - return nullptr; - } - if (flagsType && PyDict_SetItemString(reinterpret_cast<PyTypeObject *>(scope)->tp_dict, - PepType_GetNameStr(flagsType), - reinterpret_cast<PyObject *>(flagsType)) < 0) { - Py_DECREF(enumType); - return nullptr; - } - return enumType; -} + AutoDecRef PyEnumType(PyObject_GetAttr(PyEnumModule, enumName)); + assert(PyEnumType.object()); + bool isFlag = PyObject_IsSubclass(PyEnumType, PyFlag); -static PyObject *createEnumItem(PyTypeObject *enumType, const char *itemName, long itemValue) -{ - PyObject *enumItem = newItem(enumType, itemValue, itemName); - if (PyDict_SetItemString(enumType->tp_dict, itemName, enumItem) < 0) { - Py_DECREF(enumItem); - return nullptr; + // See if we should use the Int versions of the types, again + bool useIntInheritance = Enum::enumOption & Enum::ENOPT_INHERIT_INT; + if (useIntInheritance) { + auto *surrogate = PyObject_IsSubclass(PyEnumType, PyFlag) ? PyIntFlag : PyIntEnum; + Py_INCREF(surrogate); + PyEnumType.reset(surrogate); } - return enumItem; -} - -bool createGlobalEnumItem(PyTypeObject *enumType, PyObject *module, const char *itemName, long itemValue) -{ - PyObject *enumItem = createEnumItem(enumType, itemName, itemValue); - if (!enumItem) - return false; - int ok = PyModule_AddObject(module, itemName, enumItem); - Py_DECREF(enumItem); - return ok >= 0; -} -bool createScopedEnumItem(PyTypeObject *enumType, PyTypeObject *scope, - const char *itemName, long itemValue) -{ - PyObject *enumItem = createEnumItem(enumType, itemName, itemValue); - if (!enumItem) - return false; - int ok = PyDict_SetItemString(reinterpret_cast<PyTypeObject *>(scope)->tp_dict, itemName, enumItem); - Py_DECREF(enumItem); - return ok >= 0; -} - -bool createScopedEnumItem(PyTypeObject *enumType, SbkObjectType *scope, const char *itemName, long itemValue) -{ - return createScopedEnumItem(enumType, reinterpret_cast<PyTypeObject *>(scope), itemName, itemValue); -} + // Walk the enumItemStrings and create a Python enum type. + auto *pyName = name.object(); + + // We now create the new type. Since Python 3.11, we need to pass in + // `boundary=KEEP` because the default STRICT crashes on us. + // See QDir.Filter.Drives | QDir.Filter.Files + AutoDecRef callArgs(Py_BuildValue("(OO)", pyName, pyEnumItems)); + AutoDecRef callDict(PyDict_New()); + static PyObject *boundary = String::createStaticString("boundary"); + if (PyFlag_KEEP) + PyDict_SetItem(callDict, boundary, PyFlag_KEEP); + auto *obNewType = PyObject_Call(PyEnumType, callArgs, callDict); + if (!obNewType || PyObject_SetAttr(scopeOrModule, pyName, obNewType) < 0) + return nullptr; -PyObject * -newItem(PyTypeObject *enumType, long itemValue, const char *itemName) -{ - bool newValue = true; - SbkEnumObject *enumObj; - if (!itemName) { - enumObj = reinterpret_cast<SbkEnumObject *>( - getEnumItemFromValue(enumType, itemValue)); - if (enumObj) - return reinterpret_cast<PyObject *>(enumObj); - - newValue = false; + // For compatibility with Qt enums, provide a permissive missing method for (Int)?Enum. + if (!isFlag) { + bool supportMissing = !(Enum::enumOption & Enum::ENOPT_NO_MISSING); + if (supportMissing) { + AutoDecRef enum_missing(create_missing_func(obNewType)); + PyObject_SetAttrString(obNewType, "_missing_", enum_missing); + } } - enumObj = PyObject_New(SbkEnumObject, enumType); - if (!enumObj) - return nullptr; - - enumObj->ob_name = itemName ? PyBytes_FromString(itemName) : nullptr; - enumObj->ob_value = itemValue; - - if (newValue) { - auto dict = enumType->tp_dict; // Note: 'values' is borrowed - PyObject *values = PyDict_GetItemWithError(dict, Shiboken::PyName::values()); - if (values == nullptr) { - if (PyErr_Occurred()) - return nullptr; - Shiboken::AutoDecRef new_values(values = PyDict_New()); - if (values == nullptr) - return nullptr; - if (PyDict_SetItem(dict, Shiboken::PyName::values(), values) < 0) - return nullptr; + auto *newType = reinterpret_cast<PyTypeObject *>(obNewType); + PyObject_SetAttr(obNewType, PyMagicName::qualname(), qualname); + PyObject_SetAttr(obNewType, PyMagicName::module(), module); + + // See if we should re-introduce shortcuts in the enclosing object. + const bool useGlobalShortcut = (Enum::enumOption & Enum::ENOPT_GLOBAL_SHORTCUT) != 0; + const bool useScopedShortcut = (Enum::enumOption & Enum::ENOPT_SCOPED_SHORTCUT) != 0; + if (useGlobalShortcut || useScopedShortcut) { + // We have to use the iterator protokol because the values dict is a mappingproxy. + AutoDecRef values(PyObject_GetAttr(obNewType, PyMagicName::members())); + AutoDecRef mapIterator(PyObject_GetIter(values)); + AutoDecRef mapKey{}; + bool isModule = PyModule_Check(scopeOrModule); + while ((mapKey.reset(PyIter_Next(mapIterator))), mapKey.object()) { + if ((useGlobalShortcut && isModule) || (useScopedShortcut && !isModule)) { + AutoDecRef value(PyObject_GetItem(values, mapKey)); + if (PyObject_SetAttr(scopeOrModule, mapKey, value) < 0) + return nullptr; + } } - PyDict_SetItemString(values, itemName, reinterpret_cast<PyObject *>(enumObj)); } - return reinterpret_cast<PyObject *>(enumObj); + return newType; } -} // namespace Shiboken -} // namespace Enum - -static PyType_Slot SbkNewEnum_slots[] = { - {Py_tp_repr, (void *)SbkEnumObject_repr}, - {Py_tp_str, (void *)SbkEnumObject_repr}, - {Py_tp_getset, (void *)SbkEnumGetSetList}, - {Py_tp_methods, (void *)SbkEnumObject_Methods}, - {Py_tp_new, (void *)SbkEnum_tp_new}, - {Py_nb_add, (void *)enum_add}, - {Py_nb_subtract, (void *)enum_subtract}, - {Py_nb_multiply, (void *)enum_multiply}, - {Py_nb_positive, (void *)enum_int}, - {Py_nb_bool, (void *)enum_bool}, - {Py_nb_and, (void *)enum_and}, - {Py_nb_xor, (void *)enum_xor}, - {Py_nb_or, (void *)enum_or}, - {Py_nb_int, (void *)enum_int}, - {Py_nb_index, (void *)enum_int}, - {Py_tp_richcompare, (void *)enum_richcompare}, - {Py_tp_hash, (void *)enum_hash}, - {Py_tp_dealloc, (void *)enum_object_dealloc}, - {0, nullptr} -}; -static PyType_Spec SbkNewEnum_spec = { - "1:Shiboken.Enum", - sizeof(SbkEnumObject), - 0, - Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_CHECKTYPES, - SbkNewEnum_slots, -}; - -static PyTypeObject *SbkEnum_TypeF() +template <typename IntT> +static PyObject *toPyObject(IntT v) { - static auto type = SbkType_FromSpec(&SbkNewEnum_spec); - return reinterpret_cast<PyTypeObject *>(type); + if constexpr (sizeof(IntT) == 8) { + if constexpr (std::is_unsigned_v<IntT>) + return PyLong_FromUnsignedLongLong(v); + return PyLong_FromLongLong(v); + } + if constexpr (std::is_unsigned_v<IntT>) + return PyLong_FromUnsignedLong(v); + return PyLong_FromLong(v); +} + +template <typename IntT> +static PyTypeObject *createPythonEnumHelper(PyObject *module, + const char *fullName, const char *enumItemStrings[], const IntT enumValues[]) +{ + AutoDecRef args(PyList_New(0)); + auto *pyEnumItems = args.object(); + for (size_t idx = 0; enumItemStrings[idx] != nullptr; ++idx) { + const char *kv = enumItemStrings[idx]; + auto *key = PyUnicode_FromString(kv); + auto *value = toPyObject(enumValues[idx]); + auto *key_value = PyTuple_New(2); + PyTuple_SET_ITEM(key_value, 0, key); + PyTuple_SET_ITEM(key_value, 1, value); + PyList_Append(pyEnumItems, key_value); + } + return createEnumForPython(module, fullName, pyEnumItems); } -namespace Shiboken { namespace Enum { +// Now we have to concretize these functions explicitly, +// otherwise templates will not work across modules. -static void -copyNumberMethods(PyTypeObject *flagsType, - PyType_Slot number_slots[], - int *pidx) +PyTypeObject *createPythonEnum(PyObject *module, + const char *fullName, const char *enumItemStrings[], const int64_t enumValues[]) { - int idx = *pidx; -#define PUT_SLOT(name) \ - number_slots[idx].slot = (name); \ - number_slots[idx].pfunc = PyType_GetSlot(flagsType, (name)); \ - ++idx; - - PUT_SLOT(Py_nb_absolute); - PUT_SLOT(Py_nb_add); - PUT_SLOT(Py_nb_and); - PUT_SLOT(Py_nb_bool); - PUT_SLOT(Py_nb_divmod); - PUT_SLOT(Py_nb_float); - PUT_SLOT(Py_nb_floor_divide); - PUT_SLOT(Py_nb_index); - PUT_SLOT(Py_nb_inplace_add); - PUT_SLOT(Py_nb_inplace_and); - PUT_SLOT(Py_nb_inplace_floor_divide); - PUT_SLOT(Py_nb_inplace_lshift); - PUT_SLOT(Py_nb_inplace_multiply); - PUT_SLOT(Py_nb_inplace_or); - PUT_SLOT(Py_nb_inplace_power); - PUT_SLOT(Py_nb_inplace_remainder); - PUT_SLOT(Py_nb_inplace_rshift); - PUT_SLOT(Py_nb_inplace_subtract); - PUT_SLOT(Py_nb_inplace_true_divide); - PUT_SLOT(Py_nb_inplace_xor); - PUT_SLOT(Py_nb_int); - PUT_SLOT(Py_nb_invert); - PUT_SLOT(Py_nb_lshift); - PUT_SLOT(Py_nb_multiply); - PUT_SLOT(Py_nb_negative); - PUT_SLOT(Py_nb_or); - PUT_SLOT(Py_nb_positive); - PUT_SLOT(Py_nb_power); - PUT_SLOT(Py_nb_remainder); - PUT_SLOT(Py_nb_rshift); - PUT_SLOT(Py_nb_subtract); - PUT_SLOT(Py_nb_true_divide); - PUT_SLOT(Py_nb_xor); -#undef PUT_SLOT - *pidx = idx; -} - -PyTypeObject * -newTypeWithName(const char *name, - const char *cppName, - PyTypeObject *numbers_fromFlag) -{ - // Careful: SbkType_FromSpec does not allocate the string. - PyType_Slot newslots[99] = {}; // enough but not too big for the stack - PyType_Spec newspec; - newspec.name = strdup(name); - newspec.basicsize = SbkNewEnum_spec.basicsize; - newspec.itemsize = SbkNewEnum_spec.itemsize; - newspec.flags = SbkNewEnum_spec.flags; - // we must append all the number methods, so rebuild everything: - int idx = 0; - while (SbkNewEnum_slots[idx].slot) { - newslots[idx].slot = SbkNewEnum_slots[idx].slot; - newslots[idx].pfunc = SbkNewEnum_slots[idx].pfunc; - ++idx; - } - if (numbers_fromFlag) - copyNumberMethods(numbers_fromFlag, newslots, &idx); - newspec.slots = newslots; - Shiboken::AutoDecRef bases(PyTuple_New(1)); - static auto basetype = reinterpret_cast<PyObject *>(SbkEnum_TypeF()); - Py_INCREF(basetype); - PyTuple_SetItem(bases, 0, basetype); - auto *type = reinterpret_cast<PyTypeObject *>(SbkType_FromSpecWithBases(&newspec, bases)); - Py_TYPE(type) = SbkEnumType_TypeF(); - - auto *enumType = reinterpret_cast<SbkEnumType *>(type); - PepType_SETP(enumType)->cppName = cppName; - PepType_SETP(enumType)->converterPtr = &PepType_SETP(enumType)->converter; - DeclaredEnumTypes::instance().addEnumType(type); - return type; + return createPythonEnumHelper(module, fullName, enumItemStrings, enumValues); } -const char *getCppName(PyTypeObject *enumType) +PyTypeObject *createPythonEnum(PyObject *module, + const char *fullName, const char *enumItemStrings[], const uint64_t enumValues[]) { - assert(Py_TYPE(enumType) == SbkEnumType_TypeF()); - return PepType_SETP(reinterpret_cast<SbkEnumType *>(enumType))->cppName; + return createPythonEnumHelper(module, fullName, enumItemStrings, enumValues); } -long int getValue(PyObject *enumItem) +PyTypeObject *createPythonEnum(PyObject *module, + const char *fullName, const char *enumItemStrings[], const int32_t enumValues[]) { - assert(Shiboken::Enum::check(enumItem)); - return reinterpret_cast<SbkEnumObject *>(enumItem)->ob_value; + return createPythonEnumHelper(module, fullName, enumItemStrings, enumValues); } -void setTypeConverter(PyTypeObject *enumType, SbkConverter *converter) +PyTypeObject *createPythonEnum(PyObject *module, + const char *fullName, const char *enumItemStrings[], const uint32_t enumValues[]) { - //reinterpret_cast<SbkEnumType *>(enumType)->converter = converter; - *PepType_SGTP(enumType)->converter = converter; + return createPythonEnumHelper(module, fullName, enumItemStrings, enumValues); } -SbkConverter *getTypeConverter(PyTypeObject *enumType) +PyTypeObject *createPythonEnum(PyObject *module, + const char *fullName, const char *enumItemStrings[], const int16_t enumValues[]) { - //return reinterpret_cast<SbkEnumType *>(enumType)->converter; - return *PepType_SGTP(enumType)->converter; + return createPythonEnumHelper(module, fullName, enumItemStrings, enumValues); } -} // namespace Enum - -DeclaredEnumTypes &DeclaredEnumTypes::instance() +PyTypeObject *createPythonEnum(PyObject *module, + const char *fullName, const char *enumItemStrings[], const uint16_t enumValues[]) { - static DeclaredEnumTypes me; - return me; + return createPythonEnumHelper(module, fullName, enumItemStrings, enumValues); } -DeclaredEnumTypes::DeclaredEnumTypes() = default; - -DeclaredEnumTypes::~DeclaredEnumTypes() +PyTypeObject *createPythonEnum(PyObject *module, + const char *fullName, const char *enumItemStrings[], const int8_t enumValues[]) { - /* - * PYSIDE-595: This was "delete *it;" before introducing 'SbkType_FromSpec'. - * XXX what should I do now? - * Refcounts in tests are 30 or 0 at end. - * When I add the default tp_dealloc, we get negative refcounts! - * So right now I am doing nothing. Surely wrong but no crash. - * See also the comment in function 'createGlobalEnumItem'. - */ - // for (PyTypeObject *o : m_enumTypes) - // fprintf(stderr, "ttt %d %s\n", Py_REFCNT(o), o->tp_name); - m_enumTypes.clear(); + return createPythonEnumHelper(module, fullName, enumItemStrings, enumValues); } -void DeclaredEnumTypes::addEnumType(PyTypeObject *type) +PyTypeObject *createPythonEnum(PyObject *module, + const char *fullName, const char *enumItemStrings[], const uint8_t enumValues[]) { - m_enumTypes.push_back(type); + return createPythonEnumHelper(module, fullName, enumItemStrings, enumValues); } -} +} // namespace Shiboken::Enum |