https://github.com/python/cpython/commit/2b4e2b78303197dea9d3ffecfc0423fa09edf71f
commit: 2b4e2b78303197dea9d3ffecfc0423fa09edf71f
branch: main
author: Semyon Moroz <[email protected]>
committer: sobolevn <[email protected]>
date: 2025-05-05T17:17:43Z
summary:

gh-133367: Add missing options to `ast` CLI (#133369)

Co-authored-by: Stan Ulbrych <[email protected]>
Co-authored-by: sobolevn <[email protected]>

files:
A Misc/NEWS.d/next/Library/2025-05-04-13-40-05.gh-issue-133367.E5nl2u.rst
M Doc/library/ast.rst
M Doc/whatsnew/3.14.rst
M Lib/ast.py
M Lib/test/test_ast/test_ast.py

diff --git a/Doc/library/ast.rst b/Doc/library/ast.rst
index 776c63d1f0fda0..c9ae0abdd663c7 100644
--- a/Doc/library/ast.rst
+++ b/Doc/library/ast.rst
@@ -1,4 +1,4 @@
-:mod:`!ast` --- Abstract Syntax Trees
+:mod:`!ast` --- Abstract syntax trees
 =====================================
 
 .. module:: ast
@@ -29,7 +29,7 @@ compiled into a Python code object using the built-in 
:func:`compile` function.
 
 .. _abstract-grammar:
 
-Abstract Grammar
+Abstract grammar
 ----------------
 
 The abstract grammar is currently defined as follows:
@@ -2156,10 +2156,10 @@ Async and await
    of :class:`ast.operator`, :class:`ast.unaryop`, :class:`ast.cmpop`,
    :class:`ast.boolop` and :class:`ast.expr_context`) on the returned tree
    will be singletons. Changes to one will be reflected in all other
-   occurrences of the same value (e.g. :class:`ast.Add`).
+   occurrences of the same value (for example, :class:`ast.Add`).
 
 
-:mod:`ast` Helpers
+:mod:`ast` helpers
 ------------------
 
 Apart from the node classes, the :mod:`ast` module defines these utility 
functions
@@ -2484,7 +2484,7 @@ and classes for traversing abstract syntax trees:
 
 .. _ast-compiler-flags:
 
-Compiler Flags
+Compiler flags
 --------------
 
 The following flags may be passed to :func:`compile` in order to change
@@ -2533,7 +2533,7 @@ effects on the compilation of a program:
 
 .. _ast-cli:
 
-Command-Line Usage
+Command-line usage
 ------------------
 
 .. versionadded:: 3.9
@@ -2572,6 +2572,28 @@ The following options are accepted:
 
    Indentation of nodes in AST (number of spaces).
 
+.. option:: --feature-version <version>
+
+   Python version in the format 3.x (for example, 3.10). Defaults to the
+   current version of the interpreter.
+
+   .. versionadded:: next
+
+.. option:: -O <level>
+            --optimize <level>
+
+   Optimization level for parser. Defaults to no optimization.
+
+   .. versionadded:: next
+
+.. option:: --show-empty
+
+   Show empty lists and fields that are ``None``. Defaults to not showing empty
+   objects.
+
+   .. versionadded:: next
+
+
 If :file:`infile` is specified its contents are parsed to AST and dumped
 to stdout.  Otherwise, the content is read from stdin.
 
diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst
index 68ffedf4729a93..0c4bd8600858f6 100644
--- a/Doc/whatsnew/3.14.rst
+++ b/Doc/whatsnew/3.14.rst
@@ -875,6 +875,11 @@ ast
   that the root node type is appropriate.
   (Contributed by Irit Katriel in :gh:`130139`.)
 
+* Add new ``--feature-version``, ``--optimize``, ``--show-empty`` options to
+  command-line interface.
+  (Contributed by Semyon Moroz in :gh:`133367`.)
+
+
 bdb
 ---
 
diff --git a/Lib/ast.py b/Lib/ast.py
index af4fe8ff5a8e95..be6ed0805d63dd 100644
--- a/Lib/ast.py
+++ b/Lib/ast.py
@@ -643,6 +643,15 @@ def main(args=None):
                              'column offsets')
     parser.add_argument('-i', '--indent', type=int, default=3,
                         help='indentation of nodes (number of spaces)')
+    parser.add_argument('--feature-version',
+                        type=str, default=None, metavar='VERSION',
+                        help='Python version in the format 3.x '
+                             '(for example, 3.10)')
+    parser.add_argument('-O', '--optimize',
+                        type=int, default=-1, metavar='LEVEL',
+                        help='optimization level for parser (default -1)')
+    parser.add_argument('--show-empty', default=False, action='store_true',
+                        help='show empty lists and fields in dump output')
     args = parser.parse_args(args)
 
     if args.infile == '-':
@@ -652,8 +661,22 @@ def main(args=None):
         name = args.infile
         with open(args.infile, 'rb') as infile:
             source = infile.read()
-    tree = parse(source, name, args.mode, type_comments=args.no_type_comments)
-    print(dump(tree, include_attributes=args.include_attributes, 
indent=args.indent))
+
+    # Process feature_version
+    feature_version = None
+    if args.feature_version:
+        try:
+            major, minor = map(int, args.feature_version.split('.', 1))
+        except ValueError:
+            parser.error('Invalid format for --feature-version; '
+                         'expected format 3.x (for example, 3.10)')
+
+        feature_version = (major, minor)
+
+    tree = parse(source, name, args.mode, type_comments=args.no_type_comments,
+                 feature_version=feature_version, optimize=args.optimize)
+    print(dump(tree, include_attributes=args.include_attributes,
+               indent=args.indent, show_empty=args.show_empty))
 
 if __name__ == '__main__':
     main()
diff --git a/Lib/test/test_ast/test_ast.py b/Lib/test/test_ast/test_ast.py
index 6a9b7812ef6185..ae82395e9a005a 100644
--- a/Lib/test/test_ast/test_ast.py
+++ b/Lib/test/test_ast/test_ast.py
@@ -3272,6 +3272,9 @@ def test_invocation(self):
             ('--no-type-comments', '--no-type-comments'),
             ('-a', '--include-attributes'),
             ('-i=4', '--indent=4'),
+            ('--feature-version=3.13', '--feature-version=3.13'),
+            ('-O=-1', '--optimize=-1'),
+            ('--show-empty', '--show-empty'),
         )
         self.set_source('''
             print(1, 2, 3)
@@ -3389,7 +3392,7 @@ def test_include_attributes_flag(self):
                 self.check_output(source, expect, flag)
 
     def test_indent_flag(self):
-        # test 'python -m ast -i/--indent'
+        # test 'python -m ast -i/--indent 0'
         source = 'pass'
         expect = '''
             Module(
@@ -3400,6 +3403,96 @@ def test_indent_flag(self):
             with self.subTest(flag=flag):
                 self.check_output(source, expect, flag)
 
+    def test_feature_version_flag(self):
+        # test 'python -m ast --feature-version 3.9/3.10'
+        source = '''
+            match x:
+                case 1:
+                    pass
+        '''
+        expect = '''
+            Module(
+               body=[
+                  Match(
+                     subject=Name(id='x', ctx=Load()),
+                     cases=[
+                        match_case(
+                           pattern=MatchValue(
+                              value=Constant(value=1)),
+                           body=[
+                              Pass()])])])
+        '''
+        self.check_output(source, expect, '--feature-version=3.10')
+        with self.assertRaises(SyntaxError):
+            self.invoke_ast('--feature-version=3.9')
+
+    def test_no_optimize_flag(self):
+        # test 'python -m ast -O/--optimize -1/0'
+        source = '''
+            match a:
+                case 1+2j:
+                    pass
+        '''
+        expect = '''
+            Module(
+               body=[
+                  Match(
+                     subject=Name(id='a', ctx=Load()),
+                     cases=[
+                        match_case(
+                           pattern=MatchValue(
+                              value=BinOp(
+                                 left=Constant(value=1),
+                                 op=Add(),
+                                 right=Constant(value=2j))),
+                           body=[
+                              Pass()])])])
+        '''
+        for flag in ('-O=-1', '--optimize=-1', '-O=0', '--optimize=0'):
+            with self.subTest(flag=flag):
+                self.check_output(source, expect, flag)
+
+    def test_optimize_flag(self):
+        # test 'python -m ast -O/--optimize 1/2'
+        source = '''
+            match a:
+                case 1+2j:
+                    pass
+        '''
+        expect = '''
+            Module(
+               body=[
+                  Match(
+                     subject=Name(id='a', ctx=Load()),
+                     cases=[
+                        match_case(
+                           pattern=MatchValue(
+                              value=Constant(value=(1+2j))),
+                           body=[
+                              Pass()])])])
+        '''
+        for flag in ('-O=1', '--optimize=1', '-O=2', '--optimize=2'):
+            with self.subTest(flag=flag):
+                self.check_output(source, expect, flag)
+
+    def test_show_empty_flag(self):
+        # test 'python -m ast --show-empty'
+        source = 'print(1, 2, 3)'
+        expect = '''
+            Module(
+               body=[
+                  Expr(
+                     value=Call(
+                        func=Name(id='print', ctx=Load()),
+                        args=[
+                           Constant(value=1),
+                           Constant(value=2),
+                           Constant(value=3)],
+                        keywords=[]))],
+               type_ignores=[])
+        '''
+        self.check_output(source, expect, '--show-empty')
+
 
 class ASTOptimiziationTests(unittest.TestCase):
     def wrap_expr(self, expr):
diff --git 
a/Misc/NEWS.d/next/Library/2025-05-04-13-40-05.gh-issue-133367.E5nl2u.rst 
b/Misc/NEWS.d/next/Library/2025-05-04-13-40-05.gh-issue-133367.E5nl2u.rst
new file mode 100644
index 00000000000000..1c1b0352180e70
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-04-13-40-05.gh-issue-133367.E5nl2u.rst
@@ -0,0 +1,2 @@
+Add the ``--feature-version``, ``--optimize``, and ``--show-empty`` options
+to the :mod:`ast` command-line interface. Patch by Semyon Moroz.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]

Reply via email to