From eb4e5d6a3ac9946fa5cd70379e35e01455bd05f8 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sat, 18 Dec 2021 13:45:23 +0300 Subject: [PATCH 1/4] Revert "Revert "Now typechecks `traceback` in `raise e, msg, traceback` on py2 (#11289)" (#11743)" This reverts commit 510e0430700b5c07dfb8ee8130f09f67e3f7671b. --- mypy/checker.py | 88 ++++++++++++++++++++++++++----- mypy/fastparse2.py | 4 ++ mypy/nodes.py | 5 +- test-data/unit/check-python2.test | 67 ++++++++++++++++++++--- 4 files changed, 145 insertions(+), 19 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index b90221a0a5a5..e4c79a4c1c9f 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -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, @@ -3495,24 +3495,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. diff --git a/mypy/fastparse2.py b/mypy/fastparse2.py index 2d288bf158e5..33f34082cf83 100644 --- a/mypy/fastparse2.py +++ b/mypy/fastparse2.py @@ -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) diff --git a/mypy/nodes.py b/mypy/nodes.py index 156d756030ae..98c3582bb5c2 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -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) diff --git a/test-data/unit/check-python2.test b/test-data/unit/check-python2.test index d658fe013401..30213ba6a26c 100644 --- a/test-data/unit/check-python2.test +++ b/test-data/unit/check-python2.test @@ -68,18 +68,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 From 1ea50cdf69da6a6e7742a409c55519e4dcad7021 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sat, 18 Dec 2021 14:10:56 +0300 Subject: [PATCH 2/4] Handle `raise Exception(), None` properly, refs #11742 --- mypy/checker.py | 14 ++++-- test-data/unit/check-python2.test | 76 ++++++++++++++++++------------- 2 files changed, 55 insertions(+), 35 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index e4c79a4c1c9f..0c1453e1e186 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -3532,6 +3532,7 @@ def _type_check_raise_python2(self, e: Expression, s: RaiseStmt, typ: ProperType # 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 @@ -3539,7 +3540,7 @@ def _type_check_raise_python2(self, e: Expression, s: RaiseStmt, typ: ProperType # `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, + item, exc_inst_or_type, s, 'When raising a tuple, first element must by derived from BaseException', ) return @@ -3548,9 +3549,14 @@ def _type_check_raise_python2(self, e: Expression, s: RaiseStmt, typ: ProperType # `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 = exc_inst_or_type + else: + expected_type = TypeType(exc_type) self.check_subtype( - typ.items[0], TypeType(exc_type), s, - 'First argument must be BaseException subtype', + typ.items[0], expected_type, s, + 'Argument 1 must be "{}" subtype'.format(expected_type), ) # Typecheck `traceback` part: @@ -3565,7 +3571,7 @@ def _type_check_raise_python2(self, e: Expression, s: RaiseStmt, typ: ProperType ]) self.check_subtype( typ.items[2], traceback_type, s, - 'Third argument to raise must have "{}" type'.format(traceback_type), + 'Argument 3 must be "{}" subtype'.format(traceback_type), ) else: expected_type_items = [ diff --git a/test-data/unit/check-python2.test b/test-data/unit/check-python2.test index 30213ba6a26c..78687de2cd74 100644 --- a/test-data/unit/check-python2.test +++ b/test-data/unit/check-python2.test @@ -80,44 +80,56 @@ 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 +# 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(1), 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 # ok -raise t2 # ok -raise t3 # ok -raise t4 # ok +raise t1 +raise t2 +raise t3 +raise t4 -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 +# 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) @@ -127,6 +139,8 @@ 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: From aa36abac3045a91d32955cf2709557af7337b86d Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sat, 18 Dec 2021 14:12:38 +0300 Subject: [PATCH 3/4] A couple more tests --- test-data/unit/check-python2.test | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test-data/unit/check-python2.test b/test-data/unit/check-python2.test index 78687de2cd74..0481767abd63 100644 --- a/test-data/unit/check-python2.test +++ b/test-data/unit/check-python2.test @@ -92,7 +92,9 @@ 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,) From eb75022f47cfdc3268a8d284555c2754703cc926 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sat, 18 Dec 2021 14:26:48 +0300 Subject: [PATCH 4/4] Fixes types --- mypy/checker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index 0c1453e1e186..b080da634692 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -3551,7 +3551,7 @@ def _type_check_raise_python2(self, e: Expression, s: RaiseStmt, typ: ProperType 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 = exc_inst_or_type + expected_type: Type = exc_inst_or_type else: expected_type = TypeType(exc_type) self.check_subtype(