Skip to content

Commit 7143424

Browse files
authored
Handle raise Exception(), None on Python2.7 (python#11786)
Closes python#11742 Related python#11743 Related python#11289 Related python#11700
1 parent 8157a6f commit 7143424

File tree

4 files changed

+167
-19
lines changed

4 files changed

+167
-19
lines changed

mypy/checker.py

+82-12
Original file line numberDiff line numberDiff line change
@@ -3581,7 +3581,7 @@ def visit_raise_stmt(self, s: RaiseStmt) -> None:
35813581
if s.expr:
35823582
self.type_check_raise(s.expr, s)
35833583
if s.from_expr:
3584-
self.type_check_raise(s.from_expr, s, True)
3584+
self.type_check_raise(s.from_expr, s, optional=True)
35853585
self.binder.unreachable()
35863586

35873587
def type_check_raise(self, e: Expression, s: RaiseStmt,
@@ -3590,24 +3590,94 @@ def type_check_raise(self, e: Expression, s: RaiseStmt,
35903590
if isinstance(typ, DeletedType):
35913591
self.msg.deleted_as_rvalue(typ, e)
35923592
return
3593+
3594+
if self.options.python_version[0] == 2:
3595+
# Since `raise` has very different rule on python2, we use a different helper.
3596+
# https://github.com/python/mypy/pull/11289
3597+
self._type_check_raise_python2(e, s, typ)
3598+
return
3599+
3600+
# Python3 case:
35933601
exc_type = self.named_type('builtins.BaseException')
3594-
expected_type = UnionType([exc_type, TypeType(exc_type)])
3602+
expected_type_items = [exc_type, TypeType(exc_type)]
35953603
if optional:
3596-
expected_type.items.append(NoneType())
3597-
if self.options.python_version[0] == 2:
3598-
# allow `raise type, value, traceback`
3599-
# https://docs.python.org/2/reference/simple_stmts.html#the-raise-statement
3600-
# TODO: Also check tuple item types.
3601-
any_type = AnyType(TypeOfAny.implementation_artifact)
3602-
tuple_type = self.named_type('builtins.tuple')
3603-
expected_type.items.append(TupleType([any_type, any_type], tuple_type))
3604-
expected_type.items.append(TupleType([any_type, any_type, any_type], tuple_type))
3605-
self.check_subtype(typ, expected_type, s, message_registry.INVALID_EXCEPTION)
3604+
# This is used for `x` part in a case like `raise e from x`,
3605+
# where we allow `raise e from None`.
3606+
expected_type_items.append(NoneType())
3607+
3608+
self.check_subtype(
3609+
typ, UnionType.make_union(expected_type_items), s,
3610+
message_registry.INVALID_EXCEPTION,
3611+
)
36063612

36073613
if isinstance(typ, FunctionLike):
36083614
# https://github.com/python/mypy/issues/11089
36093615
self.expr_checker.check_call(typ, [], [], e)
36103616

3617+
def _type_check_raise_python2(self, e: Expression, s: RaiseStmt, typ: ProperType) -> None:
3618+
# Python2 has two possible major cases:
3619+
# 1. `raise expr`, where `expr` is some expression, it can be:
3620+
# - Exception typ
3621+
# - Exception instance
3622+
# - Old style class (not supported)
3623+
# - Tuple, where 0th item is exception type or instance
3624+
# 2. `raise exc, msg, traceback`, where:
3625+
# - `exc` is exception type (not instance!)
3626+
# - `traceback` is `types.TracebackType | None`
3627+
# Important note: `raise exc, msg` is not the same as `raise (exc, msg)`
3628+
# We call `raise exc, msg, traceback` - legacy mode.
3629+
exc_type = self.named_type('builtins.BaseException')
3630+
exc_inst_or_type = UnionType([exc_type, TypeType(exc_type)])
3631+
3632+
if (not s.legacy_mode and (isinstance(typ, TupleType) and typ.items
3633+
or (isinstance(typ, Instance) and typ.args
3634+
and typ.type.fullname == 'builtins.tuple'))):
3635+
# `raise (exc, ...)` case:
3636+
item = typ.items[0] if isinstance(typ, TupleType) else typ.args[0]
3637+
self.check_subtype(
3638+
item, exc_inst_or_type, s,
3639+
'When raising a tuple, first element must by derived from BaseException',
3640+
)
3641+
return
3642+
elif s.legacy_mode:
3643+
# `raise Exception, msg` case
3644+
# `raise Exception, msg, traceback` case
3645+
# https://docs.python.org/2/reference/simple_stmts.html#the-raise-statement
3646+
assert isinstance(typ, TupleType) # Is set in fastparse2.py
3647+
if (len(typ.items) >= 2
3648+
and isinstance(get_proper_type(typ.items[1]), NoneType)):
3649+
expected_type: Type = exc_inst_or_type
3650+
else:
3651+
expected_type = TypeType(exc_type)
3652+
self.check_subtype(
3653+
typ.items[0], expected_type, s,
3654+
'Argument 1 must be "{}" subtype'.format(expected_type),
3655+
)
3656+
3657+
# Typecheck `traceback` part:
3658+
if len(typ.items) == 3:
3659+
# Now, we typecheck `traceback` argument if it is present.
3660+
# We do this after the main check for better error message
3661+
# and better ordering: first about `BaseException` subtype,
3662+
# then about `traceback` type.
3663+
traceback_type = UnionType.make_union([
3664+
self.named_type('types.TracebackType'),
3665+
NoneType(),
3666+
])
3667+
self.check_subtype(
3668+
typ.items[2], traceback_type, s,
3669+
'Argument 3 must be "{}" subtype'.format(traceback_type),
3670+
)
3671+
else:
3672+
expected_type_items = [
3673+
# `raise Exception` and `raise Exception()` cases:
3674+
exc_type, TypeType(exc_type),
3675+
]
3676+
self.check_subtype(
3677+
typ, UnionType.make_union(expected_type_items),
3678+
s, message_registry.INVALID_EXCEPTION,
3679+
)
3680+
36113681
def visit_try_stmt(self, s: TryStmt) -> None:
36123682
"""Type check a try statement."""
36133683
# Our enclosing frame will get the result if the try/except falls through.

mypy/fastparse2.py

+4
Original file line numberDiff line numberDiff line change
@@ -665,19 +665,23 @@ def visit_With(self, n: ast27.With) -> WithStmt:
665665
typ)
666666
return self.set_line(stmt, n)
667667

668+
# 'raise' [test [',' test [',' test]]]
668669
def visit_Raise(self, n: ast27.Raise) -> RaiseStmt:
670+
legacy_mode = False
669671
if n.type is None:
670672
e = None
671673
else:
672674
if n.inst is None:
673675
e = self.visit(n.type)
674676
else:
677+
legacy_mode = True
675678
if n.tback is None:
676679
e = TupleExpr([self.visit(n.type), self.visit(n.inst)])
677680
else:
678681
e = TupleExpr([self.visit(n.type), self.visit(n.inst), self.visit(n.tback)])
679682

680683
stmt = RaiseStmt(e, None)
684+
stmt.legacy_mode = legacy_mode
681685
return self.set_line(stmt, n)
682686

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

mypy/nodes.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -1302,16 +1302,19 @@ def accept(self, visitor: StatementVisitor[T]) -> T:
13021302

13031303

13041304
class RaiseStmt(Statement):
1305-
__slots__ = ('expr', 'from_expr')
1305+
__slots__ = ('expr', 'from_expr', 'legacy_mode')
13061306

13071307
# Plain 'raise' is a valid statement.
13081308
expr: Optional[Expression]
13091309
from_expr: Optional[Expression]
1310+
# Is set when python2 has `raise exc, msg, traceback`.
1311+
legacy_mode: bool
13101312

13111313
def __init__(self, expr: Optional[Expression], from_expr: Optional[Expression]) -> None:
13121314
super().__init__()
13131315
self.expr = expr
13141316
self.from_expr = from_expr
1317+
self.legacy_mode = False
13151318

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

test-data/unit/check-python2.test

+77-6
Original file line numberDiff line numberDiff line change
@@ -68,18 +68,89 @@ A.f(1)
6868
A.f('') # E: Argument 1 to "f" of "A" has incompatible type "str"; expected "int"
6969
[builtins_py2 fixtures/staticmethod.pyi]
7070

71-
[case testRaiseTuple]
72-
import typing
73-
raise BaseException, "a"
74-
raise BaseException, "a", None
75-
[builtins_py2 fixtures/exception.pyi]
76-
7771
[case testRaiseTupleTypeFail]
7872
import typing
7973
x = None # type: typing.Type[typing.Tuple[typing.Any, typing.Any, typing.Any]]
8074
raise x # E: Exception must be derived from BaseException
8175
[builtins_py2 fixtures/exception.pyi]
8276

77+
[case testRaiseTupleOfThreeOnPython2]
78+
from types import TracebackType
79+
from typing import Optional, Tuple, Type
80+
81+
e = None # type: Optional[TracebackType]
82+
83+
# Correct raising several items:
84+
85+
raise BaseException
86+
raise BaseException(1)
87+
raise (BaseException,)
88+
raise (BaseException(1),)
89+
raise BaseException, 1
90+
raise BaseException, 1, e
91+
raise BaseException, 1, None
92+
93+
raise Exception
94+
raise Exception(1)
95+
raise Exception()
96+
raise Exception(1), None
97+
raise Exception(), None
98+
raise Exception(1), None, None
99+
raise Exception(1), None, e
100+
raise (Exception,)
101+
raise (Exception(1),)
102+
raise Exception, 1
103+
raise Exception, 1, e
104+
raise Exception, 1, None
105+
106+
# Errors:
107+
108+
raise int, 1 # E: Argument 1 must be "Type[builtins.BaseException]" subtype
109+
raise int, None # E: Argument 1 must be "Union[builtins.BaseException, Type[builtins.BaseException]]" subtype
110+
raise Exception(1), 1 # E: Argument 1 must be "Type[builtins.BaseException]" subtype
111+
raise Exception(1), 1, None # E: Argument 1 must be "Type[builtins.BaseException]" subtype
112+
raise Exception, 1, 1 # E: Argument 3 must be "Union[types.TracebackType, None]" subtype
113+
raise int, 1, 1 # E: Argument 1 must be "Type[builtins.BaseException]" subtype \
114+
# E: Argument 3 must be "Union[types.TracebackType, None]" subtype
115+
116+
# Correct raising tuple:
117+
118+
t1 = (BaseException,)
119+
t2 = (Exception(1), 2, 3, 4) # type: Tuple[Exception, int, int, int]
120+
t3 = (Exception,) # type: Tuple[Type[Exception], ...]
121+
t4 = (Exception(1),) # type: Tuple[Exception, ...]
122+
123+
raise t1
124+
raise t2
125+
raise t3
126+
raise t4
127+
128+
# Errors:
129+
130+
raise t1, 1, None # E: Argument 1 must be "Type[builtins.BaseException]" subtype
131+
raise t2, 1 # E: Argument 1 must be "Type[builtins.BaseException]" subtype
132+
raise t3, 1, e # E: Argument 1 must be "Type[builtins.BaseException]" subtype
133+
raise t4, 1, 1 # E: Argument 1 must be "Type[builtins.BaseException]" subtype \
134+
# E: Argument 3 must be "Union[types.TracebackType, None]" subtype
135+
136+
w1 = ()
137+
w2 = (1, Exception)
138+
w3 = (1,) # type: Tuple[int, ...]
139+
140+
raise w1 # E: Exception must be derived from BaseException
141+
raise w2 # E: When raising a tuple, first element must by derived from BaseException
142+
raise w3 # E: When raising a tuple, first element must by derived from BaseException
143+
144+
# Bare raise:
145+
146+
try:
147+
pass
148+
except Exception:
149+
raise # ok
150+
[builtins_py2 fixtures/exception.pyi]
151+
[file types.pyi]
152+
class TracebackType: pass
153+
83154
[case testTryExceptWithTuple]
84155
try:
85156
None

0 commit comments

Comments
 (0)