Skip to content

Commit c22beb4

Browse files
authored
Now typechecks traceback in raise e, msg, traceback on py2 (#11289)
1 parent f42c862 commit c22beb4

File tree

4 files changed

+145
-19
lines changed

4 files changed

+145
-19
lines changed

mypy/checker.py

Lines changed: 76 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3462,7 +3462,7 @@ def visit_raise_stmt(self, s: RaiseStmt) -> None:
34623462
if s.expr:
34633463
self.type_check_raise(s.expr, s)
34643464
if s.from_expr:
3465-
self.type_check_raise(s.from_expr, s, True)
3465+
self.type_check_raise(s.from_expr, s, optional=True)
34663466
self.binder.unreachable()
34673467

34683468
def type_check_raise(self, e: Expression, s: RaiseStmt,
@@ -3471,24 +3471,88 @@ def type_check_raise(self, e: Expression, s: RaiseStmt,
34713471
if isinstance(typ, DeletedType):
34723472
self.msg.deleted_as_rvalue(typ, e)
34733473
return
3474+
3475+
if self.options.python_version[0] == 2:
3476+
# Since `raise` has very different rule on python2, we use a different helper.
3477+
# https://github.com/python/mypy/pull/11289
3478+
self._type_check_raise_python2(e, s, typ)
3479+
return
3480+
3481+
# Python3 case:
34743482
exc_type = self.named_type('builtins.BaseException')
3475-
expected_type = UnionType([exc_type, TypeType(exc_type)])
3483+
expected_type_items = [exc_type, TypeType(exc_type)]
34763484
if optional:
3477-
expected_type.items.append(NoneType())
3478-
if self.options.python_version[0] == 2:
3479-
# allow `raise type, value, traceback`
3480-
# https://docs.python.org/2/reference/simple_stmts.html#the-raise-statement
3481-
# TODO: Also check tuple item types.
3482-
any_type = AnyType(TypeOfAny.implementation_artifact)
3483-
tuple_type = self.named_type('builtins.tuple')
3484-
expected_type.items.append(TupleType([any_type, any_type], tuple_type))
3485-
expected_type.items.append(TupleType([any_type, any_type, any_type], tuple_type))
3486-
self.check_subtype(typ, expected_type, s, message_registry.INVALID_EXCEPTION)
3485+
# This is used for `x` part in a case like `raise e from x`,
3486+
# where we allow `raise e from None`.
3487+
expected_type_items.append(NoneType())
3488+
3489+
self.check_subtype(
3490+
typ, UnionType.make_union(expected_type_items), s,
3491+
message_registry.INVALID_EXCEPTION,
3492+
)
34873493

34883494
if isinstance(typ, FunctionLike):
34893495
# https://github.com/python/mypy/issues/11089
34903496
self.expr_checker.check_call(typ, [], [], e)
34913497

3498+
def _type_check_raise_python2(self, e: Expression, s: RaiseStmt, typ: ProperType) -> None:
3499+
# Python2 has two possible major cases:
3500+
# 1. `raise expr`, where `expr` is some expression, it can be:
3501+
# - Exception typ
3502+
# - Exception instance
3503+
# - Old style class (not supported)
3504+
# - Tuple, where 0th item is exception type or instance
3505+
# 2. `raise exc, msg, traceback`, where:
3506+
# - `exc` is exception type (not instance!)
3507+
# - `traceback` is `types.TracebackType | None`
3508+
# Important note: `raise exc, msg` is not the same as `raise (exc, msg)`
3509+
# We call `raise exc, msg, traceback` - legacy mode.
3510+
exc_type = self.named_type('builtins.BaseException')
3511+
3512+
if (not s.legacy_mode and (isinstance(typ, TupleType) and typ.items
3513+
or (isinstance(typ, Instance) and typ.args
3514+
and typ.type.fullname == 'builtins.tuple'))):
3515+
# `raise (exc, ...)` case:
3516+
item = typ.items[0] if isinstance(typ, TupleType) else typ.args[0]
3517+
self.check_subtype(
3518+
item, UnionType([exc_type, TypeType(exc_type)]), s,
3519+
'When raising a tuple, first element must by derived from BaseException',
3520+
)
3521+
return
3522+
elif s.legacy_mode:
3523+
# `raise Exception, msg` case
3524+
# `raise Exception, msg, traceback` case
3525+
# https://docs.python.org/2/reference/simple_stmts.html#the-raise-statement
3526+
assert isinstance(typ, TupleType) # Is set in fastparse2.py
3527+
self.check_subtype(
3528+
typ.items[0], TypeType(exc_type), s,
3529+
'First argument must be BaseException subtype',
3530+
)
3531+
3532+
# Typecheck `traceback` part:
3533+
if len(typ.items) == 3:
3534+
# Now, we typecheck `traceback` argument if it is present.
3535+
# We do this after the main check for better error message
3536+
# and better ordering: first about `BaseException` subtype,
3537+
# then about `traceback` type.
3538+
traceback_type = UnionType.make_union([
3539+
self.named_type('types.TracebackType'),
3540+
NoneType(),
3541+
])
3542+
self.check_subtype(
3543+
typ.items[2], traceback_type, s,
3544+
'Third argument to raise must have "{}" type'.format(traceback_type),
3545+
)
3546+
else:
3547+
expected_type_items = [
3548+
# `raise Exception` and `raise Exception()` cases:
3549+
exc_type, TypeType(exc_type),
3550+
]
3551+
self.check_subtype(
3552+
typ, UnionType.make_union(expected_type_items),
3553+
s, message_registry.INVALID_EXCEPTION,
3554+
)
3555+
34923556
def visit_try_stmt(self, s: TryStmt) -> None:
34933557
"""Type check a try statement."""
34943558
# Our enclosing frame will get the result if the try/except falls through.

mypy/fastparse2.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -664,19 +664,23 @@ def visit_With(self, n: ast27.With) -> WithStmt:
664664
typ)
665665
return self.set_line(stmt, n)
666666

667+
# 'raise' [test [',' test [',' test]]]
667668
def visit_Raise(self, n: ast27.Raise) -> RaiseStmt:
669+
legacy_mode = False
668670
if n.type is None:
669671
e = None
670672
else:
671673
if n.inst is None:
672674
e = self.visit(n.type)
673675
else:
676+
legacy_mode = True
674677
if n.tback is None:
675678
e = TupleExpr([self.visit(n.type), self.visit(n.inst)])
676679
else:
677680
e = TupleExpr([self.visit(n.type), self.visit(n.inst), self.visit(n.tback)])
678681

679682
stmt = RaiseStmt(e, None)
683+
stmt.legacy_mode = legacy_mode
680684
return self.set_line(stmt, n)
681685

682686
# TryExcept(stmt* body, excepthandler* handlers, stmt* orelse)

mypy/nodes.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1288,16 +1288,19 @@ def accept(self, visitor: StatementVisitor[T]) -> T:
12881288

12891289

12901290
class RaiseStmt(Statement):
1291-
__slots__ = ('expr', 'from_expr')
1291+
__slots__ = ('expr', 'from_expr', 'legacy_mode')
12921292

12931293
# Plain 'raise' is a valid statement.
12941294
expr: Optional[Expression]
12951295
from_expr: Optional[Expression]
1296+
# Is set when python2 has `raise exc, msg, traceback`.
1297+
legacy_mode: bool
12961298

12971299
def __init__(self, expr: Optional[Expression], from_expr: Optional[Expression]) -> None:
12981300
super().__init__()
12991301
self.expr = expr
13001302
self.from_expr = from_expr
1303+
self.legacy_mode = False
13011304

13021305
def accept(self, visitor: StatementVisitor[T]) -> T:
13031306
return visitor.visit_raise_stmt(self)

test-data/unit/check-python2.test

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,18 +62,73 @@ A.f(1)
6262
A.f('') # E: Argument 1 to "f" of "A" has incompatible type "str"; expected "int"
6363
[builtins_py2 fixtures/staticmethod.pyi]
6464

65-
[case testRaiseTuple]
66-
import typing
67-
raise BaseException, "a"
68-
raise BaseException, "a", None
69-
[builtins_py2 fixtures/exception.pyi]
70-
7165
[case testRaiseTupleTypeFail]
7266
import typing
7367
x = None # type: typing.Type[typing.Tuple[typing.Any, typing.Any, typing.Any]]
7468
raise x # E: Exception must be derived from BaseException
7569
[builtins_py2 fixtures/exception.pyi]
7670

71+
[case testRaiseTupleOfThreeOnPython2]
72+
from types import TracebackType
73+
from typing import Optional, Tuple, Type
74+
75+
e = None # type: Optional[TracebackType]
76+
77+
raise BaseException # ok
78+
raise BaseException(1) # ok
79+
raise (BaseException,) # ok
80+
raise (BaseException(1),) # ok
81+
raise BaseException, 1 # ok
82+
raise BaseException, 1, e # ok
83+
raise BaseException, 1, None # ok
84+
85+
raise Exception # ok
86+
raise Exception(1) # ok
87+
raise (Exception,) # ok
88+
raise (Exception(1),) # ok
89+
raise Exception, 1 # ok
90+
raise Exception, 1, e # ok
91+
raise Exception, 1, None # ok
92+
93+
raise int, 1 # E: First argument must be BaseException subtype
94+
raise Exception(1), 1 # E: First argument must be BaseException subtype
95+
raise Exception(1), 1, None # E: First argument must be BaseException subtype
96+
raise Exception, 1, 1 # E: Third argument to raise must have "Union[types.TracebackType, None]" type
97+
raise int, 1, 1 # E: First argument must be BaseException subtype \
98+
# E: Third argument to raise must have "Union[types.TracebackType, None]" type
99+
100+
t1 = (BaseException,)
101+
t2 = (Exception(1), 2, 3, 4) # type: Tuple[Exception, int, int, int]
102+
t3 = (Exception,) # type: Tuple[Type[Exception], ...]
103+
t4 = (Exception(1),) # type: Tuple[Exception, ...]
104+
105+
raise t1 # ok
106+
raise t2 # ok
107+
raise t3 # ok
108+
raise t4 # ok
109+
110+
raise t1, 1, None # E: First argument must be BaseException subtype
111+
raise t2, 1 # E: First argument must be BaseException subtype
112+
raise t3, 1, e # E: First argument must be BaseException subtype
113+
raise t4, 1, 1 # E: First argument must be BaseException subtype \
114+
# E: Third argument to raise must have "Union[types.TracebackType, None]" type
115+
116+
w1 = ()
117+
w2 = (1, Exception)
118+
w3 = (1,) # type: Tuple[int, ...]
119+
120+
raise w1 # E: Exception must be derived from BaseException
121+
raise w2 # E: When raising a tuple, first element must by derived from BaseException
122+
raise w3 # E: When raising a tuple, first element must by derived from BaseException
123+
124+
try:
125+
pass
126+
except Exception:
127+
raise # ok
128+
[builtins_py2 fixtures/exception.pyi]
129+
[file types.pyi]
130+
class TracebackType: pass
131+
77132
[case testTryExceptWithTuple]
78133
try:
79134
None

0 commit comments

Comments
 (0)