Skip to content

Commit e2ab99f

Browse files
pdmccormickgnprice
authored andcommitted
Add support for tuple values on except clauses (#1610)
Fixes #1590. Also use `Union` rather than joining the exception types.
1 parent 703ca81 commit e2ab99f

File tree

2 files changed

+73
-30
lines changed

2 files changed

+73
-30
lines changed

mypy/checker.py

Lines changed: 22 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1782,7 +1782,7 @@ def visit_try_stmt(self, s: TryStmt) -> Type:
17821782
for i in range(len(s.handlers)):
17831783
self.binder.push_frame()
17841784
if s.types[i]:
1785-
t = self.exception_type(s.types[i])
1785+
t = self.visit_except_handler_test(s.types[i])
17861786
if s.vars[i]:
17871787
# To support local variables, we make this a definition line,
17881788
# causing assignment to set the variable's type.
@@ -1822,38 +1822,30 @@ def visit_try_stmt(self, s: TryStmt) -> Type:
18221822
if s.finally_body:
18231823
self.accept(s.finally_body)
18241824

1825-
def exception_type(self, n: Node) -> Type:
1826-
if isinstance(n, TupleExpr):
1827-
t = None # type: Type
1828-
for item in n.items:
1829-
tt = self.exception_type(item)
1830-
if t:
1831-
t = join_types(t, tt)
1832-
else:
1833-
t = tt
1834-
return t
1835-
else:
1836-
# A single exception type; should evaluate to a type object type.
1837-
type = self.accept(n)
1838-
return self.check_exception_type(type, n)
1839-
self.fail('Unsupported exception', n)
1840-
return AnyType()
1825+
def visit_except_handler_test(self, n: Node) -> Type:
1826+
"""Type check an exception handler test clause."""
1827+
type = self.accept(n)
1828+
if isinstance(type, AnyType):
1829+
return type
18411830

1842-
def check_exception_type(self, type: Type, context: Context) -> Type:
1843-
if isinstance(type, FunctionLike):
1844-
item = type.items()[0]
1845-
ret = item.ret_type
1846-
if (is_subtype(ret, self.named_type('builtins.BaseException'))
1831+
all_types = [] # type: List[Type]
1832+
test_types = type.items if isinstance(type, TupleType) else [type]
1833+
1834+
for ttype in test_types:
1835+
if not isinstance(ttype, FunctionLike):
1836+
self.fail(messages.INVALID_EXCEPTION_TYPE, n)
1837+
return AnyType()
1838+
1839+
item = ttype.items()[0]
1840+
ret_type = item.ret_type
1841+
if not (is_subtype(ret_type, self.named_type('builtins.BaseException'))
18471842
and item.is_type_obj()):
1848-
return ret
1849-
else:
1850-
self.fail(messages.INVALID_EXCEPTION_TYPE, context)
1843+
self.fail(messages.INVALID_EXCEPTION_TYPE, n)
18511844
return AnyType()
1852-
elif isinstance(type, AnyType):
1853-
return AnyType()
1854-
else:
1855-
self.fail(messages.INVALID_EXCEPTION_TYPE, context)
1856-
return AnyType()
1845+
1846+
all_types.append(ret_type)
1847+
1848+
return UnionType.make_simplified_union(all_types)
18571849

18581850
def visit_for_stmt(self, s: ForStmt) -> Type:
18591851
"""Type check a for statement."""

mypy/test/data/check-statements.test

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,24 @@ except (E1, E2) as e1:
573573
except (E2, E1) as e2:
574574
a = e2 # type: E1
575575
b = e2 # type: E2 # E: Incompatible types in assignment (expression has type "E1", variable has type "E2")
576+
except (E1, E2, int) as e3: # E: Exception type must be derived from BaseException
577+
pass
578+
[builtins fixtures/exception.py]
579+
580+
[case testExceptWithMultipleTypes3]
581+
import typing
582+
class E1(BaseException): pass
583+
class E1_1(E1): pass
584+
class E1_2(E1): pass
585+
try: pass
586+
except (E1, E1_1, E1_2) as e1:
587+
x = e1 # type: E1
588+
y = e1 # type: E1_1 # E: Incompatible types in assignment (expression has type "E1", variable has type "E1_1")
589+
z = e1 # type: E1_2 # E: Incompatible types in assignment (expression has type "E1", variable has type "E1_2")
590+
except (E1_1, E1_2) as e2:
591+
a = e2 # type: E1
592+
b = e2 # type: E1_1 # E: Incompatible types in assignment (expression has type "Union[E1_1, E1_2]", variable has type "E1_1")
593+
c = e2 # type: E1_2 # E: Incompatible types in assignment (expression has type "Union[E1_1, E1_2]", variable has type "E1_2")
576594
[builtins fixtures/exception.py]
577595

578596
[case testReuseTryExceptionVariable]
@@ -618,6 +636,39 @@ except exc as e: pass # E: Exception type must be derived from BaseE
618636
except BaseException() as b: pass # E: Exception type must be derived from BaseException
619637
[builtins fixtures/exception.py]
620638

639+
[case testTupleValueAsExceptionType]
640+
import typing
641+
def exc() -> BaseException: pass
642+
class E1(BaseException): pass
643+
class E1_1(E1): pass
644+
class E1_2(E1): pass
645+
646+
exs1 = (E1, E1_1, E1_2)
647+
try: pass
648+
except exs1 as e1:
649+
x = e1 # type: E1
650+
y = e1 # type: E1_1 # E: Incompatible types in assignment (expression has type "E1", variable has type "E1_1")
651+
z = e1 # type: E1_2 # E: Incompatible types in assignment (expression has type "E1", variable has type "E1_2")
652+
653+
exs2 = (E1_1, E1_2)
654+
try: pass
655+
except exs2 as e2:
656+
a = e2 # type: E1
657+
b = e2 # type: E1_1 # E: Incompatible types in assignment (expression has type "Union[E1_1, E1_2]", variable has type "E1_1")
658+
c = e2 # type: E1_2 # E: Incompatible types in assignment (expression has type "Union[E1_1, E1_2]", variable has type "E1_2")
659+
[builtins fixtures/exception.py]
660+
661+
[case testInvalidTupleValueAsExceptionType]
662+
import typing
663+
def exc() -> BaseException: pass
664+
class E1(BaseException): pass
665+
class E2(E1): pass
666+
667+
exs1 = (E1, E2, int)
668+
try: pass
669+
except exs1 as e: pass # E: Exception type must be derived from BaseException
670+
[builtins fixtures/exception.py]
671+
621672
[case testOverloadedExceptionType]
622673
from typing import overload
623674
class E(BaseException):

0 commit comments

Comments
 (0)