Repository: sphinx-doc/sphinx

Issue:
Type annotations which contain commas in autosummary table are not removed completely
### Describe the bug

Here is an example in marshmallow docs: https://marshmallow.readthedocs.io/en/stable/marshmallow.validate.html

Take the first `And` class as an example. Here is the raw definition:

```py
And(*validators: Callable[[Any], Any], error: Optional[str] = None)
```

Expected output:

```py
And(*validators, error)
```

Actual output:

```py
And(*validators, Any], error)
```

It seems that Sphinx uses the comma to separate the arguments, but it can't tell if the comma is in the arguments list or in the type annotations.

### How to Reproduce

```
$ git clone https://github.com/marshmallow-code/marshmallow
$ cd marshmallow
$ pip install tox
$ tox -e docs
$ # open _build/html/marshmallow.validate.html
```


### Expected behavior

_No response_

### Your project

https://github.com/marshmallow-code/marshmallow

### Screenshots

![](https://user-images.githubusercontent.com/12967000/129859027-41eb00bf-4655-425d-bb2f-07db925b37ea.png)

Source: https://github.com/marshmallow-code/marshmallow/blob/dev/docs/marshmallow.validate.rst
Doc: https://marshmallow.readthedocs.io/en/stable/marshmallow.validate.html

### OS

macOS Big Sur 11.4

### Python version

3.8

### Sphinx version

4.1.2

### Sphinx extensions

_No response_

### Extra tools

_No response_

### Additional context

_No response_

Discussion:

Patch:
diff --git a/CHANGES b/CHANGES
index c7e781de8..935b68709 100644
--- a/CHANGES
+++ b/CHANGES
@@ -33,6 +33,8 @@ Bugs fixed
   are not displayed well
 * #9481: autosummary: some warnings contain non-existing filenames
 * #9568: autosummary: summarise overlined sectioned headings correctly
+* #9600: autosummary: Type annotations which contain commas in autosummary table
+  are not removed completely
 * #9481: c domain: some warnings contain non-existing filenames
 * #9481: cpp domain: some warnings contain non-existing filenames
 * #9456: html search: abbreation marks are inserted to the search result if
diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py
index be89f1c38..d4123b5ea 100644
--- a/sphinx/ext/autosummary/__init__.py
+++ b/sphinx/ext/autosummary/__init__.py
@@ -58,6 +58,7 @@
 import re
 import sys
 import warnings
+from inspect import Parameter
 from os import path
 from types import ModuleType
 from typing import Any, Dict, List, Optional, Tuple, Type, cast
@@ -87,6 +88,7 @@
 from sphinx.util import logging, rst
 from sphinx.util.docutils import (NullReporter, SphinxDirective, SphinxRole, new_document,
                                   switch_source_input)
+from sphinx.util.inspect import signature_from_str
 from sphinx.util.matching import Matcher
 from sphinx.util.typing import OptionSpec
 from sphinx.writers.html import HTMLTranslator
@@ -456,10 +458,32 @@ def strip_arg_typehint(s: str) -> str:
     return s.split(':')[0].strip()
 
 
+def _cleanup_signature(s: str) -> str:
+    """Clean up signature using inspect.signautre() for mangle_signature()"""
+    try:
+        sig = signature_from_str(s)
+        parameters = list(sig.parameters.values())
+        for i, param in enumerate(parameters):
+            if param.annotation is not Parameter.empty:
+                # Remove typehints
+                param = param.replace(annotation=Parameter.empty)
+            if param.default is not Parameter.empty:
+                # Replace default value by "None"
+                param = param.replace(default=None)
+            parameters[i] = param
+        sig = sig.replace(parameters=parameters, return_annotation=Parameter.empty)
+        return str(sig)
+    except Exception:
+        # Return the original signature string if failed to clean (ex. parsing error)
+        return s
+
+
 def mangle_signature(sig: str, max_chars: int = 30) -> str:
     """Reformat a function signature to a more compact form."""
+    s = _cleanup_signature(sig)
+
     # Strip return type annotation
-    s = re.sub(r"\)\s*->\s.*$", ")", sig)
+    s = re.sub(r"\)\s*->\s.*$", ")", s)
 
     # Remove parenthesis
     s = re.sub(r"^\((.*)\)$", r"\1", s).strip()
diff --git a/tox.ini b/tox.ini
index b6a67fd85..a363e187f 100644
--- a/tox.ini
+++ b/tox.ini
@@ -8,7 +8,6 @@ passenv =
     https_proxy
     http_proxy
     no_proxy
-    COLORTERM
     PERL
     PERL5LIB
     PYTEST_ADDOPTS


Test patch:
diff --git a/tests/test_ext_autosummary.py b/tests/test_ext_autosummary.py
index 9506ad19d..cb595e454 100644
--- a/tests/test_ext_autosummary.py
+++ b/tests/test_ext_autosummary.py
@@ -63,7 +63,7 @@ def test_mangle_signature():
     (a=1, b=2, c=3) :: ([a, b, c])
     (a=1, b=<SomeClass: a, b, c>, c=3) :: ([a, b, c])
     (a=1, b=T(a=1, b=2), c=3) :: ([a, b, c])
-    (a: int, b: int) -> str :: (a, b)
+    (a: Tuple[int, str], b: int) -> str :: (a, b)
     """
 
     TEST = [[y.strip() for y in x.split("::")] for x in TEST.split("\n")