summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian Tismer <tismer@stackless.com>2019-01-13 17:56:00 +0100
committerChristian Tismer <tismer@stackless.com>2019-01-15 13:33:08 +0000
commitb7707a51337cd7982d298041fa3db2ed225bfc54 (patch)
treed264067b87e9a466d275e73843921ef8f966045a
parentaef6a443a241756e9dfaff192b69d51eaa235ef6 (diff)
Support help() using the Signature Module
The signature module will be used to generate automated documentation by using the function signatures as docstrings. This functionality should be low-hanging fruit. Actually, it was a bit tricky to get this working. The crucial point was to use PyType_Modified(). The function works fine on methods. Supporting types needs some more effort. It is not clear why the __signature__ attribute can be added, but the change to __doc__ is not recognized. May be related to the absence of Py_TPFLAGS_HAVE_VERSION_TAG ? This will be addressed another time. Task-number: PYSIDE-908 Change-Id: If8faa87927899f4c072d42b91eafd8f7658c6abc Reviewed-by: Cristian Maureira-Fredes <cristian.maureira-fredes@qt.io>
-rw-r--r--sources/shiboken2/libshiboken/pep384impl.cpp19
-rw-r--r--sources/shiboken2/libshiboken/pep384impl.h2
-rw-r--r--sources/shiboken2/libshiboken/signature.cpp129
-rw-r--r--sources/shiboken2/shibokenmodule/support/signature/errorhandler.py16
-rw-r--r--sources/shiboken2/shibokenmodule/support/signature/loader.py4
5 files changed, 148 insertions, 22 deletions
diff --git a/sources/shiboken2/libshiboken/pep384impl.cpp b/sources/shiboken2/libshiboken/pep384impl.cpp
index 7cca03c8..7b333f4f 100644
--- a/sources/shiboken2/libshiboken/pep384impl.cpp
+++ b/sources/shiboken2/libshiboken/pep384impl.cpp
@@ -77,17 +77,22 @@ static struct PyMethodDef probe_methoddef[] = {
{0}
};
+static PyGetSetDef probe_getseters[] = {
+ {0} /* Sentinel */
+};
+
#define probe_tp_call make_dummy(1)
#define probe_tp_str make_dummy(2)
#define probe_tp_traverse make_dummy(3)
#define probe_tp_clear make_dummy(4)
#define probe_tp_methods probe_methoddef
-#define probe_tp_descr_get make_dummy(6)
-#define probe_tp_init make_dummy(7)
-#define probe_tp_alloc make_dummy(8)
-#define probe_tp_new make_dummy(9)
-#define probe_tp_free make_dummy(10)
-#define probe_tp_is_gc make_dummy(11)
+#define probe_tp_getset probe_getseters
+#define probe_tp_descr_get make_dummy(7)
+#define probe_tp_init make_dummy(8)
+#define probe_tp_alloc make_dummy(9)
+#define probe_tp_new make_dummy(10)
+#define probe_tp_free make_dummy(11)
+#define probe_tp_is_gc make_dummy(12)
#define probe_tp_name "type.probe"
#define probe_tp_basicsize make_dummy_int(42)
@@ -98,6 +103,7 @@ static PyType_Slot typeprobe_slots[] = {
{Py_tp_traverse, probe_tp_traverse},
{Py_tp_clear, probe_tp_clear},
{Py_tp_methods, probe_tp_methods},
+ {Py_tp_getset, probe_tp_getset},
{Py_tp_descr_get, probe_tp_descr_get},
{Py_tp_init, probe_tp_init},
{Py_tp_alloc, probe_tp_alloc},
@@ -138,6 +144,7 @@ check_PyTypeObject_valid(void)
|| probe_tp_clear != check->tp_clear
|| probe_tp_weakrefoffset != typetype->tp_weaklistoffset
|| probe_tp_methods != check->tp_methods
+ || probe_tp_getset != check->tp_getset
|| probe_tp_base != typetype->tp_base
|| !PyDict_Check(check->tp_dict)
|| !PyDict_GetItemString(check->tp_dict, "dummy")
diff --git a/sources/shiboken2/libshiboken/pep384impl.h b/sources/shiboken2/libshiboken/pep384impl.h
index bfa8f38a..d883677c 100644
--- a/sources/shiboken2/libshiboken/pep384impl.h
+++ b/sources/shiboken2/libshiboken/pep384impl.h
@@ -113,7 +113,7 @@ typedef struct _typeobject {
void *X26; // iternextfunc tp_iternext;
struct PyMethodDef *tp_methods;
void *X28; // struct PyMemberDef *tp_members;
- void *X29; // struct PyGetSetDef *tp_getset;
+ struct PyGetSetDef *tp_getset;
struct _typeobject *tp_base;
PyObject *tp_dict;
descrgetfunc tp_descr_get;
diff --git a/sources/shiboken2/libshiboken/signature.cpp b/sources/shiboken2/libshiboken/signature.cpp
index a8874e2e..da9c56c6 100644
--- a/sources/shiboken2/libshiboken/signature.cpp
+++ b/sources/shiboken2/libshiboken/signature.cpp
@@ -75,6 +75,7 @@ typedef struct safe_globals_struc {
PyObject *sigparse_func;
PyObject *createsig_func;
PyObject *seterror_argument_func;
+ PyObject *make_helptext_func;
} safe_globals_struc, *safe_globals;
static safe_globals pyside_globals = 0;
@@ -136,13 +137,15 @@ _get_class_of_cf(PyObject *ob_cf)
// This must be an overloaded function that we handled special.
Shiboken::AutoDecRef special(Py_BuildValue("(Os)", ob_cf, "overload"));
selftype = PyDict_GetItem(pyside_globals->map_dict, special);
+ if (selftype == nullptr) {
+ // This is probably a module function. We will return type(None).
+ selftype = Py_None;
+ }
}
}
- assert(selftype);
PyObject *typemod = (PyType_Check(selftype) || PyModule_Check(selftype))
? selftype : (PyObject *)Py_TYPE(selftype);
- // do we support module functions?
Py_INCREF(typemod);
return typemod;
}
@@ -514,6 +517,9 @@ init_phase_2(safe_globals_struc *p, PyMethodDef *methods)
p->seterror_argument_func = PyObject_GetAttrString(p->helper_module, "seterror_argument");
if (p->seterror_argument_func == NULL)
goto error;
+ p->make_helptext_func = PyObject_GetAttrString(p->helper_module, "make_helptext");
+ if (p->make_helptext_func == NULL)
+ goto error;
return 0;
error:
@@ -524,20 +530,50 @@ error:
}
static int
-add_more_getsets(PyTypeObject *type, PyGetSetDef *gsp)
+_fixup_getset(PyTypeObject *type, const char *name, PyGetSetDef *new_gsp)
{
+ PyGetSetDef *gsp = type->tp_getset;
+ if (gsp != nullptr) {
+ for (; gsp->name != NULL; gsp++) {
+ if (strcmp(gsp->name, name) == 0) {
+ new_gsp->set = gsp->set;
+ new_gsp->doc = gsp->doc;
+ new_gsp->closure = gsp->closure;
+ return 1;
+ }
+ }
+ }
+ // staticmethod has just a __doc__ in the class
+ assert(strcmp(type->tp_name, "staticmethod") == 0);
+ return 0;
+}
+
+static int
+add_more_getsets(PyTypeObject *type, PyGetSetDef *gsp, PyObject **old_descr)
+{
+ /*
+ * This function is used to assign a new __signature__ attribute,
+ * and also to override a __doc__ attribute.
+ */
assert(PyType_Check(type));
PyType_Ready(type);
PyObject *dict = type->tp_dict;
for (; gsp->name != NULL; gsp++) {
- if (PyDict_GetItemString(dict, gsp->name))
- continue;
+ PyObject *have_descr = PyDict_GetItemString(dict, gsp->name);
+ if (have_descr != nullptr) {
+ assert(strcmp(gsp->name, "__doc__") == 0);
+ Py_INCREF(have_descr);
+ *old_descr = have_descr;
+ if (!_fixup_getset(type, gsp->name, gsp))
+ continue;
+ }
Shiboken::AutoDecRef descr(PyDescr_NewGetSet(type, gsp));
if (descr.isNull())
return -1;
if (PyDict_SetItemString(dict, gsp->name, descr) < 0)
return -1;
}
+ PyType_Modified(type);
return 0;
}
@@ -553,28 +589,91 @@ add_more_getsets(PyTypeObject *type, PyGetSetDef *gsp)
// Please note that in fact we are modifying 'type', the metaclass of all
// objects, because we add new functionality.
//
+// Addendum 2019-01-12: We now also compute a docstring from the signature.
+//
+
+// keep the original __doc__ functions
+static PyObject *old_cf_doc_descr = 0;
+static PyObject *old_sm_doc_descr = 0;
+static PyObject *old_md_doc_descr = 0;
+static PyObject *old_tp_doc_descr = 0;
+static PyObject *old_wd_doc_descr = 0;
+
+static int handle_doc_in_progress = 0;
+
+static PyObject *
+handle_doc(PyObject *ob, PyObject *old_descr)
+{
+ init_module_1();
+ init_module_2();
+ Shiboken::AutoDecRef ob_type(GetClassOfFunc(ob));
+ PyTypeObject *type = reinterpret_cast<PyTypeObject *>(ob_type.object());
+ if (handle_doc_in_progress || strncmp(type->tp_name, "PySide2.", 8) != 0)
+ return PyObject_CallMethod(old_descr, const_cast<char *>("__get__"), const_cast<char *>("(O)"), ob);
+ handle_doc_in_progress++;
+ PyObject *res = PyObject_CallFunction(
+ pyside_globals->make_helptext_func,
+ const_cast<char *>("(O)"), ob);
+ handle_doc_in_progress--;
+ if (res == nullptr) {
+ PyErr_Print();
+ Py_FatalError("handle_doc did not receive a result");
+ }
+ return res;
+}
+
+static PyObject *
+pyside_cf_get___doc__(PyObject *cf) {
+ return handle_doc(cf, old_cf_doc_descr);
+}
+
+static PyObject *
+pyside_sm_get___doc__(PyObject *sm) {
+ return handle_doc(sm, old_sm_doc_descr);
+}
+
+static PyObject *
+pyside_md_get___doc__(PyObject *md) {
+ return handle_doc(md, old_md_doc_descr);
+}
+
+static PyObject *
+pyside_tp_get___doc__(PyObject *tp) {
+ return handle_doc(tp, old_tp_doc_descr);
+}
+
+static PyObject *
+pyside_wd_get___doc__(PyObject *wd) {
+ return handle_doc(wd, old_wd_doc_descr);
+}
+
static PyGetSetDef new_PyCFunction_getsets[] = {
- {(char *) "__signature__", (getter)pyside_cf_get___signature__},
+ {const_cast<char *>("__signature__"), (getter)pyside_cf_get___signature__},
+ {const_cast<char *>("__doc__"), (getter)pyside_cf_get___doc__},
{0}
};
static PyGetSetDef new_PyStaticMethod_getsets[] = {
- {(char *) "__signature__", (getter)pyside_sm_get___signature__},
+ {const_cast<char *>("__signature__"), (getter)pyside_sm_get___signature__},
+ {const_cast<char *>("__doc__"), (getter)pyside_sm_get___doc__},
{0}
};
static PyGetSetDef new_PyMethodDescr_getsets[] = {
- {(char *) "__signature__", (getter)pyside_md_get___signature__},
+ {const_cast<char *>("__signature__"), (getter)pyside_md_get___signature__},
+ {const_cast<char *>("__doc__"), (getter)pyside_md_get___doc__},
{0}
};
static PyGetSetDef new_PyType_getsets[] = {
- {(char *) "__signature__", (getter)pyside_tp_get___signature__},
+ {const_cast<char *>("__signature__"), (getter)pyside_tp_get___signature__},
+ {const_cast<char *>("__doc__"), (getter)pyside_tp_get___doc__},
{0}
};
static PyGetSetDef new_PyWrapperDescr_getsets[] = {
- {(char *) "__signature__", (getter)pyside_wd_get___signature__},
+ {const_cast<char *>("__signature__"), (getter)pyside_wd_get___signature__},
+ {const_cast<char *>("__doc__"), (getter)pyside_wd_get___doc__},
{0}
};
@@ -655,11 +754,11 @@ PySide_PatchTypes(void)
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(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(Py_TYPE(wd), new_PyWrapperDescr_getsets) < 0
+ || add_more_getsets(PepMethodDescr_TypePtr, new_PyMethodDescr_getsets, &old_md_doc_descr) < 0
+ || add_more_getsets(&PyCFunction_Type, new_PyCFunction_getsets, &old_cf_doc_descr) < 0
+ || add_more_getsets(PepStaticMethod_TypePtr, new_PyStaticMethod_getsets, &old_sm_doc_descr) < 0
+ || add_more_getsets(&PyType_Type, new_PyType_getsets, &old_tp_doc_descr) < 0
+ || add_more_getsets(Py_TYPE(wd), new_PyWrapperDescr_getsets, &old_wd_doc_descr) < 0
)
return -1;
#ifndef _WIN32
diff --git a/sources/shiboken2/shibokenmodule/support/signature/errorhandler.py b/sources/shiboken2/shibokenmodule/support/signature/errorhandler.py
index 902bb05a..df24234e 100644
--- a/sources/shiboken2/shibokenmodule/support/signature/errorhandler.py
+++ b/sources/shiboken2/shibokenmodule/support/signature/errorhandler.py
@@ -121,4 +121,20 @@ def seterror_argument(args, func_name):
# We don't raise the error here, to avoid the loader in the traceback.
return TypeError, msg
+
+def make_helptext(func):
+ existing_doc = func.__doc__
+ sigs = get_signature(func)
+ if not sigs:
+ return existing_doc
+ if type(sigs) != list:
+ sigs = [sigs]
+ try:
+ func_name = func.__name__
+ except AttribureError:
+ func_name = func.__func__.__name__
+ sigtext = "\n".join(func_name + str(sig) for sig in sigs)
+ msg = sigtext + "\n\n" + existing_doc if existing_doc else sigtext
+ return msg
+
# end of file
diff --git a/sources/shiboken2/shibokenmodule/support/signature/loader.py b/sources/shiboken2/shibokenmodule/support/signature/loader.py
index be30483f..8c7739d9 100644
--- a/sources/shiboken2/shibokenmodule/support/signature/loader.py
+++ b/sources/shiboken2/shibokenmodule/support/signature/loader.py
@@ -201,4 +201,8 @@ def create_signature(props, key):
def seterror_argument(args, func_name):
return errorhandler.seterror_argument(args, func_name)
+# name used in signature.cpp
+def make_helptext(func):
+ return errorhandler.make_helptext(func)
+
# end of file