bpo-45292: [PEP-654] add except* (GH-29581)

This commit is contained in:
Irit Katriel 2021-12-14 16:48:15 +00:00 committed by GitHub
parent 850aefc2c6
commit d60457a667
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 7070 additions and 3332 deletions

View File

@ -1167,6 +1167,37 @@ Control flow
type_ignores=[]) type_ignores=[])
.. class:: TryStar(body, handlers, orelse, finalbody)
``try`` blocks which are followed by ``except*`` clauses. The attributes are the
same as for :class:`Try` but the :class:`ExceptHandler` nodes in ``handlers``
are interpreted as ``except*`` blocks rather then ``except``.
.. doctest::
>>> print(ast.dump(ast.parse("""
... try:
... ...
... except* Exception:
... ...
... """), indent=4))
Module(
body=[
TryStar(
body=[
Expr(
value=Constant(value=Ellipsis))],
handlers=[
ExceptHandler(
type=Name(id='Exception', ctx=Load()),
body=[
Expr(
value=Constant(value=Ellipsis))])],
orelse=[],
finalbody=[])],
type_ignores=[])
.. class:: ExceptHandler(type, name, body) .. class:: ExceptHandler(type, name, body)
A single ``except`` clause. ``type`` is the exception type it will match, A single ``except`` clause. ``type`` is the exception type it will match,

View File

@ -872,8 +872,10 @@ All of the following opcodes use their arguments.
.. versionadded:: 3.1 .. versionadded:: 3.1
.. opcode:: JUMP_IF_NOT_EXC_MATCH (target) .. opcode:: JUMP_IF_NOT_EXC_MATCH (target)
Performs exception matching for ``except``.
Tests whether the second value on the stack is an exception matching TOS, Tests whether the second value on the stack is an exception matching TOS,
and jumps if it is not. Pops one value from the stack. and jumps if it is not. Pops one value from the stack.
@ -883,6 +885,30 @@ All of the following opcodes use their arguments.
This opcode no longer pops the active exception. This opcode no longer pops the active exception.
.. opcode:: JUMP_IF_NOT_EG_MATCH (target)
Performs exception matching for ``except*``. Applies ``split(TOS)`` on
the exception group representing TOS1. Jumps if no match is found.
Pops one item from the stack. If a match was found, pops the 3 items representing
the exception and pushes the 3 items representing the non-matching part of
the exception group, followed by the 3 items representing the matching part.
In other words, in case of a match it pops 4 items and pushes 6.
.. versionadded:: 3.11
.. opcode:: PREP_RERAISE_STAR
Combines the raised and reraised exceptions list from TOS, into an exception
group to propagate from a try-except* block. Uses the original exception
group from TOS1 to reconstruct the structure of reraised exceptions. Pops
two items from the stack and pushes a triplet representing the exception to
reraise or three ``None`` if there isn't one.
.. versionadded:: 3.11
.. opcode:: JUMP_IF_TRUE_OR_POP (target) .. opcode:: JUMP_IF_TRUE_OR_POP (target)
If TOS is true, sets the bytecode counter to *target* and leaves TOS on the If TOS is true, sets the bytecode counter to *target* and leaves TOS on the

View File

@ -65,6 +65,8 @@ Summary -- Release highlights
.. PEP-sized items next. .. PEP-sized items next.
PEP-654: Exception Groups and ``except*``.
(Contributed by Irit Katriel in :issue:`45292`.)
New Features New Features
============ ============

View File

@ -403,6 +403,8 @@ try_stmt[stmt_ty]:
| invalid_try_stmt | invalid_try_stmt
| 'try' &&':' b=block f=finally_block { _PyAST_Try(b, NULL, NULL, f, EXTRA) } | 'try' &&':' b=block f=finally_block { _PyAST_Try(b, NULL, NULL, f, EXTRA) }
| 'try' &&':' b=block ex[asdl_excepthandler_seq*]=except_block+ el=[else_block] f=[finally_block] { _PyAST_Try(b, ex, el, f, EXTRA) } | 'try' &&':' b=block ex[asdl_excepthandler_seq*]=except_block+ el=[else_block] f=[finally_block] { _PyAST_Try(b, ex, el, f, EXTRA) }
| 'try' &&':' b=block ex[asdl_excepthandler_seq*]=except_star_block+ el=[else_block] f=[finally_block] { _PyAST_TryStar(b, ex, el, f, EXTRA) }
# Except statement # Except statement
# ---------------- # ----------------
@ -413,6 +415,11 @@ except_block[excepthandler_ty]:
_PyAST_ExceptHandler(e, (t) ? ((expr_ty) t)->v.Name.id : NULL, b, EXTRA) } _PyAST_ExceptHandler(e, (t) ? ((expr_ty) t)->v.Name.id : NULL, b, EXTRA) }
| 'except' ':' b=block { _PyAST_ExceptHandler(NULL, NULL, b, EXTRA) } | 'except' ':' b=block { _PyAST_ExceptHandler(NULL, NULL, b, EXTRA) }
| invalid_except_stmt | invalid_except_stmt
except_star_block[excepthandler_ty]:
| invalid_except_star_stmt_indent
| 'except' '*' e=expression t=['as' z=NAME { z }] ':' b=block {
_PyAST_ExceptHandler(e, (t) ? ((expr_ty) t)->v.Name.id : NULL, b, EXTRA) }
| invalid_except_stmt
finally_block[asdl_stmt_seq*]: finally_block[asdl_stmt_seq*]:
| invalid_finally_stmt | invalid_finally_stmt
| 'finally' &&':' a=block { a } | 'finally' &&':' a=block { a }
@ -1192,11 +1199,14 @@ invalid_try_stmt:
| a='try' ':' NEWLINE !INDENT { | a='try' ':' NEWLINE !INDENT {
RAISE_INDENTATION_ERROR("expected an indented block after 'try' statement on line %d", a->lineno) } RAISE_INDENTATION_ERROR("expected an indented block after 'try' statement on line %d", a->lineno) }
| 'try' ':' block !('except' | 'finally') { RAISE_SYNTAX_ERROR("expected 'except' or 'finally' block") } | 'try' ':' block !('except' | 'finally') { RAISE_SYNTAX_ERROR("expected 'except' or 'finally' block") }
| 'try' ':' block* ((except_block+ except_star_block) | (except_star_block+ except_block)) block* {
RAISE_SYNTAX_ERROR("cannot have both 'except' and 'except*' on the same 'try'") }
invalid_except_stmt: invalid_except_stmt:
| 'except' a=expression ',' expressions ['as' NAME ] ':' { | 'except' '*'? a=expression ',' expressions ['as' NAME ] ':' {
RAISE_SYNTAX_ERROR_STARTING_FROM(a, "multiple exception types must be parenthesized") } RAISE_SYNTAX_ERROR_STARTING_FROM(a, "multiple exception types must be parenthesized") }
| a='except' expression ['as' NAME ] NEWLINE { RAISE_SYNTAX_ERROR("expected ':'") } | a='except' '*'? expression ['as' NAME ] NEWLINE { RAISE_SYNTAX_ERROR("expected ':'") }
| a='except' NEWLINE { RAISE_SYNTAX_ERROR("expected ':'") } | a='except' NEWLINE { RAISE_SYNTAX_ERROR("expected ':'") }
| a='except' '*' (NEWLINE | ':') { RAISE_SYNTAX_ERROR("expected one or more exception types") }
invalid_finally_stmt: invalid_finally_stmt:
| a='finally' ':' NEWLINE !INDENT { | a='finally' ':' NEWLINE !INDENT {
RAISE_INDENTATION_ERROR("expected an indented block after 'finally' statement on line %d", a->lineno) } RAISE_INDENTATION_ERROR("expected an indented block after 'finally' statement on line %d", a->lineno) }
@ -1204,6 +1214,9 @@ invalid_except_stmt_indent:
| a='except' expression ['as' NAME ] ':' NEWLINE !INDENT { | a='except' expression ['as' NAME ] ':' NEWLINE !INDENT {
RAISE_INDENTATION_ERROR("expected an indented block after 'except' statement on line %d", a->lineno) } RAISE_INDENTATION_ERROR("expected an indented block after 'except' statement on line %d", a->lineno) }
| a='except' ':' NEWLINE !INDENT { RAISE_SYNTAX_ERROR("expected an indented block after except statement on line %d", a->lineno) } | a='except' ':' NEWLINE !INDENT { RAISE_SYNTAX_ERROR("expected an indented block after except statement on line %d", a->lineno) }
invalid_except_star_stmt_indent:
| a='except' '*' expression ['as' NAME ] ':' NEWLINE !INDENT {
RAISE_INDENTATION_ERROR("expected an indented block after 'except*' statement on line %d", a->lineno) }
invalid_match_stmt: invalid_match_stmt:
| "match" subject_expr !':' { CHECK_VERSION(void*, 10, "Pattern matching is", RAISE_SYNTAX_ERROR("expected ':'") ) } | "match" subject_expr !':' { CHECK_VERSION(void*, 10, "Pattern matching is", RAISE_SYNTAX_ERROR("expected ':'") ) }
| a="match" subject=subject_expr ':' NEWLINE !INDENT { | a="match" subject=subject_expr ':' NEWLINE !INDENT {

View File

@ -179,9 +179,9 @@ enum _stmt_kind {FunctionDef_kind=1, AsyncFunctionDef_kind=2, ClassDef_kind=3,
AugAssign_kind=7, AnnAssign_kind=8, For_kind=9, AugAssign_kind=7, AnnAssign_kind=8, For_kind=9,
AsyncFor_kind=10, While_kind=11, If_kind=12, With_kind=13, AsyncFor_kind=10, While_kind=11, If_kind=12, With_kind=13,
AsyncWith_kind=14, Match_kind=15, Raise_kind=16, Try_kind=17, AsyncWith_kind=14, Match_kind=15, Raise_kind=16, Try_kind=17,
Assert_kind=18, Import_kind=19, ImportFrom_kind=20, TryStar_kind=18, Assert_kind=19, Import_kind=20,
Global_kind=21, Nonlocal_kind=22, Expr_kind=23, Pass_kind=24, ImportFrom_kind=21, Global_kind=22, Nonlocal_kind=23,
Break_kind=25, Continue_kind=26}; Expr_kind=24, Pass_kind=25, Break_kind=26, Continue_kind=27};
struct _stmt { struct _stmt {
enum _stmt_kind kind; enum _stmt_kind kind;
union { union {
@ -295,6 +295,13 @@ struct _stmt {
asdl_stmt_seq *finalbody; asdl_stmt_seq *finalbody;
} Try; } Try;
struct {
asdl_stmt_seq *body;
asdl_excepthandler_seq *handlers;
asdl_stmt_seq *orelse;
asdl_stmt_seq *finalbody;
} TryStar;
struct { struct {
expr_ty test; expr_ty test;
expr_ty msg; expr_ty msg;
@ -688,6 +695,10 @@ stmt_ty _PyAST_Try(asdl_stmt_seq * body, asdl_excepthandler_seq * handlers,
asdl_stmt_seq * orelse, asdl_stmt_seq * finalbody, int asdl_stmt_seq * orelse, asdl_stmt_seq * finalbody, int
lineno, int col_offset, int end_lineno, int end_col_offset, lineno, int col_offset, int end_lineno, int end_col_offset,
PyArena *arena); PyArena *arena);
stmt_ty _PyAST_TryStar(asdl_stmt_seq * body, asdl_excepthandler_seq * handlers,
asdl_stmt_seq * orelse, asdl_stmt_seq * finalbody, int
lineno, int col_offset, int end_lineno, int
end_col_offset, PyArena *arena);
stmt_ty _PyAST_Assert(expr_ty test, expr_ty msg, int lineno, int col_offset, stmt_ty _PyAST_Assert(expr_ty test, expr_ty msg, int lineno, int col_offset,
int end_lineno, int end_col_offset, PyArena *arena); int end_lineno, int end_col_offset, PyArena *arena);
stmt_ty _PyAST_Import(asdl_alias_seq * names, int lineno, int col_offset, int stmt_ty _PyAST_Import(asdl_alias_seq * names, int lineno, int col_offset, int

View File

@ -132,6 +132,7 @@ struct ast_state {
PyObject *Sub_singleton; PyObject *Sub_singleton;
PyObject *Sub_type; PyObject *Sub_type;
PyObject *Subscript_type; PyObject *Subscript_type;
PyObject *TryStar_type;
PyObject *Try_type; PyObject *Try_type;
PyObject *Tuple_type; PyObject *Tuple_type;
PyObject *TypeIgnore_type; PyObject *TypeIgnore_type;

View File

@ -92,6 +92,14 @@ PyAPI_FUNC(PyObject *) _PyErr_FormatFromCauseTstate(
const char *format, const char *format,
...); ...);
PyAPI_FUNC(PyObject *) _PyExc_CreateExceptionGroup(
const char *msg,
PyObject *excs);
PyAPI_FUNC(PyObject *) _PyExc_ExceptionGroupProjection(
PyObject *left,
PyObject *right);
PyAPI_FUNC(int) _PyErr_CheckSignalsTstate(PyThreadState *tstate); PyAPI_FUNC(int) _PyErr_CheckSignalsTstate(PyThreadState *tstate);
PyAPI_FUNC(void) _Py_DumpExtensionModules(int fd, PyInterpreterState *interp); PyAPI_FUNC(void) _Py_DumpExtensionModules(int fd, PyInterpreterState *interp);

6
Include/opcode.h generated
View File

@ -45,6 +45,7 @@ extern "C" {
#define IMPORT_STAR 84 #define IMPORT_STAR 84
#define SETUP_ANNOTATIONS 85 #define SETUP_ANNOTATIONS 85
#define YIELD_VALUE 86 #define YIELD_VALUE 86
#define PREP_RERAISE_STAR 88
#define POP_EXCEPT 89 #define POP_EXCEPT 89
#define HAVE_ARGUMENT 90 #define HAVE_ARGUMENT 90
#define STORE_NAME 90 #define STORE_NAME 90
@ -83,6 +84,7 @@ extern "C" {
#define LOAD_FAST 124 #define LOAD_FAST 124
#define STORE_FAST 125 #define STORE_FAST 125
#define DELETE_FAST 126 #define DELETE_FAST 126
#define JUMP_IF_NOT_EG_MATCH 127
#define GEN_START 129 #define GEN_START 129
#define RAISE_VARARGS 130 #define RAISE_VARARGS 130
#define CALL_FUNCTION 131 #define CALL_FUNCTION 131
@ -161,7 +163,7 @@ extern "C" {
#define STORE_FAST__LOAD_FAST 80 #define STORE_FAST__LOAD_FAST 80
#define LOAD_FAST__LOAD_CONST 81 #define LOAD_FAST__LOAD_CONST 81
#define LOAD_CONST__LOAD_FAST 87 #define LOAD_CONST__LOAD_FAST 87
#define STORE_FAST__STORE_FAST 88 #define STORE_FAST__STORE_FAST 123
#define DO_TRACING 255 #define DO_TRACING 255
#ifdef NEED_OPCODE_JUMP_TABLES #ifdef NEED_OPCODE_JUMP_TABLES
static uint32_t _PyOpcode_RelativeJump[8] = { static uint32_t _PyOpcode_RelativeJump[8] = {
@ -178,7 +180,7 @@ static uint32_t _PyOpcode_Jump[8] = {
0U, 0U,
0U, 0U,
536870912U, 536870912U,
34586624U, 2182070272U,
0U, 0U,
0U, 0U,
0U, 0U,

View File

@ -683,6 +683,7 @@ class _Unparser(NodeVisitor):
self._type_ignores = {} self._type_ignores = {}
self._indent = 0 self._indent = 0
self._avoid_backslashes = _avoid_backslashes self._avoid_backslashes = _avoid_backslashes
self._in_try_star = False
def interleave(self, inter, f, seq): def interleave(self, inter, f, seq):
"""Call f on each item in seq, calling inter() in between.""" """Call f on each item in seq, calling inter() in between."""
@ -953,7 +954,7 @@ class _Unparser(NodeVisitor):
self.write(" from ") self.write(" from ")
self.traverse(node.cause) self.traverse(node.cause)
def visit_Try(self, node): def do_visit_try(self, node):
self.fill("try") self.fill("try")
with self.block(): with self.block():
self.traverse(node.body) self.traverse(node.body)
@ -968,8 +969,24 @@ class _Unparser(NodeVisitor):
with self.block(): with self.block():
self.traverse(node.finalbody) 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): def visit_ExceptHandler(self, node):
self.fill("except") self.fill("except*" if self._in_try_star else "except")
if node.type: if node.type:
self.write(" ") self.write(" ")
self.traverse(node.type) self.traverse(node.type)

View File

@ -371,6 +371,7 @@ _code_type = type(_write_atomic.__code__)
# Python 3.11a3 3464 (bpo-45636: Merge numeric BINARY_*/INPLACE_* into # Python 3.11a3 3464 (bpo-45636: Merge numeric BINARY_*/INPLACE_* into
# BINARY_OP) # BINARY_OP)
# Python 3.11a3 3465 (Add COPY_FREE_VARS opcode) # 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 # 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 # Whenever MAGIC_NUMBER is changed, the ranges in the magic_values array
# in PC/launcher.c must also be updated. # 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 _RAW_MAGIC_NUMBER = int.from_bytes(MAGIC_NUMBER, 'little') # For import.c
_PYCACHE = '__pycache__' _PYCACHE = '__pycache__'

View File

@ -103,6 +103,7 @@ def_op('IMPORT_STAR', 84)
def_op('SETUP_ANNOTATIONS', 85) def_op('SETUP_ANNOTATIONS', 85)
def_op('YIELD_VALUE', 86) def_op('YIELD_VALUE', 86)
def_op('PREP_RERAISE_STAR', 88)
def_op('POP_EXCEPT', 89) def_op('POP_EXCEPT', 89)
HAVE_ARGUMENT = 90 # Opcodes from here have an argument: HAVE_ARGUMENT = 90 # Opcodes from here have an argument:
@ -150,6 +151,8 @@ haslocal.append(125)
def_op('DELETE_FAST', 126) # Local variable number def_op('DELETE_FAST', 126) # Local variable number
haslocal.append(126) haslocal.append(126)
jabs_op('JUMP_IF_NOT_EG_MATCH', 127)
def_op('GEN_START', 129) # Kind of generator/coroutine def_op('GEN_START', 129) # Kind of generator/coroutine
def_op('RAISE_VARARGS', 130) # Number of raise arguments (1, 2, or 3) def_op('RAISE_VARARGS', 130) # Number of raise arguments (1, 2, or 3)
def_op('CALL_FUNCTION', 131) # #args def_op('CALL_FUNCTION', 131) # #args

View File

@ -86,6 +86,8 @@ exec_tests = [
"try:\n pass\nexcept Exception:\n pass", "try:\n pass\nexcept Exception:\n pass",
# TryFinally # TryFinally
"try:\n pass\nfinally:\n pass", "try:\n pass\nfinally:\n pass",
# TryStarExcept
"try:\n pass\nexcept* Exception:\n pass",
# Assert # Assert
"assert v", "assert v",
# Import # Import
@ -1310,6 +1312,26 @@ class ASTValidatorTests(unittest.TestCase):
t = ast.Try([p], e, [p], [ast.Expr(ast.Name("x", ast.Store()))]) t = ast.Try([p], e, [p], [ast.Expr(ast.Name("x", ast.Store()))])
self.stmt(t, "must have Load context") 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): def test_assert(self):
self.stmt(ast.Assert(ast.Name("x", ast.Store()), None), self.stmt(ast.Assert(ast.Name("x", ast.Store()), None),
"must have Load context") "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', [('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))], [('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', [('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', [('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', [('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)], []), ('Module', [('ImportFrom', (1, 0, 1, 17), 'sys', [('alias', (1, 16, 1, 17), 'v', None)], 0)], []),

View File

@ -1263,6 +1263,39 @@ class TestStackSizeStability(unittest.TestCase):
""" """
self.check_stack_size(snippet) 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): def test_try_finally(self):
snippet = """ snippet = """
try: try:

View File

@ -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='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_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='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='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='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), 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='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='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='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='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='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), 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_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=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='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=192, starts_line=28, is_jump_target=True, 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=194, 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=196, 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='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=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=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=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=204, starts_line=23, is_jump_target=True, 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=206, starts_line=28, is_jump_target=False, 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=208, 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=210, 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='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=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=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=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='PUSH_EXC_INFO', opcode=35, 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=220, starts_line=None, is_jump_target=False, 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=222, 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=224, 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='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=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='RERAISE', opcode=119, arg=0, argval=0, 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='POP_EXCEPT_AND_RERAISE', opcode=37, arg=None, argval=None, argrepr='', 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),
] ]
# One last piece of inspect fodder to check the default line number handling # One last piece of inspect fodder to check the default line number handling

View File

@ -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()

View File

@ -211,7 +211,8 @@ class ExceptionGroupSubgroupTests(ExceptionGroupTestBase):
def test_basics_subgroup_split__bad_arg_type(self): def test_basics_subgroup_split__bad_arg_type(self):
bad_args = ["bad arg", bad_args = ["bad arg",
OSError('instance not type'), OSError('instance not type'),
[OSError('instance not type')],] [OSError, TypeError],
(OSError, 42)]
for arg in bad_args: for arg in bad_args:
with self.assertRaises(TypeError): with self.assertRaises(TypeError):
self.eg.subgroup(arg) self.eg.subgroup(arg)

View File

@ -172,5 +172,283 @@ class ExceptionTestCase(unittest.TestCase):
self.assertTrue(hit_finally) self.assertTrue(hit_finally)
self.assertTrue(hit_except) 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__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -241,6 +241,8 @@ class ExceptionTests(unittest.TestCase):
check('def f():\n continue', 2, 3) check('def f():\n continue', 2, 3)
check('def f():\n break', 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\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 # Errors thrown by tokenizer.c
check('(0x+1)', 1, 3) check('(0x+1)', 1, 3)

View File

@ -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")
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): def test_suite(self):
# simple_stmt | NEWLINE INDENT NEWLINE* (stmt NEWLINE*)+ DEDENT # simple_stmt | NEWLINE INDENT NEWLINE* (stmt NEWLINE*)+ DEDENT
if 1: pass if 1: pass

View File

@ -955,6 +955,48 @@ Custom error messages for try blocks that are not followed by except/finally
Traceback (most recent call last): Traceback (most recent call last):
SyntaxError: expected 'except' or 'finally' block 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 Ensure that early = are not matched by the parser as invalid comparisons
>>> f(2, 4, x=34); 1 $ 2 >>> f(2, 4, x=34); 1 $ 2
Traceback (most recent call last): Traceback (most recent call last):
@ -1068,6 +1110,13 @@ Specialized indentation errors:
Traceback (most recent call last): Traceback (most recent call last):
IndentationError: expected an indented block after 'except' statement on line 3 IndentationError: expected an indented block after 'except' statement on line 3
>>> try:
... something()
... except* A:
... pass
Traceback (most recent call last):
IndentationError: expected an indented block after 'except*' statement on line 3
>>> try: >>> try:
... something() ... something()
... except A: ... except A:
@ -1077,6 +1126,15 @@ Specialized indentation errors:
Traceback (most recent call last): Traceback (most recent call last):
IndentationError: expected an indented block after 'finally' statement on line 5 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: >>> with A:
... pass ... pass
Traceback (most recent call last): Traceback (most recent call last):
@ -1180,6 +1238,48 @@ raise a custom exception
SyntaxError: multiple exception types must be parenthesized 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) >>> f(a=23, a=234)
Traceback (most recent call last): Traceback (most recent call last):
... ...

View File

@ -1213,6 +1213,181 @@ class TraceTestCase(unittest.TestCase):
(5, 'line'), (5, 'line'),
(5, 'return')]) (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): class SkipLineEventsTraceTestCase(TraceTestCase):
"""Repeat the trace tests, but with per-line events skipped""" """Repeat the trace tests, but with per-line events skipped"""

View File

@ -93,6 +93,19 @@ finally:
suite5 suite5
""" """
try_except_star_finally = """\
try:
suite1
except* ex1:
suite2
except* ex2:
suite3
else:
suite4
finally:
suite5
"""
with_simple = """\ with_simple = """\
with f(): with f():
suite1 suite1
@ -304,6 +317,9 @@ class UnparseTestCase(ASTTestCase):
def test_try_except_finally(self): def test_try_except_finally(self):
self.check_ast_roundtrip(try_except_finally) 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): def test_starred_assignment(self):
self.check_ast_roundtrip("a, *b, c = seq") self.check_ast_roundtrip("a, *b, c = seq")
self.check_ast_roundtrip("a, (*b, c) = seq") self.check_ast_roundtrip("a, (*b, c) = seq")

View File

@ -0,0 +1 @@
Complete the :pep:`654` implementation: add ``except*``.

View File

@ -773,6 +773,23 @@ error:
return NULL; return NULL;
} }
PyObject *
_PyExc_CreateExceptionGroup(const char *msg_str, PyObject *excs)
{
PyObject *msg = PyUnicode_FromString(msg_str);
if (!msg) {
return NULL;
}
PyObject *args = PyTuple_Pack(2, msg, excs);
Py_DECREF(msg);
if (!args) {
return NULL;
}
PyObject *result = PyObject_CallObject(PyExc_BaseExceptionGroup, args);
Py_DECREF(args);
return result;
}
static int static int
BaseExceptionGroup_init(PyBaseExceptionGroupObject *self, BaseExceptionGroup_init(PyBaseExceptionGroupObject *self,
PyObject *args, PyObject *kwds) PyObject *args, PyObject *kwds)
@ -878,7 +895,7 @@ exceptiongroup_subset(
if (tb) { if (tb) {
int res = PyException_SetTraceback(eg, tb); int res = PyException_SetTraceback(eg, tb);
Py_DECREF(tb); Py_DECREF(tb);
if (res == -1) { if (res < 0) {
goto error; goto error;
} }
} }
@ -896,26 +913,41 @@ typedef enum {
EXCEPTION_GROUP_MATCH_BY_TYPE = 0, EXCEPTION_GROUP_MATCH_BY_TYPE = 0,
/* A PyFunction returning True for matching exceptions */ /* A PyFunction returning True for matching exceptions */
EXCEPTION_GROUP_MATCH_BY_PREDICATE = 1, EXCEPTION_GROUP_MATCH_BY_PREDICATE = 1,
/* An instance or container thereof, checked with equality /* A set of leaf exceptions to include in the result.
* This matcher type is only used internally by the * This matcher type is used internally by the interpreter
* interpreter, it is not exposed to python code */ * to construct reraised exceptions.
*/
EXCEPTION_GROUP_MATCH_INSTANCES = 2 EXCEPTION_GROUP_MATCH_INSTANCES = 2
} _exceptiongroup_split_matcher_type; } _exceptiongroup_split_matcher_type;
static int static int
get_matcher_type(PyObject *value, get_matcher_type(PyObject *value,
_exceptiongroup_split_matcher_type *type) _exceptiongroup_split_matcher_type *type)
{ {
/* the python API supports only BY_TYPE and BY_PREDICATE */ assert(value);
if (PyExceptionClass_Check(value) ||
PyTuple_CheckExact(value)) {
*type = EXCEPTION_GROUP_MATCH_BY_TYPE;
return 0;
}
if (PyFunction_Check(value)) { if (PyFunction_Check(value)) {
*type = EXCEPTION_GROUP_MATCH_BY_PREDICATE; *type = EXCEPTION_GROUP_MATCH_BY_PREDICATE;
return 0; return 0;
} }
if (PyExceptionClass_Check(value)) {
*type = EXCEPTION_GROUP_MATCH_BY_TYPE;
return 0;
}
if (PyTuple_CheckExact(value)) {
Py_ssize_t n = PyTuple_GET_SIZE(value);
for (Py_ssize_t i=0; i<n; i++) {
if (!PyExceptionClass_Check(PyTuple_GET_ITEM(value, i))) {
goto error;
}
}
*type = EXCEPTION_GROUP_MATCH_BY_TYPE;
return 0;
}
error:
PyErr_SetString( PyErr_SetString(
PyExc_TypeError, PyExc_TypeError,
"expected a function, exception type or tuple of exception types"); "expected a function, exception type or tuple of exception types");
@ -944,10 +976,11 @@ exceptiongroup_split_check_match(PyObject *exc,
return is_true; return is_true;
} }
case EXCEPTION_GROUP_MATCH_INSTANCES: { case EXCEPTION_GROUP_MATCH_INSTANCES: {
if (PySequence_Check(matcher_value)) { assert(PySet_Check(matcher_value));
return PySequence_Contains(matcher_value, exc); if (!_PyBaseExceptionGroup_Check(exc)) {
return PySet_Contains(matcher_value, exc);
} }
return matcher_value == exc; return 0;
} }
} }
return 0; return 0;
@ -1019,7 +1052,7 @@ exceptiongroup_split_recursive(PyObject *exc,
} }
if (exceptiongroup_split_recursive( if (exceptiongroup_split_recursive(
e, matcher_type, matcher_value, e, matcher_type, matcher_value,
construct_rest, &rec_result) == -1) { construct_rest, &rec_result) < 0) {
assert(!rec_result.match); assert(!rec_result.match);
assert(!rec_result.rest); assert(!rec_result.rest);
Py_LeaveRecursiveCall(); Py_LeaveRecursiveCall();
@ -1028,7 +1061,7 @@ exceptiongroup_split_recursive(PyObject *exc,
Py_LeaveRecursiveCall(); Py_LeaveRecursiveCall();
if (rec_result.match) { if (rec_result.match) {
assert(PyList_CheckExact(match_list)); assert(PyList_CheckExact(match_list));
if (PyList_Append(match_list, rec_result.match) == -1) { if (PyList_Append(match_list, rec_result.match) < 0) {
Py_DECREF(rec_result.match); Py_DECREF(rec_result.match);
goto done; goto done;
} }
@ -1037,7 +1070,7 @@ exceptiongroup_split_recursive(PyObject *exc,
if (rec_result.rest) { if (rec_result.rest) {
assert(construct_rest); assert(construct_rest);
assert(PyList_CheckExact(rest_list)); assert(PyList_CheckExact(rest_list));
if (PyList_Append(rest_list, rec_result.rest) == -1) { if (PyList_Append(rest_list, rec_result.rest) < 0) {
Py_DECREF(rec_result.rest); Py_DECREF(rec_result.rest);
goto done; goto done;
} }
@ -1046,13 +1079,13 @@ exceptiongroup_split_recursive(PyObject *exc,
} }
/* construct result */ /* construct result */
if (exceptiongroup_subset(eg, match_list, &result->match) == -1) { if (exceptiongroup_subset(eg, match_list, &result->match) < 0) {
goto done; goto done;
} }
if (construct_rest) { if (construct_rest) {
assert(PyList_CheckExact(rest_list)); assert(PyList_CheckExact(rest_list));
if (exceptiongroup_subset(eg, rest_list, &result->rest) == -1) { if (exceptiongroup_subset(eg, rest_list, &result->rest) < 0) {
Py_CLEAR(result->match); Py_CLEAR(result->match);
goto done; goto done;
} }
@ -1061,7 +1094,7 @@ exceptiongroup_split_recursive(PyObject *exc,
done: done:
Py_DECREF(match_list); Py_DECREF(match_list);
Py_XDECREF(rest_list); Py_XDECREF(rest_list);
if (retval == -1) { if (retval < 0) {
Py_CLEAR(result->match); Py_CLEAR(result->match);
Py_CLEAR(result->rest); Py_CLEAR(result->rest);
} }
@ -1077,7 +1110,7 @@ BaseExceptionGroup_split(PyObject *self, PyObject *args)
} }
_exceptiongroup_split_matcher_type matcher_type; _exceptiongroup_split_matcher_type matcher_type;
if (get_matcher_type(matcher_value, &matcher_type) == -1) { if (get_matcher_type(matcher_value, &matcher_type) < 0) {
return NULL; return NULL;
} }
@ -1085,7 +1118,7 @@ BaseExceptionGroup_split(PyObject *self, PyObject *args)
bool construct_rest = true; bool construct_rest = true;
if (exceptiongroup_split_recursive( if (exceptiongroup_split_recursive(
self, matcher_type, matcher_value, self, matcher_type, matcher_value,
construct_rest, &split_result) == -1) { construct_rest, &split_result) < 0) {
return NULL; return NULL;
} }
@ -1108,7 +1141,7 @@ BaseExceptionGroup_subgroup(PyObject *self, PyObject *args)
} }
_exceptiongroup_split_matcher_type matcher_type; _exceptiongroup_split_matcher_type matcher_type;
if (get_matcher_type(matcher_value, &matcher_type) == -1) { if (get_matcher_type(matcher_value, &matcher_type) < 0) {
return NULL; return NULL;
} }
@ -1116,7 +1149,7 @@ BaseExceptionGroup_subgroup(PyObject *self, PyObject *args)
bool construct_rest = false; bool construct_rest = false;
if (exceptiongroup_split_recursive( if (exceptiongroup_split_recursive(
self, matcher_type, matcher_value, self, matcher_type, matcher_value,
construct_rest, &split_result) == -1) { construct_rest, &split_result) < 0) {
return NULL; return NULL;
} }
@ -1128,6 +1161,85 @@ BaseExceptionGroup_subgroup(PyObject *self, PyObject *args)
return result; return result;
} }
static int
collect_exception_group_leaves(PyObject *exc, PyObject *leaves)
{
if (Py_IsNone(exc)) {
return 0;
}
assert(PyExceptionInstance_Check(exc));
assert(PySet_Check(leaves));
/* Add all leaf exceptions in exc to the leaves set */
if (!_PyBaseExceptionGroup_Check(exc)) {
if (PySet_Add(leaves, exc) < 0) {
return -1;
}
return 0;
}
PyBaseExceptionGroupObject *eg = _PyBaseExceptionGroupObject_cast(exc);
Py_ssize_t num_excs = PyTuple_GET_SIZE(eg->excs);
/* recursive calls */
for (Py_ssize_t i = 0; i < num_excs; i++) {
PyObject *e = PyTuple_GET_ITEM(eg->excs, i);
if (Py_EnterRecursiveCall(" in collect_exception_group_leaves")) {
return -1;
}
int res = collect_exception_group_leaves(e, leaves);
Py_LeaveRecursiveCall();
if (res < 0) {
return -1;
}
}
return 0;
}
/* This function is used by the interpreter to construct reraised
* exception groups. It takes an exception group eg and a list
* of exception groups keep and returns the sub-exception group
* of eg which contains all leaf exceptions that are contained
* in any exception group in keep.
*/
PyObject *
_PyExc_ExceptionGroupProjection(PyObject *eg, PyObject *keep)
{
assert(_PyBaseExceptionGroup_Check(eg));
assert(PyList_CheckExact(keep));
PyObject *leaves = PySet_New(NULL);
if (!leaves) {
return NULL;
}
Py_ssize_t n = PyList_GET_SIZE(keep);
for (Py_ssize_t i = 0; i < n; i++) {
PyObject *e = PyList_GET_ITEM(keep, i);
assert(e != NULL);
assert(_PyBaseExceptionGroup_Check(e));
if (collect_exception_group_leaves(e, leaves) < 0) {
Py_DECREF(leaves);
return NULL;
}
}
_exceptiongroup_split_result split_result;
bool construct_rest = false;
int err = exceptiongroup_split_recursive(
eg, EXCEPTION_GROUP_MATCH_INSTANCES, leaves,
construct_rest, &split_result);
Py_DECREF(leaves);
if (err < 0) {
return NULL;
}
PyObject *result = split_result.match ?
split_result.match : Py_NewRef(Py_None);
assert(split_result.rest == NULL);
return result;
}
static PyMemberDef BaseExceptionGroup_members[] = { static PyMemberDef BaseExceptionGroup_members[] = {
{"message", T_OBJECT, offsetof(PyBaseExceptionGroupObject, msg), READONLY, {"message", T_OBJECT, offsetof(PyBaseExceptionGroupObject, msg), READONLY,
PyDoc_STR("exception message")}, PyDoc_STR("exception message")},

View File

@ -207,6 +207,7 @@ mark_stacks(PyCodeObject *code_obj, int len)
case POP_JUMP_IF_FALSE: case POP_JUMP_IF_FALSE:
case POP_JUMP_IF_TRUE: case POP_JUMP_IF_TRUE:
case JUMP_IF_NOT_EXC_MATCH: case JUMP_IF_NOT_EXC_MATCH:
case JUMP_IF_NOT_EG_MATCH:
{ {
int64_t target_stack; int64_t target_stack;
int j = get_arg(code, i); int j = get_arg(code, i);
@ -214,7 +215,9 @@ mark_stacks(PyCodeObject *code_obj, int len)
if (stacks[j] == UNINITIALIZED && j < i) { if (stacks[j] == UNINITIALIZED && j < i) {
todo = 1; todo = 1;
} }
if (opcode == JUMP_IF_NOT_EXC_MATCH) { if (opcode == JUMP_IF_NOT_EXC_MATCH ||
opcode == JUMP_IF_NOT_EG_MATCH)
{
next_stack = pop_value(pop_value(next_stack)); next_stack = pop_value(pop_value(next_stack));
target_stack = next_stack; target_stack = next_stack;
} }

View File

@ -40,6 +40,7 @@ module Python
| Raise(expr? exc, expr? cause) | Raise(expr? exc, expr? cause)
| Try(stmt* body, excepthandler* handlers, stmt* orelse, stmt* finalbody) | Try(stmt* body, excepthandler* handlers, stmt* orelse, stmt* finalbody)
| TryStar(stmt* body, excepthandler* handlers, stmt* orelse, stmt* finalbody)
| Assert(expr test, expr? msg) | Assert(expr test, expr? msg)
| Import(alias* names) | Import(alias* names)

File diff suppressed because it is too large Load Diff

229
Python/Python-ast.c generated
View File

@ -146,6 +146,7 @@ void _PyAST_Fini(PyInterpreterState *interp)
Py_CLEAR(state->Sub_singleton); Py_CLEAR(state->Sub_singleton);
Py_CLEAR(state->Sub_type); Py_CLEAR(state->Sub_type);
Py_CLEAR(state->Subscript_type); Py_CLEAR(state->Subscript_type);
Py_CLEAR(state->TryStar_type);
Py_CLEAR(state->Try_type); Py_CLEAR(state->Try_type);
Py_CLEAR(state->Tuple_type); Py_CLEAR(state->Tuple_type);
Py_CLEAR(state->TypeIgnore_type); Py_CLEAR(state->TypeIgnore_type);
@ -486,6 +487,12 @@ static const char * const Try_fields[]={
"orelse", "orelse",
"finalbody", "finalbody",
}; };
static const char * const TryStar_fields[]={
"body",
"handlers",
"orelse",
"finalbody",
};
static const char * const Assert_fields[]={ static const char * const Assert_fields[]={
"test", "test",
"msg", "msg",
@ -1139,6 +1146,7 @@ init_types(struct ast_state *state)
" | Match(expr subject, match_case* cases)\n" " | Match(expr subject, match_case* cases)\n"
" | Raise(expr? exc, expr? cause)\n" " | Raise(expr? exc, expr? cause)\n"
" | Try(stmt* body, excepthandler* handlers, stmt* orelse, stmt* finalbody)\n" " | Try(stmt* body, excepthandler* handlers, stmt* orelse, stmt* finalbody)\n"
" | TryStar(stmt* body, excepthandler* handlers, stmt* orelse, stmt* finalbody)\n"
" | Assert(expr test, expr? msg)\n" " | Assert(expr test, expr? msg)\n"
" | Import(alias* names)\n" " | Import(alias* names)\n"
" | ImportFrom(identifier? module, alias* names, int? level)\n" " | ImportFrom(identifier? module, alias* names, int? level)\n"
@ -1254,6 +1262,10 @@ init_types(struct ast_state *state)
state->Try_type = make_type(state, "Try", state->stmt_type, Try_fields, 4, state->Try_type = make_type(state, "Try", state->stmt_type, Try_fields, 4,
"Try(stmt* body, excepthandler* handlers, stmt* orelse, stmt* finalbody)"); "Try(stmt* body, excepthandler* handlers, stmt* orelse, stmt* finalbody)");
if (!state->Try_type) return 0; if (!state->Try_type) return 0;
state->TryStar_type = make_type(state, "TryStar", state->stmt_type,
TryStar_fields, 4,
"TryStar(stmt* body, excepthandler* handlers, stmt* orelse, stmt* finalbody)");
if (!state->TryStar_type) return 0;
state->Assert_type = make_type(state, "Assert", state->stmt_type, state->Assert_type = make_type(state, "Assert", state->stmt_type,
Assert_fields, 2, Assert_fields, 2,
"Assert(expr test, expr? msg)"); "Assert(expr test, expr? msg)");
@ -2379,6 +2391,28 @@ _PyAST_Try(asdl_stmt_seq * body, asdl_excepthandler_seq * handlers,
return p; return p;
} }
stmt_ty
_PyAST_TryStar(asdl_stmt_seq * body, asdl_excepthandler_seq * handlers,
asdl_stmt_seq * orelse, asdl_stmt_seq * finalbody, int lineno,
int col_offset, int end_lineno, int end_col_offset, PyArena
*arena)
{
stmt_ty p;
p = (stmt_ty)_PyArena_Malloc(arena, sizeof(*p));
if (!p)
return NULL;
p->kind = TryStar_kind;
p->v.TryStar.body = body;
p->v.TryStar.handlers = handlers;
p->v.TryStar.orelse = orelse;
p->v.TryStar.finalbody = finalbody;
p->lineno = lineno;
p->col_offset = col_offset;
p->end_lineno = end_lineno;
p->end_col_offset = end_col_offset;
return p;
}
stmt_ty stmt_ty
_PyAST_Assert(expr_ty test, expr_ty msg, int lineno, int col_offset, int _PyAST_Assert(expr_ty test, expr_ty msg, int lineno, int col_offset, int
end_lineno, int end_col_offset, PyArena *arena) end_lineno, int end_col_offset, PyArena *arena)
@ -4049,6 +4083,34 @@ ast2obj_stmt(struct ast_state *state, void* _o)
goto failed; goto failed;
Py_DECREF(value); Py_DECREF(value);
break; break;
case TryStar_kind:
tp = (PyTypeObject *)state->TryStar_type;
result = PyType_GenericNew(tp, NULL, NULL);
if (!result) goto failed;
value = ast2obj_list(state, (asdl_seq*)o->v.TryStar.body, ast2obj_stmt);
if (!value) goto failed;
if (PyObject_SetAttr(result, state->body, value) == -1)
goto failed;
Py_DECREF(value);
value = ast2obj_list(state, (asdl_seq*)o->v.TryStar.handlers,
ast2obj_excepthandler);
if (!value) goto failed;
if (PyObject_SetAttr(result, state->handlers, value) == -1)
goto failed;
Py_DECREF(value);
value = ast2obj_list(state, (asdl_seq*)o->v.TryStar.orelse,
ast2obj_stmt);
if (!value) goto failed;
if (PyObject_SetAttr(result, state->orelse, value) == -1)
goto failed;
Py_DECREF(value);
value = ast2obj_list(state, (asdl_seq*)o->v.TryStar.finalbody,
ast2obj_stmt);
if (!value) goto failed;
if (PyObject_SetAttr(result, state->finalbody, value) == -1)
goto failed;
Py_DECREF(value);
break;
case Assert_kind: case Assert_kind:
tp = (PyTypeObject *)state->Assert_type; tp = (PyTypeObject *)state->Assert_type;
result = PyType_GenericNew(tp, NULL, NULL); result = PyType_GenericNew(tp, NULL, NULL);
@ -7477,6 +7539,170 @@ obj2ast_stmt(struct ast_state *state, PyObject* obj, stmt_ty* out, PyArena*
if (*out == NULL) goto failed; if (*out == NULL) goto failed;
return 0; return 0;
} }
tp = state->TryStar_type;
isinstance = PyObject_IsInstance(obj, tp);
if (isinstance == -1) {
return 1;
}
if (isinstance) {
asdl_stmt_seq* body;
asdl_excepthandler_seq* handlers;
asdl_stmt_seq* orelse;
asdl_stmt_seq* finalbody;
if (_PyObject_LookupAttr(obj, state->body, &tmp) < 0) {
return 1;
}
if (tmp == NULL) {
PyErr_SetString(PyExc_TypeError, "required field \"body\" missing from TryStar");
return 1;
}
else {
int res;
Py_ssize_t len;
Py_ssize_t i;
if (!PyList_Check(tmp)) {
PyErr_Format(PyExc_TypeError, "TryStar field \"body\" must be a list, not a %.200s", _PyType_Name(Py_TYPE(tmp)));
goto failed;
}
len = PyList_GET_SIZE(tmp);
body = _Py_asdl_stmt_seq_new(len, arena);
if (body == NULL) goto failed;
for (i = 0; i < len; i++) {
stmt_ty val;
PyObject *tmp2 = PyList_GET_ITEM(tmp, i);
Py_INCREF(tmp2);
if (Py_EnterRecursiveCall(" while traversing 'TryStar' node")) {
goto failed;
}
res = obj2ast_stmt(state, tmp2, &val, arena);
Py_LeaveRecursiveCall();
Py_DECREF(tmp2);
if (res != 0) goto failed;
if (len != PyList_GET_SIZE(tmp)) {
PyErr_SetString(PyExc_RuntimeError, "TryStar field \"body\" changed size during iteration");
goto failed;
}
asdl_seq_SET(body, i, val);
}
Py_CLEAR(tmp);
}
if (_PyObject_LookupAttr(obj, state->handlers, &tmp) < 0) {
return 1;
}
if (tmp == NULL) {
PyErr_SetString(PyExc_TypeError, "required field \"handlers\" missing from TryStar");
return 1;
}
else {
int res;
Py_ssize_t len;
Py_ssize_t i;
if (!PyList_Check(tmp)) {
PyErr_Format(PyExc_TypeError, "TryStar field \"handlers\" must be a list, not a %.200s", _PyType_Name(Py_TYPE(tmp)));
goto failed;
}
len = PyList_GET_SIZE(tmp);
handlers = _Py_asdl_excepthandler_seq_new(len, arena);
if (handlers == NULL) goto failed;
for (i = 0; i < len; i++) {
excepthandler_ty val;
PyObject *tmp2 = PyList_GET_ITEM(tmp, i);
Py_INCREF(tmp2);
if (Py_EnterRecursiveCall(" while traversing 'TryStar' node")) {
goto failed;
}
res = obj2ast_excepthandler(state, tmp2, &val, arena);
Py_LeaveRecursiveCall();
Py_DECREF(tmp2);
if (res != 0) goto failed;
if (len != PyList_GET_SIZE(tmp)) {
PyErr_SetString(PyExc_RuntimeError, "TryStar field \"handlers\" changed size during iteration");
goto failed;
}
asdl_seq_SET(handlers, i, val);
}
Py_CLEAR(tmp);
}
if (_PyObject_LookupAttr(obj, state->orelse, &tmp) < 0) {
return 1;
}
if (tmp == NULL) {
PyErr_SetString(PyExc_TypeError, "required field \"orelse\" missing from TryStar");
return 1;
}
else {
int res;
Py_ssize_t len;
Py_ssize_t i;
if (!PyList_Check(tmp)) {
PyErr_Format(PyExc_TypeError, "TryStar field \"orelse\" must be a list, not a %.200s", _PyType_Name(Py_TYPE(tmp)));
goto failed;
}
len = PyList_GET_SIZE(tmp);
orelse = _Py_asdl_stmt_seq_new(len, arena);
if (orelse == NULL) goto failed;
for (i = 0; i < len; i++) {
stmt_ty val;
PyObject *tmp2 = PyList_GET_ITEM(tmp, i);
Py_INCREF(tmp2);
if (Py_EnterRecursiveCall(" while traversing 'TryStar' node")) {
goto failed;
}
res = obj2ast_stmt(state, tmp2, &val, arena);
Py_LeaveRecursiveCall();
Py_DECREF(tmp2);
if (res != 0) goto failed;
if (len != PyList_GET_SIZE(tmp)) {
PyErr_SetString(PyExc_RuntimeError, "TryStar field \"orelse\" changed size during iteration");
goto failed;
}
asdl_seq_SET(orelse, i, val);
}
Py_CLEAR(tmp);
}
if (_PyObject_LookupAttr(obj, state->finalbody, &tmp) < 0) {
return 1;
}
if (tmp == NULL) {
PyErr_SetString(PyExc_TypeError, "required field \"finalbody\" missing from TryStar");
return 1;
}
else {
int res;
Py_ssize_t len;
Py_ssize_t i;
if (!PyList_Check(tmp)) {
PyErr_Format(PyExc_TypeError, "TryStar field \"finalbody\" must be a list, not a %.200s", _PyType_Name(Py_TYPE(tmp)));
goto failed;
}
len = PyList_GET_SIZE(tmp);
finalbody = _Py_asdl_stmt_seq_new(len, arena);
if (finalbody == NULL) goto failed;
for (i = 0; i < len; i++) {
stmt_ty val;
PyObject *tmp2 = PyList_GET_ITEM(tmp, i);
Py_INCREF(tmp2);
if (Py_EnterRecursiveCall(" while traversing 'TryStar' node")) {
goto failed;
}
res = obj2ast_stmt(state, tmp2, &val, arena);
Py_LeaveRecursiveCall();
Py_DECREF(tmp2);
if (res != 0) goto failed;
if (len != PyList_GET_SIZE(tmp)) {
PyErr_SetString(PyExc_RuntimeError, "TryStar field \"finalbody\" changed size during iteration");
goto failed;
}
asdl_seq_SET(finalbody, i, val);
}
Py_CLEAR(tmp);
}
*out = _PyAST_TryStar(body, handlers, orelse, finalbody, lineno,
col_offset, end_lineno, end_col_offset, arena);
if (*out == NULL) goto failed;
return 0;
}
tp = state->Assert_type; tp = state->Assert_type;
isinstance = PyObject_IsInstance(obj, tp); isinstance = PyObject_IsInstance(obj, tp);
if (isinstance == -1) { if (isinstance == -1) {
@ -11687,6 +11913,9 @@ astmodule_exec(PyObject *m)
if (PyModule_AddObjectRef(m, "Try", state->Try_type) < 0) { if (PyModule_AddObjectRef(m, "Try", state->Try_type) < 0) {
return -1; return -1;
} }
if (PyModule_AddObjectRef(m, "TryStar", state->TryStar_type) < 0) {
return -1;
}
if (PyModule_AddObjectRef(m, "Assert", state->Assert_type) < 0) { if (PyModule_AddObjectRef(m, "Assert", state->Assert_type) < 0) {
return -1; return -1;
} }

View File

@ -817,6 +817,31 @@ validate_stmt(struct validator *state, stmt_ty stmt)
(!asdl_seq_LEN(stmt->v.Try.orelse) || (!asdl_seq_LEN(stmt->v.Try.orelse) ||
validate_stmts(state, stmt->v.Try.orelse)); validate_stmts(state, stmt->v.Try.orelse));
break; break;
case TryStar_kind:
if (!validate_body(state, stmt->v.TryStar.body, "TryStar"))
return 0;
if (!asdl_seq_LEN(stmt->v.TryStar.handlers) &&
!asdl_seq_LEN(stmt->v.TryStar.finalbody)) {
PyErr_SetString(PyExc_ValueError, "TryStar has neither except handlers nor finalbody");
return 0;
}
if (!asdl_seq_LEN(stmt->v.TryStar.handlers) &&
asdl_seq_LEN(stmt->v.TryStar.orelse)) {
PyErr_SetString(PyExc_ValueError, "TryStar has orelse but no except handlers");
return 0;
}
for (i = 0; i < asdl_seq_LEN(stmt->v.TryStar.handlers); i++) {
excepthandler_ty handler = asdl_seq_GET(stmt->v.TryStar.handlers, i);
if ((handler->v.ExceptHandler.type &&
!validate_expr(state, handler->v.ExceptHandler.type, Load)) ||
!validate_body(state, handler->v.ExceptHandler.body, "ExceptHandler"))
return 0;
}
ret = (!asdl_seq_LEN(stmt->v.TryStar.finalbody) ||
validate_stmts(state, stmt->v.TryStar.finalbody)) &&
(!asdl_seq_LEN(stmt->v.TryStar.orelse) ||
validate_stmts(state, stmt->v.TryStar.orelse));
break;
case Assert_kind: case Assert_kind:
ret = validate_expr(state, stmt->v.Assert.test, Load) && ret = validate_expr(state, stmt->v.Assert.test, Load) &&
(!stmt->v.Assert.msg || validate_expr(state, stmt->v.Assert.msg, Load)); (!stmt->v.Assert.msg || validate_expr(state, stmt->v.Assert.msg, Load));

View File

@ -972,6 +972,12 @@ astfold_stmt(stmt_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
CALL_SEQ(astfold_stmt, stmt, node_->v.Try.orelse); CALL_SEQ(astfold_stmt, stmt, node_->v.Try.orelse);
CALL_SEQ(astfold_stmt, stmt, node_->v.Try.finalbody); CALL_SEQ(astfold_stmt, stmt, node_->v.Try.finalbody);
break; break;
case TryStar_kind:
CALL_SEQ(astfold_stmt, stmt, node_->v.TryStar.body);
CALL_SEQ(astfold_excepthandler, excepthandler, node_->v.TryStar.handlers);
CALL_SEQ(astfold_stmt, stmt, node_->v.TryStar.orelse);
CALL_SEQ(astfold_stmt, stmt, node_->v.TryStar.finalbody);
break;
case Assert_kind: case Assert_kind:
CALL(astfold_expr, expr_ty, node_->v.Assert.test); CALL(astfold_expr, expr_ty, node_->v.Assert.test);
CALL_OPT(astfold_expr, expr_ty, node_->v.Assert.msg); CALL_OPT(astfold_expr, expr_ty, node_->v.Assert.msg);

View File

@ -37,6 +37,7 @@
#include "structmember.h" // struct PyMemberDef, T_OFFSET_EX #include "structmember.h" // struct PyMemberDef, T_OFFSET_EX
#include <ctype.h> #include <ctype.h>
#include <stdbool.h>
#ifdef Py_DEBUG #ifdef Py_DEBUG
/* For debugging the interpreter: */ /* For debugging the interpreter: */
@ -95,6 +96,7 @@ static void format_exc_check_arg(PyThreadState *, PyObject *, const char *, PyOb
static void format_exc_unbound(PyThreadState *tstate, PyCodeObject *co, int oparg); static void format_exc_unbound(PyThreadState *tstate, PyCodeObject *co, int oparg);
static int check_args_iterable(PyThreadState *, PyObject *func, PyObject *vararg); static int check_args_iterable(PyThreadState *, PyObject *func, PyObject *vararg);
static int check_except_type_valid(PyThreadState *tstate, PyObject* right); static int check_except_type_valid(PyThreadState *tstate, PyObject* right);
static int check_except_star_type_valid(PyThreadState *tstate, PyObject* right);
static void format_kwargs_error(PyThreadState *, PyObject *func, PyObject *kwargs); static void format_kwargs_error(PyThreadState *, PyObject *func, PyObject *kwargs);
static void format_awaitable_error(PyThreadState *, PyTypeObject *, int, int); static void format_awaitable_error(PyThreadState *, PyTypeObject *, int, int);
static int get_exception_handler(PyCodeObject *, int, int*, int*, int*); static int get_exception_handler(PyCodeObject *, int, int*, int*, int*);
@ -1090,6 +1092,11 @@ fail:
static int do_raise(PyThreadState *tstate, PyObject *exc, PyObject *cause); static int do_raise(PyThreadState *tstate, PyObject *exc, PyObject *cause);
static PyObject *do_reraise_star(PyObject *excs, PyObject *orig);
static int exception_group_match(
PyObject *exc_type, PyObject* exc_value, PyObject *match_type,
PyObject **match, PyObject **rest);
static int unpack_iterable(PyThreadState *, PyObject *, int, int, PyObject **); static int unpack_iterable(PyThreadState *, PyObject *, int, int, PyObject **);
#ifdef Py_DEBUG #ifdef Py_DEBUG
@ -2727,6 +2734,7 @@ check_eval_breaker:
type = exc_info->exc_type; type = exc_info->exc_type;
value = exc_info->exc_value; value = exc_info->exc_value;
traceback = exc_info->exc_traceback; traceback = exc_info->exc_traceback;
exc_info->exc_type = POP(); exc_info->exc_type = POP();
exc_info->exc_value = POP(); exc_info->exc_value = POP();
exc_info->exc_traceback = POP(); exc_info->exc_traceback = POP();
@ -2791,6 +2799,36 @@ check_eval_breaker:
goto exception_unwind; goto exception_unwind;
} }
TARGET(PREP_RERAISE_STAR) {
PyObject *excs = POP();
assert(PyList_Check(excs));
PyObject *orig = POP();
PyObject *val = do_reraise_star(excs, orig);
Py_DECREF(excs);
Py_DECREF(orig);
if (val == NULL) {
goto error;
}
PyObject *lasti_unused = Py_NewRef(_PyLong_GetZero());
PUSH(lasti_unused);
if (!Py_IsNone(val)) {
PyObject *tb = PyException_GetTraceback(val);
PUSH(tb ? tb : Py_NewRef(Py_None));
PUSH(val);
PUSH(Py_NewRef(Py_TYPE(val)));
}
else {
// nothing to reraise
PUSH(Py_NewRef(Py_None));
PUSH(val);
PUSH(Py_NewRef(Py_None));
}
DISPATCH();
}
TARGET(END_ASYNC_FOR) { TARGET(END_ASYNC_FOR) {
PyObject *exc = POP(); PyObject *exc = POP();
PyObject *val = POP(); PyObject *val = POP();
@ -3922,6 +3960,83 @@ check_eval_breaker:
DISPATCH(); DISPATCH();
} }
TARGET(JUMP_IF_NOT_EG_MATCH) {
PyObject *match_type = POP();
PyObject *exc_type = TOP();
PyObject *exc_value = SECOND();
if (check_except_star_type_valid(tstate, match_type) < 0) {
Py_DECREF(match_type);
goto error;
}
PyObject *match = NULL, *rest = NULL;
int res = exception_group_match(exc_type, exc_value,
match_type, &match, &rest);
Py_DECREF(match_type);
if (res < 0) {
goto error;
}
if (match == NULL || rest == NULL) {
assert(match == NULL);
assert(rest == NULL);
goto error;
}
if (Py_IsNone(match)) {
Py_DECREF(match);
Py_XDECREF(rest);
/* no match - jump to target */
JUMPTO(oparg);
}
else {
/* Total or partial match - update the stack from
* [tb, val, exc]
* to
* [tb, rest, exc, tb, match, exc]
* (rest can be Py_None)
*/
PyObject *type = TOP();
PyObject *val = SECOND();
PyObject *tb = THIRD();
if (!Py_IsNone(rest)) {
/* tb remains the same */
SET_TOP(Py_NewRef(Py_TYPE(rest)));
SET_SECOND(Py_NewRef(rest));
SET_THIRD(Py_NewRef(tb));
}
else {
SET_TOP(Py_NewRef(Py_None));
SET_SECOND(Py_NewRef(Py_None));
SET_THIRD(Py_NewRef(Py_None));
}
/* Push match */
PUSH(Py_NewRef(tb));
PUSH(Py_NewRef(match));
PUSH(Py_NewRef(Py_TYPE(match)));
// set exc_info to the current match
PyErr_SetExcInfo(
Py_NewRef(Py_TYPE(match)),
Py_NewRef(match),
Py_NewRef(tb));
Py_DECREF(tb);
Py_DECREF(val);
Py_DECREF(type);
Py_DECREF(match);
Py_DECREF(rest);
}
DISPATCH();
}
TARGET(JUMP_IF_NOT_EXC_MATCH) { TARGET(JUMP_IF_NOT_EXC_MATCH) {
PyObject *right = POP(); PyObject *right = POP();
ASSERT_EXC_TYPE_IS_REDUNDANT(TOP(), SECOND()); ASSERT_EXC_TYPE_IS_REDUNDANT(TOP(), SECOND());
@ -3931,17 +4046,12 @@ check_eval_breaker:
Py_DECREF(right); Py_DECREF(right);
goto error; goto error;
} }
int res = PyErr_GivenExceptionMatches(left, right); int res = PyErr_GivenExceptionMatches(left, right);
Py_DECREF(right); Py_DECREF(right);
if (res > 0) { if (res == 0) {
/* Exception matches -- Do nothing */;
}
else if (res == 0) {
JUMPTO(oparg); JUMPTO(oparg);
} }
else {
goto error;
}
DISPATCH(); DISPATCH();
} }
@ -6127,6 +6237,196 @@ raise_error:
return 0; return 0;
} }
/* Logic for matching an exception in an except* clause (too
complicated for inlining).
*/
static int
exception_group_match(PyObject *exc_type, PyObject* exc_value,
PyObject *match_type, PyObject **match, PyObject **rest)
{
if (Py_IsNone(exc_type)) {
assert(Py_IsNone(exc_value));
*match = Py_NewRef(Py_None);
*rest = Py_NewRef(Py_None);
return 0;
}
assert(PyExceptionClass_Check(exc_type));
assert(PyExceptionInstance_Check(exc_value));
if (PyErr_GivenExceptionMatches(exc_type, match_type)) {
/* Full match of exc itself */
bool is_eg = _PyBaseExceptionGroup_Check(exc_value);
if (is_eg) {
*match = Py_NewRef(exc_value);
}
else {
/* naked exception - wrap it */
PyObject *excs = PyTuple_Pack(1, exc_value);
if (excs == NULL) {
return -1;
}
PyObject *wrapped = _PyExc_CreateExceptionGroup("", excs);
Py_DECREF(excs);
if (wrapped == NULL) {
return -1;
}
*match = wrapped;
}
*rest = Py_NewRef(Py_None);
return 0;
}
/* exc_value does not match match_type.
* Check for partial match if it's an exception group.
*/
if (_PyBaseExceptionGroup_Check(exc_value)) {
PyObject *pair = PyObject_CallMethod(exc_value, "split", "(O)",
match_type);
if (pair == NULL) {
return -1;
}
assert(PyTuple_CheckExact(pair));
assert(PyTuple_GET_SIZE(pair) == 2);
*match = Py_NewRef(PyTuple_GET_ITEM(pair, 0));
*rest = Py_NewRef(PyTuple_GET_ITEM(pair, 1));
Py_DECREF(pair);
return 0;
}
/* no match */
*match = Py_NewRef(Py_None);
*rest = Py_NewRef(Py_None);
return 0;
}
/* Logic for the final raise/reraise of a try-except* contruct
(too complicated for inlining).
*/
static bool
is_same_exception_metadata(PyObject *exc1, PyObject *exc2)
{
assert(PyExceptionInstance_Check(exc1));
assert(PyExceptionInstance_Check(exc2));
PyObject *tb1 = PyException_GetTraceback(exc1);
PyObject *ctx1 = PyException_GetContext(exc1);
PyObject *cause1 = PyException_GetCause(exc1);
PyObject *tb2 = PyException_GetTraceback(exc2);
PyObject *ctx2 = PyException_GetContext(exc2);
PyObject *cause2 = PyException_GetCause(exc2);
bool result = (Py_Is(tb1, tb2) &&
Py_Is(ctx1, ctx2) &&
Py_Is(cause1, cause2));
Py_XDECREF(tb1);
Py_XDECREF(ctx1);
Py_XDECREF(cause1);
Py_XDECREF(tb2);
Py_XDECREF(ctx2);
Py_XDECREF(cause2);
return result;
}
/*
excs: a list of exceptions to raise/reraise
orig: the original except that was caught
Calculates an exception group to raise. It contains
all exceptions in excs, where those that were reraised
have same nesting structure as in orig, and those that
were raised (if any) are added as siblings in a new EG.
Returns NULL and sets an exception on failure.
*/
static PyObject *
do_reraise_star(PyObject *excs, PyObject *orig)
{
assert(PyList_Check(excs));
assert(PyExceptionInstance_Check(orig));
Py_ssize_t numexcs = PyList_GET_SIZE(excs);
if (numexcs == 0) {
return Py_NewRef(Py_None);
}
if (!_PyBaseExceptionGroup_Check(orig)) {
/* a naked exception was caught and wrapped. Only one except* clause
* could have executed,so there is at most one exception to raise.
*/
assert(numexcs == 1 || (numexcs == 2 && PyList_GET_ITEM(excs, 1) == Py_None));
PyObject *e = PyList_GET_ITEM(excs, 0);
assert(e != NULL);
return Py_NewRef(e);
}
PyObject *raised_list = PyList_New(0);
if (raised_list == NULL) {
return NULL;
}
PyObject* reraised_list = PyList_New(0);
if (reraised_list == NULL) {
Py_DECREF(raised_list);
return NULL;
}
/* Now we are holding refs to raised_list and reraised_list */
PyObject *result = NULL;
/* Split excs into raised and reraised by comparing metadata with orig */
for (Py_ssize_t i = 0; i < numexcs; i++) {
PyObject *e = PyList_GET_ITEM(excs, i);
assert(e != NULL);
if (Py_IsNone(e)) {
continue;
}
bool is_reraise = is_same_exception_metadata(e, orig);
PyObject *append_list = is_reraise ? reraised_list : raised_list;
if (PyList_Append(append_list, e) < 0) {
goto done;
}
}
PyObject *reraised_eg = _PyExc_ExceptionGroupProjection(orig, reraised_list);
if (reraised_eg == NULL) {
goto done;
}
if (!Py_IsNone(reraised_eg)) {
assert(is_same_exception_metadata(reraised_eg, orig));
}
Py_ssize_t num_raised = PyList_GET_SIZE(raised_list);
if (num_raised == 0) {
result = reraised_eg;
}
else if (num_raised > 0) {
int res = 0;
if (!Py_IsNone(reraised_eg)) {
res = PyList_Append(raised_list, reraised_eg);
}
Py_DECREF(reraised_eg);
if (res < 0) {
goto done;
}
result = _PyExc_CreateExceptionGroup("", raised_list);
if (result == NULL) {
goto done;
}
}
done:
Py_XDECREF(raised_list);
Py_XDECREF(reraised_list);
return result;
}
/* Iterate v argcnt times and store the results on the stack (via decreasing /* Iterate v argcnt times and store the results on the stack (via decreasing
sp). Return 1 for success, 0 if error. sp). Return 1 for success, 0 if error.
@ -7020,10 +7320,12 @@ import_all_from(PyThreadState *tstate, PyObject *locals, PyObject *v)
return err; return err;
} }
#define CANNOT_CATCH_MSG "catching classes that do not inherit from "\ #define CANNOT_CATCH_MSG "catching classes that do not inherit from "\
"BaseException is not allowed" "BaseException is not allowed"
#define CANNOT_EXCEPT_STAR_EG "catching ExceptionGroup with except* "\
"is not allowed. Use except instead."
static int static int
check_except_type_valid(PyThreadState *tstate, PyObject* right) check_except_type_valid(PyThreadState *tstate, PyObject* right)
{ {
@ -7049,6 +7351,43 @@ check_except_type_valid(PyThreadState *tstate, PyObject* right)
return 0; return 0;
} }
static int
check_except_star_type_valid(PyThreadState *tstate, PyObject* right)
{
if (check_except_type_valid(tstate, right) < 0) {
return -1;
}
/* reject except *ExceptionGroup */
int is_subclass = 0;
if (PyTuple_Check(right)) {
Py_ssize_t length = PyTuple_GET_SIZE(right);
for (Py_ssize_t i = 0; i < length; i++) {
PyObject *exc = PyTuple_GET_ITEM(right, i);
is_subclass = PyObject_IsSubclass(exc, PyExc_BaseExceptionGroup);
if (is_subclass < 0) {
return -1;
}
if (is_subclass) {
break;
}
}
}
else {
is_subclass = PyObject_IsSubclass(right, PyExc_BaseExceptionGroup);
if (is_subclass < 0) {
return -1;
}
}
if (is_subclass) {
_PyErr_SetString(tstate, PyExc_TypeError,
CANNOT_EXCEPT_STAR_EG);
return -1;
}
return 0;
}
static int static int
check_args_iterable(PyThreadState *tstate, PyObject *func, PyObject *args) check_args_iterable(PyThreadState *tstate, PyObject *func, PyObject *args)
{ {

View File

@ -175,7 +175,7 @@ compiler IR.
enum fblocktype { WHILE_LOOP, FOR_LOOP, TRY_EXCEPT, FINALLY_TRY, FINALLY_END, enum fblocktype { WHILE_LOOP, FOR_LOOP, TRY_EXCEPT, FINALLY_TRY, FINALLY_END,
WITH, ASYNC_WITH, HANDLER_CLEANUP, POP_VALUE, EXCEPTION_HANDLER, WITH, ASYNC_WITH, HANDLER_CLEANUP, POP_VALUE, EXCEPTION_HANDLER,
ASYNC_COMPREHENSION_GENERATOR }; EXCEPTION_GROUP_HANDLER, ASYNC_COMPREHENSION_GENERATOR };
struct fblockinfo { struct fblockinfo {
enum fblocktype fb_type; enum fblocktype fb_type;
@ -323,6 +323,7 @@ static int compiler_call_helper(struct compiler *c, int n,
asdl_expr_seq *args, asdl_expr_seq *args,
asdl_keyword_seq *keywords); asdl_keyword_seq *keywords);
static int compiler_try_except(struct compiler *, stmt_ty); static int compiler_try_except(struct compiler *, stmt_ty);
static int compiler_try_star_except(struct compiler *, stmt_ty);
static int compiler_set_qualname(struct compiler *); static int compiler_set_qualname(struct compiler *);
static int compiler_sync_comprehension_generator( static int compiler_sync_comprehension_generator(
@ -1094,6 +1095,8 @@ stack_effect(int opcode, int oparg, int jump)
return -1; return -1;
case JUMP_IF_NOT_EXC_MATCH: case JUMP_IF_NOT_EXC_MATCH:
return -1; return -1;
case JUMP_IF_NOT_EG_MATCH:
return jump > 0 ? -1 : 2;
case IMPORT_NAME: case IMPORT_NAME:
return -1; return -1;
case IMPORT_FROM: case IMPORT_FROM:
@ -1131,6 +1134,8 @@ stack_effect(int opcode, int oparg, int jump)
* if an exception be raised. */ * if an exception be raised. */
return jump ? -1 + 4 : 0; return jump ? -1 + 4 : 0;
case PREP_RERAISE_STAR:
return 2;
case RERAISE: case RERAISE:
return -3; return -3;
case PUSH_EXC_INFO: case PUSH_EXC_INFO:
@ -1755,6 +1760,18 @@ find_ann(asdl_stmt_seq *stmts)
find_ann(st->v.Try.finalbody) || find_ann(st->v.Try.finalbody) ||
find_ann(st->v.Try.orelse); find_ann(st->v.Try.orelse);
break; break;
case TryStar_kind:
for (j = 0; j < asdl_seq_LEN(st->v.TryStar.handlers); j++) {
excepthandler_ty handler = (excepthandler_ty)asdl_seq_GET(
st->v.TryStar.handlers, j);
if (find_ann(handler->v.ExceptHandler.body)) {
return 1;
}
}
res = find_ann(st->v.TryStar.body) ||
find_ann(st->v.TryStar.finalbody) ||
find_ann(st->v.TryStar.orelse);
break;
default: default:
res = 0; res = 0;
} }
@ -1816,6 +1833,7 @@ compiler_unwind_fblock(struct compiler *c, struct fblockinfo *info,
switch (info->fb_type) { switch (info->fb_type) {
case WHILE_LOOP: case WHILE_LOOP:
case EXCEPTION_HANDLER: case EXCEPTION_HANDLER:
case EXCEPTION_GROUP_HANDLER:
case ASYNC_COMPREHENSION_GENERATOR: case ASYNC_COMPREHENSION_GENERATOR:
return 1; return 1;
@ -1919,6 +1937,10 @@ compiler_unwind_fblock_stack(struct compiler *c, int preserve_tos, struct fblock
return 1; return 1;
} }
struct fblockinfo *top = &c->u->u_fblock[c->u->u_nfblocks-1]; struct fblockinfo *top = &c->u->u_fblock[c->u->u_nfblocks-1];
if (top->fb_type == EXCEPTION_GROUP_HANDLER) {
return compiler_error(
c, "'break', 'continue' and 'return' cannot appear in an except* block");
}
if (loop != NULL && (top->fb_type == WHILE_LOOP || top->fb_type == FOR_LOOP)) { if (loop != NULL && (top->fb_type == WHILE_LOOP || top->fb_type == FOR_LOOP)) {
*loop = top; *loop = top;
return 1; return 1;
@ -3202,6 +3224,62 @@ compiler_try_finally(struct compiler *c, stmt_ty s)
return 1; return 1;
} }
static int
compiler_try_star_finally(struct compiler *c, stmt_ty s)
{
basicblock *body = compiler_new_block(c);
if (body == NULL) {
return 0;
}
basicblock *end = compiler_new_block(c);
if (!end) {
return 0;
}
basicblock *exit = compiler_new_block(c);
if (!exit) {
return 0;
}
basicblock *cleanup = compiler_new_block(c);
if (!cleanup) {
return 0;
}
/* `try` block */
ADDOP_JUMP(c, SETUP_FINALLY, end);
compiler_use_next_block(c, body);
if (!compiler_push_fblock(c, FINALLY_TRY, body, end, s->v.TryStar.finalbody)) {
return 0;
}
if (s->v.TryStar.handlers && asdl_seq_LEN(s->v.TryStar.handlers)) {
if (!compiler_try_star_except(c, s)) {
return 0;
}
}
else {
VISIT_SEQ(c, stmt, s->v.TryStar.body);
}
ADDOP_NOLINE(c, POP_BLOCK);
compiler_pop_fblock(c, FINALLY_TRY, body);
VISIT_SEQ(c, stmt, s->v.TryStar.finalbody);
ADDOP_JUMP_NOLINE(c, JUMP_FORWARD, exit);
/* `finally` block */
compiler_use_next_block(c, end);
UNSET_LOC(c);
ADDOP_JUMP(c, SETUP_CLEANUP, cleanup);
ADDOP(c, PUSH_EXC_INFO);
if (!compiler_push_fblock(c, FINALLY_END, end, NULL, NULL)) {
return 0;
}
VISIT_SEQ(c, stmt, s->v.TryStar.finalbody);
compiler_pop_fblock(c, FINALLY_END, end);
ADDOP_I(c, RERAISE, 0);
compiler_use_next_block(c, cleanup);
ADDOP(c, POP_EXCEPT_AND_RERAISE);
compiler_use_next_block(c, exit);
return 1;
}
/* /*
Code generated for "try: S except E1 as V1: S1 except E2 as V2: S2 ...": Code generated for "try: S except E1 as V1: S1 except E2 as V2: S2 ...":
(The contents of the value stack is shown in [], with the top (The contents of the value stack is shown in [], with the top
@ -3360,6 +3438,253 @@ compiler_try_except(struct compiler *c, stmt_ty s)
ADDOP(c, POP_EXCEPT_AND_RERAISE); ADDOP(c, POP_EXCEPT_AND_RERAISE);
compiler_use_next_block(c, orelse); compiler_use_next_block(c, orelse);
VISIT_SEQ(c, stmt, s->v.Try.orelse); VISIT_SEQ(c, stmt, s->v.Try.orelse);
ADDOP_JUMP(c, JUMP_FORWARD, end);
compiler_use_next_block(c, end);
return 1;
}
/*
Code generated for "try: S except* E1 as V1: S1 except* E2 as V2: S2 ...":
(The contents of the value stack is shown in [], with the top
at the right; 'tb' is trace-back info, 'val' the exception instance,
and 'typ' the exception's type.)
Value stack Label Instruction Argument
[] SETUP_FINALLY L1
[] <code for S>
[] POP_BLOCK
[] JUMP_FORWARD L0
[tb, val, typ] L1: DUP_TOP_TWO ) save a copy of the
[tb, val, typ, orig, typ] POP_TOP ) original raised exception
[tb, val, typ, orig] ROT_FOUR )
[orig, tb, val, typ] BUILD_LIST ) list for raised/reraised
[orig, tb, val, typ, res] ROT_FOUR ) exceptions ("result")
[orig, res, tb, val, typ] <evaluate E1> )
[orig, res, tb, val, typ, E1] JUMP_IF_NOT_EXC_MATCH L2 ) only if E1
[orig, res, tb, rest, typ, tb, match, typ] POP
[orig, res, tb, rest, typ, tb, match] <assign to V1> (or POP if no V1)
[orig, res, tb, rest, typ, tb] POP
[orig, res, tb, rest, typ] SETUP_FINALLY R1
[orig, res, tb, rest, typ] <code for S1>
[orig, res, tb, rest, typ] JUMP_FORWARD L2
[orig, res, tb, rest, typ, i, tb, v, t] R1: POP ) exception raised in except* body
[orig, res, tb, rest, typ, i, tb, v] LIST_APPEND 6 ) add it to res
[orig, res, tb, rest, typ, i, tb] POP
[orig, res, tb, rest, typ, i] POP
[orig, res, tb, rest, typ] L2: <evaluate E2>
.............................etc.......................
[orig, res, tb, rest, typ] Ln+1: POP ) add unhandled exception
[orig, res, tb, rest] LIST_APPEND 2 ) to res (could be None)
[orig, res, tb] POP
[orig, res] PREP_RERAISE_STAR
[i, tb, val, typ] POP_JUMP_IF_TRUE RER
[i, tb, val, typ] POP
[i, tb, val] POP
[i, tb] POP
[i] POP
[] JUMP_FORWARD L0
[i, tb, val, typ] RER: POP_EXCEPT_AND_RERAISE
[] L0: <next statement>
*/
static int
compiler_try_star_except(struct compiler *c, stmt_ty s)
{
basicblock *body = compiler_new_block(c);
if (body == NULL) {
return 0;
}
basicblock *except = compiler_new_block(c);
if (except == NULL) {
return 0;
}
basicblock *orelse = compiler_new_block(c);
if (orelse == NULL) {
return 0;
}
basicblock *end = compiler_new_block(c);
if (end == NULL) {
return 0;
}
basicblock *cleanup = compiler_new_block(c);
if (cleanup == NULL) {
return 0;
}
basicblock *reraise_star = compiler_new_block(c);
if (reraise_star == NULL) {
return 0;
}
ADDOP_JUMP(c, SETUP_FINALLY, except);
compiler_use_next_block(c, body);
if (!compiler_push_fblock(c, TRY_EXCEPT, body, NULL, NULL)) {
return 0;
}
VISIT_SEQ(c, stmt, s->v.TryStar.body);
compiler_pop_fblock(c, TRY_EXCEPT, body);
ADDOP_NOLINE(c, POP_BLOCK);
ADDOP_JUMP_NOLINE(c, JUMP_FORWARD, orelse);
Py_ssize_t n = asdl_seq_LEN(s->v.TryStar.handlers);
compiler_use_next_block(c, except);
UNSET_LOC(c);
ADDOP_JUMP(c, SETUP_CLEANUP, cleanup);
ADDOP(c, PUSH_EXC_INFO);
/* Runtime will push a block here, so we need to account for that */
if (!compiler_push_fblock(c, EXCEPTION_GROUP_HANDLER,
NULL, NULL, "except handler")) {
return 0;
}
for (Py_ssize_t i = 0; i < n; i++) {
excepthandler_ty handler = (excepthandler_ty)asdl_seq_GET(
s->v.TryStar.handlers, i);
SET_LOC(c, handler);
except = compiler_new_block(c);
if (except == NULL) {
return 0;
}
if (i == 0) {
/* Push the original EG into the stack */
/*
[tb, val, exc] DUP_TOP_TWO
[tb, val, exc, val, exc] POP_TOP
[tb, val, exc, val] ROT_FOUR
[val, tb, val, exc]
*/
ADDOP(c, DUP_TOP_TWO);
ADDOP(c, POP_TOP);
ADDOP(c, ROT_FOUR);
/* create empty list for exceptions raised/reraise in the except* blocks */
/*
[val, tb, val, exc] BUILD_LIST
[val, tb, val, exc, []] ROT_FOUR
[val, [], tb, val, exc]
*/
ADDOP_I(c, BUILD_LIST, 0);
ADDOP(c, ROT_FOUR);
}
if (handler->v.ExceptHandler.type) {
VISIT(c, expr, handler->v.ExceptHandler.type);
ADDOP_JUMP(c, JUMP_IF_NOT_EG_MATCH, except);
NEXT_BLOCK(c);
}
ADDOP(c, POP_TOP); // exc_type
basicblock *cleanup_end = compiler_new_block(c);
if (cleanup_end == NULL) {
return 0;
}
basicblock *cleanup_body = compiler_new_block(c);
if (cleanup_body == NULL) {
return 0;
}
if (handler->v.ExceptHandler.name) {
compiler_nameop(c, handler->v.ExceptHandler.name, Store);
}
else {
ADDOP(c, POP_TOP); // val
}
ADDOP(c, POP_TOP); // tb
/*
try:
# body
except type as name:
try:
# body
finally:
name = None # in case body contains "del name"
del name
*/
/* second try: */
ADDOP_JUMP(c, SETUP_CLEANUP, cleanup_end);
compiler_use_next_block(c, cleanup_body);
if (!compiler_push_fblock(c, HANDLER_CLEANUP, cleanup_body, NULL, handler->v.ExceptHandler.name))
return 0;
/* second # body */
VISIT_SEQ(c, stmt, handler->v.ExceptHandler.body);
compiler_pop_fblock(c, HANDLER_CLEANUP, cleanup_body);
/* name = None; del name; # Mark as artificial */
UNSET_LOC(c);
ADDOP(c, POP_BLOCK);
if (handler->v.ExceptHandler.name) {
ADDOP_LOAD_CONST(c, Py_None);
compiler_nameop(c, handler->v.ExceptHandler.name, Store);
compiler_nameop(c, handler->v.ExceptHandler.name, Del);
}
ADDOP_JUMP(c, JUMP_FORWARD, except);
/* except: */
compiler_use_next_block(c, cleanup_end);
/* name = None; del name; # Mark as artificial */
UNSET_LOC(c);
if (handler->v.ExceptHandler.name) {
ADDOP_LOAD_CONST(c, Py_None);
compiler_nameop(c, handler->v.ExceptHandler.name, Store);
compiler_nameop(c, handler->v.ExceptHandler.name, Del);
}
/* add exception raised to the res list */
ADDOP(c, POP_TOP); // type
ADDOP_I(c, LIST_APPEND, 6); // exc
ADDOP(c, POP_TOP); // tb
ADDOP(c, POP_TOP); // lasti
ADDOP_JUMP(c, JUMP_ABSOLUTE, except);
compiler_use_next_block(c, except);
if (i == n - 1) {
/* Add exc to the list (if not None it's the unhandled part of the EG) */
ADDOP(c, POP_TOP);
ADDOP_I(c, LIST_APPEND, 2);
ADDOP(c, POP_TOP);
ADDOP_JUMP(c, JUMP_FORWARD, reraise_star);
}
}
/* Mark as artificial */
UNSET_LOC(c);
compiler_pop_fblock(c, EXCEPTION_GROUP_HANDLER, NULL);
basicblock *reraise = compiler_new_block(c);
if (!reraise) {
return 0;
}
compiler_use_next_block(c, reraise_star);
ADDOP(c, PREP_RERAISE_STAR);
ADDOP(c, DUP_TOP);
ADDOP_JUMP(c, POP_JUMP_IF_TRUE, reraise);
NEXT_BLOCK(c);
/* Nothing to reraise - pop it */
ADDOP(c, POP_TOP);
ADDOP(c, POP_TOP);
ADDOP(c, POP_TOP);
ADDOP(c, POP_TOP);
ADDOP(c, POP_BLOCK);
ADDOP(c, POP_EXCEPT);
ADDOP_JUMP(c, JUMP_FORWARD, end);
compiler_use_next_block(c, reraise);
ADDOP(c, POP_BLOCK);
ADDOP(c, POP_EXCEPT_AND_RERAISE);
compiler_use_next_block(c, cleanup);
ADDOP(c, POP_EXCEPT_AND_RERAISE);
compiler_use_next_block(c, orelse);
VISIT_SEQ(c, stmt, s->v.TryStar.orelse);
ADDOP_JUMP(c, JUMP_FORWARD, end);
compiler_use_next_block(c, end); compiler_use_next_block(c, end);
return 1; return 1;
} }
@ -3372,6 +3697,16 @@ compiler_try(struct compiler *c, stmt_ty s) {
return compiler_try_except(c, s); return compiler_try_except(c, s);
} }
static int
compiler_try_star(struct compiler *c, stmt_ty s)
{
if (s->v.TryStar.finalbody && asdl_seq_LEN(s->v.TryStar.finalbody)) {
return compiler_try_star_finally(c, s);
}
else {
return compiler_try_star_except(c, s);
}
}
static int static int
compiler_import_as(struct compiler *c, identifier name, identifier asname) compiler_import_as(struct compiler *c, identifier name, identifier asname)
@ -3634,6 +3969,8 @@ compiler_visit_stmt(struct compiler *c, stmt_ty s)
break; break;
case Try_kind: case Try_kind:
return compiler_try(c, s); return compiler_try(c, s);
case TryStar_kind:
return compiler_try_star(c, s);
case Assert_kind: case Assert_kind:
return compiler_assert(c, s); return compiler_assert(c, s);
case Import_kind: case Import_kind:

View File

@ -87,7 +87,7 @@ static void *opcode_targets[256] = {
&&TARGET_SETUP_ANNOTATIONS, &&TARGET_SETUP_ANNOTATIONS,
&&TARGET_YIELD_VALUE, &&TARGET_YIELD_VALUE,
&&TARGET_LOAD_CONST__LOAD_FAST, &&TARGET_LOAD_CONST__LOAD_FAST,
&&TARGET_STORE_FAST__STORE_FAST, &&TARGET_PREP_RERAISE_STAR,
&&TARGET_POP_EXCEPT, &&TARGET_POP_EXCEPT,
&&TARGET_STORE_NAME, &&TARGET_STORE_NAME,
&&TARGET_DELETE_NAME, &&TARGET_DELETE_NAME,
@ -122,11 +122,11 @@ static void *opcode_targets[256] = {
&&TARGET_COPY, &&TARGET_COPY,
&&TARGET_JUMP_IF_NOT_EXC_MATCH, &&TARGET_JUMP_IF_NOT_EXC_MATCH,
&&TARGET_BINARY_OP, &&TARGET_BINARY_OP,
&&_unknown_opcode, &&TARGET_STORE_FAST__STORE_FAST,
&&TARGET_LOAD_FAST, &&TARGET_LOAD_FAST,
&&TARGET_STORE_FAST, &&TARGET_STORE_FAST,
&&TARGET_DELETE_FAST, &&TARGET_DELETE_FAST,
&&_unknown_opcode, &&TARGET_JUMP_IF_NOT_EG_MATCH,
&&_unknown_opcode, &&_unknown_opcode,
&&TARGET_GEN_START, &&TARGET_GEN_START,
&&TARGET_RAISE_VARARGS, &&TARGET_RAISE_VARARGS,

View File

@ -1345,6 +1345,12 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s)
VISIT_SEQ(st, excepthandler, s->v.Try.handlers); VISIT_SEQ(st, excepthandler, s->v.Try.handlers);
VISIT_SEQ(st, stmt, s->v.Try.finalbody); VISIT_SEQ(st, stmt, s->v.Try.finalbody);
break; break;
case TryStar_kind:
VISIT_SEQ(st, stmt, s->v.TryStar.body);
VISIT_SEQ(st, stmt, s->v.TryStar.orelse);
VISIT_SEQ(st, excepthandler, s->v.TryStar.handlers);
VISIT_SEQ(st, stmt, s->v.TryStar.finalbody);
break;
case Assert_kind: case Assert_kind:
VISIT(st, expr, s->v.Assert.test); VISIT(st, expr, s->v.Assert.test);
if (s->v.Assert.msg) if (s->v.Assert.msg)