Repository: sphinx-doc/sphinx

Issue:
`python_use_unqualified_type_names = True` ignored in base classes
### Describe the bug

`python_use_unqualified_type_names = True` should print unqualified names if they are documented. However when using the option `:show-inheritance:` with `:autoclass:` names that are documented, and link correctly, still have the fully-qualified name printed.

### How to Reproduce

```
$ git clone https://github.com/ktbarrett/pyhdltypes/tree/sphinx-error
$ cd pyhdltypes
$ nox -e docs
$ # open docs_out/reference.html#logic-array-types
```

See `LogicArrayBase`, `StdLogicArray`, `X01ZArray`, and `BitArray`.

### Expected behavior

I expect the types that are documented to be printed with unqualified names per the intent of the option `python_use_unqualified_type_names`.

### Your project

https://github.com/ktbarrett/pyhdltypes/tree/sphinx-error

### Screenshots

![image](https://user-images.githubusercontent.com/22162772/147613183-cb179eed-26b6-46c5-bf80-c58100c18bb4.png)

All the listed base types are documented and the links work, but the names are fully qualified when they shouldn't be.

### OS

Linux

### Python version

3.9

### Sphinx version

4.3.2

### Sphinx extensions

autodoc

### Extra tools

_No response_

### Additional context

_No response_

Discussion:

Patch:
diff --git a/CHANGES b/CHANGES
index 59f238dda..ffb4c78aa 100644
--- a/CHANGES
+++ b/CHANGES
@@ -26,8 +26,6 @@ Features added
 * #9800: extlinks: Emit warning if a hardcoded link is replaceable
   by an extlink, suggesting a replacement.
 * #9961: html: Support nested <kbd> HTML elements in other HTML builders
-* #10013: html: Allow to change the loading method of JS via ``loading_method``
-  parameter for :meth:`Sphinx.add_js_file()`
 * #9815: html theme: Wrap sidebar components in div to allow customizing their
   layout via CSS
 * #9899: py domain: Allows to specify cross-reference specifier (``.`` and
@@ -52,7 +50,6 @@ Bugs fixed
 * #9968: autodoc: instance variables are not shown if __init__ method has
   position-only-arguments
 * #9194: autodoc: types under the "typing" module are not hyperlinked
-* #10009: autodoc: Crashes if target object raises an error on getting docstring
 * #9947: i18n: topic directive having a bullet list can't be translatable
 * #9878: mathjax: MathJax configuration is placed after loading MathJax itself
 * #9857: Generated RFC links use outdated base url
@@ -62,8 +59,6 @@ Bugs fixed
 * #9944: LaTeX: extra vertical whitespace for some nested declarations
 * #9940: LaTeX: Multi-function declaration in Python domain has cramped
   vertical spacing in latexpdf output
-* #10015: py domain: types under the "typing" module are not hyperlinked defined
-  at info-field-list
 * #9390: texinfo: Do not emit labels inside footnotes
 * #9979: Error level messages were displayed as warning messages
 
diff --git a/sphinx/application.py b/sphinx/application.py
index 7776c54ae..a26c3d659 100644
--- a/sphinx/application.py
+++ b/sphinx/application.py
@@ -930,8 +930,7 @@ def add_post_transform(self, transform: Type[Transform]) -> None:
         """
         self.registry.add_post_transform(transform)
 
-    def add_js_file(self, filename: str, priority: int = 500,
-                    loading_method: Optional[str] = None, **kwargs: Any) -> None:
+    def add_js_file(self, filename: str, priority: int = 500, **kwargs: Any) -> None:
         """Register a JavaScript file to include in the HTML output.
 
         :param filename: The filename of the JavaScript file.  It must be relative to the HTML
@@ -943,8 +942,6 @@ def add_js_file(self, filename: str, priority: int = 500,
                          files" below.  If the priority of the JavaScript files it the same
                          as others, the JavaScript files will be loaded in order of
                          registration.
-        :param loading_method: The loading method of the JavaScript file.  ``'async'`` or
-                               ``'defer'`` is allowed.
         :param kwargs: Extra keyword arguments are included as attributes of the ``<script>``
                        tag.  A special keyword argument ``body`` is given, its value will be
                        added between the ``<script>`` tag.
@@ -954,7 +951,7 @@ def add_js_file(self, filename: str, priority: int = 500,
             app.add_js_file('example.js')
             # => <script src="_static/example.js"></script>
 
-            app.add_js_file('example.js', loading_method="async")
+            app.add_js_file('example.js', async="async")
             # => <script src="_static/example.js" async="async"></script>
 
             app.add_js_file(None, body="var myVariable = 'foo';")
@@ -983,15 +980,7 @@ def add_js_file(self, filename: str, priority: int = 500,
 
         .. versionchanged:: 3.5
            Take priority argument.  Allow to add a JavaScript file to the specific page.
-        .. versionchanged:: 4.4
-           Take loading_method argument.  Allow to change the loading method of the
-           JavaScript file.
         """
-        if loading_method == 'async':
-            kwargs['async'] = 'async'
-        elif loading_method == 'defer':
-            kwargs['defer'] = 'defer'
-
         self.registry.add_js_file(filename, priority=priority, **kwargs)
         if hasattr(self.builder, 'add_js_file'):
             self.builder.add_js_file(filename, priority=priority, **kwargs)  # type: ignore
diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py
index cfe420f39..240db309b 100644
--- a/sphinx/domains/python.py
+++ b/sphinx/domains/python.py
@@ -80,53 +80,45 @@ class ModuleEntry(NamedTuple):
     deprecated: bool
 
 
-def parse_reftarget(reftarget: str, suppress_prefix: bool = False
-                    ) -> Tuple[str, str, str, bool]:
-    """Parse a type string and return (reftype, reftarget, title, refspecific flag)"""
-    refspecific = False
-    if reftarget.startswith('.'):
-        reftarget = reftarget[1:]
-        title = reftarget
-        refspecific = True
-    elif reftarget.startswith('~'):
-        reftarget = reftarget[1:]
-        title = reftarget.split('.')[-1]
-    elif suppress_prefix:
-        title = reftarget.split('.')[-1]
-    elif reftarget.startswith('typing.'):
-        title = reftarget[7:]
-    else:
-        title = reftarget
-
-    if reftarget == 'None' or reftarget.startswith('typing.'):
+def type_to_xref(target: str, env: BuildEnvironment = None, suppress_prefix: bool = False
+                 ) -> addnodes.pending_xref:
+    """Convert a type string to a cross reference node."""
+    if target == 'None' or target.startswith('typing.'):
         # typing module provides non-class types.  Obj reference is good to refer them.
         reftype = 'obj'
     else:
         reftype = 'class'
 
-    return reftype, reftarget, title, refspecific
-
-
-def type_to_xref(target: str, env: BuildEnvironment = None, suppress_prefix: bool = False
-                 ) -> addnodes.pending_xref:
-    """Convert a type string to a cross reference node."""
     if env:
         kwargs = {'py:module': env.ref_context.get('py:module'),
                   'py:class': env.ref_context.get('py:class')}
     else:
         kwargs = {}
 
-    reftype, target, title, refspecific = parse_reftarget(target, suppress_prefix)
+    refspecific = False
+    if target.startswith('.'):
+        target = target[1:]
+        text = target
+        refspecific = True
+    elif target.startswith('~'):
+        target = target[1:]
+        text = target.split('.')[-1]
+    elif suppress_prefix:
+        text = target.split('.')[-1]
+    elif target.startswith('typing.'):
+        text = target[7:]
+    else:
+        text = target
 
     if env.config.python_use_unqualified_type_names:
         # Note: It would be better to use qualname to describe the object to support support
         # nested classes.  But python domain can't access the real python object because this
         # module should work not-dynamically.
-        shortname = title.split('.')[-1]
+        shortname = text.split('.')[-1]
         contnodes: List[Node] = [pending_xref_condition('', shortname, condition='resolved'),
-                                 pending_xref_condition('', title, condition='*')]
+                                 pending_xref_condition('', text, condition='*')]
     else:
-        contnodes = [nodes.Text(title)]
+        contnodes = [nodes.Text(text)]
 
     return pending_xref('', *contnodes,
                         refdomain='py', reftype=reftype, reftarget=target,
@@ -362,27 +354,27 @@ def make_xref(self, rolename: str, domain: str, target: str,
         result = super().make_xref(rolename, domain, target,  # type: ignore
                                    innernode, contnode,
                                    env, inliner=None, location=None)
-        if isinstance(result, pending_xref):
-            result['refspecific'] = True
-            result['py:module'] = env.ref_context.get('py:module')
-            result['py:class'] = env.ref_context.get('py:class')
-
-            reftype, reftarget, reftitle, _ = parse_reftarget(target)
-            if reftarget != reftitle:
-                result['reftype'] = reftype
-                result['reftarget'] = reftarget
-
-                result.clear()
-                result += innernode(reftitle, reftitle)
-            elif env.config.python_use_unqualified_type_names:
-                children = result.children
-                result.clear()
-
-                shortname = target.split('.')[-1]
-                textnode = innernode('', shortname)
-                contnodes = [pending_xref_condition('', '', textnode, condition='resolved'),
-                             pending_xref_condition('', '', *children, condition='*')]
-                result.extend(contnodes)
+        result['refspecific'] = True
+        result['py:module'] = env.ref_context.get('py:module')
+        result['py:class'] = env.ref_context.get('py:class')
+        if target.startswith(('.', '~')):
+            prefix, result['reftarget'] = target[0], target[1:]
+            if prefix == '.':
+                text = target[1:]
+            elif prefix == '~':
+                text = target.split('.')[-1]
+            for node in list(result.traverse(nodes.Text)):
+                node.parent[node.parent.index(node)] = nodes.Text(text)
+                break
+        elif isinstance(result, pending_xref) and env.config.python_use_unqualified_type_names:
+            children = result.children
+            result.clear()
+
+            shortname = target.split('.')[-1]
+            textnode = innernode('', shortname)
+            contnodes = [pending_xref_condition('', '', textnode, condition='resolved'),
+                         pending_xref_condition('', '', *children, condition='*')]
+            result.extend(contnodes)
 
         return result
 
@@ -415,7 +407,16 @@ def make_xrefs(self, rolename: str, domain: str, target: str,
 
 
 class PyField(PyXrefMixin, Field):
-    pass
+    def make_xref(self, rolename: str, domain: str, target: str,
+                  innernode: Type[TextlikeNode] = nodes.emphasis,
+                  contnode: Node = None, env: BuildEnvironment = None,
+                  inliner: Inliner = None, location: Node = None) -> Node:
+        if rolename == 'class' and target == 'None':
+            # None is not a type, so use obj role instead.
+            rolename = 'obj'
+
+        return super().make_xref(rolename, domain, target, innernode, contnode,
+                                 env, inliner, location)
 
 
 class PyGroupedField(PyXrefMixin, GroupedField):
@@ -423,7 +424,16 @@ class PyGroupedField(PyXrefMixin, GroupedField):
 
 
 class PyTypedField(PyXrefMixin, TypedField):
-    pass
+    def make_xref(self, rolename: str, domain: str, target: str,
+                  innernode: Type[TextlikeNode] = nodes.emphasis,
+                  contnode: Node = None, env: BuildEnvironment = None,
+                  inliner: Inliner = None, location: Node = None) -> Node:
+        if rolename == 'class' and target == 'None':
+            # None is not a type, so use obj role instead.
+            rolename = 'obj'
+
+        return super().make_xref(rolename, domain, target, innernode, contnode,
+                                 env, inliner, location)
 
 
 class PyObject(ObjectDescription[Tuple[str, str]]):
diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py
index 5ac15fc36..48f9efb5e 100644
--- a/sphinx/ext/autodoc/__init__.py
+++ b/sphinx/ext/autodoc/__init__.py
@@ -711,111 +711,109 @@ def is_filtered_inherited_member(name: str, obj: Any) -> bool:
 
         # process members and determine which to skip
         for obj in members:
-            try:
-                membername, member = obj
-                # if isattr is True, the member is documented as an attribute
-                if member is INSTANCEATTR:
-                    isattr = True
-                elif (namespace, membername) in attr_docs:
-                    isattr = True
-                else:
-                    isattr = False
-
-                doc = getdoc(member, self.get_attr, self.config.autodoc_inherit_docstrings,
-                             self.object, membername)
-                if not isinstance(doc, str):
-                    # Ignore non-string __doc__
+            membername, member = obj
+            # if isattr is True, the member is documented as an attribute
+            if member is INSTANCEATTR:
+                isattr = True
+            elif (namespace, membername) in attr_docs:
+                isattr = True
+            else:
+                isattr = False
+
+            doc = getdoc(member, self.get_attr, self.config.autodoc_inherit_docstrings,
+                         self.object, membername)
+            if not isinstance(doc, str):
+                # Ignore non-string __doc__
+                doc = None
+
+            # if the member __doc__ is the same as self's __doc__, it's just
+            # inherited and therefore not the member's doc
+            cls = self.get_attr(member, '__class__', None)
+            if cls:
+                cls_doc = self.get_attr(cls, '__doc__', None)
+                if cls_doc == doc:
                     doc = None
 
-                # if the member __doc__ is the same as self's __doc__, it's just
-                # inherited and therefore not the member's doc
-                cls = self.get_attr(member, '__class__', None)
-                if cls:
-                    cls_doc = self.get_attr(cls, '__doc__', None)
-                    if cls_doc == doc:
-                        doc = None
-
-                if isinstance(obj, ObjectMember) and obj.docstring:
-                    # hack for ClassDocumenter to inject docstring via ObjectMember
-                    doc = obj.docstring
-
-                doc, metadata = separate_metadata(doc)
-                has_doc = bool(doc)
-
-                if 'private' in metadata:
-                    # consider a member private if docstring has "private" metadata
-                    isprivate = True
-                elif 'public' in metadata:
-                    # consider a member public if docstring has "public" metadata
-                    isprivate = False
-                else:
-                    isprivate = membername.startswith('_')
+            if isinstance(obj, ObjectMember) and obj.docstring:
+                # hack for ClassDocumenter to inject docstring via ObjectMember
+                doc = obj.docstring
+
+            doc, metadata = separate_metadata(doc)
+            has_doc = bool(doc)
 
+            if 'private' in metadata:
+                # consider a member private if docstring has "private" metadata
+                isprivate = True
+            elif 'public' in metadata:
+                # consider a member public if docstring has "public" metadata
+                isprivate = False
+            else:
+                isprivate = membername.startswith('_')
+
+            keep = False
+            if ismock(member) and (namespace, membername) not in attr_docs:
+                # mocked module or object
+                pass
+            elif self.options.exclude_members and membername in self.options.exclude_members:
+                # remove members given by exclude-members
                 keep = False
-                if ismock(member) and (namespace, membername) not in attr_docs:
-                    # mocked module or object
-                    pass
-                elif (self.options.exclude_members and
-                      membername in self.options.exclude_members):
-                    # remove members given by exclude-members
-                    keep = False
-                elif want_all and special_member_re.match(membername):
-                    # special __methods__
-                    if (self.options.special_members and
-                            membername in self.options.special_members):
-                        if membername == '__doc__':
-                            keep = False
-                        elif is_filtered_inherited_member(membername, obj):
-                            keep = False
-                        else:
-                            keep = has_doc or self.options.undoc_members
-                    else:
+            elif want_all and special_member_re.match(membername):
+                # special __methods__
+                if self.options.special_members and membername in self.options.special_members:
+                    if membername == '__doc__':
+                        keep = False
+                    elif is_filtered_inherited_member(membername, obj):
                         keep = False
-                elif (namespace, membername) in attr_docs:
-                    if want_all and isprivate:
-                        if self.options.private_members is None:
-                            keep = False
-                        else:
-                            keep = membername in self.options.private_members
-                    else:
-                        # keep documented attributes
-                        keep = True
-                elif want_all and isprivate:
-                    if has_doc or self.options.undoc_members:
-                        if self.options.private_members is None:
-                            keep = False
-                        elif is_filtered_inherited_member(membername, obj):
-                            keep = False
-                        else:
-                            keep = membername in self.options.private_members
                     else:
+                        keep = has_doc or self.options.undoc_members
+                else:
+                    keep = False
+            elif (namespace, membername) in attr_docs:
+                if want_all and isprivate:
+                    if self.options.private_members is None:
                         keep = False
+                    else:
+                        keep = membername in self.options.private_members
                 else:
-                    if (self.options.members is ALL and
-                            is_filtered_inherited_member(membername, obj)):
+                    # keep documented attributes
+                    keep = True
+            elif want_all and isprivate:
+                if has_doc or self.options.undoc_members:
+                    if self.options.private_members is None:
+                        keep = False
+                    elif is_filtered_inherited_member(membername, obj):
                         keep = False
                     else:
-                        # ignore undocumented members if :undoc-members: is not given
-                        keep = has_doc or self.options.undoc_members
-
-                if isinstance(obj, ObjectMember) and obj.skipped:
-                    # forcedly skipped member (ex. a module attribute not defined in __all__)
+                        keep = membername in self.options.private_members
+                else:
+                    keep = False
+            else:
+                if (self.options.members is ALL and
+                        is_filtered_inherited_member(membername, obj)):
                     keep = False
+                else:
+                    # ignore undocumented members if :undoc-members: is not given
+                    keep = has_doc or self.options.undoc_members
+
+            if isinstance(obj, ObjectMember) and obj.skipped:
+                # forcedly skipped member (ex. a module attribute not defined in __all__)
+                keep = False
 
-                # give the user a chance to decide whether this member
-                # should be skipped
-                if self.env.app:
-                    # let extensions preprocess docstrings
+            # give the user a chance to decide whether this member
+            # should be skipped
+            if self.env.app:
+                # let extensions preprocess docstrings
+                try:
                     skip_user = self.env.app.emit_firstresult(
                         'autodoc-skip-member', self.objtype, membername, member,
                         not keep, self.options)
                     if skip_user is not None:
                         keep = not skip_user
-            except Exception as exc:
-                logger.warning(__('autodoc: failed to determine %s.%s (%r) to be documented, '
-                                  'the following exception was raised:\n%s'),
-                               self.name, membername, member, exc, type='autodoc')
-                keep = False
+                except Exception as exc:
+                    logger.warning(__('autodoc: failed to determine %r to be documented, '
+                                      'the following exception was raised:\n%s'),
+                                   member, exc, type='autodoc')
+                    keep = False
 
             if keep:
                 ret.append((membername, member, isattr))
@@ -1676,7 +1674,11 @@ def add_directive_header(self, sig: str) -> None:
             self.env.events.emit('autodoc-process-bases',
                                  self.fullname, self.object, self.options, bases)
 
-            base_classes = [restify(cls) for cls in bases]
+            if self.config.autodoc_typehints_format == "short":
+                base_classes = [restify(cls, "smart") for cls in bases]
+            else:
+                base_classes = [restify(cls) for cls in bases]
+
             sourcename = self.get_sourcename()
             self.add_line('', sourcename)
             self.add_line('   ' + _('Bases: %s') % ', '.join(base_classes), sourcename)
@@ -1773,7 +1775,11 @@ def add_content(self, more_content: Optional[StringList], no_docstring: bool = F
 
         if self.doc_as_attr and not self.get_variable_comment():
             try:
-                more_content = StringList([_('alias of %s') % restify(self.object)], source='')
+                if self.config.autodoc_typehints_format == "short":
+                    alias = restify(self.object, "smart")
+                else:
+                    alias = restify(self.object)
+                more_content = StringList([_('alias of %s') % alias], source='')
             except AttributeError:
                 pass  # Invalid class object is passed.
 
@@ -1846,7 +1852,12 @@ def should_suppress_directive_header(self) -> bool:
 
     def update_content(self, more_content: StringList) -> None:
         if inspect.isgenericalias(self.object):
-            more_content.append(_('alias of %s') % restify(self.object), '')
+            if self.config.autodoc_typehints_format == "short":
+                alias = restify(self.object, "smart")
+            else:
+                alias = restify(self.object)
+
+            more_content.append(_('alias of %s') % alias, '')
             more_content.append('', '')
 
         super().update_content(more_content)
@@ -1864,7 +1875,11 @@ def should_suppress_directive_header(self) -> bool:
 
     def update_content(self, more_content: StringList) -> None:
         if inspect.isNewType(self.object):
-            supertype = restify(self.object.__supertype__)
+            if self.config.autodoc_typehints_format == "short":
+                supertype = restify(self.object.__supertype__, "smart")
+            else:
+                supertype = restify(self.object.__supertype__)
+
             more_content.append(_('alias of %s') % supertype, '')
             more_content.append('', '')
 
@@ -1901,7 +1916,11 @@ def update_content(self, more_content: StringList) -> None:
             for constraint in self.object.__constraints__:
                 attrs.append(stringify_typehint(constraint))
             if self.object.__bound__:
-                attrs.append(r"bound=\ " + restify(self.object.__bound__))
+                if self.config.autodoc_typehints_format == "short":
+                    bound = restify(self.object.__bound__, "smart")
+                else:
+                    bound = restify(self.object.__bound__)
+                attrs.append(r"bound=\ " + bound)
             if self.object.__covariant__:
                 attrs.append("covariant=True")
             if self.object.__contravariant__:
diff --git a/sphinx/ext/autodoc/typehints.py b/sphinx/ext/autodoc/typehints.py
index 5102e6706..f4b4dd35e 100644
--- a/sphinx/ext/autodoc/typehints.py
+++ b/sphinx/ext/autodoc/typehints.py
@@ -23,11 +23,6 @@
 def record_typehints(app: Sphinx, objtype: str, name: str, obj: Any,
                      options: Dict, args: str, retann: str) -> None:
     """Record type hints to env object."""
-    if app.config.autodoc_typehints_format == 'short':
-        mode = 'smart'
-    else:
-        mode = 'fully-qualified'
-
     try:
         if callable(obj):
             annotations = app.env.temp_data.setdefault('annotations', {})
@@ -35,9 +30,9 @@ def record_typehints(app: Sphinx, objtype: str, name: str, obj: Any,
             sig = inspect.signature(obj, type_aliases=app.config.autodoc_type_aliases)
             for param in sig.parameters.values():
                 if param.annotation is not param.empty:
-                    annotation[param.name] = typing.stringify(param.annotation, mode)
+                    annotation[param.name] = typing.stringify(param.annotation)
             if sig.return_annotation is not sig.empty:
-                annotation['return'] = typing.stringify(sig.return_annotation, mode)
+                annotation['return'] = typing.stringify(sig.return_annotation)
     except (TypeError, ValueError):
         pass
 
diff --git a/sphinx/roles.py b/sphinx/roles.py
index c6949b0c9..e194db5bc 100644
--- a/sphinx/roles.py
+++ b/sphinx/roles.py
@@ -15,7 +15,7 @@
 from docutils.nodes import Element, Node, TextElement, system_message
 
 from sphinx import addnodes
-from sphinx.locale import _, __
+from sphinx.locale import _
 from sphinx.util import ws_re
 from sphinx.util.docutils import ReferenceRole, SphinxRole
 from sphinx.util.typing import RoleFunction
@@ -190,7 +190,7 @@ def run(self) -> Tuple[List[Node], List[system_message]]:
                 title = "PEP " + self.title
                 reference += nodes.strong(title, title)
         except ValueError:
-            msg = self.inliner.reporter.error(__('invalid PEP number %s') % self.target,
+            msg = self.inliner.reporter.error('invalid PEP number %s' % self.target,
                                               line=self.lineno)
             prb = self.inliner.problematic(self.rawtext, self.rawtext, msg)
             return [prb], [msg]
@@ -224,7 +224,7 @@ def run(self) -> Tuple[List[Node], List[system_message]]:
                 title = "RFC " + self.title
                 reference += nodes.strong(title, title)
         except ValueError:
-            msg = self.inliner.reporter.error(__('invalid RFC number %s') % self.target,
+            msg = self.inliner.reporter.error('invalid RFC number %s' % self.target,
                                               line=self.lineno)
             prb = self.inliner.problematic(self.rawtext, self.rawtext, msg)
             return [prb], [msg]
diff --git a/sphinx/util/docfields.py b/sphinx/util/docfields.py
index 081713b0f..49ee26789 100644
--- a/sphinx/util/docfields.py
+++ b/sphinx/util/docfields.py
@@ -78,8 +78,8 @@ def make_xref(self, rolename: str, domain: str, target: str,
         role = env.get_domain(domain).role(rolename)
         if role is None or inliner is None:
             if role is None and inliner is not None:
-                msg = __("Problem in %s domain: field is supposed "
-                         "to use role '%s', but that role is not in the domain.")
+                msg = "Problem in %s domain: field is supposed "
+                msg += "to use role '%s', but that role is not in the domain."
                 logger.warning(__(msg), domain, rolename, location=location)
             refnode = addnodes.pending_xref('', refdomain=domain, refexplicit=False,
                                             reftype=rolename, reftarget=target)
diff --git a/sphinx/util/typing.py b/sphinx/util/typing.py
index 005599f79..537b61172 100644
--- a/sphinx/util/typing.py
+++ b/sphinx/util/typing.py
@@ -105,10 +105,24 @@ def is_system_TypeVar(typ: Any) -> bool:
     return modname == 'typing' and isinstance(typ, TypeVar)
 
 
-def restify(cls: Optional[Type]) -> str:
-    """Convert python class to a reST reference."""
+def restify(cls: Optional[Type], mode: str = 'fully-qualified-except-typing') -> str:
+    """Convert python class to a reST reference.
+
+    :param mode: Specify a method how annotations will be stringified.
+
+                 'fully-qualified-except-typing'
+                     Show the module name and qualified name of the annotation except
+                     the "typing" module.
+                 'smart'
+                     Show the name of the annotation.
+    """
     from sphinx.util import inspect  # lazy loading
 
+    if mode == 'smart':
+        modprefix = '~'
+    else:
+        modprefix = ''
+
     try:
         if cls is None or cls is NoneType:
             return ':py:obj:`None`'
@@ -117,63 +131,67 @@ def restify(cls: Optional[Type]) -> str:
         elif isinstance(cls, str):
             return cls
         elif cls in INVALID_BUILTIN_CLASSES:
-            return ':py:class:`%s`' % INVALID_BUILTIN_CLASSES[cls]
+            return ':py:class:`%s%s`' % (modprefix, INVALID_BUILTIN_CLASSES[cls])
         elif inspect.isNewType(cls):
             if sys.version_info > (3, 10):
                 # newtypes have correct module info since Python 3.10+
-                print(cls, type(cls), dir(cls))
-                return ':py:class:`%s.%s`' % (cls.__module__, cls.__name__)
+                return ':py:class:`%s%s.%s`' % (modprefix, cls.__module__, cls.__name__)
             else:
                 return ':py:class:`%s`' % cls.__name__
         elif UnionType and isinstance(cls, UnionType):
             if len(cls.__args__) > 1 and None in cls.__args__:
-                args = ' | '.join(restify(a) for a in cls.__args__ if a)
+                args = ' | '.join(restify(a, mode) for a in cls.__args__ if a)
                 return 'Optional[%s]' % args
             else:
-                return ' | '.join(restify(a) for a in cls.__args__)
+                return ' | '.join(restify(a, mode) for a in cls.__args__)
         elif cls.__module__ in ('__builtin__', 'builtins'):
             if hasattr(cls, '__args__'):
                 return ':py:class:`%s`\\ [%s]' % (
                     cls.__name__,
-                    ', '.join(restify(arg) for arg in cls.__args__),
+                    ', '.join(restify(arg, mode) for arg in cls.__args__),
                 )
             else:
                 return ':py:class:`%s`' % cls.__name__
         else:
             if sys.version_info >= (3, 7):  # py37+
-                return _restify_py37(cls)
+                return _restify_py37(cls, mode)
             else:
-                return _restify_py36(cls)
+                return _restify_py36(cls, mode)
     except (AttributeError, TypeError):
         return inspect.object_description(cls)
 
 
-def _restify_py37(cls: Optional[Type]) -> str:
+def _restify_py37(cls: Optional[Type], mode: str = 'fully-qualified-except-typing') -> str:
     """Convert python class to a reST reference."""
     from sphinx.util import inspect  # lazy loading
 
+    if mode == 'smart':
+        modprefix = '~'
+    else:
+        modprefix = ''
+
     if (inspect.isgenericalias(cls) and
             cls.__module__ == 'typing' and cls.__origin__ is Union):
         # Union
         if len(cls.__args__) > 1 and cls.__args__[-1] is NoneType:
             if len(cls.__args__) > 2:
-                args = ', '.join(restify(a) for a in cls.__args__[:-1])
+                args = ', '.join(restify(a, mode) for a in cls.__args__[:-1])
                 return ':py:obj:`~typing.Optional`\\ [:obj:`~typing.Union`\\ [%s]]' % args
             else:
-                return ':py:obj:`~typing.Optional`\\ [%s]' % restify(cls.__args__[0])
+                return ':py:obj:`~typing.Optional`\\ [%s]' % restify(cls.__args__[0], mode)
         else:
-            args = ', '.join(restify(a) for a in cls.__args__)
+            args = ', '.join(restify(a, mode) for a in cls.__args__)
             return ':py:obj:`~typing.Union`\\ [%s]' % args
     elif inspect.isgenericalias(cls):
         if isinstance(cls.__origin__, typing._SpecialForm):
-            text = restify(cls.__origin__)  # type: ignore
+            text = restify(cls.__origin__, mode)  # type: ignore
         elif getattr(cls, '_name', None):
             if cls.__module__ == 'typing':
                 text = ':py:class:`~%s.%s`' % (cls.__module__, cls._name)
             else:
-                text = ':py:class:`%s.%s`' % (cls.__module__, cls._name)
+                text = ':py:class:`%s%s.%s`' % (modprefix, cls.__module__, cls._name)
         else:
-            text = restify(cls.__origin__)
+            text = restify(cls.__origin__, mode)
 
         origin = getattr(cls, '__origin__', None)
         if not hasattr(cls, '__args__'):
@@ -182,12 +200,12 @@ def _restify_py37(cls: Optional[Type]) -> str:
             # Suppress arguments if all system defined TypeVars (ex. Dict[KT, VT])
             pass
         elif cls.__module__ == 'typing' and cls._name == 'Callable':
-            args = ', '.join(restify(a) for a in cls.__args__[:-1])
-            text += r"\ [[%s], %s]" % (args, restify(cls.__args__[-1]))
+            args = ', '.join(restify(a, mode) for a in cls.__args__[:-1])
+            text += r"\ [[%s], %s]" % (args, restify(cls.__args__[-1], mode))
         elif cls.__module__ == 'typing' and getattr(origin, '_name', None) == 'Literal':
             text += r"\ [%s]" % ', '.join(repr(a) for a in cls.__args__)
         elif cls.__args__:
-            text += r"\ [%s]" % ", ".join(restify(a) for a in cls.__args__)
+            text += r"\ [%s]" % ", ".join(restify(a, mode) for a in cls.__args__)
 
         return text
     elif isinstance(cls, typing._SpecialForm):
@@ -196,7 +214,7 @@ def _restify_py37(cls: Optional[Type]) -> str:
         if cls.__module__ == 'typing':
             return ':py:class:`~%s.%s`' % (cls.__module__, cls.__qualname__)
         else:
-            return ':py:class:`%s.%s`' % (cls.__module__, cls.__qualname__)
+            return ':py:class:`%s%s.%s`' % (modprefix, cls.__module__, cls.__qualname__)
     elif isinstance(cls, ForwardRef):
         return ':py:class:`%s`' % cls.__forward_arg__
     else:
@@ -204,10 +222,15 @@ def _restify_py37(cls: Optional[Type]) -> str:
         if cls.__module__ == 'typing':
             return ':py:obj:`~%s.%s`' % (cls.__module__, cls.__name__)
         else:
-            return ':py:obj:`%s.%s`' % (cls.__module__, cls.__name__)
+            return ':py:obj:`%s%s.%s`' % (modprefix, cls.__module__, cls.__name__)
+
 
+def _restify_py36(cls: Optional[Type], mode: str = 'fully-qualified-except-typing') -> str:
+    if mode == 'smart':
+        modprefix = '~'
+    else:
+        modprefix = ''
 
-def _restify_py36(cls: Optional[Type]) -> str:
     module = getattr(cls, '__module__', None)
     if module == 'typing':
         if getattr(cls, '_name', None):
@@ -221,7 +244,7 @@ def _restify_py36(cls: Optional[Type]) -> str:
         else:
             qualname = repr(cls).replace('typing.', '')
     elif hasattr(cls, '__qualname__'):
-        qualname = '%s.%s' % (module, cls.__qualname__)
+        qualname = '%s%s.%s' % (modprefix, module, cls.__qualname__)
     else:
         qualname = repr(cls)
 
@@ -230,11 +253,11 @@ def _restify_py36(cls: Optional[Type]) -> str:
         if module == 'typing':
             reftext = ':py:class:`~typing.%s`' % qualname
         else:
-            reftext = ':py:class:`%s`' % qualname
+            reftext = ':py:class:`%s%s`' % (modprefix, qualname)
 
         params = cls.__args__
         if params:
-            param_str = ', '.join(restify(p) for p in params)
+            param_str = ', '.join(restify(p, mode) for p in params)
             return reftext + '\\ [%s]' % param_str
         else:
             return reftext
@@ -242,19 +265,19 @@ def _restify_py36(cls: Optional[Type]) -> str:
         if module == 'typing':
             reftext = ':py:class:`~typing.%s`' % qualname
         else:
-            reftext = ':py:class:`%s`' % qualname
+            reftext = ':py:class:`%s%s`' % (modprefix, qualname)
 
         if cls.__args__ is None or len(cls.__args__) <= 2:
             params = cls.__args__
         elif cls.__origin__ == Generator:
             params = cls.__args__
         else:  # typing.Callable
-            args = ', '.join(restify(arg) for arg in cls.__args__[:-1])
-            result = restify(cls.__args__[-1])
+            args = ', '.join(restify(arg, mode) for arg in cls.__args__[:-1])
+            result = restify(cls.__args__[-1], mode)
             return reftext + '\\ [[%s], %s]' % (args, result)
 
         if params:
-            param_str = ', '.join(restify(p) for p in params)
+            param_str = ', '.join(restify(p, mode) for p in params)
             return reftext + '\\ [%s]' % (param_str)
         else:
             return reftext
@@ -264,13 +287,13 @@ def _restify_py36(cls: Optional[Type]) -> str:
         if params is not None:
             if len(params) > 1 and params[-1] is NoneType:
                 if len(params) > 2:
-                    param_str = ", ".join(restify(p) for p in params[:-1])
+                    param_str = ", ".join(restify(p, mode) for p in params[:-1])
                     return (':py:obj:`~typing.Optional`\\ '
                             '[:py:obj:`~typing.Union`\\ [%s]]' % param_str)
                 else:
-                    return ':py:obj:`~typing.Optional`\\ [%s]' % restify(params[0])
+                    return ':py:obj:`~typing.Optional`\\ [%s]' % restify(params[0], mode)
             else:
-                param_str = ', '.join(restify(p) for p in params)
+                param_str = ', '.join(restify(p, mode) for p in params)
                 return ':py:obj:`~typing.Union`\\ [%s]' % param_str
         else:
             return ':py:obj:`Union`'
@@ -278,25 +301,25 @@ def _restify_py36(cls: Optional[Type]) -> str:
         if cls.__module__ == 'typing':
             return ':py:class:`~%s.%s`' % (cls.__module__, cls.__qualname__)
         else:
-            return ':py:class:`%s.%s`' % (cls.__module__, cls.__qualname__)
+            return ':py:class:`%s%s.%s`' % (modprefix, cls.__module__, cls.__qualname__)
     elif hasattr(cls, '_name'):
         # SpecialForm
         if cls.__module__ == 'typing':
             return ':py:obj:`~%s.%s`' % (cls.__module__, cls._name)
         else:
-            return ':py:obj:`%s.%s`' % (cls.__module__, cls._name)
+            return ':py:obj:`%s%s.%s`' % (modprefix, cls.__module__, cls._name)
     elif hasattr(cls, '__name__'):
         # not a class (ex. TypeVar)
         if cls.__module__ == 'typing':
             return ':py:obj:`~%s.%s`' % (cls.__module__, cls.__name__)
         else:
-            return ':py:obj:`%s.%s`' % (cls.__module__, cls.__name__)
+            return ':py:obj:`%s%s.%s`' % (modprefix, cls.__module__, cls.__name__)
     else:
         # others (ex. Any)
         if cls.__module__ == 'typing':
             return ':py:obj:`~%s.%s`' % (cls.__module__, qualname)
         else:
-            return ':py:obj:`%s.%s`' % (cls.__module__, qualname)
+            return ':py:obj:`%s%s.%s`' % (modprefix, cls.__module__, qualname)
 
 
 def stringify(annotation: Any, mode: str = 'fully-qualified-except-typing') -> str:


Test patch:
diff --git a/tests/roots/test-ext-autodoc/target/genericalias.py b/tests/roots/test-ext-autodoc/target/genericalias.py
index 9909efca1..3856e034d 100644
--- a/tests/roots/test-ext-autodoc/target/genericalias.py
+++ b/tests/roots/test-ext-autodoc/target/genericalias.py
@@ -9,3 +9,6 @@
 class Class:
     #: A list of int
     T = List[int]
+
+#: A list of Class
+L = List[Class]
diff --git a/tests/roots/test-ext-autodoc/target/typevar.py b/tests/roots/test-ext-autodoc/target/typevar.py
index c330e2d88..ff2d46d19 100644
--- a/tests/roots/test-ext-autodoc/target/typevar.py
+++ b/tests/roots/test-ext-autodoc/target/typevar.py
@@ -1,3 +1,4 @@
+from datetime import date
 from typing import NewType, TypeVar
 
 #: T1
@@ -15,7 +16,7 @@
 T5 = TypeVar("T5", contravariant=True)
 
 #: T6
-T6 = NewType("T6", int)
+T6 = NewType("T6", date)
 
 #: T7
 T7 = TypeVar("T7", bound=int)
@@ -26,4 +27,4 @@ class Class:
     T1 = TypeVar("T1")
 
     #: T6
-    T6 = NewType("T6", int)
+    T6 = NewType("T6", date)
diff --git a/tests/test_build_html.py b/tests/test_build_html.py
index d2437a4dd..a1d4717e7 100644
--- a/tests/test_build_html.py
+++ b/tests/test_build_html.py
@@ -1195,20 +1195,6 @@ def test_assets_order(app):
     assert re.search(pattern, content, re.S)
 
 
-@pytest.mark.sphinx('html', testroot='html_assets')
-def test_javscript_loading_method(app):
-    app.add_js_file('normal.js')
-    app.add_js_file('early.js', loading_method='async')
-    app.add_js_file('late.js', loading_method='defer')
-
-    app.builder.build_all()
-    content = (app.outdir / 'index.html').read_text()
-
-    assert '<script src="_static/normal.js"></script>' in content
-    assert '<script async="async" src="_static/early.js"></script>' in content
-    assert '<script defer="defer" src="_static/late.js"></script>' in content
-
-
 @pytest.mark.sphinx('html', testroot='basic', confoverrides={'html_copy_source': False})
 def test_html_copy_source(app):
     app.builder.build_all()
diff --git a/tests/test_domain_py.py b/tests/test_domain_py.py
index b32d74699..ca0148cbb 100644
--- a/tests/test_domain_py.py
+++ b/tests/test_domain_py.py
@@ -1196,9 +1196,7 @@ def test_type_field(app):
     text = (".. py:data:: var1\n"
             "   :type: .int\n"
             ".. py:data:: var2\n"
-            "   :type: ~builtins.int\n"
-            ".. py:data:: var3\n"
-            "   :type: typing.Optional[typing.Tuple[int, typing.Any]]\n")
+            "   :type: ~builtins.int\n")
     doctree = restructuredtext.parse(app, text)
     assert_node(doctree, (addnodes.index,
                           [desc, ([desc_signature, ([desc_name, "var1"],
@@ -1211,28 +1209,9 @@ def test_type_field(app):
                                                     [desc_annotation, ([desc_sig_punctuation, ':'],
                                                                        desc_sig_space,
                                                                        [pending_xref, "int"])])],
-                                  [desc_content, ()])],
-                          addnodes.index,
-                          [desc, ([desc_signature, ([desc_name, "var3"],
-                                                    [desc_annotation, ([desc_sig_punctuation, ":"],
-                                                                       desc_sig_space,
-                                                                       [pending_xref, "Optional"],
-                                                                       [desc_sig_punctuation, "["],
-                                                                       [pending_xref, "Tuple"],
-                                                                       [desc_sig_punctuation, "["],
-                                                                       [pending_xref, "int"],
-                                                                       [desc_sig_punctuation, ","],
-                                                                       desc_sig_space,
-                                                                       [pending_xref, "Any"],
-                                                                       [desc_sig_punctuation, "]"],
-                                                                       [desc_sig_punctuation, "]"])])],
                                   [desc_content, ()])]))
     assert_node(doctree[1][0][1][2], pending_xref, reftarget='int', refspecific=True)
     assert_node(doctree[3][0][1][2], pending_xref, reftarget='builtins.int', refspecific=False)
-    assert_node(doctree[5][0][1][2], pending_xref, reftarget='typing.Optional', refspecific=False)
-    assert_node(doctree[5][0][1][4], pending_xref, reftarget='typing.Tuple', refspecific=False)
-    assert_node(doctree[5][0][1][6], pending_xref, reftarget='int', refspecific=False)
-    assert_node(doctree[5][0][1][9], pending_xref, reftarget='typing.Any', refspecific=False)
 
 
 @pytest.mark.sphinx(freshenv=True)
diff --git a/tests/test_ext_autodoc.py b/tests/test_ext_autodoc.py
index c853fbb03..e701d7778 100644
--- a/tests/test_ext_autodoc.py
+++ b/tests/test_ext_autodoc.py
@@ -1874,6 +1874,12 @@ def test_autodoc_GenericAlias(app):
             '',
             '      alias of :py:class:`~typing.List`\\ [:py:class:`int`]',
             '',
+            '.. py:attribute:: L',
+            '   :module: target.genericalias',
+            '',
+            '   A list of Class',
+            '',
+            '',
             '.. py:attribute:: T',
             '   :module: target.genericalias',
             '',
@@ -1898,6 +1904,15 @@ def test_autodoc_GenericAlias(app):
             '      alias of :py:class:`~typing.List`\\ [:py:class:`int`]',
             '',
             '',
+            '.. py:data:: L',
+            '   :module: target.genericalias',
+            '',
+            '   A list of Class',
+            '',
+            '   alias of :py:class:`~typing.List`\\ '
+            '[:py:class:`target.genericalias.Class`]',
+            '',
+            '',
             '.. py:data:: T',
             '   :module: target.genericalias',
             '',
@@ -1935,7 +1950,7 @@ def test_autodoc_TypeVar(app):
         '',
         '      T6',
         '',
-        '      alias of :py:class:`int`',
+        '      alias of :py:class:`datetime.date`',
         '',
         '',
         '.. py:data:: T1',
@@ -1975,7 +1990,7 @@ def test_autodoc_TypeVar(app):
         '',
         '   T6',
         '',
-        '   alias of :py:class:`int`',
+        '   alias of :py:class:`datetime.date`',
         '',
         '',
         '.. py:data:: T7',
diff --git a/tests/test_ext_autodoc_autoattribute.py b/tests/test_ext_autodoc_autoattribute.py
index 8fe065d65..826b73dc7 100644
--- a/tests/test_ext_autodoc_autoattribute.py
+++ b/tests/test_ext_autodoc_autoattribute.py
@@ -183,7 +183,7 @@ def test_autoattribute_NewType(app):
         '',
         '   T6',
         '',
-        '   alias of :py:class:`int`',
+        '   alias of :py:class:`datetime.date`',
         '',
     ]
 
diff --git a/tests/test_ext_autodoc_autodata.py b/tests/test_ext_autodoc_autodata.py
index f983726ad..36a96a9d3 100644
--- a/tests/test_ext_autodoc_autodata.py
+++ b/tests/test_ext_autodoc_autodata.py
@@ -111,7 +111,7 @@ def test_autodata_NewType(app):
         '',
         '   T6',
         '',
-        '   alias of :py:class:`int`',
+        '   alias of :py:class:`datetime.date`',
         '',
     ]
 
diff --git a/tests/test_ext_autodoc_configs.py b/tests/test_ext_autodoc_configs.py
index 6a4961ff6..e1a9901dc 100644
--- a/tests/test_ext_autodoc_configs.py
+++ b/tests/test_ext_autodoc_configs.py
@@ -835,7 +835,7 @@ def test_autodoc_typehints_description(app):
             '      **x** (*Tuple**[**int**, **Union**[**int**, **str**]**]*) --\n'
             '\n'
             '   Return type:\n'
-            '      *Tuple*[int, int]\n'
+            '      Tuple[int, int]\n'
             in context)
 
     # Overloads still get displayed in the signature
@@ -887,7 +887,7 @@ def test_autodoc_typehints_description_no_undoc(app):
             '      another tuple\n'
             '\n'
             '   Return type:\n'
-            '      *Tuple*[int, int]\n'
+            '      Tuple[int, int]\n'
             in context)
 
 
@@ -978,7 +978,7 @@ def test_autodoc_typehints_both(app):
             '      **x** (*Tuple**[**int**, **Union**[**int**, **str**]**]*) --\n'
             '\n'
             '   Return type:\n'
-            '      *Tuple*[int, int]\n'
+            '      Tuple[int, int]\n'
             in context)
 
     # Overloads still get displayed in the signature
@@ -1231,6 +1231,62 @@ def test_autodoc_typehints_format_short(app):
     ]
 
 
+@pytest.mark.sphinx('html', testroot='ext-autodoc',
+                    confoverrides={'autodoc_typehints_format': "short"})
+def test_autodoc_typehints_format_short_for_class_alias(app):
+    actual = do_autodoc(app, 'class', 'target.classes.Alias')
+    assert list(actual) == [
+        '',
+        '.. py:attribute:: Alias',
+        '   :module: target.classes',
+        '',
+        '   alias of :py:class:`~target.classes.Foo`',
+    ]
+
+
+@pytest.mark.sphinx('html', testroot='ext-autodoc',
+                    confoverrides={'autodoc_typehints_format': "short"})
+def test_autodoc_typehints_format_short_for_generic_alias(app):
+    actual = do_autodoc(app, 'data', 'target.genericalias.L')
+    if sys.version_info < (3, 7):
+        assert list(actual) == [
+            '',
+            '.. py:data:: L',
+            '   :module: target.genericalias',
+            '   :value: typing.List[target.genericalias.Class]',
+            '',
+            '   A list of Class',
+            '',
+        ]
+    else:
+        assert list(actual) == [
+            '',
+            '.. py:data:: L',
+            '   :module: target.genericalias',
+            '',
+            '   A list of Class',
+            '',
+            '   alias of :py:class:`~typing.List`\\ [:py:class:`~target.genericalias.Class`]',
+            '',
+        ]
+
+
+@pytest.mark.sphinx('html', testroot='ext-autodoc',
+                    confoverrides={'autodoc_typehints_format': "short"})
+def test_autodoc_typehints_format_short_for_newtype_alias(app):
+    actual = do_autodoc(app, 'data', 'target.typevar.T6')
+    assert list(actual) == [
+        '',
+        '.. py:data:: T6',
+        '   :module: target.typevar',
+        '',
+        '   T6',
+        '',
+        '   alias of :py:class:`~datetime.date`',
+        '',
+    ]
+
+
 @pytest.mark.sphinx('html', testroot='ext-autodoc')
 def test_autodoc_default_options(app):
     # no settings
diff --git a/tests/test_util_typing.py b/tests/test_util_typing.py
index 9ee217f96..20359c8c2 100644
--- a/tests/test_util_typing.py
+++ b/tests/test_util_typing.py
@@ -43,13 +43,28 @@ class BrokenType:
 
 def test_restify():
     assert restify(int) == ":py:class:`int`"
+    assert restify(int, "smart") == ":py:class:`int`"
+
     assert restify(str) == ":py:class:`str`"
+    assert restify(str, "smart") == ":py:class:`str`"
+
     assert restify(None) == ":py:obj:`None`"
+    assert restify(None, "smart") == ":py:obj:`None`"
+
     assert restify(Integral) == ":py:class:`numbers.Integral`"
+    assert restify(Integral, "smart") == ":py:class:`~numbers.Integral`"
+
     assert restify(Struct) == ":py:class:`struct.Struct`"
+    assert restify(Struct, "smart") == ":py:class:`~struct.Struct`"
+
     assert restify(TracebackType) == ":py:class:`types.TracebackType`"
+    assert restify(TracebackType, "smart") == ":py:class:`~types.TracebackType`"
+
     assert restify(Any) == ":py:obj:`~typing.Any`"
+    assert restify(Any, "smart") == ":py:obj:`~typing.Any`"
+
     assert restify('str') == "str"
+    assert restify('str', "smart") == "str"
 
 
 def test_restify_type_hints_containers():
@@ -99,13 +114,24 @@ def test_restify_type_hints_Union():
     if sys.version_info >= (3, 7):
         assert restify(Union[int, Integral]) == (":py:obj:`~typing.Union`\\ "
                                                  "[:py:class:`int`, :py:class:`numbers.Integral`]")
+        assert restify(Union[int, Integral], "smart") == (":py:obj:`~typing.Union`\\ "
+                                                          "[:py:class:`int`,"
+                                                          " :py:class:`~numbers.Integral`]")
+
         assert (restify(Union[MyClass1, MyClass2]) ==
                 (":py:obj:`~typing.Union`\\ "
                  "[:py:class:`tests.test_util_typing.MyClass1`, "
                  ":py:class:`tests.test_util_typing.<MyClass2>`]"))
+        assert (restify(Union[MyClass1, MyClass2], "smart") ==
+                (":py:obj:`~typing.Union`\\ "
+                 "[:py:class:`~tests.test_util_typing.MyClass1`,"
+                 " :py:class:`~tests.test_util_typing.<MyClass2>`]"))
     else:
         assert restify(Union[int, Integral]) == ":py:class:`numbers.Integral`"
+        assert restify(Union[int, Integral], "smart") == ":py:class:`~numbers.Integral`"
+
         assert restify(Union[MyClass1, MyClass2]) == ":py:class:`tests.test_util_typing.MyClass1`"
+        assert restify(Union[MyClass1, MyClass2], "smart") == ":py:class:`~tests.test_util_typing.MyClass1`"
 
 
 @pytest.mark.skipif(sys.version_info < (3, 7), reason='python 3.7+ is required.')
@@ -115,19 +141,31 @@ def test_restify_type_hints_typevars():
     T_contra = TypeVar('T_contra', contravariant=True)
 
     assert restify(T) == ":py:obj:`tests.test_util_typing.T`"
+    assert restify(T, "smart") == ":py:obj:`~tests.test_util_typing.T`"
+
     assert restify(T_co) == ":py:obj:`tests.test_util_typing.T_co`"
+    assert restify(T_co, "smart") == ":py:obj:`~tests.test_util_typing.T_co`"
+
     assert restify(T_contra) == ":py:obj:`tests.test_util_typing.T_contra`"
+    assert restify(T_contra, "smart") == ":py:obj:`~tests.test_util_typing.T_contra`"
+
     assert restify(List[T]) == ":py:class:`~typing.List`\\ [:py:obj:`tests.test_util_typing.T`]"
+    assert restify(List[T], "smart") == ":py:class:`~typing.List`\\ [:py:obj:`~tests.test_util_typing.T`]"
 
     if sys.version_info >= (3, 10):
         assert restify(MyInt) == ":py:class:`tests.test_util_typing.MyInt`"
+        assert restify(MyInt, "smart") == ":py:class:`~tests.test_util_typing.MyInt`"
     else:
         assert restify(MyInt) == ":py:class:`MyInt`"
+        assert restify(MyInt, "smart") == ":py:class:`MyInt`"
 
 
 def test_restify_type_hints_custom_class():
     assert restify(MyClass1) == ":py:class:`tests.test_util_typing.MyClass1`"
+    assert restify(MyClass1, "smart") == ":py:class:`~tests.test_util_typing.MyClass1`"
+
     assert restify(MyClass2) == ":py:class:`tests.test_util_typing.<MyClass2>`"
+    assert restify(MyClass2, "smart") == ":py:class:`~tests.test_util_typing.<MyClass2>`"
 
 
 def test_restify_type_hints_alias():
@@ -169,12 +207,14 @@ def test_restify_type_union_operator():
 
 def test_restify_broken_type_hints():
     assert restify(BrokenType) == ':py:class:`tests.test_util_typing.BrokenType`'
+    assert restify(BrokenType, "smart") == ':py:class:`~tests.test_util_typing.BrokenType`'
 
 
 def test_restify_mock():
     with mock(['unknown']):
         import unknown
         assert restify(unknown.secret.Class) == ':py:class:`unknown.secret.Class`'
+        assert restify(unknown.secret.Class, "smart") == ':py:class:`~unknown.secret.Class`'
 
 
 def test_stringify():