Repository: sphinx-doc/sphinx

Issue:
Fail to parse signature with typing that contains a Union with a plain Tuple on Python 3.6 and 3.5
**Describe the bug**
Sphinx fails to parse the signature of a class method that uses type hints and has a parameter with a `Union` type that includes a plain `Tuple` on Python 3.6 and 3.5.
Example signature:
```python
    def mymethod(self, myparam: Union[int, Tuple] = 10) -> List[Dict]:
```

When parsing this code Sphinx fails on Python 3.5 and 3.6 with:
```
Warning, treated as error:
error while formatting arguments for mymodule.MyClass.mymethod: 'NoneType' object is not iterable
```

While it work as expected on Python 3.7.
Just for reference a plain `Tuple` is a valid type and is equivalent to `Tuple[Any, ...]`.

**To Reproduce**
To my understanding any signature with a type hint of `Union[Tuple]` should reproduce the error.

**Expected behavior**
Sphinx should have correctly parsed the signature.

**Environment info**
- OS: the issue was reproduced on both Linux Debian and Macos
- Python version: 3.5.6, 3.6.7 for the repro, 3.7.3 where it works
- Sphinx version: 2.0.1
- Sphinx extensions:  not applicable

**Additional context**
I've managed to get a full stacktrace that is pretty long, so pasting here only the final relevant part. This is on 3.5 but the 3.6 one is equivalent:
```python
  File "/tmp/myproject/.tox/py35-tests/lib/python3.5/site-packages/sphinx/ext/autodoc/__init__.py", line 1305, in format_args
    args = Signature(self.object, bound_method=True).format_args()
  File "/tmp/myproject/.tox/py35-tests/lib/python3.5/site-packages/sphinx/util/inspect.py", line 384, in format_args
    arg.write(self.format_annotation(param.annotation))
  File "/tmp/myproject/.tox/py35-tests/lib/python3.5/site-packages/sphinx/util/inspect.py", line 437, in format_annotation
    return self.format_annotation_old(annotation)
  File "/tmp/myproject/.tox/py35-tests/lib/python3.5/site-packages/sphinx/util/inspect.py", line 540, in format_annotation_old
    param_str = ', '.join(self.format_annotation(p) for p in params)
  File "/tmp/myproject/.tox/py35-tests/lib/python3.5/site-packages/sphinx/util/inspect.py", line 540, in <genexpr>
    param_str = ', '.join(self.format_annotation(p) for p in params)
  File "/tmp/myproject/.tox/py35-tests/lib/python3.5/site-packages/sphinx/util/inspect.py", line 437, in format_annotation
    return self.format_annotation_old(annotation)
  File "/tmp/myproject/.tox/py35-tests/lib/python3.5/site-packages/sphinx/util/inspect.py", line 499, in format_annotation_old
    param_str = ', '.join(self.format_annotation(p) for p in params)
TypeError: 'NoneType' object is not iterable
```

After some debugging I've noticed that the code enters the first `if` block in https://github.com/sphinx-doc/sphinx/blob/master/sphinx/util/inspect.py#L526 where at line 530-531 does:
```python
            params = annotation.__args__
            param_str = ', '.join(self.format_annotation(p) for p in params)
```

But on Python 3.5 and 3.6 a plain Tuple has `__args__` that is `None`:
```python
>>> from typing import Tuple
>>> Tuple.__args__ is None
True
```
So the iteration over `params` fails.

**Possible Fix**
A quick fix could be to just replace line 530 with:
```python
            params = annotation.__args__ or ()
```

Although I've not sent yet a PR because the comments in that part of the code confuses me as they seem to contradict each other, in particular line 529 `# This is for Python 3.6+, 3.5 case is handled below` vs line 533 `# for py36 or below`.
When clearly the first `if` block would not match Python 3.7 given that on 3.7:
```python
>>> hasattr(typing, 'TupleMeta')
False
```
So I would like some advice from people more familiar with that part of the code before sending a patch.

Discussion:

Patch:
diff --git a/CHANGES b/CHANGES
index 1a6a22341..70286204b 100644
--- a/CHANGES
+++ b/CHANGES
@@ -119,6 +119,7 @@ Bugs fixed
 * #6351: "Hyperlink target is not referenced" message is shown even if
   referenced
 * #6165: autodoc: ``tab_width`` setting of docutils has been ignored
+* #6347: autodoc: crashes with a plain Tuple on Python 3.6 and 3.5
 * #6311: autosummary: autosummary table gets confused by complex type hints
 * #6350: autosummary: confused by an argument having some kind of default value
 * Generated Makefiles lack a final EOL (refs: #6232)
diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py
index ba0056879..42530eb40 100644
--- a/sphinx/util/inspect.py
+++ b/sphinx/util/inspect.py
@@ -531,6 +531,8 @@ def format_annotation_new(self, annotation):
                 args = ', '.join(self.format_annotation(a) for a in annotation.__args__[:-1])
                 returns = self.format_annotation(annotation.__args__[-1])
                 return '%s[[%s], %s]' % (qualname, args, returns)
+            elif annotation._special:
+                return qualname
             else:
                 args = ', '.join(self.format_annotation(a) for a in annotation.__args__)
                 return '%s[%s]' % (qualname, args)
@@ -562,8 +564,11 @@ def format_annotation_old(self, annotation):
                 not hasattr(annotation, '__tuple_params__')):
             # This is for Python 3.6+, 3.5 case is handled below
             params = annotation.__args__
-            param_str = ', '.join(self.format_annotation(p) for p in params)
-            return '%s[%s]' % (qualname, param_str)
+            if params:
+                param_str = ', '.join(self.format_annotation(p) for p in params)
+                return '%s[%s]' % (qualname, param_str)
+            else:
+                return qualname
         elif (hasattr(typing, 'GenericMeta') and  # for py36 or below
               isinstance(annotation, typing.GenericMeta)):
             # In Python 3.5.2+, all arguments are stored in __args__,


Test patch:
diff --git a/tests/test_util_inspect.py b/tests/test_util_inspect.py
index c80b2b7c8..22f8ded6f 100644
--- a/tests/test_util_inspect.py
+++ b/tests/test_util_inspect.py
@@ -196,7 +196,7 @@ def meth2(self, arg1, arg2):
 
 def test_Signature_annotations():
     from typing_test_data import (f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10,
-                                  f11, f12, f13, f14, f15, f16, f17, Node)
+                                  f11, f12, f13, f14, f15, f16, f17, f18, Node)
 
     # Class annotations
     sig = inspect.Signature(f0).format_args()
@@ -273,6 +273,9 @@ def test_Signature_annotations():
     sig = inspect.Signature(f17).format_args()
     assert sig == '(*, arg3, arg4)'
 
+    sig = inspect.Signature(f18).format_args()
+    assert sig == '(self, arg1: Union[int, Tuple] = 10) -> List[Dict]'
+
     # type hints by string
     sig = inspect.Signature(Node.children).format_args()
     if (3, 5, 0) <= sys.version_info < (3, 5, 3):
diff --git a/tests/typing_test_data.py b/tests/typing_test_data.py
index 2e0e27ced..e7289e800 100644
--- a/tests/typing_test_data.py
+++ b/tests/typing_test_data.py
@@ -1,5 +1,5 @@
 from numbers import Integral
-from typing import Any, List, TypeVar, Union, Callable, Tuple, Optional
+from typing import Any, Dict, List, TypeVar, Union, Callable, Tuple, Optional
 
 
 def f0(x: int, y: Integral) -> None:
@@ -88,6 +88,10 @@ def f17(*, arg3, arg4):
     pass
 
 
+def f18(self, arg1: Union[int, Tuple] = 10) -> List[Dict]:
+    pass
+
+
 class Node:
     def __init__(self, parent: Optional['Node']) -> None:
         pass