aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian Tismer <tismer@stackless.com>2022-06-21 10:22:04 +0200
committerQt Cherry-pick Bot <cherrypick_bot@qt-project.org>2022-07-14 17:07:19 +0000
commitb9339a7721b42f2359cfba8cc558c29ef9a2e124 (patch)
tree680b2472bf920479661098dbd71ff11081a6250f
parent02412cd6c3ad0453c2610b6bc03b35c58f298530 (diff)
PyEnum: Increase compatibility by allowing defaults and old flag names
This patch supports to write Qt.Alignment() instead of Qt.AlignmentFlag(0) Also supported is Qt.AlignmentFlag() which is mapped to Qt.AlignmentFlag(0) This trickery was quite involved since the Python opcodes needed to be analyzed if we have a parameterless call. Only in that case, we insert a partial object which supplies the missing value=0 default. Changing the implementation of PyEnum was not desired because this is highly complicated, not portable and even not possible. The change has been tested with Python 3.6 to 3.11.0b3 . [ChangeLog][shiboken6] The new Python enums are made as compatible to the old ones as possible. It is again allowed to use Qt.Alignment() instead of Qt.AlignmentFlag(0), and a default of 0 is always allowed. Change-Id: If6a93f8210ff6cae4e38251420e1ad5fffbe42cb Task-number: PYSIDE-1735 Reviewed-by: Christian Tismer <tismer@stackless.com> (cherry picked from commit f92b7dcac9537fb091dc15d3ee218f1984d8abef) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
-rw-r--r--sources/pyside6/tests/QtCore/qflags_test.py19
-rw-r--r--sources/pyside6/tests/pysidetest/enum_test.py115
-rw-r--r--sources/shiboken6/generator/shiboken/cppgenerator.cpp32
-rw-r--r--sources/shiboken6/libshiboken/basewrapper.h3
-rw-r--r--sources/shiboken6/libshiboken/basewrapper_p.h2
-rw-r--r--sources/shiboken6/libshiboken/sbkfeature_base.cpp169
-rw-r--r--sources/shiboken6/libshiboken/signature/signature.cpp7
7 files changed, 339 insertions, 8 deletions
diff --git a/sources/pyside6/tests/QtCore/qflags_test.py b/sources/pyside6/tests/QtCore/qflags_test.py
index 3f13b0db1..078b610f1 100644
--- a/sources/pyside6/tests/QtCore/qflags_test.py
+++ b/sources/pyside6/tests/QtCore/qflags_test.py
@@ -136,5 +136,24 @@ class QFlagsWrongType(unittest.TestCase):
self.assertEqual(operator.or_(Qt.NoItemFlags, 43), 43)
+class QEnumFlagDefault(unittest.TestCase):
+ """
+ Check that old flag and enum syntax can be used.
+ The signatures of these surrogate functions intentionally do not exist
+ because people should learn to use the new Enums correctly.
+ """
+ def testOldQFlag(self):
+ self.assertEqual(Qt.AlignmentFlag(), Qt.AlignmentFlag(0))
+ oldFlag = Qt.Alignment()
+ oldEnum = Qt.AlignmentFlag()
+ self.assertEqual(type(oldFlag), Qt.Alignment)
+ self.assertEqual(type(oldEnum), Qt.AlignmentFlag)
+ if sys.pyside63_option_python_enum:
+ self.assertEqual(type(oldFlag), type(oldEnum))
+ else:
+ with self.assertRaises(AssertionError):
+ self.assertEqual(type(oldFlag), type(oldEnum))
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/sources/pyside6/tests/pysidetest/enum_test.py b/sources/pyside6/tests/pysidetest/enum_test.py
index 7f2a2febe..c0cc8deec 100644
--- a/sources/pyside6/tests/pysidetest/enum_test.py
+++ b/sources/pyside6/tests/pysidetest/enum_test.py
@@ -40,6 +40,7 @@ init_test_paths(True)
from PySide6.QtCore import Qt
from testbinding import Enum1, TestObjectWithoutNamespace
+import dis
class ListConnectionTest(unittest.TestCase):
@@ -71,6 +72,120 @@ class ListConnectionTest(unittest.TestCase):
self.assertFalse(Qt.AlignBottom < Qt.AlignHCenter)
self.assertTrue(Qt.AlignBottom > Qt.AlignHCenter)
+# PYSIDE-1735: We are testing that opcodes do what they are supposed to do.
+# This is needed in the PyEnum forgiveness mode where we need
+# to introspect the code if an Enum was called with no args.
+class InvestigateOpcodesTest(unittest.TestCase):
+
+ def probe_function1(self):
+ x = Qt.Alignment
+
+ def probe_function2(self):
+ x = Qt.Alignment()
+
+ @staticmethod
+ def read_code(func, **kw):
+ return list(instr[:3] for instr in dis.Bytecode(func, **kw))
+
+ @staticmethod
+ def get_sizes(func, **kw):
+ ops = list((instr.opname, instr.offset) for instr in dis.Bytecode(func, **kw))
+ res = []
+ for idx in range(1, len(ops)):
+ res.append((ops[idx - 1][0], ops[idx][1] - ops[idx - 1][1]))
+ return sorted(res, key=lambda x: (x[1], x[0]))
+
+ def testByteCode(self):
+ # opname, opcode, arg
+ result_1 = [('LOAD_GLOBAL', 116, 0),
+ ('LOAD_ATTR', 106, 1),
+ ('STORE_FAST', 125, 1),
+ ('LOAD_CONST', 100, 0),
+ ('RETURN_VALUE', 83, None)]
+
+ result_2 = [('LOAD_GLOBAL', 116, 0),
+ ('LOAD_METHOD', 160, 1),
+ ('CALL_METHOD', 161, 0),
+ ('STORE_FAST', 125, 1),
+ ('LOAD_CONST', 100, 0),
+ ('RETURN_VALUE', 83, None)]
+
+ if sys.version_info[:2] <= (3, 6):
+
+ result_2 = [('LOAD_GLOBAL', 116, 0),
+ ('LOAD_ATTR', 106, 1),
+ ('CALL_FUNCTION', 131, 0),
+ ('STORE_FAST', 125, 1),
+ ('LOAD_CONST', 100, 0),
+ ('RETURN_VALUE', 83, None)]
+
+ if sys.version_info[:2] >= (3, 11):
+ # Note: Python 3.11 is a bit more complex because it can optimize itself.
+ # Opcodes are a bit different, and a hidden second code object is used.
+ # We investigate this a bit, because we want to be warned when things change.
+ QUICKENING_WARMUP_DELAY = 8
+
+ result_1 = [('RESUME', 151, 0),
+ ('LOAD_GLOBAL', 116, 0),
+ ('LOAD_ATTR', 106, 1),
+ ('STORE_FAST', 125, 1),
+ ('LOAD_CONST', 100, 0),
+ ('RETURN_VALUE', 83, None)]
+
+ result_2 = [('RESUME', 151, 0),
+ ('LOAD_GLOBAL', 116, 1),
+ ('LOAD_ATTR', 106, 1),
+ ('PRECALL', 166, 0),
+ ('CALL', 171, 0),
+ ('STORE_FAST', 125, 1),
+ ('LOAD_CONST', 100, 0),
+ ('RETURN_VALUE', 83, None)]
+
+ sizes_2 = [('LOAD_CONST', 2),
+ ('RESUME', 2),
+ ('STORE_FAST', 2),
+ ('PRECALL', 4),
+ ('CALL', 10),
+ ('LOAD_ATTR', 10),
+ ('LOAD_GLOBAL', 12)]
+
+ self.assertEqual(self.read_code(self.probe_function2, adaptive=True), result_2)
+ self.assertEqual(self.get_sizes(self.probe_function2, adaptive=True), sizes_2)
+
+ @staticmethod
+ def code_quicken(f, times):
+ # running the code triggers acceleration after some runs.
+ for _ in range(times):
+ f()
+
+ code_quicken(self.probe_function2, QUICKENING_WARMUP_DELAY-1)
+ self.assertEqual(self.read_code(self.probe_function2, adaptive=True), result_2)
+ self.assertEqual(self.get_sizes(self.probe_function2, adaptive=True), sizes_2)
+
+ result_3 = [('RESUME_QUICK', 150, 0),
+ ('LOAD_GLOBAL_MODULE', 55, 1),
+ ('LOAD_ATTR_ADAPTIVE', 39, 1),
+ ('PRECALL_ADAPTIVE', 64, 0),
+ ('CALL_ADAPTIVE', 22, 0),
+ ('STORE_FAST', 125, 1),
+ ('LOAD_CONST', 100, 0),
+ ('RETURN_VALUE', 83, None)]
+
+ sizes_3 = [('LOAD_CONST', 2),
+ ('RESUME_QUICK', 2),
+ ('STORE_FAST', 2),
+ ('PRECALL_ADAPTIVE', 4),
+ ('CALL_ADAPTIVE', 10),
+ ('LOAD_ATTR_ADAPTIVE', 10),
+ ('LOAD_GLOBAL_MODULE', 12)]
+
+ code_quicken(self.probe_function2, 1)
+ self.assertEqual(self.read_code(self.probe_function2, adaptive=True), result_3)
+ self.assertEqual(self.get_sizes(self.probe_function2, adaptive=True), sizes_3)
+
+ self.assertEqual(self.read_code(self.probe_function1), result_1)
+ self.assertEqual(self.read_code(self.probe_function2), result_2)
+
if __name__ == '__main__':
unittest.main()
diff --git a/sources/shiboken6/generator/shiboken/cppgenerator.cpp b/sources/shiboken6/generator/shiboken/cppgenerator.cpp
index c4d625ff2..6d88957f5 100644
--- a/sources/shiboken6/generator/shiboken/cppgenerator.cpp
+++ b/sources/shiboken6/generator/shiboken/cppgenerator.cpp
@@ -382,6 +382,24 @@ static QString buildPropertyString(const QPropertySpec &spec)
return text;
}
+static QString _plainName(const QString &s)
+{
+ auto cutPos = s.lastIndexOf(u"::"_s);
+ return cutPos < 0 ? s : s.right(s.length() - (cutPos + 2));
+}
+
+static QString BuildEnumFlagInfo(const EnumTypeEntry *enumType)
+{
+ QString result = _plainName(enumType->name());
+ auto flags = enumType->flags();
+ if (flags) {
+ result += u":IntFlag:"_s + _plainName(flags->flagsName());
+ } else {
+ result += u":IntEnum"_s;
+ }
+ return u'"' + result + u'"';
+}
+
static void writePyGetSetDefEntry(TextStream &s, const QString &name,
const QString &getFunc, const QString &setFunc)
{
@@ -671,7 +689,18 @@ void CppGenerator::generateClass(TextStream &s, const GeneratorContext &classCon
s << entry << ",\n";
s << NULL_PTR << " // Sentinel\n"
<< outdent << "};\n\n";
+
}
+ // PYSIDE-1735: Write an EnumFlagInfo structure
+ QStringList sorter;
+ for (const auto &entry : qAsConst(classEnums))
+ sorter.append(BuildEnumFlagInfo(entry.typeEntry()));
+ sorter.sort();
+ s << "static const char *" << className << "_EnumFlagInfo[] = {\n" << indent;
+ for (const auto &entry : qAsConst(sorter))
+ s << entry << ",\n";
+ s << NULL_PTR << " // Sentinel\n"
+ << outdent << "};\n\n";
// Write methods definition
writePyMethodDefs(s, className, methodsDefinitions, typeEntry->isValue());
@@ -5935,6 +5964,9 @@ void CppGenerator::writeClassRegister(TextStream &s,
metaClass->getEnumsFromInvisibleNamespacesToBeGenerated(&classEnums);
writeEnumsInitialization(s, classEnums, ErrorReturn::Void);
+ if (!classContext.forSmartPointer())
+ s << "SbkObjectType_SetEnumFlagInfo(pyType, " << chopType(pyTypeName)
+ << "_EnumFlagInfo);\n";
if (metaClass->hasSignals())
writeSignalInitialization(s, metaClass);
diff --git a/sources/shiboken6/libshiboken/basewrapper.h b/sources/shiboken6/libshiboken/basewrapper.h
index 815a85e51..7a8b8c902 100644
--- a/sources/shiboken6/libshiboken/basewrapper.h
+++ b/sources/shiboken6/libshiboken/basewrapper.h
@@ -108,6 +108,9 @@ LIBSHIBOKEN_API void SbkObjectType_UpdateFeature(PyTypeObject *type);
LIBSHIBOKEN_API const char **SbkObjectType_GetPropertyStrings(PyTypeObject *type);
LIBSHIBOKEN_API void SbkObjectType_SetPropertyStrings(PyTypeObject *type, const char **strings);
+/// PYSIDE-1735: Store the enumFlagInfo.
+LIBSHIBOKEN_API void SbkObjectType_SetEnumFlagInfo(PyTypeObject *type, const char **strings);
+
/// PYSIDE-1470: Set the function to kill a Q*Application.
typedef void(*DestroyQAppHook)();
LIBSHIBOKEN_API void setDestroyQApplication(DestroyQAppHook func);
diff --git a/sources/shiboken6/libshiboken/basewrapper_p.h b/sources/shiboken6/libshiboken/basewrapper_p.h
index f16df86cf..259206d7a 100644
--- a/sources/shiboken6/libshiboken/basewrapper_p.h
+++ b/sources/shiboken6/libshiboken/basewrapper_p.h
@@ -149,6 +149,8 @@ struct SbkObjectTypePrivate
DeleteUserDataFunc d_func;
void (*subtype_init)(PyTypeObject *, PyObject *, PyObject *);
const char **propertyStrings;
+ const char **enumFlagInfo;
+ PyObject *flagsDict;
};
diff --git a/sources/shiboken6/libshiboken/sbkfeature_base.cpp b/sources/shiboken6/libshiboken/sbkfeature_base.cpp
index 2348a5d81..e8e19ac72 100644
--- a/sources/shiboken6/libshiboken/sbkfeature_base.cpp
+++ b/sources/shiboken6/libshiboken/sbkfeature_base.cpp
@@ -46,6 +46,7 @@
#include "sbkstaticstrings_p.h"
#include "signature.h"
#include "sbkfeature_base.h"
+#include "gilstate.h"
using namespace Shiboken;
@@ -114,6 +115,111 @@ SelectableFeatureHook initSelectableFeature(SelectableFeatureHook func)
return ret;
}
+// This useful function is for debugging
+[[maybe_unused]] static void disassembleFrame(const char *marker)
+{
+ Shiboken::GilState gil;
+ PyObject *error_type, *error_value, *error_traceback;
+ PyErr_Fetch(&error_type, &error_value, &error_traceback);
+ static PyObject *dismodule = PyImport_ImportModule("dis");
+ static PyObject *disco = PyObject_GetAttrString(dismodule, "disco");
+ static PyObject *const _f_lasti = Shiboken::String::createStaticString("f_lasti");
+ static PyObject *const _f_code = Shiboken::String::createStaticString("f_code");
+ auto *frame = reinterpret_cast<PyObject *>(PyEval_GetFrame());
+ AutoDecRef f_lasti(PyObject_GetAttr(frame, _f_lasti));
+ AutoDecRef f_code(PyObject_GetAttr(frame, _f_code));
+ fprintf(stdout, "\n%s BEGIN\n", marker);
+ PyObject_CallFunctionObjArgs(disco, f_code.object(), f_lasti.object(), nullptr);
+ fprintf(stdout, "%s END\n\n", marker);
+ static PyObject *sysmodule = PyImport_ImportModule("sys");
+ static PyObject *stdout_file = PyObject_GetAttrString(sysmodule, "stdout");
+ PyObject_CallMethod(stdout_file, "flush", nullptr);
+ PyErr_Restore(error_type, error_value, error_traceback);
+}
+
+// PYTHON 3.11
+static int const PRECALL = 166;
+// we have "big instructins" with gaps after them
+static int const LOAD_ATTR_GAP = 4 * 2;
+static int const LOAD_METHOD_GAP = 10 * 2;
+// Python 3.7 - 3.10
+static int const LOAD_METHOD = 160;
+static int const CALL_METHOD = 161;
+// Python 3.6
+static int const CALL_FUNCTION = 131;
+static int const LOAD_ATTR = 106;
+
+static bool currentOpcode_Is_CallMethNoArgs()
+{
+ // We look into the currently active operation if we are going to call
+ // a method with zero arguments.
+ static PyObject *const _f_code = Shiboken::String::createStaticString("f_code");
+ static PyObject *const _f_lasti = Shiboken::String::createStaticString("f_lasti");
+ static PyObject *const _co_code = Shiboken::String::createStaticString("co_code");
+ auto *frame = reinterpret_cast<PyObject *>(PyEval_GetFrame());
+ // We use the limited API for frame and code objects.
+ AutoDecRef f_code(PyObject_GetAttr(frame, _f_code));
+ AutoDecRef dec_f_lasti(PyObject_GetAttr(frame, _f_lasti));
+ Py_ssize_t f_lasti = PyLong_AsSsize_t(dec_f_lasti);
+ AutoDecRef dec_co_code(PyObject_GetAttr(f_code, _co_code));
+ Py_ssize_t code_len;
+ char *co_code{};
+ PyBytes_AsStringAndSize(dec_co_code, &co_code, &code_len);
+ uint8_t opcode1 = co_code[f_lasti];
+ uint8_t opcode2 = co_code[f_lasti + 2];
+ uint8_t oparg2 = co_code[f_lasti + 3];
+ static PyObject *sysmodule = PyImport_AddModule("sys");
+ static PyObject *version = PyObject_GetAttrString(sysmodule, "version_info");
+ static PyObject *major = PyTuple_GetItem(version, 0);
+ static PyObject *minor = PyTuple_GetItem(version, 1);
+ auto number = PyLong_AsLong(major) * 1000 + PyLong_AsLong(minor);
+ if (number < 3007)
+ return opcode1 == LOAD_ATTR && opcode2 == CALL_FUNCTION && oparg2 == 0;
+ if (number < 3011)
+ return opcode1 == LOAD_METHOD && opcode2 == CALL_METHOD && oparg2 == 0;
+
+ // With Python 3.11, the opcodes get bigger and change a bit.
+ // Note: The new adaptive opcodes are elegantly hidden and we
+ // don't need to take care of them.
+ if (opcode1 == LOAD_METHOD)
+ f_lasti += LOAD_METHOD_GAP;
+ else if (opcode1 == LOAD_ATTR)
+ f_lasti += LOAD_ATTR_GAP;
+ else
+ return false;
+
+ opcode2 = co_code[f_lasti + 2];
+ oparg2 = co_code[f_lasti + 3];
+
+ return opcode2 == PRECALL && oparg2 == 0;
+}
+
+static void _initFlagsDict(SbkObjectTypePrivate *sotp)
+{
+ static PyObject *const split = Shiboken::String::createStaticString("split");
+ static PyObject *const colon = Shiboken::String::createStaticString(":");
+ auto **enumFlagInfo = sotp->enumFlagInfo;
+ auto *dict = PyDict_New();
+ for (; *enumFlagInfo; ++enumFlagInfo) {
+ AutoDecRef line(PyUnicode_FromString(*enumFlagInfo));
+ AutoDecRef parts(PyObject_CallMethodObjArgs(line, split, colon, nullptr));
+ if (PyList_Size(parts) == 3) {
+ auto *key = PyList_GetItem(parts, 2);
+ auto *value = PyList_GetItem(parts, 0);
+ PyDict_SetItem(dict, key, value);
+ }
+ }
+ sotp->flagsDict = dict;
+}
+
+static PyObject *replaceNoArgWithZero(PyObject *callable)
+{
+ static auto *functools = PyImport_ImportModule("_functools"); // builtin
+ static auto *partial = PyObject_GetAttrString(functools, "partial");
+ static auto *zero = PyLong_FromLong(0);
+ return PyObject_CallFunctionObjArgs(partial, callable, zero, nullptr);
+}
+
PyObject *mangled_type_getattro(PyTypeObject *type, PyObject *name)
{
/*
@@ -142,24 +248,76 @@ PyObject *mangled_type_getattro(PyTypeObject *type, PyObject *name)
// Qt.AlignLeft instead of Qt.Alignment.AlignLeft, is still implemented but
// 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;
+ }
+
if (!ret && name != ignAttr1 && name != ignAttr2) {
PyObject *error_type, *error_value, *error_traceback;
PyErr_Fetch(&error_type, &error_value, &error_traceback);
// This is similar to `find_name_in_mro`, but instead of looking directly into
- // tp_dict, we search for the attribute in local classes of that dict.
+ // tp_dict, we also search for the attribute in local classes of that dict (Part 2).
PyObject *mro = type->tp_mro;
assert(PyTuple_Check(mro));
size_t idx, n = PyTuple_GET_SIZE(mro);
for (idx = 0; idx < n; ++idx) {
- // FIXME This loop should further be optimized by installing an extra
- // <classname>_EnumInfo structure. This comes with the next compatibility patch.
auto *base = PyTuple_GET_ITEM(mro, idx);
auto *type_base = reinterpret_cast<PyTypeObject *>(base);
+ auto sotp = PepType_SOTP(type_base);
+ // The EnumFlagInfo structure tells us if there are Enums at all.
+ const char **enumFlagInfo = sotp->enumFlagInfo;
+ if (!(enumFlagInfo && enumFlagInfo[0]))
+ continue;
+ if (!sotp->flagsDict)
+ _initFlagsDict(sotp);
+ auto *rename = PyDict_GetItem(sotp->flagsDict, name);
+ if (rename) {
+ /*
+ * Part 1: Look into the flagsDict 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_);
@@ -231,6 +389,11 @@ void SbkObjectType_SetPropertyStrings(PyTypeObject *type, const char **strings)
PepType_SOTP(type)->propertyStrings = strings;
}
+void SbkObjectType_SetEnumFlagInfo(PyTypeObject *type, const char **strings)
+{
+ PepType_SOTP(type)->enumFlagInfo = strings;
+}
+
// PYSIDE-1626: Enforcing a context switch without further action.
void SbkObjectType_UpdateFeature(PyTypeObject *type)
{
diff --git a/sources/shiboken6/libshiboken/signature/signature.cpp b/sources/shiboken6/libshiboken/signature/signature.cpp
index 7b6eb8462..ca46c777c 100644
--- a/sources/shiboken6/libshiboken/signature/signature.cpp
+++ b/sources/shiboken6/libshiboken/signature/signature.cpp
@@ -111,22 +111,19 @@ PyObject *GetTypeKey(PyObject *ob)
*
* PYSIDE-1286: We use correct __module__ and __qualname__, now.
*/
- // XXX we obtain also the current selection.
- // from the current module name.
AutoDecRef module_name(PyObject_GetAttr(ob, PyMagicName::module()));
if (module_name.isNull()) {
// We have no module_name because this is a module ;-)
PyErr_Clear();
module_name.reset(PyObject_GetAttr(ob, PyMagicName::name()));
- return Py_BuildValue("O"/*i"*/, module_name.object()/*, getFeatureSelectId()*/);
+ return Py_BuildValue("O", module_name.object());
}
AutoDecRef class_name(PyObject_GetAttr(ob, PyMagicName::qualname()));
if (class_name.isNull()) {
Py_FatalError("Signature: missing class name in GetTypeKey");
return nullptr;
}
- return Py_BuildValue("(O"/*i*/"O)", module_name.object(), /*getFeatureSelectId(),*/
- class_name.object());
+ return Py_BuildValue("(OO)", module_name.object(), class_name.object());
}
static PyObject *empty_dict = nullptr;