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-30 14:06:05 +0200
commita4ee46632e19d9031dab36ac33bf78c3a8e7d35b (patch)
treef246b3ec54d3e7f571e4fa81f2f52483a2f2aeac
parentc431daa37d3a9684d10ba2cd1f130910b65fcc9a (diff)
PyEnum: Relax the Impact of New Enums and Make Everything Optional
This patch makes every PyEnum feature of PySide optional. It allows to test the whole functionality. Some flags might also make sense for people who cannot use the new enums without modifications. Maybe this should be there for now for internal use, only. The flags for PYSIDE63_OPTION_PYTHON_ENUM are (hex) 1 (True) the default for PySide 6.4, full implementation 2 turn all Enum into IntEnum and Flag into IntFlag 4 re-add shortcuts for global enums 8 re-add shortcuts for scoped enums 10 don't fake shortcuts (forgiveness mode) 20 don't fake rename (forgiveness mode) 40 don't use zero default (forgiveness mode) 80 don't allow missing values in Enum A startup setting of for instance PYSIDE63_OPTION_PYTHON_ENUM=6 should work in most cases, avoiding the fall-back to old enums. Task-number: PYSIDE-1735 Change-Id: I636c4d9f8e671f5185058820605da73f688c16b0 Pick-to: 6.3 Reviewed-by: Friedemann Kleint <Friedemann.Kleint@qt.io>
-rw-r--r--sources/pyside6/tests/pysidetest/CMakeLists.txt1
-rw-r--r--sources/pyside6/tests/pysidetest/pyenum_relax_options_test.py130
-rw-r--r--sources/shiboken6/libshiboken/sbkenum.cpp58
-rw-r--r--sources/shiboken6/libshiboken/sbkenum_p.h18
-rw-r--r--sources/shiboken6/libshiboken/sbkfeature_base.cpp119
-rw-r--r--sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/parser.py18
6 files changed, 269 insertions, 75 deletions
diff --git a/sources/pyside6/tests/pysidetest/CMakeLists.txt b/sources/pyside6/tests/pysidetest/CMakeLists.txt
index ee2a295fe..f7df67751 100644
--- a/sources/pyside6/tests/pysidetest/CMakeLists.txt
+++ b/sources/pyside6/tests/pysidetest/CMakeLists.txt
@@ -157,3 +157,4 @@ PYSIDE_TEST(signalwithdefaultvalue_test.py)
PYSIDE_TEST(typedef_signal_test.py)
PYSIDE_TEST(version_test.py)
PYSIDE_TEST(mock_as_slot_test.py)
+PYSIDE_TEST(pyenum_relax_options_test.py)
diff --git a/sources/pyside6/tests/pysidetest/pyenum_relax_options_test.py b/sources/pyside6/tests/pysidetest/pyenum_relax_options_test.py
new file mode 100644
index 000000000..0dcec5a4c
--- /dev/null
+++ b/sources/pyside6/tests/pysidetest/pyenum_relax_options_test.py
@@ -0,0 +1,130 @@
+#!/usr/bin/python
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+"""
+PYSIDE-1735: Testing different relax options for Enums
+
+This test uses different configurations and initializes QtCore with it.
+Because re-initialization is not possible, the test uses a subprocess
+for it. This makes the test pretty slow.
+
+Maybe we should implement a way to re-initialize QtCore enums without
+using subprocess, just to speed this up??
+"""
+
+import os
+import sys
+import unittest
+
+from pathlib import Path
+sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
+from init_paths import init_test_paths
+init_test_paths(False)
+
+import subprocess
+import tempfile
+from textwrap import dedent
+
+
+def runtest(program):
+ passed_path = os.fspath(Path(__file__).resolve().parents[1])
+ with tempfile.NamedTemporaryFile(mode="w+", delete=False, suffix=".py") as fp:
+ preamble = dedent(f"""
+ import os
+ import sys
+ from pathlib import Path
+ sys.path.append({passed_path!r})
+ from init_paths import init_test_paths
+ init_test_paths(False)
+ """)
+ print(preamble, program, file=fp)
+ fp.close()
+ try:
+ subprocess.run([sys.executable, fp.name], check=True, capture_output=True)
+ return True
+ except subprocess.CalledProcessError as e:
+ print(f"\ninfo: {e.__class__.__name__}: {e.stderr}")
+ return False
+ finally:
+ os.unlink(fp.name)
+
+def testprog2(option):
+ return runtest(dedent(f"""
+ sys.pyside63_option_python_enum = {option}
+ from PySide6 import QtCore
+ from enum import IntEnum
+ assert(issubclass(QtCore.Qt.DateFormat, IntEnum))
+ """))
+
+def testprog4(option):
+ return runtest(dedent(f"""
+ sys.pyside63_option_python_enum = {option}
+ from PySide6 import QtCore
+ QtCore.QtDebugMsg
+ """))
+
+def testprog8_16(option):
+ # this test needs flag 16, or the effect would be hidden by forgiving mode
+ return runtest(dedent(f"""
+ sys.pyside63_option_python_enum = {option}
+ from PySide6 import QtCore
+ QtCore.Qt.AlignTop
+ """))
+
+def testprog32(option):
+ return runtest(dedent(f"""
+ sys.pyside63_option_python_enum = {option}
+ from PySide6 import QtCore
+ QtCore.Qt.Alignment
+ """))
+
+def testprog64(option):
+ return runtest(dedent(f"""
+ sys.pyside63_option_python_enum = {option}
+ from PySide6 import QtCore
+ QtCore.Qt.AlignmentFlag()
+ """))
+
+def testprog128(option):
+ return runtest(dedent(f"""
+ sys.pyside63_option_python_enum = {option}
+ from PySide6 import QtCore
+ QtCore.Qt.Key(1234567)
+ """))
+
+
+class TestPyEnumRelaxOption(unittest.TestCase):
+ """
+ This test is a bit involved, because we cannot unload QtCore after it is loaded once.
+ We use subprocess to test different cases, anyway.
+ """
+
+ def test_enumIsIntEnum(self):
+ self.assertTrue(testprog2(2))
+ self.assertFalse(testprog2(4))
+
+ def test_globalDefault(self):
+ self.assertTrue(testprog4(4))
+ self.assertFalse(testprog4(1))
+ self.assertTrue(testprog4(12))
+
+ def test_localDefault(self):
+ self.assertTrue(testprog8_16(8+16))
+ self.assertFalse(testprog8_16(0+16))
+
+ def test_fakeRenames(self):
+ self.assertTrue(testprog32(1))
+ self.assertFalse(testprog32(32))
+
+ def test_zeroDefault(self):
+ self.assertTrue(testprog64(1))
+ self.assertFalse(testprog64(64))
+
+ def test_Missing(self):
+ self.assertTrue(testprog128(1))
+ self.assertFalse(testprog128(128))
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/sources/shiboken6/libshiboken/sbkenum.cpp b/sources/shiboken6/libshiboken/sbkenum.cpp
index 3b0757417..6c49b986c 100644
--- a/sources/shiboken6/libshiboken/sbkenum.cpp
+++ b/sources/shiboken6/libshiboken/sbkenum.cpp
@@ -365,6 +365,8 @@ PyObject *unpickleEnum(PyObject *enum_class_name, PyObject *value)
return PyObject_CallFunctionObjArgs(klass, value, nullptr);
}
+int enumOption{};
+
} // namespace Enum
} // namespace Shiboken
@@ -432,23 +434,25 @@ PyTypeObject *getPyEnumMeta()
void init_enum()
{
- static bool is_initialized = false;
- if (is_initialized)
+ static bool isInitialized = false;
+ if (isInitialized)
return;
- if (!(is_initialized || enum_unpickler || _init_enum()))
+ if (!(isInitialized || enum_unpickler || _init_enum()))
Py_FatalError("could not load enum pickling helper function");
Py_AtExit(cleanupEnumTypes);
// PYSIDE-1735: Determine whether we should use the old or the new enum implementation.
- static const char *envname = "PYSIDE63_OPTION_PYTHON_ENUM";
- const char *envsetting = getenv(envname);
- // I tried to use the save version getenv_s instead, but this function does not
- // exist on macOS. But this does no harm:
- // This variable has been set already by parser.py initialization.
- assert(envsetting);
- useOldEnum = strncmp(envsetting, "0", 10) == 0;
+ static PyObject *sysmodule = PyImport_AddModule("sys");
+ static PyObject *option = PyObject_GetAttrString(sysmodule, "pyside63_option_python_enum");
+ if (!option || !PyLong_Check(option)) {
+ PyErr_Clear();
+ option = PyLong_FromLong(0);
+ }
+ int ignoreOver{};
+ Enum::enumOption = PyLong_AsLongAndOverflow(option, &ignoreOver);
+ useOldEnum = Enum::enumOption == Enum::ENOPT_OLD_ENUM;
getPyEnumMeta();
- is_initialized = true;
+ isInitialized = true;
}
// PYSIDE-1735: Helper function supporting QEnum
@@ -996,7 +1000,7 @@ static bool is_old_version()
// 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)
+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.
@@ -1114,6 +1118,14 @@ PyTypeObject *morphLastEnumToPython()
assert(PyEnumType.object());
bool isFlag = PyObject_IsSubclass(PyEnumType, PyFlag);
+ // 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);
+ }
+
// Walk the values dict and create a Python enum type.
AutoDecRef name(PyUnicode_FromString(lec.name));
AutoDecRef args(PyList_New(0));
@@ -1142,8 +1154,11 @@ PyTypeObject *morphLastEnumToPython()
// 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);
+ bool supportMissing = !(Enum::enumOption & Enum::ENOPT_NO_MISSING);
+ if (supportMissing) {
+ AutoDecRef enum_missing(create_missing_func(obNewType));
+ PyObject_SetAttrString(obNewType, "_missing_", enum_missing);
+ }
}
auto *newType = reinterpret_cast<PyTypeObject *>(obNewType);
@@ -1152,6 +1167,21 @@ PyTypeObject *morphLastEnumToPython()
PyObject_SetAttr(obNewType, PyMagicName::qualname(), qual_name);
AutoDecRef module(PyObject_GetAttr(obEnumType, PyMagicName::module()));
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) {
+ bool isModule = PyModule_Check(scopeOrModule);
+ pos = 0;
+ while (PyDict_Next(values, &pos, &key, &value)) {
+ AutoDecRef entry(PyObject_GetAttr(obNewType, key));
+ if ((useGlobalShortcut && isModule) || (useScopedShortcut && !isModule))
+ if (PyObject_SetAttr(scopeOrModule, key, entry) < 0)
+ return nullptr;
+ }
+ }
+
// Protect against double initialization
setp->replacementType = newType;
diff --git a/sources/shiboken6/libshiboken/sbkenum_p.h b/sources/shiboken6/libshiboken/sbkenum_p.h
index 756aab2b5..d8477b4b3 100644
--- a/sources/shiboken6/libshiboken/sbkenum_p.h
+++ b/sources/shiboken6/libshiboken/sbkenum_p.h
@@ -32,4 +32,22 @@ LIBSHIBOKEN_API bool usingNewEnum();
}
+namespace Shiboken { namespace Enum {
+
+enum : int {
+ ENOPT_OLD_ENUM = 0x00,
+ ENOPT_NEW_ENUM = 0x01,
+ ENOPT_INHERIT_INT = 0x02,
+ ENOPT_GLOBAL_SHORTCUT = 0x04,
+ ENOPT_SCOPED_SHORTCUT = 0x08,
+ ENOPT_NO_FAKESHORTCUT = 0x10,
+ ENOPT_NO_FAKERENAMES = 0x20,
+ ENOPT_NO_ZERODEFAULT = 0x40,
+ ENOPT_NO_MISSING = 0x80,
+};
+
+LIBSHIBOKEN_API extern int enumOption;
+
+}}
+
#endif // SBKENUM_P_H
diff --git a/sources/shiboken6/libshiboken/sbkfeature_base.cpp b/sources/shiboken6/libshiboken/sbkfeature_base.cpp
index 4b17091f3..44dea8b53 100644
--- a/sources/shiboken6/libshiboken/sbkfeature_base.cpp
+++ b/sources/shiboken6/libshiboken/sbkfeature_base.cpp
@@ -236,10 +236,13 @@ PyObject *mangled_type_getattro(PyTypeObject *type, PyObject *name)
// no longer advertized in PYI files or line completion.
if (ret && Py_TYPE(ret) == EnumMeta && currentOpcode_Is_CallMethNoArgs()) {
- // We provide a zero argument for compatibility if it is a call with no args.
- auto *hold = replaceNoArgWithZero(ret);
- Py_DECREF(ret);
- ret = hold;
+ bool useZeroDefault = !(Enum::enumOption & Enum::ENOPT_NO_ZERODEFAULT);
+ if (useZeroDefault) {
+ // We provide a zero argument for compatibility if it is a call with no args.
+ auto *hold = replaceNoArgWithZero(ret);
+ Py_DECREF(ret);
+ ret = hold;
+ }
}
if (!ret && name != ignAttr1 && name != ignAttr2) {
@@ -261,58 +264,64 @@ PyObject *mangled_type_getattro(PyTypeObject *type, PyObject *name)
continue;
if (!sotp->enumFlagsDict)
initEnumFlagsDict(type_base);
- auto *rename = PyDict_GetItem(sotp->enumFlagsDict, name);
- if (rename) {
- /*
- * Part 1: Look into the enumFlagsDict if we have an old flags name.
- * -------------------------------------------------------------
- * We need to replace the parameterless
-
- QtCore.Qt.Alignment()
-
- * by the one-parameter call
-
- QtCore.Qt.AlignmentFlag(0)
-
- * That means: We need to bind the zero as default into a wrapper and
- * return that to be called.
- *
- * Addendum:
- * ---------
- * We first need to look into the current opcode of the bytecode to find
- * out if we have a call like above or just a type lookup.
- */
- auto *flagType = PyDict_GetItem(type_base->tp_dict, rename);
- if (currentOpcode_Is_CallMethNoArgs())
- return replaceNoArgWithZero(flagType);
- Py_INCREF(flagType);
- return flagType;
+ bool useFakeRenames = !(Enum::enumOption & Enum::ENOPT_NO_FAKERENAMES);
+ if (useFakeRenames) {
+ auto *rename = PyDict_GetItem(sotp->enumFlagsDict, name);
+ if (rename) {
+ /*
+ * Part 1: Look into the enumFlagsDict if we have an old flags name.
+ * -------------------------------------------------------------
+ * We need to replace the parameterless
+
+ QtCore.Qt.Alignment()
+
+ * by the one-parameter call
+
+ QtCore.Qt.AlignmentFlag(0)
+
+ * That means: We need to bind the zero as default into a wrapper and
+ * return that to be called.
+ *
+ * Addendum:
+ * ---------
+ * We first need to look into the current opcode of the bytecode to find
+ * out if we have a call like above or just a type lookup.
+ */
+ auto *flagType = PyDict_GetItem(type_base->tp_dict, rename);
+ if (currentOpcode_Is_CallMethNoArgs())
+ return replaceNoArgWithZero(flagType);
+ Py_INCREF(flagType);
+ return flagType;
+ }
}
- auto *dict = type_base->tp_dict;
- PyObject *key, *value;
- Py_ssize_t pos = 0;
- while (PyDict_Next(dict, &pos, &key, &value)) {
- /*
- * Part 2: Check for a duplication into outer scope.
- * -------------------------------------------------
- * We need to replace the shortcut
-
- QtCore.Qt.AlignLeft
-
- * by the correct call
-
- QtCore.Qt.AlignmentFlag.AlignLeft
-
- * That means: We need to search all Enums of the class.
- */
- if (Py_TYPE(value) == EnumMeta) {
- auto *valtype = reinterpret_cast<PyTypeObject *>(value);
- auto *member_map = PyDict_GetItem(valtype->tp_dict, _member_map_);
- if (member_map && PyDict_Check(member_map)) {
- auto *result = PyDict_GetItem(member_map, name);
- if (result) {
- Py_INCREF(result);
- return result;
+ bool useFakeShortcuts = !(Enum::enumOption & Enum::ENOPT_NO_FAKESHORTCUT);
+ if (useFakeShortcuts) {
+ auto *dict = type_base->tp_dict;
+ PyObject *key, *value;
+ Py_ssize_t pos = 0;
+ while (PyDict_Next(dict, &pos, &key, &value)) {
+ /*
+ * Part 2: Check for a duplication into outer scope.
+ * -------------------------------------------------
+ * We need to replace the shortcut
+
+ QtCore.Qt.AlignLeft
+
+ * by the correct call
+
+ QtCore.Qt.AlignmentFlag.AlignLeft
+
+ * That means: We need to search all Enums of the class.
+ */
+ if (Py_TYPE(value) == EnumMeta) {
+ auto *valtype = reinterpret_cast<PyTypeObject *>(value);
+ auto *member_map = PyDict_GetItem(valtype->tp_dict, _member_map_);
+ if (member_map && PyDict_Check(member_map)) {
+ auto *result = PyDict_GetItem(member_map, name);
+ if (result) {
+ Py_INCREF(result);
+ return result;
+ }
}
}
}
diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/parser.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/parser.py
index ab6a46fff..76bb114d2 100644
--- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/parser.py
+++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/parser.py
@@ -1,6 +1,7 @@
# Copyright (C) 2022 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
+import ast
import enum
import functools
import keyword
@@ -58,10 +59,16 @@ def _get_flag_enum_option():
flag = True
elif opt in ("no", "off", "false"):
flag = False
- elif opt.isnumeric():
- flag = bool(int(opt))
+ else:
+ # instead of a simple int() conversion, let's allow for "0xf" or "0b1111"
+ try:
+ flag = ast.literal_eval(opt)
+ except Exception:
+ flag = True
elif hasattr(sys, sysname):
- flag = bool(getattr(sys, sysname))
+ flag = getattr(sys, sysname)
+ if not isinstance(flag, int):
+ flag = True
# PYSIDE-1797: Emit a warning when we may remove pep384_issue33738.cpp
if pyminver and pyminver >= (3, 8):
warnings.warn(f"\n *** Python is at version {'.'.join(map(str, pyminver))} now. "
@@ -79,10 +86,9 @@ def _get_flag_enum_option():
if ver[:2] >= (7, 0):
warnings.warn(f"\n *** PySide is at version {'.'.join(map(str, ver[:2]))} now. "
f"Please drop the forgiving Enum behavior in `mangled_type_getattro` ***")
- # modify the sys attribute to bool
+ # normalize the sys attribute
setattr(sys, sysname, flag)
- # modify the env attribute to "0" or "1"
- os.environ[envname] = str(int(flag))
+ os.environ[envname] = str(flag)
return flag