Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle raise Exception(), None on Python2.7 #11786

Merged
merged 4 commits into from
Feb 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
94 changes: 82 additions & 12 deletions mypy/checker.py
Expand Up @@ -3486,7 +3486,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 @@ -3495,24 +3495,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 @@ -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
Expand Up @@ -1294,16 +1294,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