diff --git a/CHANGES.current b/CHANGES.current index 8965355de..7b685b74a 100644 --- a/CHANGES.current +++ b/CHANGES.current @@ -7,6 +7,20 @@ the issue number to the end of the URL: https://github.com/swig/swig/issues/ Version 4.0.0 (in progress) =========================== +2019-02-02: ahnolds + [Python] Documentation enhancements for Python: + + #728 Fixed the handling of autodoc when using -fastproxy. + + #1367 Added documentation to wrapped member variables using the + property(... doc="...") construct. + + Only show a single documentation entry for functions with default arguments when + using autodoc. + + Fixed a bug where a cached doxygen docstring could be deleted while still in use, + causing swig to segfault. + 2019-01-31: olly SWIG now requires a target language to be specified instead of defaulting to wrapping for Tcl. Specifying swig --help without diff --git a/Examples/test-suite/autodoc.i b/Examples/test-suite/autodoc.i index 0e9356901..9f4365ee1 100644 --- a/Examples/test-suite/autodoc.i +++ b/Examples/test-suite/autodoc.i @@ -76,6 +76,10 @@ %feature("autodoc","1") D::D(int a, int b, Hola h); // names + types %feature("autodoc","2") E::E(int a, int b, Hola h); // extended %feature("autodoc","3") F::F(int a, int b, Hola h); // extended + types +%feature("autodoc","0") C::~C(); // names +%feature("autodoc","1") D::~D(); // names + types +%feature("autodoc","2") E::~E(); // extended +%feature("autodoc","3") F::~F(); // extended + types %inline { diff --git a/Examples/test-suite/python/autodoc_runme.py b/Examples/test-suite/python/autodoc_runme.py index d866cb990..6002d49fe 100644 --- a/Examples/test-suite/python/autodoc_runme.py +++ b/Examples/test-suite/python/autodoc_runme.py @@ -1,28 +1,19 @@ from autodoc import * +import _autodoc import comment_verifier import inspect import sys def check(got, expected, expected_builtin=None, skip=False): - if not skip: + if is_python_builtin() and skip: + # Only skip for builtins + pass + else: expect = expected if is_python_builtin() and expected_builtin != None: expect = expected_builtin comment_verifier.check(got, expect) -def is_fastproxy(): - fastproxy = True - try: - from autodoc import _swig_new_instance_method - except ImportError: - fastproxy = False - return fastproxy - -if is_fastproxy(): - # Detect when -fastproxy is specified and skip test as it changes the function names making it - # hard to test... skip until the number of options are reduced in SWIG-3.1 and autodoc is improved - sys.exit(0) - # skip builtin check - the autodoc is missing, but it probably should not be skip = True @@ -48,11 +39,9 @@ check(inspect.getdoc(A.func3), "hello: int tuple[2]") check(inspect.getdoc(A.func0default), - "func0default(self, e, arg3, hello, f=2) -> int\n" - "func0default(self, e, arg3, hello) -> int") + "func0default(self, e, arg3, hello, f=2) -> int") check(inspect.getdoc(A.func1default), - "func1default(A self, A e, short arg3, Tuple hello, double f=2) -> int\n" - "func1default(A self, A e, short arg3, Tuple hello) -> int") + "func1default(A self, A e, short arg3, Tuple hello, double f=2) -> int") check(inspect.getdoc(A.func2default), "func2default(self, e, arg3, hello, f=2) -> int\n" "\n" @@ -61,15 +50,7 @@ check(inspect.getdoc(A.func2default), "e: A *\n" "arg3: short\n" "hello: int tuple[2]\n" - "f: double\n" - "\n" - "func2default(self, e, arg3, hello) -> int\n" - "\n" - "Parameters\n" - "----------\n" - "e: A *\n" - "arg3: short\n" - "hello: int tuple[2]") + "f: double") check(inspect.getdoc(A.func3default), "func3default(A self, A e, short arg3, Tuple hello, double f=2) -> int\n" "\n" @@ -78,22 +59,20 @@ check(inspect.getdoc(A.func3default), "e: A *\n" "arg3: short\n" "hello: int tuple[2]\n" - "f: double\n" - "\n" - "func3default(A self, A e, short arg3, Tuple hello) -> int\n" - "\n" - "Parameters\n" - "----------\n" - "e: A *\n" - "arg3: short\n" - "hello: int tuple[2]") + "f: double") check(inspect.getdoc(A.func0static), - "func0static(e, arg2, hello, f=2) -> int\n" - "func0static(e, arg2, hello) -> int") + "func0static(e, arg2, hello, f=2) -> int") +check(inspect.getdoc(_autodoc.A_func0static), + "A_func0static(e, arg2, hello, f=2) -> int") +check(inspect.getdoc(A_func0static), + "A_func0static(e, arg2, hello, f=2) -> int") check(inspect.getdoc(A.func1static), - "func1static(A e, short arg2, Tuple hello, double f=2) -> int\n" - "func1static(A e, short arg2, Tuple hello) -> int") + "func1static(A e, short arg2, Tuple hello, double f=2) -> int") +check(inspect.getdoc(_autodoc.A_func1static), + "A_func1static(A e, short arg2, Tuple hello, double f=2) -> int") +check(inspect.getdoc(A_func1static), + "A_func1static(A e, short arg2, Tuple hello, double f=2) -> int") check(inspect.getdoc(A.func2static), "func2static(e, arg2, hello, f=2) -> int\n" "\n" @@ -102,15 +81,25 @@ check(inspect.getdoc(A.func2static), "e: A *\n" "arg2: short\n" "hello: int tuple[2]\n" - "f: double\n" - "\n" - "func2static(e, arg2, hello) -> int\n" + "f: double") +check(inspect.getdoc(_autodoc.A_func2static), + "A_func2static(e, arg2, hello, f=2) -> int\n" "\n" "Parameters\n" "----------\n" "e: A *\n" "arg2: short\n" - "hello: int tuple[2]") + "hello: int tuple[2]\n" + "f: double") +check(inspect.getdoc(A_func2static), + "A_func2static(e, arg2, hello, f=2) -> int\n" + "\n" + "Parameters\n" + "----------\n" + "e: A *\n" + "arg2: short\n" + "hello: int tuple[2]\n" + "f: double") check(inspect.getdoc(A.func3static), "func3static(A e, short arg2, Tuple hello, double f=2) -> int\n" "\n" @@ -119,42 +108,128 @@ check(inspect.getdoc(A.func3static), "e: A *\n" "arg2: short\n" "hello: int tuple[2]\n" - "f: double\n" - "\n" - "func3static(A e, short arg2, Tuple hello) -> int\n" + "f: double") +check(inspect.getdoc(_autodoc.A_func3static), + "A_func3static(A e, short arg2, Tuple hello, double f=2) -> int\n" "\n" "Parameters\n" "----------\n" "e: A *\n" "arg2: short\n" - "hello: int tuple[2]") + "hello: int tuple[2]\n" + "f: double") +check(inspect.getdoc(A_func3static), + "A_func3static(A e, short arg2, Tuple hello, double f=2) -> int\n" + "\n" + "Parameters\n" + "----------\n" + "e: A *\n" + "arg2: short\n" + "hello: int tuple[2]\n" + "f: double") -if sys.version_info[0:2] > (2, 4): - # Python 2.4 does not seem to work - check(inspect.getdoc(A.variable_a), - "A_variable_a_get(self) -> int", - "A.variable_a" - ) - check(inspect.getdoc(A.variable_b), - "A_variable_b_get(A self) -> int", - "A.variable_b" - ) - check(inspect.getdoc(A.variable_c), - "A_variable_c_get(self) -> int\n" +check(inspect.getdoc(A.variable_a), + "variable_a" + ) +check(inspect.getdoc(A.variable_b), + "variable_b : int" + ) +check(inspect.getdoc(A.variable_c), + "variable_c" + ) +check(inspect.getdoc(A.variable_d), + "variable_d : int" + ) + +# Check the low-level functions (not present when using -builtin except for the static ones) +if not is_python_builtin(): + check(inspect.getdoc(_autodoc.A_funk), "just a string.") + check(inspect.getdoc(_autodoc.A_func0), + "A_func0(self, arg2, hello) -> int") + check(inspect.getdoc(_autodoc.A_func1), + "A_func1(A self, short arg2, Tuple hello) -> int") + check(inspect.getdoc(_autodoc.A_func2), + "A_func2(self, arg2, hello) -> int\n" "\n" "Parameters\n" "----------\n" - "self: A *", - "A.variable_c" - ) - check(inspect.getdoc(A.variable_d), - "A_variable_d_get(A self) -> int\n" + "arg2: short\n" + "hello: int tuple[2]") + check(inspect.getdoc(_autodoc.A_func3), + "A_func3(A self, short arg2, Tuple hello) -> int\n" "\n" "Parameters\n" "----------\n" - "self: A *", - "A.variable_d" - ) + "arg2: short\n" + "hello: int tuple[2]") + check(inspect.getdoc(_autodoc.A_func0default), + "A_func0default(self, e, arg3, hello, f=2) -> int") + check(inspect.getdoc(_autodoc.A_func1default), + "A_func1default(A self, A e, short arg3, Tuple hello, double f=2) -> int") + check(inspect.getdoc(_autodoc.A_func2default), + "A_func2default(self, e, arg3, hello, f=2) -> int\n" + "\n" + "Parameters\n" + "----------\n" + "e: A *\n" + "arg3: short\n" + "hello: int tuple[2]\n" + "f: double") + check(inspect.getdoc(_autodoc.A_func3default), + "A_func3default(A self, A e, short arg3, Tuple hello, double f=2) -> int\n" + "\n" + "Parameters\n" + "----------\n" + "e: A *\n" + "arg3: short\n" + "hello: int tuple[2]\n" + "f: double") + check(inspect.getdoc(_autodoc.A_variable_a_set), "A_variable_a_set(self, variable_a)") + check(inspect.getdoc(_autodoc.A_variable_a_get), "A_variable_a_get(self) -> int" ) + check(inspect.getdoc(_autodoc.A_variable_b_set), "A_variable_b_set(A self, int variable_b)") + check(inspect.getdoc(_autodoc.A_variable_b_get), "A_variable_b_get(A self) -> int") + check(inspect.getdoc(_autodoc.A_variable_c_set), + "A_variable_c_set(self, variable_c)\n" + "\n" + "Parameters\n" + "----------\n" + "variable_c: int" + ) + check(inspect.getdoc(_autodoc.A_variable_c_get), "A_variable_c_get(self) -> int") + check(inspect.getdoc(_autodoc.A_variable_d_set), + "A_variable_d_set(A self, int variable_d)\n" + "\n" + "Parameters\n" + "----------\n" + "variable_d: int" + ) + check(inspect.getdoc(_autodoc.A_variable_d_get), "A_variable_d_get(A self) -> int") + check(inspect.getdoc(_autodoc.new_C), "new_C(a, b, h) -> C") + check(inspect.getdoc(_autodoc.delete_C), "delete_C(self)") + check(inspect.getdoc(_autodoc.new_D), "new_D(int a, int b, Hola h) -> D") + check(inspect.getdoc(_autodoc.delete_D), "delete_D(D self)") + check(inspect.getdoc(_autodoc.new_E), + "new_E(a, b, h) -> E\n" + "\n" + "Parameters\n" + "----------\n" + "a: special comment for parameter a\n" + "b: another special comment for parameter b\n" + "h: enum Hola" + ) + check(inspect.getdoc(_autodoc.delete_E), "delete_E(self)") + check(inspect.getdoc(_autodoc.new_F), + "new_F(int a, int b, Hola h) -> F\n" + "\n" + "Parameters\n" + "----------\n" + "a: special comment for parameter a\n" + "b: another special comment for parameter b\n" + "h: enum Hola" + ) + check(inspect.getdoc(_autodoc.delete_F), "delete_F(F self)") + check(inspect.getdoc(_autodoc.B_funk), "B_funk(B self, int c, int d) -> int") + check(inspect.getdoc(_autodoc.TInteger_inout), "TInteger_inout(TInteger self, TInteger t) -> TInteger") check(inspect.getdoc(B), "Proxy of C++ B class.", @@ -164,8 +239,6 @@ check(inspect.getdoc(C.__init__), "__init__(self, a, b, h) -> C", None, skip) check(inspect.getdoc(D.__init__), "__init__(D self, int a, int b, Hola h) -> D", None, skip) check(inspect.getdoc(E.__init__), - "__init__(self, a, b, h) -> E\n" - "\n" "__init__(self, a, b, h) -> E\n" "\n" "Parameters\n" @@ -175,8 +248,6 @@ check(inspect.getdoc(E.__init__), "h: enum Hola", None, skip ) check(inspect.getdoc(F.__init__), - "__init__(F self, int a, int b, Hola h) -> F\n" - "\n" "__init__(F self, int a, int b, Hola h) -> F\n" "\n" "Parameters\n" @@ -190,8 +261,7 @@ check(inspect.getdoc(B.funk), "funk(B self, int c, int d) -> int") check(inspect.getdoc(funk), "funk(A e, short arg2, int c, int d) -> int") check(inspect.getdoc(funkdefaults), - "funkdefaults(A e, short arg2, int c, int d, double f=2) -> int\n" - "funkdefaults(A e, short arg2, int c, int d) -> int") + "funkdefaults(A e, short arg2, int c, int d, double f=2) -> int") check(inspect.getdoc(func_input), "func_input(int * INPUT) -> int") check(inspect.getdoc(func_output), "func_output() -> int") diff --git a/Lib/python/pyapi.swg b/Lib/python/pyapi.swg index 89f57fc83..5c2c65c45 100644 --- a/Lib/python/pyapi.swg +++ b/Lib/python/pyapi.swg @@ -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 diff --git a/Source/Modules/lang.cxx b/Source/Modules/lang.cxx index 92445dc3f..f9af9723a 100644 --- a/Source/Modules/lang.cxx +++ b/Source/Modules/lang.cxx @@ -1081,6 +1081,8 @@ int Language::functionHandler(Node *n) { globalfunctionHandler(n); InClass = oldInClass; } else { + // This is a member function, set a flag so the documentation type is correct + SetFlag(n, "memberfunction"); Node *explicit_n = 0; if (directorsEnabled() && is_member_director(CurrentClass, n) && !extraDirectorProtectedCPPMethodsRequired()) { bool virtual_but_not_pure_virtual = (!(Cmp(storage, "virtual")) && (Cmp(Getattr(n, "value"), "0") != 0)); @@ -1270,6 +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); + /* Explicitly save low-level and high-level documentation names */ + Setattr(n, "doc:low:name", fname); + Setattr(n, "doc:high:name", symname); functionWrapper(n); @@ -1330,6 +1335,9 @@ int Language::staticmemberfunctionHandler(Node *n) { Setattr(n, "name", cname); Setattr(n, "sym:name", mrename); + /* 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); diff --git a/Source/Modules/python.cxx b/Source/Modules/python.cxx index 86f3a38e2..b93aa6c86 100755 --- a/Source/Modules/python.cxx +++ b/Source/Modules/python.cxx @@ -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; @@ -100,7 +101,8 @@ enum autodoc_t { AUTODOC_STATICFUNC, AUTODOC_FUNC, AUTODOC_METHOD, - AUTODOC_CONST + AUTODOC_CONST, + AUTODOC_VAR }; @@ -571,6 +573,7 @@ public: const_code = NewString(""); methods = NewString(""); + methods_proxydocs = NewString(""); Swig_banner(f_begin); @@ -701,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); } { @@ -800,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"); @@ -824,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", + " int found = 0;\n", + " while (SwigMethods_proxydocs[offset].ml_meth != NULL) {\n", + " if (strcmp(SwigMethods_proxydocs[offset].ml_name, name) == 0) {\n", + " found = 1;\n", + " break;\n", + " }\n", + " offset++;\n", + " }\n", + " /* Use the copy with the modified docstring if available */\n", + " if (found == 1) {\n", + " return &SwigMethods_proxydocs[offset];\n", + " } else {\n", + " return NULL;\n", + " }\n", + "}\n", NIL); if (builtin) { Dump(f_builtins, f_wrappers); @@ -925,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() * @@ -1477,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); @@ -1489,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"); @@ -1517,6 +1571,11 @@ public: // depends on the comment which is not going to change, so we can // safely cache it. Setattr(n, "python:docstring", Copy(docstr)); + } else { + // Must copy here since if the docstring is multi-line, the String* + // here will get Deleted below, which is bad if it is a pointer to + // the cached object! + docstr = Copy(docstr); } } } @@ -1552,8 +1611,8 @@ public: * set then it will build a combined docstring. * ------------------------------------------------------------ */ - String *docstring(Node *n, autodoc_t ad_type, const String *indent) { - String *docstr = build_combined_docstring(n, ad_type, indent); + String *docstring(Node *n, autodoc_t ad_type, const String *indent, bool low_level = false) { + String *docstr = build_combined_docstring(n, ad_type, indent, low_level); if (!Len(docstr)) return docstr; @@ -1582,9 +1641,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\""); @@ -1598,14 +1657,14 @@ public: * * Inputs: * plist - entire parameter list - * arg_offset - argument number for first parameter + * arg_num - the number to start from when naming arguments * Side effects: * The "lname" attribute in each parameter in plist will be contain a parameter name * ----------------------------------------------------------------------------- */ - void addMissingParameterNames(Node *n, ParmList *plist, int arg_offset) { + void addMissingParameterNames(Node *n, ParmList *plist, int arg_num) { Parm *p = plist; - int i = arg_offset; + int i = arg_num; while (p) { if (!Getattr(p, "lname")) { String *name = makeParameterName(n, p, i); @@ -1622,10 +1681,11 @@ public: * * Generate the documentation for the function parameters * Parameters: + * arg_num: The number to start assigning unnamed arguments from * func_annotation: Function annotation support * ------------------------------------------------------------ */ - String *make_autodocParmList(Node *n, bool showTypes, bool calling = false, bool func_annotation = false) { + String *make_autodocParmList(Node *n, bool showTypes, int arg_num = 1, bool calling = false, bool func_annotation = false) { String *doc = NewString(""); String *pdocs = 0; @@ -1633,15 +1693,6 @@ public: Parm *p; Parm *pnext; - - // Normally we start counting auto-generated argument names from 1, but we should do it from 2 - // if the first argument is "self", i.e. if we're handling a non-static member function. - int arg_num = 1; - if (is_wrapping_class()) { - if (Cmp(Getattr(n, "storage"), "static") != 0) - arg_num++; - } - if (calling) func_annotation = false; @@ -1654,8 +1705,7 @@ public: return doc; } - for (p = plist; p; p = pnext, arg_num++) { - + for (p = plist; p; p = pnext) { String *tm = Getattr(p, "tmap:in"); if (tm) { pnext = Getattr(p, "tmap:in:next"); @@ -1676,12 +1726,21 @@ public: value = Getattr(p, "tmap:doc:value"); } + // Skip the "self" argument - it is added to the parameter list automatically + // and shouldn't be included in the Parameters block + if (Getattr(p, "self")) { + continue; + } + // Note: the generated name should be consistent with that in kwnames[] String *made_name = 0; if (!name) { name = made_name = makeParameterName(n, p, arg_num); } + // Increment the argument number once we are sure this is a real argument to count + arg_num++; + type = type ? type : Getattr(p, "type"); value = value ? value : Getattr(p, "value"); @@ -1749,8 +1808,9 @@ 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; + bool first_func = true; // If the function is overloaded then this function is called // for the last one. Rewind to the first so the docstrings are // in order. @@ -1787,10 +1847,24 @@ public: } if (!skipAuto) { - String *symname = Getattr(n, "sym:name"); + /* 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; + // If the function has default arguments, then that documentation covers this version too + if (Getattr(n, "defaultargs") != NULL) { + n = Getattr(n, "sym:nextSibling"); + continue; + } + + if (! first_func) + Append(doc, "\n"); + if (type) { if (Strcmp(type, "void") == 0) { type_str = NULL; @@ -1800,6 +1874,21 @@ 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; + } + } + /* Treat destructors as methods for documentation purposes */ + String *nodeType = Getattr(n, "nodeType"); + if (nodeType && Strcmp(nodeType, "destructor") == 0) { + if (ad_type == AUTODOC_FUNC) { + ad_type = AUTODOC_METHOD; + } + } + switch (ad_type) { case AUTODOC_CLASS: { @@ -1823,7 +1912,7 @@ public: break; case AUTODOC_CTOR: if (Strcmp(class_name, symname) == 0) { - String *paramList = make_autodocParmList(n, showTypes); + String *paramList = make_autodocParmList(n, showTypes, 2); Printf(doc, "__init__("); if (showTypes) Printf(doc, "%s ", class_name); @@ -1856,7 +1945,7 @@ public: case AUTODOC_METHOD: { - String *paramList = make_autodocParmList(n, showTypes); + String *paramList = make_autodocParmList(n, showTypes, 2); Printf(doc, "%s(", symname); if (showTypes) Printf(doc, "%s ", class_name); @@ -1873,19 +1962,36 @@ public: // There is no autodoc support for constants currently, this enum // element only exists to allow calling docstring() with it. return NULL; + case AUTODOC_VAR: + // Variables can also be documented (e.g. through the property() function in python) + Printf(doc, "%s", symname); + if (showTypes) { + String *type = Getattr(n, "tmap:doc:type"); + if (! type) + type = Getattr(n, "membervariableHandler:type"); + if (! type) + type = Getattr(n, "type"); + Printf(doc, " : %s", type); + } + break; } Delete(type_str); - } - if (extended) { - 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 n = Getattr(n, "sym:nextSibling"); if (n) - Append(doc, "\n"); + first_func = false; } return doc; @@ -2132,7 +2238,7 @@ public: * Generate parameter list for Python functions or methods, * reuse make_autodocParmList() to do so. * ------------------------------------------------------------ */ - String *make_pyParmList(Node *n, bool in_class, bool is_calling, int kw) { + String *make_pyParmList(Node *n, bool in_class, bool is_calling, int kw, bool has_self_for_count = false) { /* Get the original function for a defaultargs copy, * see default_arguments() in parser.y. */ Node *nn = Getattr(n, "defaultargs"); @@ -2165,7 +2271,7 @@ public: bool funcanno = py3 ? true : false; String *params = NewString(""); - String *_params = make_autodocParmList(n, false, is_calling, funcanno); + String *_params = make_autodocParmList(n, false, ((in_class || has_self_for_count)? 2 : 1), is_calling, funcanno); if (in_class) { Printf(params, "self"); @@ -2244,7 +2350,7 @@ public: * ------------------------------------------------------------ */ bool have_addtofunc(Node *n) { - return have_pythonappend(n) || have_pythonprepend(n) || have_docstring(n); + return have_pythonappend(n) || have_pythonprepend(n); } @@ -2302,7 +2408,7 @@ public: /* Make a wrapper function to insert the code into */ Printv(f_dest, "\n", "def ", name, "(", parms, ")", returnTypeAnnotation(n), ":\n", NIL); if (have_docstring(n)) - Printv(f_dest, tab4, docstring(n, AUTODOC_FUNC, tab4), "\n", NIL); + Printv(f_dest, tab4, docstring(n, AUTODOC_FUNC, tab4, true), "\n", NIL); if (have_pythonprepend(n)) Printv(f_dest, indent_pythoncode(pythonprepend(n), tab4, Getfile(n), Getline(n), "%pythonprepend or %feature(\"pythonprepend\")"), "\n", NIL); if (have_pythonappend(n)) { @@ -2340,38 +2446,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)) { - String *ds = cdocstring(n, 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"); + } } /* ------------------------------------------------------------ @@ -3187,6 +3317,10 @@ public: Delete(h); } Setattr(h, "getter", "SwigPyObject_get___dict__"); + if (! Getattr(h, "doc")) { + Setattr(n, "doc:high:name", Getattr(n, "name")); + Setattr(h, "doc", cdocstring(n, AUTODOC_VAR)); + } } if (builtin_getter) { @@ -3201,6 +3335,10 @@ public: } Setattr(h, "getter", wrapper_name); Delattr(n, "memberget"); + if (! Getattr(h, "doc")) { + Setattr(n, "doc:high:name", Getattr(n, "name")); + Setattr(h, "doc", cdocstring(n, AUTODOC_VAR)); + } } if (builtin_setter) { String *memname = Getattr(n, "membervariableHandler:sym:name"); @@ -3214,6 +3352,10 @@ public: } Setattr(h, "setter", wrapper_name); Delattr(n, "memberset"); + if (! Getattr(h, "doc")) { + Setattr(n, "doc:high:name", Getattr(n, "name")); + Setattr(h, "doc", cdocstring(n, AUTODOC_VAR)); + } } if (in_class && builtin) { @@ -3888,9 +4030,15 @@ public: const char *setter_closure = setter ? funpack ? "SwigPyBuiltin_FunpackSetterClosure" : "SwigPyBuiltin_SetterClosure" : "0"; String *gspair = NewStringf("%s_%s_getset", symname, memname); Printf(f, "static SwigPyGetSet %s = { %s, %s };\n", gspair, getter ? getter : "0", setter ? setter : "0"); + String *doc; + if (Getattr(mgetset, "doc")) { + doc = Getattr(mgetset, "doc"); + } else { + doc = NewStringf("%s.%s", name, memname); + } String *entry = - NewStringf("{ (char *)\"%s\", (getter)%s, (setter)%s, (char *)\"%s.%s\", (void *)&%s }\n", memname, getter_closure, - setter_closure, name, memname, gspair); + NewStringf("{ (char *)\"%s\", (getter)%s, (setter)%s, (char *)\"%s\", (void *)&%s }\n", memname, getter_closure, + setter_closure, doc, gspair); if (GetFlag(mgetset, "static")) { Printf(f, "static PyGetSetDef %s_def = %s;\n", gspair, entry); Printf(f_init, "static_getset = SwigPyStaticVar_new_getset(metatype, &%s_def);\n", gspair); @@ -4559,6 +4707,8 @@ public: if (!have_addtofunc(n)) { if (!fastproxy || olddefs) { Printv(f_shadow, "\n", tab4, "def ", symname, "(", parms, ")", returnTypeAnnotation(n), ":\n", NIL); + if (have_docstring(n)) + Printv(f_shadow, tab8, docstring(n, AUTODOC_METHOD, tab8), "\n", NIL); Printv(f_shadow, tab8, "return ", funcCall(fullname, callParms), "\n", NIL); } } else { @@ -4642,6 +4792,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; @@ -4664,9 +4815,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; } @@ -4743,7 +4895,7 @@ public: String *parms = make_pyParmList(n, true, false, allow_kwargs); /* Pass 'self' only if using director */ - String *callParms = make_pyParmList(n, false, true, allow_kwargs); + String *callParms = make_pyParmList(n, false, true, allow_kwargs, true); if (use_director) { Insert(callParms, 0, "_self, "); @@ -4881,6 +5033,8 @@ public: Printv(f_shadow, tab4, symname, " = property(", module, ".", getname, NIL); if (assignable) Printv(f_shadow, ", ", module, ".", setname, NIL); + if (have_docstring(n)) + Printv(f_shadow, ", doc=", docstring(n, AUTODOC_VAR, tab4), NIL); Printv(f_shadow, ")\n", NIL); Delete(mname); Delete(setname); @@ -4946,6 +5100,8 @@ public: Printv(f_shadow, tab4, symname, " = property(", module, ".", getname, NIL); if (assignable) Printv(f_shadow, ", ", module, ".", setname, NIL); + if (have_docstring(n)) + Printv(f_shadow, ", doc=", docstring(n, AUTODOC_VAR, tab4), NIL); Printv(f_shadow, ")\n", NIL); } String *getter = Getattr(n, "pybuiltin:getter");