diff options
author | Irit Katriel <1055913+iritkatriel@users.noreply.github.com> | 2021-12-14 16:48:15 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-12-14 16:48:15 (GMT) |
commit | d60457a6673cf0263213c2f2be02c633ec2e2038 (patch) | |
tree | 04461db9079cf30a98c5a4070098f795275aa910 /Lib | |
parent | 850aefc2c651110a784cd5478af9774b1f6287a3 (diff) | |
download | cpython-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.py | 21 | ||||
-rw-r--r-- | Lib/importlib/_bootstrap_external.py | 3 | ||||
-rw-r--r-- | Lib/opcode.py | 3 | ||||
-rw-r--r-- | Lib/test/test_ast.py | 23 | ||||
-rw-r--r-- | Lib/test/test_compile.py | 33 | ||||
-rw-r--r-- | Lib/test/test_dis.py | 52 | ||||
-rw-r--r-- | Lib/test/test_except_star.py | 976 | ||||
-rw-r--r-- | Lib/test/test_exception_group.py | 3 | ||||
-rw-r--r-- | Lib/test/test_exception_variations.py | 278 | ||||
-rw-r--r-- | Lib/test/test_exceptions.py | 2 | ||||
-rw-r--r-- | Lib/test/test_grammar.py | 24 | ||||
-rw-r--r-- | Lib/test/test_syntax.py | 100 | ||||
-rw-r--r-- | Lib/test/test_sys_settrace.py | 175 | ||||
-rw-r--r-- | Lib/test/test_unparse.py | 16 |
14 files changed, 1675 insertions, 34 deletions
@@ -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") |