Skip to content

Commit

Permalink
Handle raise Exception(), None on Python2.7 (#11786)
Browse files Browse the repository at this point in the history
Closes #11742
Related #11743
Related #11289
Related #11700
  • Loading branch information
sobolevn committed Feb 22, 2022
1 parent 8157a6f commit 7143424
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 19 deletions.
94 changes: 82 additions & 12 deletions mypy/checker.py
Expand Up @@ -3581,7 +3581,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 @@ -3590,24 +3590,94 @@ 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')
exc_inst_or_type = UnionType([exc_type, TypeType(exc_type)])

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, exc_inst_or_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
if (len(typ.items) >= 2
and isinstance(get_proper_type(typ.items[1]), NoneType)):
expected_type: Type = exc_inst_or_type
else:
expected_type = TypeType(exc_type)
self.check_subtype(
typ.items[0], expected_type, s,
'Argument 1 must be "{}" subtype'.format(expected_type),
)

# 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,
'Argument 3 must be "{}" subtype'.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
Expand Up @@ -665,19 +665,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
Expand Up @@ -1302,16 +1302,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
83 changes: 77 additions & 6 deletions test-data/unit/check-python2.test
Expand Up @@ -68,18 +68,89 @@ 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]

# Correct raising several items:

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

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

# Errors:

raise int, 1 # E: Argument 1 must be "Type[builtins.BaseException]" subtype
raise int, None # E: Argument 1 must be "Union[builtins.BaseException, Type[builtins.BaseException]]" subtype
raise Exception(1), 1 # E: Argument 1 must be "Type[builtins.BaseException]" subtype
raise Exception(1), 1, None # E: Argument 1 must be "Type[builtins.BaseException]" subtype
raise Exception, 1, 1 # E: Argument 3 must be "Union[types.TracebackType, None]" subtype
raise int, 1, 1 # E: Argument 1 must be "Type[builtins.BaseException]" subtype \
# E: Argument 3 must be "Union[types.TracebackType, None]" subtype

# Correct raising tuple:

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
raise t2
raise t3
raise t4

# Errors:

raise t1, 1, None # E: Argument 1 must be "Type[builtins.BaseException]" subtype
raise t2, 1 # E: Argument 1 must be "Type[builtins.BaseException]" subtype
raise t3, 1, e # E: Argument 1 must be "Type[builtins.BaseException]" subtype
raise t4, 1, 1 # E: Argument 1 must be "Type[builtins.BaseException]" subtype \
# E: Argument 3 must be "Union[types.TracebackType, None]" subtype

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

# Bare raise:

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 7143424

Please sign in to comment.