[Python-checkins] [3.12] gh-104799: PEP 695 backward compatibility for ast.unparse (GH-105846) (#105862)

JelleZijlstra webhook-mailer at python.org
Fri Jun 16 12:59:29 EDT 2023


https://github.com/python/cpython/commit/5ca707d1e41af165d6bbc6bbc8026256a0a941d3
commit: 5ca707d1e41af165d6bbc6bbc8026256a0a941d3
branch: 3.12
author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com>
committer: JelleZijlstra <jelle.zijlstra at gmail.com>
date: 2023-06-16T16:59:25Z
summary:

[3.12] gh-104799: PEP 695 backward compatibility for ast.unparse (GH-105846) (#105862)

(cherry picked from commit 957a974d4fc1575787e4a29a399a47520d6df6d3)

Co-authored-by: Jelle Zijlstra <jelle.zijlstra at gmail.com>

files:
A Misc/NEWS.d/next/Library/2023-06-15-18-11-47.gh-issue-104799.BcLzbP.rst
M Lib/ast.py
M Lib/test/test_unparse.py

diff --git a/Lib/ast.py b/Lib/ast.py
index 226910ecac0b..a307f3ecd061 100644
--- a/Lib/ast.py
+++ b/Lib/ast.py
@@ -1051,7 +1051,8 @@ def visit_ClassDef(self, node):
             self.fill("@")
             self.traverse(deco)
         self.fill("class " + node.name)
-        self._type_params_helper(node.type_params)
+        if hasattr(node, "type_params"):
+            self._type_params_helper(node.type_params)
         with self.delimit_if("(", ")", condition = node.bases or node.keywords):
             comma = False
             for e in node.bases:
@@ -1083,7 +1084,8 @@ def _function_helper(self, node, fill_suffix):
             self.traverse(deco)
         def_str = fill_suffix + " " + node.name
         self.fill(def_str)
-        self._type_params_helper(node.type_params)
+        if hasattr(node, "type_params"):
+            self._type_params_helper(node.type_params)
         with self.delimit("(", ")"):
             self.traverse(node.args)
         if node.returns:
diff --git a/Lib/test/test_unparse.py b/Lib/test/test_unparse.py
index 88c7c3a0af87..41a6318d1499 100644
--- a/Lib/test/test_unparse.py
+++ b/Lib/test/test_unparse.py
@@ -1,4 +1,4 @@
-"""Tests for the unparse.py script in the Tools/parser directory."""
+"""Tests for ast.unparse."""
 
 import unittest
 import test.support
@@ -625,6 +625,78 @@ def test_star_expr_assign_target_multiple(self):
         self.check_src_roundtrip("a, b = [c, d] = e, f = g")
 
 
+class ManualASTCreationTestCase(unittest.TestCase):
+    """Test that AST nodes created without a type_params field unparse correctly."""
+
+    def test_class(self):
+        node = ast.ClassDef(name="X", bases=[], keywords=[], body=[ast.Pass()], decorator_list=[])
+        ast.fix_missing_locations(node)
+        self.assertEqual(ast.unparse(node), "class X:\n    pass")
+
+    def test_class_with_type_params(self):
+        node = ast.ClassDef(name="X", bases=[], keywords=[], body=[ast.Pass()], decorator_list=[],
+                             type_params=[ast.TypeVar("T")])
+        ast.fix_missing_locations(node)
+        self.assertEqual(ast.unparse(node), "class X[T]:\n    pass")
+
+    def test_function(self):
+        node = ast.FunctionDef(
+            name="f",
+            args=ast.arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]),
+            body=[ast.Pass()],
+            decorator_list=[],
+            returns=None,
+        )
+        ast.fix_missing_locations(node)
+        self.assertEqual(ast.unparse(node), "def f():\n    pass")
+
+    def test_function_with_type_params(self):
+        node = ast.FunctionDef(
+            name="f",
+            args=ast.arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]),
+            body=[ast.Pass()],
+            decorator_list=[],
+            returns=None,
+            type_params=[ast.TypeVar("T")],
+        )
+        ast.fix_missing_locations(node)
+        self.assertEqual(ast.unparse(node), "def f[T]():\n    pass")
+
+    def test_function_with_type_params_and_bound(self):
+        node = ast.FunctionDef(
+            name="f",
+            args=ast.arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]),
+            body=[ast.Pass()],
+            decorator_list=[],
+            returns=None,
+            type_params=[ast.TypeVar("T", bound=ast.Name("int"))],
+        )
+        ast.fix_missing_locations(node)
+        self.assertEqual(ast.unparse(node), "def f[T: int]():\n    pass")
+
+    def test_async_function(self):
+        node = ast.AsyncFunctionDef(
+            name="f",
+            args=ast.arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]),
+            body=[ast.Pass()],
+            decorator_list=[],
+            returns=None,
+        )
+        ast.fix_missing_locations(node)
+        self.assertEqual(ast.unparse(node), "async def f():\n    pass")
+
+    def test_async_function_with_type_params(self):
+        node = ast.AsyncFunctionDef(
+            name="f",
+            args=ast.arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]),
+            body=[ast.Pass()],
+            decorator_list=[],
+            returns=None,
+            type_params=[ast.TypeVar("T")],
+        )
+        ast.fix_missing_locations(node)
+        self.assertEqual(ast.unparse(node), "async def f[T]():\n    pass")
+
 
 class DirectoryTestCase(ASTTestCase):
     """Test roundtrip behaviour on all files in Lib and Lib/test."""
diff --git a/Misc/NEWS.d/next/Library/2023-06-15-18-11-47.gh-issue-104799.BcLzbP.rst b/Misc/NEWS.d/next/Library/2023-06-15-18-11-47.gh-issue-104799.BcLzbP.rst
new file mode 100644
index 000000000000..d0dbff4f1553
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2023-06-15-18-11-47.gh-issue-104799.BcLzbP.rst
@@ -0,0 +1,3 @@
+Enable :func:`ast.unparse` to unparse function and class definitions created
+without the new ``type_params`` field from :pep:`695`. Patch by Jelle
+Zijlstra.



More information about the Python-checkins mailing list