Repository: python/mypy

Issue:
Implement type checking "raise from"
Python 3 supports raise from.



Discussion:
This seems to be implemented already.

@spkersten The implementation is partial, and type checking is not complete. This code passes type checking, even though it shouldn't:

```
import typing
raise Exception() from 1
```

Running it fails:

```
Traceback (most recent call last):
  File "t.py", line 2, in <module>
    raise Exception() from 1
TypeError: exception causes must derive from BaseException
```


Resolving patch:
diff --git a/mypy/checker.py b/mypy/checker.py
--- a/mypy/checker.py
+++ b/mypy/checker.py
@@ -1328,19 +1328,24 @@ def visit_raise_stmt(self, s: RaiseStmt) -> Type:
         """Type check a raise statement."""
         self.breaking_out = True
         if s.expr:
-            typ = self.accept(s.expr)
-            if isinstance(typ, FunctionLike):
-                if typ.is_type_obj():
-                    # Cases like "raise ExceptionClass".
-                    typeinfo = typ.type_object()
-                    base = self.lookup_typeinfo('builtins.BaseException')
-                    if base in typeinfo.mro:
-                        # Good!
-                        return None
-                    # Else fall back to the check below (which will fail).
-            self.check_subtype(typ,
-                               self.named_type('builtins.BaseException'), s,
-                               messages.INVALID_EXCEPTION)
+            self.type_check_raise(s.expr, s)
+        if s.from_expr:
+            self.type_check_raise(s.from_expr, s)
+
+    def type_check_raise(self, e: Node, s: RaiseStmt) -> None:
+        typ = self.accept(e)
+        if isinstance(typ, FunctionLike):
+            if typ.is_type_obj():
+                # Cases like "raise/from ExceptionClass".
+                typeinfo = typ.type_object()
+                base = self.lookup_typeinfo('builtins.BaseException')
+                if base in typeinfo.mro:
+                    # Good!
+                    return None
+                # Else fall back to the check below (which will fail).
+        self.check_subtype(typ,
+                           self.named_type('builtins.BaseException'), s,
+                           messages.INVALID_EXCEPTION)
 
     def visit_try_stmt(self, s: TryStmt) -> Type:
         """Type check a try statement."""
diff --git a/mypy/semanal.py b/mypy/semanal.py
--- a/mypy/semanal.py
+++ b/mypy/semanal.py
@@ -993,6 +993,8 @@ def visit_return_stmt(self, s: ReturnStmt) -> None:
     def visit_raise_stmt(self, s: RaiseStmt) -> None:
         if s.expr:
             s.expr.accept(self)
+        if s.from_expr:
+            s.from_expr.accept(self)
 
     def visit_yield_stmt(self, s: YieldStmt) -> None:
         if not self.is_func_scope():


Test patch:
diff --git a/mypy/test/data/check-statements.test b/mypy/test/data/check-statements.test
--- a/mypy/test/data/check-statements.test
+++ b/mypy/test/data/check-statements.test
@@ -313,6 +313,30 @@ raise object # E: Exception must be derived from BaseException
 raise f # E: Exception must be derived from BaseException
 [builtins fixtures/exception.py]
 
+[case testRaiseFromStatement]
+from typing import Undefined
+e = Undefined # type: BaseException
+f = Undefined # type: MyError
+a = Undefined # type: A
+raise e from a # E: Exception must be derived from BaseException
+raise e from e
+raise e from f
+class A: pass
+class MyError(BaseException): pass
+[builtins fixtures/exception.py]
+
+[case testRaiseFromClassobject]
+import typing
+class A: pass
+class MyError(BaseException): pass
+def f(): pass
+raise BaseException from BaseException
+raise BaseException from MyError
+raise BaseException from A # E: Exception must be derived from BaseException
+raise BaseException from object # E: Exception must be derived from BaseException
+raise BaseException from f # E: Exception must be derived from BaseException
+[builtins fixtures/exception.py]
+
 [case testTryFinallyStatement]
 import typing
 try: