chiark / gitweb /
documentation/python: support documenting particular function overloads.
authorVladimír Vondruš <mosra@centrum.cz>
Tue, 27 Aug 2019 21:32:34 +0000 (23:32 +0200)
committerVladimír Vondruš <mosra@centrum.cz>
Wed, 28 Aug 2019 20:34:23 +0000 (22:34 +0200)
doc/plugins/sphinx.rst
documentation/python.py
documentation/test_python/CMakeLists.txt
documentation/test_python/pybind_external_overload_docs/docs.rst [new file with mode: 0644]
documentation/test_python/pybind_external_overload_docs/pybind_external_overload_docs.Class.html [new file with mode: 0644]
documentation/test_python/pybind_external_overload_docs/pybind_external_overload_docs.cpp [new file with mode: 0644]
documentation/test_python/pybind_external_overload_docs/pybind_external_overload_docs.html [new file with mode: 0644]
documentation/test_python/test_pybind.py

index abc6c3737bebd9a47e7b6e53ab236cb4639b87d1..63c9384b18379faaf6ccf8d7691b543f58937803 100644 (file)
@@ -248,7 +248,6 @@ doing the following will ensure it can be easily used:
         PYMEM_DOMAIN_OBJ c:var 1 c-api/memory.html#c.$ -
         ...
 
-
 `Module, class, enum, function, property and data docs`_
 ========================================================
 
@@ -305,6 +304,7 @@ function signature will cause a warning. Example:
 .. code:: rst
 
     .. py:function:: mymodule.MyContainer.add
+        :summary: Add a key/value pair to the container
         :param key:                 Key to add
         :param value:               Corresponding value
         :param overwrite_existing:  Overwrite existing value if already present
@@ -312,5 +312,23 @@ function signature will cause a warning. Example:
         :return:                    The inserted tuple or the existing
             key/value pair in case ``overwrite_existing`` is not set
 
-        Add a key/value pair to the container, optionally overwriting the
-        previous value.
+        The operation has a :math:`\mathcal{O}(\log{}n)` complexity.
+
+For overloaded functions (such as those coming from pybind11), it's possible to
+specify the full signature to distinguish between particular overloads.
+Directives with the full signature have a priority, if no signature matches
+given function, a signature-less directive is searched for as a fallback.
+Example:
+
+.. code:: rst
+
+    .. py:function:: magnum.math.dot(a: magnum.Complex, b: magnum.Complex)
+        :summary: Dot product of two complex numbers
+
+    .. py:function:: magnum.math.dot(a: magnum.Quaternion, b: magnum.Quaternion)
+        :summary: Dot product of two quaternions
+
+    .. py:function:: magnum.math.dot
+        :summary: Dot product
+
+        .. this documentation will be used for all other overloads
index 22d802e1e7e94d245e6afe570ed805711f2e8d2f..42d7f15f997799c5a712ceab09645d26d0ebd14d 100755 (executable)
@@ -797,19 +797,28 @@ def split_summary_content(doc: str) -> str:
     content = content.strip()
     return summary, ('\n'.join(['<p>{}</p>'.format(p) for p in content.split('\n\n')]) if content else None)
 
-def extract_summary(state: State, external_docs, path: List[str], doc: str) -> str:
+def extract_summary(state: State, external_docs, path: List[str], doc: str, *, signature=None) -> str:
     # Prefer external docs, if available
     path_str = '.'.join(path)
-    if path_str in external_docs and external_docs[path_str]['summary']:
-        return render_inline_rst(state, external_docs[path_str]['summary'])
+    if signature and path_str + signature in external_docs:
+        external_doc_entry = external_docs[path_str + signature]
+    elif path_str in external_docs:
+        external_doc_entry = external_docs[path_str]
+    else:
+        external_doc_entry = None
+    if external_doc_entry and external_doc_entry['summary']:
+        return render_inline_rst(state, external_doc_entry['summary'])
 
     # some modules (xml.etree) have None as a docstring :(
     # TODO: render as rst (config option for that)
     return html.escape(inspect.cleandoc(doc or '').partition('\n\n')[0])
 
-def extract_docs(state: State, external_docs, path: List[str], doc: str) -> Tuple[str, str]:
+def extract_docs(state: State, external_docs, path: List[str], doc: str, *, signature=None) -> Tuple[str, str]:
     path_str = '.'.join(path)
-    if path_str in external_docs:
+    # If function signature is supplied, try that first
+    if signature and path_str + signature in external_docs:
+        external_doc_entry = external_docs[path_str + signature]
+    elif path_str in external_docs:
         external_doc_entry = external_docs[path_str]
     else:
         external_doc_entry = None
@@ -1065,6 +1074,8 @@ def extract_function_doc(state: State, parent, entry: Empty) -> List[Any]:
     if not state.config['SEARCH_DISABLED']:
         page_url = state.name_map['.'.join(entry.path[:-1])].url
 
+    overloads = []
+
     # Extract the signature from the docstring for pybind11, since it can't
     # expose it to the metadata: https://github.com/pybind/pybind11/issues/990
     # What's not solvable with metadata, however, are function overloads ---
@@ -1084,8 +1095,7 @@ def extract_function_doc(state: State, parent, entry: Empty) -> List[Any]:
             out.name = entry.path[-1]
             out.params = []
             out.has_complex_params = False
-            out.summary, out.content = extract_docs(state, state.function_docs, entry.path, summary)
-            out.has_details = bool(out.content)
+            out.has_details = False
             out.type, out.type_link = type, type_link
 
             # There's no other way to check staticmethods than to check for
@@ -1130,6 +1140,7 @@ def extract_function_doc(state: State, parent, entry: Empty) -> List[Any]:
                         break
 
             arg_types = []
+            signature = []
             for i, arg in enumerate(args):
                 name, type, type_link, default = arg
                 param = Empty()
@@ -1138,9 +1149,11 @@ def extract_function_doc(state: State, parent, entry: Empty) -> List[Any]:
                 if i == 0 and name == 'self':
                     param.type, param.type_link = None, None
                     arg_types += [None]
+                    signature += ['self']
                 else:
                     param.type, param.type_link = type, type_link
                     arg_types += [type]
+                    signature += ['{}: {}'.format(name, type)]
                 if default:
                     # If the type is a registered enum, try to make a link to
                     # the value -- for an enum of type `module.EnumType`,
@@ -1171,26 +1184,13 @@ def extract_function_doc(state: State, parent, entry: Empty) -> List[Any]:
             # thus name alone is not enough.
             out.id = state.config['ID_FORMATTER'](EntryType.OVERLOADED_FUNCTION, entry.path[-1:] + arg_types)
 
-            if not state.config['SEARCH_DISABLED']:
-                result = Empty()
-                result.flags = ResultFlag.from_type(ResultFlag.NONE, EntryType.FUNCTION)
-                result.url = '{}#{}'.format(page_url, out.id)
-                result.prefix = entry.path[:-1]
-                result.name = entry.path[-1]
-                result.params = []
-                # If there's more than one overload, add the params as well to
-                # disambiguate
-                if len(funcs) != 1:
-                    for i in range(len(out.params)):
-                        param = out.params[i]
-                        result.params += ['{}: {}'.format(param.name, make_relative_name(state, entry.path, arg_types[i])) if arg_types[i] else param.name]
-                state.search += [result]
+            # Get summary and details. Passing the signature as well, so
+            # different overloads can (but don't need to) have different docs.
+            out.summary, out.content = extract_docs(state, state.function_docs, entry.path, summary, signature='({})'.format(', '.join(signature)))
+            if out.content: out.has_details = True
 
             overloads += [out]
 
-        # TODO: assign docs and particular param docs to overloads
-        return overloads
-
     # Sane introspection path for non-pybind11 code
     else:
         out = Empty()
@@ -1236,43 +1236,57 @@ def extract_function_doc(state: State, parent, entry: Empty) -> List[Any]:
                 param.kind = str(i.kind)
                 out.params += [param]
 
-            # Get docs for each param and for the return value
-            path_str = '.'.join(entry.path)
-            if path_str in state.function_docs:
-                # Having no parameters documented is okay, having self
-                # undocumented as well. But having the rest documented only
-                # partially isn't okay.
-                if state.function_docs[path_str]['params']:
-                    param_docs = state.function_docs[path_str]['params']
-                    used_params = set()
-                    for param in out.params:
-                        if param.name not in param_docs:
-                            if param.name != 'self':
-                                logging.warning("%s() parameter %s is not documented", path_str, param.name)
-                            continue
-                        param.content = render_inline_rst(state, param_docs[param.name])
-                        used_params.add(param.name)
-                        out.has_param_details = True
-                        out.has_details = True
-                    # Having unused param docs isn't okay either
-                    for name, _ in param_docs.items():
-                        if name not in used_params:
-                            logging.warning("%s() documents parameter %s, which isn't in the signature", path_str, name)
-
-                if state.function_docs[path_str]['return']:
-                    out.return_value = render_inline_rst(state, state.function_docs[path_str]['return'])
-                    out.has_details = True
-
         # In CPython, some builtin functions (such as math.log) do not provide
         # metadata about their arguments. Source:
         # https://docs.python.org/3/library/inspect.html#inspect.signature
         except ValueError:
             param = Empty()
             param.name = '...'
-            param.name_type = param.name
+            param.type, param.type_link = None, None
             out.params = [param]
             out.type, out.type_link = None, None
 
+        overloads = [out]
+
+    # Common path for parameter / return value docs and search
+    path_str = '.'.join(entry.path)
+    for out in overloads:
+        signature = '({})'.format(', '.join(['{}: {}'.format(param.name, param.type) if param.type else param.name for param in out.params]))
+
+        # Get docs for each param and for the return value. Try this
+        # particular overload first, if not found then fall back to generic
+        # docs for all overloads.
+        if path_str + signature in state.function_docs:
+            function_docs = state.function_docs[path_str + signature]
+        elif path_str in state.function_docs:
+            function_docs = state.function_docs[path_str]
+        else:
+            function_docs = None
+        if function_docs:
+            # Having no parameters documented is okay, having self
+            # undocumented as well. But having the rest documented only
+            # partially isn't okay.
+            if function_docs['params']:
+                param_docs = function_docs['params']
+                used_params = set()
+                for param in out.params:
+                    if param.name not in param_docs:
+                        if param.name != 'self':
+                            logging.warning("%s%s parameter %s is not documented", path_str, signature, param.name)
+                        continue
+                    param.content = render_inline_rst(state, param_docs[param.name])
+                    used_params.add(param.name)
+                    out.has_param_details = True
+                    out.has_details = True
+                # Having unused param docs isn't okay either
+                for name, _ in param_docs.items():
+                    if name not in used_params:
+                        logging.warning("%s%s documents parameter %s, which isn't in the signature", path_str, signature, name)
+
+            if function_docs['return']:
+                out.return_value = render_inline_rst(state, function_docs['return'])
+                out.has_details = True
+
         if not state.config['SEARCH_DISABLED']:
             result = Empty()
             result.flags = ResultFlag.from_type(ResultFlag.NONE, EntryType.FUNCTION)
@@ -1280,9 +1294,15 @@ def extract_function_doc(state: State, parent, entry: Empty) -> List[Any]:
             result.prefix = entry.path[:-1]
             result.name = entry.path[-1]
             result.params = []
+            # If the function has multiple overloads (e.g. pybind functions),
+            # add arguments to each to distinguish between them
+            if len(overloads) != 1:
+                for i in range(len(out.params)):
+                    param = out.params[i]
+                    result.params += ['{}: {}'.format(param.name, make_relative_name(state, entry.path, param.type)) if param.type else param.name]
             state.search += [result]
 
-        return [out]
+    return overloads
 
 def extract_property_doc(state: State, parent, entry: Empty):
     assert inspect.isdatadescriptor(entry.object)
index 298c40f8dbd679b3ff70dcebc7646bd6f7bbdc24..98424d475e65356c11a0d672ef82664e3fdd5680 100644 (file)
@@ -27,7 +27,7 @@ project(McssDocumentationPybindTests)
 
 find_package(pybind11 CONFIG REQUIRED)
 
-foreach(target pybind_signatures pybind_enums pybind_submodules pybind_type_links search_long_suffix_length)
+foreach(target pybind_signatures pybind_enums pybind_external_overload_docs pybind_submodules pybind_type_links search_long_suffix_length)
     pybind11_add_module(${target} ${target}/${target}.cpp)
     set_target_properties(${target} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${target})
 endforeach()
diff --git a/documentation/test_python/pybind_external_overload_docs/docs.rst b/documentation/test_python/pybind_external_overload_docs/docs.rst
new file mode 100644 (file)
index 0000000..242c1c2
--- /dev/null
@@ -0,0 +1,38 @@
+.. py:function:: pybind_external_overload_docs.foo(a: int, b: typing.Tuple[int, str])
+    :param a: First parameter
+    :param b: Second parameter
+
+    Details for the first overload.
+
+.. py:function:: pybind_external_overload_docs.foo(arg0: typing.Callable[[float, typing.List[float]], int])
+    :param arg0: The caller
+
+    Complex signatures in the second overload should be matched properly, too.
+
+.. py:function:: pybind_external_overload_docs.foo
+    :param name: Ha!
+
+    This is a generic documentation and will be caught only by the third
+    overload. Luckily we just document that exact parameter.
+
+.. py:function:: pybind_external_overload_docs.foo(param: int)
+    :param param: This has a default value of 4 but that shouldn't be part of
+        the signature.
+
+    Fourth overload has a default value.
+
+.. py:function:: pybind_external_overload_docs.Class.foo(self, index: int)
+    :return: Nothing at all.
+
+    Class methods don't have type annotation on self.
+
+.. py:function:: pybind_external_overload_docs.Class.foo
+
+    And the fallback matching works there, too.
+
+.. py:function:: pybind_external_overload_docs.foo(first: int)
+    :param second: But the second argument doesn't exist?!
+
+.. py:function:: pybind_external_overload_docs.foo(terrible: wow)
+
+    This docs can't match any overload and thus get unused.
diff --git a/documentation/test_python/pybind_external_overload_docs/pybind_external_overload_docs.Class.html b/documentation/test_python/pybind_external_overload_docs/pybind_external_overload_docs.Class.html
new file mode 100644 (file)
index 0000000..548939e
--- /dev/null
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>pybind_external_overload_docs.Class | My Python Project</title>
+  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+  <link rel="stylesheet" href="m-dark+documentation.compiled.css" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+  <div class="m-container">
+    <div class="m-row">
+      <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Python Project</a>
+    </div>
+  </div>
+</nav></header>
+<main><article>
+  <div class="m-container m-container-inflatable">
+    <div class="m-row">
+      <div class="m-col-l-10 m-push-l-1">
+        <h1>
+          <span class="m-breadcrumb"><a href="pybind_external_overload_docs.html">pybind_external_overload_docs</a>.<wbr/></span>Class <span class="m-thin">class</span>
+        </h1>
+        <p>My fun class!</p>
+        <div class="m-block m-default">
+          <h3>Contents</h3>
+          <ul>
+            <li>
+              Reference
+              <ul>
+                <li><a href="#methods">Methods</a></li>
+              </ul>
+            </li>
+          </ul>
+        </div>
+        <section id="methods">
+          <h2><a href="#methods">Methods</a></h2>
+          <dl class="m-doc">
+            <dt>
+              <span class="m-doc-wrap-bumper">def <a href="#foo-745a3" class="m-doc">foo</a>(</span><span class="m-doc-wrap">self,
+              index: int) -&gt; None</span>
+            </dt>
+            <dd>First overload</dd>
+            <dt>
+              <span class="m-doc-wrap-bumper">def <a href="#foo-6315e" class="m-doc">foo</a>(</span><span class="m-doc-wrap">self,
+              name: str) -&gt; None</span>
+            </dt>
+            <dd>Second overload</dd>
+          </dl>
+        </section>
+        <section>
+          <h2>Method documentation</h2>
+          <section class="m-doc-details" id="foo-745a3"><div>
+            <h3>
+              <span class="m-doc-wrap-bumper">def pybind_external_overload_docs.<wbr />Class.<wbr /></span><span class="m-doc-wrap"><span class="m-doc-wrap-bumper"><a href="#foo-745a3" class="m-doc-self">foo</a>(</span><span class="m-doc-wrap">self,
+              index: int) -&gt; None</span></span>
+            </h3>
+            <p>First overload</p>
+            <table class="m-table m-fullwidth m-flat">
+              <tfoot>
+                <tr>
+                  <th style="width: 1%">Returns</th>
+                  <td>Nothing at all.</td>
+                </tr>
+              </tfoot>
+            </table>
+<p>Class methods don't have type annotation on self.</p>
+          </div></section>
+          <section class="m-doc-details" id="foo-6315e"><div>
+            <h3>
+              <span class="m-doc-wrap-bumper">def pybind_external_overload_docs.<wbr />Class.<wbr /></span><span class="m-doc-wrap"><span class="m-doc-wrap-bumper"><a href="#foo-6315e" class="m-doc-self">foo</a>(</span><span class="m-doc-wrap">self,
+              name: str) -&gt; None</span></span>
+            </h3>
+            <p>Second overload</p>
+<p>And the fallback matching works there, too.</p>
+          </div></section>
+        </section>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
diff --git a/documentation/test_python/pybind_external_overload_docs/pybind_external_overload_docs.cpp b/documentation/test_python/pybind_external_overload_docs/pybind_external_overload_docs.cpp
new file mode 100644 (file)
index 0000000..e5e8260
--- /dev/null
@@ -0,0 +1,31 @@
+#include <functional>
+#include <pybind11/pybind11.h>
+#include <pybind11/stl.h> /* needed for std::vector! */
+#include <pybind11/functional.h> /* for std::function */
+
+namespace py = pybind11;
+
+void foo1(int, const std::tuple<int, std::string>&) {}
+void foo2(std::function<int(float, std::vector<float>&)>) {}
+void foo3(std::string) {}
+void foo4(int) {}
+
+struct Class {
+    void foo1(int) {}
+    void foo2(std::string) {}
+};
+
+PYBIND11_MODULE(pybind_external_overload_docs, m) {
+    m.doc() = "pybind11 external overload docs";
+
+    m
+        .def("foo", &foo1, "First overload", py::arg("a"), py::arg("b"))
+        .def("foo", &foo2, "Second overload")
+        .def("foo", &foo3, "Third overload", py::arg("name"))
+        .def("foo", &foo4, "Fourth overload", py::arg("param") = 4)
+        .def("foo", &foo4, "This will produce param documentation mismatch warnings", py::arg("first"));
+
+    py::class_<Class>(m, "Class", "My fun class!")
+        .def("foo", &Class::foo1, "First overload", py::arg("index"))
+        .def("foo", &Class::foo2, "Second overload", py::arg("name"));
+}
diff --git a/documentation/test_python/pybind_external_overload_docs/pybind_external_overload_docs.html b/documentation/test_python/pybind_external_overload_docs/pybind_external_overload_docs.html
new file mode 100644 (file)
index 0000000..2768ba3
--- /dev/null
@@ -0,0 +1,158 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>pybind_external_overload_docs | My Python Project</title>
+  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+  <link rel="stylesheet" href="m-dark+documentation.compiled.css" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+  <div class="m-container">
+    <div class="m-row">
+      <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Python Project</a>
+    </div>
+  </div>
+</nav></header>
+<main><article>
+  <div class="m-container m-container-inflatable">
+    <div class="m-row">
+      <div class="m-col-l-10 m-push-l-1">
+        <h1>
+          pybind_external_overload_docs <span class="m-thin">module</span>
+        </h1>
+        <p>pybind11 external overload docs</p>
+        <div class="m-block m-default">
+          <h3>Contents</h3>
+          <ul>
+            <li>
+              Reference
+              <ul>
+                <li><a href="#classes">Classes</a></li>
+                <li><a href="#functions">Functions</a></li>
+              </ul>
+            </li>
+          </ul>
+        </div>
+        <section id="classes">
+          <h2><a href="#classes">Classes</a></h2>
+          <dl class="m-doc">
+            <dt>class <a href="pybind_external_overload_docs.Class.html" class="m-doc">Class</a></dt>
+            <dd>My fun class!</dd>
+          </dl>
+        </section>
+        <section id="functions">
+          <h2><a href="#functions">Functions</a></h2>
+          <dl class="m-doc">
+            <dt>
+              <span class="m-doc-wrap-bumper">def <a href="#foo-0a6d7" class="m-doc">foo</a>(</span><span class="m-doc-wrap">a: int,
+              b: typing.Tuple[int, str]) -&gt; None</span>
+            </dt>
+            <dd>First overload</dd>
+            <dt>
+              <span class="m-doc-wrap-bumper">def <a href="#foo-515df" class="m-doc">foo</a>(</span><span class="m-doc-wrap">arg0: typing.Callable[[float, typing.List[float]], int]<span class="m-text m-dim">, /</span>) -&gt; None</span>
+            </dt>
+            <dd>Second overload</dd>
+            <dt>
+              <span class="m-doc-wrap-bumper">def <a href="#foo-34424" class="m-doc">foo</a>(</span><span class="m-doc-wrap">name: str) -&gt; None</span>
+            </dt>
+            <dd>Third overload</dd>
+            <dt>
+              <span class="m-doc-wrap-bumper">def <a href="#foo-46f8a" class="m-doc">foo</a>(</span><span class="m-doc-wrap">param: int = 4) -&gt; None</span>
+            </dt>
+            <dd>Fourth overload</dd>
+            <dt id="foo-46f8a">
+              <span class="m-doc-wrap-bumper">def <a href="#foo-46f8a" class="m-doc-self">foo</a>(</span><span class="m-doc-wrap">first: int) -&gt; None</span>
+            </dt>
+            <dd>This will produce param documentation mismatch warnings</dd>
+          </dl>
+        </section>
+        <section>
+          <h2>Function documentation</h2>
+          <section class="m-doc-details" id="foo-0a6d7"><div>
+            <h3>
+              <span class="m-doc-wrap-bumper">def pybind_external_overload_docs.<wbr /></span><span class="m-doc-wrap"><span class="m-doc-wrap-bumper"><a href="#foo-0a6d7" class="m-doc-self">foo</a>(</span><span class="m-doc-wrap">a: int,
+              b: typing.Tuple[int, str]) -&gt; None</span></span>
+            </h3>
+            <p>First overload</p>
+            <table class="m-table m-fullwidth m-flat">
+              <thead>
+                <tr><th colspan="2">Parameters</th></tr>
+              </thead>
+              <tbody>
+                <tr>
+                  <td style="width: 1%">a</td>
+                  <td>First parameter</td>
+                </tr>
+                <tr>
+                  <td>b</td>
+                  <td>Second parameter</td>
+                </tr>
+              </tbody>
+            </table>
+<p>Details for the first overload.</p>
+          </div></section>
+          <section class="m-doc-details" id="foo-515df"><div>
+            <h3>
+              <span class="m-doc-wrap-bumper">def pybind_external_overload_docs.<wbr /></span><span class="m-doc-wrap"><span class="m-doc-wrap-bumper"><a href="#foo-515df" class="m-doc-self">foo</a>(</span><span class="m-doc-wrap">arg0: typing.Callable[[float, typing.List[float]], int]<span class="m-text m-dim">, /</span>) -&gt; None</span></span>
+            </h3>
+            <p>Second overload</p>
+            <table class="m-table m-fullwidth m-flat">
+              <thead>
+                <tr><th colspan="2">Parameters</th></tr>
+              </thead>
+              <tbody>
+                <tr>
+                  <td style="width: 1%">arg0</td>
+                  <td>The caller</td>
+                </tr>
+              </tbody>
+            </table>
+<p>Complex signatures in the second overload should be matched properly, too.</p>
+          </div></section>
+          <section class="m-doc-details" id="foo-34424"><div>
+            <h3>
+              <span class="m-doc-wrap-bumper">def pybind_external_overload_docs.<wbr /></span><span class="m-doc-wrap"><span class="m-doc-wrap-bumper"><a href="#foo-34424" class="m-doc-self">foo</a>(</span><span class="m-doc-wrap">name: str) -&gt; None</span></span>
+            </h3>
+            <p>Third overload</p>
+            <table class="m-table m-fullwidth m-flat">
+              <thead>
+                <tr><th colspan="2">Parameters</th></tr>
+              </thead>
+              <tbody>
+                <tr>
+                  <td style="width: 1%">name</td>
+                  <td>Ha!</td>
+                </tr>
+              </tbody>
+            </table>
+<p>This is a generic documentation and will be caught only by the third
+overload. Luckily we just document that exact parameter.</p>
+          </div></section>
+          <section class="m-doc-details" id="foo-46f8a"><div>
+            <h3>
+              <span class="m-doc-wrap-bumper">def pybind_external_overload_docs.<wbr /></span><span class="m-doc-wrap"><span class="m-doc-wrap-bumper"><a href="#foo-46f8a" class="m-doc-self">foo</a>(</span><span class="m-doc-wrap">param: int = 4) -&gt; None</span></span>
+            </h3>
+            <p>Fourth overload</p>
+            <table class="m-table m-fullwidth m-flat">
+              <thead>
+                <tr><th colspan="2">Parameters</th></tr>
+              </thead>
+              <tbody>
+                <tr>
+                  <td style="width: 1%">param</td>
+                  <td>This has a default value of 4 but that shouldn't be part of
+the signature.</td>
+                </tr>
+              </tbody>
+            </table>
+<p>Fourth overload has a default value.</p>
+          </div></section>
+        </section>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
index 6ce2535973b2a397a900185d9fd5425641b9b181..b0aa1110ee88c215604a3dee3d213080ccf959d1 100644 (file)
@@ -276,3 +276,13 @@ class TypeLinks(BaseInspectTestCase):
         })
         self.assertEqual(*self.actual_expected_contents('pybind_type_links.html'))
         self.assertEqual(*self.actual_expected_contents('pybind_type_links.Foo.html'))
+
+class ExternalOverloadDocs(BaseInspectTestCase):
+    def test(self):
+        self.run_python({
+            'PLUGINS': ['m.sphinx'],
+            'INPUT_DOCS': ['docs.rst'],
+            'PYBIND11_COMPATIBILITY': True
+        })
+        self.assertEqual(*self.actual_expected_contents('pybind_external_overload_docs.html'))
+        self.assertEqual(*self.actual_expected_contents('pybind_external_overload_docs.Class.html'))