chiark / gitweb /
documentation/python: implement linking for types.
authorVladimír Vondruš <mosra@centrum.cz>
Fri, 12 Jul 2019 10:36:09 +0000 (12:36 +0200)
committerVladimír Vondruš <mosra@centrum.cz>
Sun, 14 Jul 2019 17:11:08 +0000 (19:11 +0200)
28 files changed:
documentation/python.py
documentation/test_python/CMakeLists.txt
documentation/test_python/inspect_annotations/inspect_annotations.Foo.html
documentation/test_python/inspect_annotations/inspect_annotations.html
documentation/test_python/inspect_name_mapping/inspect_name_mapping.Class.html
documentation/test_python/inspect_name_mapping/inspect_name_mapping.html
documentation/test_python/inspect_name_mapping/inspect_name_mapping.submodule.html
documentation/test_python/inspect_type_links/inspect_type_links.first.Foo.Foo.html [new file with mode: 0644]
documentation/test_python/inspect_type_links/inspect_type_links.first.Foo.html [new file with mode: 0644]
documentation/test_python/inspect_type_links/inspect_type_links.first.html [new file with mode: 0644]
documentation/test_python/inspect_type_links/inspect_type_links.first.sub.Foo.html [new file with mode: 0644]
documentation/test_python/inspect_type_links/inspect_type_links.first.sub.html [new file with mode: 0644]
documentation/test_python/inspect_type_links/inspect_type_links.second.Foo.html [new file with mode: 0644]
documentation/test_python/inspect_type_links/inspect_type_links.second.html [new file with mode: 0644]
documentation/test_python/inspect_type_links/inspect_type_links/__init__.py [new file with mode: 0644]
documentation/test_python/inspect_type_links/inspect_type_links/first/__init__.py [new file with mode: 0644]
documentation/test_python/inspect_type_links/inspect_type_links/first/sub.py [new file with mode: 0644]
documentation/test_python/inspect_type_links/inspect_type_links/second.py [new file with mode: 0644]
documentation/test_python/link_formatting/m.link_formatting.pybind.html
documentation/test_python/pybind_name_mapping/pybind_name_mapping.Class.html
documentation/test_python/pybind_name_mapping/pybind_name_mapping.html
documentation/test_python/pybind_name_mapping/pybind_name_mapping.submodule.html
documentation/test_python/pybind_signatures/pybind_signatures.MyClass.html
documentation/test_python/pybind_type_links/pybind_type_links.Foo.html [new file with mode: 0644]
documentation/test_python/pybind_type_links/pybind_type_links.cpp [new file with mode: 0644]
documentation/test_python/pybind_type_links/pybind_type_links.html [new file with mode: 0644]
documentation/test_python/test_inspect.py
documentation/test_python/test_pybind.py

index 2b79f402b1d218c96064e13186f6935d3e5b0e4e..f4382583f2f945ad34f6c3024b13029017bd5fd0 100755 (executable)
@@ -460,34 +460,95 @@ def crawl_module(state: State, path: List[str], module) -> List[Tuple[List[str],
 
     return submodules_to_crawl
 
+def make_type_link(state: State, referrer_path: List[str], type) -> str:
+    if type is None: return None
+    assert isinstance(type, str)
+
+    # Not found, return as-is
+    if not type in state.name_map: return type
+
+    entry = state.name_map[type]
+
+    # Strip common prefix from both paths. We always want to keep at least one
+    # element from the entry path, so strip the last element off.
+    common_prefix_length = len(os.path.commonprefix([referrer_path, entry.path[:-1]]))
+
+    # Check for ambiguity of the shortened path -- for example, with referrer
+    # being `module.sub.Foo`, target `module.Foo`, the path will get shortened
+    # to `Foo`, making it seem like the target is `module.sub.Foo` instead of
+    # `module.Foo`. To fix that, the shortened path needs to be `sub.Foo`
+    # instead of `Foo`.
+    def is_ambiguous(shortened_path):
+        # Concatenate the shortened path with a prefix of the referrer path,
+        # going from longest to shortest, until we find a name that exists. If
+        # the first found name is the actual target, it's not ambiguous --
+        # for example, linking from `module.sub` to `module.sub.Foo` can be
+        # done just with `Foo` even though `module.Foo` exists as well, as it's
+        # "closer" to the referrer.
+        # TODO: See test cases in `inspect_type_links.first.Foo` for very
+        #  *very* pathological cases where we're referencing `Foo` from
+        # `module.Foo` and there's also `module.Foo.Foo`. Not sure which way is
+        # better.
+        for i in reversed(range(len(referrer_path))):
+            potentially_ambiguous = referrer_path[:i] + shortened_path
+            if '.'.join(potentially_ambiguous) in state.name_map:
+                if potentially_ambiguous == entry.path: return False
+                else: return True
+        # the target *has to be* found
+        assert False # pragma: no cover
+    shortened_path = entry.path[common_prefix_length:]
+    while common_prefix_length and is_ambiguous(shortened_path):
+        common_prefix_length -= 1
+        shortened_path = entry.path[common_prefix_length:]
+
+    # Format the URL
+    if entry.type == EntryType.CLASS:
+        url = state.config['URL_FORMATTER'](entry.type, entry.path)[1]
+    else:
+        assert entry.type == EntryType.ENUM
+        parent_entry = state.name_map['.'.join(entry.path[:-1])]
+        url = '{}#{}'.format(
+            state.config['URL_FORMATTER'](parent_entry.type, parent_entry.path)[1],
+            state.config['ID_FORMATTER'](entry.type, entry.path[-1:]))
+
+    return '<a href="{}" class="m-doc">{}</a>'.format(url, '.'.join(shortened_path))
+
 _pybind_name_rx = re.compile('[a-zA-Z0-9_]*')
 _pybind_arg_name_rx = re.compile('[*a-zA-Z0-9_]+')
 _pybind_type_rx = re.compile('[a-zA-Z0-9_.]+')
 _pybind_default_value_rx = re.compile('[^,)]+')
 
-def parse_pybind_type(state: State, signature: str) -> str:
+def parse_pybind_type(state: State, referrer_path: List[str], signature: str) -> str:
     input_type = _pybind_type_rx.match(signature).group(0)
     signature = signature[len(input_type):]
     type = map_name_prefix(state, input_type)
+    type_link = make_type_link(state, referrer_path, type)
     if signature and signature[0] == '[':
         type += '['
+        type_link += '['
         signature = signature[1:]
         while signature[0] != ']':
-            signature, inner_type = parse_pybind_type(state, signature)
+            signature, inner_type, inner_type_link = parse_pybind_type(state, referrer_path, signature)
             type += inner_type
+            type_link += inner_type_link
 
             if signature[0] == ']': break
             assert signature.startswith(', ')
             signature = signature[2:]
             type += ', '
+            type_link += ', '
 
         assert signature[0] == ']'
         signature = signature[1:]
         type += ']'
+        type_link += ']'
 
-    return signature, type
+    return signature, type, type_link
 
-def parse_pybind_signature(state: State, signature: str) -> Tuple[str, str, List[Tuple[str, str, str]], str]:
+# Returns function name, summary, list of arguments (name, type, type with HTML
+# links, default value) and return type. If argument parsing failed, the
+# argument list is a single "ellipsis" item.
+def parse_pybind_signature(state: State, referrer_path: List[str], signature: str) -> Tuple[str, str, List[Tuple[str, str, str, str]], str]:
     original_signature = signature # For error reporting
     name = _pybind_name_rx.match(signature).group(0)
     signature = signature[len(name):]
@@ -505,9 +566,10 @@ def parse_pybind_signature(state: State, signature: str) -> Tuple[str, str, List
         # Type (optional)
         if signature.startswith(': '):
             signature = signature[2:]
-            signature, arg_type = parse_pybind_type(state, signature)
+            signature, arg_type, arg_type_link = parse_pybind_type(state, referrer_path, signature)
         else:
             arg_type = None
+            arg_type_link = None
 
         # Default (optional) -- for now take everything until the next comma
         # TODO: ugh, do properly
@@ -520,7 +582,7 @@ def parse_pybind_signature(state: State, signature: str) -> Tuple[str, str, List
         else:
             default = None
 
-        args += [(arg_name, arg_type, default)]
+        args += [(arg_name, arg_type, arg_type_link, default)]
 
         if signature[0] == ')': break
 
@@ -532,7 +594,7 @@ def parse_pybind_signature(state: State, signature: str) -> Tuple[str, str, List
                 summary = extract_summary(state, {}, [], original_signature[end + 1:])
             else:
                 summary = ''
-            return (name, summary, [('…', None, None)], None)
+            return (name, summary, [('…', None, None, None)], None)
 
         signature = signature[2:]
 
@@ -542,9 +604,9 @@ def parse_pybind_signature(state: State, signature: str) -> Tuple[str, str, List
     # Return type (optional)
     if signature.startswith(' -> '):
         signature = signature[4:]
-        signature, return_type = parse_pybind_type(state, signature)
+        signature, _, return_type_link = parse_pybind_type(state, referrer_path, signature)
     else:
-        return_type = None
+        return_type_link = None
 
     if signature and signature[0] != '\n':
         end = original_signature.find('\n')
@@ -553,16 +615,18 @@ def parse_pybind_signature(state: State, signature: str) -> Tuple[str, str, List
             summary = extract_summary(state, {}, [], original_signature[end + 1:])
         else:
             summary = ''
-        return (name, summary, [('…', None, None)], None)
+        return (name, summary, [('…', None, None, None)], None)
 
     if len(signature) > 1 and signature[1] == '\n':
         summary = extract_summary(state, {}, [], signature[2:])
     else:
         summary = ''
 
-    return (name, summary, args, return_type)
+    return (name, summary, args, return_type_link)
+
+def parse_pybind_docstring(state: State, referrer_path: List[str], doc: str) -> List[Tuple[str, str, List[Tuple[str, str, str]], str]]:
+    name = referrer_path[-1]
 
-def parse_pybind_docstring(state: State, name: str, doc: str) -> List[Tuple[str, str, List[Tuple[str, str, str]], str]]:
     # Multiple overloads, parse each separately
     overload_header = "{}(*args, **kwargs)\nOverloaded function.\n\n".format(name);
     if doc.startswith(overload_header):
@@ -575,7 +639,7 @@ def parse_pybind_docstring(state: State, name: str, doc: str) -> List[Tuple[str,
             next = doc.find('{}. {}('.format(id, name))
 
             # Parse the signature and docs from known slice
-            overloads += [parse_pybind_signature(state, doc[len(str(id - 1)) + 2:next])]
+            overloads += [parse_pybind_signature(state, referrer_path, doc[len(str(id - 1)) + 2:next])]
             assert overloads[-1][0] == name
             if next == -1: break
 
@@ -586,7 +650,7 @@ def parse_pybind_docstring(state: State, name: str, doc: str) -> List[Tuple[str,
 
     # Normal function, parse and return the first signature
     else:
-        return [parse_pybind_signature(state, doc)]
+        return [parse_pybind_signature(state, referrer_path, doc)]
 
 def extract_summary(state: State, external_docs, path: List[str], doc: str) -> str:
     # Prefer external docs, if available
@@ -604,12 +668,12 @@ def extract_type(type) -> str:
     # builtins (i.e., we want re.Match but not builtins.int).
     return (type.__module__ + '.' if type.__module__ != 'builtins' else '') + type.__name__
 
-def extract_annotation(state: State, annotation) -> str:
+def extract_annotation(state: State, referrer_path: List[str], annotation) -> str:
     # TODO: why this is not None directly?
     if annotation is inspect.Signature.empty: return None
 
     # Annotations can be strings, also https://stackoverflow.com/a/33533514
-    if type(annotation) == str: return map_name_prefix(state, annotation)
+    if type(annotation) == str: out = annotation
 
     # To avoid getting <class 'foo.bar'> for types (and getting foo.bar
     # instead) but getting the actual type for types annotated with e.g.
@@ -617,8 +681,11 @@ def extract_annotation(state: State, annotation) -> str:
     # typing module or it's directly a type. In Python 3.7 this worked with
     # inspect.isclass(annotation), but on 3.6 that gives True for annotations
     # as well and then we would get just List instead of List[int].
-    if annotation.__module__ == 'typing': return map_name_prefix(state, str(annotation))
-    return map_name_prefix(state, extract_type(annotation))
+    elif annotation.__module__ == 'typing': out = str(annotation)
+    else: out = extract_type(annotation)
+
+    # Map name prefix, add links to the result
+    return make_type_link(state, referrer_path, map_name_prefix(state, out))
 
 def extract_module_doc(state: State, path: List[str], module):
     assert inspect.ismodule(module)
@@ -656,6 +723,7 @@ def extract_enum_doc(state: State, path: List[str], enum_):
             out.summary = extract_summary(state, {}, [], enum_.__doc__)
 
         out.base = extract_type(enum_.__base__)
+        if out.base: out.base = make_type_link(state, path, out.base)
 
         for i in enum_:
             value = Empty()
@@ -705,7 +773,7 @@ def extract_function_doc(state: State, parent, path: List[str], function) -> Lis
     # one function in Python may equal more than one function on the C++ side.
     # To make the docs usable, list all overloads separately.
     if state.config['PYBIND11_COMPATIBILITY'] and function.__doc__.startswith(path[-1]):
-        funcs = parse_pybind_docstring(state, path[-1], function.__doc__)
+        funcs = parse_pybind_docstring(state, path, function.__doc__)
         overloads = []
         for name, summary, args, type in funcs:
             out = Empty()
@@ -718,6 +786,7 @@ def extract_function_doc(state: State, parent, path: List[str], function) -> Lis
 
             # Don't show None return type for void functions
             out.type = None if type == 'None' else type
+            if out.type: out.type = make_type_link(state, path, out.type)
 
             # There's no other way to check staticmethods than to check for
             # self being the name of first parameter :( No support for
@@ -742,7 +811,7 @@ def extract_function_doc(state: State, parent, path: List[str], function) -> Lis
 
                 positional_only = True
                 for i, arg in enumerate(args[1:]):
-                    name, type, default = arg
+                    name, type, type_link, default = arg
                     if name != 'arg{}'.format(i):
                         positional_only = False
                         break
@@ -752,18 +821,23 @@ def extract_function_doc(state: State, parent, path: List[str], function) -> Lis
             else:
                 positional_only = True
                 for i, arg in enumerate(args):
-                    name, type, default = arg
+                    name, type, type_link, default = arg
                     if name != 'arg{}'.format(i):
                         positional_only = False
                         break
 
+            arg_types = []
             for i, arg in enumerate(args):
-                name, type, default = arg
+                name, type, type_link, default = arg
                 param = Empty()
                 param.name = name
                 # Don't include redundant type for the self argument
-                if name == 'self': param.type = None
-                else: param.type = type
+                if name == 'self':
+                    param.type = None
+                    arg_types += [None]
+                else:
+                    param.type = type_link
+                    arg_types += [type]
                 param.default = html.escape(default or '')
                 if type or default: out.has_complex_params = True
 
@@ -782,7 +856,7 @@ def extract_function_doc(state: State, parent, path: List[str], function) -> Lis
 
             # Format the anchor. Pybind11 functions are sometimes overloaded,
             # thus name alone is not enough.
-            out.id = state.config['ID_FORMATTER'](EntryType.OVERLOADED_FUNCTION, path[-1:] + [param.type for param in out.params])
+            out.id = state.config['ID_FORMATTER'](EntryType.OVERLOADED_FUNCTION, path[-1:] + arg_types)
 
             overloads += [out]
 
@@ -806,11 +880,11 @@ def extract_function_doc(state: State, parent, path: List[str], function) -> Lis
 
         try:
             signature = inspect.signature(function)
-            out.type = extract_annotation(state, signature.return_annotation)
+            out.type = extract_annotation(state, path, signature.return_annotation)
             for i in signature.parameters.values():
                 param = Empty()
                 param.name = i.name
-                param.type = extract_annotation(state, i.annotation)
+                param.type = extract_annotation(state, path, i.annotation)
                 if param.type:
                     out.has_complex_params = True
                 if i.default is inspect.Signature.empty:
@@ -847,11 +921,11 @@ def extract_property_doc(state: State, path: List[str], property):
 
     try:
         signature = inspect.signature(property.fget)
-        out.type = extract_annotation(state, signature.return_annotation)
+        out.type = extract_annotation(state, path, signature.return_annotation)
     except ValueError:
         # pybind11 properties have the type in the docstring
         if state.config['PYBIND11_COMPATIBILITY']:
-            out.type = parse_pybind_signature(state, property.fget.__doc__)[3]
+            out.type = parse_pybind_signature(state, path, property.fget.__doc__)[3]
         else:
             out.type = None
 
@@ -867,7 +941,7 @@ def extract_data_doc(state: State, parent, path: List[str], data):
     out.summary = ''
     out.has_details = False
     if hasattr(parent, '__annotations__') and out.name in parent.__annotations__:
-        out.type = extract_annotation(state, parent.__annotations__[out.name])
+        out.type = extract_annotation(state, path, parent.__annotations__[out.name])
     else:
         out.type = None
     # The autogenerated <foo.bar at 0xbadbeef> is useless, so provide the value
index 9f41e4966f5ac16fd06cac8754dc3fba2f883af8..e3966fdbc318f759ec7614510966695e8c8bc166 100644 (file)
@@ -27,7 +27,7 @@ project(McssDocumentationPybindTests)
 
 find_package(pybind11 CONFIG REQUIRED)
 
-foreach(target signatures enums submodules)
+foreach(target signatures enums submodules type_links)
     pybind11_add_module(pybind_${target} pybind_${target}/pybind_${target}.cpp)
     set_target_properties(pybind_${target} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/pybind_${target})
 endforeach()
index a83a1714064e3eef8f0f0bd561ee92283c8977e9..7add22f86116da95b981e93f42b05ddc54ea4724 100644 (file)
@@ -39,7 +39,7 @@
           <h2><a href="#methods">Methods</a></h2>
           <dl class="m-doc">
             <dt>
-              <span class="m-doc-wrap-bumper">def <a href="#string_annotation" class="m-doc-self" id="string_annotation">string_annotation</a>(</span><span class="m-doc-wrap">self: inspect_annotations.Foo)</span>
+              <span class="m-doc-wrap-bumper">def <a href="#string_annotation" class="m-doc-self" id="string_annotation">string_annotation</a>(</span><span class="m-doc-wrap">self: <a href="inspect_annotations.Foo.html" class="m-doc">Foo</a>)</span>
             </dt>
             <dd>String annotations</dd>
           </dl>
index 31af659e694f293aedcfa4b300c318010c538393..4f62c8eae50b937bac4a4257cf1128759b6511a5 100644 (file)
@@ -54,7 +54,7 @@
             <dt>
               <span class="m-doc-wrap-bumper">def <a href="#annotation" class="m-doc-self" id="annotation">annotation</a>(</span><span class="m-doc-wrap">param: typing.List[int],
               another: bool,
-              third: str = 'hello') -&gt; inspect_annotations.Foo</span>
+              third: str = 'hello') -&gt; <a href="inspect_annotations.Foo.html" class="m-doc">Foo</a></span>
             </dt>
             <dd>An annotated function</dd>
             <dt>
@@ -75,7 +75,7 @@
               <span class="m-doc-wrap-bumper">def <a href="#partial_annotation" class="m-doc-self" id="partial_annotation">partial_annotation</a>(</span><span class="m-doc-wrap">foo,
               param: typing.Tuple[int, int],
               unannotated,
-              cls: inspect_annotations.Foo)</span>
+              cls: <a href="inspect_annotations.Foo.html" class="m-doc">Foo</a>)</span>
             </dt>
             <dd>Partially annotated function</dd>
             <dt>
index 1772ea86c0fd329912b87961afc4a96c1b4a429f..381b567e9c0c5f6c78724cff6b6384951f84c79e 100644 (file)
@@ -38,7 +38,7 @@
           <h2><a href="#methods">Methods</a></h2>
           <dl class="m-doc">
             <dt>
-              <span class="m-doc-wrap-bumper">def <a href="#a_thing" class="m-doc-self" id="a_thing">a_thing</a>(</span><span class="m-doc-wrap">self) -&gt; inspect_name_mapping.Class</span>
+              <span class="m-doc-wrap-bumper">def <a href="#a_thing" class="m-doc-self" id="a_thing">a_thing</a>(</span><span class="m-doc-wrap">self) -&gt; <a href="inspect_name_mapping.Class.html" class="m-doc">Class</a></span>
             </dt>
             <dd>A method</dd>
           </dl>
index 53ff83d659e022d5169cc6bb55017a78a2a6706d..dd07e0337208e189ba07e4f40ec9aa831d926820 100644 (file)
@@ -53,7 +53,7 @@
           <h2><a href="#functions">Functions</a></h2>
           <dl class="m-doc">
             <dt>
-              <span class="m-doc-wrap-bumper">def <a href="#foo" class="m-doc-self" id="foo">foo</a>(</span><span class="m-doc-wrap">) -&gt; inspect_name_mapping.Class</span>
+              <span class="m-doc-wrap-bumper">def <a href="#foo" class="m-doc-self" id="foo">foo</a>(</span><span class="m-doc-wrap">) -&gt; <a href="inspect_name_mapping.Class.html" class="m-doc">Class</a></span>
             </dt>
             <dd>This function returns Class, *not* _sub.Foo</dd>
           </dl>
index 016ba03ef500cef7ff2dc2f763f7186d5acc4e6c..5c0034076856abcbc4d5a7b918d13e624cf2bd1d 100644 (file)
@@ -38,7 +38,7 @@
           <h2><a href="#functions">Functions</a></h2>
           <dl class="m-doc">
             <dt>
-              <span class="m-doc-wrap-bumper">def <a href="#foo" class="m-doc-self" id="foo">foo</a>(</span><span class="m-doc-wrap">a: inspect_name_mapping.Class,
+              <span class="m-doc-wrap-bumper">def <a href="#foo" class="m-doc-self" id="foo">foo</a>(</span><span class="m-doc-wrap">a: <a href="inspect_name_mapping.Class.html" class="m-doc">Class</a>,
               b: int) -&gt; int</span>
             </dt>
             <dd>A function</dd>
diff --git a/documentation/test_python/inspect_type_links/inspect_type_links.first.Foo.Foo.html b/documentation/test_python/inspect_type_links/inspect_type_links.first.Foo.Foo.html
new file mode 100644 (file)
index 0000000..3c7eb46
--- /dev/null
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>inspect_type_links.first.Foo.Foo | 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="inspect_type_links.html">inspect_type_links</a>.<wbr/></span><span class="m-breadcrumb"><a href="inspect_type_links.first.html">first</a>.<wbr/></span><span class="m-breadcrumb"><a href="inspect_type_links.first.Foo.html">Foo</a>.<wbr/></span>Foo <span class="m-thin">class</span>
+        </h1>
+        <p>An inner class in the first module</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="#reference_parent" class="m-doc-self" id="reference_parent">reference_parent</a>(</span><span class="m-doc-wrap">self,
+              a: <a href="inspect_type_links.first.Foo.html" class="m-doc">first.Foo</a>)</span>
+            </dt>
+            <dd>A method referencing its parent wrapper class</dd>
+            <dt>
+              <span class="m-doc-wrap-bumper">def <a href="#reference_self" class="m-doc-self" id="reference_self">reference_self</a>(</span><span class="m-doc-wrap">self,
+              a: <a href="inspect_type_links.first.Foo.Foo.html" class="m-doc">Foo</a>)</span>
+            </dt>
+            <dd>A method referencing its wrapper class</dd>
+          </dl>
+        </section>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
diff --git a/documentation/test_python/inspect_type_links/inspect_type_links.first.Foo.html b/documentation/test_python/inspect_type_links/inspect_type_links.first.Foo.html
new file mode 100644 (file)
index 0000000..1fb3539
--- /dev/null
@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>inspect_type_links.first.Foo | 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="inspect_type_links.html">inspect_type_links</a>.<wbr/></span><span class="m-breadcrumb"><a href="inspect_type_links.first.html">first</a>.<wbr/></span>Foo <span class="m-thin">class</span>
+        </h1>
+        <p>A class in the first module</p>
+        <div class="m-block m-default">
+          <h3>Contents</h3>
+          <ul>
+            <li>
+              Reference
+              <ul>
+                <li><a href="#classes">Classes</a></li>
+                <li><a href="#methods">Methods</a></li>
+              </ul>
+            </li>
+          </ul>
+        </div>
+        <section id="classes">
+          <h2><a href="#classes">Classes</a></h2>
+          <dl class="m-doc">
+            <dt>class <a href="inspect_type_links.first.Foo.Foo.html" class="m-doc">Foo</a></dt>
+            <dd>An inner class in the first module</dd>
+          </dl>
+        </section>
+        <section id="methods">
+          <h2><a href="#methods">Methods</a></h2>
+          <dl class="m-doc">
+            <dt>
+              <span class="m-doc-wrap-bumper">def <a href="#reference_inner" class="m-doc-self" id="reference_inner">reference_inner</a>(</span><span class="m-doc-wrap">self,
+              a: <a href="inspect_type_links.first.Foo.Foo.html" class="m-doc">Foo</a>)</span>
+            </dt>
+            <dd>A method referencing an inner class. This is quite a pathological case and I&#x27;m not sure if Foo or Foo.Foo is better.</dd>
+            <dt>
+              <span class="m-doc-wrap-bumper">def <a href="#reference_other" class="m-doc-self" id="reference_other">reference_other</a>(</span><span class="m-doc-wrap">self,
+              a: <a href="inspect_type_links.second.Foo.html" class="m-doc">second.Foo</a>)</span>
+            </dt>
+            <dd>A method referencing a type in another module</dd>
+            <dt>
+              <span class="m-doc-wrap-bumper">def <a href="#reference_self" class="m-doc-self" id="reference_self">reference_self</a>(</span><span class="m-doc-wrap">self,
+              a: <a href="inspect_type_links.first.Foo.html" class="m-doc">first.Foo</a>)</span>
+            </dt>
+            <dd>A method referencing its wrapper class. Due to the inner Foo this is quite a pathological case and I&#x27;m not sure if first.Foo or Foo is better.</dd>
+            <dt>
+              <span class="m-doc-wrap-bumper">def <a href="#reference_sub" class="m-doc-self" id="reference_sub">reference_sub</a>(</span><span class="m-doc-wrap">self,
+              a: <a href="inspect_type_links.first.sub.Foo.html" class="m-doc">sub.Foo</a>,
+              b: <a href="inspect_type_links.first.sub.Foo.html" class="m-doc">sub.Foo</a>)</span>
+            </dt>
+            <dd>A method referencing a type in a submodule</dd>
+          </dl>
+        </section>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
diff --git a/documentation/test_python/inspect_type_links/inspect_type_links.first.html b/documentation/test_python/inspect_type_links/inspect_type_links.first.html
new file mode 100644 (file)
index 0000000..535daf3
--- /dev/null
@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>inspect_type_links.first | 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="inspect_type_links.html">inspect_type_links</a>.<wbr/></span>first <span class="m-thin">module</span>
+        </h1>
+        <p>First module</p>
+        <div class="m-block m-default">
+          <h3>Contents</h3>
+          <ul>
+            <li>
+              Reference
+              <ul>
+                <li><a href="#packages">Modules</a></li>
+                <li><a href="#classes">Classes</a></li>
+                <li><a href="#functions">Functions</a></li>
+              </ul>
+            </li>
+          </ul>
+        </div>
+        <section id="namespaces">
+          <h2><a href="#namespaces">Modules</a></h2>
+          <dl class="m-doc">
+            <dt>module <a href="inspect_type_links.first.sub.html" class="m-doc">sub</a></dt>
+            <dd>Submodule</dd>
+          </dl>
+        </section>
+        <section id="classes">
+          <h2><a href="#classes">Classes</a></h2>
+          <dl class="m-doc">
+            <dt>class <a href="inspect_type_links.first.Foo.html" class="m-doc">Foo</a></dt>
+            <dd>A class in the first module</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="#reference_other" class="m-doc-self" id="reference_other">reference_other</a>(</span><span class="m-doc-wrap">a: <a href="inspect_type_links.second.Foo.html" class="m-doc">second.Foo</a>)</span>
+            </dt>
+            <dd>A function referencing a type in another module</dd>
+            <dt>
+              <span class="m-doc-wrap-bumper">def <a href="#reference_self" class="m-doc-self" id="reference_self">reference_self</a>(</span><span class="m-doc-wrap">a: <a href="inspect_type_links.first.Foo.html" class="m-doc">Foo</a>,
+              b: <a href="inspect_type_links.first.Foo.html" class="m-doc">Foo</a>)</span>
+            </dt>
+            <dd>A function referencing a type in this module</dd>
+            <dt>
+              <span class="m-doc-wrap-bumper">def <a href="#reference_sub" class="m-doc-self" id="reference_sub">reference_sub</a>(</span><span class="m-doc-wrap">a: <a href="inspect_type_links.first.sub.Foo.html" class="m-doc">sub.Foo</a>,
+              b: <a href="inspect_type_links.first.sub.Foo.html" class="m-doc">sub.Foo</a>)</span>
+            </dt>
+            <dd>A function referencing a type in a submodule</dd>
+          </dl>
+        </section>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
diff --git a/documentation/test_python/inspect_type_links/inspect_type_links.first.sub.Foo.html b/documentation/test_python/inspect_type_links/inspect_type_links.first.sub.Foo.html
new file mode 100644 (file)
index 0000000..b70351a
--- /dev/null
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>inspect_type_links.first.sub.Foo | 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="inspect_type_links.html">inspect_type_links</a>.<wbr/></span><span class="m-breadcrumb"><a href="inspect_type_links.first.html">first</a>.<wbr/></span><span class="m-breadcrumb"><a href="inspect_type_links.first.sub.html">sub</a>.<wbr/></span>Foo <span class="m-thin">class</span>
+        </h1>
+        <p>A class in the submodule</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="#reference_parent" class="m-doc-self" id="reference_parent">reference_parent</a>(</span><span class="m-doc-wrap">a: <a href="inspect_type_links.first.Foo.html" class="m-doc">first.Foo</a>,
+              b: <a href="inspect_type_links.first.Foo.html" class="m-doc">first.Foo</a>)</span>
+            </dt>
+            <dd>A method referencing a type in a parent module</dd>
+            <dt>
+              <span class="m-doc-wrap-bumper">def <a href="#reference_self" class="m-doc-self" id="reference_self">reference_self</a>(</span><span class="m-doc-wrap">a: <a href="inspect_type_links.first.sub.Foo.html" class="m-doc">Foo</a>)</span>
+            </dt>
+            <dd>A method referencing a type in this submodule</dd>
+          </dl>
+        </section>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
diff --git a/documentation/test_python/inspect_type_links/inspect_type_links.first.sub.html b/documentation/test_python/inspect_type_links/inspect_type_links.first.sub.html
new file mode 100644 (file)
index 0000000..94ca8c6
--- /dev/null
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>inspect_type_links.first.sub | 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="inspect_type_links.html">inspect_type_links</a>.<wbr/></span><span class="m-breadcrumb"><a href="inspect_type_links.first.html">first</a>.<wbr/></span>sub <span class="m-thin">module</span>
+        </h1>
+        <p>Submodule</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="inspect_type_links.first.sub.Foo.html" class="m-doc">Foo</a></dt>
+            <dd>A class in the submodule</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="#reference_parent" class="m-doc-self" id="reference_parent">reference_parent</a>(</span><span class="m-doc-wrap">a: <a href="inspect_type_links.first.Foo.html" class="m-doc">first.Foo</a>,
+              b: <a href="inspect_type_links.first.Foo.html" class="m-doc">first.Foo</a>)</span>
+            </dt>
+            <dd>A function referencing a type in a parent module</dd>
+            <dt>
+              <span class="m-doc-wrap-bumper">def <a href="#reference_self" class="m-doc-self" id="reference_self">reference_self</a>(</span><span class="m-doc-wrap">a: <a href="inspect_type_links.first.sub.Foo.html" class="m-doc">Foo</a>,
+              b: <a href="inspect_type_links.first.sub.Foo.html" class="m-doc">Foo</a>)</span>
+            </dt>
+            <dd>A function referencing a type in this submodule</dd>
+          </dl>
+        </section>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
diff --git a/documentation/test_python/inspect_type_links/inspect_type_links.second.Foo.html b/documentation/test_python/inspect_type_links/inspect_type_links.second.Foo.html
new file mode 100644 (file)
index 0000000..d57e054
--- /dev/null
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>inspect_type_links.second.Foo | 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="inspect_type_links.html">inspect_type_links</a>.<wbr/></span><span class="m-breadcrumb"><a href="inspect_type_links.second.html">second</a>.<wbr/></span>Foo <span class="m-thin">class</span>
+        </h1>
+        <p>A class in the second module</p>
+        <div class="m-block m-default">
+          <h3>Contents</h3>
+          <ul>
+            <li>
+              Reference
+              <ul>
+                <li><a href="#properties">Properties</a></li>
+              </ul>
+            </li>
+          </ul>
+        </div>
+        <section id="properties">
+          <h2><a href="#properties">Properties</a></h2>
+          <dl class="m-doc">
+            <dt>
+              <a href="#type_property" class="m-doc-self" id="type_property">type_property</a>: <a href="inspect_type_links.second.html#Enum" class="m-doc">Enum</a> <span class="m-label m-flat m-warning">get</span>
+            </dt>
+            <dd>A property</dd>
+          </dl>
+        </section>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
diff --git a/documentation/test_python/inspect_type_links/inspect_type_links.second.html b/documentation/test_python/inspect_type_links/inspect_type_links.second.html
new file mode 100644 (file)
index 0000000..801fed8
--- /dev/null
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>inspect_type_links.second | 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="inspect_type_links.html">inspect_type_links</a>.<wbr/></span>second <span class="m-thin">module</span>
+        </h1>
+        <p>Second module</p>
+        <div class="m-block m-default">
+          <h3>Contents</h3>
+          <ul>
+            <li>
+              Reference
+              <ul>
+                <li><a href="#classes">Classes</a></li>
+                <li><a href="#enums">Enums</a></li>
+                <li><a href="#functions">Functions</a></li>
+                <li><a href="#data">Data</a></li>
+              </ul>
+            </li>
+          </ul>
+        </div>
+        <section id="classes">
+          <h2><a href="#classes">Classes</a></h2>
+          <dl class="m-doc">
+            <dt>class <a href="inspect_type_links.second.Foo.html" class="m-doc">Foo</a></dt>
+            <dd>A class in the second module</dd>
+          </dl>
+        </section>
+        <section id="enums">
+          <h2><a href="#enums">Enums</a></h2>
+          <dl class="m-doc">
+            <dt>
+              <span class="m-doc-wrap-bumper">class <a href="#Enum" class="m-doc-self" id="Enum">Enum</a>(enum.Enum): </span><span class="m-doc-wrap"><a href="#Enum-FIRST" class="m-doc-self" id="Enum-FIRST">FIRST</a> = 1
+              <a href="#Enum-SECOND" class="m-doc-self" id="Enum-SECOND">SECOND</a> = 2</span>
+            </dt>
+            <dd>An enum</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="#type_enum" class="m-doc-self" id="type_enum">type_enum</a>(</span><span class="m-doc-wrap">a: <a href="inspect_type_links.second.html#Enum" class="m-doc">Enum</a>)</span>
+            </dt>
+            <dd>Function referencing an enum</dd>
+            <dt>
+              <span class="m-doc-wrap-bumper">def <a href="#type_return" class="m-doc-self" id="type_return">type_return</a>(</span><span class="m-doc-wrap">) -&gt; <a href="inspect_type_links.second.Foo.html" class="m-doc">Foo</a></span>
+            </dt>
+            <dd>A function with a return type annotation</dd>
+          </dl>
+        </section>
+        <section id="data">
+          <h2><a href="#data">Data</a></h2>
+          <dl class="m-doc">
+            <dt>
+              <a href="#TYPE_DATA" class="m-doc-self" id="TYPE_DATA">TYPE_DATA</a>: <a href="inspect_type_links.second.Foo.html" class="m-doc">Foo</a>
+            </dt>
+            <dd></dd>
+          </dl>
+        </section>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
diff --git a/documentation/test_python/inspect_type_links/inspect_type_links/__init__.py b/documentation/test_python/inspect_type_links/inspect_type_links/__init__.py
new file mode 100644 (file)
index 0000000..35e9047
--- /dev/null
@@ -0,0 +1 @@
+from . import first, second
diff --git a/documentation/test_python/inspect_type_links/inspect_type_links/first/__init__.py b/documentation/test_python/inspect_type_links/inspect_type_links/first/__init__.py
new file mode 100644 (file)
index 0000000..30b9afd
--- /dev/null
@@ -0,0 +1,41 @@
+"""First module"""
+
+from inspect_type_links import first
+from inspect_type_links import second
+
+class Foo:
+    """A class in the first module"""
+
+    def reference_self(self, a: 'inspect_type_links.first.Foo'):
+        """A method referencing its wrapper class. Due to the inner Foo this is quite a pathological case and I'm not sure if first.Foo or Foo is better."""
+
+    def reference_inner(self, a: 'inspect_type_links.first.Foo.Foo'):
+        """A method referencing an inner class. This is quite a pathological case and I'm not sure if Foo or Foo.Foo is better."""
+
+    def reference_other(self, a: second.Foo):
+        """A method referencing a type in another module"""
+
+    class Foo:
+        """An inner class in the first module"""
+
+        def reference_self(self, a: 'inspect_type_links.first.Foo.Foo'):
+            """A method referencing its wrapper class"""
+
+        def reference_parent(self, a: 'inspect_type_links.first.Foo'):
+            """A method referencing its parent wrapper class"""
+
+def reference_self(a: Foo, b: first.Foo):
+    """A function referencing a type in this module"""
+
+def reference_other(a: second.Foo):
+    """A function referencing a type in another module"""
+
+from . import sub
+
+def _foo_reference_sub(self, a: sub.Foo, b: first.sub.Foo):
+    """A method referencing a type in a submodule"""
+
+setattr(Foo, 'reference_sub', _foo_reference_sub)
+
+def reference_sub(a: sub.Foo, b: first.sub.Foo):
+    """A function referencing a type in a submodule"""
diff --git a/documentation/test_python/inspect_type_links/inspect_type_links/first/sub.py b/documentation/test_python/inspect_type_links/inspect_type_links/first/sub.py
new file mode 100644 (file)
index 0000000..7795405
--- /dev/null
@@ -0,0 +1,18 @@
+"""Submodule"""
+
+from inspect_type_links import first
+
+class Foo:
+    """A class in the submodule"""
+
+    def reference_self(a: 'inspect_type_links.first.sub.Foo'):
+        """A method referencing a type in this submodule"""
+
+    def reference_parent(a: first.Foo, b: first.Foo):
+        """A method referencing a type in a parent module"""
+
+def reference_self(a: Foo, b: 'inspect_type_links.first.sub.Foo'):
+    """A function referencing a type in this submodule"""
+
+def reference_parent(a: first.Foo, b: first.Foo):
+    """A function referencing a type in a parent module"""
diff --git a/documentation/test_python/inspect_type_links/inspect_type_links/second.py b/documentation/test_python/inspect_type_links/inspect_type_links/second.py
new file mode 100644 (file)
index 0000000..3ca7908
--- /dev/null
@@ -0,0 +1,24 @@
+"""Second module"""
+
+import enum
+
+class Enum(enum.Enum):
+    """An enum"""
+
+    FIRST = 1
+    SECOND = 2
+
+def type_enum(a: Enum):
+    """Function referencing an enum"""
+
+class Foo:
+    """A class in the second module"""
+
+    @property
+    def type_property(self) -> Enum:
+        """A property"""
+
+def type_return() -> Foo:
+    """A function with a return type annotation"""
+
+TYPE_DATA: Foo = Foo()
index d0210b14e796c27e1eef594ccf373b22407cebad..214996a76c503ba4eca2959e0474e99f8e27a80d 100644 (file)
@@ -74,7 +74,7 @@
             <dd>Each overload should have a different hash</dd>
             <dt>
               <span class="m-doc-wrap-bumper">def <a href="#o-an_overloaded_function-d36ce" class="m-doc-self" id="o-an_overloaded_function-d36ce">an_overloaded_function</a>(</span><span class="m-doc-wrap">arg0: int,
-              arg1: link_formatting.pybind.Foo<span class="m-text m-dim">, /</span>) -&gt; int</span>
+              arg1: <a href="c.link_formatting.pybind.Foo.html#this-is-an-url" class="m-doc">Foo</a><span class="m-text m-dim">, /</span>) -&gt; int</span>
             </dt>
             <dd>Each overload should have a different hash</dd>
             <dt>
index db2e17179aa5b3a120949745d70d48aa6f6cbe9d..b65fa10fa328faf9617106f155e88b5e4ac375c9 100644 (file)
@@ -38,7 +38,7 @@
           <h2><a href="#staticmethods">Static methods</a></h2>
           <dl class="m-doc">
             <dt>
-              <span class="m-doc-wrap-bumper">def <a href="#a_thing-da39a" class="m-doc-self" id="a_thing-da39a">a_thing</a>(</span><span class="m-doc-wrap">) -&gt; pybind_name_mapping.Class</span>
+              <span class="m-doc-wrap-bumper">def <a href="#a_thing-da39a" class="m-doc-self" id="a_thing-da39a">a_thing</a>(</span><span class="m-doc-wrap">) -&gt; <a href="pybind_name_mapping.Class.html" class="m-doc">Class</a></span>
             </dt>
             <dd>A method</dd>
           </dl>
index 0c28175e10edae7a0eaab56031ef84c0a286a6f6..8f3df052ad79c86beac8e0ee0e88bbbacbdddcdf 100644 (file)
@@ -53,7 +53,7 @@
           <h2><a href="#functions">Functions</a></h2>
           <dl class="m-doc">
             <dt>
-              <span class="m-doc-wrap-bumper">def <a href="#foo" class="m-doc-self" id="foo">foo</a>(</span><span class="m-doc-wrap">) -&gt; pybind_name_mapping.Class</span>
+              <span class="m-doc-wrap-bumper">def <a href="#foo" class="m-doc-self" id="foo">foo</a>(</span><span class="m-doc-wrap">) -&gt; <a href="pybind_name_mapping.Class.html" class="m-doc">Class</a></span>
             </dt>
             <dd>This function returns Class, *not* _sub.Foo</dd>
           </dl>
index fcdbee289038bef117b69c2c18e4a0ea1378c3b7..ce8dd513ee7a9545924715d7c37b092e28412420 100644 (file)
@@ -38,7 +38,7 @@
           <h2><a href="#functions">Functions</a></h2>
           <dl class="m-doc">
             <dt>
-              <span class="m-doc-wrap-bumper">def <a href="#foo-c5914" class="m-doc-self" id="foo-c5914">foo</a>(</span><span class="m-doc-wrap">arg0: pybind_name_mapping.Class,
+              <span class="m-doc-wrap-bumper">def <a href="#foo-c5914" class="m-doc-self" id="foo-c5914">foo</a>(</span><span class="m-doc-wrap">arg0: <a href="pybind_name_mapping.Class.html" class="m-doc">Class</a>,
               arg1: int<span class="m-text m-dim">, /</span>) -&gt; int</span>
             </dt>
             <dd>A function</dd>
index d4ea689e8e5b8776be4e387da1c851264a4a26dc..a15394b269845b1325ff54fc7d26e0b534d69d2c 100644 (file)
@@ -42,7 +42,7 @@
           <dl class="m-doc">
             <dt>
               <span class="m-doc-wrap-bumper">def <a href="#static_function-8f19c" class="m-doc-self" id="static_function-8f19c">static_function</a>(</span><span class="m-doc-wrap">arg0: int,
-              arg1: float<span class="m-text m-dim">, /</span>) -&gt; pybind_signatures.MyClass</span>
+              arg1: float<span class="m-text m-dim">, /</span>) -&gt; <a href="pybind_signatures.MyClass.html" class="m-doc">MyClass</a></span>
             </dt>
             <dd>Static method with positional-only args</dd>
           </dl>
diff --git a/documentation/test_python/pybind_type_links/pybind_type_links.Foo.html b/documentation/test_python/pybind_type_links/pybind_type_links.Foo.html
new file mode 100644 (file)
index 0000000..c9ca00a
--- /dev/null
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>pybind_type_links.Foo | 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_type_links.html">pybind_type_links</a>.<wbr/></span>Foo <span class="m-thin">class</span>
+        </h1>
+        <p>A class</p>
+        <div class="m-block m-default">
+          <h3>Contents</h3>
+          <ul>
+            <li>
+              Reference
+              <ul>
+                <li><a href="#dunder-methods">Special methods</a></li>
+                <li><a href="#properties">Properties</a></li>
+              </ul>
+            </li>
+          </ul>
+        </div>
+        <section id="dunder-methods">
+          <h2><a href="#dunder-methods">Special methods</a></h2>
+          <dl class="m-doc">
+            <dt>
+              <span class="m-doc-wrap-bumper">def <a href="#__init__-8f54a" class="m-doc-self" id="__init__-8f54a">__init__</a>(</span><span class="m-doc-wrap">self,
+              arg0: <a href="pybind_type_links.html#Enum" class="m-doc">Enum</a><span class="m-text m-dim">, /</span>)</span>
+            </dt>
+            <dd>Constructor</dd>
+          </dl>
+        </section>
+        <section id="properties">
+          <h2><a href="#properties">Properties</a></h2>
+          <dl class="m-doc">
+            <dt>
+              <a href="#property" class="m-doc-self" id="property">property</a>: <a href="pybind_type_links.html#Enum" class="m-doc">Enum</a> <span class="m-label m-flat m-success">get set</span>
+            </dt>
+            <dd>A property</dd>
+          </dl>
+        </section>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
diff --git a/documentation/test_python/pybind_type_links/pybind_type_links.cpp b/documentation/test_python/pybind_type_links/pybind_type_links.cpp
new file mode 100644 (file)
index 0000000..fb99618
--- /dev/null
@@ -0,0 +1,35 @@
+#include <pybind11/pybind11.h>
+
+namespace py = pybind11;
+
+namespace {
+
+enum class Enum {
+    First, Second
+};
+
+void typeEnum(Enum) {}
+
+struct Foo {
+    Enum property;
+};
+
+Foo typeReturn() { return {}; }
+
+}
+
+PYBIND11_MODULE(pybind_type_links, m) {
+    m.doc() = "pybind11 type linking";
+
+    py::enum_<Enum>{m, "Enum", "An enum"}
+        .value("FIRST", Enum::First)
+        .value("SECOND", Enum::Second);
+
+    py::class_<Foo>{m, "Foo", "A class"}
+        .def(py::init<Enum>(), "Constructor")
+        .def_readwrite("property", &Foo::property, "A property");
+
+    m
+        .def("type_enum", &typeEnum, "A function taking an enum")
+        .def("type_return", &typeReturn, "A function returning a type");
+}
diff --git a/documentation/test_python/pybind_type_links/pybind_type_links.html b/documentation/test_python/pybind_type_links/pybind_type_links.html
new file mode 100644 (file)
index 0000000..57cf588
--- /dev/null
@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>pybind_type_links | 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_type_links <span class="m-thin">module</span>
+        </h1>
+        <p>pybind11 type linking</p>
+        <div class="m-block m-default">
+          <h3>Contents</h3>
+          <ul>
+            <li>
+              Reference
+              <ul>
+                <li><a href="#classes">Classes</a></li>
+                <li><a href="#enums">Enums</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_type_links.Foo.html" class="m-doc">Foo</a></dt>
+            <dd>A class</dd>
+          </dl>
+        </section>
+        <section id="enums">
+          <h2><a href="#enums">Enums</a></h2>
+          <dl class="m-doc">
+            <dt>
+              <span class="m-doc-wrap-bumper">class <a href="#Enum" class="m-doc-self" id="Enum">Enum</a>: </span><span class="m-doc-wrap"><a href="#Enum-FIRST" class="m-doc-self" id="Enum-FIRST">FIRST</a> = 0
+              <a href="#Enum-SECOND" class="m-doc-self" id="Enum-SECOND">SECOND</a> = 1</span>
+            </dt>
+            <dd>An enum</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="#type_enum-3b87d" class="m-doc-self" id="type_enum-3b87d">type_enum</a>(</span><span class="m-doc-wrap">arg0: <a href="pybind_type_links.html#Enum" class="m-doc">Enum</a><span class="m-text m-dim">, /</span>)</span>
+            </dt>
+            <dd>A function taking an enum</dd>
+            <dt>
+              <span class="m-doc-wrap-bumper">def <a href="#type_return-da39a" class="m-doc-self" id="type_return-da39a">type_return</a>(</span><span class="m-doc-wrap">) -&gt; <a href="pybind_type_links.Foo.html" class="m-doc">Foo</a></span>
+            </dt>
+            <dd>A function returning a type</dd>
+          </dl>
+        </section>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
index e7ee4612015bb93e3a043be5e31165a64d0f7d69..f28ed0de626aaa7bebed4df4139b7c21b4cc0a8f 100644 (file)
@@ -146,3 +146,18 @@ class Recursive(BaseInspectTestCase):
         self.assertEqual(*self.actual_expected_contents('inspect_recursive.html'))
         self.assertEqual(*self.actual_expected_contents('inspect_recursive.first.html'))
         self.assertEqual(*self.actual_expected_contents('inspect_recursive.a.html'))
+
+class TypeLinks(BaseInspectTestCase):
+    def __init__(self, *args, **kwargs):
+        super().__init__(__file__, 'type_links', *args, **kwargs)
+
+    def test(self):
+        self.run_python()
+        self.assertEqual(*self.actual_expected_contents('inspect_type_links.first.html'))
+        self.assertEqual(*self.actual_expected_contents('inspect_type_links.first.Foo.html'))
+        self.assertEqual(*self.actual_expected_contents('inspect_type_links.first.Foo.Foo.html'))
+        self.assertEqual(*self.actual_expected_contents('inspect_type_links.first.sub.html'))
+        self.assertEqual(*self.actual_expected_contents('inspect_type_links.first.sub.Foo.html'))
+
+        self.assertEqual(*self.actual_expected_contents('inspect_type_links.second.html'))
+        self.assertEqual(*self.actual_expected_contents('inspect_type_links.second.Foo.html'))
index 76a6d68974b70c81e22f79b18b4e74903d995b91..098c9de4075ebaac9d82b716befc253fb6b87c26 100644 (file)
@@ -31,124 +31,124 @@ from . import BaseInspectTestCase
 
 class Signature(unittest.TestCase):
     def test(self):
-        self.assertEqual(parse_pybind_signature(State({}),
+        self.assertEqual(parse_pybind_signature(State({}), [],
             'foo(a: int, a2: module.Thing) -> module.Thing3'),
             ('foo', '', [
-                ('a', 'int', None),
-                ('a2', 'module.Thing', None),
+                ('a', 'int', 'int', None),
+                ('a2', 'module.Thing', 'module.Thing', None),
             ], 'module.Thing3'))
 
     def test_newline(self):
-        self.assertEqual(parse_pybind_signature(State({}),
+        self.assertEqual(parse_pybind_signature(State({}), [],
             'foo(a: int, a2: module.Thing) -> module.Thing3\n'),
             ('foo', '', [
-                ('a', 'int', None),
-                ('a2', 'module.Thing', None),
+                ('a', 'int', 'int', None),
+                ('a2', 'module.Thing', 'module.Thing', None),
             ], 'module.Thing3'))
 
     def test_docs(self):
-        self.assertEqual(parse_pybind_signature(State({}),
+        self.assertEqual(parse_pybind_signature(State({}), [],
             'foo(a: int, a2: module.Thing) -> module.Thing3\n\nDocs here!!'),
             ('foo', 'Docs here!!', [
-                ('a', 'int', None),
-                ('a2', 'module.Thing', None),
+                ('a', 'int', 'int', None),
+                ('a2', 'module.Thing', 'module.Thing', None),
             ], 'module.Thing3'))
 
     def test_no_args(self):
-        self.assertEqual(parse_pybind_signature(State({}),
+        self.assertEqual(parse_pybind_signature(State({}), [],
             'thingy() -> str'),
             ('thingy', '', [], 'str'))
 
     def test_no_return(self):
-        self.assertEqual(parse_pybind_signature(State({}),
+        self.assertEqual(parse_pybind_signature(State({}), [],
             '__init__(self: module.Thing)'),
             ('__init__', '', [
-                ('self', 'module.Thing', None),
+                ('self', 'module.Thing', 'module.Thing', None),
             ], None))
 
     def test_no_arg_types(self):
-        self.assertEqual(parse_pybind_signature(State({}),
+        self.assertEqual(parse_pybind_signature(State({}), [],
             'thingy(self, the_other_thing)'),
             ('thingy', '', [
-                ('self', None, None),
-                ('the_other_thing', None, None),
+                ('self', None, None, None),
+                ('the_other_thing', None, None, None),
             ], None))
 
     def test_square_brackets(self):
-        self.assertEqual(parse_pybind_signature(State({}),
+        self.assertEqual(parse_pybind_signature(State({}), [],
             'foo(a: Tuple[int, str], no_really: str) -> List[str]'),
             ('foo', '', [
-                ('a', 'Tuple[int, str]', None),
-                ('no_really', 'str', None),
+                ('a', 'Tuple[int, str]', 'Tuple[int, str]', None),
+                ('no_really', 'str', 'str', None),
             ], 'List[str]'))
 
     def test_nested_square_brackets(self):
-        self.assertEqual(parse_pybind_signature(State({}),
+        self.assertEqual(parse_pybind_signature(State({}), [],
             'foo(a: Tuple[int, List[Tuple[int, int]]], another: float) -> Union[str, Any]'),
             ('foo', '', [
-                ('a', 'Tuple[int, List[Tuple[int, int]]]', None),
-                ('another', 'float', None),
+                ('a', 'Tuple[int, List[Tuple[int, int]]]', 'Tuple[int, List[Tuple[int, int]]]', None),
+                ('another', 'float', 'float', None),
             ], 'Union[str, Any]'))
 
     def test_kwargs(self):
-        self.assertEqual(parse_pybind_signature(State({}),
+        self.assertEqual(parse_pybind_signature(State({}), [],
             'foo(*args, **kwargs)'),
             ('foo', '', [
-                ('*args', None, None),
-                ('**kwargs', None, None),
+                ('*args', None, None, None),
+                ('**kwargs', None, None, None),
             ], None))
 
     # https://github.com/pybind/pybind11/commit/0826b3c10607c8d96e1d89dc819c33af3799a7b8,
     # released in 2.3.0. We want to support both, so test both.
     def test_default_values_pybind22(self):
-        self.assertEqual(parse_pybind_signature(State({}),
+        self.assertEqual(parse_pybind_signature(State({}), [],
             'foo(a: float=1.0, b: str=\'hello\')'),
             ('foo', '', [
-                ('a', 'float', '1.0'),
-                ('b', 'str', '\'hello\''),
+                ('a', 'float', 'float', '1.0'),
+                ('b', 'str', 'str', '\'hello\''),
             ], None))
 
     def test_default_values_pybind23(self):
-        self.assertEqual(parse_pybind_signature(State({}),
+        self.assertEqual(parse_pybind_signature(State({}), [],
             'foo(a: float = 1.0, b: str = \'hello\')'),
             ('foo', '', [
-                ('a', 'float', '1.0'),
-                ('b', 'str', '\'hello\''),
+                ('a', 'float', 'float', '1.0'),
+                ('b', 'str', 'str', '\'hello\''),
             ], None))
 
     def test_crazy_stuff(self):
-        self.assertEqual(parse_pybind_signature(State({}),
+        self.assertEqual(parse_pybind_signature(State({}), [],
             'foo(a: int, b: Math::Vector<4, UnsignedInt>)'),
-            ('foo', '', [('…', None, None)], None))
+            ('foo', '', [('…', None, None, None)], None))
 
     def test_crazy_stuff_docs(self):
-        self.assertEqual(parse_pybind_signature(State({}),
+        self.assertEqual(parse_pybind_signature(State({}), [],
             'foo(a: int, b: Math::Vector<4, UnsignedInt>)\n\nThis is text!!'),
-            ('foo', 'This is text!!', [('…', None, None)], None))
+            ('foo', 'This is text!!', [('…', None, None, None)], None))
 
     def test_crazy_return(self):
-        self.assertEqual(parse_pybind_signature(State({}),
+        self.assertEqual(parse_pybind_signature(State({}), [],
             'foo(a: int) -> Math::Vector<4, UnsignedInt>'),
-            ('foo', '', [('…', None, None)], None))
+            ('foo', '', [('…', None, None, None)], None))
 
     def test_crazy_return_docs(self):
-        self.assertEqual(parse_pybind_signature(State({}),
+        self.assertEqual(parse_pybind_signature(State({}), [],
             'foo(a: int) -> Math::Vector<4, UnsignedInt>\n\nThis returns!'),
-            ('foo', 'This returns!', [('…', None, None)], None))
+            ('foo', 'This returns!', [('…', None, None, None)], None))
 
     def test_no_name(self):
-        self.assertEqual(parse_pybind_signature(State({}),
+        self.assertEqual(parse_pybind_signature(State({}), [],
             '(arg0: MyClass) -> float'),
-            ('', '', [('arg0', 'MyClass', None)], 'float'))
+            ('', '', [('arg0', 'MyClass', 'MyClass', None)], 'float'))
 
     def test_module_mapping(self):
         state = State({})
         state.module_mapping['module._module'] = 'module'
 
-        self.assertEqual(parse_pybind_signature(state,
+        self.assertEqual(parse_pybind_signature(state, [],
             'foo(a: module._module.Foo, b: Tuple[int, module._module.Bar]) -> module._module.Baz'),
-            ('foo', '', [('a', 'module.Foo', None),
-                         ('b', 'Tuple[int, module.Bar]', None)], 'module.Baz'))
+            ('foo', '', [('a', 'module.Foo', 'module.Foo', None),
+                         ('b', 'Tuple[int, module.Bar]', 'Tuple[int, module.Bar]', None)], 'module.Baz'))
 
 class Signatures(BaseInspectTestCase):
     def __init__(self, *args, **kwargs):
@@ -233,3 +233,14 @@ class NameMapping(BaseInspectTestCase):
         self.assertEqual(*self.actual_expected_contents('pybind_name_mapping.html'))
         self.assertEqual(*self.actual_expected_contents('pybind_name_mapping.Class.html'))
         self.assertEqual(*self.actual_expected_contents('pybind_name_mapping.submodule.html'))
+
+class TypeLinks(BaseInspectTestCase):
+    def __init__(self, *args, **kwargs):
+        super().__init__(__file__, 'type_links', *args, **kwargs)
+
+    def test(self):
+        self.run_python({
+            'PYBIND11_COMPATIBILITY': True
+        })
+        self.assertEqual(*self.actual_expected_contents('pybind_type_links.html'))
+        self.assertEqual(*self.actual_expected_contents('pybind_type_links.Foo.html'))