diff options
Diffstat (limited to 'sources/shiboken2/libshiboken/signature.cpp')
-rw-r--r-- | sources/shiboken2/libshiboken/signature.cpp | 820 |
1 files changed, 524 insertions, 296 deletions
diff --git a/sources/shiboken2/libshiboken/signature.cpp b/sources/shiboken2/libshiboken/signature.cpp index f0bb8e609..922f85906 100644 --- a/sources/shiboken2/libshiboken/signature.cpp +++ b/sources/shiboken2/libshiboken/signature.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2018 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt for Python. @@ -38,6 +38,7 @@ ****************************************************************************/ #include "basewrapper.h" +#include "autodecref.h" extern "C" { @@ -77,18 +78,25 @@ typedef struct safe_globals_struc { static safe_globals pyside_globals = 0; -static PyObject *GetSignature_Function(PyCFunctionObject *); -static PyObject *GetSignature_TypeMod(PyObject *); +static PyObject *GetClassKey(PyObject *ob); + +static PyObject *GetSignature_Function(PyObject *, const char *); +static PyObject *GetSignature_TypeMod(PyObject *, const char *); +static PyObject *GetSignature_Wrapper(PyObject *, const char *); +static PyObject *get_signature(PyObject *self, PyObject *args); static PyObject *PySide_BuildSignatureProps(PyObject *class_mod); +static void init_module_1(void); +static void init_module_2(void); + const char helper_module_name[] = "signature_loader"; const char bootstrap_name[] = "bootstrap"; const char arg_name[] = "pyside_arg_dict"; const char func_name[] = "pyside_type_init"; static PyObject * -CreateSignature(PyObject *props, const char *sig_kind) +CreateSignature(PyObject *props, PyObject *key) { /* * Here is the new function to create all signatures. It simply calls @@ -97,30 +105,95 @@ CreateSignature(PyObject *props, const char *sig_kind) * to support '_signature_is_functionlike()'. */ return PyObject_CallFunction(pyside_globals->createsig_func, - (char *)"(Os)", props, sig_kind); + (char *)"(OO)", props, key); } static PyObject * -pyside_cf_get___signature__(PyObject *func) +pyside_cf_get___signature__(PyObject *func, const char *modifier) { - return GetSignature_Function((PyCFunctionObject *)func); + init_module_2(); + return GetSignature_Function(func, modifier); } static PyObject * -pyside_sm_get___signature__(PyObject *sm) +pyside_sm_get___signature__(PyObject *sm, const char *modifier) { - PyObject *func, *ret; + init_module_2(); + Shiboken::AutoDecRef func(PyObject_GetAttrString(sm, "__func__")); + if (Py_TYPE(func) == PepFunction_TypePtr) + Py_RETURN_NONE; + return GetSignature_Function(func, modifier); +} - func = PyObject_GetAttrString(sm, "__func__"); - ret = GetSignature_Function((PyCFunctionObject *)func); - Py_XDECREF(func); - return ret; +static PyObject * +_get_class_of_cf(PyObject *ob_cf) +{ + PyObject *selftype = PyCFunction_GET_SELF(ob_cf); + if (selftype == NULL) + selftype = PyDict_GetItem(pyside_globals->map_dict, (PyObject *)ob_cf); + if (selftype == NULL) { + if (!PyErr_Occurred()) + Py_RETURN_NONE; + return NULL; + } + PyObject *typemod = (PyType_Check(selftype) || PyModule_Check(selftype)) + ? selftype : (PyObject *)Py_TYPE(selftype); + // do we support module functions? + Py_INCREF(typemod); + return typemod; +} + +static PyObject * +_get_class_of_sm(PyObject *ob_sm) +{ + Shiboken::AutoDecRef func(PyObject_GetAttrString(ob_sm, "__func__")); + return _get_class_of_cf(func); } -#ifdef Py_LIMITED_API +static PyObject * +_get_class_of_descr(PyObject *ob) +{ + Shiboken::AutoDecRef func_name(PyObject_GetAttrString(ob, "__name__")); + return PyObject_GetAttrString(ob, "__objclass__"); +} + +static PyObject * +GetClassOfFunc(PyObject *ob) +{ + if (PyType_Check(ob)) + return ob; + if (Py_TYPE(ob) == &PyCFunction_Type) + return _get_class_of_cf(ob); + if (Py_TYPE(ob) == PepStaticMethod_TypePtr) + return _get_class_of_sm(ob); + if (Py_TYPE(ob) == PepMethodDescr_TypePtr) + return _get_class_of_descr(ob); + if (Py_TYPE(ob) == &PyWrapperDescr_Type) + return _get_class_of_descr(ob); + Py_FatalError("unexpected type in GetClassOfFunc"); + return nullptr; +} + +static PyObject * +compute_name_key(PyObject *ob) +{ + if (PyType_Check(ob)) + return GetClassKey(GetClassOfFunc(ob)); + PyObject *func = ob; + if (Py_TYPE(ob) == PepStaticMethod_TypePtr) + func = PyObject_GetAttrString(ob, "__func__"); + else + Py_INCREF(func); + Shiboken::AutoDecRef func_name(PyObject_GetAttrString(func, "__name__")); + Py_DECREF(func); + if (func_name.isNull()) + Py_FatalError("unexpected name problem in compute_name_key"); + Shiboken::AutoDecRef type_key(GetClassKey(GetClassOfFunc(ob))); + return Py_BuildValue("(OO)", type_key.object(), func_name.object()); +} static int -build_qualname_to_func(PyObject *obtype) +build_name_key_to_func(PyObject *obtype) { PyTypeObject *type = (PyTypeObject *)obtype; PyMethodDef *meth = type->tp_methods; @@ -129,230 +202,245 @@ build_qualname_to_func(PyObject *obtype) return 0; for (; meth->ml_name != NULL; meth++) { - PyObject *func = PyCFunction_NewEx(meth, obtype, NULL); - PyObject *qualname = PyObject_GetAttrString(func, "__qualname__"); - if (func == NULL || qualname == NULL) { - return -1; - } - if (PyDict_SetItem(pyside_globals->map_dict, qualname, func) < 0) { + Shiboken::AutoDecRef func(PyCFunction_NewEx(meth, obtype, NULL)); + Shiboken::AutoDecRef name_key(compute_name_key(func)); + if (func.isNull() || name_key.isNull() + || PyDict_SetItem(pyside_globals->map_dict, name_key, func) < 0) return -1; - } - Py_DECREF(func); - Py_DECREF(qualname); } return 0; } static PyObject * -qualname_to_typename(PyObject *qualname) -{ - PyObject *func = PyObject_GetAttrString(qualname, "split"); - PyObject *list = func ? PyObject_CallFunction(func, (char *)"(s)", ".") - : NULL; - PyObject *res = list ? PyList_GetItem(list, 0) : NULL; - Py_XINCREF(res); - Py_XDECREF(func); - Py_XDECREF(list); - return res; -} - -static PyObject * -qualname_to_func(PyObject *ob) +name_key_to_func(PyObject *ob) { /* - * If we have __qualname__, then we can easily build a mapping - * from __qualname__ to PyCFunction. This is necessary when - * the limited API does not let us go easily from descriptor - * to PyMethodDef. + * We build a mapping from name_key to function. + * This could also be computed directly, but the Limited API + * makes this impossible. So we always build our own mapping. */ - PyObject *ret; - PyObject *qualname = PyObject_GetAttrString((PyObject *)ob, - "__qualname__"); - if (qualname != NULL) { - ret = PyDict_GetItem(pyside_globals->map_dict, qualname); - if (ret == NULL) { - // do a lazy initialization - PyObject *type_name = qualname_to_typename(qualname); - PyObject *type = PyDict_GetItem(pyside_globals->map_dict, - type_name); - Py_XDECREF(type_name); - if (type == NULL) - Py_RETURN_NONE; - if (build_qualname_to_func(type) < 0) - return NULL; - ret = PyDict_GetItem(pyside_globals->map_dict, qualname); - } - Py_XINCREF(ret); - Py_DECREF(qualname); - } - else + Shiboken::AutoDecRef name_key(compute_name_key(ob)); + if (name_key.isNull()) Py_RETURN_NONE; + + PyObject *ret = PyDict_GetItem(pyside_globals->map_dict, name_key); + if (ret == NULL) { + // do a lazy initialization + Shiboken::AutoDecRef type_key(GetClassKey(GetClassOfFunc(ob))); + PyObject *type = PyDict_GetItem(pyside_globals->map_dict, + type_key); + if (type == nullptr) + Py_RETURN_NONE; + assert(PyType_Check(type)); + if (build_name_key_to_func(type) < 0) + return NULL; + ret = PyDict_GetItem(pyside_globals->map_dict, name_key); + } + Py_XINCREF(ret); return ret; } -#endif static PyObject * -pyside_md_get___signature__(PyObject *ob) -{ - PyObject *func; - PyObject *result; -#ifndef Py_LIMITED_API - PyMethodDescrObject *descr = (PyMethodDescrObject *)ob; - -# if PYTHON_USES_D_COMMON - func = PyCFunction_NewEx(descr->d_method, - (PyObject *)descr->d_common.d_type, NULL); -# else - func = PyCFunction_NewEx(descr->d_method, - (PyObject *)descr->d_type, NULL); -# endif -#else - /* - * With limited access, we cannot use the fields of a method descriptor, - * but in Python 3 we have the __qualname__ field which allows us to - * grab the method object from our registry. - */ - func = qualname_to_func(ob); -#endif - if (func == Py_None) +pyside_md_get___signature__(PyObject *ob_md, const char *modifier) +{ + init_module_2(); + Shiboken::AutoDecRef func(name_key_to_func(ob_md)); + if (func.object() == Py_None) return Py_None; - if (func == NULL) + if (func.isNull()) Py_FatalError("missing mapping in MethodDescriptor"); - result = pyside_cf_get___signature__(func); - Py_DECREF(func); - return result; + return pyside_cf_get___signature__(func, modifier); } static PyObject * -pyside_tp_get___signature__(PyObject *typemod) +pyside_wd_get___signature__(PyObject *ob, const char *modifier) { - return GetSignature_TypeMod(typemod); + init_module_2(); + return GetSignature_Wrapper(ob, modifier); } static PyObject * -GetSignature_Function(PyCFunctionObject *func) +pyside_tp_get___signature__(PyObject *typemod, const char *modifier) { - PyObject *typemod, *type_name, *dict, *props, *value, *selftype; - PyObject *func_name = PyObject_GetAttrString((PyObject *)func, "__name__"); - const char *sig_kind; - int flags; + init_module_2(); + return GetSignature_TypeMod(typemod, modifier); +} - selftype = PyCFunction_GET_SELF((PyObject *)func); - if (selftype == NULL) - selftype = PyDict_GetItem(pyside_globals->map_dict, (PyObject *)func); - if (selftype == NULL) { - if (!PyErr_Occurred()) { - PyErr_Format(PyExc_SystemError, - "the signature for \"%s\" should exist", - PepCFunction_GET_NAMESTR(func) - ); - } - return NULL; +// forward +static PyObject * +GetSignature_Cached(PyObject *props, const char *sig_kind, const char *modifier); + +static PyObject * +GetClassKey(PyObject *ob) +{ + assert(PyType_Check(ob) || PyModule_Check(ob)); + /* + * We obtain a unique key using the module name and the class name. + * + * The class name is a bit funny when modules are nested. + * Example: + * + * "sample.Photon.ValueIdentity" is a class. + * name: "ValueIdentity" + * module: "sample.Photon" + * + * This is the PyCFunction behavior, as opposed to Python functions. + */ + Shiboken::AutoDecRef class_name(PyObject_GetAttrString(ob, "__name__")); + Shiboken::AutoDecRef module_name(PyObject_GetAttrString(ob, "__module__")); + + if (module_name.isNull()) + PyErr_Clear(); + + // Note: if we have a module, then __module__ is null, and we get + // the module name through __name__ . + if (class_name.isNull()) + return nullptr; + if (module_name.object()) + return Py_BuildValue("(OO)", module_name.object(), class_name.object()); + return Py_BuildValue("O", class_name.object()); +} + +static PyObject *empty_dict = nullptr; + +static PyObject * +TypeKey_to_PropsDict(PyObject *type_key, PyObject *obtype) +{ + PyObject *dict = PyDict_GetItem(pyside_globals->arg_dict, type_key); + if (dict == nullptr) { + if (empty_dict == nullptr) + empty_dict = PyDict_New(); + dict = empty_dict; } - if ((PyType_Check(selftype) || PyModule_Check(selftype))) - typemod = selftype; - else - typemod = (PyObject *)Py_TYPE(selftype); - type_name = PyObject_GetAttrString(typemod, "__name__"); - if (type_name == NULL) + if (PyTuple_Check(dict)) + dict = PySide_BuildSignatureProps(obtype); + return dict; +} + +static PyObject * +GetSignature_Function(PyObject *ob_func, const char *modifier) +{ + // make sure that we look into PyCFunction, only... + if (Py_TYPE(ob_func) == PepFunction_TypePtr) Py_RETURN_NONE; - dict = PyDict_GetItem(pyside_globals->arg_dict, type_name); - Py_DECREF(type_name); - if (dict == NULL) + Shiboken::AutoDecRef typemod(GetClassOfFunc(ob_func)); + Shiboken::AutoDecRef type_key(GetClassKey(typemod)); + if (type_key.isNull()) Py_RETURN_NONE; - if (PyTuple_Check(dict)) { - /* - * We do the initialization lazily. - * This has also the advantage that we can freely import PySide. - */ - dict = PySide_BuildSignatureProps(typemod); - if (dict == NULL) - Py_RETURN_NONE; - } - props = PyDict_GetItem(dict, func_name); - if (props == NULL) + PyObject *dict = TypeKey_to_PropsDict(type_key, typemod); + if (dict == nullptr) + return nullptr; + Shiboken::AutoDecRef func_name(PyObject_GetAttrString(ob_func, "__name__")); + PyObject *props = !func_name.isNull() ? PyDict_GetItem(dict, func_name) : nullptr; + if (props == nullptr) Py_RETURN_NONE; - flags = PyCFunction_GET_FLAGS((PyObject *)func); - if (flags & METH_CLASS) + + int flags = PyCFunction_GET_FLAGS(ob_func); + const char *sig_kind; + if (PyModule_Check(typemod)) + sig_kind = "function"; + else if (flags & METH_CLASS) sig_kind = "classmethod"; else if (flags & METH_STATIC) sig_kind = "staticmethod"; else sig_kind = "method"; - value = PyDict_GetItemString(props, sig_kind); - if (value == NULL) { - // we need to compute a signature object - value = CreateSignature(props, sig_kind); - if (value != NULL) { - if (PyDict_SetItemString(props, sig_kind, value) < 0) - return NULL; - } - else - Py_RETURN_NONE; - } - return Py_INCREF(value), value; + return GetSignature_Cached(props, sig_kind, modifier); } static PyObject * -GetSignature_TypeMod(PyObject *ob) +GetSignature_Wrapper(PyObject *ob, const char *modifier) { - PyObject *ob_name, *dict, *props, *value; - const char *sig_kind; - - ob_name = PyObject_GetAttrString(ob, "__name__"); - dict = PyDict_GetItem(pyside_globals->arg_dict, ob_name); - if (dict == NULL) + Shiboken::AutoDecRef func_name(PyObject_GetAttrString(ob, "__name__")); + Shiboken::AutoDecRef objclass(PyObject_GetAttrString(ob, "__objclass__")); + Shiboken::AutoDecRef class_key(GetClassKey(objclass)); + + if (func_name.isNull() || objclass.isNull() || class_key.isNull()) + return nullptr; + PyObject *dict = TypeKey_to_PropsDict(class_key, objclass); + if (dict == nullptr) + return nullptr; + PyObject *props = PyDict_GetItem(dict, func_name); + if (props == nullptr) Py_RETURN_NONE; - if (PyTuple_Check(dict)) { - dict = PySide_BuildSignatureProps(ob); - if (dict == NULL) { - Py_RETURN_NONE; - } - } - props = PyDict_GetItem(dict, ob_name); - Py_DECREF(ob_name); - if (props == NULL) + return GetSignature_Cached(props, "method", modifier); +} + +static PyObject * +GetSignature_TypeMod(PyObject *ob, const char *modifier) +{ + Shiboken::AutoDecRef ob_name(PyObject_GetAttrString(ob, "__name__")); + Shiboken::AutoDecRef ob_key(GetClassKey(ob)); + + PyObject *dict = TypeKey_to_PropsDict(ob_key, ob); + if (dict == nullptr) + return nullptr; + PyObject *props = PyDict_GetItem(dict, ob_name); + if (props == nullptr) Py_RETURN_NONE; - sig_kind = "method"; - value = PyDict_GetItemString(props, sig_kind); - if (value == NULL) { + return GetSignature_Cached(props, "method", modifier); +} + +static PyObject * +GetSignature_Cached(PyObject *props, const char *sig_kind, const char *modifier) +{ + Shiboken::AutoDecRef key(modifier == nullptr + ? Py_BuildValue("s", sig_kind) + : Py_BuildValue("(ss)", sig_kind, modifier)); + PyObject *value = PyDict_GetItem(props, key); + if (value == nullptr) { // we need to compute a signature object - value = CreateSignature(props, sig_kind); - if (value != NULL) { - if (PyDict_SetItemString(props, sig_kind, value) < 0) - return NULL; + value = CreateSignature(props, key); + if (value != nullptr) { + if (PyDict_SetItem(props, key, value) < 0) + // this is an error + return nullptr; } - else + else { + // key not found Py_RETURN_NONE; + } } return Py_INCREF(value), value; } - static const char PySide_PythonCode[] = - "from __future__ import print_function, absolute_import\n" - "import sys, os, traceback\n" - - "pyside_package_dir = os.environ.get('PYSIDE_PACKAGE_DIR', '.')\n" - "__file__ = os.path.join(pyside_package_dir, 'support', 'signature', 'loader.py')\n" - - "def bootstrap():\n" - " try:\n" - " with open(__file__) as _f:\n" - " exec(compile(_f.read(), __file__, 'exec'))\n" - " except Exception as e:\n" - " print('Exception:', e)\n" - " traceback.print_exc(file=sys.stdout)\n" - " globals().update(locals())\n" - ; + "from __future__ import print_function, absolute_import\n" R"~(if True: + + # This is becoming the 'signature_loader' module. + + import sys, os, traceback + # We avoid imports in phase 1 that could fail. "import shiboken" of the + # binary would even crash in FinishSignatureInitialization. + + def bootstrap(): + global __file__ + try: + import shiboken2 as root + except ImportError: + # uninstalled case without ctest, try only this one which has __init__: + from shibokenmodule import shiboken2 as root + rp = os.path.realpath(os.path.dirname(root.__file__)) + # This can be the shiboken2 directory or the binary module, so search. + while len(rp) > 3 and not os.path.exists(os.path.join(rp, 'support')): + rp = os.path.abspath(os.path.join(rp, '..')) + __file__ = os.path.join(rp, 'support', 'signature', 'loader.py') + try: + with open(__file__) as _f: + exec(compile(_f.read(), __file__, 'exec')) + except Exception as e: + print('Exception:', e) + traceback.print_exc(file=sys.stdout) + globals().update(locals()) + + )~"; static safe_globals_struc * init_phase_1(void) { - safe_globals_struc *p; PyObject *d, *v; - - p = (safe_globals_struc *)malloc(sizeof(safe_globals_struc)); + safe_globals_struc *p = (safe_globals_struc *) + malloc(sizeof(safe_globals_struc)); if (p == NULL) goto error; p->helper_module = PyImport_AddModule((char *) helper_module_name); @@ -373,11 +461,10 @@ init_phase_1(void) if (p->map_dict == NULL) goto error; - // Build a dict for the prepared arguments + // build a dict for the prepared arguments p->arg_dict = PyDict_New(); - if (p->arg_dict == NULL) - goto error; - if (PyObject_SetAttrString(p->helper_module, arg_name, p->arg_dict) < 0) + if (p->arg_dict == NULL + || PyObject_SetAttrString(p->helper_module, arg_name, p->arg_dict) < 0) goto error; return p; @@ -387,16 +474,24 @@ error: } static int -init_phase_2(safe_globals_struc *p) +init_phase_2(safe_globals_struc *p, PyMethodDef *methods) { - PyObject *bootstrap_func; - + PyObject *bootstrap_func, *v = nullptr; + PyMethodDef *ml; + + // The single function to be called, but maybe more to come. + for (ml = methods; ml->ml_name != NULL; ml++) { + v = PyCFunction_NewEx(ml, nullptr, nullptr); + if (v == nullptr + || PyObject_SetAttrString(p->helper_module, ml->ml_name, v) != 0) + goto error; + Py_DECREF(v); + } bootstrap_func = PyObject_GetAttrString(p->helper_module, bootstrap_name); - if (bootstrap_func == NULL) + if (bootstrap_func == NULL + || PyObject_CallFunction(bootstrap_func, (char *)"()") == NULL) goto error; - if (PyObject_CallFunction(bootstrap_func, (char *)"()") == NULL) - goto error; - // now the loader is initialized + // now the loader should be initialized p->sigparse_func = PyObject_GetAttrString(p->helper_module, func_name); if (p->sigparse_func == NULL) goto error; @@ -406,6 +501,8 @@ init_phase_2(safe_globals_struc *p) return 0; error: + Py_XDECREF(v); + PyErr_Print(); PyErr_SetString(PyExc_SystemError, "could not initialize part 2"); return -1; } @@ -413,20 +510,17 @@ error: static int add_more_getsets(PyTypeObject *type, PyGetSetDef *gsp) { + assert(PyType_Check(type)); + PyType_Ready(type); PyObject *dict = type->tp_dict; - for (; gsp->name != NULL; gsp++) { - PyObject *descr; if (PyDict_GetItemString(dict, gsp->name)) continue; - descr = PyDescr_NewGetSet(type, gsp); - if (descr == NULL) + Shiboken::AutoDecRef descr(PyDescr_NewGetSet(type, gsp)); + if (descr.isNull()) return -1; - if (PyDict_SetItemString(dict, gsp->name, descr) < 0) { - Py_DECREF(descr); + if (PyDict_SetItemString(dict, gsp->name, descr) < 0) return -1; - } - Py_DECREF(descr); } return 0; } @@ -463,6 +557,47 @@ static PyGetSetDef new_PyType_getsets[] = { {0} }; +static PyGetSetDef new_PyWrapperDescr_getsets[] = { + {(char *) "__signature__", (getter)pyside_wd_get___signature__}, + {0} +}; + +//////////////////////////////////////////////////////////////////////////// +// +// get_signature -- providing a superior interface +// +// Additionally to the interface via __signature__, we also provide +// a general function, which allows for different signature layouts. +// The "modifier" argument is a string that is passed in from loader.py . +// Configuration what the modifiers mean is completely in Python. +// + +static PyObject * +get_signature(PyObject *self, PyObject *args) +{ + PyObject *ob; + const char *modifier = nullptr; + + init_module_1(); + + if (!PyArg_ParseTuple(args, "O|s", &ob, &modifier)) + return NULL; + if (Py_TYPE(ob) == PepFunction_TypePtr) + Py_RETURN_NONE; + + if (Py_TYPE(ob) == &PyCFunction_Type) + return pyside_cf_get___signature__(ob, modifier); + if (Py_TYPE(ob) == PepStaticMethod_TypePtr) + return pyside_sm_get___signature__(ob, modifier); + if (Py_TYPE(ob) == PepMethodDescr_TypePtr) + return pyside_md_get___signature__(ob, modifier); + if (PyType_Check(ob)) + return pyside_tp_get___signature__(ob, modifier); + if (Py_TYPE(ob) == &PyWrapperDescr_Type) + return pyside_wd_get___signature__(ob, modifier); + Py_RETURN_NONE; +} + //////////////////////////////////////////////////////////////////////////// // // This special Type_Ready does certain initializations earlier with @@ -495,25 +630,22 @@ void handler(int sig) { #endif // _WIN32 static int -PySideType_Ready(PyTypeObject *type) +PySide_PatchTypes(void) { - PyObject *md; static int init_done = 0; if (!init_done) { - // Python2 does not expose certain types. We look them up: - // PyMethodDescr_Type 'type(str.__dict__["split"])' - // PyClassMethodDescr_Type. 'type(dict.__dict__["fromkeys"])' - // The latter is not needed until we use class methods in PySide. - md = PyObject_GetAttrString((PyObject *)&PyString_Type, "split"); - if (md == NULL + Shiboken::AutoDecRef md(PyObject_GetAttrString((PyObject *)&PyString_Type, "split")); // method-descriptor + Shiboken::AutoDecRef wd(PyObject_GetAttrString((PyObject *)Py_TYPE(Py_True), "__add__")); // wrapper-descriptor + if (md.isNull() || wd.isNull() || PyType_Ready(Py_TYPE(md)) < 0 - || add_more_getsets(Py_TYPE(md), new_PyMethodDescr_getsets) < 0 + || add_more_getsets(PepMethodDescr_TypePtr, new_PyMethodDescr_getsets) < 0 || add_more_getsets(&PyCFunction_Type, new_PyCFunction_getsets) < 0 || add_more_getsets(PepStaticMethod_TypePtr, new_PyStaticMethod_getsets) < 0 - || add_more_getsets(&PyType_Type, new_PyType_getsets) < 0) + || add_more_getsets(&PyType_Type, new_PyType_getsets) < 0 + || add_more_getsets(Py_TYPE(wd), new_PyWrapperDescr_getsets) < 0 + ) return -1; - Py_DECREF(md); #ifndef _WIN32 // We enable the stack trace in CI, only. const char *testEnv = getenv("QTEST_ENVIRONMENT"); @@ -522,58 +654,38 @@ PySideType_Ready(PyTypeObject *type) #endif // _WIN32 init_done = 1; } - return PyType_Ready(type); + return 0; } -static int -build_func_to_type(PyObject *obtype) +static void +init_module_1(void) { - PyTypeObject *type = (PyTypeObject *)obtype; - PyObject *dict = type->tp_dict; - PyMethodDef *meth = type->tp_methods; - - if (meth == 0) - return 0; + static int init_done = 0; - for (; meth->ml_name != NULL; meth++) { - if (meth->ml_flags & METH_STATIC) { - PyObject *descr = PyDict_GetItemString(dict, meth->ml_name); - if (descr == NULL) - return -1; - PyObject *func = PyObject_GetAttrString(descr, "__func__"); - if (func == NULL || - PyDict_SetItem(pyside_globals->map_dict, func, obtype) < 0) - return -1; - Py_DECREF(func); - } + if (!init_done) { + pyside_globals = init_phase_1(); + if (pyside_globals != nullptr) + init_done = 1; } - return 0; } static int PySide_BuildSignatureArgs(PyObject *module, PyObject *type, const char *signatures) { - PyObject *type_name, *arg_tup; - const char *name = NULL; - static int init_done = 0; + PyObject *type_key, *arg_tup; - if (!init_done) { - pyside_globals = init_phase_1(); - if (pyside_globals == NULL) - return -1; - init_done = 1; - } + init_module_1(); arg_tup = Py_BuildValue("(Os)", type, signatures); if (arg_tup == NULL) return -1; + /* + * We either get a module name or the dict of an EnclosingObject. + * We can ignore the EnclosingObject since we get full name info + * from the type. + */ if (!PyModule_Check(module)) - return 0; - name = PyModule_GetName(module); - if (name == NULL) - return -1; - if (strncmp(name, "PySide2.Qt", 10) != 0) - return 0; + assert(PyDict_Check(module)); /* * Normally, we would now just call the Python function with the * arguments and then continue processing. @@ -585,51 +697,69 @@ PySide_BuildSignatureArgs(PyObject *module, PyObject *type, * - by calling the python function late, we can freely import PySide * without recursion problems. */ - type_name = PyObject_GetAttrString(type, "__name__"); - if (type_name == NULL) + type_key = GetClassKey(type); + if (type_key == nullptr) return -1; - if (PyDict_SetItem(pyside_globals->arg_dict, type_name, arg_tup) < 0) + if (PyDict_SetItem(pyside_globals->arg_dict, type_key, arg_tup) < 0) return -1; /* - * We record also a mapping from type name to type. This helps to lazily - * initialize the Py_LIMITED_API in qualname_to_func(). + * We record also a mapping from type key to type. This helps to lazily + * initialize the Py_LIMITED_API in name_key_to_func(). */ - if (PyDict_SetItem(pyside_globals->map_dict, type_name, type) < 0) + + if (PyDict_SetItem(pyside_globals->map_dict, type_key, type) < 0) return -1; return 0; } -static PyObject * -PySide_BuildSignatureProps(PyObject *classmod) +static PyMethodDef signature_methods[] = { + {"get_signature", (PyCFunction)get_signature, METH_VARARGS, + "get the __signature__, but pass an optional string parameter"}, + {NULL, NULL} +}; + +static void +init_module_2(void) { - PyObject *arg_tup, *dict, *type_name; static int init_done = 0; if (!init_done) { - if (init_phase_2(pyside_globals) < 0) - return NULL; + // Phase 2 will call __init__.py which touches a signature, itself. + // Therefore we set init_done prior to init_phase_2(). init_done = 1; + init_phase_2(pyside_globals, signature_methods); } +} + +static PyObject * +PySide_BuildSignatureProps(PyObject *classmod) +{ /* * Here is the second part of the function. * This part will be called on-demand when needed by some attribute. * We simply pick up the arguments that we stored here and replace * them by the function result. */ - type_name = PyObject_GetAttrString(classmod, "__name__"); - if (type_name == NULL) - return NULL; - arg_tup = PyDict_GetItem(pyside_globals->arg_dict, type_name); - if (arg_tup == NULL) - return NULL; - dict = PyObject_CallObject(pyside_globals->sigparse_func, arg_tup); - if (dict == NULL) - return NULL; + init_module_2(); + Shiboken::AutoDecRef type_key(GetClassKey(classmod)); + if (type_key.isNull()) + return nullptr; + PyObject *arg_tup = PyDict_GetItem(pyside_globals->arg_dict, type_key); + if (arg_tup == nullptr) + return nullptr; + PyObject *dict = PyObject_CallObject(pyside_globals->sigparse_func, arg_tup); + if (dict == nullptr) { + if (PyErr_Occurred()) + return nullptr; + // No error: return an empty dict. + if (empty_dict == nullptr) + empty_dict = PyDict_New(); + return empty_dict; + } // We replace the arguments by the result dict. - if (PyDict_SetItem(pyside_globals->arg_dict, type_name, dict) < 0) - return NULL; - Py_DECREF(type_name); + if (PyDict_SetItem(pyside_globals->arg_dict, type_key, dict) < 0) + return nullptr; return dict; } @@ -638,7 +768,7 @@ SbkSpecial_Type_Ready(PyObject *module, PyTypeObject *type, const char *signatures) { int ret; - if (PySideType_Ready(type) < 0) + if (PyType_Ready(type) < 0) return -1; ret = PySide_BuildSignatureArgs(module, (PyObject *)type, signatures); if (ret < 0) { @@ -648,27 +778,25 @@ SbkSpecial_Type_Ready(PyObject *module, PyTypeObject *type, return ret; } +static int _finish_nested_classes(PyObject *dict); +static int _build_func_to_type(PyObject *obtype); + static int PySide_FinishSignatures(PyObject *module, const char *signatures) { - const char *name = NULL; + /* + * Initialization of module functions and resolving of static methods. + */ - // CRUCIAL: Do not call this on "testbinding": - // The module is different and should not get signatures, anyway. - name = PyModule_GetName(module); + const char *name = PyModule_GetName(module); if (name == NULL) return -1; - if (strncmp(name, "PySide2.Qt", 10) != 0) - return 0; // we abuse the call for types, since they both have a __name__ attribute. if (PySide_BuildSignatureArgs(module, module, signatures) < 0) return -1; /* - * Python2 does not abuse the 'm_self' field for the type. So we need to - * supply this for all static methods. - * * Note: This function crashed when called from PySide_BuildSignatureArgs. * Probably this was too early. * @@ -676,20 +804,111 @@ PySide_FinishSignatures(PyObject *module, const char *signatures) * to the PyCFunction attributes. Therefore I simplified things * and always use our own mapping. */ - { - PyObject *key, *value; - Py_ssize_t pos = 0; - PyObject *dict = PyModule_GetDict(module); + PyObject *key, *func, *obdict = PyModule_GetDict(module); + Py_ssize_t pos = 0; + + while (PyDict_Next(obdict, &pos, &key, &func)) + if (PyCFunction_Check(func)) + if (PyDict_SetItem(pyside_globals->map_dict, func, module) < 0) + return -1; + if (_finish_nested_classes(obdict) < 0) + return -1; + return 0; +} + +static int +_finish_nested_classes(PyObject *obdict) +{ + PyObject *key, *value, *obtype; + PyTypeObject *subtype; + Py_ssize_t pos = 0; + + if (obdict == NULL) + return -1; + while (PyDict_Next(obdict, &pos, &key, &value)) { + if (PyType_Check(value)) { + obtype = value; + if (_build_func_to_type(obtype) < 0) + return -1; + // now continue with nested cases + subtype = reinterpret_cast<PyTypeObject *>(obtype); + if (_finish_nested_classes(subtype->tp_dict) < 0) + return -1; + } + } + return 0; +} + +static int +_build_func_to_type(PyObject *obtype) +{ + /* + * There is no general way to directly get the type of a static method. + * On Python 3, the type is hidden in an unused pointer in the + * PyCFunction structure, but the Limited API does not allow to access + * this, either. + * + * In the end, it was easier to avoid such tricks and build an explicit + * mapping from function to type. + * + * We walk through the method list of the type + * and record the mapping from static method to this type in a dict. + * We also check for hidden methods, see below. + */ + PyTypeObject *type = reinterpret_cast<PyTypeObject *>(obtype); + PyObject *dict = type->tp_dict; + PyMethodDef *meth = type->tp_methods; - if (dict == NULL) + if (meth == 0) + return 0; + + for (; meth->ml_name != NULL; meth++) { + /* + * It is possible that a method is overwritten by another + * attribute with the same name. This case was obviously provoked + * explicitly in "testbinding.TestObject.staticMethodDouble", + * where instead of the method a "PySide2.QtCore.Signal" object + * was in the dict. + * This overlap is also found in regular PySide under + * "PySide2.QtCore.QProcess.error" where again a signal object is + * returned. These hidden methods will be opened for the + * signature module by adding them under the name + * "{name}.overload". + */ + PyObject *descr = PyDict_GetItemString(dict, meth->ml_name); + const char *look_attr = meth->ml_flags & METH_STATIC ? "__func__" : "__name__"; + int check_name = meth->ml_flags & METH_STATIC ? 0 : 1; + if (descr == NULL) return -1; - while (PyDict_Next(dict, &pos, &key, &value)) { - if (PyType_Check(value)) { - PyObject *type = value; - if (build_func_to_type(type) < 0) - return -1; - } + // We first check all methods if one is hidden by something else. + Shiboken::AutoDecRef look(PyObject_GetAttrString(descr, look_attr)); + Shiboken::AutoDecRef given(Py_BuildValue("s", meth->ml_name)); + if (look.isNull() + || (check_name && PyObject_RichCompareBool(look, given, Py_EQ) != 1)) { + PyErr_Clear(); + Shiboken::AutoDecRef cfunc(PyCFunction_NewEx(meth, (PyObject*)type, NULL)); + if (cfunc.isNull()) + return -1; + if (meth->ml_flags & METH_STATIC) + descr = PyStaticMethod_New(cfunc); + else + descr = PyDescr_NewMethod(type, meth); + if (descr == nullptr) + return -1; + char mangled_name[200]; + strcpy(mangled_name, meth->ml_name); + strcat(mangled_name, ".overload"); + if (PyDict_SetItemString(dict, mangled_name, descr) < 0) + return -1; + if (PyDict_SetItemString(pyside_globals->map_dict, mangled_name, obtype) < 0) + return -1; + continue; + } + // Then we insert the mapping for static methods. + if (meth->ml_flags & METH_STATIC) { + if (PyDict_SetItem(pyside_globals->map_dict, look, obtype) < 0) + return -1; } } return 0; @@ -698,6 +917,15 @@ PySide_FinishSignatures(PyObject *module, const char *signatures) void FinishSignatureInitialization(PyObject *module, const char *signatures) { + /* + * This function is called at the very end of a module initialization. + * We now patch certain types to support the __signature__ attribute, + * initialize module functions and resolve static methods. + * + * Still, it is not possible to call init phase 2 from here, + * because the import is still running. Do it from Python! + */ + PySide_PatchTypes(); if (PySide_FinishSignatures(module, signatures) < 0) { PyErr_Print(); PyErr_SetNone(PyExc_ImportError); |