Skip to content

Commit

Permalink
Now typechecks traceback in raise e, msg, traceback on py2 (#11289)
Browse files Browse the repository at this point in the history
  • Loading branch information
sobolevn authored Oct 10, 2021
1 parent f42c862 commit c22beb4
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 19 deletions.
88 changes: 76 additions & 12 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -3462,7 +3462,7 @@ def visit_raise_stmt(self, s: RaiseStmt) -> None:
if s.expr:
self.type_check_raise(s.expr, s)
if s.from_expr:
self.type_check_raise(s.from_expr, s, True)
self.type_check_raise(s.from_expr, s, optional=True)
self.binder.unreachable()

def type_check_raise(self, e: Expression, s: RaiseStmt,
Expand All @@ -3471,24 +3471,88 @@ def type_check_raise(self, e: Expression, s: RaiseStmt,
if isinstance(typ, DeletedType):
self.msg.deleted_as_rvalue(typ, e)
return

if self.options.python_version[0] == 2:
# Since `raise` has very different rule on python2, we use a different helper.
# https://github.com/python/mypy/pull/11289
self._type_check_raise_python2(e, s, typ)
return

# Python3 case:
exc_type = self.named_type('builtins.BaseException')
expected_type = UnionType([exc_type, TypeType(exc_type)])
expected_type_items = [exc_type, TypeType(exc_type)]
if optional:
expected_type.items.append(NoneType())
if self.options.python_version[0] == 2:
# allow `raise type, value, traceback`
# https://docs.python.org/2/reference/simple_stmts.html#the-raise-statement
# TODO: Also check tuple item types.
any_type = AnyType(TypeOfAny.implementation_artifact)
tuple_type = self.named_type('builtins.tuple')
expected_type.items.append(TupleType([any_type, any_type], tuple_type))
expected_type.items.append(TupleType([any_type, any_type, any_type], tuple_type))
self.check_subtype(typ, expected_type, s, message_registry.INVALID_EXCEPTION)
# This is used for `x` part in a case like `raise e from x`,
# where we allow `raise e from None`.
expected_type_items.append(NoneType())

self.check_subtype(
typ, UnionType.make_union(expected_type_items), s,
message_registry.INVALID_EXCEPTION,
)

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

def _type_check_raise_python2(self, e: Expression, s: RaiseStmt, typ: ProperType) -> None:
# Python2 has two possible major cases:
# 1. `raise expr`, where `expr` is some expression, it can be:
# - Exception typ
# - Exception instance
# - Old style class (not supported)
# - Tuple, where 0th item is exception type or instance
# 2. `raise exc, msg, traceback`, where:
# - `exc` is exception type (not instance!)
# - `traceback` is `types.TracebackType | None`
# Important note: `raise exc, msg` is not the same as `raise (exc, msg)`
# We call `raise exc, msg, traceback` - legacy mode.
exc_type = self.named_type('builtins.BaseException')

if (not s.legacy_mode and (isinstance(typ, TupleType) and typ.items
or (isinstance(typ, Instance) and typ.args
and typ.type.fullname == 'builtins.tuple'))):
# `raise (exc, ...)` case:
item = typ.items[0] if isinstance(typ, TupleType) else typ.args[0]
self.check_subtype(
item, UnionType([exc_type, TypeType(exc_type)]), s,
'When raising a tuple, first element must by derived from BaseException',
)
return
elif s.legacy_mode:
# `raise Exception, msg` case
# `raise Exception, msg, traceback` case
# https://docs.python.org/2/reference/simple_stmts.html#the-raise-statement
assert isinstance(typ, TupleType) # Is set in fastparse2.py
self.check_subtype(
typ.items[0], TypeType(exc_type), s,
'First argument must be BaseException subtype',
)

# Typecheck `traceback` part:
if len(typ.items) == 3:
# Now, we typecheck `traceback` argument if it is present.
# We do this after the main check for better error message
# and better ordering: first about `BaseException` subtype,
# then about `traceback` type.
traceback_type = UnionType.make_union([
self.named_type('types.TracebackType'),
NoneType(),
])
self.check_subtype(
typ.items[2], traceback_type, s,
'Third argument to raise must have "{}" type'.format(traceback_type),
)
else:
expected_type_items = [
# `raise Exception` and `raise Exception()` cases:
exc_type, TypeType(exc_type),
]
self.check_subtype(
typ, UnionType.make_union(expected_type_items),
s, message_registry.INVALID_EXCEPTION,
)

def visit_try_stmt(self, s: TryStmt) -> None:
"""Type check a try statement."""
# Our enclosing frame will get the result if the try/except falls through.
Expand Down
4 changes: 4 additions & 0 deletions mypy/fastparse2.py
Original file line number Diff line number Diff line change
Expand Up @@ -664,19 +664,23 @@ def visit_With(self, n: ast27.With) -> WithStmt:
typ)
return self.set_line(stmt, n)

# 'raise' [test [',' test [',' test]]]
def visit_Raise(self, n: ast27.Raise) -> RaiseStmt:
legacy_mode = False
if n.type is None:
e = None
else:
if n.inst is None:
e = self.visit(n.type)
else:
legacy_mode = True
if n.tback is None:
e = TupleExpr([self.visit(n.type), self.visit(n.inst)])
else:
e = TupleExpr([self.visit(n.type), self.visit(n.inst), self.visit(n.tback)])

stmt = RaiseStmt(e, None)
stmt.legacy_mode = legacy_mode
return self.set_line(stmt, n)

# TryExcept(stmt* body, excepthandler* handlers, stmt* orelse)
Expand Down
5 changes: 4 additions & 1 deletion mypy/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1288,16 +1288,19 @@ def accept(self, visitor: StatementVisitor[T]) -> T:


class RaiseStmt(Statement):
__slots__ = ('expr', 'from_expr')
__slots__ = ('expr', 'from_expr', 'legacy_mode')

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

def __init__(self, expr: Optional[Expression], from_expr: Optional[Expression]) -> None:
super().__init__()
self.expr = expr
self.from_expr = from_expr
self.legacy_mode = False

def accept(self, visitor: StatementVisitor[T]) -> T:
return visitor.visit_raise_stmt(self)
Expand Down
67 changes: 61 additions & 6 deletions test-data/unit/check-python2.test
Original file line number Diff line number Diff line change
Expand Up @@ -62,18 +62,73 @@ A.f(1)
A.f('') # E: Argument 1 to "f" of "A" has incompatible type "str"; expected "int"
[builtins_py2 fixtures/staticmethod.pyi]

[case testRaiseTuple]
import typing
raise BaseException, "a"
raise BaseException, "a", None
[builtins_py2 fixtures/exception.pyi]

[case testRaiseTupleTypeFail]
import typing
x = None # type: typing.Type[typing.Tuple[typing.Any, typing.Any, typing.Any]]
raise x # E: Exception must be derived from BaseException
[builtins_py2 fixtures/exception.pyi]

[case testRaiseTupleOfThreeOnPython2]
from types import TracebackType
from typing import Optional, Tuple, Type

e = None # type: Optional[TracebackType]

raise BaseException # ok
raise BaseException(1) # ok
raise (BaseException,) # ok
raise (BaseException(1),) # ok
raise BaseException, 1 # ok
raise BaseException, 1, e # ok
raise BaseException, 1, None # ok

raise Exception # ok
raise Exception(1) # ok
raise (Exception,) # ok
raise (Exception(1),) # ok
raise Exception, 1 # ok
raise Exception, 1, e # ok
raise Exception, 1, None # ok

raise int, 1 # E: First argument must be BaseException subtype
raise Exception(1), 1 # E: First argument must be BaseException subtype
raise Exception(1), 1, None # E: First argument must be BaseException subtype
raise Exception, 1, 1 # E: Third argument to raise must have "Union[types.TracebackType, None]" type
raise int, 1, 1 # E: First argument must be BaseException subtype \
# E: Third argument to raise must have "Union[types.TracebackType, None]" type

t1 = (BaseException,)
t2 = (Exception(1), 2, 3, 4) # type: Tuple[Exception, int, int, int]
t3 = (Exception,) # type: Tuple[Type[Exception], ...]
t4 = (Exception(1),) # type: Tuple[Exception, ...]

raise t1 # ok
raise t2 # ok
raise t3 # ok
raise t4 # ok

raise t1, 1, None # E: First argument must be BaseException subtype
raise t2, 1 # E: First argument must be BaseException subtype
raise t3, 1, e # E: First argument must be BaseException subtype
raise t4, 1, 1 # E: First argument must be BaseException subtype \
# E: Third argument to raise must have "Union[types.TracebackType, None]" type

w1 = ()
w2 = (1, Exception)
w3 = (1,) # type: Tuple[int, ...]

raise w1 # E: Exception must be derived from BaseException
raise w2 # E: When raising a tuple, first element must by derived from BaseException
raise w3 # E: When raising a tuple, first element must by derived from BaseException

try:
pass
except Exception:
raise # ok
[builtins_py2 fixtures/exception.pyi]
[file types.pyi]
class TracebackType: pass

[case testTryExceptWithTuple]
try:
None
Expand Down

0 comments on commit c22beb4

Please sign in to comment.