From c4911621eab5a6cc05adac63fc7d632955dd1d94 Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Fri, 18 Aug 2023 22:02:27 +0100 Subject: [PATCH 1/9] gh-108113: optimize ASTs in ast.parse/ast.literal_eval/compile(..., flags=ast.PyCF_ONLY_AST) --- Doc/library/ast.rst | 10 ++- Doc/whatsnew/3.13.rst | 14 ++++ Lib/ast.py | 6 +- Lib/test/test_ast.py | 76 +++++++++++-------- Lib/test/test_builtin.py | 2 +- ...-08-18-18-21-27.gh-issue-108113.1h0poE.rst | 6 ++ Python/pythonrun.c | 23 ++++++ 7 files changed, 99 insertions(+), 38 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-08-18-18-21-27.gh-issue-108113.1h0poE.rst diff --git a/Doc/library/ast.rst b/Doc/library/ast.rst index cd657aedf6d23d..836ec18f2c0980 100644 --- a/Doc/library/ast.rst +++ b/Doc/library/ast.rst @@ -2122,10 +2122,10 @@ Async and await Apart from the node classes, the :mod:`ast` module defines these utility functions and classes for traversing abstract syntax trees: -.. function:: parse(source, filename='', mode='exec', *, type_comments=False, feature_version=None) +.. function:: parse(source, filename='', mode='exec', *, type_comments=False, feature_version=None, optimize=-1) Parse the source into an AST node. Equivalent to ``compile(source, - filename, mode, ast.PyCF_ONLY_AST)``. + filename, mode, flags=ast.PyCF_ONLY_AST, optimize=optimize)``. If ``type_comments=True`` is given, the parser is modified to check and return type comments as specified by :pep:`484` and :pep:`526`. @@ -2172,6 +2172,10 @@ and classes for traversing abstract syntax trees: .. versionchanged:: 3.13 The minimum supported version for feature_version is now (3,7) + The output AST is now optimized with constant folding. + The ``optimize`` argument was added to control additional + optimizations. + .. function:: unparse(ast_obj) @@ -2229,6 +2233,8 @@ and classes for traversing abstract syntax trees: .. versionchanged:: 3.10 For string inputs, leading spaces and tabs are now stripped. + .. versionchanged:: 3.13 + This function now understands and collapses const expressions. .. function:: get_docstring(node, clean=True) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 13ae6e595dc993..b1ed6f02390c23 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -85,6 +85,10 @@ Other Language Changes This change will affect tools using docstrings, like :mod:`doctest`. (Contributed by Inada Naoki in :gh:`81283`.) +* The :func:`compile` built-in no longer ignores the ``optimize`` argument + when called with the ``ast.PyCF_ONLY_AST`` flag. + (Contributed by Irit Katriel in :gh:`108113`). + New Modules =========== @@ -94,6 +98,16 @@ New Modules Improved Modules ================ +ast +--- + +* :func:`ast.parse` and :func:`ast.literal_eval` now perform constant folding + and other AST optimizations. This means that AST are more concise, and + :func:`ast.literal_eval` understands and collapses const expressions. + :func:`ast.parse` also accepts a new optional argument ``optimize``, which + it forwards to the :func:`compile` built-in. + (Contributed by Irit Katriel in :gh:`108113`). + array ----- diff --git a/Lib/ast.py b/Lib/ast.py index a307f3ecd06175..16c8773c2d0a01 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -32,7 +32,7 @@ def parse(source, filename='', mode='exec', *, - type_comments=False, feature_version=None): + type_comments=False, feature_version=None, optimize=-1): """ Parse the source into an AST node. Equivalent to compile(source, filename, mode, PyCF_ONLY_AST). @@ -50,7 +50,7 @@ def parse(source, filename='', mode='exec', *, feature_version = minor # Else it should be an int giving the minor version for 3.x. return compile(source, filename, mode, flags, - _feature_version=feature_version) + _feature_version=feature_version, optimize=optimize) def literal_eval(node_or_string): @@ -63,7 +63,7 @@ def literal_eval(node_or_string): Caution: A complex expression can overflow the C stack and cause a crash. """ if isinstance(node_or_string, str): - node_or_string = parse(node_or_string.lstrip(" \t"), mode='eval') + node_or_string = parse(node_or_string.lstrip(" \t"), mode='eval', optimize=0) if isinstance(node_or_string, Expression): node_or_string = node_or_string.body def _raise_malformed_node(node): diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index 5346b39043f0f5..dba9e799c1380a 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -21,8 +21,8 @@ def to_tuple(t): if t is None or isinstance(t, (str, int, complex)) or t is Ellipsis: return t - elif isinstance(t, list): - return [to_tuple(e) for e in t] + elif isinstance(t, (list, tuple)): + return type(t)([to_tuple(e) for e in t]) result = [t.__class__.__name__] if hasattr(t, 'lineno') and hasattr(t, 'col_offset'): result.append((t.lineno, t.col_offset)) @@ -274,7 +274,7 @@ def to_tuple(t): # Tuple "1,2,3", # Tuple - "(1,2,3)", + "(1,x,3)", # Empty tuple "()", # Combination @@ -357,6 +357,15 @@ def test_ast_validation(self): tree = ast.parse(snippet) compile(tree, '', 'exec') + def test_optimization_levels(self): + cases = [(-1, __debug__), (0, True), (1, False), (2, False)] + for (optval, expected) in cases: + with self.subTest(optval=optval, expected=expected): + res = ast.parse("__debug__", optimize=optval) + self.assertIsInstance(res.body[0], ast.Expr) + self.assertIsInstance(res.body[0].value, ast.Constant) + self.assertEqual(res.body[0].value.value, expected) + def test_invalid_position_information(self): invalid_linenos = [ (10, 1), (-10, -11), (10, -11), (-5, -2), (-5, 1) @@ -948,7 +957,7 @@ def bad_normalize(*args): self.assertRaises(TypeError, ast.parse, '\u03D5') def test_issue18374_binop_col_offset(self): - tree = ast.parse('4+5+6+7') + tree = ast.parse('a+b+c+d') parent_binop = tree.body[0].value child_binop = parent_binop.left grandchild_binop = child_binop.left @@ -959,7 +968,7 @@ def test_issue18374_binop_col_offset(self): self.assertEqual(grandchild_binop.col_offset, 0) self.assertEqual(grandchild_binop.end_col_offset, 3) - tree = ast.parse('4+5-\\\n 6-7') + tree = ast.parse('a+b-\\\n c-d') parent_binop = tree.body[0].value child_binop = parent_binop.left grandchild_binop = child_binop.left @@ -1266,13 +1275,14 @@ def test_dump_incomplete(self): ) def test_copy_location(self): - src = ast.parse('1 + 1', mode='eval') + src = ast.parse('x + 1', mode='eval') src.body.right = ast.copy_location(ast.Constant(2), src.body.right) self.assertEqual(ast.dump(src, include_attributes=True), - 'Expression(body=BinOp(left=Constant(value=1, lineno=1, col_offset=0, ' - 'end_lineno=1, end_col_offset=1), op=Add(), right=Constant(value=2, ' - 'lineno=1, col_offset=4, end_lineno=1, end_col_offset=5), lineno=1, ' - 'col_offset=0, end_lineno=1, end_col_offset=5))' + "Expression(body=BinOp(left=Name(id='x', ctx=Load(), lineno=1, " + "col_offset=0, end_lineno=1, end_col_offset=1), op=Add(), " + "right=Constant(value=2, lineno=1, col_offset=4, end_lineno=1, " + "end_col_offset=5), lineno=1, col_offset=0, end_lineno=1, " + "end_col_offset=5))" ) src = ast.Call(col_offset=1, lineno=1, end_lineno=1, end_col_offset=1) new = ast.copy_location(src, ast.Call(col_offset=None, lineno=None)) @@ -1302,20 +1312,22 @@ def test_fix_missing_locations(self): ) def test_increment_lineno(self): - src = ast.parse('1 + 1', mode='eval') + src = ast.parse('x + 1', mode='eval') self.assertEqual(ast.increment_lineno(src, n=3), src) self.assertEqual(ast.dump(src, include_attributes=True), - 'Expression(body=BinOp(left=Constant(value=1, lineno=4, col_offset=0, ' - 'end_lineno=4, end_col_offset=1), op=Add(), right=Constant(value=1, ' + 'Expression(body=BinOp(left=Name(id=\'x\', ctx=Load(), ' + 'lineno=4, col_offset=0, end_lineno=4, end_col_offset=1), ' + 'op=Add(), right=Constant(value=1, ' 'lineno=4, col_offset=4, end_lineno=4, end_col_offset=5), lineno=4, ' 'col_offset=0, end_lineno=4, end_col_offset=5))' ) # issue10869: do not increment lineno of root twice - src = ast.parse('1 + 1', mode='eval') + src = ast.parse('y + 2', mode='eval') self.assertEqual(ast.increment_lineno(src.body, n=3), src.body) self.assertEqual(ast.dump(src, include_attributes=True), - 'Expression(body=BinOp(left=Constant(value=1, lineno=4, col_offset=0, ' - 'end_lineno=4, end_col_offset=1), op=Add(), right=Constant(value=1, ' + 'Expression(body=BinOp(left=Name(id=\'y\', ctx=Load(), ' + 'lineno=4, col_offset=0, end_lineno=4, end_col_offset=1), ' + 'op=Add(), right=Constant(value=2, ' 'lineno=4, col_offset=4, end_lineno=4, end_col_offset=5), lineno=4, ' 'col_offset=0, end_lineno=4, end_col_offset=5))' ) @@ -1446,9 +1458,9 @@ def test_literal_eval(self): self.assertEqual(ast.literal_eval('+3.25'), 3.25) self.assertEqual(ast.literal_eval('-3.25'), -3.25) self.assertEqual(repr(ast.literal_eval('-0.0')), '-0.0') - self.assertRaises(ValueError, ast.literal_eval, '++6') - self.assertRaises(ValueError, ast.literal_eval, '+True') - self.assertRaises(ValueError, ast.literal_eval, '2+3') + self.assertEqual(ast.literal_eval('++6'), 6) + self.assertEqual(ast.literal_eval('+True'), 1) + self.assertEqual(ast.literal_eval('2+3'), 5) def test_literal_eval_str_int_limit(self): with support.adjust_int_max_str_digits(4000): @@ -1473,11 +1485,11 @@ def test_literal_eval_complex(self): self.assertEqual(ast.literal_eval('3.25-6.75j'), 3.25-6.75j) self.assertEqual(ast.literal_eval('-3.25-6.75j'), -3.25-6.75j) self.assertEqual(ast.literal_eval('(3+6j)'), 3+6j) - self.assertRaises(ValueError, ast.literal_eval, '-6j+3') - self.assertRaises(ValueError, ast.literal_eval, '-6j+3j') - self.assertRaises(ValueError, ast.literal_eval, '3+-6j') - self.assertRaises(ValueError, ast.literal_eval, '3+(0+6j)') - self.assertRaises(ValueError, ast.literal_eval, '-(3+6j)') + self.assertEqual(ast.literal_eval('-6j+3'), 3-6j) + self.assertEqual(ast.literal_eval('-6j+3j'), -3j) + self.assertEqual(ast.literal_eval('3+-6j'), 3-6j) + self.assertEqual(ast.literal_eval('3+(0+6j)'), 3+6j) + self.assertEqual(ast.literal_eval('-(3+6j)'), -3-6j) def test_literal_eval_malformed_dict_nodes(self): malformed = ast.Dict(keys=[ast.Constant(1), ast.Constant(2)], values=[ast.Constant(3)]) @@ -1494,7 +1506,7 @@ def test_literal_eval_trailing_ws(self): def test_literal_eval_malformed_lineno(self): msg = r'malformed node or string on line 3:' with self.assertRaisesRegex(ValueError, msg): - ast.literal_eval("{'a': 1,\n'b':2,\n'c':++3,\n'd':4}") + ast.literal_eval("{'a': 1,\n'b':2,\n'c':++x,\n'd':4}") node = ast.UnaryOp( ast.UAdd(), ast.UnaryOp(ast.UAdd(), ast.Constant(6))) @@ -2265,7 +2277,7 @@ def test_load_const(self): consts) def test_literal_eval(self): - tree = ast.parse("1 + 2") + tree = ast.parse("x + 2") binop = tree.body[0].value new_left = ast.Constant(value=10) @@ -2479,14 +2491,14 @@ def test_slices(self): def test_binop(self): s = dedent(''' - (1 * 2 + (3 ) + + (1 * x + (3 ) + 4 ) ''').strip() binop = self._parse_value(s) self._check_end_pos(binop, 2, 6) self._check_content(s, binop.right, '4') - self._check_content(s, binop.left, '1 * 2 + (3 )') + self._check_content(s, binop.left, '1 * x + (3 )') self._check_content(s, binop.left.right, '3') def test_boolop(self): @@ -3039,7 +3051,7 @@ def main(): ('Module', [('FunctionDef', (1, 0, 1, 38), 'f', ('arguments', [], [], None, [], [], None, []), [('Pass', (1, 34, 1, 38))], [], None, None, [('TypeVar', (1, 6, 1, 19), 'T', ('Tuple', (1, 9, 1, 19), [('Name', (1, 10, 1, 13), 'int', ('Load',)), ('Name', (1, 15, 1, 18), 'str', ('Load',))], ('Load',))), ('TypeVarTuple', (1, 21, 1, 24), 'Ts'), ('ParamSpec', (1, 26, 1, 29), 'P')])], []), ] single_results = [ -('Interactive', [('Expr', (1, 0, 1, 3), ('BinOp', (1, 0, 1, 3), ('Constant', (1, 0, 1, 1), 1, None), ('Add',), ('Constant', (1, 2, 1, 3), 2, None)))]), +('Interactive', [('Expr', (1, 0, 1, 3), ('Constant', (1, 0, 1, 3), 3, None))]), ] eval_results = [ ('Expression', ('Constant', (1, 0, 1, 4), None, None)), @@ -3073,9 +3085,9 @@ def main(): ('Expression', ('Name', (1, 0, 1, 1), 'v', ('Load',))), ('Expression', ('List', (1, 0, 1, 7), [('Constant', (1, 1, 1, 2), 1, None), ('Constant', (1, 3, 1, 4), 2, None), ('Constant', (1, 5, 1, 6), 3, None)], ('Load',))), ('Expression', ('List', (1, 0, 1, 2), [], ('Load',))), -('Expression', ('Tuple', (1, 0, 1, 5), [('Constant', (1, 0, 1, 1), 1, None), ('Constant', (1, 2, 1, 3), 2, None), ('Constant', (1, 4, 1, 5), 3, None)], ('Load',))), -('Expression', ('Tuple', (1, 0, 1, 7), [('Constant', (1, 1, 1, 2), 1, None), ('Constant', (1, 3, 1, 4), 2, None), ('Constant', (1, 5, 1, 6), 3, None)], ('Load',))), -('Expression', ('Tuple', (1, 0, 1, 2), [], ('Load',))), +('Expression', ('Constant', (1, 0, 1, 5), (1, 2, 3), None)), +('Expression', ('Tuple', (1, 0, 1, 7), [('Constant', (1, 1, 1, 2), 1, None), ('Name', (1, 3, 1, 4), 'x', ('Load',)), ('Constant', (1, 5, 1, 6), 3, None)], ('Load',))), +('Expression', ('Constant', (1, 0, 1, 2), (), None)), ('Expression', ('Call', (1, 0, 1, 17), ('Attribute', (1, 0, 1, 7), ('Attribute', (1, 0, 1, 5), ('Attribute', (1, 0, 1, 3), ('Name', (1, 0, 1, 1), 'a', ('Load',)), 'b', ('Load',)), 'c', ('Load',)), 'd', ('Load',)), [('Subscript', (1, 8, 1, 16), ('Attribute', (1, 8, 1, 11), ('Name', (1, 8, 1, 9), 'a', ('Load',)), 'b', ('Load',)), ('Slice', (1, 12, 1, 15), ('Constant', (1, 12, 1, 13), 1, None), ('Constant', (1, 14, 1, 15), 2, None), None), ('Load',))], [])), ] main() diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index f5a5c037f1bf1b..fb73fe59c71fb1 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -372,7 +372,7 @@ def f(): """doc""" # test both direct compilation and compilation via AST codeobjs = [] codeobjs.append(compile(codestr, "", "exec", optimize=optval)) - tree = ast.parse(codestr) + tree = ast.parse(codestr, optimize=optval) codeobjs.append(compile(tree, "", "exec", optimize=optval)) for code in codeobjs: ns = {} diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-08-18-18-21-27.gh-issue-108113.1h0poE.rst b/Misc/NEWS.d/next/Core and Builtins/2023-08-18-18-21-27.gh-issue-108113.1h0poE.rst new file mode 100644 index 00000000000000..edf033d77dce11 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-08-18-18-21-27.gh-issue-108113.1h0poE.rst @@ -0,0 +1,6 @@ +The :func:`compile` built-in no longer ignores the ``optimize`` argument +when called with the ``ast.PyCF_ONLY_AST`` flag. The :func:`ast.parse` +function now accepts an optional argument ``optimize``, which it forwards to +:func:`compile`. :func:`ast.parse` and :func:`ast.literal_eval` perform +const folding, so ASTs are more concise and :func:`ast.literal_eval` +accepts const expressions. diff --git a/Python/pythonrun.c b/Python/pythonrun.c index 721c527745c44a..a3ab0da2882bf1 100644 --- a/Python/pythonrun.c +++ b/Python/pythonrun.c @@ -21,6 +21,7 @@ #include "pycore_pyerrors.h" // _PyErr_GetRaisedException, _Py_Offer_Suggestions #include "pycore_pylifecycle.h" // _Py_UnhandledKeyboardInterrupt #include "pycore_pystate.h" // _PyInterpreterState_GET() +#include "pycore_symtable.h" // _PyFuture_FromAST() #include "pycore_sysmodule.h" // _PySys_Audit() #include "pycore_traceback.h" // _PyTraceBack_Print_Indented() @@ -1790,6 +1791,24 @@ run_pyc_file(FILE *fp, PyObject *globals, PyObject *locals, return NULL; } +static int +call_ast_optimize(mod_ty mod, PyObject *filename, PyCompilerFlags *cf, + int optimize, PyArena *arena) +{ + PyFutureFeatures future; + if (!_PyFuture_FromAST(mod, filename, &future)) { + return -1; + } + int flags = future.ff_features | cf->cf_flags; + if (optimize == -1) { + optimize = _Py_GetConfig()->optimization_level; + } + if (!_PyAST_Optimize(mod, arena, optimize, flags)) { + return -1; + } + return 0; +} + PyObject * Py_CompileStringObject(const char *str, PyObject *filename, int start, PyCompilerFlags *flags, int optimize) @@ -1806,6 +1825,10 @@ Py_CompileStringObject(const char *str, PyObject *filename, int start, return NULL; } if (flags && (flags->cf_flags & PyCF_ONLY_AST)) { + if (call_ast_optimize(mod, filename, flags, optimize, arena) < 0) { + _PyArena_Free(arena); + return NULL; + } PyObject *result = PyAST_mod2obj(mod); _PyArena_Free(arena); return result; From 65b14610d626736f5c924e51d4d16e0cd4b4c774 Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Sat, 19 Aug 2023 18:27:17 +0100 Subject: [PATCH 2/9] add PyCF_OPTIMIZED_AST --- Include/cpython/compile.h | 3 +- Lib/ast.py | 2 + Lib/test/test_ast.py | 77 ++++++++++++++++++++------------------- Python/Python-ast.c | 3 ++ Python/pythonrun.c | 12 +++--- 5 files changed, 53 insertions(+), 44 deletions(-) diff --git a/Include/cpython/compile.h b/Include/cpython/compile.h index e6cd39af2ba739..ae17cef554fa17 100644 --- a/Include/cpython/compile.h +++ b/Include/cpython/compile.h @@ -19,9 +19,10 @@ #define PyCF_TYPE_COMMENTS 0x1000 #define PyCF_ALLOW_TOP_LEVEL_AWAIT 0x2000 #define PyCF_ALLOW_INCOMPLETE_INPUT 0x4000 +#define PyCF_OPTIMIZED_AST (0x8000 | PyCF_ONLY_AST) #define PyCF_COMPILE_MASK (PyCF_ONLY_AST | PyCF_ALLOW_TOP_LEVEL_AWAIT | \ PyCF_TYPE_COMMENTS | PyCF_DONT_IMPLY_DEDENT | \ - PyCF_ALLOW_INCOMPLETE_INPUT) + PyCF_ALLOW_INCOMPLETE_INPUT | PyCF_OPTIMIZED_AST) typedef struct { int cf_flags; /* bitmask of CO_xxx flags relevant to future */ diff --git a/Lib/ast.py b/Lib/ast.py index 16c8773c2d0a01..c336bfc884e05d 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -39,6 +39,8 @@ def parse(source, filename='', mode='exec', *, Pass type_comments=True to get back type comments where the syntax allows. """ flags = PyCF_ONLY_AST + if optimize > 0: + flags |= PyCF_OPTIMIZED_AST if type_comments: flags |= PyCF_TYPE_COMMENTS if feature_version is None: diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index dba9e799c1380a..d570d9b687b2d2 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -21,8 +21,8 @@ def to_tuple(t): if t is None or isinstance(t, (str, int, complex)) or t is Ellipsis: return t - elif isinstance(t, (list, tuple)): - return type(t)([to_tuple(e) for e in t]) + elif isinstance(t, list): + return [to_tuple(e) for e in t] result = [t.__class__.__name__] if hasattr(t, 'lineno') and hasattr(t, 'col_offset'): result.append((t.lineno, t.col_offset)) @@ -274,7 +274,7 @@ def to_tuple(t): # Tuple "1,2,3", # Tuple - "(1,x,3)", + "(1,2,3)", # Empty tuple "()", # Combination @@ -358,13 +358,17 @@ def test_ast_validation(self): compile(tree, '', 'exec') def test_optimization_levels(self): - cases = [(-1, __debug__), (0, True), (1, False), (2, False)] + cases = [(-1, '__debug__'), (0, '__debug__'), (1, False), (2, False)] for (optval, expected) in cases: with self.subTest(optval=optval, expected=expected): res = ast.parse("__debug__", optimize=optval) self.assertIsInstance(res.body[0], ast.Expr) - self.assertIsInstance(res.body[0].value, ast.Constant) - self.assertEqual(res.body[0].value.value, expected) + if isinstance(expected, bool): + self.assertIsInstance(res.body[0].value, ast.Constant) + self.assertEqual(res.body[0].value.value, expected) + else: + self.assertIsInstance(res.body[0].value, ast.Name) + self.assertEqual(res.body[0].value.id, expected) def test_invalid_position_information(self): invalid_linenos = [ @@ -957,7 +961,7 @@ def bad_normalize(*args): self.assertRaises(TypeError, ast.parse, '\u03D5') def test_issue18374_binop_col_offset(self): - tree = ast.parse('a+b+c+d') + tree = ast.parse('4+5+6+7') parent_binop = tree.body[0].value child_binop = parent_binop.left grandchild_binop = child_binop.left @@ -968,7 +972,7 @@ def test_issue18374_binop_col_offset(self): self.assertEqual(grandchild_binop.col_offset, 0) self.assertEqual(grandchild_binop.end_col_offset, 3) - tree = ast.parse('a+b-\\\n c-d') + tree = ast.parse('4+5-\\\n 6-7') parent_binop = tree.body[0].value child_binop = parent_binop.left grandchild_binop = child_binop.left @@ -1275,14 +1279,13 @@ def test_dump_incomplete(self): ) def test_copy_location(self): - src = ast.parse('x + 1', mode='eval') + src = ast.parse('1 + 1', mode='eval') src.body.right = ast.copy_location(ast.Constant(2), src.body.right) self.assertEqual(ast.dump(src, include_attributes=True), - "Expression(body=BinOp(left=Name(id='x', ctx=Load(), lineno=1, " - "col_offset=0, end_lineno=1, end_col_offset=1), op=Add(), " - "right=Constant(value=2, lineno=1, col_offset=4, end_lineno=1, " - "end_col_offset=5), lineno=1, col_offset=0, end_lineno=1, " - "end_col_offset=5))" + 'Expression(body=BinOp(left=Constant(value=1, lineno=1, col_offset=0, ' + 'end_lineno=1, end_col_offset=1), op=Add(), right=Constant(value=2, ' + 'lineno=1, col_offset=4, end_lineno=1, end_col_offset=5), lineno=1, ' + 'col_offset=0, end_lineno=1, end_col_offset=5))' ) src = ast.Call(col_offset=1, lineno=1, end_lineno=1, end_col_offset=1) new = ast.copy_location(src, ast.Call(col_offset=None, lineno=None)) @@ -1312,22 +1315,20 @@ def test_fix_missing_locations(self): ) def test_increment_lineno(self): - src = ast.parse('x + 1', mode='eval') + src = ast.parse('1 + 1', mode='eval') self.assertEqual(ast.increment_lineno(src, n=3), src) self.assertEqual(ast.dump(src, include_attributes=True), - 'Expression(body=BinOp(left=Name(id=\'x\', ctx=Load(), ' - 'lineno=4, col_offset=0, end_lineno=4, end_col_offset=1), ' - 'op=Add(), right=Constant(value=1, ' + 'Expression(body=BinOp(left=Constant(value=1, lineno=4, col_offset=0, ' + 'end_lineno=4, end_col_offset=1), op=Add(), right=Constant(value=1, ' 'lineno=4, col_offset=4, end_lineno=4, end_col_offset=5), lineno=4, ' 'col_offset=0, end_lineno=4, end_col_offset=5))' ) # issue10869: do not increment lineno of root twice - src = ast.parse('y + 2', mode='eval') + src = ast.parse('1 + 1', mode='eval') self.assertEqual(ast.increment_lineno(src.body, n=3), src.body) self.assertEqual(ast.dump(src, include_attributes=True), - 'Expression(body=BinOp(left=Name(id=\'y\', ctx=Load(), ' - 'lineno=4, col_offset=0, end_lineno=4, end_col_offset=1), ' - 'op=Add(), right=Constant(value=2, ' + 'Expression(body=BinOp(left=Constant(value=1, lineno=4, col_offset=0, ' + 'end_lineno=4, end_col_offset=1), op=Add(), right=Constant(value=1, ' 'lineno=4, col_offset=4, end_lineno=4, end_col_offset=5), lineno=4, ' 'col_offset=0, end_lineno=4, end_col_offset=5))' ) @@ -1458,9 +1459,9 @@ def test_literal_eval(self): self.assertEqual(ast.literal_eval('+3.25'), 3.25) self.assertEqual(ast.literal_eval('-3.25'), -3.25) self.assertEqual(repr(ast.literal_eval('-0.0')), '-0.0') - self.assertEqual(ast.literal_eval('++6'), 6) - self.assertEqual(ast.literal_eval('+True'), 1) - self.assertEqual(ast.literal_eval('2+3'), 5) + self.assertRaises(ValueError, ast.literal_eval, '++6') + self.assertRaises(ValueError, ast.literal_eval, '+True') + self.assertRaises(ValueError, ast.literal_eval, '2+3') def test_literal_eval_str_int_limit(self): with support.adjust_int_max_str_digits(4000): @@ -1485,11 +1486,11 @@ def test_literal_eval_complex(self): self.assertEqual(ast.literal_eval('3.25-6.75j'), 3.25-6.75j) self.assertEqual(ast.literal_eval('-3.25-6.75j'), -3.25-6.75j) self.assertEqual(ast.literal_eval('(3+6j)'), 3+6j) - self.assertEqual(ast.literal_eval('-6j+3'), 3-6j) - self.assertEqual(ast.literal_eval('-6j+3j'), -3j) - self.assertEqual(ast.literal_eval('3+-6j'), 3-6j) - self.assertEqual(ast.literal_eval('3+(0+6j)'), 3+6j) - self.assertEqual(ast.literal_eval('-(3+6j)'), -3-6j) + self.assertRaises(ValueError, ast.literal_eval, '-6j+3') + self.assertRaises(ValueError, ast.literal_eval, '-6j+3j') + self.assertRaises(ValueError, ast.literal_eval, '3+-6j') + self.assertRaises(ValueError, ast.literal_eval, '3+(0+6j)') + self.assertRaises(ValueError, ast.literal_eval, '-(3+6j)') def test_literal_eval_malformed_dict_nodes(self): malformed = ast.Dict(keys=[ast.Constant(1), ast.Constant(2)], values=[ast.Constant(3)]) @@ -1506,7 +1507,7 @@ def test_literal_eval_trailing_ws(self): def test_literal_eval_malformed_lineno(self): msg = r'malformed node or string on line 3:' with self.assertRaisesRegex(ValueError, msg): - ast.literal_eval("{'a': 1,\n'b':2,\n'c':++x,\n'd':4}") + ast.literal_eval("{'a': 1,\n'b':2,\n'c':++3,\n'd':4}") node = ast.UnaryOp( ast.UAdd(), ast.UnaryOp(ast.UAdd(), ast.Constant(6))) @@ -2277,7 +2278,7 @@ def test_load_const(self): consts) def test_literal_eval(self): - tree = ast.parse("x + 2") + tree = ast.parse("1 + 2") binop = tree.body[0].value new_left = ast.Constant(value=10) @@ -2491,14 +2492,14 @@ def test_slices(self): def test_binop(self): s = dedent(''' - (1 * x + (3 ) + + (1 * 2 + (3 ) + 4 ) ''').strip() binop = self._parse_value(s) self._check_end_pos(binop, 2, 6) self._check_content(s, binop.right, '4') - self._check_content(s, binop.left, '1 * x + (3 )') + self._check_content(s, binop.left, '1 * 2 + (3 )') self._check_content(s, binop.left.right, '3') def test_boolop(self): @@ -3051,7 +3052,7 @@ def main(): ('Module', [('FunctionDef', (1, 0, 1, 38), 'f', ('arguments', [], [], None, [], [], None, []), [('Pass', (1, 34, 1, 38))], [], None, None, [('TypeVar', (1, 6, 1, 19), 'T', ('Tuple', (1, 9, 1, 19), [('Name', (1, 10, 1, 13), 'int', ('Load',)), ('Name', (1, 15, 1, 18), 'str', ('Load',))], ('Load',))), ('TypeVarTuple', (1, 21, 1, 24), 'Ts'), ('ParamSpec', (1, 26, 1, 29), 'P')])], []), ] single_results = [ -('Interactive', [('Expr', (1, 0, 1, 3), ('Constant', (1, 0, 1, 3), 3, None))]), +('Interactive', [('Expr', (1, 0, 1, 3), ('BinOp', (1, 0, 1, 3), ('Constant', (1, 0, 1, 1), 1, None), ('Add',), ('Constant', (1, 2, 1, 3), 2, None)))]), ] eval_results = [ ('Expression', ('Constant', (1, 0, 1, 4), None, None)), @@ -3085,9 +3086,9 @@ def main(): ('Expression', ('Name', (1, 0, 1, 1), 'v', ('Load',))), ('Expression', ('List', (1, 0, 1, 7), [('Constant', (1, 1, 1, 2), 1, None), ('Constant', (1, 3, 1, 4), 2, None), ('Constant', (1, 5, 1, 6), 3, None)], ('Load',))), ('Expression', ('List', (1, 0, 1, 2), [], ('Load',))), -('Expression', ('Constant', (1, 0, 1, 5), (1, 2, 3), None)), -('Expression', ('Tuple', (1, 0, 1, 7), [('Constant', (1, 1, 1, 2), 1, None), ('Name', (1, 3, 1, 4), 'x', ('Load',)), ('Constant', (1, 5, 1, 6), 3, None)], ('Load',))), -('Expression', ('Constant', (1, 0, 1, 2), (), None)), +('Expression', ('Tuple', (1, 0, 1, 5), [('Constant', (1, 0, 1, 1), 1, None), ('Constant', (1, 2, 1, 3), 2, None), ('Constant', (1, 4, 1, 5), 3, None)], ('Load',))), +('Expression', ('Tuple', (1, 0, 1, 7), [('Constant', (1, 1, 1, 2), 1, None), ('Constant', (1, 3, 1, 4), 2, None), ('Constant', (1, 5, 1, 6), 3, None)], ('Load',))), +('Expression', ('Tuple', (1, 0, 1, 2), [], ('Load',))), ('Expression', ('Call', (1, 0, 1, 17), ('Attribute', (1, 0, 1, 7), ('Attribute', (1, 0, 1, 5), ('Attribute', (1, 0, 1, 3), ('Name', (1, 0, 1, 1), 'a', ('Load',)), 'b', ('Load',)), 'c', ('Load',)), 'd', ('Load',)), [('Subscript', (1, 8, 1, 16), ('Attribute', (1, 8, 1, 11), ('Name', (1, 8, 1, 9), 'a', ('Load',)), 'b', ('Load',)), ('Slice', (1, 12, 1, 15), ('Constant', (1, 12, 1, 13), 1, None), ('Constant', (1, 14, 1, 15), 2, None), None), ('Load',))], [])), ] main() diff --git a/Python/Python-ast.c b/Python/Python-ast.c index 8047b1259c5d86..60dd121d60b8d0 100644 --- a/Python/Python-ast.c +++ b/Python/Python-ast.c @@ -12659,6 +12659,9 @@ astmodule_exec(PyObject *m) if (PyModule_AddIntMacro(m, PyCF_TYPE_COMMENTS) < 0) { return -1; } + if (PyModule_AddIntMacro(m, PyCF_OPTIMIZED_AST) < 0) { + return -1; + } if (PyModule_AddObjectRef(m, "mod", state->mod_type) < 0) { return -1; } diff --git a/Python/pythonrun.c b/Python/pythonrun.c index a3ab0da2882bf1..b2e04cfa317c00 100644 --- a/Python/pythonrun.c +++ b/Python/pythonrun.c @@ -1792,8 +1792,8 @@ run_pyc_file(FILE *fp, PyObject *globals, PyObject *locals, } static int -call_ast_optimize(mod_ty mod, PyObject *filename, PyCompilerFlags *cf, - int optimize, PyArena *arena) +ast_optimize(mod_ty mod, PyObject *filename, PyCompilerFlags *cf, + int optimize, PyArena *arena) { PyFutureFeatures future; if (!_PyFuture_FromAST(mod, filename, &future)) { @@ -1825,9 +1825,11 @@ Py_CompileStringObject(const char *str, PyObject *filename, int start, return NULL; } if (flags && (flags->cf_flags & PyCF_ONLY_AST)) { - if (call_ast_optimize(mod, filename, flags, optimize, arena) < 0) { - _PyArena_Free(arena); - return NULL; + if ((flags->cf_flags & PyCF_OPTIMIZED_AST) == PyCF_OPTIMIZED_AST) { + if (ast_optimize(mod, filename, flags, optimize, arena) < 0) { + _PyArena_Free(arena); + return NULL; + } } PyObject *result = PyAST_mod2obj(mod); _PyArena_Free(arena); From 1273e4c06b296109b5f1511515b573da1342ea57 Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Sat, 19 Aug 2023 18:45:09 +0100 Subject: [PATCH 3/9] update docs --- Doc/library/ast.rst | 11 ++++------- Doc/whatsnew/3.13.rst | 14 +++++++------- Lib/ast.py | 2 +- .../2023-08-18-18-21-27.gh-issue-108113.1h0poE.rst | 14 ++++++++------ 4 files changed, 20 insertions(+), 21 deletions(-) diff --git a/Doc/library/ast.rst b/Doc/library/ast.rst index 836ec18f2c0980..2237a07eb9d8aa 100644 --- a/Doc/library/ast.rst +++ b/Doc/library/ast.rst @@ -2125,7 +2125,9 @@ and classes for traversing abstract syntax trees: .. function:: parse(source, filename='', mode='exec', *, type_comments=False, feature_version=None, optimize=-1) Parse the source into an AST node. Equivalent to ``compile(source, - filename, mode, flags=ast.PyCF_ONLY_AST, optimize=optimize)``. + filename, mode, flags=FLAGS_VALUE, optimize=optimize)``, + where ``FLAGS_VALUE`` is ``ast.PyCF_ONLY_AST`` if ``optimize <= 0`` + and ``ast.PyCF_OPTIMIZED_AST`` otherwise. If ``type_comments=True`` is given, the parser is modified to check and return type comments as specified by :pep:`484` and :pep:`526`. @@ -2171,10 +2173,7 @@ and classes for traversing abstract syntax trees: .. versionchanged:: 3.13 The minimum supported version for feature_version is now (3,7) - - The output AST is now optimized with constant folding. - The ``optimize`` argument was added to control additional - optimizations. + The ``optimize`` argument was added. .. function:: unparse(ast_obj) @@ -2233,8 +2232,6 @@ and classes for traversing abstract syntax trees: .. versionchanged:: 3.10 For string inputs, leading spaces and tabs are now stripped. - .. versionchanged:: 3.13 - This function now understands and collapses const expressions. .. function:: get_docstring(node, clean=True) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index b1ed6f02390c23..9e44aa2cb5960d 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -85,8 +85,10 @@ Other Language Changes This change will affect tools using docstrings, like :mod:`doctest`. (Contributed by Inada Naoki in :gh:`81283`.) -* The :func:`compile` built-in no longer ignores the ``optimize`` argument - when called with the ``ast.PyCF_ONLY_AST`` flag. +* The :func:`compile` built-in can now accept a new flag, + ``ast.PyCF_OPTIMIZED_AST``, which is similar to ``ast.PyCF_ONLY_AST`` + except that the returned ``AST`` is optimized according to the value + of the ``optimize`` argument. (Contributed by Irit Katriel in :gh:`108113`). New Modules @@ -101,11 +103,9 @@ Improved Modules ast --- -* :func:`ast.parse` and :func:`ast.literal_eval` now perform constant folding - and other AST optimizations. This means that AST are more concise, and - :func:`ast.literal_eval` understands and collapses const expressions. - :func:`ast.parse` also accepts a new optional argument ``optimize``, which - it forwards to the :func:`compile` built-in. +* :func:`ast.parse` now accepts an optional argument ``optimize`` + which is passed on to the :func:`compile` built-in. This makes it + possible to obtain an optimized ``AST``. (Contributed by Irit Katriel in :gh:`108113`). array diff --git a/Lib/ast.py b/Lib/ast.py index c336bfc884e05d..45b95963f81885 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -65,7 +65,7 @@ def literal_eval(node_or_string): Caution: A complex expression can overflow the C stack and cause a crash. """ if isinstance(node_or_string, str): - node_or_string = parse(node_or_string.lstrip(" \t"), mode='eval', optimize=0) + node_or_string = parse(node_or_string.lstrip(" \t"), mode='eval') if isinstance(node_or_string, Expression): node_or_string = node_or_string.body def _raise_malformed_node(node): diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-08-18-18-21-27.gh-issue-108113.1h0poE.rst b/Misc/NEWS.d/next/Core and Builtins/2023-08-18-18-21-27.gh-issue-108113.1h0poE.rst index edf033d77dce11..66680578c9b43b 100644 --- a/Misc/NEWS.d/next/Core and Builtins/2023-08-18-18-21-27.gh-issue-108113.1h0poE.rst +++ b/Misc/NEWS.d/next/Core and Builtins/2023-08-18-18-21-27.gh-issue-108113.1h0poE.rst @@ -1,6 +1,8 @@ -The :func:`compile` built-in no longer ignores the ``optimize`` argument -when called with the ``ast.PyCF_ONLY_AST`` flag. The :func:`ast.parse` -function now accepts an optional argument ``optimize``, which it forwards to -:func:`compile`. :func:`ast.parse` and :func:`ast.literal_eval` perform -const folding, so ASTs are more concise and :func:`ast.literal_eval` -accepts const expressions. +The :func:`compile` built-in can now accept a new flag, +``ast.PyCF_OPTIMIZED_AST``, which is similar to ``ast.PyCF_ONLY_AST`` +except that the returned ``AST`` is optimized according to the value +of the ``optimize`` argument. + +:func:`ast.parse` now accepts an optional argument ``optimize`` +which is passed on to the :func:`compile` built-in. This makes it +possible to obtain an optimized ``AST``. From ece282e5ee6aeb34a04a6c18514e626fd86828b9 Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Sat, 19 Aug 2023 18:52:38 +0100 Subject: [PATCH 4/9] revert unnecessary change --- Lib/test/test_builtin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index fb73fe59c71fb1..3850d89efc0f9b 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -369,10 +369,11 @@ def f(): """doc""" (1, False, 'doc', False, False), (2, False, None, False, False)] for optval, *expected in values: + with self.subTest(opt = optval): # test both direct compilation and compilation via AST codeobjs = [] codeobjs.append(compile(codestr, "", "exec", optimize=optval)) - tree = ast.parse(codestr, optimize=optval) + tree = ast.parse(codestr) codeobjs.append(compile(tree, "", "exec", optimize=optval)) for code in codeobjs: ns = {} From 69d969dd411ad7369e3e3d612ac2d5f0350aed00 Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Sat, 19 Aug 2023 22:50:36 +0100 Subject: [PATCH 5/9] add tests --- Lib/test/test_ast.py | 17 ++++++++++++++++- Lib/test/test_builtin.py | 22 ++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index d570d9b687b2d2..f3c7229f0b6c76 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -357,7 +357,7 @@ def test_ast_validation(self): tree = ast.parse(snippet) compile(tree, '', 'exec') - def test_optimization_levels(self): + def test_optimization_levels__debug__(self): cases = [(-1, '__debug__'), (0, '__debug__'), (1, False), (2, False)] for (optval, expected) in cases: with self.subTest(optval=optval, expected=expected): @@ -370,6 +370,21 @@ def test_optimization_levels(self): self.assertIsInstance(res.body[0].value, ast.Name) self.assertEqual(res.body[0].value.id, expected) + def test_optimization_levels_const_folding(self): + folded = ('Expr', (1, 0, 1, 5), ('Constant', (1, 0, 1, 5), 3, None)) + not_folded = ('Expr', (1, 0, 1, 5), + ('BinOp', (1, 0, 1, 5), + ('Constant', (1, 0, 1, 1), 1, None), + ('Add',), + ('Constant', (1, 4, 1, 5), 2, None))) + + cases = [(-1, not_folded), (0, not_folded), (1, folded), (2, folded)] + for (optval, expected) in cases: + with self.subTest(optval=optval): + tree = ast.parse("1 + 2", optimize=optval) + res = to_tuple(tree.body[0]) + self.assertEqual(res, expected) + def test_invalid_position_information(self): invalid_linenos = [ (10, 1), (-10, -11), (10, -11), (-5, -2), (-5, 1) diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index 3850d89efc0f9b..0ede6f60527664 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -518,6 +518,28 @@ def test_compile_async_generator(self): exec(co, glob) self.assertEqual(type(glob['ticker']()), AsyncGeneratorType) + def test_compile_ast(self): + args = ("a*(1+2)", "f.py", "exec") + raw = compile(*args, flags = ast.PyCF_ONLY_AST).body[0] + opt = compile(*args, flags = ast.PyCF_OPTIMIZED_AST).body[0] + + for tree in (raw, opt): + self.assertIsInstance(tree.value, ast.BinOp) + self.assertIsInstance(tree.value.op, ast.Mult) + self.assertIsInstance(tree.value.left, ast.Name) + self.assertEqual(tree.value.left.id, 'a') + + raw_right = raw.value.right # expect BinOp(1, '+', 2) + self.assertIsInstance(raw_right, ast.BinOp) + self.assertIsInstance(raw_right.left, ast.Constant) + self.assertEqual(raw_right.left.value, 1) + self.assertIsInstance(raw_right.right, ast.Constant) + self.assertEqual(raw_right.right.value, 2) + + opt_right = opt.value.right # expect Constant(3) + self.assertIsInstance(opt_right, ast.Constant) + self.assertEqual(opt_right.value, 3) + def test_delattr(self): sys.spam = 1 delattr(sys, 'spam') From 385052140efda4780bf7d481855bf0f0055144fc Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Sat, 19 Aug 2023 23:38:12 +0100 Subject: [PATCH 6/9] update Parser/asdl_c.py --- Parser/asdl_c.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Parser/asdl_c.py b/Parser/asdl_c.py index 2a36610527f898..1733cd4b15aa4c 100755 --- a/Parser/asdl_c.py +++ b/Parser/asdl_c.py @@ -1208,6 +1208,9 @@ def visitModule(self, mod): self.emit('if (PyModule_AddIntMacro(m, PyCF_TYPE_COMMENTS) < 0) {', 1) self.emit("return -1;", 2) self.emit('}', 1) + self.emit('if (PyModule_AddIntMacro(m, PyCF_OPTIMIZED_AST) < 0) {', 1) + self.emit("return -1;", 2) + self.emit('}', 1) for dfn in mod.dfns: self.visit(dfn) self.emit("return 0;", 1) From b6d49a850ef6a0bb5acb72e25999fc8b2d2a7a1c Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Mon, 21 Aug 2023 10:06:25 +0100 Subject: [PATCH 7/9] make patchcheck --- Lib/test/test_builtin.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index 0ede6f60527664..ee3ba6ab07bbdf 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -369,17 +369,17 @@ def f(): """doc""" (1, False, 'doc', False, False), (2, False, None, False, False)] for optval, *expected in values: - with self.subTest(opt = optval): + with self.subTest(optval=optval): # test both direct compilation and compilation via AST - codeobjs = [] - codeobjs.append(compile(codestr, "", "exec", optimize=optval)) - tree = ast.parse(codestr) - codeobjs.append(compile(tree, "", "exec", optimize=optval)) - for code in codeobjs: - ns = {} - exec(code, ns) - rv = ns['f']() - self.assertEqual(rv, tuple(expected)) + codeobjs = [] + codeobjs.append(compile(codestr, "", "exec", optimize=optval)) + tree = ast.parse(codestr) + codeobjs.append(compile(tree, "", "exec", optimize=optval)) + for code in codeobjs: + ns = {} + exec(code, ns) + rv = ns['f']() + self.assertEqual(rv, tuple(expected)) def test_compile_top_level_await_no_coro(self): """Make sure top level non-await codes get the correct coroutine flags""" From 1070d01264f242bf1e5f521a33c476699e2639db Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Mon, 21 Aug 2023 10:09:55 +0100 Subject: [PATCH 8/9] Update Doc/whatsnew/3.13.rst Co-authored-by: Dong-hee Na --- Doc/whatsnew/3.13.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index bfab868d1c5b62..496901c8d8be44 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -86,7 +86,7 @@ Other Language Changes (Contributed by Inada Naoki in :gh:`81283`.) * The :func:`compile` built-in can now accept a new flag, - ``ast.PyCF_OPTIMIZED_AST``, which is similar to ``ast.PyCF_ONLY_AST`` + :data:`ast.PyCF_OPTIMIZED_AST`, which is similar to :data:`ast.PyCF_ONLY_AST` except that the returned ``AST`` is optimized according to the value of the ``optimize`` argument. (Contributed by Irit Katriel in :gh:`108113`). From d0eba599efcbb208dc95ed57a7068be7c2b7e0b1 Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Mon, 21 Aug 2023 16:48:17 +0100 Subject: [PATCH 9/9] Revert "Update Doc/whatsnew/3.13.rst" This reverts commit 1070d01264f242bf1e5f521a33c476699e2639db. --- Doc/whatsnew/3.13.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 496901c8d8be44..bfab868d1c5b62 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -86,7 +86,7 @@ Other Language Changes (Contributed by Inada Naoki in :gh:`81283`.) * The :func:`compile` built-in can now accept a new flag, - :data:`ast.PyCF_OPTIMIZED_AST`, which is similar to :data:`ast.PyCF_ONLY_AST` + ``ast.PyCF_OPTIMIZED_AST``, which is similar to ``ast.PyCF_ONLY_AST`` except that the returned ``AST`` is optimized according to the value of the ``optimize`` argument. (Contributed by Irit Katriel in :gh:`108113`).