gh-116021: Deprecate support for instantiating abstract AST nodes (#1… · python/cpython@bdedc4a · GitHub
Skip to content

Commit bdedc4a

Browse files
brianschubertpicnixzJelleZijlstra
authored
gh-116021: Deprecate support for instantiating abstract AST nodes (#137865)
Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
1 parent b1d6231 commit bdedc4a

9 files changed

Lines changed: 173 additions & 14 deletions

File tree

Doc/deprecations/pending-removal-in-3.20.rst

Lines changed: 5 additions & 0 deletions

Doc/library/ast.rst

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ Node classes
4242

4343
.. class:: AST
4444

45-
This is the base of all AST node classes. The actual node classes are
45+
This is the abstract base of all AST node classes. The actual node classes are
4646
derived from the :file:`Parser/Python.asdl` file, which is reproduced
4747
:ref:`above <abstract-grammar>`. They are defined in the :mod:`!_ast` C
4848
module and re-exported in :mod:`!ast`.
@@ -168,6 +168,15 @@ Node classes
168168
arguments that were set as attributes of the AST node, even if they did not
169169
match any of the fields of the AST node. These cases now raise a :exc:`TypeError`.
170170

171+
.. deprecated-removed:: next 3.20
172+
173+
In the :ref:`grammar above <abstract-grammar>`, the AST node classes that
174+
correspond to production rules with variants (aka "sums") are abstract
175+
classes. Previous versions of Python allowed for the creation of direct
176+
instances of these abstract node classes. This behavior is deprecated and
177+
will be removed in Python 3.20.
178+
179+
171180
.. note::
172181
The descriptions of the specific node classes displayed here
173182
were initially adapted from the fantastic `Green Tree

Doc/whatsnew/3.15.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1846,6 +1846,13 @@ Deprecated
18461846
New deprecations
18471847
----------------
18481848

1849+
* :mod:`ast`
1850+
1851+
* Creating instances of abstract AST nodes (such as :class:`ast.AST`
1852+
or :class:`!ast.expr`) is deprecated and will raise an error in Python 3.20.
1853+
1854+
(Contributed by Brian Schubert in :gh:`116021`.)
1855+
18491856
* :mod:`base64`:
18501857

18511858
* Accepting the ``+`` and ``/`` characters with an alternative alphabet in

Include/internal/pycore_ast_state.h

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/test/test_ast/test_ast.py

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import _ast_unparse
2+
import _ast
23
import ast
34
import builtins
45
import contextlib
@@ -85,7 +86,9 @@ def _assertTrueorder(self, ast_node, parent_pos):
8586
self.assertEqual(ast_node._fields, ast_node.__match_args__)
8687

8788
def test_AST_objects(self):
88-
x = ast.AST()
89+
# Directly instantiating abstract node class AST is allowed (but deprecated)
90+
with self.assertWarns(DeprecationWarning):
91+
x = ast.AST()
8992
self.assertEqual(x._fields, ())
9093
x.foobar = 42
9194
self.assertEqual(x.foobar, 42)
@@ -94,7 +97,7 @@ def test_AST_objects(self):
9497
with self.assertRaises(AttributeError):
9598
x.vararg
9699

97-
with self.assertRaises(TypeError):
100+
with self.assertRaises(TypeError), self.assertWarns(DeprecationWarning):
98101
# "ast.AST constructor takes 0 positional arguments"
99102
ast.AST(2)
100103

@@ -110,15 +113,21 @@ def cleanup():
110113

111114
msg = "type object 'ast.AST' has no attribute '_fields'"
112115
# Both examples used to crash:
113-
with self.assertRaisesRegex(AttributeError, msg):
116+
with (
117+
self.assertRaisesRegex(AttributeError, msg),
118+
self.assertWarns(DeprecationWarning),
119+
):
114120
ast.AST(arg1=123)
115-
with self.assertRaisesRegex(AttributeError, msg):
121+
with (
122+
self.assertRaisesRegex(AttributeError, msg),
123+
self.assertWarns(DeprecationWarning),
124+
):
116125
ast.AST()
117126

118-
def test_AST_garbage_collection(self):
127+
def test_node_garbage_collection(self):
119128
class X:
120129
pass
121-
a = ast.AST()
130+
a = ast.Module()
122131
a.x = X()
123132
a.x.a = a
124133
ref = weakref.ref(a.x)
@@ -439,7 +448,15 @@ def _construct_ast_class(self, cls):
439448
elif typ is object:
440449
kwargs[name] = b'capybara'
441450
elif isinstance(typ, type) and issubclass(typ, ast.AST):
442-
kwargs[name] = self._construct_ast_class(typ)
451+
if _ast._is_abstract(typ):
452+
# Use an arbitrary concrete subclass
453+
concrete = next((sub for sub in typ.__subclasses__()
454+
if not _ast._is_abstract(sub)), None)
455+
msg = f"abstract node class {typ} has no concrete subclasses"
456+
self.assertIsNotNone(concrete, msg)
457+
else:
458+
concrete = typ
459+
kwargs[name] = self._construct_ast_class(concrete)
443460
return cls(**kwargs)
444461

445462
def test_arguments(self):
@@ -578,14 +595,21 @@ def test_nodeclasses(self):
578595
with self.assertRaisesRegex(TypeError, re.escape(msg)):
579596
ast.BinOp(1, 2, 3, foobarbaz=42)
580597

598+
# Directly instantiating abstract node types is allowed (but deprecated)
599+
self.assertWarns(DeprecationWarning, ast.stmt)
600+
self.assertWarns(DeprecationWarning, ast.expr_context)
601+
581602
def test_no_fields(self):
582603
# this used to fail because Sub._fields was None
583604
x = ast.Sub()
584605
self.assertEqual(x._fields, ())
585606

586607
def test_invalid_sum(self):
587608
pos = dict(lineno=2, col_offset=3)
588-
m = ast.Module([ast.Expr(ast.expr(**pos), **pos)], [])
609+
with self.assertWarns(DeprecationWarning):
610+
# Creating instances of ast.expr is deprecated
611+
e = ast.expr(**pos)
612+
m = ast.Module([ast.Expr(e, **pos)], [])
589613
with self.assertRaises(TypeError) as cm:
590614
compile(m, "<test>", "exec")
591615
self.assertIn("but got expr()", str(cm.exception))
@@ -1107,14 +1131,19 @@ class CopyTests(unittest.TestCase):
11071131
def iter_ast_classes():
11081132
"""Iterate over the (native) subclasses of ast.AST recursively.
11091133
1110-
This excludes the special class ast.Index since its constructor
1111-
returns an integer.
1134+
This excludes:
1135+
* abstract AST nodes
1136+
* the special class ast.Index, since its constructor returns
1137+
an integer.
11121138
"""
11131139
def do(cls):
11141140
if cls.__module__ != 'ast':
11151141
return
11161142
if cls is ast.Index:
11171143
return
1144+
# Don't attempt to create instances of abstract AST nodes
1145+
if _ast._is_abstract(cls):
1146+
return
11181147

11191148
yield cls
11201149
for sub in cls.__subclasses__():

Lib/test/test_sys.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1908,7 +1908,7 @@ def test_pythontypes(self):
19081908
check = self.check_sizeof
19091909
# _ast.AST
19101910
import _ast
1911-
check(_ast.AST(), size('P'))
1911+
check(_ast.Module(), size('3P'))
19121912
try:
19131913
raise TypeError
19141914
except TypeError as e:
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Support for creating instances of abstract AST nodes from the :mod:`ast` module
2+
is deprecated and scheduled for removal in Python 3.20. Patch by Brian Schubert.

Parser/asdl_c.py

Lines changed: 48 additions & 1 deletion

0 commit comments

Comments
 (0)