Fixes so that fastproxy and autodoc work correctly with both low-level C API and high-level Python Shadow API

This commit is contained in:
Alec Woods 2019-01-25 08:06:17 -05:00
commit 86e08c8e34
5 changed files with 163 additions and 42 deletions

View file

@ -12,8 +12,8 @@ Version 4.0.0 (in progress)
#728 Fixed the handling of autodoc when using -fastproxy.
Added documentation to wrapped member variables using the propery(... doc="...")
construct.
#1367 Added documentation to wrapped member variables using the
propery(... doc="...") construct.
Only show a single documentation entry for functions with default arguments when
using autodoc.

View file

@ -191,37 +191,27 @@ check(inspect.getdoc(A.variable_c),
"A.variable_c"
)
check(inspect.getdoc(_autodoc.A_variable_c_set),
"A_variable_c_set(self, variable_c) -> int\n"
"A_variable_c_set(self, variable_c)\n"
"\n"
"Parameters\n"
"----------\n"
"self: A *\n"
"variable_c: int"
)
check(inspect.getdoc(_autodoc.A_variable_c_get),
"A_variable_c_get(self) -> int\n"
"\n"
"Parameters\n"
"----------\n"
"self: A *"
"A_variable_c_get(self) -> int"
)
check(inspect.getdoc(A.variable_d),
"A.variable_d : int"
)
check(inspect.getdoc(_autodoc.A_variable_d_set),
"A_variable_d_set(A self, int variable_d) -> int\n"
"A_variable_d_set(A self, int variable_d)\n"
"\n"
"Parameters\n"
"----------\n"
"self: A *\n"
"variable_d: int"
)
check(inspect.getdoc(_autodoc.A_variable_d_get),
"A_variable_d_get(self) -> int\n"
"\n"
"Parameters\n"
"----------\n"
"self: A *"
"A_variable_d_get(A self) -> int"
)
check(inspect.getdoc(B),

View file

@ -24,6 +24,11 @@ typedef struct swig_const_info {
swig_type_info **ptype;
} swig_const_info;
/* -----------------------------------------------------------------------------
* Function to find the method definition with the correct docstring for the
* proxy module as opposed to the low-level API
* ----------------------------------------------------------------------------- */
PyMethodDef* getProxyDoc(const char* name);
/* -----------------------------------------------------------------------------
* Wrapper of PyInstanceMethod_New() used in Python 3
@ -31,6 +36,17 @@ typedef struct swig_const_info {
* ----------------------------------------------------------------------------- */
SWIGRUNTIME PyObject* SWIG_PyInstanceMethod_New(PyObject *SWIGUNUSEDPARM(self), PyObject *func)
{
if (PyCFunction_Check(func)) {
/* Unpack the existing PyCFunction */
PyMethodDef* ml = ((PyCFunctionObject*) func)->m_ml;
PyObject* self = ((PyCFunctionObject*) func)->m_self;
PyObject* module = ((PyCFunctionObject*) func)->m_module;
/* Use the copy with the modified docstring if available */
ml = getProxyDoc(ml->ml_name);
if (ml != NULL) {
func = PyCFunction_NewEx(ml, self, module);
}
}
#if PY_VERSION_HEX >= 0x03000000
return PyInstanceMethod_New(func);
#else
@ -38,6 +54,26 @@ SWIGRUNTIME PyObject* SWIG_PyInstanceMethod_New(PyObject *SWIGUNUSEDPARM(self),
#endif
}
/* -----------------------------------------------------------------------------
* Wrapper of PyStaticMethod_New()
* It is exported to the generated module, used for -fastproxy
* ----------------------------------------------------------------------------- */
SWIGRUNTIME PyObject* SWIG_PyStaticMethod_New(PyObject *SWIGUNUSEDPARM(self), PyObject *func)
{
if (PyCFunction_Check(func)) {
/* Unpack the existing PyCFunction */
PyMethodDef* ml = ((PyCFunctionObject*) func)->m_ml;
PyObject* self = ((PyCFunctionObject*) func)->m_self;
PyObject* module = ((PyCFunctionObject*) func)->m_module;
/* Use the copy with the modified docstring if available */
ml = getProxyDoc(ml->ml_name);
if (ml != NULL) {
func = PyCFunction_NewEx(ml, self, module);
}
}
return PyStaticMethod_New(func);
}
#ifdef __cplusplus
}
#endif

View file

@ -1272,8 +1272,9 @@ int Language::memberfunctionHandler(Node *n) {
int flags = Getattr(n, "template") ? extendmember | SmartPointer : Extend | SmartPointer | DirectorExtraCall;
Swig_MethodToFunction(n, NSpace, ClassType, flags, director_type, is_member_director(CurrentClass, n));
Setattr(n, "sym:name", fname);
// Save the original name for use in documentation
Setattr(n, "doc:name", symname);
/* Explicitly save low-level and high-level documentation names */
Setattr(n, "doc:low:name", fname);
Setattr(n, "doc:high:name", symname);
functionWrapper(n);
@ -1334,8 +1335,9 @@ int Language::staticmemberfunctionHandler(Node *n) {
Setattr(n, "name", cname);
Setattr(n, "sym:name", mrename);
// Save the original name for use in documentation
Setattr(n, "doc:name", symname);
/* Explicitly save low-level and high-level documentation names */
Setattr(n, "doc:low:name", mrename);
Setattr(n, "doc:high:name", symname);
if (cb) {
String *cbname = NewStringf(cb, symname);

View file

@ -62,6 +62,7 @@ static String *builtin_default_unref = 0;
static String *builtin_closures_code = 0;
static String *methods;
static String *methods_proxydocs;
static String *class_name;
static String *shadow_indent = 0;
static int in_class = 0;
@ -572,6 +573,7 @@ public:
const_code = NewString("");
methods = NewString("");
methods_proxydocs = NewString("");
Swig_banner(f_begin);
@ -702,6 +704,7 @@ public:
if (!builtin && fastproxy) {
Printf(f_shadow, "\n");
Printf(f_shadow, "_swig_new_instance_method = %s.SWIG_PyInstanceMethod_New\n", module);
Printf(f_shadow, "_swig_new_static_method = %s.SWIG_PyStaticMethod_New\n", module);
}
{
@ -801,9 +804,11 @@ public:
Printf(f_wrappers, "#endif\n");
Append(const_code, "static swig_const_info swig_const_table[] = {\n");
Append(methods, "static PyMethodDef SwigMethods[] = {\n");
Append(methods_proxydocs, "static PyMethodDef SwigMethods_proxydocs[] = {\n");
/* the method exported for replacement of new.instancemethod in Python 3 */
add_pyinstancemethod_new();
add_pystaticmethod_new();
if (builtin) {
SwigType *s = NewString("SwigPyObject");
@ -825,6 +830,30 @@ public:
Append(methods, "\t { NULL, NULL, 0, NULL }\n");
Append(methods, "};\n");
Printf(f_wrappers, "%s\n", methods);
Append(methods_proxydocs, "\t { NULL, NULL, 0, NULL }\n");
Append(methods_proxydocs, "};\n");
Printf(f_wrappers, "%s\n", methods_proxydocs);
/* Need to define the function to find the proxy documentation after the proxy docs themselves */
Printv(f_wrappers, "PyMethodDef* getProxyDoc(const char* name)\n",
"{\n",
" /* Find the function in the modified method table */\n",
" size_t offset = 0;\n",
" bool found = false;\n",
" while (SwigMethods_proxydocs[offset].ml_meth != NULL) {\n",
" if (strcmp(SwigMethods_proxydocs[offset].ml_name, name) == 0) {\n",
" found = true;\n",
" break;\n",
" }\n",
" offset++;\n",
" }\n",
" /* Use the copy with the modified docstring if available */\n",
" if (found) {\n",
" return &SwigMethods_proxydocs[offset];\n",
" } else {\n",
" return NULL;\n",
" }\n",
"}\n", NIL);
if (builtin) {
Dump(f_builtins, f_wrappers);
@ -926,11 +955,35 @@ public:
* ------------------------------------------------------------ */
int add_pyinstancemethod_new() {
String *name = NewString("SWIG_PyInstanceMethod_New");
Printf(methods, "\t { \"%s\", %s, METH_O, NULL},\n", name, name);
String *line = NewString("");
Printf(line, "\t { \"%s\", %s, METH_O, NULL},\n", name, name);
Append(methods, line);
if (fastproxy) {
Append(methods_proxydocs, line);
}
Delete(line);
Delete(name);
return 0;
}
/* ------------------------------------------------------------
* Emit the wrapper for PyStaticMethod_New to MethodDef array.
* This wrapper is used to ensure the correct documentation is
* generated for static methods when using -fastproxy
* ------------------------------------------------------------ */
int add_pystaticmethod_new() {
if (fastproxy) {
String *name = NewString("SWIG_PyStaticMethod_New");
String *line = NewString("");
Printf(line, "\t { \"%s\", %s, METH_O, NULL},\n", name, name);
Append(methods, line);
Append(methods_proxydocs, line);
Delete(line);
Delete(name);
}
return 0;
}
/* ------------------------------------------------------------
* subpkg_tail()
*
@ -1478,7 +1531,7 @@ public:
* may be empty if there is no docstring).
* ------------------------------------------------------------ */
String *build_combined_docstring(Node *n, autodoc_t ad_type, const String *indent = "") {
String *build_combined_docstring(Node *n, autodoc_t ad_type, const String *indent = "", bool low_level = false) {
String *docstr = Getattr(n, "feature:docstring");
if (docstr && Len(docstr)) {
docstr = Copy(docstr);
@ -1490,7 +1543,7 @@ public:
}
if (Getattr(n, "feature:autodoc") && !GetFlag(n, "feature:noautodoc")) {
String *autodoc = make_autodoc(n, ad_type);
String *autodoc = make_autodoc(n, ad_type, low_level);
if (autodoc && Len(autodoc) > 0) {
if (docstr && Len(docstr)) {
Append(autodoc, "\n");
@ -1583,9 +1636,9 @@ public:
* source code (but without quotes around it).
* ------------------------------------------------------------ */
String *cdocstring(Node *n, autodoc_t ad_type)
String *cdocstring(Node *n, autodoc_t ad_type, bool low_level = false)
{
String *ds = build_combined_docstring(n, ad_type);
String *ds = build_combined_docstring(n, ad_type, "", low_level);
Replaceall(ds, "\\", "\\\\");
Replaceall(ds, "\"", "\\\"");
Replaceall(ds, "\n", "\\n\"\n\t\t\"");
@ -1750,7 +1803,7 @@ public:
* and use it directly.
* ------------------------------------------------------------ */
String *make_autodoc(Node *n, autodoc_t ad_type) {
String *make_autodoc(Node *n, autodoc_t ad_type, bool low_level = false) {
int extended = 0;
// If the function is overloaded then this function is called
// for the last one. Rewind to the first so the docstrings are
@ -1788,10 +1841,12 @@ public:
}
if (!skipAuto) {
// Use the documentation-specific symbol name if available
String *symname = Getattr(n, "doc:name");
if (!symname)
/* Check if a documentation name was given for either the low-level C API or high-level Python shadow API */
String *symname = Getattr(n, low_level? "doc:low:name" : "doc:high:name");
if (!symname) {
symname = Getattr(n, "sym:name");
}
SwigType *type = Getattr(n, "type");
String *type_str = NULL;
@ -1810,6 +1865,14 @@ public:
}
}
/* Treat the low-level C API functions for getting/setting variables as methods for documentation purposes */
String *kind = Getattr(n, "kind");
if (kind && Strcmp(kind, "variable") == 0) {
if (ad_type == AUTODOC_FUNC) {
ad_type = AUTODOC_METHOD;
}
}
switch (ad_type) {
case AUTODOC_CLASS:
{
@ -1896,11 +1959,16 @@ public:
break;
}
Delete(type_str);
}
if (extended && ad_type != AUTODOC_VAR) {
String *pdocs = Getattr(n, "feature:pdocs");
if (pdocs) {
Printv(doc, "\n", pdocs, NULL);
// Special case: wrapper functions to get a variable should have no parameters.
// Because the node is re-used for the setter and getter, the feature:pdocs field will
// exist for the getter function, so explicitly avoid printing parameters in this case.
bool variable_getter = kind && Strcmp(kind, "variable") == 0 && Getattr(n, "memberget");
if (extended && ad_type != AUTODOC_VAR && !variable_getter) {
String *pdocs = Getattr(n, "feature:pdocs");
if (pdocs) {
Printv(doc, "\n", pdocs, NULL);
}
}
}
// if it's overloaded then get the next decl and loop around again
@ -2361,39 +2429,62 @@ public:
* ------------------------------------------------------------ */
void add_method(String *name, String *function, int kw, Node *n = 0, int funpack = 0, int num_required = -1, int num_arguments = -1) {
String * meth_str = NewString("");
if (!kw) {
if (n && funpack) {
if (num_required == 0 && num_arguments == 0) {
Printf(methods, "\t { \"%s\", %s, METH_NOARGS, ", name, function);
Printf(meth_str, "\t { \"%s\", %s, METH_NOARGS, ", name, function);
} else if (num_required == 1 && num_arguments == 1) {
Printf(methods, "\t { \"%s\", %s, METH_O, ", name, function);
Printf(meth_str, "\t { \"%s\", %s, METH_O, ", name, function);
} else {
Printf(methods, "\t { \"%s\", %s, METH_VARARGS, ", name, function);
Printf(meth_str, "\t { \"%s\", %s, METH_VARARGS, ", name, function);
}
} else {
Printf(methods, "\t { \"%s\", %s, METH_VARARGS, ", name, function);
Printf(meth_str, "\t { \"%s\", %s, METH_VARARGS, ", name, function);
}
} else {
// Cast via void(*)(void) to suppress GCC -Wcast-function-type warning.
// Python should always call the function correctly, but the Python C API
// requires us to store it in function pointer of a different type.
Printf(methods, "\t { \"%s\", (PyCFunction)(void(*)(void))%s, METH_VARARGS|METH_KEYWORDS, ", name, function);
Printf(meth_str, "\t { \"%s\", (PyCFunction)(void(*)(void))%s, METH_VARARGS|METH_KEYWORDS, ", name, function);
}
Append(methods, meth_str);
if (fastproxy) {
Append(methods_proxydocs, meth_str);
}
Delete(meth_str);
if (!n) {
Append(methods, "NULL");
if (fastproxy) {
Append(methods_proxydocs, "NULL");
}
} else if (have_docstring(n)) {
// The format for the documentation differs based on whether this is a member function or a free function
String *ds = cdocstring(n, Getattr(n, "memberfunction") ? AUTODOC_METHOD : AUTODOC_FUNC);
/* Use the low-level docstring here since this is the docstring that will be used for the C API */
String *ds = cdocstring(n, Getattr(n, "memberfunction") ? AUTODOC_METHOD : AUTODOC_FUNC, true);
Printf(methods, "\"%s\"", ds);
if (fastproxy) {
/* In the fastproxy case, we must also record the high-level docstring for use in the Python shadow API */
ds = cdocstring(n, Getattr(n, "memberfunction") ? AUTODOC_METHOD : AUTODOC_FUNC);
Printf(methods_proxydocs, "\"%s\"", ds);
}
Delete(ds);
} else if (Getattr(n, "feature:callback")) {
Printf(methods, "\"swig_ptr: %s\"", Getattr(n, "feature:callback:name"));
if (fastproxy) {
Printf(methods_proxydocs, "\"swig_ptr: %s\"", Getattr(n, "feature:callback:name"));
}
} else {
Append(methods, "NULL");
if (fastproxy) {
Append(methods_proxydocs, "NULL");
}
}
Append(methods, "},\n");
if (fastproxy) {
Append(methods_proxydocs, "},\n");
}
}
/* ------------------------------------------------------------
@ -4684,6 +4775,7 @@ public:
}
if (shadow) {
String *staticfunc_name = NewString(fastproxy ? "_swig_new_static_method" : "staticmethod");
bool fast = (fastproxy && !have_addtofunc(n)) || Getattr(n, "feature:callback");
if (!fast || olddefs) {
int kw = (check_kwargs(n) && !Getattr(n, "sym:overloaded")) ? 1 : 0;
@ -4706,9 +4798,10 @@ public:
// Below may result in a 2nd definition of the method when -olddefs is used. The Python interpreter will use the second definition as it overwrites the first.
if (fast) {
Printv(f_shadow, tab4, symname, " = staticmethod(", module, ".", Swig_name_member(NSPACE_TODO, class_name, symname),
Printv(f_shadow, tab4, symname, " = ", staticfunc_name, "(", module, ".", Swig_name_member(NSPACE_TODO, class_name, symname),
")\n", NIL);
}
Delete(staticfunc_name);
}
return SWIG_OK;
}