Repository: python/mypy

Issue:
False positive "Exception type must be derived from BaseException"
Mypy incorrectly emits an error when using a variable typed as a variadic tuple of exception classes in an `except` clause. `fn1` and `fn2` below trigger an error:

```
from typing import Tuple, Type, Union

def fn1(exc: Tuple[Type[Exception], ...]) -> None:
    try:
        1 / 0
    except exc:  # E: Exception type must be derived from BaseException
        pass

def fn2(exc: Union[Type[Exception], Tuple[Type[Exception], ...]]) -> None:
    try:
        1 / 0
    except exc:  # E: Exception type must be derived from BaseException
        pass

def fn3(exc: Type[Exception]) -> None:
    try:
        1 / 0
    except exc:  # this is fine
        pass

def fn4(exc1: Type[Exception], exc2: Type[Exception]) -> None:
    try:
        1 / 0
    except (exc1, exc2):  # this is fine too
        pass

def fn5(exc: Tuple[Type[Exception], Type[Exception]]) -> None:
    try:
        1 / 0
    except exc:  # no error
        pass
```

I came across this trying to add types to a function similar to `@retry()` from https://github.com/quora/qcore/blob/master/qcore/decorators.py#L204.
Crash in TypeVarType.serialize() when extracting dict().get as a variable
Repro:
```python
from typing import *
d = {}  # type: Dict[str, int]
g = d.get
```
Traceback:
```
Traceback (most recent call last):
  File "/usr/local/lib/python3.6/runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "/usr/local/lib/python3.6/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/Users/guido/src/mypy/mypy/__main__.py", line 5, in <module>
    main(None)
  File "/Users/guido/src/mypy/mypy/main.py", line 42, in main
    res = type_check_only(sources, bin_dir, options)
  File "/Users/guido/src/mypy/mypy/main.py", line 86, in type_check_only
    options=options)
  File "/Users/guido/src/mypy/mypy/build.py", line 183, in build
    dispatch(sources, manager)
  File "/Users/guido/src/mypy/mypy/build.py", line 1531, in dispatch
    process_graph(graph, manager)
  File "/Users/guido/src/mypy/mypy/build.py", line 1768, in process_graph
    process_stale_scc(graph, scc)
  File "/Users/guido/src/mypy/mypy/build.py", line 1856, in process_stale_scc
    graph[id].write_cache()
  File "/Users/guido/src/mypy/mypy/build.py", line 1512, in write_cache
    self.manager)
  File "/Users/guido/src/mypy/mypy/build.py", line 862, in write_cache
    data = tree.serialize()
  File "/Users/guido/src/mypy/mypy/nodes.py", line 271, in serialize
    'names': self.names.serialize(self._fullname),
  File "/Users/guido/src/mypy/mypy/nodes.py", line 2265, in serialize
    data[key] = value.serialize(fullname, key)
  File "/Users/guido/src/mypy/mypy/nodes.py", line 2211, in serialize
    data['node'] = self.node.serialize()
  File "/Users/guido/src/mypy/mypy/nodes.py", line 671, in serialize
    'type': None if self.type is None else self.type.serialize(),
  File "/Users/guido/src/mypy/mypy/types.py", line 837, in serialize
    'items': [t.serialize() for t in self.items()],
  File "/Users/guido/src/mypy/mypy/types.py", line 837, in <listcomp>
    'items': [t.serialize() for t in self.items()],
  File "/Users/guido/src/mypy/mypy/types.py", line 763, in serialize
    for t in self.arg_types],
  File "/Users/guido/src/mypy/mypy/types.py", line 763, in <listcomp>
    for t in self.arg_types],
  File "/Users/guido/src/mypy/mypy/types.py", line 1079, in serialize
    'items': [t.serialize() for t in self.items],
  File "/Users/guido/src/mypy/mypy/types.py", line 1079, in <listcomp>
    'items': [t.serialize() for t in self.items],
  File "/Users/guido/src/mypy/mypy/types.py", line 496, in serialize
    assert not self.id.is_meta_var()
AssertionError
```


Discussion:
Good find. Looks like these cases can all easily be added to `visit_except_handler_test()` in checker.py. (The variadic tuple case should go in the initialization of `test_types`, the union case should go in the if/elif sequence.) 
@gvanrossum 
I think I have a fix for the problem #2809 
The problem is caused by accessing generic methods:
```python
from typing import TypeVar
T = TypeVar('T')

class D:
    def m(self, x: T) -> T:
        return x

g = D().m  # This crashes.
```
There are more details in the PR description.

Resolving patch:
diff --git a/mypy/checker.py b/mypy/checker.py
--- a/mypy/checker.py
+++ b/mypy/checker.py
@@ -1919,7 +1919,7 @@ def check_except_handler_test(self, n: Expression) -> Type:
         typ = self.expr_checker.accept(n)
 
         all_types = []  # type: List[Type]
-        test_types = typ.items if isinstance(typ, TupleType) else [typ]
+        test_types = self.get_types_from_except_handler(typ, n)
 
         for ttype in test_types:
             if isinstance(ttype, AnyType):
@@ -1946,6 +1946,22 @@ def check_except_handler_test(self, n: Expression) -> Type:
 
         return UnionType.make_simplified_union(all_types)
 
+    def get_types_from_except_handler(self, typ: Type, n: Expression) -> List[Type]:
+        """Helper for check_except_handler_test to retrieve handler types."""
+        if isinstance(typ, TupleType):
+            return typ.items
+        elif isinstance(typ, UnionType):
+            return [
+                union_typ
+                for item in typ.items
+                for union_typ in self.get_types_from_except_handler(item, n)
+            ]
+        elif isinstance(typ, Instance) and is_named_instance(typ, 'builtins.tuple'):
+            # variadic tuple
+            return [typ.args[0]]
+        else:
+            return [typ]
+
     def visit_for_stmt(self, s: ForStmt) -> None:
         """Type check a for statement."""
         if s.is_async:


Test patch:
diff --git a/test-data/unit/check-statements.test b/test-data/unit/check-statements.test
--- a/test-data/unit/check-statements.test
+++ b/test-data/unit/check-statements.test
@@ -660,6 +660,57 @@ except (E1_1, E1_2) as e2:
     c = e2 # type: E1_2 # E: Incompatible types in assignment (expression has type "Union[E1_1, E1_2]", variable has type "E1_2")
 [builtins fixtures/exception.pyi]
 
+[case testExceptWithMultipleTypes4]
+from typing import Tuple, Type, Union
+
+class E1(BaseException): pass
+class E2(BaseException): pass
+class E3(BaseException): pass
+
+def variadic(exc: Tuple[Type[E1], ...]) -> None:
+    try:
+        pass
+    except exc as e:
+        reveal_type(e)  # E: Revealed type is '__main__.E1'
+
+def union(exc: Union[Type[E1], Type[E2]]) -> None:
+    try:
+        pass
+    except exc as e:
+        reveal_type(e)  # E: Revealed type is 'Union[__main__.E1, __main__.E2]'
+
+def tuple_in_union(exc: Union[Type[E1], Tuple[Type[E2], Type[E3]]]) -> None:
+    try:
+        pass
+    except exc as e:
+        reveal_type(e)  # E: Revealed type is 'Union[__main__.E1, __main__.E2, __main__.E3]'
+
+def variadic_in_union(exc: Union[Type[E1], Tuple[Type[E2], ...]]) -> None:
+    try:
+        pass
+    except exc as e:
+        reveal_type(e)  # E: Revealed type is 'Union[__main__.E1, __main__.E2]'
+
+def nested_union(exc: Union[Type[E1], Union[Type[E2], Type[E3]]]) -> None:
+    try:
+        pass
+    except exc as e:
+        reveal_type(e)  # E: Revealed type is 'Union[__main__.E1, __main__.E2, __main__.E3]'
+
+def error_in_union(exc: Union[Type[E1], int]) -> None:
+    try:
+        pass
+    except exc as e:  # E: Exception type must be derived from BaseException
+        pass
+
+def error_in_variadic(exc: Tuple[int, ...]) -> None:
+    try:
+        pass
+    except exc as e:  # E: Exception type must be derived from BaseException
+        pass
+
+[builtins fixtures/tuple.pyi]
+
 [case testExceptWithAnyTypes]
 from typing import Any