diff options
author | Christian Tismer <tismer@stackless.com> | 2022-04-10 17:22:14 +0200 |
---|---|---|
committer | Christian Tismer <tismer@stackless.com> | 2022-05-23 22:45:33 +0200 |
commit | 8472a2b6eb32e6238e82f5778add908652b2cd82 (patch) | |
tree | 8fd91deb2d6114c0fcf77807b5c71e43acd206c4 | |
parent | 79a23bc22ad635531c242280627d942e86fcfd45 (diff) |
PyEnum: Prepare to support both implementations
The enum implementation should be switchable between the
old and the new version. This switching is possible only
before PySide import.
This patch prepares the switching capability for the
signature module and installs fields that will affect
the global header files.
The new version can be selected by setting the environment
variable
PYSIDE63_OPTION_PYTHON_ENUM=1
or setting sys.pyside63_option_python_enum=1
[ChangeLog][PySide6] The signature module was prepared to support
both C++ enums and Python enums. This can be selected at startup.
Task-number: PYSIDE-1735
Change-Id: I14999e1049fbaaccd00f00d1b7b1257bc9287255
Reviewed-by: Friedemann Kleint <Friedemann.Kleint@qt.io>
(cherry picked from commit 31deae2a0ea3725d038433f6be91119865a8f399)
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
8 files changed, 214 insertions, 38 deletions
diff --git a/sources/pyside6/libpyside/pysideqflags.cpp b/sources/pyside6/libpyside/pysideqflags.cpp index cd57b2bb9..84e87f038 100644 --- a/sources/pyside6/libpyside/pysideqflags.cpp +++ b/sources/pyside6/libpyside/pysideqflags.cpp @@ -41,6 +41,7 @@ #include <autodecref.h> #include <sbkenum.h> +#include <sbkenum_p.h> extern "C" { struct SbkConverter; diff --git a/sources/shiboken6/ApiExtractor/abstractmetatype.cpp b/sources/shiboken6/ApiExtractor/abstractmetatype.cpp index b5fa4100c..d47b25ef2 100644 --- a/sources/shiboken6/ApiExtractor/abstractmetatype.cpp +++ b/sources/shiboken6/ApiExtractor/abstractmetatype.cpp @@ -668,8 +668,16 @@ QString AbstractMetaTypeData::formatPythonSignature() const result += TypeInfo::indirectionKeyword(i); // If it is a flags type, we replace it with the full name: // "PySide6.QtCore.Qt.ItemFlags" instead of "PySide6.QtCore.QFlags<Qt.ItemFlag>" - if (m_typeEntry->isFlags()) - result = m_typeEntry->qualifiedTargetLangName(); + if (m_typeEntry->isFlags()) { + // PYSIDE-1735: We need to provide both the flags type and the original enum type + // as a choice at runtime. + auto flagsTypeEntry = static_cast<const FlagsTypeEntry *>(m_typeEntry); + auto enumTypeEntry = flagsTypeEntry->originator(); + result = m_typeEntry->targetLangPackage() + u".^^"_s + + flagsTypeEntry->targetLangName() + u"^^"_s + + enumTypeEntry->targetLangName() + u"^^"_s; + } + result.replace(u"::"_s, u"."_s); return result; } diff --git a/sources/shiboken6/generator/shiboken/cppgenerator.cpp b/sources/shiboken6/generator/shiboken/cppgenerator.cpp index 60f89666e..26a5340b5 100644 --- a/sources/shiboken6/generator/shiboken/cppgenerator.cpp +++ b/sources/shiboken6/generator/shiboken/cppgenerator.cpp @@ -4193,7 +4193,7 @@ void CppGenerator::writeEnumConverterInitialization(TextStream &s, const TypeEnt if (!enumType) return; QString enumFlagName = enumType->isFlags() ? u"flag"_s : u"enum"_s; - QString enumPythonType = cpythonTypeNameExt(enumType); + QString enumPythonVar = enumType->isFlags() ? u"FType"_s : u"EType"_s; const FlagsTypeEntry *flags = nullptr; if (enumType->isFlags()) @@ -4205,7 +4205,7 @@ void CppGenerator::writeEnumConverterInitialization(TextStream &s, const TypeEnt Indentation indent(s); QString typeName = fixedCppTypeName(enumType); s << "SbkConverter *converter = Shiboken::Conversions::createConverter(" - << enumPythonType << ',' << '\n'; + << enumPythonVar << ',' << '\n'; { Indentation indent(s); s << cppToPythonFunctionName(typeName, typeName) << ");\n"; @@ -4228,7 +4228,7 @@ void CppGenerator::writeEnumConverterInitialization(TextStream &s, const TypeEnt writeAddPythonToCppConversion(s, u"converter"_s, toCpp, isConv); } - s << "Shiboken::Enum::setTypeConverter(" << enumPythonType + s << "Shiboken::Enum::setTypeConverter(" << enumPythonVar << ", converter, " << (enumType->isFlags() ? "true" : "false") << ");\n"; QString signature = enumType->qualifiedCppName(); @@ -5417,10 +5417,21 @@ void CppGenerator::writeEnumsInitialization(TextStream &s, AbstractMetaEnumList { if (enums.isEmpty()) return; - s << "// Initialization of enums.\n\n"; + bool preambleWrittenE = false; + bool preambleWrittenF = false; for (const AbstractMetaEnum &cppEnum : qAsConst(enums)) { if (cppEnum.isPrivate()) continue; + if (!preambleWrittenE) { + s << "// Initialization of enums.\n" + << "PyTypeObject *EType{};\n\n"; + preambleWrittenE = true; + } + if (!preambleWrittenF && cppEnum.typeEntry()->flags()) { + s << "// Initialization of enums, flags part.\n" + << "PyTypeObject *FType{};\n\n"; + preambleWrittenF = true; + } writeEnumInitialization(s, cppEnum, errorReturn); } } @@ -5436,7 +5447,8 @@ void CppGenerator::writeEnumInitialization(TextStream &s, const AbstractMetaEnum ErrorReturn errorReturn) const { const AbstractMetaClass *enclosingClass = cppEnum.targetLangEnclosingClass(); - bool hasUpperEnclosingClass = enclosingClass && enclosingClass->targetLangEnclosingClass() != nullptr; + bool hasUpperEnclosingClass = enclosingClass + && enclosingClass->targetLangEnclosingClass() != nullptr; const EnumTypeEntry *enumTypeEntry = cppEnum.typeEntry(); QString enclosingObjectVariable; if (enclosingClass) @@ -5450,7 +5462,7 @@ void CppGenerator::writeEnumInitialization(TextStream &s, const AbstractMetaEnum s << (cppEnum.isAnonymous() ? "anonymous enum identified by enum value" : "enum"); s << " '" << cppEnum.name() << "'.\n"; - QString enumVarTypeObj; + QString enumVarTypeObj = cpythonTypeNameExt(enumTypeEntry); if (!cppEnum.isAnonymous()) { int packageLevel = packageName().count(u'.') + 1; FlagsTypeEntry *flags = enumTypeEntry->flags(); @@ -5459,15 +5471,15 @@ void CppGenerator::writeEnumInitialization(TextStream &s, const AbstractMetaEnum // We need 'flags->flagsName()' with the full module/class path. QString fullPath = getClassTargetFullName(cppEnum); fullPath.truncate(fullPath.lastIndexOf(u'.') + 1); - s << cpythonTypeNameExt(flags) << " = PySide::QFlags::create(\"" - << packageLevel << ':' << fullPath << flags->flagsName() << "\", " - << cpythonEnumName(cppEnum) << "_number_slots);\n"; + s << "FType = PySide::QFlags::create(\"" + << packageLevel << ':' << fullPath << flags->flagsName() << "\", \n" << indent + << cpythonEnumName(cppEnum) << "_number_slots);\n" << outdent + << cpythonTypeNameExt(flags) << " = FType;\n"; } - enumVarTypeObj = cpythonTypeNameExt(enumTypeEntry); - - s << enumVarTypeObj << " = Shiboken::Enum::" - << ((enclosingClass || hasUpperEnclosingClass) ? "createScopedEnum" : "createGlobalEnum") + s << "EType = Shiboken::Enum::" + << ((enclosingClass + || hasUpperEnclosingClass) ? "createScopedEnum" : "createGlobalEnum") << '(' << enclosingObjectVariable << ',' << '\n'; { Indentation indent(s); @@ -5475,10 +5487,10 @@ void CppGenerator::writeEnumInitialization(TextStream &s, const AbstractMetaEnum << '"' << packageLevel << ':' << getClassTargetFullName(cppEnum) << "\",\n" << '"' << cppEnum.qualifiedCppName() << '"'; if (flags) - s << ",\n" << cpythonTypeNameExt(flags); + s << ",\nFType"; s << ");\n"; } - s << "if (!" << cpythonTypeNameExt(cppEnum.typeEntry()) << ")\n" + s << "if (!EType)\n" << indent << errorReturn << outdent << '\n'; } @@ -5521,8 +5533,9 @@ void CppGenerator::writeEnumInitialization(TextStream &s, const AbstractMetaEnum break; case CEnum: { s << "if (!Shiboken::Enum::"; - s << ((enclosingClass || hasUpperEnclosingClass) ? "createScopedEnumItem" : "createGlobalEnumItem"); - s << '(' << enumVarTypeObj << ',' << '\n'; + s << ((enclosingClass || hasUpperEnclosingClass) ? "createScopedEnumItem" + : "createGlobalEnumItem"); + s << '(' << "EType" << ',' << '\n'; Indentation indent(s); s << enclosingObjectVariable << ", \"" << mangledName << "\", " << enumValueText << "))\n" << errorReturn; @@ -5530,15 +5543,22 @@ void CppGenerator::writeEnumInitialization(TextStream &s, const AbstractMetaEnum break; case EnumClass: { s << "if (!Shiboken::Enum::createScopedEnumItem(" - << enumVarTypeObj << ',' << '\n'; + << "EType" << ",\n"; Indentation indentation(s); - s << enumVarTypeObj<< ", \"" << mangledName << "\", " + s << "EType" << ", \"" << mangledName << "\", " << enumValueText << "))\n" << errorReturn; } break; } } - + s << "// PYSIDE-1735: Resolving the whole enum class at the end for API compatibility.\n" + << "EType = morphLastEnumToPython();\n" + << enumVarTypeObj << " = EType;\n"; + if (cppEnum.typeEntry()->flags()) { + s << "// PYSIDE-1735: Mapping the flags class to the same enum class.\n" + << cpythonTypeNameExt(cppEnum.typeEntry()->flags()) << " =\n" + << indent << "mapFlagsToSameEnum(FType, EType);\n" << outdent; + } writeEnumConverterInitialization(s, cppEnum); s << "// End of '" << cppEnum.name() << "' enum"; diff --git a/sources/shiboken6/libshiboken/sbkenum.cpp b/sources/shiboken6/libshiboken/sbkenum.cpp index 211b131a4..1ec21f81e 100644 --- a/sources/shiboken6/libshiboken/sbkenum.cpp +++ b/sources/shiboken6/libshiboken/sbkenum.cpp @@ -59,6 +59,17 @@ static void cleanupEnumTypes(); extern "C" { +// forward +struct lastEnumCreated; + +// forward +static PyTypeObject *recordCurrentEnum(PyObject *scopeOrModule, + const char *name, + const char *fullName, + const char *cppName, + PyTypeObject *enumType, + PyTypeObject *flagsType); + struct SbkEnumType { PyTypeObject type; @@ -119,7 +130,7 @@ static const char *SbkEnum_SignatureStrings[] = { "Shiboken.Enum(self,itemValue:int=0)", nullptr}; // Sentinel -void enum_object_dealloc(PyObject *ob) +static void enum_object_dealloc(PyObject *ob) { auto *self = reinterpret_cast<SbkEnumObject *>(ob); Py_XDECREF(self->ob_name); @@ -485,13 +496,15 @@ static PyTypeObject *createEnum(const char *fullName, const char *cppName, return enumType; } -PyTypeObject *createGlobalEnum(PyObject *module, const char *name, const char *fullName, const char *cppName, PyTypeObject *flagsType) +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; } + flagsType = recordCurrentEnum(module, name, fullName, cppName, enumType, flagsType); if (flagsType && PyModule_AddObject(module, PepType_GetNameStr(flagsType), reinterpret_cast<PyObject *>(flagsType)) < 0) { Py_DECREF(enumType); @@ -500,7 +513,8 @@ PyTypeObject *createGlobalEnum(PyObject *module, const char *name, const char *f return enumType; } -PyTypeObject *createScopedEnum(PyTypeObject *scope, const char *name, const char *fullName, const char *cppName, PyTypeObject *flagsType) +PyTypeObject *createScopedEnum(PyTypeObject *scope, const char *name, const char *fullName, + const char *cppName, PyTypeObject *flagsType) { PyTypeObject *enumType = createEnum(fullName, cppName, flagsType); if (enumType && PyDict_SetItemString(scope->tp_dict, name, @@ -508,6 +522,8 @@ PyTypeObject *createScopedEnum(PyTypeObject *scope, const char *name, const char Py_DECREF(enumType); return nullptr; } + auto *obScope = reinterpret_cast<PyObject *>(scope); + flagsType = recordCurrentEnum(obScope, name, fullName, cppName, enumType, flagsType); if (flagsType && PyDict_SetItemString(scope->tp_dict, PepType_GetNameStr(flagsType), reinterpret_cast<PyObject *>(flagsType)) < 0) { @@ -516,7 +532,6 @@ PyTypeObject *createScopedEnum(PyTypeObject *scope, const char *name, const char } return enumType; } - static PyObject *createEnumItem(PyTypeObject *enumType, const char *itemName, long itemValue) { PyObject *enumItem = newItem(enumType, itemValue, itemName); @@ -675,6 +690,8 @@ copyNumberMethods(PyTypeObject *flagsType, *pidx = idx; } +// PySIDE-1735: This function is in the API. Support it with the new enums. +// PyTypeObject * newTypeWithName(const char *name, const char *cppName, @@ -769,3 +786,66 @@ static void cleanupEnumTypes() Shiboken::DeclaredEnumTypes::instance().cleanup(); } +/////////////////////////////////////////////////////////////////////// +// +// PYSIDE-1735: Re-implementation of Enums using Python +// ==================================================== +// +// This is a very simple, first implementation of a replacement +// for the Qt-like Enums using the Python Enum module. +// +// The basic idea: +// --------------- +// * We create the Enums as always +// * After creation of each enum, a special function is called that +// * grabs the last generated enum +// * reads all Enum items +// * generates a class statement for the Python Enum +// * creates a new Python Enum class +// * replaces the already inserted Enum with the new one. +// +// There are lots of ways to optimize that. Will be added later. +// +extern "C" { + +struct lastEnumCreated { + PyObject *scopeOrModule; + const char *name; + const char *fullName; + const char *cppName; + PyTypeObject *enumType; + PyTypeObject *flagsType; +}; + +static lastEnumCreated lec{}; + +static PyTypeObject *recordCurrentEnum(PyObject *scopeOrModule, + const char *name, + const char *fullName, + const char *cppName, + PyTypeObject *enumType, + PyTypeObject *flagsType) +{ + lec.scopeOrModule = scopeOrModule; + lec.name = name; + lec.fullName = fullName; + lec.cppName = cppName; + lec.enumType = enumType; + lec.flagsType = flagsType; + // We later return nullptr as flagsType to disable flag creation. + return flagsType; +} + +PyTypeObject *morphLastEnumToPython() +{ + // to be implemented... + return lec.enumType; +} + +PyTypeObject *mapFlagsToSameEnum(PyTypeObject *FType, PyTypeObject *EType) +{ + // this will be switchable... + return FType; +} + +} // extern "C" diff --git a/sources/shiboken6/libshiboken/sbkenum_p.h b/sources/shiboken6/libshiboken/sbkenum_p.h index 895c1ddcb..eeb505b34 100644 --- a/sources/shiboken6/libshiboken/sbkenum_p.h +++ b/sources/shiboken6/libshiboken/sbkenum_p.h @@ -47,6 +47,15 @@ struct SbkEnumTypePrivate { SbkConverter *converter; const char *cppName; + PyTypeObject *replacementType; }; +extern "C" { + +/// PYSIDE-1735: Patching the Enum / Flags implementation. Remove in 6.4 +LIBSHIBOKEN_API PyTypeObject *morphLastEnumToPython(); +LIBSHIBOKEN_API PyTypeObject *mapFlagsToSameEnum(PyTypeObject *FType, PyTypeObject *EType); + +} + #endif // SKB_PYENUM_P_H diff --git a/sources/shiboken6/libshiboken/shiboken.h b/sources/shiboken6/libshiboken/shiboken.h index 3e1df5235..283590565 100644 --- a/sources/shiboken6/libshiboken/shiboken.h +++ b/sources/shiboken6/libshiboken/shiboken.h @@ -50,6 +50,7 @@ #include "sbkarrayconverter.h" #include "sbkconverter.h" #include "sbkenum.h" +#include "sbkenum_p.h" // PYSIDE-1735: This is during the migration, only. #include "sbkmodule.h" #include "sbkstring.h" #include "sbkstaticstrings.h" diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/mapping.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/mapping.py index d714eb09c..c069d644a 100644 --- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/mapping.py +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/mapping.py @@ -417,6 +417,7 @@ def init_sample(): type_map.update({ "char": int, "char**": typing.List[str], + "const char*": str, "Complex": complex, "double": float, "Foo.HANDLE": int, @@ -426,6 +427,7 @@ def init_sample(): "OddBool": bool, "PStr": str, "PyDate": datetime.date, + "PyBuffer": bytes, "sample.bool": bool, "sample.char": int, "sample.double": float, @@ -436,6 +438,7 @@ def init_sample(): "sample.Photon.TemplateBase[Photon.IdentityType]": sample.Photon.ValueIdentity, "sample.Point": Point, "sample.PStr": str, + "SampleNamespace.InValue.ZeroIn": 0, "sample.unsigned char": int, "std.size_t": int, "std.string": str, @@ -591,6 +594,8 @@ def init_PySide6_QtWidgets(): "QWidget.RenderFlags(QWidget.DrawWindowBackground | QWidget.DrawChildren)"), "static_cast<Qt.MatchFlags>(Qt.MatchExactly|Qt.MatchCaseSensitive)": Instance( "Qt.MatchFlags(Qt.MatchExactly | Qt.MatchCaseSensitive)"), + "static_cast<Qt.MatchFlag>(Qt.MatchExactly|Qt.MatchCaseSensitive)": Instance( + "Qt.MatchFlag(Qt.MatchExactly | Qt.MatchCaseSensitive)"), "QListWidgetItem.ItemType.Type": PySide6.QtWidgets.QListWidgetItem.Type, "QTableWidgetItem.ItemType.Type": PySide6.QtWidgets.QTableWidgetItem.Type, "QTreeWidgetItem.ItemType.Type": PySide6.QtWidgets.QTreeWidgetItem.Type, diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/parser.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/parser.py index 84c42dd11..4f8bdb8b9 100644 --- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/parser.py +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/parser.py @@ -37,13 +37,15 @@ ## ############################################################################# -import sys +import enum +import functools +import keyword +import os import re -import warnings +import sys import types import typing -import keyword -import functools +import warnings from types import SimpleNamespace from shibokensupport.signature.mapping import (type_map, update_mapping, @@ -74,6 +76,36 @@ guesses, we provide an entry in 'type_map' that resolves it. In effect, 'type_map' maps text to real Python objects. """ +def _get_flag_enum_option(): + flag = False # XXX get default out of version number? + envname = "PYSIDE63_OPTION_PYTHON_ENUM" + sysname = envname.lower() + opt = os.environ.get(envname) + if opt: + opt = opt.lower() + if opt in ("yes", "on", "true"): + flag = True + elif opt in ("no", "off", "false"): + flag = False + elif opt.isnumeric(): + flag = bool(int(opt)) + elif hasattr(sys, sysname): + flag = bool(getattr(sys, sysname)) + # modify the sys attribute to bool + setattr(sys, sysname, flag) + # modify the env attribute to "0" or "1" + os.environ[envname] = str(int(flag)) + return flag + + +class EnumSelect(enum.Enum): + # PYSIDE-1735: Here we could save object.value expressions by using IntEnum. + # But it is nice to use just an Enum for selecting Enum version. + OLD = 1 + NEW = 2 + SELECTION = NEW if _get_flag_enum_option() else OLD + + def dprint(*args, **kw): if _DEBUG: import pprint @@ -189,10 +221,11 @@ def make_good_value(thing, valtype): if thing.endswith("()"): thing = f'Default("{thing[:-2]}")' else: - ret = eval(thing, namespace) + # PYSIDE-1735: Use explicit globals and locals because of a bug in VsCode + ret = eval(thing, globals(), namespace) if valtype and repr(ret).startswith("<"): thing = f'Instance("{thing}")' - return eval(thing, namespace) + return eval(thing, globals(), namespace) except Exception: pass @@ -264,12 +297,18 @@ def _resolve_arraytype(thing, line): def to_string(thing): + # This function returns a string that creates the same object. + # It is absolutely crucial that str(eval(thing)) == str(thing), + # i.e. it must be an idempotent mapping. if isinstance(thing, str): return thing if hasattr(thing, "__name__") and thing.__module__ != "typing": - dot = "." in str(thing) + m = thing.__module__ + dot = "." in str(thing) or m not in (thing.__qualname__, "builtins") name = get_name(thing) - return thing.__module__ + "." + name if dot else name + ret = m + "." + name if dot else name + assert(eval(ret, globals(), namespace)) + return ret # Note: This captures things from the typing module: return str(thing) @@ -280,7 +319,8 @@ def handle_matrix(arg): n, m, typstr = tuple(map(lambda x:x.strip(), arg.split(","))) assert typstr == "float" result = f"PySide6.QtGui.QMatrix{n}x{m}" - return eval(result, namespace) + return eval(result, globals(), namespace) + def _resolve_type(thing, line, level, var_handler, func_name=None): # manual set of 'str' instead of 'bytes' @@ -319,7 +359,7 @@ def _resolve_type(thing, line, level, var_handler, func_name=None): result = f"{contr}[{thing}]" # PYSIDE-1538: Make sure that the eval does not crash. try: - return eval(result, namespace) + return eval(result, globals(), namespace) except Exception as e: warnings.warn(f"""pyside_type_init:_resolve_type @@ -370,6 +410,18 @@ def handle_retvar(obj): def calculate_props(line): + # PYSIDE-1735: QFlag is now divided into fields for future Python Enums, like + # "PySide.QtCore.^^Qt.ItemFlags^^Qt.ItemFlag^^" + # Resolve that until Enum is finally settled. + while "^^" in line: + parts = line.split("^^", 3) + selected = EnumSelect.SELECTION + line = parts[0] + parts[selected.value] + parts[3] + if selected is EnumSelect.NEW: + _old, _new = EnumSelect.OLD.value, EnumSelect.NEW.value + line = re.sub(rf"\b{parts[_old]}\b", parts[_new], line) + type_map[parts[_old]] = parts[_new] + parsed = SimpleNamespace(**_parse_line(line.strip())) arglist = parsed.arglist annotations = {} @@ -378,7 +430,7 @@ def calculate_props(line): name, ann = tup[:2] if ann == "...": name = "*args" if name.startswith("arg_") else "*" + name - # copy the pathed fields back + # copy the patched fields back ann = 'nullptr' # maps to None tup = name, ann arglist[idx] = tup @@ -455,7 +507,7 @@ def fix_variables(props, line): else: retvars_str = ", ".join(map(to_string, retvars)) typestr = f"typing.Tuple[{retvars_str}]" - returntype = eval(typestr, namespace) + returntype = eval(typestr, globals(), namespace) props.annotations["return"] = returntype props.varnames = tuple(varnames) props.defaults = tuple(defaults) |