chiark / gitweb /
documentation/python: expose also plain relative types to the template.
authorVladimír Vondruš <mosra@centrum.cz>
Tue, 24 Sep 2024 16:19:46 +0000 (18:19 +0200)
committerVladimír Vondruš <mosra@centrum.cz>
Sat, 28 Sep 2024 01:44:25 +0000 (03:44 +0200)
Not just type links, but also plain types with redundant name prefixes
removed, for use in contexts where a link doesn't make sense, such as
tooltips. They're HTML escaped like the other variants, but right now not
used in the template at all.

The only breaking change is that what was `param.default` / `data.value`
is now in `param.default_link` / `data.value_link`. Those are different
from `param.default` / `data.value` only if the type is a (known) enum,
in which case the link is to the enum value, other than that these two
are the same.

doc/documentation/python.rst
documentation/python.py
documentation/templates/python/details-function.html
documentation/templates/python/entry-data.html
documentation/templates/python/entry-function.html
documentation/test_python/test_pybind.py

index 40c8ce715b02dcab1117b052c1643777ade1ad5a..55126328413dca13360d8ad137a47b24ded87f27 100644 (file)
@@ -1263,10 +1263,15 @@ Property                                Description
 :py:`enum.id`                           Enum ID [5]_
 :py:`enum.summary`                      Doc summary
 :py:`enum.content`                      Detailed documentation, if any
-:py:`enum.base`                         Base class from which the enum is
-                                        derived. Set to :py:`None` if no base
-                                        class information is available.
-:py:`enum.base_link`                    Like :py:`enum.base`, but with
+:py:`enum.base`                         Fully qualified name of a base class
+                                        from which the enum is derived. Set to
+                                        :py:`None` if no base class information
+                                        is available.
+:py:`enum.base_relative`                Like :py:`enum.base`, but relative,
+                                        i.e. with a common prefix of the base
+                                        class and containing module / class
+                                        omitted
+:py:`enum.base_link`                    Like :py:`enum.base_relative`, but with
                                         cross-linked types
 :py:`enum.values`                       List of enum values
 :py:`enum.has_details`                  If there is enough content for the full
@@ -1301,8 +1306,12 @@ Property                            Description
 :py:`function.id`                   Function ID [5]_
 :py:`function.summary`              Doc summary
 :py:`function.content`              Detailed documentation, if any
-:py:`function.type`                 Function return type annotation [2]_
-:py:`function.type_link`            Like :py:`function.type`, but with
+:py:`function.type`                 Fully qualified function return type
+                                    annotation [2]_
+:py:`function.type_relative`        Like :py:`function.type`, but relative,
+                                    i.e. with a common prefix of the type and
+                                    containing module / class omitted
+:py:`function.type_link`            Like :py:`function.type_relative`, but with
                                     cross-linked types
 :py:`function.params`               List of function parameters. See below for
                                     details.
@@ -1332,18 +1341,30 @@ description. Each item has the following properties:
 
 .. class:: m-table m-fullwidth
 
-=========================== ===================================================
-Property                    Description
-=========================== ===================================================
-:py:`param.name`            Parameter name
-:py:`param.type`            Parameter type annotation [2]_
-:py:`param.type_link`       Like :py:`param.type`, but with cross-linked types
-:py:`param.default`         Default parameter value, if any
-:py:`param.kind`            Parameter kind, a string equivalent to one of the
-                            `inspect.Parameter.kind <https://docs.python.org/3/library/inspect.html#inspect.Parameter.kind>`_
-                            values
-:py:`param.content`         Detailed documentation, if any
-=========================== ===================================================
+=============================== ===============================================
+Property                        Description
+=============================== ===============================================
+:py:`param.name`                Parameter name
+:py:`param.type`                Fully qualified parameter type annotation [2]_
+:py:`param.type_relative`       Like :py:`param.type`, but relative, i.e. with
+                                a common prefix of the type and containing
+                                module / class omitted
+:py:`param.type_link`           Like :py:`param.type_relative`, but with
+                                cross-linked types
+:py:`param.default`             Default parameter value, if any. If
+                                :py:`param.type` is an enum, is a
+                                fully-qualified enum value.
+:py:`param.default_relative`    Like :py:`param.default`, but relative, i.e.
+                                with a common prefix of the type and containing
+                                module / class omitted if :py:`param.type` is
+                                an enum
+:py:`param.default_link`        Like :py:`param.default_relative`, but with
+                                cross-linked types
+:py:`param.kind`                Parameter kind, a string equivalent to one of
+                                the `inspect.Parameter.kind <https://docs.python.org/3/library/inspect.html#inspect.Parameter.kind>`_
+                                values
+:py:`param.content`             Detailed documentation, if any
+=============================== ===============================================
 
 In some cases (for example in case of native APIs), the parameters can't be
 introspected. In that case, the parameter list is a single entry with ``name``
@@ -1354,59 +1375,69 @@ Each item has the following properties:
 
 .. class:: m-table m-fullwidth
 
-=========================== ===================================================
-Property                    Description
-=========================== ===================================================
-:py:`exception.type`        Exception type
-:py:`exception.type_link`   Like :py:`exception`, but with a cross-linked type
-:py:`exception.content`     Detailed documentation
-=========================== ===================================================
+=============================== ===============================================
+Property                        Description
+=============================== ===============================================
+:py:`exception.type`            Fully qualified exception type
+:py:`exception.type_relative`   Like :py:`exception.type`, but relative, i.e.
+                                with a common prefix of the type and containing
+                                module / class omitted
+:py:`exception.type_link`       Like :py:`exception.type_relative`, but with a
+                                cross-linked type
+:py:`exception.content`         Detailed documentation
+=============================== ===============================================
 
 `Property properties`_
 ``````````````````````
 
 .. class:: m-table m-fullwidth
 
-=================================== ===========================================
-Property                            Description
-=================================== ===========================================
-:py:`property.name`                 Property name
-:py:`property.id`                   Property ID [5]_
-:py:`property.type`                 Property getter return type annotation [2]_
-:py:`property.type_link`            Like :py:`property.type`, but with
-                                    cross-linked types
-:py:`property.summary`              Doc summary
-:py:`property.content`              Detailed documentation, if any
-:py:`property.exceptions`           List of exceptions raised when accessing
-                                    this property. Same as
-                                    :py:`function.exceptions` described in
-                                    `function properties`_.
-:py:`property.is_gettable`          If the property is gettable
-:py:`property.is_settable`          If the property is settable
-:py:`property.is_deletable`         If the property is deletable with :py:`del`
-:py:`property.has_details`          If there is enough content for the full
-                                    description block [3]_
-=================================== ===========================================
+=============================== ===============================================
+Property                        Description
+=============================== ===============================================
+:py:`property.name`             Property name
+:py:`property.id`               Property ID [5]_
+:py:`property.type`             Fully qualified property getter return type
+                                annotation [2]_
+:py:`property.type_relative`    Like :py:`property.type`, but relative, i.e.
+                                with a common prefix of the type and containing
+                                module / class omitted
+:py:`property.type_link`        Like :py:`property.type_relative`, but with
+                                cross-linked types
+:py:`property.summary`          Doc summary
+:py:`property.content`          Detailed documentation, if any
+:py:`property.exceptions`       List of exceptions raised when accessing this
+                                property. Same as :py:`function.exceptions`
+                                described in `function properties`_.
+:py:`property.is_gettable`      If the property is gettable
+:py:`property.is_settable`      If the property is settable
+:py:`property.is_deletable`     If the property is deletable with :py:`del`
+:py:`property.has_details`      If there is enough content for the full
+                                description block [3]_
+=============================== ===============================================
 
 `Data properties`_
 ``````````````````
 
 .. class:: m-table m-fullwidth
 
-=================================== ===========================================
-Property                            Description
-=================================== ===========================================
-:py:`data.name`                     Data name
-:py:`data.id`                       Data ID [5]_
-:py:`data.type`                     Data type
-:py:`data.type_link`                Like :py:`data.type_link`, but with
-                                    cross-linked types
-:py:`data.summary`                  Doc summary
-:py:`data.content`                  Detailed documentation, if any
-:py:`data.value`                    Data value representation
-:py:`data.has_details`              If there is enough content for the full
-                                    description block [3]_
-=================================== ===========================================
+=============================== ===============================================
+Property                        Description
+=============================== ===============================================
+:py:`data.name`                 Data name
+:py:`data.id`                   Data ID [5]_
+:py:`data.type`                 Fully qualified data type annotation [2]_
+:py:`data.type_relative`        Like :py:`data.type`, but relative, i.e. with a
+                                common prefix of the type and containing module
+                                / class omitted
+:py:`data.type_link`            Like :py:`data.type_relative`, but with
+                                cross-linked types
+:py:`data.summary`              Doc summary
+:py:`data.content`              Detailed documentation, if any
+:py:`data.value`                Data value representation
+:py:`data.has_details`          If there is enough content for the full
+                                description block [3]_
+=============================== ===============================================
 
 `Index page templates`_
 -----------------------
@@ -1458,8 +1489,9 @@ Module/class list is ordered in a way that all modules are before all classes.
 
 -------------------------------
 
-.. [2] :py:`i.type` is extracted out of function annotation. If the types
-    aren't annotated, the annotation is empty.
+.. [2] :py:`i.type` is extracted out of function parameter type, function
+    return type, property type and data type annotation. If the types aren't
+    annotated, the annotation is empty.
 .. [3] :py:`page.has_*_details` and :py:`i.has_details` are :py:`True` if
     there is detailed description, function parameter documentation or
     *documented* enum value listing that makes it worth to render the full
index 088ed138bfca4e045442031af2736fa9bbb378d2..34302d98995be07dc57e375ee9fafe75d1f6e271 100755 (executable)
@@ -836,7 +836,7 @@ def make_relative_name(state: State, referrer_path: List[str], name):
 
     return '.'.join(shortened_path)
 
-def make_name_link(state: State, referrer_path: List[str], name) -> str:
+def make_name_relative_link(state: State, referrer_path: List[str], name) -> Tuple[str, str]:
     assert isinstance(name, str)
 
     # Not found, return as-is. However, if the prefix is one of the
@@ -851,13 +851,13 @@ def make_name_link(state: State, referrer_path: List[str], name) -> str:
             if name.startswith(module_name + '.'):
                 logging.warning("could not resolve a link to %s which is among INPUT_MODULES (referred from %s), possibly hidden/undocumented?", name, '.'.join(referrer_path))
                 break
-        return name
+        return name, name
 
     # Make a shorter name that's relative to the referrer but still unambiguous
     relative_name = make_relative_name(state, referrer_path, name)
 
     entry = state.name_map[name]
-    return '<a href="{}" class="{}">{}</a>'.format(entry.url, ' '.join(entry.css_classes), relative_name)
+    return relative_name, '<a href="{}" class="{}">{}</a>'.format(entry.url, ' '.join(entry.css_classes), relative_name)
 
 _pybind_name_rx = re.compile('[a-zA-Z0-9_]*')
 _pybind_arg_name_rx = re.compile('[/*a-zA-Z0-9_]+')
@@ -924,13 +924,13 @@ def _pybind_map_name_prefix_or_add_typing_suffix(state: State, input_type: str):
     else:
         return map_name_prefix(state, input_type)
 
-def parse_pybind_type(state: State, referrer_path: List[str], signature: str):
+def parse_pybind_type(state: State, referrer_path: List[str], signature: str) -> Tuple[str, str, str, str]:
     match = _pybind_type_rx.match(signature)
     if match:
         input_type = match.group(0)
         signature = signature[len(input_type):]
         type = _pybind_map_name_prefix_or_add_typing_suffix(state, input_type)
-        type_link = make_name_link(state, referrer_path, type)
+        type_relative, type_link = make_name_relative_link(state, referrer_path, type)
     else:
         raise SyntaxError("Cannot match pybind type")
 
@@ -942,6 +942,7 @@ def parse_pybind_type(state: State, referrer_path: List[str], signature: str):
             i += 1
             lvl += 1
             type += c
+            type_relative += c
             type_link += c
             continue
         if lvl == 0:
@@ -950,11 +951,13 @@ def parse_pybind_type(state: State, referrer_path: List[str], signature: str):
             i += 1
             lvl -= 1
             type += c
+            type_relative += c
             type_link += c
             continue
         if c in ', ':
             i += 1
             type += c
+            type_relative += c
             type_link += c
             continue
         match = _pybind_type_rx.match(signature[i:])
@@ -964,16 +967,18 @@ def parse_pybind_type(state: State, referrer_path: List[str], signature: str):
         i += len(input_type)
         input_type = _pybind_map_name_prefix_or_add_typing_suffix(state, input_type)
         type += input_type
-        type_link += make_name_link(state, referrer_path, input_type)
+        input_type_relative, input_type_link = make_name_relative_link(state, referrer_path, input_type)
+        type_relative += input_type_relative
+        type_link += input_type_link
     if lvl != 0:
         raise SyntaxError("Unbalanced [] in python type {}".format(signature))
     signature = signature[i:]
-    return signature, type, type_link
+    return signature, type, type_relative, type_link
 
 # 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]:
+def parse_pybind_signature(state: State, referrer_path: List[str], signature: str) -> Tuple[str, str, List[Tuple[str, str, str, str, str]], str, str, str]:
     original_signature = signature # For error reporting
     name = _pybind_name_rx.match(signature).group(0)
     signature = signature[len(name):]
@@ -994,10 +999,9 @@ def parse_pybind_signature(state: State, referrer_path: List[str], signature: st
             # Type (optional)
             if signature.startswith(': '):
                 signature = signature[2:]
-                signature, arg_type, arg_type_link = parse_pybind_type(state, referrer_path, signature)
+                signature, arg_type, arg_type_relative, arg_type_link = parse_pybind_type(state, referrer_path, signature)
             else:
-                arg_type = None
-                arg_type_link = None
+                arg_type, arg_type_relative, arg_type_link = None, None, None
 
             # Default (optional)
             if signature.startswith(' = '):
@@ -1006,7 +1010,7 @@ def parse_pybind_signature(state: State, referrer_path: List[str], signature: st
             else:
                 default = None
 
-            args += [(arg_name, arg_type, arg_type_link, default)]
+            args += [(arg_name, arg_type, arg_type_relative, arg_type_link, default)]
 
             if signature[0] == ')': break
 
@@ -1020,9 +1024,9 @@ def parse_pybind_signature(state: State, referrer_path: List[str], signature: st
         # Return type (optional)
         if signature.startswith(' -> '):
             signature = signature[4:]
-            signature, return_type, return_type_link = parse_pybind_type(state, referrer_path, signature)
+            signature, return_type, return_type_relative, return_type_link = parse_pybind_type(state, referrer_path, signature)
         else:
-            return_type, return_type_link = None, None
+            return_type, return_type_relative, return_type_link = None, None, None
 
         # Expecting end of the signature line now, if not there, we failed
         if signature and signature[0] != '\n': raise SyntaxError("Expected end of the signature, got `{}`".format(signature))
@@ -1035,14 +1039,14 @@ def parse_pybind_signature(state: State, referrer_path: List[str], signature: st
             docstring = inspect.cleandoc(original_signature[end + 1:])
         else:
             docstring = ''
-        return (name, docstring, [('…', None, None, None)], None, None)
+        return (name, docstring, [('…', None, None, None, None)], None, None, None)
 
     if len(signature) > 1 and signature[1] == '\n':
         docstring = inspect.cleandoc(signature[2:])
     else:
         docstring = ''
 
-    return (name, docstring, args, return_type, return_type_link)
+    return (name, docstring, args, return_type, return_type_relative, 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]
@@ -1078,21 +1082,23 @@ def parse_pybind_docstring(state: State, referrer_path: List[str], doc: str) ->
 
 # Used to format function default arguments and data values. *Not* pybind's
 # function default arguments, as those are parsed from a string representation.
-def format_value(state: State, referrer_path: List[str], value) -> Optional[str]:
+def format_value(state: State, referrer_path: List[str], value) -> Optional[Tuple[str, str, str]]:
     if value is None:
-        return str(value)
+        return str(value), str(value), str(value)
     if isinstance(value, enum.Enum):
-        return make_name_link(state, referrer_path, '{}.{}.{}'.format(value.__class__.__module__, value.__class__.__qualname__, value.name))
+        # TODO Python 3.8+ supports `a, *b`, switch to that once 3.7 is dropped
+        return (value.name, ) + make_name_relative_link(state, referrer_path, '{}.{}.{}'.format(value.__class__.__module__, value.__class__.__qualname__, value.name))
     # pybind enums have the __members__ attribute instead
     elif state.config['PYBIND11_COMPATIBILITY'] and hasattr(value.__class__, '__members__'):
-        return make_name_link(state, referrer_path, '{}.{}.{}'.format(value.__class__.__module__, value.__class__.__qualname__, value.name))
+        # TODO Python 3.8+ supports `a, *b`, switch to that once 3.7 is dropped
+        return (value.name, ) + make_name_relative_link(state, referrer_path, '{}.{}.{}'.format(value.__class__.__module__, value.__class__.__qualname__, value.name))
     elif inspect.isfunction(value):
-        return html.escape('<function {}>'.format(value.__name__))
+        return (html.escape('<function {}>'.format(value.__name__)), )*3
     elif '__repr__' in type(value).__dict__:
         rendered = repr(value)
         # TODO: tuples of non-representable values will still be ugly
         # If the value is too large, return just an ellipsis
-        return html.escape(rendered) if len(rendered) < 128 else '…'
+        return (html.escape(rendered) if len(rendered) < 128 else '…', )*3
     else:
         return None
 
@@ -1232,31 +1238,31 @@ def get_type_hints_or_nothing(state: State, path: List[str], object) -> Dict:
         logging.warning("failed to dereference type hints for %s (%s), falling back to non-dereferenced", '.'.join(path), e.__class__.__name__)
         return {}
 
-def extract_annotation(state: State, referrer_path: List[str], annotation) -> Tuple[str, str]:
+def extract_annotation(state: State, referrer_path: List[str], annotation) -> Tuple[str, str, str]:
     # Empty annotation, as opposed to a None annotation, handled below
     if annotation is inspect.Signature.empty:
-        return None, None
+        return None, None, None
 
     # If dereferencing with typing.get_type_hints() failed, we might end up
     # with forward-referenced types being plain strings. Keep them as is, since
     # those are most probably an error.
     if type(annotation) == str:
-        return annotation, annotation
+        return (annotation, )*3
 
     # Or the plain strings might be inside (e.g. List['Foo']), which gets
     # converted by Python to ForwardRef. Hammer out the actual string and again
     # leave it as-is, since it's most probably an error.
     elif isinstance(annotation, typing.ForwardRef if sys.version_info >= (3, 7) else typing._ForwardRef):
-        return annotation.__forward_arg__, annotation.__forward_arg__
+        return (annotation.__forward_arg__, )*3
 
     # Generic type names -- use their name directly
     elif isinstance(annotation, typing.TypeVar):
-        return annotation.__name__, annotation.__name__
+        return annotation.__name__, annotation.__name__, annotation.__name__
 
     # Ellipsis -- print a literal `...`
     # TODO: any chance to link this to python official docs?
     elif annotation is ...:
-        return '...', '...'
+        return ('...', )*3
 
     # If the annotation is from the typing module, it ... gets complicated. It
     # could be a "bracketed" type, in which case we want to recurse to its
@@ -1301,10 +1307,10 @@ def extract_annotation(state: State, referrer_path: List[str], annotation) -> Tu
         # representation at least.
         else: # pragma: no cover
             logging.warning("can't inspect annotation %s for %s, falling back to a string representation", annotation, '.'.join(referrer_path))
-            return str(annotation), str(annotation)
+            return (str(annotation), )*3
 
         # Add type links to name
-        name_link = make_name_link(state, referrer_path, name)
+        name_relative, name_link = make_name_relative_link(state, referrer_path, name)
 
         # Arguments of generic types, recurse inside
         if args:
@@ -1314,58 +1320,66 @@ def extract_annotation(state: State, referrer_path: List[str], annotation) -> Tu
                 assert len(args) >= 1
 
                 nested_types = []
+                nested_types_relative = []
                 nested_type_links = []
                 for i in args[:-1]:
-                    nested_type, nested_type_link = extract_annotation(state, referrer_path, i)
+                    nested_type, nested_type_relative, nested_type_link = extract_annotation(state, referrer_path, i)
                     nested_types += [nested_type]
+                    nested_types_relative += [nested_type_relative]
                     nested_type_links += [nested_type_link]
-                nested_return_type, nested_return_type_link = extract_annotation(state, referrer_path, args[-1])
+                nested_return_type, nested_return_type_relative, nested_return_type_link = extract_annotation(state, referrer_path, args[-1])
 
                 # If nested parsing failed (the invalid annotation below),
                 # fail the whole thing
                 if None in nested_types or nested_return_type is None:
-                    return None, None
+                    return None, None, None
 
                 return (
                     '{}[[{}], {}]'.format(name, ', '.join(nested_types), nested_return_type),
+                    '{}[[{}], {}]'.format(name, ', '.join(nested_types_relative), nested_return_type_relative),
                     '{}[[{}], {}]'.format(name_link, ', '.join(nested_type_links), nested_return_type_link)
                 )
 
             else:
                 nested_types = []
+                nested_types_relative = []
                 nested_type_links = []
                 for i in args:
-                    nested_type, nested_type_link = extract_annotation(state, referrer_path, i)
+                    nested_type, nested_type_relative, nested_type_link = extract_annotation(state, referrer_path, i)
                     nested_types += [nested_type]
+                    nested_types_relative += [nested_type_relative]
                     nested_type_links += [nested_type_link]
 
                 # If nested parsing failed (the invalid annotation below),
                 # fail the whole thing
                 if None in nested_types:
-                    return None, None
+                    return None, None, None
 
                 return (
                     '{}[{}]'.format(name, ', '.join(nested_types)),
+                    '{}[{}]'.format(name_relative, ', '.join(nested_types_relative)),
                     '{}[{}]'.format(name_link, ', '.join(nested_type_links)),
                 )
 
-        else: return name, name_link
+        else: return name, name_relative, name_link
 
     # Things like (float, int) instead of Tuple[float, int] or using np.array
     # instead of np.ndarray. Ignore with a warning.
     elif not isinstance(annotation, type):
         logging.warning("invalid annotation %s in %s, ignoring", annotation, '.'.join(referrer_path))
-        return None, None
+        return None, None, None
 
     # According to https://www.python.org/dev/peps/pep-0484/#using-none,
     # None and type(None) are equivalent. Calling extract_type() on None would
     # give us NoneType, which is unnecessarily long.
     elif annotation is type(None):
-        return 'None', make_name_link(state, referrer_path, 'None')
+        # TODO Python 3.8+ supports `a, *b`, switch to that once 3.7 is dropped
+        return ('None', ) + make_name_relative_link(state, referrer_path, 'None')
 
     # Otherwise it's a plain type. Turn it into a link.
     name = extract_type(annotation)
-    return name, make_name_link(state, referrer_path, map_name_prefix(state, name))
+    # TODO Python 3.8+ supports `a, *b`, switch to that once 3.7 is dropped
+    return (name, ) + make_name_relative_link(state, referrer_path, map_name_prefix(state, name))
 
 def extract_module_doc(state: State, entry: Empty):
     assert inspect.ismodule(entry.object)
@@ -1420,7 +1434,7 @@ def extract_enum_doc(state: State, entry: Empty):
             docstring = entry.object.__doc__
 
         out.base = extract_type(entry.object.__base__)
-        out.base_link = make_name_link(state, entry.path, out.base)
+        out.base_relative, out.base_link = make_name_relative_link(state, entry.path, out.base)
 
         for i in entry.object:
             value = Empty()
@@ -1522,13 +1536,13 @@ def extract_function_doc(state: State, parent, entry: Empty) -> List[Any]:
         # already, so check that we have that consistent
         assert (len(funcs) > 1) == (entry.type == EntryType.OVERLOADED_FUNCTION)
         overloads = []
-        for name, summary, args, type, type_link in funcs:
+        for name, summary, args, type, type_relative, type_link in funcs:
             out = Empty()
             out.name = name
             out.params = []
             out.has_complex_params = False
             out.has_details = False
-            out.type, out.type_link = type, type_link
+            out.type, out.type_relative, out.type_link = type, type_relative, type_link
 
             # There's no other way to check staticmethods than to check for
             # self being the name of first parameter :( No support for
@@ -1589,7 +1603,7 @@ def extract_function_doc(state: State, parent, entry: Empty) -> List[Any]:
             param_types = []
             signature = []
             for i, arg in enumerate(args):
-                arg_name, arg_type, arg_type_link, arg_default = arg
+                arg_name, arg_type, arg_type_relative, arg_type_link, arg_default = arg
                 param = Empty()
                 param.name = arg_name
                 param_names += [arg_name]
@@ -1606,11 +1620,11 @@ def extract_function_doc(state: State, parent, entry: Empty) -> List[Any]:
 
                 # Don't include redundant type for the self argument
                 if i == 0 and arg_name == 'self':
-                    param.type, param.type_link = None, None
+                    param.type, param.type_relative, param.type_link = None, None, None
                     param_types += [None]
                     signature += ['self']
                 else:
-                    param.type, param.type_link = arg_type, arg_type_link
+                    param.type, param.type_relative, param.type_link = arg_type, arg_type_relative, arg_type_link
                     param_types += [arg_type]
                     signature += ['{}: {}'.format(arg_name, arg_type)]
 
@@ -1621,11 +1635,13 @@ def extract_function_doc(state: State, parent, entry: Empty) -> List[Any]:
                     # fully qualified), concatenate it together to have
                     # `module.EnumType.VALUE`
                     if arg_type in state.name_map and state.name_map[arg_type].type == EntryType.ENUM:
-                        param.default = make_name_link(state, entry.path, '.'.join(state.name_map[arg_type].path[:-1] + [arg_default]))
+                        param.default = '.'.join(state.name_map[arg_type].path[:-1] + [arg_default])
+                        param.default_relative, param.default_link = make_name_relative_link(state, entry.path, param.default)
                     else:
                         param.default = html.escape(arg_default)
+                        param.default_relative, param.default_link = param.default, param.default
                 else:
-                    param.default = None
+                    param.default, param.default_relative, param.default_link = None, None, None
                 if arg_type or arg_default: out.has_complex_params = True
 
                 # *args / **kwargs can still appear in the parsed signatures if
@@ -1687,24 +1703,24 @@ def extract_function_doc(state: State, parent, entry: Empty) -> List[Any]:
             signature = inspect.signature(entry.object)
 
             if 'return' in type_hints:
-                out.type, out.type_link = extract_annotation(state, entry.path, type_hints['return'])
+                out.type, out.type_relative, out.type_link = extract_annotation(state, entry.path, type_hints['return'])
             else:
-                out.type, out.type_link = extract_annotation(state, entry.path, signature.return_annotation)
+                out.type, out.type_relative, out.type_link = extract_annotation(state, entry.path, signature.return_annotation)
             param_names = []
             for i in signature.parameters.values():
                 param = Empty()
                 param.name = i.name
                 param_names += [i.name]
                 if i.name in type_hints:
-                    param.type, param.type_link = extract_annotation(state, entry.path, type_hints[i.name])
+                    param.type, param.type_relative, param.type_link = extract_annotation(state, entry.path, type_hints[i.name])
                 else:
-                    param.type, param.type_link = extract_annotation(state, entry.path, i.annotation)
+                    param.type, param.type_relative, param.type_link = extract_annotation(state, entry.path, i.annotation)
                 if param.type:
                     out.has_complex_params = True
                 if i.default is inspect.Signature.empty:
-                    param.default = None
+                    param.default, param.default_relative, param.default_link = None, None, None
                 else:
-                    param.default = format_value(state, entry.path, i.default) or '…'
+                    param.default, param.default_relative, param.default_link = format_value(state, entry.path, i.default) or ('…', )*3
                     out.has_complex_params = True
                 param.kind = str(i.kind)
                 out.params += [param]
@@ -1715,10 +1731,10 @@ def extract_function_doc(state: State, parent, entry: Empty) -> List[Any]:
         except ValueError:
             param = Empty()
             param.name = '...'
-            param.type, param.type_link = None, None
-            param.default = None
+            param.type, param.type_relative, param.type_link = None, None, None
+            param.default, param.default_relative, param.default_link = None, None, None
             out.params = [param]
-            out.type, out.type_link = None, None
+            out.type, out.type_relative, out.type_link = None, None, None
             param_names = []
 
         # Call all scope enter hooks
@@ -1785,7 +1801,7 @@ def extract_function_doc(state: State, parent, entry: Empty) -> List[Any]:
                 for type_, content in function_docs['raise']:
                     exception = Empty()
                     exception.type = type_
-                    exception.type_link = make_name_link(state, entry.path, type_)
+                    exception.type_relative, exception.type_link = make_name_relative_link(state, entry.path, type_)
                     exception.content = render_inline_rst(state, content)
                     out.exceptions += [exception]
                 out.has_details = True
@@ -1814,7 +1830,7 @@ def extract_function_doc(state: State, parent, entry: Empty) -> List[Any]:
             if len(overloads) != 1:
                 for i in range(len(out.params)):
                     param = out.params[i]
-                    result.params += ['{}: {}'.format(param.name, make_relative_name(state, entry.path, param.type)) if param.type else param.name]
+                    result.params += ['{}: {}'.format(param.name, param.type_relative) if param.type_relative else param.name]
             state.search += [result]
 
     return overloads
@@ -1837,7 +1853,7 @@ def extract_property_doc(state: State, parent, entry: Empty):
         out.is_gettable = True
         out.is_settable = True
         out.is_deletable = True
-        out.type, out.type_link = extract_annotation(state, entry.path, entry.object.type)
+        out.type, out.type_relative, out.type_link = extract_annotation(state, entry.path, entry.object.type)
 
     # If this is a slot, there won't be any fget / fset / fdel. Assume they're
     # gettable and settable (couldn't find any way to make them *inspectably*
@@ -1860,11 +1876,11 @@ def extract_property_doc(state: State, parent, entry: Empty):
         type_hints = get_type_hints_or_nothing(state, entry.path, parent)
 
         if out.name in type_hints:
-            out.type, out.type_link = extract_annotation(state, entry.path, type_hints[out.name])
+            out.type, out.type_relative, out.type_link = extract_annotation(state, entry.path, type_hints[out.name])
         elif hasattr(parent, '__annotations__') and out.name in parent.__annotations__:
-            out.type, out.type_link = extract_annotation(state, entry.path, parent.__annotations__[out.name])
+            out.type, out.type_relative, out.type_link = extract_annotation(state, entry.path, parent.__annotations__[out.name])
         else:
-            out.type, out.type_link = None, None
+            out.type, out.type_relative, out.type_link = None, None, None
 
     # The properties can be defined using the low-level descriptor protocol
     # instead of the higher-level property() decorator. That means there's no
@@ -1880,7 +1896,7 @@ def extract_property_doc(state: State, parent, entry: Empty):
         out.is_gettable = True
         out.is_settable = False
         out.is_deletable = False
-        out.type, out.type_link = None, None
+        out.type, out.type_relative, out.type_link = None, None, None
 
     # Otherwise it's a classic property
     else:
@@ -1915,9 +1931,9 @@ def extract_property_doc(state: State, parent, entry: Empty):
                 type_hints = get_type_hints_or_nothing(state, entry.path, entry.object.fget)
 
                 if 'return' in type_hints:
-                    out.type, out.type_link = extract_annotation(state, entry.path, type_hints['return'])
+                    out.type, out.type_relative, out.type_link = extract_annotation(state, entry.path, type_hints['return'])
                 else:
-                    out.type, out.type_link = extract_annotation(state, entry.path, signature.return_annotation)
+                    out.type, out.type_relative, out.type_link = extract_annotation(state, entry.path, signature.return_annotation)
             else:
                 assert entry.object.fset
                 signature = inspect.signature(entry.object.fset)
@@ -1930,25 +1946,25 @@ def extract_property_doc(state: State, parent, entry: Empty):
                 # non-dereferenced version
                 value_parameter = list(signature.parameters.values())[1]
                 if value_parameter.name in type_hints:
-                    out.type, out.type_link = extract_annotation(state, entry.path, type_hints[value_parameter.name])
+                    out.type, out.type_relative, out.type_link = extract_annotation(state, entry.path, type_hints[value_parameter.name])
                 else:
-                    out.type, out.type_link = extract_annotation(state, entry.path, value_parameter.annotation)
+                    out.type, out.type_relative, out.type_link = extract_annotation(state, entry.path, value_parameter.annotation)
 
         except ValueError:
             # pybind11 properties have the type in the docstring
             if state.config['PYBIND11_COMPATIBILITY']:
                 if entry.object.fget:
-                    out.type, out.type_link = parse_pybind_signature(state, entry.path, entry.object.fget.__doc__)[3:]
+                    out.type, out.type_relative, out.type_link = parse_pybind_signature(state, entry.path, entry.object.fget.__doc__)[3:]
                 else:
                     assert entry.object.fset
                     parsed_args = parse_pybind_signature(state, entry.path, entry.object.fset.__doc__)[2]
                     # If argument parsing failed, we're screwed
                     if len(parsed_args) == 1:
-                        out.type, out.type_link = None, None
+                        out.type, out.type_relative, out.type_link = None, None, None
                     else:
-                        out.type, out.type_link = parsed_args[1][1:3]
+                        out.type, out.type_relative, out.type_link = parsed_args[1][1:4]
             else:
-                out.type, out.type_link = None, None
+                out.type, out.type_relative, out.type_link = None, None, None
 
     # Call all scope enter hooks before rendering the docs
     for hook in state.hooks_pre_scope:
@@ -1970,7 +1986,7 @@ def extract_property_doc(state: State, parent, entry: Empty):
         for type_, content in exception_docs:
             exception = Empty()
             exception.type = type_
-            exception.type_link = make_name_link(state, entry.path, type_)
+            exception.type_relative, exception.type_link = make_name_relative_link(state, entry.path, type_)
             exception.content = render_inline_rst(state, content)
             out.exceptions += [exception]
         out.has_details = True
@@ -2009,13 +2025,13 @@ def extract_data_doc(state: State, parent, entry: Empty):
     type_hints = get_type_hints_or_nothing(state, entry.path, parent)
 
     if out.name in type_hints:
-        out.type, out.type_link = extract_annotation(state, entry.path, type_hints[out.name])
+        out.type, out.type_relative, out.type_link = extract_annotation(state, entry.path, type_hints[out.name])
     elif hasattr(parent, '__annotations__') and out.name in parent.__annotations__:
-        out.type, out.type_link = extract_annotation(state, entry.path, parent.__annotations__[out.name])
+        out.type, out.type_relative, out.type_link = extract_annotation(state, entry.path, parent.__annotations__[out.name])
     else:
-        out.type, out.type_link = None, None
+        out.type, out.type_relative, out.type_link = None, None, None
 
-    out.value = format_value(state, entry.path, entry.object)
+    out.value, out.value_relative, out.value_link = format_value(state, entry.path, entry.object) or (None, None, None)
 
     if not state.config['SEARCH_DISABLED']:
         result = Empty()
index 25440508bb901b5f27f554da64d48ff3c3d043c6..c4008218b6d70e761e7f15c772b535d5362bc7f1 100644 (file)
@@ -1,7 +1,7 @@
           <section class="m-doc-details" id="{{ function.id }}"><div>
             <h3>
               {% set j = joiner('\n              ' if function.has_complex_params else ' ') %}
-              <span class="m-doc-wrap-bumper">def {{ prefix }}</span><span class="m-doc-wrap"><span class="m-doc-wrap-bumper"><a href="#{{ function.id }}" class="m-doc-self">{{ function.name }}</a>(</span><span class="m-doc-wrap">{% for param in function.params %}{% if loop.index0 %}{% if function.params[loop.index0 - 1].kind == 'POSITIONAL_OR_KEYWORD' and param.kind == 'KEYWORD_ONLY' %},<span class="m-text m-dim"> *,</span>{% else %},{% endif %}{% endif %}{{ j() }}{% if param.kind == 'VAR_POSITIONAL' %}*{% elif param.kind == 'VAR_KEYWORD' %}**{% endif %}{{ param.name }}{% if param.type_link %}: {{ param.type_link }}{% endif %}{% if param.default %} = {{ param.default }}{% endif %}{% if param.kind == 'POSITIONAL_ONLY' and (loop.last or function.params[loop.index0 + 1].kind != 'POSITIONAL_ONLY') %}<span class="m-text m-dim">, /</span>{% endif %}{% endfor %}){% if function.type_link %} -&gt; {{ function.type_link }}{% endif %}{% if function.is_classmethod %} <span class="m-label m-success">classmethod</span>{% elif function.is_staticmethod %} <span class="m-label m-info">staticmethod</span>{% endif %}</span></span>
+              <span class="m-doc-wrap-bumper">def {{ prefix }}</span><span class="m-doc-wrap"><span class="m-doc-wrap-bumper"><a href="#{{ function.id }}" class="m-doc-self">{{ function.name }}</a>(</span><span class="m-doc-wrap">{% for param in function.params %}{% if loop.index0 %}{% if function.params[loop.index0 - 1].kind == 'POSITIONAL_OR_KEYWORD' and param.kind == 'KEYWORD_ONLY' %},<span class="m-text m-dim"> *,</span>{% else %},{% endif %}{% endif %}{{ j() }}{% if param.kind == 'VAR_POSITIONAL' %}*{% elif param.kind == 'VAR_KEYWORD' %}**{% endif %}{{ param.name }}{% if param.type_link %}: {{ param.type_link }}{% endif %}{% if param.default_link %} = {{ param.default_link }}{% endif %}{% if param.kind == 'POSITIONAL_ONLY' and (loop.last or function.params[loop.index0 + 1].kind != 'POSITIONAL_ONLY') %}<span class="m-text m-dim">, /</span>{% endif %}{% endfor %}){% if function.type_link %} -&gt; {{ function.type_link }}{% endif %}{% if function.is_classmethod %} <span class="m-label m-success">classmethod</span>{% elif function.is_staticmethod %} <span class="m-label m-info">staticmethod</span>{% endif %}</span></span>
             </h3>
             {% if function.summary %}
             <p>{{ function.summary }}</p>
index 49227c7ac4cd94d7169a02866881686dee0dda70..265878290563e2a93ea070e4856a08975f0bab44 100644 (file)
@@ -1,5 +1,5 @@
             <dt{% if not data.has_details %} id="{{ data.id }}"{% endif %}>
-              <a href="#{{ data.id }}" class="m-doc{% if not data.has_details %}-self{% endif %}">{{ data.name }}</a>{% if data.type_link %}: {{ data.type_link }}{% endif %}{% if data.value %} = {{ data.value }}{% endif %}
+              <a href="#{{ data.id }}" class="m-doc{% if not data.has_details %}-self{% endif %}">{{ data.name }}</a>{% if data.type_link %}: {{ data.type_link }}{% endif %}{% if data.value_link %} = {{ data.value_link }}{% endif %}
               {# This has to be here to avoid the newline being eaten #}
 
             </dt>
index 96493ecd80cb6cfe28f0733f8017258f10c55b44..5e7714132648a936f761fa430dfc7cbff6eaf333 100644 (file)
@@ -1,5 +1,5 @@
             <dt{% if not function.has_details %} id="{{ function.id }}"{% endif %}>
               {% set j = joiner('\n              ' if function.has_complex_params else ' ') %}
-              <span class="m-doc-wrap-bumper">def <a href="#{{ function.id }}" class="m-doc{% if not function.has_details %}-self{% endif %}">{{ function.name }}</a>(</span><span class="m-doc-wrap">{% for param in function.params %}{% if loop.index0 %}{% if function.params[loop.index0 - 1].kind == 'POSITIONAL_OR_KEYWORD' and param.kind == 'KEYWORD_ONLY' %},<span class="m-text m-dim"> *,</span>{% else %},{% endif %}{% endif %}{{ j() }}{% if param.kind == 'VAR_POSITIONAL' %}*{% elif param.kind == 'VAR_KEYWORD' %}**{% endif %}{{ param.name }}{% if param.type_link %}: {{ param.type_link }}{% endif %}{% if param.default %} = {{ param.default }}{% endif %}{% if param.kind == 'POSITIONAL_ONLY' and (loop.last or function.params[loop.index0 + 1].kind != 'POSITIONAL_ONLY') %}<span class="m-text m-dim">, /</span>{% endif %}{% endfor %}){% if function.type_link %} -&gt; {{ function.type_link }}{% endif %}</span>
+              <span class="m-doc-wrap-bumper">def <a href="#{{ function.id }}" class="m-doc{% if not function.has_details %}-self{% endif %}">{{ function.name }}</a>(</span><span class="m-doc-wrap">{% for param in function.params %}{% if loop.index0 %}{% if function.params[loop.index0 - 1].kind == 'POSITIONAL_OR_KEYWORD' and param.kind == 'KEYWORD_ONLY' %},<span class="m-text m-dim"> *,</span>{% else %},{% endif %}{% endif %}{{ j() }}{% if param.kind == 'VAR_POSITIONAL' %}*{% elif param.kind == 'VAR_KEYWORD' %}**{% endif %}{{ param.name }}{% if param.type_link %}: {{ param.type_link }}{% endif %}{% if param.default_link %} = {{ param.default_link }}{% endif %}{% if param.kind == 'POSITIONAL_ONLY' and (loop.last or function.params[loop.index0 + 1].kind != 'POSITIONAL_ONLY') %}<span class="m-text m-dim">, /</span>{% endif %}{% endfor %}){% if function.type_link %} -&gt; {{ function.type_link }}{% endif %}</span>
             </dt>
             <dd>{{ function.summary }}</dd>
index 73db6017b6880b5d04e029340ec94320d0d2e4ed..3b581731a5d4a2f8e495110a3e3df2cdbf669d49 100644 (file)
@@ -41,60 +41,60 @@ class Signature(unittest.TestCase):
         self.assertEqual(parse_pybind_signature(self.state, [],
             'foo(a: int, a2: module.Thing) -> module.Thing3'),
             ('foo', '', [
-                ('a', 'int', 'int', None),
-                ('a2', 'module.Thing', 'module.Thing', None),
-            ], 'module.Thing3', 'module.Thing3'))
+                ('a', 'int', 'int', 'int', None),
+                ('a2', 'module.Thing', 'module.Thing', 'module.Thing', None),
+            ], 'module.Thing3', 'module.Thing3', 'module.Thing3'))
 
     def test_newline(self):
         self.assertEqual(parse_pybind_signature(self.state, [],
             'foo(a: int, a2: module.Thing) -> module.Thing3\n'),
             ('foo', '', [
-                ('a', 'int', 'int', None),
-                ('a2', 'module.Thing', 'module.Thing', None),
-            ], 'module.Thing3', 'module.Thing3'))
+                ('a', 'int', 'int', 'int', None),
+                ('a2', 'module.Thing', 'module.Thing', 'module.Thing', None),
+            ], 'module.Thing3', 'module.Thing3', 'module.Thing3'))
 
     def test_docs(self):
         self.assertEqual(parse_pybind_signature(self.state, [],
             'foo(a: int, a2: module.Thing) -> module.Thing3\n\nDocs here!!'),
             ('foo', 'Docs here!!', [
-                ('a', 'int', 'int', None),
-                ('a2', 'module.Thing', 'module.Thing', None),
-            ], 'module.Thing3', 'module.Thing3'))
+                ('a', 'int', 'int', 'int', None),
+                ('a2', 'module.Thing', 'module.Thing', 'module.Thing', None),
+            ], 'module.Thing3', 'module.Thing3', 'module.Thing3'))
 
     def test_no_args(self):
         self.assertEqual(parse_pybind_signature(self.state, [],
             'thingy() -> str'),
-            ('thingy', '', [], 'str', 'str'))
+            ('thingy', '', [], 'str', 'str', 'str'))
 
     def test_no_return(self):
         self.assertEqual(parse_pybind_signature(self.state, [],
             '__init__(self: module.Thing)'),
             ('__init__', '', [
-                ('self', 'module.Thing', 'module.Thing', None),
-            ], None, None))
+                ('self', 'module.Thing', 'module.Thing', 'module.Thing', None),
+            ], None, None, None))
 
     def test_none_return(self):
         self.assertEqual(parse_pybind_signature(self.state, [],
             '__init__(self: module.Thing) -> None'),
             ('__init__', '', [
-                ('self', 'module.Thing', 'module.Thing', None),
-            ], 'None', 'None'))
+                ('self', 'module.Thing', 'module.Thing', 'module.Thing', None),
+            ], 'None', 'None', 'None'))
 
     def test_no_arg_types(self):
         self.assertEqual(parse_pybind_signature(self.state, [],
             'thingy(self, the_other_thing)'),
             ('thingy', '', [
-                ('self', None, None, None),
-                ('the_other_thing', None, None, None),
-            ], None, None))
+                ('self', None, None, None, None),
+                ('the_other_thing', None, None, None, None),
+            ], None, None, None))
 
     def test_none_arg_types(self):
         self.assertEqual(parse_pybind_signature(self.state, [],
             'thingy(self, the_other_thing: Callable[[], None])'),
             ('thingy', '', [
-                ('self', None, None, None),
-                ('the_other_thing', 'typing.Callable[[], None]', 'typing.Callable[[], None]', None),
-            ], None, None))
+                ('self', None, None, None, None),
+                ('the_other_thing', 'typing.Callable[[], None]', 'typing.Callable[[], None]', 'typing.Callable[[], None]', None),
+            ], None, None, None))
 
     def test_square_brackets(self):
         for i in [
@@ -105,9 +105,9 @@ class Signature(unittest.TestCase):
         ]:
             self.assertEqual(parse_pybind_signature(self.state, [], i),
                 ('foo', '', [
-                    ('a', 'tuple[int, str]', 'tuple[int, str]', None),
-                    ('no_really', 'str', 'str', None),
-                ], 'list[str]', 'list[str]'))
+                    ('a', 'tuple[int, str]', 'tuple[int, str]', 'tuple[int, str]', None),
+                    ('no_really', 'str', 'str', 'str', None),
+                ], 'list[str]', 'list[str]', 'list[str]'))
 
     def test_nested_square_brackets(self):
         for i in [
@@ -118,9 +118,9 @@ class Signature(unittest.TestCase):
         ]:
             self.assertEqual(parse_pybind_signature(self.state, [], i),
                 ('foo', '', [
-                    ('a', 'tuple[int, set[tuple[int, int]]]', 'tuple[int, set[tuple[int, int]]]', None),
-                    ('another', 'float', 'float', None),
-                ], 'typing.Union[str, None]', 'typing.Union[str, None]'))
+                    ('a', 'tuple[int, set[tuple[int, int]]]', 'tuple[int, set[tuple[int, int]]]', 'tuple[int, set[tuple[int, int]]]', None),
+                    ('another', 'float', 'float', 'float', None),
+                ], 'typing.Union[str, None]', 'typing.Union[str, None]', 'typing.Union[str, None]'))
 
     def test_callable(self):
         for i in [
@@ -131,43 +131,43 @@ class Signature(unittest.TestCase):
         ]:
             self.assertEqual(parse_pybind_signature(self.state, [], i),
                 ('foo', '', [
-                    ('a', 'typing.Callable[[int, dict[int, int]], float]', 'typing.Callable[[int, dict[int, int]], float]', None),
-                    ('another', 'float', 'float', None),
-                ], None, None))
+                    ('a', 'typing.Callable[[int, dict[int, int]], float]', 'typing.Callable[[int, dict[int, int]], float]', 'typing.Callable[[int, dict[int, int]], float]', None),
+                    ('another', 'float', 'float', 'float', None),
+                ], None, None, None))
 
     def test_kwargs(self):
         self.assertEqual(parse_pybind_signature(self.state, [],
             'foo(*args, **kwargs)'),
             ('foo', '', [
-                ('*args', None, None, None),
-                ('**kwargs', None, None, None),
-            ], None, None))
+                ('*args', None, None, None, None),
+                ('**kwargs', None, None, None, None),
+            ], None, None, None))
 
     def test_keyword_positional_only(self):
         self.assertEqual(parse_pybind_signature(self.state, [],
             'foo(a: int, /, b: float, *, keyword: str)'),
             ('foo', '', [
-                ('a', 'int', 'int', None),
-                ('/', None, None, None),
-                ('b', 'float', 'float', None),
-                ('*', None, None, None),
-                ('keyword', 'str', 'str', None),
-            ], None, None))
+                ('a', 'int', 'int', 'int', None),
+                ('/', None, None, None, None),
+                ('b', 'float', 'float', 'float', None),
+                ('*', None, None, None, None),
+                ('keyword', 'str', 'str', 'str', None),
+            ], None, None, None))
 
     def test_default_values(self):
         self.assertEqual(parse_pybind_signature(self.state, [],
             'foo(a: float = 1.0, b: str = \'hello\')'),
             ('foo', '', [
-                ('a', 'float', 'float', '1.0'),
-                ('b', 'str', 'str', '\'hello\''),
-            ], None, None))
+                ('a', 'float', 'float', 'float', '1.0'),
+                ('b', 'str', 'str', 'str', '\'hello\''),
+            ], None, None, None))
 
         self.assertEqual(parse_pybind_signature(self.state, [],
             'foo(a: float = libA.foo(libB.goo(123), libB.bar + 13) + 2, b = 3)'),
             ('foo', '', [
-                ('a', 'float', 'float', 'libA.foo(libB.goo(123), libB.bar + 13) + 2'),
-                ('b', None, None, '3'),
-            ], None, None))
+                ('a', 'float', 'float', 'float', 'libA.foo(libB.goo(123), libB.bar + 13) + 2'),
+                ('b', None, None, None, '3'),
+            ], None, None, None))
 
         for i in [
             # pybind11 2.11 and older
@@ -177,17 +177,17 @@ class Signature(unittest.TestCase):
         ]:
             self.assertEqual(parse_pybind_signature(self.state, [], i),
                 ('foo', '', [
-                    ('a', 'tuple[int, ...]', 'tuple[int, ...]',
+                    ('a', 'tuple[int, ...]', 'tuple[int, ...]', 'tuple[int, ...]',
                         '(1,("hello", \'world\'),3,4)')
-                ], None, None))
+                ], None, None, None))
 
         self.assertEqual(parse_pybind_signature(self.state, [],
             'foo(a: str = [dict(key="A", value=\'B\')["key"][0], None][0])'),
              ('foo', '', [
-                 ('a', 'str', 'str', '[dict(key="A", value=\'B\')["key"][0], None][0]')
-             ], None, None))
+                 ('a', 'str', 'str', 'str', '[dict(key="A", value=\'B\')["key"][0], None][0]')
+             ], None, None, None))
 
-        bad_signature = ('foo', '', [('…', None, None, None)], None, None)
+        bad_signature = ('foo', '', [('…', None, None, None, None)], None, None, None)
 
         self.assertEqual(parse_pybind_signature(self.state, [], 'foo(a: float = [0][)'), bad_signature)
         self.assertEqual(parse_pybind_signature(self.state, [], 'foo(a: float = ()'), bad_signature)
@@ -204,54 +204,54 @@ class Signature(unittest.TestCase):
         self.assertEqual(parse_pybind_signature(self.state, [],
             'foo(a: bar.Enum = Enum.FOO_BAR)'),
             ('foo', '', [
-                ('a', 'bar.Enum', 'bar.Enum', 'Enum.FOO_BAR')
-            ], None, None))
+                ('a', 'bar.Enum', 'bar.Enum', 'bar.Enum', 'Enum.FOO_BAR')
+            ], None, None, None))
 
         # After the insane change
         self.assertEqual(parse_pybind_signature(self.state, [],
             'foo(a: bar.Enum = <Enum.FOO_BAR: -13376>)'),
             ('foo', '', [
-                ('a', 'bar.Enum', 'bar.Enum', 'Enum.FOO_BAR')
-            ], None, None))
+                ('a', 'bar.Enum', 'bar.Enum', 'bar.Enum', 'Enum.FOO_BAR')
+            ], None, None, None))
 
         # Nested
         self.assertEqual(parse_pybind_signature(self.state, [],
             'foo(a: bar.Enum = (4, [<Enum.FOO_BAR: -13376>], <Enum.FIZZ_PISS: 1>))'),
             ('foo', '', [
-                ('a', 'bar.Enum', 'bar.Enum', '(4, [Enum.FOO_BAR], Enum.FIZZ_PISS)')
-            ], None, None))
+                ('a', 'bar.Enum', 'bar.Enum', 'bar.Enum', '(4, [Enum.FOO_BAR], Enum.FIZZ_PISS)')
+            ], None, None, None))
 
         # This isn't really expected to happen but yeah it still treats it as
         # an enum
         self.assertEqual(parse_pybind_signature(self.state, [],
             'foo(a: Enum = <Enum_MISSING_DOT:>)'),
             ('foo', '', [
-                ('a', 'Enum', 'Enum', 'Enum_MISSING_DOT')
-            ], None, None))
+                ('a', 'Enum', 'Enum', 'Enum', 'Enum_MISSING_DOT')
+            ], None, None, None))
 
         # This is how pybind prints various objects, should be passed as-is.
         # It should not corrupt any parameters after.
         self.assertEqual(parse_pybind_signature(self.state, [],
             'foo(a: FooBar = <FooBar object at 0xabcd>, b: int = 3)'),
             ('foo', '', [
-                ('a', 'FooBar', 'FooBar', '<FooBar object at 0xabcd>'),
-                ('b', 'int', 'int', '3')
-            ], None, None))
+                ('a', 'FooBar', 'FooBar', 'FooBar', '<FooBar object at 0xabcd>'),
+                ('b', 'int', 'int', 'int', '3')
+            ], None, None, None))
 
         # This is weird and so will be passed as-is.
         self.assertEqual(parse_pybind_signature(self.state, [],
             'foo(a: Enum = <Enum_MISSING_GT: -1)'),
             ('foo', '', [
-                ('a', 'Enum', 'Enum', '<Enum_MISSING_GT: -1')
-            ], None, None))
+                ('a', 'Enum', 'Enum', 'Enum', '<Enum_MISSING_GT: -1')
+            ], None, None, None))
 
         # This will fail
-        bad_signature = ('foo', '', [('…', None, None, None)], None, None)
+        bad_signature = ('foo', '', [('…', None, None, None, None)], None, None, None)
         self.assertEqual(parse_pybind_signature(self.state, [], 'foo(a: Enum = <Enum.CHARACTERS_AFTER: 17>a)'), bad_signature)
         self.assertEqual(parse_pybind_signature(self.state, [], 'foo(a: Enum = <Enum.CHARACTERS_AFTER: 89><)'), bad_signature)
 
     def test_bad_return_type(self):
-        bad_signature = ('foo', '', [('…', None, None, None)], None, None)
+        bad_signature = ('foo', '', [('…', None, None, None, None)], None, None, None)
         for i in [
             # pybind11 2.11 and older
             'foo() -> List[[]',
@@ -268,7 +268,7 @@ class Signature(unittest.TestCase):
     def test_crazy_stuff(self):
         self.assertEqual(parse_pybind_signature(self.state, [],
             'foo(a: int, b: Math::Vector<4, UnsignedInt>)'),
-            ('foo', '', [('…', None, None, None)], None, None))
+            ('foo', '', [('…', None, None, None, None)], None, None, None))
 
     def test_crazy_stuff_nested(self):
         for i in [
@@ -278,17 +278,17 @@ class Signature(unittest.TestCase):
             'foo(a: int, b: list[Math::Vector<4, UnsignedInt>])'
         ]:
             self.assertEqual(parse_pybind_signature(self.state, [], i),
-                ('foo', '', [('…', None, None, None)], None, None))
+                ('foo', '', [('…', None, None, None, None)], None, None, None))
 
     def test_crazy_stuff_docs(self):
         self.assertEqual(parse_pybind_signature(self.state, [],
             'foo(a: int, b: Math::Vector<4, UnsignedInt>)\n\nThis is text!!'),
-            ('foo', 'This is text!!', [('…', None, None, None)], None, None))
+            ('foo', 'This is text!!', [('…', None, None, None, None)], None, None, None))
 
     def test_crazy_return(self):
         self.assertEqual(parse_pybind_signature(self.state, [],
             'foo(a: int) -> Math::Vector<4, UnsignedInt>'),
-            ('foo', '', [('…', None, None, None)], None, None))
+            ('foo', '', [('…', None, None, None, None)], None, None, None))
 
     def test_crazy_return_nested(self):
         for i in [
@@ -298,17 +298,17 @@ class Signature(unittest.TestCase):
             'foo(a: int) -> list[Math::Vector<4, UnsignedInt>]'
         ]:
             self.assertEqual(parse_pybind_signature(self.state, [], i),
-                ('foo', '', [('…', None, None, None)], None, None))
+                ('foo', '', [('…', None, None, None, None)], None, None, None))
 
     def test_crazy_return_docs(self):
         self.assertEqual(parse_pybind_signature(self.state, [],
             'foo(a: int) -> Math::Vector<4, UnsignedInt>\n\nThis returns!'),
-            ('foo', 'This returns!', [('…', None, None, None)], None, None))
+            ('foo', 'This returns!', [('…', None, None, None, None)], None, None, None))
 
     def test_no_name(self):
         self.assertEqual(parse_pybind_signature(self.state, [],
             '(arg0: MyClass) -> float'),
-            ('', '', [('arg0', 'MyClass', 'MyClass', None)], 'float', 'float'))
+            ('', '', [('arg0', 'MyClass', 'MyClass', 'MyClass', None)], 'float', 'float', 'float'))
 
     def test_name_mapping(self):
         state = copy.deepcopy(self.state)
@@ -322,9 +322,9 @@ class Signature(unittest.TestCase):
         ]:
             self.assertEqual(parse_pybind_signature(state, [], i),
                 ('foo', '', [
-                    ('a', 'module.Foo', 'module.Foo', None),
-                    ('b', 'tuple[int, module.Bar]', 'tuple[int, module.Bar]', None)
-                ], 'module.Baz', 'module.Baz'))
+                    ('a', 'module.Foo', 'module.Foo', 'module.Foo', None),
+                    ('b', 'tuple[int, module.Bar]', 'tuple[int, module.Bar]', 'tuple[int, module.Bar]', None)
+                ], 'module.Baz', 'module.Baz', 'module.Baz'))
 
 class Signatures(BaseInspectTestCase):
     def test_positional_args(self):