chiark / gitweb /
documentation/python: implement context-relative linking.
authorVladimír Vondruš <mosra@centrum.cz>
Sat, 24 Aug 2019 16:13:15 +0000 (18:13 +0200)
committerVladimír Vondruš <mosra@centrum.cz>
Sun, 25 Aug 2019 10:47:45 +0000 (12:47 +0200)
I didn't change much, but the amount of research needed to verify that I'm
indeed doing it (mostly) correct was extreme. There's a pathological case
inside a named subclass where I'm doing it differently than Python itself,
but I don't expect this to be hit very often, so postponing until later.

The tests also assert inside the function definitions (and those functions
get called) to verify we're indeed doing (mostly) correct stuff.

doc/documentation/python.rst
documentation/python.py
documentation/test_python/inspect_type_links/docs.rst [new file with mode: 0644]
documentation/test_python/inspect_type_links/inspect_type_links.first.Foo.Foo.html
documentation/test_python/inspect_type_links/inspect_type_links.first.Foo.html
documentation/test_python/inspect_type_links/inspect_type_links.first.html
documentation/test_python/inspect_type_links/inspect_type_links.first.sub.Foo.html
documentation/test_python/inspect_type_links/inspect_type_links/__init__.py
documentation/test_python/inspect_type_links/inspect_type_links/first/__init__.py
documentation/test_python/test_inspect.py
plugins/m/sphinx.py

index 9c2b413ea3d414033b0c8d0ecdedce0a80c56d21..11efd304b61c6ba59bae73244604c01012f8731d 100644 (file)
@@ -736,11 +736,24 @@ Keyword argument    Content
     added by the plugin *need* to have :py:`object` set to :py:`None` so the
     script as well as other plugins can correctly distinguish them.
 
-The :py:`hooks_pre_page` and :py:`hooks_post_run` are called before each page
-of output gets rendered (for example, resetting an some internal counter for
-page-wide unique element IDs) and after the whole run is done (for example to
-serialize cached internal state). Currently, those functions get no arguments
-passed.
+The :py:`hooks_pre_page` is called before each page of output gets rendered.
+Can be used for example for resetting some internal counter for page-wide
+unique element IDs. It gets passed the following arguments:
+
+.. class:: m-table
+
+=================== ===========================================================
+Keyword argument    Content
+=================== ===========================================================
+:py:`path`          Path of the module/class/page to render. A list of names,
+                    for modules and classes :py:`'.'.join(path)` is equivalent
+                    to the fully qualified name. Useful to provide
+                    context-sensitive linking capabilities.
+=================== ===========================================================
+
+The :py:`hooks_post_run` is called after the whole run is done, useful for
+example to serialize cached internal state. Currently, this function get no
+arguments passed.
 
 Registration function for a plugin that needs to query the :py:`OUTPUT` setting
 might look like this --- the remaining keyword arguments will collapse into
index dabfc69a964c6d64b255188ef11381fa5390e758..b45f791eb7aeee7baf5cc5733f17a13e133a9403 100755 (executable)
@@ -552,8 +552,12 @@ def make_relative_name(state: State, referrer_path: List[str], name):
     # 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`
+    # `module.Foo`. To fix that, the shortened path needs to be `module.Foo`
     # instead of `Foo`.
+    #
+    # There's many corner cases, see test_inspect.InspectTypeLinks for the full
+    # description, tests and verification against python's internal name
+    # resolution rules.
     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
@@ -561,10 +565,6 @@ def make_relative_name(state: State, referrer_path: List[str], name):
         # 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:
@@ -1421,7 +1421,8 @@ def render_module(state: State, path, module, env):
     logging.debug("generating %s", filename)
 
     # Call all registered page begin hooks
-    for hook in state.hooks_pre_page: hook()
+    for hook in state.hooks_pre_page:
+        hook(path=path)
 
     page = Empty()
     page.summary, page.content = extract_docs(state, state.module_docs, path, module.__doc__)
@@ -1496,7 +1497,8 @@ def render_class(state: State, path, class_, env):
     logging.debug("generating %s", filename)
 
     # Call all registered page begin hooks
-    for hook in state.hooks_pre_page: hook()
+    for hook in state.hooks_pre_page:
+        hook(path=path)
 
     page = Empty()
     page.summary, page.content = extract_docs(state, state.class_docs, path, class_.__doc__)
@@ -1716,7 +1718,8 @@ def render_page(state: State, path, input_filename, env):
     logging.debug("generating %s", filename)
 
     # Call all registered page begin hooks
-    for hook in state.hooks_pre_page: hook()
+    for hook in state.hooks_pre_page:
+        hook(path=path)
 
     # Render the file
     with open(input_filename, 'r') as f: pub = publish_rst(state, f.read(), source_path=input_filename)
@@ -1908,9 +1911,6 @@ def run(basedir, config, *, templates=default_templates, search_add_lookahead_ba
             hooks_pre_page=state.hooks_pre_page,
             hooks_post_run=state.hooks_post_run)
 
-    # Call all registered page begin hooks for the first time
-    for hook in state.hooks_pre_page: hook()
-
     # Crawl all input modules to gather the name tree, put their names into a
     # list for the index. The crawl is done breadth-first, so the function
     # returns a list of submodules to be crawled next.
@@ -1958,6 +1958,10 @@ def run(basedir, config, *, templates=default_templates, search_add_lookahead_ba
     for hook in state.hooks_post_crawl:
         hook(name_map=state.name_map)
 
+    # Call all registered page begin hooks for the doc rendering
+    for hook in state.hooks_pre_page:
+        hook(path=[])
+
     # Then process the doc input files so we have all data for rendering
     # module pages. This needs to be done *after* the initial crawl so
     # cross-linking works as expected.
diff --git a/documentation/test_python/inspect_type_links/docs.rst b/documentation/test_python/inspect_type_links/docs.rst
new file mode 100644 (file)
index 0000000..90ce021
--- /dev/null
@@ -0,0 +1,56 @@
+.. py:module:: inspect_type_links
+
+    :ref:`first.Foo` and :ref:`inspect_type_links.first.Foo` should lead to the
+    same class.
+
+.. py:module:: inspect_type_links.first
+
+    :ref:`Foo`, :ref:`first.Foo` and :ref:`inspect_type_links.first.Foo` should
+    lead to the same class.
+
+.. py:class:: inspect_type_links.first.Foo
+
+    :ref:`first.Foo` and :ref:`inspect_type_links.first.Foo` should
+    lead to self; referencing the subclass via :ref:`Foo`, :ref:`Foo.Foo`,
+    :ref:`first.Foo.Foo` or :ref:`Bar`. :ref:`second.Foo` and
+    :ref:`inspect_type_links.Bar` lead to other classes.
+
+    This is consistent with how Python type annotations inside *a class* are
+    interpreted -- see :ref:`reference_self_data`, :ref:`reference_inner_data`
+    and :ref:`reference_inner_other_data`. Inside *function definitions* the
+    rules are different as per https://docs.python.org/3/reference/executionmodel.html#resolution-of-names:
+
+        The scope of names defined in a class block is limited to the class
+        block; it does not extend to the code blocks of methods
+
+    This means relative annotations in :ref:`reference_self()`,
+    :ref:`reference_inner()` and :ref:`reference_inner_other()` are parsed
+    differently -- but in the documentation, these are shown with the same
+    rules as for data themselves.
+
+.. py:data:: inspect_type_links.first.Foo.reference_self_data
+    :summary: Referencing its wrapper class using ``first.Foo``, displayed
+        as ``first.Foo`` as well. ``Foo`` alone would reference the inner. This
+        is different from :ref:`reference_self()`.
+
+.. py:data:: inspect_type_links.first.Foo.reference_inner_data
+    :summary: Referencing the inner class using ``Foo``, ``Foo.Foo`` or
+        ``first.Foo.Foo``, displayed as just ``Foo``. This is different from
+        :ref:`reference_inner()`.
+
+.. py:data:: inspect_type_links.first.Foo.reference_inner_other_data
+    :summary: Referencing another inner class using ``Bar``, ``Foo.Bar`` or
+        ``first.Foo.Bar``, displayed as just ``Bar``. This is different from
+        :ref:`reference_inner_other()`.
+
+.. py:class:: inspect_type_links.first.Foo.Foo
+
+    Referencing self as :ref:`Foo` or :ref:`Foo.Foo`, parent as
+    :ref:`first.Foo`, other as :ref:`second.Foo`. However inside annotations
+    ``Foo`` references the parent, consistently in a function and in data?
+    Am I doing something wrong?
+
+.. py:class:: inspect_type_links.first.sub.Foo
+
+    Referencing self as :ref:`Foo` or :ref:`sub.Foo`, parent as
+    :ref:`first.Foo`, other as :ref:`second.Foo`.
index 129c8253d88c5f5173cb85215bf8979a7efb7dbf..7b00c5e0e789599a57ded66857000d24430badc7 100644 (file)
               Reference
               <ul>
                 <li><a href="#methods">Methods</a></li>
+                <li><a href="#data">Data</a></li>
               </ul>
             </li>
           </ul>
         </div>
+<p>Referencing self as <a class="m-doc" href="inspect_type_links.first.Foo.Foo.html">Foo</a> or <a class="m-doc" href="inspect_type_links.first.Foo.Foo.html">Foo.Foo</a>, parent as
+<a class="m-doc" href="inspect_type_links.first.Foo.html">first.Foo</a>, other as <a class="m-doc" href="inspect_type_links.second.Foo.html">second.Foo</a>. However inside annotations
+<code>Foo</code> references the parent, consistently in a function and in data?
+Am I doing something wrong?</p>
         <section id="methods">
           <h2><a href="#methods">Methods</a></h2>
           <dl class="m-doc">
             <dt id="reference_parent">
               <span class="m-doc-wrap-bumper">def <a href="#reference_parent" class="m-doc-self">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>
+              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 its parent wrapper class</dd>
+            <dd>A method referencing its parent wrapper class using first.Foo. Foo works too, though. Weird. Displayed as first.Foo.</dd>
             <dt id="reference_self">
               <span class="m-doc-wrap-bumper">def <a href="#reference_self" class="m-doc-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>
+              a: <a href="inspect_type_links.first.Foo.Foo.html" class="m-doc">Foo</a>,
+              b: <a href="inspect_type_links.first.Foo.Foo.html" class="m-doc">Foo</a>)</span>
             </dt>
-            <dd>A method referencing its wrapper class</dd>
+            <dd>A method referencing its wrapper class using Foo.Foo or first.Foo.Foo, displayed as Foo in both cases; however using just Foo in the annotation references the parent?!</dd>
+          </dl>
+        </section>
+        <section id="data">
+          <h2><a href="#data">Data</a></h2>
+          <dl class="m-doc">
+            <dt id="reference_parent_data">
+              <a href="#reference_parent_data" class="m-doc-self">reference_parent_data</a>: <a href="https://docs.python.org/3/library/typing.html#typing.Tuple" class="m-doc-external">typing.Tuple</a>[<a href="inspect_type_links.first.Foo.html" class="m-doc">first.Foo</a>, <a href="inspect_type_links.first.Foo.html" class="m-doc">first.Foo</a>] = {}
+            </dt>
+            <dd></dd>
+            <dt id="reference_self_data">
+              <a href="#reference_self_data" class="m-doc-self">reference_self_data</a>: <a href="https://docs.python.org/3/library/typing.html#typing.Tuple" class="m-doc-external">typing.Tuple</a>[<a href="inspect_type_links.first.Foo.Foo.html" class="m-doc">Foo</a>, <a href="inspect_type_links.first.Foo.Foo.html" class="m-doc">Foo</a>] = {}
+            </dt>
+            <dd></dd>
           </dl>
         </section>
       </div>
index aac4c36ea0b91f4f498e99b5fce2bf297f829415..3e0e810d07394314344769532b0604426591f74a 100644 (file)
               <ul>
                 <li><a href="#classes">Classes</a></li>
                 <li><a href="#methods">Methods</a></li>
+                <li><a href="#data">Data</a></li>
               </ul>
             </li>
           </ul>
         </div>
+<p><a class="m-doc" href="inspect_type_links.first.Foo.html">first.Foo</a> and <a class="m-doc" href="inspect_type_links.first.Foo.html">inspect_type_links.first.Foo</a> should
+lead to self; referencing the subclass via <a class="m-doc" href="inspect_type_links.first.Foo.Foo.html">Foo</a>, <a class="m-doc" href="inspect_type_links.first.Foo.Foo.html">Foo.Foo</a>,
+<a class="m-doc" href="inspect_type_links.first.Foo.Foo.html">first.Foo.Foo</a> or <a class="m-doc" href="inspect_type_links.first.Foo.Bar.html">Bar</a>. <a class="m-doc" href="inspect_type_links.second.Foo.html">second.Foo</a> and
+<a class="m-doc" href="inspect_type_links.Bar.html">inspect_type_links.Bar</a> lead to other classes.</p>
+<p>This is consistent with how Python type annotations inside <em>a class</em> are
+interpreted -- see <a class="m-doc" href="inspect_type_links.first.Foo.html#reference_self_data">reference_self_data</a>, <a class="m-doc" href="inspect_type_links.first.Foo.html#reference_inner_data">reference_inner_data</a>
+and <a class="m-doc" href="inspect_type_links.first.Foo.html#reference_inner_other_data">reference_inner_other_data</a>. Inside <em>function definitions</em> the
+rules are different as per <a href="https://docs.python.org/3/reference/executionmodel.html#resolution-of-names">https://docs.python.org/3/reference/executionmodel.html#resolution-of-names</a>:</p>
+<blockquote>
+The scope of names defined in a class block is limited to the class
+block; it does not extend to the code blocks of methods</blockquote>
+<p>This means relative annotations in <a class="m-doc" href="inspect_type_links.first.Foo.html#reference_self">reference_self()</a>,
+<a class="m-doc" href="inspect_type_links.first.Foo.html#reference_inner">reference_inner()</a> and <a class="m-doc" href="inspect_type_links.first.Foo.html#reference_inner_other">reference_inner_other()</a> are parsed
+differently -- but in the documentation, these are shown with the same
+rules as for data themselves.</p>
         <section id="classes">
           <h2><a href="#classes">Classes</a></h2>
           <dl class="m-doc">
+            <dt>class <a href="inspect_type_links.first.Foo.Bar.html" class="m-doc">Bar</a></dt>
+            <dd>Another inner class.</dd>
             <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>
           <dl class="m-doc">
             <dt id="reference_inner">
               <span class="m-doc-wrap-bumper">def <a href="#reference_inner" class="m-doc-self">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>
+              a: <a href="inspect_type_links.first.Foo.Foo.html" class="m-doc">Foo</a>,
+              b: <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>
+            <dd>Referencing an inner class using Foo.Foo and first.Foo.Foo. Outside of a function it would be enough to reference via Foo, thus docs display just Foo.</dd>
+            <dt id="reference_inner_other">
+              <span class="m-doc-wrap-bumper">def <a href="#reference_inner_other" class="m-doc-self">reference_inner_other</a>(</span><span class="m-doc-wrap">self,
+              a: <a href="inspect_type_links.first.Foo.Bar.html" class="m-doc">Bar</a>)</span>
+            </dt>
+            <dd>Referencing another inner class using Foo.Bar. Bar alone doesn&#x27;t work, outside of a function it would, thus docs display just Bar.</dd>
             <dt id="reference_other">
               <span class="m-doc-wrap-bumper">def <a href="#reference_other" class="m-doc-self">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>
+              a: <a href="inspect_type_links.second.Foo.html" class="m-doc">second.Foo</a>,
+              b: <a href="inspect_type_links.Bar.html" class="m-doc">inspect_type_links.Bar</a>)</span>
+            </dt>
+            <dd>Referencing a type in another module using second.Foo or inspect_type_links.Bar.</dd>
+            <dt id="reference_parent">
+              <span class="m-doc-wrap-bumper">def <a href="#reference_parent" class="m-doc-self">reference_parent</a>(</span><span class="m-doc-wrap">self,
+              a: <a href="inspect_type_links.Foo.html" class="m-doc">inspect_type_links.Foo</a>)</span>
             </dt>
-            <dd>A method referencing a type in another module</dd>
+            <dd>Referencing a class in a parent module using inspect_type_links.Foo.</dd>
             <dt id="reference_self">
               <span class="m-doc-wrap-bumper">def <a href="#reference_self" class="m-doc-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>
+              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>Referencing its wrapper class using Foo and first.Foo. Outside of a function Foo would reference the inner, thus docs display first.Foo to disambiguate.</dd>
+          </dl>
+        </section>
+        <section id="data">
+          <h2><a href="#data">Data</a></h2>
+          <dl class="m-doc">
+            <dt id="reference_inner_data">
+              <a href="#reference_inner_data" class="m-doc-self">reference_inner_data</a>: <a href="https://docs.python.org/3/library/typing.html#typing.Tuple" class="m-doc-external">typing.Tuple</a>[<a href="inspect_type_links.first.Foo.Foo.html" class="m-doc">Foo</a>, <a href="inspect_type_links.first.Foo.Foo.html" class="m-doc">Foo</a>, <a href="inspect_type_links.first.Foo.Foo.html" class="m-doc">Foo</a>] = {}
+            </dt>
+            <dd>Referencing the inner class using <code>Foo</code>, <code>Foo.Foo</code> or
+<code>first.Foo.Foo</code>, displayed as just <code>Foo</code>. This is different from
+<a class="m-doc" href="inspect_type_links.first.Foo.html#reference_inner">reference_inner()</a>.</dd>
+            <dt id="reference_inner_other_data">
+              <a href="#reference_inner_other_data" class="m-doc-self">reference_inner_other_data</a>: <a href="https://docs.python.org/3/library/typing.html#typing.Tuple" class="m-doc-external">typing.Tuple</a>[<a href="inspect_type_links.first.Foo.Bar.html" class="m-doc">Bar</a>, <a href="inspect_type_links.first.Foo.Bar.html" class="m-doc">Bar</a>, <a href="inspect_type_links.first.Foo.Bar.html" class="m-doc">Bar</a>] = {}
             </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 id="reference_sub">
-              <span class="m-doc-wrap-bumper">def <a href="#reference_sub" class="m-doc-self">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>
+            <dd>Referencing another inner class using <code>Bar</code>, <code>Foo.Bar</code> or
+<code>first.Foo.Bar</code>, displayed as just <code>Bar</code>. This is different from
+<a class="m-doc" href="inspect_type_links.first.Foo.html#reference_inner_other">reference_inner_other()</a>.</dd>
+            <dt id="reference_self_data">
+              <a href="#reference_self_data" class="m-doc-self">reference_self_data</a>: <a href="https://docs.python.org/3/library/typing.html#typing.Tuple" class="m-doc-external">typing.Tuple</a>[<a href="inspect_type_links.first.Foo.html" class="m-doc">first.Foo</a>] = {}
             </dt>
-            <dd>A method referencing a type in a submodule</dd>
+            <dd>Referencing its wrapper class using <code>first.Foo</code>, displayed
+as <code>first.Foo</code> as well. <code>Foo</code> alone would reference the inner. This
+is different from <a class="m-doc" href="inspect_type_links.first.Foo.html#reference_self">reference_self()</a>.</dd>
           </dl>
         </section>
       </div>
index 39bf563f30d9f51cfb57b521684525f5609fca5f..4ad7b7b503296c027b0d79a21bc6e6054483b751 100644 (file)
@@ -36,6 +36,8 @@
             </li>
           </ul>
         </div>
+<p><a class="m-doc" href="inspect_type_links.first.Foo.html">Foo</a>, <a class="m-doc" href="inspect_type_links.first.Foo.html">first.Foo</a> and <a class="m-doc" href="inspect_type_links.first.Foo.html">inspect_type_links.first.Foo</a> should
+lead to the same class.</p>
         <section id="namespaces">
           <h2><a href="#namespaces">Modules</a></h2>
           <dl class="m-doc">
index 43f9dfcd9161be7f69b1977087414319f2d33623..69fbb74f4114531dd632f2e27691f078a5aad67f 100644 (file)
@@ -34,6 +34,8 @@
             </li>
           </ul>
         </div>
+<p>Referencing self as <a class="m-doc" href="inspect_type_links.first.sub.Foo.html">Foo</a> or <a class="m-doc" href="inspect_type_links.first.sub.Foo.html">sub.Foo</a>, parent as
+<a class="m-doc" href="inspect_type_links.first.Foo.html">first.Foo</a>, other as <a class="m-doc" href="inspect_type_links.second.Foo.html">second.Foo</a>.</p>
         <section id="methods">
           <h2><a href="#methods">Methods</a></h2>
           <dl class="m-doc">
index 35e9047aa7307f9ff2a7d68d57d189ecb338ef17..09fe0bc317f05c3222c55f47a5227eac059bb039 100644 (file)
@@ -1 +1,7 @@
 from . import first, second
+
+class Foo:
+    """A class in the root module"""
+
+class Bar:
+    """Another class in the root module"""
index b5a52c71f814545a4ac76e493fc0c62057c1de8f..4a83acbf44b018608a33e25638a0636fa39bb4ca 100644 (file)
@@ -1,28 +1,59 @@
 """First module"""
 
+from typing import Tuple
+
+import inspect_type_links
 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: '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_self(self, a: 'Foo', b: 'first.Foo'):
+        """Referencing its wrapper class using Foo and first.Foo. Outside of a function Foo would reference the inner, thus docs display first.Foo to disambiguate."""
+
+        assert Foo is first.Foo
+
+    def reference_inner(self, a: 'Foo.Foo', b: 'first.Foo.Foo'):
+        """Referencing an inner class using Foo.Foo and first.Foo.Foo. Outside of a function it would be enough to reference via Foo, thus docs display just Foo."""
+
+        assert Foo.Foo is first.Foo.Foo
 
-    def reference_inner(self, a: '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_inner_other(self, a: 'Foo.Bar'):
+        """Referencing another inner class using Foo.Bar. Bar alone doesn't work, outside of a function it would, thus docs display just Bar."""
 
-    def reference_other(self, a: second.Foo):
-        """A method referencing a type in another module"""
+        assert Foo.Bar is first.Foo.Bar
+
+    def reference_parent(self, a: 'inspect_type_links.Foo'):
+        """Referencing a class in a parent module using inspect_type_links.Foo."""
+
+    def reference_other(self, a: second.Foo, b: 'inspect_type_links.Bar'):
+        """Referencing a type in another module using second.Foo or inspect_type_links.Bar."""
 
     class Foo:
         """An inner class in the first module"""
 
-        def reference_self(self, a: 'first.Foo.Foo'):
-            """A method referencing its wrapper class"""
+        def reference_self(self, a: 'Foo.Foo', b: 'first.Foo.Foo'):
+            """A method referencing its wrapper class using Foo.Foo or first.Foo.Foo, displayed as Foo in both cases; however using just Foo in the annotation references the parent?!"""
+
+            assert Foo.Foo is first.Foo.Foo
+
+        def reference_parent(self, a: 'Foo', b: 'first.Foo'):
+            """A method referencing its parent wrapper class using first.Foo. Foo works too, though. Weird. Displayed as first.Foo."""
 
-        def reference_parent(self, a: 'first.Foo'):
-            """A method referencing its parent wrapper class"""
+            assert Foo is first.Foo
+
+        reference_self_data: Tuple['Foo.Foo', 'first.Foo.Foo'] = {}
+        reference_parent_data: Tuple['Foo', 'first.Foo'] = {}
+
+    class Bar:
+        """Another inner class."""
+
+    reference_self_data: Tuple['first.Foo'] = {}
+    reference_inner_data: Tuple[Foo, 'Foo.Foo', 'first.Foo.Foo'] = {}
+    reference_inner_other_data: Tuple[Bar, 'Foo.Bar', 'first.Foo.Bar'] = {}
+
+    #assert Foo is first.Foo.Foo
 
 def reference_self(a: Foo, b: first.Foo):
     """A function referencing a type in this module"""
@@ -32,10 +63,12 @@ def reference_other(a: second.Foo):
 
 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"""
+
+# Asserting on our assumptions
+Foo().reference_self(None, None)
+Foo().reference_inner(None, None)
+Foo().reference_inner_other(None)
+Foo.Foo().reference_self(None, None)
+Foo.Foo().reference_parent(None, None)
index 501b9de38c5f4cabddb7458dc4dcc78fa33eada4..92b8f7373bb44ed7b3eb7beb7bf5b6e688a74beb 100644 (file)
@@ -159,6 +159,7 @@ class TypeLinks(BaseInspectTestCase):
     def test(self):
         self.run_python({
             'PLUGINS': ['m.sphinx'],
+            'INPUT_DOCS': ['docs.rst'],
             'INPUT_PAGES': ['index.rst'],
             'M_SPHINX_INVENTORIES': [
                 ('../../../doc/documentation/python.inv', 'https://docs.python.org/3/', [], ['m-doc-external'])]
index 74a700332846420e222b30666659e59b92b5b5cf..310fa82f33ca80842e2653b973e078c5b4936ba2 100644 (file)
@@ -38,6 +38,7 @@ from docutils.parsers.rst.states import Inliner
 
 from pelican import signals
 
+referer_path = []
 module_doc_output = None
 class_doc_output = None
 enum_doc_output = None
@@ -217,9 +218,12 @@ def ref(name, rawtext, text, lineno, inliner: Inliner, options={}, content=[]):
     # Avoid assert on adding to undefined member later
     if 'classes' not in _options: _options['classes'] = []
 
-    # Iterate through all prefixes, try to find the name
+    # Add prefixes of the referer path to the global prefix list, iterate
+    # through all of them, with names "closest" to the referer having a
+    # priority and try to find the name
     global intersphinx_inventory, intersphinx_name_prefixes
-    for prefix in intersphinx_name_prefixes:
+    prefixes = ['.'.join(referer_path[:len(referer_path) - i]) + '.' for i, _ in enumerate(referer_path)] + intersphinx_name_prefixes
+    for prefix in prefixes:
         found = None
 
         # If the target is prefixed with a type, try looking up that type
@@ -285,6 +289,10 @@ def ref(name, rawtext, text, lineno, inliner: Inliner, options={}, content=[]):
         node = nodes.literal(rawtext, target, **_options)
     return [node], []
 
+def remember_referer_path(path):
+    global referer_path
+    referer_path = path
+
 def merge_inventories(name_map, **kwargs):
     global intersphinx_inventory
 
@@ -353,7 +361,7 @@ def merge_inventories(name_map, **kwargs):
             assert path not in data
             data[path] = value
 
-def register_mcss(mcss_settings, module_doc_contents, class_doc_contents, enum_doc_contents, function_doc_contents, property_doc_contents, data_doc_contents, hooks_post_crawl, **kwargs):
+def register_mcss(mcss_settings, module_doc_contents, class_doc_contents, enum_doc_contents, function_doc_contents, property_doc_contents, data_doc_contents, hooks_post_crawl, hooks_pre_page, **kwargs):
     global module_doc_output, class_doc_output, enum_doc_output, function_doc_output, property_doc_output, data_doc_output
     module_doc_output = module_doc_contents
     class_doc_output = class_doc_contents
@@ -374,6 +382,7 @@ def register_mcss(mcss_settings, module_doc_contents, class_doc_contents, enum_d
 
     rst.roles.register_local_role('ref', ref)
 
+    hooks_pre_page += [remember_referer_path]
     hooks_post_crawl += [merge_inventories]
 
 def _pelican_configure(pelicanobj):