Skip to content

Commit 5ca707d

Browse files
[3.12] gh-104799: PEP 695 backward compatibility for ast.unparse (GH-105846) (#105862)
(cherry picked from commit 957a974) Co-authored-by: Jelle Zijlstra <[email protected]>
1 parent e6982c5 commit 5ca707d

File tree

3 files changed

+80
-3
lines changed

3 files changed

+80
-3
lines changed

Lib/ast.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -1051,7 +1051,8 @@ def visit_ClassDef(self, node):
10511051
self.fill("@")
10521052
self.traverse(deco)
10531053
self.fill("class " + node.name)
1054-
self._type_params_helper(node.type_params)
1054+
if hasattr(node, "type_params"):
1055+
self._type_params_helper(node.type_params)
10551056
with self.delimit_if("(", ")", condition = node.bases or node.keywords):
10561057
comma = False
10571058
for e in node.bases:
@@ -1083,7 +1084,8 @@ def _function_helper(self, node, fill_suffix):
10831084
self.traverse(deco)
10841085
def_str = fill_suffix + " " + node.name
10851086
self.fill(def_str)
1086-
self._type_params_helper(node.type_params)
1087+
if hasattr(node, "type_params"):
1088+
self._type_params_helper(node.type_params)
10871089
with self.delimit("(", ")"):
10881090
self.traverse(node.args)
10891091
if node.returns:

Lib/test/test_unparse.py

+73-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Tests for the unparse.py script in the Tools/parser directory."""
1+
"""Tests for ast.unparse."""
22

33
import unittest
44
import test.support
@@ -625,6 +625,78 @@ def test_star_expr_assign_target_multiple(self):
625625
self.check_src_roundtrip("a, b = [c, d] = e, f = g")
626626

627627

628+
class ManualASTCreationTestCase(unittest.TestCase):
629+
"""Test that AST nodes created without a type_params field unparse correctly."""
630+
631+
def test_class(self):
632+
node = ast.ClassDef(name="X", bases=[], keywords=[], body=[ast.Pass()], decorator_list=[])
633+
ast.fix_missing_locations(node)
634+
self.assertEqual(ast.unparse(node), "class X:\n pass")
635+
636+
def test_class_with_type_params(self):
637+
node = ast.ClassDef(name="X", bases=[], keywords=[], body=[ast.Pass()], decorator_list=[],
638+
type_params=[ast.TypeVar("T")])
639+
ast.fix_missing_locations(node)
640+
self.assertEqual(ast.unparse(node), "class X[T]:\n pass")
641+
642+
def test_function(self):
643+
node = ast.FunctionDef(
644+
name="f",
645+
args=ast.arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]),
646+
body=[ast.Pass()],
647+
decorator_list=[],
648+
returns=None,
649+
)
650+
ast.fix_missing_locations(node)
651+
self.assertEqual(ast.unparse(node), "def f():\n pass")
652+
653+
def test_function_with_type_params(self):
654+
node = ast.FunctionDef(
655+
name="f",
656+
args=ast.arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]),
657+
body=[ast.Pass()],
658+
decorator_list=[],
659+
returns=None,
660+
type_params=[ast.TypeVar("T")],
661+
)
662+
ast.fix_missing_locations(node)
663+
self.assertEqual(ast.unparse(node), "def f[T]():\n pass")
664+
665+
def test_function_with_type_params_and_bound(self):
666+
node = ast.FunctionDef(
667+
name="f",
668+
args=ast.arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]),
669+
body=[ast.Pass()],
670+
decorator_list=[],
671+
returns=None,
672+
type_params=[ast.TypeVar("T", bound=ast.Name("int"))],
673+
)
674+
ast.fix_missing_locations(node)
675+
self.assertEqual(ast.unparse(node), "def f[T: int]():\n pass")
676+
677+
def test_async_function(self):
678+
node = ast.AsyncFunctionDef(
679+
name="f",
680+
args=ast.arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]),
681+
body=[ast.Pass()],
682+
decorator_list=[],
683+
returns=None,
684+
)
685+
ast.fix_missing_locations(node)
686+
self.assertEqual(ast.unparse(node), "async def f():\n pass")
687+
688+
def test_async_function_with_type_params(self):
689+
node = ast.AsyncFunctionDef(
690+
name="f",
691+
args=ast.arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]),
692+
body=[ast.Pass()],
693+
decorator_list=[],
694+
returns=None,
695+
type_params=[ast.TypeVar("T")],
696+
)
697+
ast.fix_missing_locations(node)
698+
self.assertEqual(ast.unparse(node), "async def f[T]():\n pass")
699+
628700

629701
class DirectoryTestCase(ASTTestCase):
630702
"""Test roundtrip behaviour on all files in Lib and Lib/test."""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Enable :func:`ast.unparse` to unparse function and class definitions created
2+
without the new ``type_params`` field from :pep:`695`. Patch by Jelle
3+
Zijlstra.

0 commit comments

Comments
 (0)