summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorIrit Katriel <1055913+iritkatriel@users.noreply.github.com>2021-12-14 16:48:15 (GMT)
committerGitHub <noreply@github.com>2021-12-14 16:48:15 (GMT)
commitd60457a6673cf0263213c2f2be02c633ec2e2038 (patch)
tree04461db9079cf30a98c5a4070098f795275aa910 /Lib
parent850aefc2c651110a784cd5478af9774b1f6287a3 (diff)
downloadcpython-d60457a6673cf0263213c2f2be02c633ec2e2038.zip
cpython-d60457a6673cf0263213c2f2be02c633ec2e2038.tar.gz
cpython-d60457a6673cf0263213c2f2be02c633ec2e2038.tar.bz2
bpo-45292: [PEP-654] add except* (GH-29581)
Diffstat (limited to 'Lib')
-rw-r--r--Lib/ast.py21
-rw-r--r--Lib/importlib/_bootstrap_external.py3
-rw-r--r--Lib/opcode.py3
-rw-r--r--Lib/test/test_ast.py23
-rw-r--r--Lib/test/test_compile.py33
-rw-r--r--Lib/test/test_dis.py52
-rw-r--r--Lib/test/test_except_star.py976
-rw-r--r--Lib/test/test_exception_group.py3
-rw-r--r--Lib/test/test_exception_variations.py278
-rw-r--r--Lib/test/test_exceptions.py2
-rw-r--r--Lib/test/test_grammar.py24
-rw-r--r--Lib/test/test_syntax.py100
-rw-r--r--Lib/test/test_sys_settrace.py175
-rw-r--r--Lib/test/test_unparse.py16
14 files changed, 1675 insertions, 34 deletions
diff --git a/Lib/ast.py b/Lib/ast.py
index 400d7c4..625738a 100644
--- a/Lib/ast.py
+++ b/Lib/ast.py
@@ -683,6 +683,7 @@ class _Unparser(NodeVisitor):
self._type_ignores = {}
self._indent = 0
self._avoid_backslashes = _avoid_backslashes
+ self._in_try_star = False
def interleave(self, inter, f, seq):
"""Call f on each item in seq, calling inter() in between."""
@@ -953,7 +954,7 @@ class _Unparser(NodeVisitor):
self.write(" from ")
self.traverse(node.cause)
- def visit_Try(self, node):
+ def do_visit_try(self, node):
self.fill("try")
with self.block():
self.traverse(node.body)
@@ -968,8 +969,24 @@ class _Unparser(NodeVisitor):
with self.block():
self.traverse(node.finalbody)
+ def visit_Try(self, node):
+ prev_in_try_star = self._in_try_star
+ try:
+ self._in_try_star = False
+ self.do_visit_try(node)
+ finally:
+ self._in_try_star = prev_in_try_star
+
+ def visit_TryStar(self, node):
+ prev_in_try_star = self._in_try_star
+ try:
+ self._in_try_star = True
+ self.do_visit_try(node)
+ finally:
+ self._in_try_star = prev_in_try_star
+
def visit_ExceptHandler(self, node):
- self.fill("except")
+ self.fill("except*" if self._in_try_star else "except")
if node.type:
self.write(" ")
self.traverse(node.type)
diff --git a/Lib/importlib/_bootstrap_external.py b/Lib/importlib/_bootstrap_external.py
index 6970e9f..6e7ea7b 100644
--- a/Lib/importlib/_bootstrap_external.py
+++ b/Lib/importlib/_bootstrap_external.py
@@ -371,6 +371,7 @@ _code_type = type(_write_atomic.__code__)
# Python 3.11a3 3464 (bpo-45636: Merge numeric BINARY_*/INPLACE_* into
# BINARY_OP)
# Python 3.11a3 3465 (Add COPY_FREE_VARS opcode)
+# Python 3.11a3 3466 (bpo-45292: PEP-654 except*)
#
# MAGIC must change whenever the bytecode emitted by the compiler may no
@@ -380,7 +381,7 @@ _code_type = type(_write_atomic.__code__)
# Whenever MAGIC_NUMBER is changed, the ranges in the magic_values array
# in PC/launcher.c must also be updated.
-MAGIC_NUMBER = (3465).to_bytes(2, 'little') + b'\r\n'
+MAGIC_NUMBER = (3466).to_bytes(2, 'little') + b'\r\n'
_RAW_MAGIC_NUMBER = int.from_bytes(MAGIC_NUMBER, 'little') # For import.c
_PYCACHE = '__pycache__'
diff --git a/Lib/opcode.py b/Lib/opcode.py
index e5889bc..299216d 100644
--- a/Lib/opcode.py
+++ b/Lib/opcode.py
@@ -103,6 +103,7 @@ def_op('IMPORT_STAR', 84)
def_op('SETUP_ANNOTATIONS', 85)
def_op('YIELD_VALUE', 86)
+def_op('PREP_RERAISE_STAR', 88)
def_op('POP_EXCEPT', 89)
HAVE_ARGUMENT = 90 # Opcodes from here have an argument:
@@ -150,6 +151,8 @@ haslocal.append(125)
def_op('DELETE_FAST', 126) # Local variable number
haslocal.append(126)
+jabs_op('JUMP_IF_NOT_EG_MATCH', 127)
+
def_op('GEN_START', 129) # Kind of generator/coroutine
def_op('RAISE_VARARGS', 130) # Number of raise arguments (1, 2, or 3)
def_op('CALL_FUNCTION', 131) # #args
diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py
index e630677..314b360 100644
--- a/Lib/test/test_ast.py
+++ b/Lib/test/test_ast.py
@@ -86,6 +86,8 @@ exec_tests = [
"try:\n pass\nexcept Exception:\n pass",
# TryFinally
"try:\n pass\nfinally:\n pass",
+ # TryStarExcept
+ "try:\n pass\nexcept* Exception:\n pass",
# Assert
"assert v",
# Import
@@ -1310,6 +1312,26 @@ class ASTValidatorTests(unittest.TestCase):
t = ast.Try([p], e, [p], [ast.Expr(ast.Name("x", ast.Store()))])
self.stmt(t, "must have Load context")
+ def test_try_star(self):
+ p = ast.Pass()
+ t = ast.TryStar([], [], [], [p])
+ self.stmt(t, "empty body on TryStar")
+ t = ast.TryStar([ast.Expr(ast.Name("x", ast.Store()))], [], [], [p])
+ self.stmt(t, "must have Load context")
+ t = ast.TryStar([p], [], [], [])
+ self.stmt(t, "TryStar has neither except handlers nor finalbody")
+ t = ast.TryStar([p], [], [p], [p])
+ self.stmt(t, "TryStar has orelse but no except handlers")
+ t = ast.TryStar([p], [ast.ExceptHandler(None, "x", [])], [], [])
+ self.stmt(t, "empty body on ExceptHandler")
+ e = [ast.ExceptHandler(ast.Name("x", ast.Store()), "y", [p])]
+ self.stmt(ast.TryStar([p], e, [], []), "must have Load context")
+ e = [ast.ExceptHandler(None, "x", [p])]
+ t = ast.TryStar([p], e, [ast.Expr(ast.Name("x", ast.Store()))], [p])
+ self.stmt(t, "must have Load context")
+ t = ast.TryStar([p], e, [p], [ast.Expr(ast.Name("x", ast.Store()))])
+ self.stmt(t, "must have Load context")
+
def test_assert(self):
self.stmt(ast.Assert(ast.Name("x", ast.Store()), None),
"must have Load context")
@@ -2316,6 +2338,7 @@ exec_results = [
('Module', [('Raise', (1, 0, 1, 25), ('Call', (1, 6, 1, 25), ('Name', (1, 6, 1, 15), 'Exception', ('Load',)), [('Constant', (1, 16, 1, 24), 'string', None)], []), None)], []),
('Module', [('Try', (1, 0, 4, 6), [('Pass', (2, 2, 2, 6))], [('ExceptHandler', (3, 0, 4, 6), ('Name', (3, 7, 3, 16), 'Exception', ('Load',)), None, [('Pass', (4, 2, 4, 6))])], [], [])], []),
('Module', [('Try', (1, 0, 4, 6), [('Pass', (2, 2, 2, 6))], [], [], [('Pass', (4, 2, 4, 6))])], []),
+('Module', [('TryStar', (1, 0, 4, 6), [('Pass', (2, 2, 2, 6))], [('ExceptHandler', (3, 0, 4, 6), ('Name', (3, 8, 3, 17), 'Exception', ('Load',)), None, [('Pass', (4, 2, 4, 6))])], [], [])], []),
('Module', [('Assert', (1, 0, 1, 8), ('Name', (1, 7, 1, 8), 'v', ('Load',)), None)], []),
('Module', [('Import', (1, 0, 1, 10), [('alias', (1, 7, 1, 10), 'sys', None)])], []),
('Module', [('ImportFrom', (1, 0, 1, 17), 'sys', [('alias', (1, 16, 1, 17), 'v', None)], 0)], []),
diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py
index f4ed6c7..5bd7b06 100644
--- a/Lib/test/test_compile.py
+++ b/Lib/test/test_compile.py
@@ -1263,6 +1263,39 @@ class TestStackSizeStability(unittest.TestCase):
"""
self.check_stack_size(snippet)
+ def test_try_except_star_qualified(self):
+ snippet = """
+ try:
+ a
+ except* ImportError:
+ b
+ else:
+ c
+ """
+ self.check_stack_size(snippet)
+
+ def test_try_except_star_as(self):
+ snippet = """
+ try:
+ a
+ except* ImportError as e:
+ b
+ else:
+ c
+ """
+ self.check_stack_size(snippet)
+
+ def test_try_except_star_finally(self):
+ snippet = """
+ try:
+ a
+ except* A:
+ b
+ finally:
+ c
+ """
+ self.check_stack_size(snippet)
+
def test_try_finally(self):
snippet = """
try:
diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py
index dd328f0..e821e00 100644
--- a/Lib/test/test_dis.py
+++ b/Lib/test/test_dis.py
@@ -1119,7 +1119,7 @@ expected_opinfo_jumpy = [
Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='', offset=132, starts_line=None, is_jump_target=False, positions=None),
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=134, starts_line=None, is_jump_target=False, positions=None),
Instruction(opname='POP_EXCEPT', opcode=89, arg=None, argval=None, argrepr='', offset=136, starts_line=None, is_jump_target=False, positions=None),
- Instruction(opname='JUMP_FORWARD', opcode=110, arg=33, argval=206, argrepr='to 206', offset=138, starts_line=None, is_jump_target=False, positions=None),
+ Instruction(opname='JUMP_FORWARD', opcode=110, arg=32, argval=204, argrepr='to 204', offset=138, starts_line=None, is_jump_target=False, positions=None),
Instruction(opname='RERAISE', opcode=119, arg=0, argval=0, argrepr='', offset=140, starts_line=22, is_jump_target=True, positions=None),
Instruction(opname='POP_EXCEPT_AND_RERAISE', opcode=37, arg=None, argval=None, argrepr='', offset=142, starts_line=None, is_jump_target=False, positions=None),
Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=144, starts_line=25, is_jump_target=True, positions=None),
@@ -1134,7 +1134,7 @@ expected_opinfo_jumpy = [
Instruction(opname='DUP_TOP', opcode=4, arg=None, argval=None, argrepr='', offset=162, starts_line=None, is_jump_target=False, positions=None),
Instruction(opname='CALL_FUNCTION', opcode=131, arg=3, argval=3, argrepr='', offset=164, starts_line=None, is_jump_target=False, positions=None),
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=166, starts_line=None, is_jump_target=False, positions=None),
- Instruction(opname='JUMP_FORWARD', opcode=110, arg=25, argval=220, argrepr='to 220', offset=168, starts_line=None, is_jump_target=False, positions=None),
+ Instruction(opname='JUMP_FORWARD', opcode=110, arg=11, argval=192, argrepr='to 192', offset=168, starts_line=None, is_jump_target=False, positions=None),
Instruction(opname='PUSH_EXC_INFO', opcode=35, arg=None, argval=None, argrepr='', offset=170, starts_line=None, is_jump_target=False, positions=None),
Instruction(opname='WITH_EXCEPT_START', opcode=49, arg=None, argval=None, argrepr='', offset=172, starts_line=None, is_jump_target=False, positions=None),
Instruction(opname='POP_JUMP_IF_TRUE', opcode=115, arg=90, argval=180, argrepr='to 180', offset=174, starts_line=None, is_jump_target=False, positions=None),
@@ -1146,34 +1146,26 @@ expected_opinfo_jumpy = [
Instruction(opname='POP_EXCEPT', opcode=89, arg=None, argval=None, argrepr='', offset=186, starts_line=None, is_jump_target=False, positions=None),
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=188, starts_line=None, is_jump_target=False, positions=None),
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=190, starts_line=None, is_jump_target=False, positions=None),
- Instruction(opname='NOP', opcode=9, arg=None, argval=None, argrepr='', offset=192, starts_line=None, is_jump_target=False, positions=None),
- Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=194, starts_line=28, is_jump_target=False, positions=None),
- Instruction(opname='LOAD_CONST', opcode=100, arg=10, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=196, starts_line=None, is_jump_target=False, positions=None),
- Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='', offset=198, starts_line=None, is_jump_target=False, positions=None),
- Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=200, starts_line=None, is_jump_target=False, positions=None),
- Instruction(opname='LOAD_CONST', opcode=100, arg=0, argval=None, argrepr='None', offset=202, starts_line=None, is_jump_target=False, positions=None),
- Instruction(opname='RETURN_VALUE', opcode=83, arg=None, argval=None, argrepr='', offset=204, starts_line=None, is_jump_target=False, positions=None),
- Instruction(opname='NOP', opcode=9, arg=None, argval=None, argrepr='', offset=206, starts_line=23, is_jump_target=True, positions=None),
- Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=208, starts_line=28, is_jump_target=False, positions=None),
- Instruction(opname='LOAD_CONST', opcode=100, arg=10, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=210, starts_line=None, is_jump_target=False, positions=None),
- Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='', offset=212, starts_line=None, is_jump_target=False, positions=None),
- Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=214, starts_line=None, is_jump_target=False, positions=None),
- Instruction(opname='LOAD_CONST', opcode=100, arg=0, argval=None, argrepr='None', offset=216, starts_line=None, is_jump_target=False, positions=None),
- Instruction(opname='RETURN_VALUE', opcode=83, arg=None, argval=None, argrepr='', offset=218, starts_line=None, is_jump_target=False, positions=None),
- Instruction(opname='NOP', opcode=9, arg=None, argval=None, argrepr='', offset=220, starts_line=25, is_jump_target=True, positions=None),
- Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=222, starts_line=28, is_jump_target=False, positions=None),
- Instruction(opname='LOAD_CONST', opcode=100, arg=10, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=224, starts_line=None, is_jump_target=False, positions=None),
- Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='', offset=226, starts_line=None, is_jump_target=False, positions=None),
- Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=228, starts_line=None, is_jump_target=False, positions=None),
- Instruction(opname='LOAD_CONST', opcode=100, arg=0, argval=None, argrepr='None', offset=230, starts_line=None, is_jump_target=False, positions=None),
- Instruction(opname='RETURN_VALUE', opcode=83, arg=None, argval=None, argrepr='', offset=232, starts_line=None, is_jump_target=False, positions=None),
- Instruction(opname='PUSH_EXC_INFO', opcode=35, arg=None, argval=None, argrepr='', offset=234, starts_line=None, is_jump_target=False, positions=None),
- Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=236, starts_line=None, is_jump_target=False, positions=None),
- Instruction(opname='LOAD_CONST', opcode=100, arg=10, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=238, starts_line=None, is_jump_target=False, positions=None),
- Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='', offset=240, starts_line=None, is_jump_target=False, positions=None),
- Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=242, starts_line=None, is_jump_target=False, positions=None),
- Instruction(opname='RERAISE', opcode=119, arg=0, argval=0, argrepr='', offset=244, starts_line=None, is_jump_target=False, positions=None),
- Instruction(opname='POP_EXCEPT_AND_RERAISE', opcode=37, arg=None, argval=None, argrepr='', offset=246, starts_line=None, is_jump_target=False, positions=None),
+ Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=192, starts_line=28, is_jump_target=True, positions=None),
+ Instruction(opname='LOAD_CONST', opcode=100, arg=10, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=194, starts_line=None, is_jump_target=False, positions=None),
+ Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='', offset=196, starts_line=None, is_jump_target=False, positions=None),
+ Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=198, starts_line=None, is_jump_target=False, positions=None),
+ Instruction(opname='LOAD_CONST', opcode=100, arg=0, argval=None, argrepr='None', offset=200, starts_line=None, is_jump_target=False, positions=None),
+ Instruction(opname='RETURN_VALUE', opcode=83, arg=None, argval=None, argrepr='', offset=202, starts_line=None, is_jump_target=False, positions=None),
+ Instruction(opname='NOP', opcode=9, arg=None, argval=None, argrepr='', offset=204, starts_line=23, is_jump_target=True, positions=None),
+ Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=206, starts_line=28, is_jump_target=False, positions=None),
+ Instruction(opname='LOAD_CONST', opcode=100, arg=10, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=208, starts_line=None, is_jump_target=False, positions=None),
+ Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='', offset=210, starts_line=None, is_jump_target=False, positions=None),
+ Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=212, starts_line=None, is_jump_target=False, positions=None),
+ Instruction(opname='LOAD_CONST', opcode=100, arg=0, argval=None, argrepr='None', offset=214, starts_line=None, is_jump_target=False, positions=None),
+ Instruction(opname='RETURN_VALUE', opcode=83, arg=None, argval=None, argrepr='', offset=216, starts_line=None, is_jump_target=False, positions=None),
+ Instruction(opname='PUSH_EXC_INFO', opcode=35, arg=None, argval=None, argrepr='', offset=218, starts_line=None, is_jump_target=False, positions=None),
+ Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=220, starts_line=None, is_jump_target=False, positions=None),
+ Instruction(opname='LOAD_CONST', opcode=100, arg=10, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=222, starts_line=None, is_jump_target=False, positions=None),
+ Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='', offset=224, starts_line=None, is_jump_target=False, positions=None),
+ Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=226, starts_line=None, is_jump_target=False, positions=None),
+ Instruction(opname='RERAISE', opcode=119, arg=0, argval=0, argrepr='', offset=228, starts_line=None, is_jump_target=False, positions=None),
+ Instruction(opname='POP_EXCEPT_AND_RERAISE', opcode=37, arg=None, argval=None, argrepr='', offset=230, starts_line=None, is_jump_target=False, positions=None),
]
# One last piece of inspect fodder to check the default line number handling
diff --git a/Lib/test/test_except_star.py b/Lib/test/test_except_star.py
new file mode 100644
index 0000000..b03de9c
--- /dev/null
+++ b/Lib/test/test_except_star.py
@@ -0,0 +1,976 @@
+import sys
+import unittest
+import textwrap
+
+class TestInvalidExceptStar(unittest.TestCase):
+ def test_mixed_except_and_except_star_is_syntax_error(self):
+ errors = [
+ "try: pass\nexcept ValueError: pass\nexcept* TypeError: pass\n",
+ "try: pass\nexcept* ValueError: pass\nexcept TypeError: pass\n",
+ "try: pass\nexcept ValueError as e: pass\nexcept* TypeError: pass\n",
+ "try: pass\nexcept* ValueError as e: pass\nexcept TypeError: pass\n",
+ "try: pass\nexcept ValueError: pass\nexcept* TypeError as e: pass\n",
+ "try: pass\nexcept* ValueError: pass\nexcept TypeError as e: pass\n",
+ "try: pass\nexcept ValueError: pass\nexcept*: pass\n",
+ "try: pass\nexcept* ValueError: pass\nexcept: pass\n",
+ ]
+
+ for err in errors:
+ with self.assertRaises(SyntaxError):
+ compile(err, "<string>", "exec")
+
+ def test_except_star_ExceptionGroup_is_runtime_error_single(self):
+ with self.assertRaises(TypeError):
+ try:
+ raise OSError("blah")
+ except* ExceptionGroup as e:
+ pass
+
+ def test_except_star_ExceptionGroup_is_runtime_error_tuple(self):
+ with self.assertRaises(TypeError):
+ try:
+ raise ExceptionGroup("eg", [ValueError(42)])
+ except* (TypeError, ExceptionGroup):
+ pass
+
+ def test_except_star_invalid_exception_type(self):
+ with self.assertRaises(TypeError):
+ try:
+ raise ValueError
+ except* 42:
+ pass
+
+ with self.assertRaises(TypeError):
+ try:
+ raise ValueError
+ except* (ValueError, 42):
+ pass
+
+
+class TestBreakContinueReturnInExceptStarBlock(unittest.TestCase):
+ MSG = (r"'break', 'continue' and 'return'"
+ r" cannot appear in an except\* block")
+
+ def check_invalid(self, src):
+ with self.assertRaisesRegex(SyntaxError, self.MSG):
+ compile(textwrap.dedent(src), "<string>", "exec")
+
+ def test_break_in_except_star(self):
+ self.check_invalid(
+ """
+ try:
+ raise ValueError
+ except* Exception as e:
+ break
+ """)
+
+ self.check_invalid(
+ """
+ for i in range(5):
+ try:
+ pass
+ except* Exception as e:
+ if i == 2:
+ break
+ """)
+
+ self.check_invalid(
+ """
+ for i in range(5):
+ try:
+ pass
+ except* Exception as e:
+ if i == 2:
+ break
+ finally:
+ return 0
+ """)
+
+
+ def test_continue_in_except_star_block_invalid(self):
+ self.check_invalid(
+ """
+ for i in range(5):
+ try:
+ raise ValueError
+ except* Exception as e:
+ continue
+ """)
+
+ self.check_invalid(
+ """
+ for i in range(5):
+ try:
+ pass
+ except* Exception as e:
+ if i == 2:
+ continue
+ """)
+
+ self.check_invalid(
+ """
+ for i in range(5):
+ try:
+ pass
+ except* Exception as e:
+ if i == 2:
+ continue
+ finally:
+ return 0
+ """)
+
+ def test_return_in_except_star_block_invalid(self):
+ self.check_invalid(
+ """
+ def f():
+ try:
+ raise ValueError
+ except* Exception as e:
+ return 42
+ """)
+
+ self.check_invalid(
+ """
+ def f():
+ try:
+ pass
+ except* Exception as e:
+ return 42
+ finally:
+ finished = True
+ """)
+
+ def test_break_continue_in_except_star_block_valid(self):
+ try:
+ raise ValueError(42)
+ except* Exception as e:
+ count = 0
+ for i in range(5):
+ if i == 0:
+ continue
+ if i == 4:
+ break
+ count += 1
+
+ self.assertEqual(count, 3)
+ self.assertEqual(i, 4)
+ exc = e
+ self.assertIsInstance(exc, ExceptionGroup)
+
+ def test_return_in_except_star_block_valid(self):
+ try:
+ raise ValueError(42)
+ except* Exception as e:
+ def f(x):
+ return 2*x
+ r = f(3)
+ exc = e
+ self.assertEqual(r, 6)
+ self.assertIsInstance(exc, ExceptionGroup)
+
+
+class ExceptStarTest(unittest.TestCase):
+ def assertExceptionIsLike(self, exc, template):
+ if exc is None and template is None:
+ return
+
+ if template is None:
+ self.fail(f"unexpected exception: {exc}")
+
+ if exc is None:
+ self.fail(f"expected an exception like {template!r}, got None")
+
+ if not isinstance(exc, ExceptionGroup):
+ self.assertEqual(exc.__class__, template.__class__)
+ self.assertEqual(exc.args[0], template.args[0])
+ else:
+ self.assertEqual(exc.message, template.message)
+ self.assertEqual(len(exc.exceptions), len(template.exceptions))
+ for e, t in zip(exc.exceptions, template.exceptions):
+ self.assertExceptionIsLike(e, t)
+
+ def assertMetadataEqual(self, e1, e2):
+ if e1 is None or e2 is None:
+ self.assertTrue(e1 is None and e2 is None)
+ else:
+ self.assertEqual(e1.__context__, e2.__context__)
+ self.assertEqual(e1.__cause__, e2.__cause__)
+ self.assertEqual(e1.__traceback__, e2.__traceback__)
+
+ def assertMetadataNotEqual(self, e1, e2):
+ if e1 is None or e2 is None:
+ self.assertNotEqual(e1, e2)
+ else:
+ return not (e1.__context__ == e2.__context__
+ and e1.__cause__ == e2.__cause__
+ and e1.__traceback__ == e2.__traceback__)
+
+
+class TestExceptStarSplitSemantics(ExceptStarTest):
+ def doSplitTestNamed(self, exc, T, match_template, rest_template):
+ initial_exc_info = sys.exc_info()
+ exc_info = match = rest = None
+ try:
+ try:
+ raise exc
+ except* T as e:
+ exc_info = sys.exc_info()
+ match = e
+ except BaseException as e:
+ rest = e
+
+ if match_template:
+ self.assertEqual(exc_info[1], match)
+ else:
+ self.assertIsNone(exc_info)
+ self.assertExceptionIsLike(match, match_template)
+ self.assertExceptionIsLike(rest, rest_template)
+ self.assertEqual(sys.exc_info(), initial_exc_info)
+
+ def doSplitTestUnnamed(self, exc, T, match_template, rest_template):
+ initial_exc_info = sys.exc_info()
+ exc_info = match = rest = None
+ try:
+ try:
+ raise exc
+ except* T:
+ exc_info = sys.exc_info()
+ match = sys.exc_info()[1]
+ else:
+ if rest_template:
+ self.fail("Exception not raised")
+ except BaseException as e:
+ rest = e
+ self.assertExceptionIsLike(match, match_template)
+ if match_template:
+ self.assertEqual(exc_info[0], type(match_template))
+ self.assertExceptionIsLike(rest, rest_template)
+ self.assertEqual(sys.exc_info(), initial_exc_info)
+
+ def doSplitTestInExceptHandler(self, exc, T, match_template, rest_template):
+ try:
+ raise ExceptionGroup('eg', [TypeError(1), ValueError(2)])
+ except Exception:
+ self.doSplitTestNamed(exc, T, match_template, rest_template)
+ self.doSplitTestUnnamed(exc, T, match_template, rest_template)
+
+ def doSplitTestInExceptStarHandler(self, exc, T, match_template, rest_template):
+ try:
+ raise ExceptionGroup('eg', [TypeError(1), ValueError(2)])
+ except* Exception:
+ self.doSplitTestNamed(exc, T, match_template, rest_template)
+ self.doSplitTestUnnamed(exc, T, match_template, rest_template)
+
+ def doSplitTest(self, exc, T, match_template, rest_template):
+ self.doSplitTestNamed(exc, T, match_template, rest_template)
+ self.doSplitTestUnnamed(exc, T, match_template, rest_template)
+ self.doSplitTestInExceptHandler(exc, T, match_template, rest_template)
+ self.doSplitTestInExceptStarHandler(exc, T, match_template, rest_template)
+
+ def test_no_match_single_type(self):
+ self.doSplitTest(
+ ExceptionGroup("test1", [ValueError("V"), TypeError("T")]),
+ SyntaxError,
+ None,
+ ExceptionGroup("test1", [ValueError("V"), TypeError("T")]))
+
+ def test_match_single_type(self):
+ self.doSplitTest(
+ ExceptionGroup("test2", [ValueError("V1"), ValueError("V2")]),
+ ValueError,
+ ExceptionGroup("test2", [ValueError("V1"), ValueError("V2")]),
+ None)
+
+ def test_match_single_type_partial_match(self):
+ self.doSplitTest(
+ ExceptionGroup(
+ "test3",
+ [ValueError("V1"), OSError("OS"), ValueError("V2")]),
+ ValueError,
+ ExceptionGroup("test3", [ValueError("V1"), ValueError("V2")]),
+ ExceptionGroup("test3", [OSError("OS")]))
+
+ def test_match_single_type_nested(self):
+ self.doSplitTest(
+ ExceptionGroup(
+ "g1", [
+ ValueError("V1"),
+ OSError("OS1"),
+ ExceptionGroup(
+ "g2", [
+ OSError("OS2"),
+ ValueError("V2"),
+ TypeError("T")])]),
+ ValueError,
+ ExceptionGroup(
+ "g1", [
+ ValueError("V1"),
+ ExceptionGroup("g2", [ValueError("V2")])]),
+ ExceptionGroup("g1", [
+ OSError("OS1"),
+ ExceptionGroup("g2", [
+ OSError("OS2"), TypeError("T")])]))
+
+ def test_match_type_tuple_nested(self):
+ self.doSplitTest(
+ ExceptionGroup(
+ "h1", [
+ ValueError("V1"),
+ OSError("OS1"),
+ ExceptionGroup(
+ "h2", [OSError("OS2"), ValueError("V2"), TypeError("T")])]),
+ (ValueError, TypeError),
+ ExceptionGroup(
+ "h1", [
+ ValueError("V1"),
+ ExceptionGroup("h2", [ValueError("V2"), TypeError("T")])]),
+ ExceptionGroup(
+ "h1", [
+ OSError("OS1"),
+ ExceptionGroup("h2", [OSError("OS2")])]))
+
+ def test_empty_groups_removed(self):
+ self.doSplitTest(
+ ExceptionGroup(
+ "eg", [
+ ExceptionGroup("i1", [ValueError("V1")]),
+ ExceptionGroup("i2", [ValueError("V2"), TypeError("T1")]),
+ ExceptionGroup("i3", [TypeError("T2")])]),
+ TypeError,
+ ExceptionGroup("eg", [
+ ExceptionGroup("i2", [TypeError("T1")]),
+ ExceptionGroup("i3", [TypeError("T2")])]),
+ ExceptionGroup("eg", [
+ ExceptionGroup("i1", [ValueError("V1")]),
+ ExceptionGroup("i2", [ValueError("V2")])]))
+
+ def test_singleton_groups_are_kept(self):
+ self.doSplitTest(
+ ExceptionGroup("j1", [
+ ExceptionGroup("j2", [
+ ExceptionGroup("j3", [ValueError("V1")]),
+ ExceptionGroup("j4", [TypeError("T")])])]),
+ TypeError,
+ ExceptionGroup(
+ "j1",
+ [ExceptionGroup("j2", [ExceptionGroup("j4", [TypeError("T")])])]),
+ ExceptionGroup(
+ "j1",
+ [ExceptionGroup("j2", [ExceptionGroup("j3", [ValueError("V1")])])]))
+
+ def test_naked_exception_matched_wrapped1(self):
+ self.doSplitTest(
+ ValueError("V"),
+ ValueError,
+ ExceptionGroup("", [ValueError("V")]),
+ None)
+
+ def test_naked_exception_matched_wrapped2(self):
+ self.doSplitTest(
+ ValueError("V"),
+ Exception,
+ ExceptionGroup("", [ValueError("V")]),
+ None)
+
+ def test_exception_group_except_star_Exception_not_wrapped(self):
+ self.doSplitTest(
+ ExceptionGroup("eg", [ValueError("V")]),
+ Exception,
+ ExceptionGroup("eg", [ValueError("V")]),
+ None)
+
+ def test_plain_exception_not_matched(self):
+ self.doSplitTest(
+ ValueError("V"),
+ TypeError,
+ None,
+ ValueError("V"))
+
+ def test_match__supertype(self):
+ self.doSplitTest(
+ ExceptionGroup("st", [BlockingIOError("io"), TypeError("T")]),
+ OSError,
+ ExceptionGroup("st", [BlockingIOError("io")]),
+ ExceptionGroup("st", [TypeError("T")]))
+
+ def test_multiple_matches_named(self):
+ try:
+ raise ExceptionGroup("mmn", [OSError("os"), BlockingIOError("io")])
+ except* BlockingIOError as e:
+ self.assertExceptionIsLike(e,
+ ExceptionGroup("mmn", [BlockingIOError("io")]))
+ except* OSError as e:
+ self.assertExceptionIsLike(e,
+ ExceptionGroup("mmn", [OSError("os")]))
+ else:
+ self.fail("Exception not raised")
+
+ def test_multiple_matches_unnamed(self):
+ try:
+ raise ExceptionGroup("mmu", [OSError("os"), BlockingIOError("io")])
+ except* BlockingIOError:
+ e = sys.exc_info()[1]
+ self.assertExceptionIsLike(e,
+ ExceptionGroup("mmu", [BlockingIOError("io")]))
+ except* OSError:
+ e = sys.exc_info()[1]
+ self.assertExceptionIsLike(e,
+ ExceptionGroup("mmu", [OSError("os")]))
+ else:
+ self.fail("Exception not raised")
+
+ def test_first_match_wins_named(self):
+ try:
+ raise ExceptionGroup("fst", [BlockingIOError("io")])
+ except* OSError as e:
+ self.assertExceptionIsLike(e,
+ ExceptionGroup("fst", [BlockingIOError("io")]))
+ except* BlockingIOError:
+ self.fail("Should have been matched as OSError")
+ else:
+ self.fail("Exception not raised")
+
+ def test_first_match_wins_unnamed(self):
+ try:
+ raise ExceptionGroup("fstu", [BlockingIOError("io")])
+ except* OSError:
+ e = sys.exc_info()[1]
+ self.assertExceptionIsLike(e,
+ ExceptionGroup("fstu", [BlockingIOError("io")]))
+ except* BlockingIOError:
+ pass
+ else:
+ self.fail("Exception not raised")
+
+ def test_nested_except_stars(self):
+ try:
+ raise ExceptionGroup("n", [BlockingIOError("io")])
+ except* BlockingIOError:
+ try:
+ raise ExceptionGroup("n", [ValueError("io")])
+ except* ValueError:
+ pass
+ else:
+ self.fail("Exception not raised")
+ e = sys.exc_info()[1]
+ self.assertExceptionIsLike(e,
+ ExceptionGroup("n", [BlockingIOError("io")]))
+ else:
+ self.fail("Exception not raised")
+
+ def test_nested_in_loop(self):
+ for _ in range(2):
+ try:
+ raise ExceptionGroup("nl", [BlockingIOError("io")])
+ except* BlockingIOError:
+ pass
+ else:
+ self.fail("Exception not raised")
+
+
+class TestExceptStarReraise(ExceptStarTest):
+ def test_reraise_all_named(self):
+ try:
+ try:
+ raise ExceptionGroup(
+ "eg", [TypeError(1), ValueError(2), OSError(3)])
+ except* TypeError as e:
+ raise
+ except* ValueError as e:
+ raise
+ # OSError not handled
+ except ExceptionGroup as e:
+ exc = e
+
+ self.assertExceptionIsLike(
+ exc,
+ ExceptionGroup("eg", [TypeError(1), ValueError(2), OSError(3)]))
+
+ def test_reraise_all_unnamed(self):
+ try:
+ try:
+ raise ExceptionGroup(
+ "eg", [TypeError(1), ValueError(2), OSError(3)])
+ except* TypeError:
+ raise
+ except* ValueError:
+ raise
+ # OSError not handled
+ except ExceptionGroup as e:
+ exc = e
+
+ self.assertExceptionIsLike(
+ exc,
+ ExceptionGroup("eg", [TypeError(1), ValueError(2), OSError(3)]))
+
+ def test_reraise_some_handle_all_named(self):
+ try:
+ try:
+ raise ExceptionGroup(
+ "eg", [TypeError(1), ValueError(2), OSError(3)])
+ except* TypeError as e:
+ raise
+ except* ValueError as e:
+ pass
+ # OSError not handled
+ except ExceptionGroup as e:
+ exc = e
+
+ self.assertExceptionIsLike(
+ exc, ExceptionGroup("eg", [TypeError(1), OSError(3)]))
+
+ def test_reraise_partial_handle_all_unnamed(self):
+ try:
+ try:
+ raise ExceptionGroup(
+ "eg", [TypeError(1), ValueError(2)])
+ except* TypeError:
+ raise
+ except* ValueError:
+ pass
+ except ExceptionGroup as e:
+ exc = e
+
+ self.assertExceptionIsLike(
+ exc, ExceptionGroup("eg", [TypeError(1)]))
+
+ def test_reraise_partial_handle_some_named(self):
+ try:
+ try:
+ raise ExceptionGroup(
+ "eg", [TypeError(1), ValueError(2), OSError(3)])
+ except* TypeError as e:
+ raise
+ except* ValueError as e:
+ pass
+ # OSError not handled
+ except ExceptionGroup as e:
+ exc = e
+
+ self.assertExceptionIsLike(
+ exc, ExceptionGroup("eg", [TypeError(1), OSError(3)]))
+
+ def test_reraise_partial_handle_some_unnamed(self):
+ try:
+ try:
+ raise ExceptionGroup(
+ "eg", [TypeError(1), ValueError(2), OSError(3)])
+ except* TypeError:
+ raise
+ except* ValueError:
+ pass
+ except ExceptionGroup as e:
+ exc = e
+
+ self.assertExceptionIsLike(
+ exc, ExceptionGroup("eg", [TypeError(1), OSError(3)]))
+
+ def test_reraise_plain_exception_named(self):
+ try:
+ try:
+ raise ValueError(42)
+ except* ValueError as e:
+ raise
+ except ExceptionGroup as e:
+ exc = e
+
+ self.assertExceptionIsLike(
+ exc, ExceptionGroup("", [ValueError(42)]))
+
+ def test_reraise_plain_exception_unnamed(self):
+ try:
+ try:
+ raise ValueError(42)
+ except* ValueError:
+ raise
+ except ExceptionGroup as e:
+ exc = e
+
+ self.assertExceptionIsLike(
+ exc, ExceptionGroup("", [ValueError(42)]))
+
+
+class TestExceptStarRaise(ExceptStarTest):
+ def test_raise_named(self):
+ orig = ExceptionGroup("eg", [ValueError(1), OSError(2)])
+ try:
+ try:
+ raise orig
+ except* OSError as e:
+ raise TypeError(3)
+ except ExceptionGroup as e:
+ exc = e
+
+ self.assertExceptionIsLike(
+ exc,
+ ExceptionGroup(
+ "", [TypeError(3), ExceptionGroup("eg", [ValueError(1)])]))
+
+ self.assertExceptionIsLike(
+ exc.exceptions[0].__context__,
+ ExceptionGroup("eg", [OSError(2)]))
+
+ self.assertMetadataNotEqual(orig, exc)
+ self.assertMetadataEqual(orig, exc.exceptions[0].__context__)
+
+ def test_raise_unnamed(self):
+ orig = ExceptionGroup("eg", [ValueError(1), OSError(2)])
+ try:
+ try:
+ raise orig
+ except* OSError:
+ raise TypeError(3)
+ except ExceptionGroup as e:
+ exc = e
+
+ self.assertExceptionIsLike(
+ exc,
+ ExceptionGroup(
+ "", [TypeError(3), ExceptionGroup("eg", [ValueError(1)])]))
+
+ self.assertExceptionIsLike(
+ exc.exceptions[0].__context__,
+ ExceptionGroup("eg", [OSError(2)]))
+
+ self.assertMetadataNotEqual(orig, exc)
+ self.assertMetadataEqual(orig, exc.exceptions[0].__context__)
+
+ def test_raise_handle_all_raise_one_named(self):
+ orig = ExceptionGroup("eg", [TypeError(1), ValueError(2)])
+ try:
+ try:
+ raise orig
+ except* (TypeError, ValueError) as e:
+ raise SyntaxError(3)
+ except BaseException as e:
+ exc = e
+
+ self.assertExceptionIsLike(
+ exc, ExceptionGroup("", [SyntaxError(3)]))
+
+ self.assertExceptionIsLike(
+ exc.exceptions[0].__context__,
+ ExceptionGroup("eg", [TypeError(1), ValueError(2)]))
+
+ self.assertMetadataNotEqual(orig, exc)
+ self.assertMetadataEqual(orig, exc.exceptions[0].__context__)
+
+ def test_raise_handle_all_raise_one_unnamed(self):
+ orig = ExceptionGroup("eg", [TypeError(1), ValueError(2)])
+ try:
+ try:
+ raise orig
+ except* (TypeError, ValueError) as e:
+ raise SyntaxError(3)
+ except ExceptionGroup as e:
+ exc = e
+
+ self.assertExceptionIsLike(
+ exc, ExceptionGroup("", [SyntaxError(3)]))
+
+ self.assertExceptionIsLike(
+ exc.exceptions[0].__context__,
+ ExceptionGroup("eg", [TypeError(1), ValueError(2)]))
+
+ self.assertMetadataNotEqual(orig, exc)
+ self.assertMetadataEqual(orig, exc.exceptions[0].__context__)
+
+ def test_raise_handle_all_raise_two_named(self):
+ orig = ExceptionGroup("eg", [TypeError(1), ValueError(2)])
+ try:
+ try:
+ raise orig
+ except* TypeError as e:
+ raise SyntaxError(3)
+ except* ValueError as e:
+ raise SyntaxError(4)
+ except ExceptionGroup as e:
+ exc = e
+
+ self.assertExceptionIsLike(
+ exc, ExceptionGroup("", [SyntaxError(3), SyntaxError(4)]))
+
+ self.assertExceptionIsLike(
+ exc.exceptions[0].__context__,
+ ExceptionGroup("eg", [TypeError(1)]))
+
+ self.assertExceptionIsLike(
+ exc.exceptions[1].__context__,
+ ExceptionGroup("eg", [ValueError(2)]))
+
+ self.assertMetadataNotEqual(orig, exc)
+ self.assertMetadataEqual(orig, exc.exceptions[0].__context__)
+ self.assertMetadataEqual(orig, exc.exceptions[1].__context__)
+
+ def test_raise_handle_all_raise_two_unnamed(self):
+ orig = ExceptionGroup("eg", [TypeError(1), ValueError(2)])
+ try:
+ try:
+ raise orig
+ except* TypeError:
+ raise SyntaxError(3)
+ except* ValueError:
+ raise SyntaxError(4)
+ except ExceptionGroup as e:
+ exc = e
+
+ self.assertExceptionIsLike(
+ exc, ExceptionGroup("", [SyntaxError(3), SyntaxError(4)]))
+
+ self.assertExceptionIsLike(
+ exc.exceptions[0].__context__,
+ ExceptionGroup("eg", [TypeError(1)]))
+
+ self.assertExceptionIsLike(
+ exc.exceptions[1].__context__,
+ ExceptionGroup("eg", [ValueError(2)]))
+
+ self.assertMetadataNotEqual(orig, exc)
+ self.assertMetadataEqual(orig, exc.exceptions[0].__context__)
+ self.assertMetadataEqual(orig, exc.exceptions[1].__context__)
+
+
+class TestExceptStarRaiseFrom(ExceptStarTest):
+ def test_raise_named(self):
+ orig = ExceptionGroup("eg", [ValueError(1), OSError(2)])
+ try:
+ try:
+ raise orig
+ except* OSError as e:
+ raise TypeError(3) from e
+ except ExceptionGroup as e:
+ exc = e
+
+ self.assertExceptionIsLike(
+ exc,
+ ExceptionGroup(
+ "", [TypeError(3), ExceptionGroup("eg", [ValueError(1)])]))
+
+ self.assertExceptionIsLike(
+ exc.exceptions[0].__context__,
+ ExceptionGroup("eg", [OSError(2)]))
+
+ self.assertExceptionIsLike(
+ exc.exceptions[0].__cause__,
+ ExceptionGroup("eg", [OSError(2)]))
+
+ self.assertMetadataNotEqual(orig, exc)
+ self.assertMetadataEqual(orig, exc.exceptions[0].__context__)
+ self.assertMetadataEqual(orig, exc.exceptions[0].__cause__)
+ self.assertMetadataNotEqual(orig, exc.exceptions[1].__context__)
+ self.assertMetadataNotEqual(orig, exc.exceptions[1].__cause__)
+
+ def test_raise_unnamed(self):
+ orig = ExceptionGroup("eg", [ValueError(1), OSError(2)])
+ try:
+ try:
+ raise orig
+ except* OSError:
+ e = sys.exc_info()[1]
+ raise TypeError(3) from e
+ except ExceptionGroup as e:
+ exc = e
+
+ self.assertExceptionIsLike(
+ exc,
+ ExceptionGroup(
+ "", [TypeError(3), ExceptionGroup("eg", [ValueError(1)])]))
+
+ self.assertExceptionIsLike(
+ exc.exceptions[0].__context__,
+ ExceptionGroup("eg", [OSError(2)]))
+
+ self.assertExceptionIsLike(
+ exc.exceptions[0].__cause__,
+ ExceptionGroup("eg", [OSError(2)]))
+
+ self.assertMetadataNotEqual(orig, exc)
+ self.assertMetadataEqual(orig, exc.exceptions[0].__context__)
+ self.assertMetadataEqual(orig, exc.exceptions[0].__cause__)
+ self.assertMetadataNotEqual(orig, exc.exceptions[1].__context__)
+ self.assertMetadataNotEqual(orig, exc.exceptions[1].__cause__)
+
+ def test_raise_handle_all_raise_one_named(self):
+ orig = ExceptionGroup("eg", [TypeError(1), ValueError(2)])
+ try:
+ try:
+ raise orig
+ except* (TypeError, ValueError) as e:
+ raise SyntaxError(3) from e
+ except BaseException as e:
+ exc = e
+
+ self.assertExceptionIsLike(
+ exc, ExceptionGroup("", [SyntaxError(3)]))
+
+ self.assertExceptionIsLike(
+ exc.exceptions[0].__context__,
+ ExceptionGroup("eg", [TypeError(1), ValueError(2)]))
+
+ self.assertExceptionIsLike(
+ exc.exceptions[0].__cause__,
+ ExceptionGroup("eg", [TypeError(1), ValueError(2)]))
+
+ self.assertMetadataNotEqual(orig, exc)
+ self.assertMetadataEqual(orig, exc.exceptions[0].__context__)
+ self.assertMetadataEqual(orig, exc.exceptions[0].__cause__)
+
+ def test_raise_handle_all_raise_one_unnamed(self):
+ orig = ExceptionGroup("eg", [TypeError(1), ValueError(2)])
+ try:
+ try:
+ raise orig
+ except* (TypeError, ValueError) as e:
+ e = sys.exc_info()[1]
+ raise SyntaxError(3) from e
+ except ExceptionGroup as e:
+ exc = e
+
+ self.assertExceptionIsLike(
+ exc, ExceptionGroup("", [SyntaxError(3)]))
+
+ self.assertExceptionIsLike(
+ exc.exceptions[0].__context__,
+ ExceptionGroup("eg", [TypeError(1), ValueError(2)]))
+
+ self.assertExceptionIsLike(
+ exc.exceptions[0].__cause__,
+ ExceptionGroup("eg", [TypeError(1), ValueError(2)]))
+
+ self.assertMetadataNotEqual(orig, exc)
+ self.assertMetadataEqual(orig, exc.exceptions[0].__context__)
+ self.assertMetadataEqual(orig, exc.exceptions[0].__cause__)
+
+ def test_raise_handle_all_raise_two_named(self):
+ orig = ExceptionGroup("eg", [TypeError(1), ValueError(2)])
+ try:
+ try:
+ raise orig
+ except* TypeError as e:
+ raise SyntaxError(3) from e
+ except* ValueError as e:
+ raise SyntaxError(4) from e
+ except ExceptionGroup as e:
+ exc = e
+
+ self.assertExceptionIsLike(
+ exc, ExceptionGroup("", [SyntaxError(3), SyntaxError(4)]))
+
+ self.assertExceptionIsLike(
+ exc.exceptions[0].__context__,
+ ExceptionGroup("eg", [TypeError(1)]))
+
+ self.assertExceptionIsLike(
+ exc.exceptions[0].__cause__,
+ ExceptionGroup("eg", [TypeError(1)]))
+
+ self.assertExceptionIsLike(
+ exc.exceptions[1].__context__,
+ ExceptionGroup("eg", [ValueError(2)]))
+
+ self.assertExceptionIsLike(
+ exc.exceptions[1].__cause__,
+ ExceptionGroup("eg", [ValueError(2)]))
+
+ self.assertMetadataNotEqual(orig, exc)
+ self.assertMetadataEqual(orig, exc.exceptions[0].__context__)
+ self.assertMetadataEqual(orig, exc.exceptions[0].__cause__)
+
+ def test_raise_handle_all_raise_two_unnamed(self):
+ orig = ExceptionGroup("eg", [TypeError(1), ValueError(2)])
+ try:
+ try:
+ raise orig
+ except* TypeError:
+ e = sys.exc_info()[1]
+ raise SyntaxError(3) from e
+ except* ValueError:
+ e = sys.exc_info()[1]
+ raise SyntaxError(4) from e
+ except ExceptionGroup as e:
+ exc = e
+
+ self.assertExceptionIsLike(
+ exc, ExceptionGroup("", [SyntaxError(3), SyntaxError(4)]))
+
+ self.assertExceptionIsLike(
+ exc.exceptions[0].__context__,
+ ExceptionGroup("eg", [TypeError(1)]))
+
+ self.assertExceptionIsLike(
+ exc.exceptions[0].__cause__,
+ ExceptionGroup("eg", [TypeError(1)]))
+
+ self.assertExceptionIsLike(
+ exc.exceptions[1].__context__,
+ ExceptionGroup("eg", [ValueError(2)]))
+
+ self.assertExceptionIsLike(
+ exc.exceptions[1].__cause__,
+ ExceptionGroup("eg", [ValueError(2)]))
+
+ self.assertMetadataNotEqual(orig, exc)
+ self.assertMetadataEqual(orig, exc.exceptions[0].__context__)
+ self.assertMetadataEqual(orig, exc.exceptions[0].__cause__)
+ self.assertMetadataEqual(orig, exc.exceptions[1].__context__)
+ self.assertMetadataEqual(orig, exc.exceptions[1].__cause__)
+
+
+class TestExceptStarExceptionGroupSubclass(ExceptStarTest):
+ def test_except_star_EG_subclass(self):
+ class EG(ExceptionGroup):
+ def __new__(cls, message, excs, code):
+ obj = super().__new__(cls, message, excs)
+ obj.code = code
+ return obj
+
+ def derive(self, excs):
+ return EG(self.message, excs, self.code)
+
+ try:
+ try:
+ try:
+ try:
+ raise TypeError(2)
+ except TypeError as te:
+ raise EG("nested", [te], 101) from None
+ except EG as nested:
+ try:
+ raise ValueError(1)
+ except ValueError as ve:
+ raise EG("eg", [ve, nested], 42)
+ except* ValueError as eg:
+ veg = eg
+ except EG as eg:
+ teg = eg
+
+ self.assertIsInstance(veg, EG)
+ self.assertIsInstance(teg, EG)
+ self.assertIsInstance(teg.exceptions[0], EG)
+ self.assertMetadataEqual(veg, teg)
+ self.assertEqual(veg.code, 42)
+ self.assertEqual(teg.code, 42)
+ self.assertEqual(teg.exceptions[0].code, 101)
+
+
+class TestExceptStarCleanup(ExceptStarTest):
+ def test_exc_info_restored(self):
+ try:
+ try:
+ raise ValueError(42)
+ except:
+ try:
+ raise TypeError(int)
+ except* Exception:
+ pass
+ 1/0
+ except Exception as e:
+ exc = e
+
+ self.assertExceptionIsLike(exc, ZeroDivisionError('division by zero'))
+ self.assertExceptionIsLike(exc.__context__, ValueError(42))
+ self.assertEqual(sys.exc_info(), (None, None, None))
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/Lib/test/test_exception_group.py b/Lib/test/test_exception_group.py
index 62ec1ee..c793c45 100644
--- a/Lib/test/test_exception_group.py
+++ b/Lib/test/test_exception_group.py
@@ -211,7 +211,8 @@ class ExceptionGroupSubgroupTests(ExceptionGroupTestBase):
def test_basics_subgroup_split__bad_arg_type(self):
bad_args = ["bad arg",
OSError('instance not type'),
- [OSError('instance not type')],]
+ [OSError, TypeError],
+ (OSError, 42)]
for arg in bad_args:
with self.assertRaises(TypeError):
self.eg.subgroup(arg)
diff --git a/Lib/test/test_exception_variations.py b/Lib/test/test_exception_variations.py
index d874b0e..aabac6d 100644
--- a/Lib/test/test_exception_variations.py
+++ b/Lib/test/test_exception_variations.py
@@ -172,5 +172,283 @@ class ExceptionTestCase(unittest.TestCase):
self.assertTrue(hit_finally)
self.assertTrue(hit_except)
+
+class ExceptStarTestCases(unittest.TestCase):
+ def test_try_except_else_finally(self):
+ hit_except = False
+ hit_else = False
+ hit_finally = False
+
+ try:
+ raise Exception('nyaa!')
+ except* BaseException:
+ hit_except = True
+ else:
+ hit_else = True
+ finally:
+ hit_finally = True
+
+ self.assertTrue(hit_except)
+ self.assertTrue(hit_finally)
+ self.assertFalse(hit_else)
+
+ def test_try_except_else_finally_no_exception(self):
+ hit_except = False
+ hit_else = False
+ hit_finally = False
+
+ try:
+ pass
+ except* BaseException:
+ hit_except = True
+ else:
+ hit_else = True
+ finally:
+ hit_finally = True
+
+ self.assertFalse(hit_except)
+ self.assertTrue(hit_finally)
+ self.assertTrue(hit_else)
+
+ def test_try_except_finally(self):
+ hit_except = False
+ hit_finally = False
+
+ try:
+ raise Exception('yarr!')
+ except* BaseException:
+ hit_except = True
+ finally:
+ hit_finally = True
+
+ self.assertTrue(hit_except)
+ self.assertTrue(hit_finally)
+
+ def test_try_except_finally_no_exception(self):
+ hit_except = False
+ hit_finally = False
+
+ try:
+ pass
+ except* BaseException:
+ hit_except = True
+ finally:
+ hit_finally = True
+
+ self.assertFalse(hit_except)
+ self.assertTrue(hit_finally)
+
+ def test_try_except(self):
+ hit_except = False
+
+ try:
+ raise Exception('ahoy!')
+ except* BaseException:
+ hit_except = True
+
+ self.assertTrue(hit_except)
+
+ def test_try_except_no_exception(self):
+ hit_except = False
+
+ try:
+ pass
+ except* BaseException:
+ hit_except = True
+
+ self.assertFalse(hit_except)
+
+ def test_try_except_else(self):
+ hit_except = False
+ hit_else = False
+
+ try:
+ raise Exception('foo!')
+ except* BaseException:
+ hit_except = True
+ else:
+ hit_else = True
+
+ self.assertFalse(hit_else)
+ self.assertTrue(hit_except)
+
+ def test_try_except_else_no_exception(self):
+ hit_except = False
+ hit_else = False
+
+ try:
+ pass
+ except* BaseException:
+ hit_except = True
+ else:
+ hit_else = True
+
+ self.assertFalse(hit_except)
+ self.assertTrue(hit_else)
+
+ def test_try_finally_no_exception(self):
+ hit_finally = False
+
+ try:
+ pass
+ finally:
+ hit_finally = True
+
+ self.assertTrue(hit_finally)
+
+ def test_nested(self):
+ hit_finally = False
+ hit_inner_except = False
+ hit_inner_finally = False
+
+ try:
+ try:
+ raise Exception('inner exception')
+ except* BaseException:
+ hit_inner_except = True
+ finally:
+ hit_inner_finally = True
+ finally:
+ hit_finally = True
+
+ self.assertTrue(hit_inner_except)
+ self.assertTrue(hit_inner_finally)
+ self.assertTrue(hit_finally)
+
+ def test_nested_else(self):
+ hit_else = False
+ hit_finally = False
+ hit_except = False
+ hit_inner_except = False
+ hit_inner_else = False
+
+ try:
+ try:
+ pass
+ except* BaseException:
+ hit_inner_except = True
+ else:
+ hit_inner_else = True
+
+ raise Exception('outer exception')
+ except* BaseException:
+ hit_except = True
+ else:
+ hit_else = True
+ finally:
+ hit_finally = True
+
+ self.assertFalse(hit_inner_except)
+ self.assertTrue(hit_inner_else)
+ self.assertFalse(hit_else)
+ self.assertTrue(hit_finally)
+ self.assertTrue(hit_except)
+
+ def test_nested_mixed1(self):
+ hit_except = False
+ hit_finally = False
+ hit_inner_except = False
+ hit_inner_finally = False
+
+ try:
+ try:
+ raise Exception('inner exception')
+ except* BaseException:
+ hit_inner_except = True
+ finally:
+ hit_inner_finally = True
+ except:
+ hit_except = True
+ finally:
+ hit_finally = True
+
+ self.assertTrue(hit_inner_except)
+ self.assertTrue(hit_inner_finally)
+ self.assertFalse(hit_except)
+ self.assertTrue(hit_finally)
+
+ def test_nested_mixed2(self):
+ hit_except = False
+ hit_finally = False
+ hit_inner_except = False
+ hit_inner_finally = False
+
+ try:
+ try:
+ raise Exception('inner exception')
+ except:
+ hit_inner_except = True
+ finally:
+ hit_inner_finally = True
+ except* BaseException:
+ hit_except = True
+ finally:
+ hit_finally = True
+
+ self.assertTrue(hit_inner_except)
+ self.assertTrue(hit_inner_finally)
+ self.assertFalse(hit_except)
+ self.assertTrue(hit_finally)
+
+
+ def test_nested_else_mixed1(self):
+ hit_else = False
+ hit_finally = False
+ hit_except = False
+ hit_inner_except = False
+ hit_inner_else = False
+
+ try:
+ try:
+ pass
+ except* BaseException:
+ hit_inner_except = True
+ else:
+ hit_inner_else = True
+
+ raise Exception('outer exception')
+ except:
+ hit_except = True
+ else:
+ hit_else = True
+ finally:
+ hit_finally = True
+
+ self.assertFalse(hit_inner_except)
+ self.assertTrue(hit_inner_else)
+ self.assertFalse(hit_else)
+ self.assertTrue(hit_finally)
+ self.assertTrue(hit_except)
+
+ def test_nested_else_mixed2(self):
+ hit_else = False
+ hit_finally = False
+ hit_except = False
+ hit_inner_except = False
+ hit_inner_else = False
+
+ try:
+ try:
+ pass
+ except:
+ hit_inner_except = True
+ else:
+ hit_inner_else = True
+
+ raise Exception('outer exception')
+ except* BaseException:
+ hit_except = True
+ else:
+ hit_else = True
+ finally:
+ hit_finally = True
+
+ self.assertFalse(hit_inner_except)
+ self.assertTrue(hit_inner_else)
+ self.assertFalse(hit_else)
+ self.assertTrue(hit_finally)
+ self.assertTrue(hit_except)
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py
index ffcb5e1..3e7808c 100644
--- a/Lib/test/test_exceptions.py
+++ b/Lib/test/test_exceptions.py
@@ -241,6 +241,8 @@ class ExceptionTests(unittest.TestCase):
check('def f():\n continue', 2, 3)
check('def f():\n break', 2, 3)
check('try:\n pass\nexcept:\n pass\nexcept ValueError:\n pass', 3, 1)
+ check('try:\n pass\nexcept*:\n pass', 3, 8)
+ check('try:\n pass\nexcept*:\n pass\nexcept* ValueError:\n pass', 3, 8)
# Errors thrown by tokenizer.c
check('(0x+1)', 1, 3)
diff --git a/Lib/test/test_grammar.py b/Lib/test/test_grammar.py
index b6c4574..a2460ad 100644
--- a/Lib/test/test_grammar.py
+++ b/Lib/test/test_grammar.py
@@ -1419,6 +1419,30 @@ class GrammarTests(unittest.TestCase):
compile("try:\n pass\nexcept Exception as a.b:\n pass", "?", "exec")
compile("try:\n pass\nexcept Exception as a[b]:\n pass", "?", "exec")
+ def test_try_star(self):
+ ### try_stmt: 'try': suite (except_star_clause : suite) + ['else' ':' suite]
+ ### except_star_clause: 'except*' expr ['as' NAME]
+ try:
+ 1/0
+ except* ZeroDivisionError:
+ pass
+ else:
+ pass
+ try: 1/0
+ except* EOFError: pass
+ except* ZeroDivisionError as msg: pass
+ else: pass
+ try: 1/0
+ except* (EOFError, TypeError, ZeroDivisionError): pass
+ try: 1/0
+ except* (EOFError, TypeError, ZeroDivisionError) as msg: pass
+ try: pass
+ finally: pass
+ with self.assertRaises(SyntaxError):
+ compile("try:\n pass\nexcept* Exception as a.b:\n pass", "?", "exec")
+ compile("try:\n pass\nexcept* Exception as a[b]:\n pass", "?", "exec")
+ compile("try:\n pass\nexcept*:\n pass", "?", "exec")
+
def test_suite(self):
# simple_stmt | NEWLINE INDENT NEWLINE* (stmt NEWLINE*)+ DEDENT
if 1: pass
diff --git a/Lib/test/test_syntax.py b/Lib/test/test_syntax.py
index fc3c629..6286529 100644
--- a/Lib/test/test_syntax.py
+++ b/Lib/test/test_syntax.py
@@ -955,6 +955,48 @@ Custom error messages for try blocks that are not followed by except/finally
Traceback (most recent call last):
SyntaxError: expected 'except' or 'finally' block
+Custom error message for try block mixing except and except*
+
+ >>> try:
+ ... pass
+ ... except TypeError:
+ ... pass
+ ... except* ValueError:
+ ... pass
+ Traceback (most recent call last):
+ SyntaxError: cannot have both 'except' and 'except*' on the same 'try'
+
+ >>> try:
+ ... pass
+ ... except* TypeError:
+ ... pass
+ ... except ValueError:
+ ... pass
+ Traceback (most recent call last):
+ SyntaxError: cannot have both 'except' and 'except*' on the same 'try'
+
+ >>> try:
+ ... pass
+ ... except TypeError:
+ ... pass
+ ... except TypeError:
+ ... pass
+ ... except* ValueError:
+ ... pass
+ Traceback (most recent call last):
+ SyntaxError: cannot have both 'except' and 'except*' on the same 'try'
+
+ >>> try:
+ ... pass
+ ... except* TypeError:
+ ... pass
+ ... except* TypeError:
+ ... pass
+ ... except ValueError:
+ ... pass
+ Traceback (most recent call last):
+ SyntaxError: cannot have both 'except' and 'except*' on the same 'try'
+
Ensure that early = are not matched by the parser as invalid comparisons
>>> f(2, 4, x=34); 1 $ 2
Traceback (most recent call last):
@@ -1070,6 +1112,13 @@ Specialized indentation errors:
>>> try:
... something()
+ ... except* A:
+ ... pass
+ Traceback (most recent call last):
+ IndentationError: expected an indented block after 'except*' statement on line 3
+
+ >>> try:
+ ... something()
... except A:
... pass
... finally:
@@ -1077,6 +1126,15 @@ Specialized indentation errors:
Traceback (most recent call last):
IndentationError: expected an indented block after 'finally' statement on line 5
+ >>> try:
+ ... something()
+ ... except* A:
+ ... pass
+ ... finally:
+ ... pass
+ Traceback (most recent call last):
+ IndentationError: expected an indented block after 'finally' statement on line 5
+
>>> with A:
... pass
Traceback (most recent call last):
@@ -1180,6 +1238,48 @@ raise a custom exception
SyntaxError: multiple exception types must be parenthesized
+ >>> try:
+ ... pass
+ ... except* A, B:
+ ... pass
+ Traceback (most recent call last):
+ SyntaxError: multiple exception types must be parenthesized
+
+ >>> try:
+ ... pass
+ ... except* A, B, C:
+ ... pass
+ Traceback (most recent call last):
+ SyntaxError: multiple exception types must be parenthesized
+
+ >>> try:
+ ... pass
+ ... except* A, B, C as blech:
+ ... pass
+ Traceback (most recent call last):
+ SyntaxError: multiple exception types must be parenthesized
+
+ >>> try:
+ ... pass
+ ... except* A, B, C as blech:
+ ... pass
+ ... finally:
+ ... pass
+ Traceback (most recent call last):
+ SyntaxError: multiple exception types must be parenthesized
+
+Custom exception for 'except*' without an exception type
+
+ >>> try:
+ ... pass
+ ... except* A as a:
+ ... pass
+ ... except*:
+ ... pass
+ Traceback (most recent call last):
+ SyntaxError: expected one or more exception types
+
+
>>> f(a=23, a=234)
Traceback (most recent call last):
...
diff --git a/Lib/test/test_sys_settrace.py b/Lib/test/test_sys_settrace.py
index 15c33a2..dc2aef1 100644
--- a/Lib/test/test_sys_settrace.py
+++ b/Lib/test/test_sys_settrace.py
@@ -1213,6 +1213,181 @@ class TraceTestCase(unittest.TestCase):
(5, 'line'),
(5, 'return')])
+ def test_try_except_star_no_exception(self):
+
+ def func():
+ try:
+ 2
+ except* Exception:
+ 4
+ else:
+ 6
+ finally:
+ 8
+
+ self.run_and_compare(func,
+ [(0, 'call'),
+ (1, 'line'),
+ (2, 'line'),
+ (6, 'line'),
+ (8, 'line'),
+ (8, 'return')])
+
+ def test_try_except_star_named_no_exception(self):
+
+ def func():
+ try:
+ 2
+ except* Exception as e:
+ 4
+ else:
+ 6
+ finally:
+ 8
+
+ self.run_and_compare(func,
+ [(0, 'call'),
+ (1, 'line'),
+ (2, 'line'),
+ (6, 'line'),
+ (8, 'line'),
+ (8, 'return')])
+
+ def test_try_except_star_exception_caught(self):
+
+ def func():
+ try:
+ raise ValueError(2)
+ except* ValueError:
+ 4
+ else:
+ 6
+ finally:
+ 8
+
+ self.run_and_compare(func,
+ [(0, 'call'),
+ (1, 'line'),
+ (2, 'line'),
+ (2, 'exception'),
+ (3, 'line'),
+ (4, 'line'),
+ (8, 'line'),
+ (8, 'return')])
+
+ def test_try_except_star_named_exception_caught(self):
+
+ def func():
+ try:
+ raise ValueError(2)
+ except* ValueError as e:
+ 4
+ else:
+ 6
+ finally:
+ 8
+
+ self.run_and_compare(func,
+ [(0, 'call'),
+ (1, 'line'),
+ (2, 'line'),
+ (2, 'exception'),
+ (3, 'line'),
+ (4, 'line'),
+ (8, 'line'),
+ (8, 'return')])
+
+ def test_try_except_star_exception_not_caught(self):
+
+ def func():
+ try:
+ try:
+ raise ValueError(3)
+ except* TypeError:
+ 5
+ except ValueError:
+ 7
+
+ self.run_and_compare(func,
+ [(0, 'call'),
+ (1, 'line'),
+ (2, 'line'),
+ (3, 'line'),
+ (3, 'exception'),
+ (4, 'line'),
+ (6, 'line'),
+ (7, 'line'),
+ (7, 'return')])
+
+ def test_try_except_star_named_exception_not_caught(self):
+
+ def func():
+ try:
+ try:
+ raise ValueError(3)
+ except* TypeError as e:
+ 5
+ except ValueError:
+ 7
+
+ self.run_and_compare(func,
+ [(0, 'call'),
+ (1, 'line'),
+ (2, 'line'),
+ (3, 'line'),
+ (3, 'exception'),
+ (4, 'line'),
+ (6, 'line'),
+ (7, 'line'),
+ (7, 'return')])
+
+ def test_try_except_star_nested(self):
+
+ def func():
+ try:
+ try:
+ raise ExceptionGroup(
+ 'eg',
+ [ValueError(5), TypeError('bad type')])
+ except* TypeError as e:
+ 7
+ except* OSError:
+ 9
+ except* ValueError:
+ raise
+ except* ValueError:
+ try:
+ raise TypeError(14)
+ except* OSError:
+ 16
+ except* TypeError as e:
+ 18
+ return 0
+
+ self.run_and_compare(func,
+ [(0, 'call'),
+ (1, 'line'),
+ (2, 'line'),
+ (3, 'line'),
+ (4, 'line'),
+ (5, 'line'),
+ (3, 'line'),
+ (3, 'exception'),
+ (6, 'line'),
+ (7, 'line'),
+ (8, 'line'),
+ (10, 'line'),
+ (11, 'line'),
+ (12, 'line'),
+ (13, 'line'),
+ (14, 'line'),
+ (14, 'exception'),
+ (15, 'line'),
+ (17, 'line'),
+ (18, 'line'),
+ (19, 'line'),
+ (19, 'return')])
+
class SkipLineEventsTraceTestCase(TraceTestCase):
"""Repeat the trace tests, but with per-line events skipped"""
diff --git a/Lib/test/test_unparse.py b/Lib/test/test_unparse.py
index d8ba487..f5be13a 100644
--- a/Lib/test/test_unparse.py
+++ b/Lib/test/test_unparse.py
@@ -93,6 +93,19 @@ finally:
suite5
"""
+try_except_star_finally = """\
+try:
+ suite1
+except* ex1:
+ suite2
+except* ex2:
+ suite3
+else:
+ suite4
+finally:
+ suite5
+"""
+
with_simple = """\
with f():
suite1
@@ -304,6 +317,9 @@ class UnparseTestCase(ASTTestCase):
def test_try_except_finally(self):
self.check_ast_roundtrip(try_except_finally)
+ def test_try_except_star_finally(self):
+ self.check_ast_roundtrip(try_except_star_finally)
+
def test_starred_assignment(self):
self.check_ast_roundtrip("a, *b, c = seq")
self.check_ast_roundtrip("a, (*b, c) = seq")