From 8de6eba6d7bcbe9bce0264edacae00675430a535 Mon Sep 17 00:00:00 2001
From: Serhiy Storchaka <storchaka@gmail.com>
Date: Sat, 19 Feb 2022 14:20:14 +0200
Subject: [PATCH 1/2] bpo-46797: Emit deprecation warnings for deprecated ast
 features

---
 Lib/ast.py                                    |  68 ++-
 Lib/test/test_ast.py                          | 467 +++++++++++++-----
 .../2022-02-19-14-19-34.bpo-46797.6BXZX4.rst  |   2 +
 3 files changed, 402 insertions(+), 135 deletions(-)
 create mode 100644 Misc/NEWS.d/next/Library/2022-02-19-14-19-34.bpo-46797.6BXZX4.rst

diff --git a/Lib/ast.py b/Lib/ast.py
index 625738ad681af4..070552576e44ad 100644
--- a/Lib/ast.py
+++ b/Lib/ast.py
@@ -284,9 +284,7 @@ def get_docstring(node, clean=True):
     if not(node.body and isinstance(node.body[0], Expr)):
         return None
     node = node.body[0].value
-    if isinstance(node, Str):
-        text = node.s
-    elif isinstance(node, Constant) and isinstance(node.value, str):
+    if isinstance(node, Constant) and isinstance(node.value, str):
         text = node.value
     else:
         return None
@@ -505,15 +503,34 @@ def generic_visit(self, node):
     # The following code is for backward compatibility.
     # It will be removed in future.
 
-    def _getter(self):
+    def _n_getter(self):
         """Deprecated. Use value instead."""
+        import warnings
+        warnings.warn("Attribute n is deprecated; use value instead",
+                      DeprecationWarning, stacklevel=2)
         return self.value
 
-    def _setter(self, value):
+    def _n_setter(self, value):
+        import warnings
+        warnings.warn("Attribute n is deprecated; use value instead",
+                      DeprecationWarning, stacklevel=2)
         self.value = value
 
-    Constant.n = property(_getter, _setter)
-    Constant.s = property(_getter, _setter)
+    def _s_getter(self):
+        """Deprecated. Use value instead."""
+        import warnings
+        warnings.warn("Attribute s is deprecated; use value instead",
+                      DeprecationWarning, stacklevel=2)
+        return self.value
+
+    def _s_setter(self, value):
+        import warnings
+        warnings.warn("Attribute s is deprecated; use value instead",
+                      DeprecationWarning, stacklevel=2)
+        self.value = value
+
+    Constant.n = property(_n_getter, _n_setter)
+    Constant.s = property(_s_getter, _s_setter)
 
 class _ABC(type):
 
@@ -544,6 +561,9 @@ def _new(cls, *args, **kwargs):
         if pos < len(args):
             raise TypeError(f"{cls.__name__} got multiple values for argument {key!r}")
     if cls in _const_types:
+        import warnings
+        warnings.warn(f"ast.{cls.__qualname__} is deprecated; use ast.Constant instead",
+                    DeprecationWarning, stacklevel=2)
         return Constant(*args, **kwargs)
     return Constant.__new__(cls, *args, **kwargs)
 
@@ -566,10 +586,15 @@ class Ellipsis(Constant, metaclass=_ABC):
     _fields = ()
 
     def __new__(cls, *args, **kwargs):
-        if cls is Ellipsis:
+        if cls is _ast_Ellipsis:
+            import warnings
+            warnings.warn("ast.Ellipsis is deprecated; use ast.Constant instead",
+                        DeprecationWarning, stacklevel=2)
             return Constant(..., *args, **kwargs)
         return Constant.__new__(cls, *args, **kwargs)
 
+_ast_Ellipsis = Ellipsis
+
 _const_types = {
     Num: (int, float, complex),
     Str: (str,),
@@ -603,6 +628,9 @@ def __new__(cls, value, **kwargs):
 class ExtSlice(slice):
     """Deprecated AST node class. Use ast.Tuple instead."""
     def __new__(cls, dims=(), **kwargs):
+        import warnings
+        warnings.warn("ast.ExtSlice is deprecated; use ast.Tuple instead",
+                      DeprecationWarning, stacklevel=2)
         return Tuple(list(dims), Load(), **kwargs)
 
 # If the ast module is loaded more than once, only add deprecated methods once
@@ -612,9 +640,14 @@ def __new__(cls, dims=(), **kwargs):
 
     def _dims_getter(self):
         """Deprecated. Use elts instead."""
+        import warnings
+        warnings.warn("Attribute dims is deprecated; use elts instead",
+                      DeprecationWarning, stacklevel=2)
         return self.elts
 
     def _dims_setter(self, value):
+        warnings.warn("Attribute dims is deprecated, use elts instead",
+                      DeprecationWarning, stacklevel=2)
         self.elts = value
 
     Tuple.dims = property(_dims_getter, _dims_setter)
@@ -1699,6 +1732,25 @@ def unparse(ast_obj):
     return unparser.visit(ast_obj)
 
 
+_deprecated_globals = {
+    name: (globals().pop(name), '; use ast.Constant instead')
+    for name in ('Num', 'Str', 'Bytes', 'NameConstant', 'Ellipsis')
+}
+_deprecated_globals['slice'] = globals().pop('slice'), ''
+_deprecated_globals['Index'] = globals().pop('Index'), ''
+_deprecated_globals['ExtSlice'] = globals().pop('ExtSlice'), '; use ast.Tuple instead'
+
+def __getattr__(name):
+    if name in _deprecated_globals:
+        value, details = _deprecated_globals[name]
+        globals()[name] = value
+        import warnings
+        warnings.warn(f"ast.{name} is deprecated{details}",
+                      DeprecationWarning, stacklevel=2)
+        return value
+    raise AttributeError(f"module 'ast' has no attribute '{name}'")
+
+
 def main():
     import argparse
 
diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py
index 039d1c1010b6d1..f50e55afc30f82 100644
--- a/Lib/test/test_ast.py
+++ b/Lib/test/test_ast.py
@@ -11,6 +11,7 @@
 from textwrap import dedent
 
 from test import support
+from test.support.import_helper import import_fresh_module
 
 def to_tuple(t):
     if t is None or isinstance(t, (str, int, complex)):
@@ -251,6 +252,7 @@ def to_tuple(t):
 # excepthandler, arguments, keywords, alias
 
 class AST_Tests(unittest.TestCase):
+    maxDiff = None
 
     def _is_ast_node(self, name, node):
         if not isinstance(node, type):
@@ -366,13 +368,48 @@ def test_base_classes(self):
         self.assertTrue(issubclass(ast.comprehension, ast.AST))
         self.assertTrue(issubclass(ast.Gt, ast.AST))
 
-    def test_field_attr_existence(self):
-        for name, item in ast.__dict__.items():
+    def test_import_deprecated(self):
+        ast = import_fresh_module('ast')
+        for name in 'Num', 'Str', 'Bytes', 'NameConstant', 'Ellipsis':
+            with self.assertWarnsRegex(DeprecationWarning,
+                    fr'ast\.{name} is deprecated; use ast\.Constant instead'):
+                getattr(ast, name)
+        with self.assertWarnsRegex(DeprecationWarning,
+                r'ast\.slice is deprecated'):
+            ast.slice
+        with self.assertWarnsRegex(DeprecationWarning,
+                r'ast\.Index is deprecated'):
+            ast.Index
+        with self.assertWarnsRegex(DeprecationWarning,
+                r'ast\.ExtSlice is deprecated; use ast\.Tuple instead'):
+            ast.ExtSlice
+
+    def test_field_attr_existence_deprecated(self):
+        with warnings.catch_warnings():
+            warnings.filterwarnings('ignore', '', DeprecationWarning)
+            from ast import Num, Str, Bytes, NameConstant, Ellipsis
+            from ast import slice, Index, ExtSlice
+
+        for name in ('Num', 'Str', 'Bytes', 'NameConstant', 'Ellipsis',
+                     'slice', 'Index', 'ExtSlice'):
+            item = getattr(ast, name)
             if self._is_ast_node(name, item):
                 if name == 'Index':
                     # Index(value) just returns value now.
                     # The argument is required.
                     continue
+                with self.subTest(item):
+                 with self.assertWarns(DeprecationWarning):
+                    x = item()
+                if isinstance(x, ast.AST):
+                    self.assertEqual(type(x._fields), tuple)
+
+    def test_field_attr_existence(self):
+        for name, item in ast.__dict__.items():
+            if name in {'Num', 'Str', 'Bytes', 'NameConstant', 'Ellipsis',
+                        'slice', 'Index', 'ExtSlice'}:
+                continue
+            if self._is_ast_node(name, item):
                 x = item()
                 if isinstance(x, ast.AST):
                     self.assertEqual(type(x._fields), tuple)
@@ -390,25 +427,108 @@ def test_arguments(self):
         self.assertEqual(x.args, 2)
         self.assertEqual(x.vararg, 3)
 
+    def test_field_attr_writable_deprecated(self):
+        with warnings.catch_warnings():
+            warnings.filterwarnings('ignore', '', DeprecationWarning)
+            x = ast.Num()
+        # We can assign to _fields
+        x._fields = 666
+        self.assertEqual(x._fields, 666)
+
     def test_field_attr_writable(self):
-        x = ast.Num()
+        x = ast.Constant()
         # We can assign to _fields
         x._fields = 666
         self.assertEqual(x._fields, 666)
 
+    def test_classattrs_deprecated(self):
+        with warnings.catch_warnings():
+            warnings.filterwarnings('ignore', '', DeprecationWarning)
+            from ast import Num, Str, Bytes, NameConstant, Ellipsis
+
+        with warnings.catch_warnings(record=True) as wlog:
+            warnings.filterwarnings('always', '', DeprecationWarning)
+            x = ast.Num()
+            self.assertEqual(x._fields, ('value', 'kind'))
+
+            with self.assertRaises(AttributeError):
+                x.value
+
+            with self.assertRaises(AttributeError):
+                x.n
+
+            x = ast.Num(42)
+            self.assertEqual(x.value, 42)
+            self.assertEqual(x.n, 42)
+
+            with self.assertRaises(AttributeError):
+                x.lineno
+
+            with self.assertRaises(AttributeError):
+                x.foobar
+
+            x = ast.Num(lineno=2)
+            self.assertEqual(x.lineno, 2)
+
+            x = ast.Num(42, lineno=0)
+            self.assertEqual(x.lineno, 0)
+            self.assertEqual(x._fields, ('value', 'kind'))
+            self.assertEqual(x.value, 42)
+            self.assertEqual(x.n, 42)
+
+            self.assertRaises(TypeError, ast.Num, 1, None, 2)
+            self.assertRaises(TypeError, ast.Num, 1, None, 2, lineno=0)
+
+            # Arbitrary keyword arguments are supported
+            self.assertEqual(ast.Num(1, foo='bar').foo, 'bar')
+
+            with self.assertRaisesRegex(TypeError, "Num got multiple values for argument 'n'"):
+                ast.Num(1, n=2)
+
+            self.assertEqual(ast.Num(42).n, 42)
+            self.assertEqual(ast.Num(4.25).n, 4.25)
+            self.assertEqual(ast.Num(4.25j).n, 4.25j)
+            self.assertEqual(ast.Str('42').s, '42')
+            self.assertEqual(ast.Bytes(b'42').s, b'42')
+            self.assertIs(ast.NameConstant(True).value, True)
+            self.assertIs(ast.NameConstant(False).value, False)
+            self.assertIs(ast.NameConstant(None).value, None)
+
+        self.assertEqual([str(w.message) for w in wlog], [
+            'ast.Num is deprecated; use ast.Constant instead',
+            'Attribute n is deprecated; use value instead',
+            'ast.Num is deprecated; use ast.Constant instead',
+            'Attribute n is deprecated; use value instead',
+            'ast.Num is deprecated; use ast.Constant instead',
+            'ast.Num is deprecated; use ast.Constant instead',
+            'Attribute n is deprecated; use value instead',
+            'ast.Num is deprecated; use ast.Constant instead',
+            'ast.Num is deprecated; use ast.Constant instead',
+            'ast.Num is deprecated; use ast.Constant instead',
+            'ast.Num is deprecated; use ast.Constant instead',
+            'Attribute n is deprecated; use value instead',
+            'ast.Num is deprecated; use ast.Constant instead',
+            'Attribute n is deprecated; use value instead',
+            'ast.Num is deprecated; use ast.Constant instead',
+            'Attribute n is deprecated; use value instead',
+            'ast.Str is deprecated; use ast.Constant instead',
+            'Attribute s is deprecated; use value instead',
+            'ast.Bytes is deprecated; use ast.Constant instead',
+            'Attribute s is deprecated; use value instead',
+            'ast.NameConstant is deprecated; use ast.Constant instead',
+            'ast.NameConstant is deprecated; use ast.Constant instead',
+            'ast.NameConstant is deprecated; use ast.Constant instead',
+        ])
+
     def test_classattrs(self):
-        x = ast.Num()
+        x = ast.Constant()
         self.assertEqual(x._fields, ('value', 'kind'))
 
         with self.assertRaises(AttributeError):
             x.value
 
-        with self.assertRaises(AttributeError):
-            x.n
-
-        x = ast.Num(42)
+        x = ast.Constant(42)
         self.assertEqual(x.value, 42)
-        self.assertEqual(x.n, 42)
 
         with self.assertRaises(AttributeError):
             x.lineno
@@ -416,36 +536,23 @@ def test_classattrs(self):
         with self.assertRaises(AttributeError):
             x.foobar
 
-        x = ast.Num(lineno=2)
+        x = ast.Constant(lineno=2)
         self.assertEqual(x.lineno, 2)
 
-        x = ast.Num(42, lineno=0)
+        x = ast.Constant(42, lineno=0)
         self.assertEqual(x.lineno, 0)
         self.assertEqual(x._fields, ('value', 'kind'))
         self.assertEqual(x.value, 42)
-        self.assertEqual(x.n, 42)
 
-        self.assertRaises(TypeError, ast.Num, 1, None, 2)
-        self.assertRaises(TypeError, ast.Num, 1, None, 2, lineno=0)
+        self.assertRaises(TypeError, ast.Constant, 1, None, 2)
+        self.assertRaises(TypeError, ast.Constant, 1, None, 2, lineno=0)
 
         # Arbitrary keyword arguments are supported
         self.assertEqual(ast.Constant(1, foo='bar').foo, 'bar')
-        self.assertEqual(ast.Num(1, foo='bar').foo, 'bar')
 
-        with self.assertRaisesRegex(TypeError, "Num got multiple values for argument 'n'"):
-            ast.Num(1, n=2)
         with self.assertRaisesRegex(TypeError, "Constant got multiple values for argument 'value'"):
             ast.Constant(1, value=2)
 
-        self.assertEqual(ast.Num(42).n, 42)
-        self.assertEqual(ast.Num(4.25).n, 4.25)
-        self.assertEqual(ast.Num(4.25j).n, 4.25j)
-        self.assertEqual(ast.Str('42').s, '42')
-        self.assertEqual(ast.Bytes(b'42').s, b'42')
-        self.assertIs(ast.NameConstant(True).value, True)
-        self.assertIs(ast.NameConstant(False).value, False)
-        self.assertIs(ast.NameConstant(None).value, None)
-
         self.assertEqual(ast.Constant(42).value, 42)
         self.assertEqual(ast.Constant(4.25).value, 4.25)
         self.assertEqual(ast.Constant(4.25j).value, 4.25j)
@@ -457,85 +564,162 @@ def test_classattrs(self):
         self.assertIs(ast.Constant(...).value, ...)
 
     def test_realtype(self):
-        self.assertEqual(type(ast.Num(42)), ast.Constant)
-        self.assertEqual(type(ast.Num(4.25)), ast.Constant)
-        self.assertEqual(type(ast.Num(4.25j)), ast.Constant)
-        self.assertEqual(type(ast.Str('42')), ast.Constant)
-        self.assertEqual(type(ast.Bytes(b'42')), ast.Constant)
-        self.assertEqual(type(ast.NameConstant(True)), ast.Constant)
-        self.assertEqual(type(ast.NameConstant(False)), ast.Constant)
-        self.assertEqual(type(ast.NameConstant(None)), ast.Constant)
-        self.assertEqual(type(ast.Ellipsis()), ast.Constant)
+        with warnings.catch_warnings():
+            warnings.filterwarnings('ignore', '', DeprecationWarning)
+            from ast import Num, Str, Bytes, NameConstant, Ellipsis
+
+        with warnings.catch_warnings(record=True) as wlog:
+            warnings.filterwarnings('always', '', DeprecationWarning)
+            self.assertEqual(type(ast.Num(42)), ast.Constant)
+            self.assertEqual(type(ast.Num(4.25)), ast.Constant)
+            self.assertEqual(type(ast.Num(4.25j)), ast.Constant)
+            self.assertEqual(type(ast.Str('42')), ast.Constant)
+            self.assertEqual(type(ast.Bytes(b'42')), ast.Constant)
+            self.assertEqual(type(ast.NameConstant(True)), ast.Constant)
+            self.assertEqual(type(ast.NameConstant(False)), ast.Constant)
+            self.assertEqual(type(ast.NameConstant(None)), ast.Constant)
+            self.assertEqual(type(ast.Ellipsis()), ast.Constant)
+
+        self.assertEqual([str(w.message) for w in wlog], [
+            'ast.Num is deprecated; use ast.Constant instead',
+            'ast.Num is deprecated; use ast.Constant instead',
+            'ast.Num is deprecated; use ast.Constant instead',
+            'ast.Str is deprecated; use ast.Constant instead',
+            'ast.Bytes is deprecated; use ast.Constant instead',
+            'ast.NameConstant is deprecated; use ast.Constant instead',
+            'ast.NameConstant is deprecated; use ast.Constant instead',
+            'ast.NameConstant is deprecated; use ast.Constant instead',
+            'ast.Ellipsis is deprecated; use ast.Constant instead',
+        ])
 
     def test_isinstance(self):
-        self.assertTrue(isinstance(ast.Num(42), ast.Num))
-        self.assertTrue(isinstance(ast.Num(4.2), ast.Num))
-        self.assertTrue(isinstance(ast.Num(4.2j), ast.Num))
-        self.assertTrue(isinstance(ast.Str('42'), ast.Str))
-        self.assertTrue(isinstance(ast.Bytes(b'42'), ast.Bytes))
-        self.assertTrue(isinstance(ast.NameConstant(True), ast.NameConstant))
-        self.assertTrue(isinstance(ast.NameConstant(False), ast.NameConstant))
-        self.assertTrue(isinstance(ast.NameConstant(None), ast.NameConstant))
-        self.assertTrue(isinstance(ast.Ellipsis(), ast.Ellipsis))
-
-        self.assertTrue(isinstance(ast.Constant(42), ast.Num))
-        self.assertTrue(isinstance(ast.Constant(4.2), ast.Num))
-        self.assertTrue(isinstance(ast.Constant(4.2j), ast.Num))
-        self.assertTrue(isinstance(ast.Constant('42'), ast.Str))
-        self.assertTrue(isinstance(ast.Constant(b'42'), ast.Bytes))
-        self.assertTrue(isinstance(ast.Constant(True), ast.NameConstant))
-        self.assertTrue(isinstance(ast.Constant(False), ast.NameConstant))
-        self.assertTrue(isinstance(ast.Constant(None), ast.NameConstant))
-        self.assertTrue(isinstance(ast.Constant(...), ast.Ellipsis))
-
-        self.assertFalse(isinstance(ast.Str('42'), ast.Num))
-        self.assertFalse(isinstance(ast.Num(42), ast.Str))
-        self.assertFalse(isinstance(ast.Str('42'), ast.Bytes))
-        self.assertFalse(isinstance(ast.Num(42), ast.NameConstant))
-        self.assertFalse(isinstance(ast.Num(42), ast.Ellipsis))
-        self.assertFalse(isinstance(ast.NameConstant(True), ast.Num))
-        self.assertFalse(isinstance(ast.NameConstant(False), ast.Num))
-
-        self.assertFalse(isinstance(ast.Constant('42'), ast.Num))
-        self.assertFalse(isinstance(ast.Constant(42), ast.Str))
-        self.assertFalse(isinstance(ast.Constant('42'), ast.Bytes))
-        self.assertFalse(isinstance(ast.Constant(42), ast.NameConstant))
-        self.assertFalse(isinstance(ast.Constant(42), ast.Ellipsis))
-        self.assertFalse(isinstance(ast.Constant(True), ast.Num))
-        self.assertFalse(isinstance(ast.Constant(False), ast.Num))
-
-        self.assertFalse(isinstance(ast.Constant(), ast.Num))
-        self.assertFalse(isinstance(ast.Constant(), ast.Str))
-        self.assertFalse(isinstance(ast.Constant(), ast.Bytes))
-        self.assertFalse(isinstance(ast.Constant(), ast.NameConstant))
-        self.assertFalse(isinstance(ast.Constant(), ast.Ellipsis))
-
-        class S(str): pass
-        self.assertTrue(isinstance(ast.Constant(S('42')), ast.Str))
-        self.assertFalse(isinstance(ast.Constant(S('42')), ast.Num))
-
-    def test_subclasses(self):
-        class N(ast.Num):
+        with warnings.catch_warnings():
+            warnings.filterwarnings('ignore', '', DeprecationWarning)
+            from ast import Num, Str, Bytes, NameConstant, Ellipsis
+
+        with warnings.catch_warnings(record=True) as wlog:
+            warnings.filterwarnings('always', '', DeprecationWarning)
+            self.assertTrue(isinstance(ast.Num(42), ast.Num))
+            self.assertTrue(isinstance(ast.Num(4.2), ast.Num))
+            self.assertTrue(isinstance(ast.Num(4.2j), ast.Num))
+            self.assertTrue(isinstance(ast.Str('42'), ast.Str))
+            self.assertTrue(isinstance(ast.Bytes(b'42'), ast.Bytes))
+            self.assertTrue(isinstance(ast.NameConstant(True), ast.NameConstant))
+            self.assertTrue(isinstance(ast.NameConstant(False), ast.NameConstant))
+            self.assertTrue(isinstance(ast.NameConstant(None), ast.NameConstant))
+            self.assertTrue(isinstance(ast.Ellipsis(), ast.Ellipsis))
+
+            self.assertTrue(isinstance(ast.Constant(42), ast.Num))
+            self.assertTrue(isinstance(ast.Constant(4.2), ast.Num))
+            self.assertTrue(isinstance(ast.Constant(4.2j), ast.Num))
+            self.assertTrue(isinstance(ast.Constant('42'), ast.Str))
+            self.assertTrue(isinstance(ast.Constant(b'42'), ast.Bytes))
+            self.assertTrue(isinstance(ast.Constant(True), ast.NameConstant))
+            self.assertTrue(isinstance(ast.Constant(False), ast.NameConstant))
+            self.assertTrue(isinstance(ast.Constant(None), ast.NameConstant))
+            self.assertTrue(isinstance(ast.Constant(...), ast.Ellipsis))
+
+            self.assertFalse(isinstance(ast.Str('42'), ast.Num))
+            self.assertFalse(isinstance(ast.Num(42), ast.Str))
+            self.assertFalse(isinstance(ast.Str('42'), ast.Bytes))
+            self.assertFalse(isinstance(ast.Num(42), ast.NameConstant))
+            self.assertFalse(isinstance(ast.Num(42), ast.Ellipsis))
+            self.assertFalse(isinstance(ast.NameConstant(True), ast.Num))
+            self.assertFalse(isinstance(ast.NameConstant(False), ast.Num))
+
+            self.assertFalse(isinstance(ast.Constant('42'), ast.Num))
+            self.assertFalse(isinstance(ast.Constant(42), ast.Str))
+            self.assertFalse(isinstance(ast.Constant('42'), ast.Bytes))
+            self.assertFalse(isinstance(ast.Constant(42), ast.NameConstant))
+            self.assertFalse(isinstance(ast.Constant(42), ast.Ellipsis))
+            self.assertFalse(isinstance(ast.Constant(True), ast.Num))
+            self.assertFalse(isinstance(ast.Constant(False), ast.Num))
+
+            self.assertFalse(isinstance(ast.Constant(), ast.Num))
+            self.assertFalse(isinstance(ast.Constant(), ast.Str))
+            self.assertFalse(isinstance(ast.Constant(), ast.Bytes))
+            self.assertFalse(isinstance(ast.Constant(), ast.NameConstant))
+            self.assertFalse(isinstance(ast.Constant(), ast.Ellipsis))
+
+            class S(str): pass
+            self.assertTrue(isinstance(ast.Constant(S('42')), ast.Str))
+            self.assertFalse(isinstance(ast.Constant(S('42')), ast.Num))
+
+        self.assertEqual([str(w.message) for w in wlog], [
+            'ast.Num is deprecated; use ast.Constant instead',
+            'ast.Num is deprecated; use ast.Constant instead',
+            'ast.Num is deprecated; use ast.Constant instead',
+            'ast.Str is deprecated; use ast.Constant instead',
+            'ast.Bytes is deprecated; use ast.Constant instead',
+            'ast.NameConstant is deprecated; use ast.Constant instead',
+            'ast.NameConstant is deprecated; use ast.Constant instead',
+            'ast.NameConstant is deprecated; use ast.Constant instead',
+            'ast.Ellipsis is deprecated; use ast.Constant instead',
+            'ast.Str is deprecated; use ast.Constant instead',
+            'ast.Num is deprecated; use ast.Constant instead',
+            'ast.Str is deprecated; use ast.Constant instead',
+            'ast.Num is deprecated; use ast.Constant instead',
+            'ast.Num is deprecated; use ast.Constant instead',
+            'ast.NameConstant is deprecated; use ast.Constant instead',
+            'ast.NameConstant is deprecated; use ast.Constant instead',
+        ])
+
+    def test_constant_subclasses_deprecated(self):
+        with warnings.catch_warnings():
+            warnings.filterwarnings('ignore', '', DeprecationWarning)
+            from ast import Num
+
+        with warnings.catch_warnings(record=True) as wlog:
+            warnings.filterwarnings('always', '', DeprecationWarning)
+            class N(ast.Num):
+                def __init__(self, *args, **kwargs):
+                    super().__init__(*args, **kwargs)
+                    self.z = 'spam'
+            class N2(ast.Num):
+                pass
+
+            n = N(42)
+            self.assertEqual(n.n, 42)
+            self.assertEqual(n.z, 'spam')
+            self.assertEqual(type(n), N)
+            self.assertTrue(isinstance(n, N))
+            self.assertTrue(isinstance(n, ast.Num))
+            self.assertFalse(isinstance(n, N2))
+            self.assertFalse(isinstance(ast.Num(42), N))
+            n = N(n=42)
+            self.assertEqual(n.n, 42)
+            self.assertEqual(type(n), N)
+
+        self.assertEqual([str(w.message) for w in wlog], [
+            'Attribute n is deprecated; use value instead',
+            'Attribute n is deprecated; use value instead',
+            'ast.Num is deprecated; use ast.Constant instead',
+            'Attribute n is deprecated; use value instead',
+            'Attribute n is deprecated; use value instead',
+        ])
+
+    def test_constant_subclasses(self):
+        class N(ast.Constant):
             def __init__(self, *args, **kwargs):
                 super().__init__(*args, **kwargs)
                 self.z = 'spam'
-        class N2(ast.Num):
+        class N2(ast.Constant):
             pass
 
         n = N(42)
-        self.assertEqual(n.n, 42)
+        self.assertEqual(n.value, 42)
         self.assertEqual(n.z, 'spam')
         self.assertEqual(type(n), N)
         self.assertTrue(isinstance(n, N))
-        self.assertTrue(isinstance(n, ast.Num))
+        self.assertTrue(isinstance(n, ast.Constant))
         self.assertFalse(isinstance(n, N2))
-        self.assertFalse(isinstance(ast.Num(42), N))
-        n = N(n=42)
-        self.assertEqual(n.n, 42)
+        self.assertFalse(isinstance(ast.Constant(42), N))
+        n = N(value=42)
+        self.assertEqual(n.value, 42)
         self.assertEqual(type(n), N)
 
     def test_module(self):
-        body = [ast.Num(42)]
+        body = [ast.Constant(42)]
         x = ast.Module(body, [])
         self.assertEqual(x.body, body)
 
@@ -548,8 +732,8 @@ def test_nodeclasses(self):
         x.foobarbaz = 5
         self.assertEqual(x.foobarbaz, 5)
 
-        n1 = ast.Num(1)
-        n3 = ast.Num(3)
+        n1 = ast.Constant(1)
+        n3 = ast.Constant(3)
         addop = ast.Add()
         x = ast.BinOp(n1, addop, n3)
         self.assertEqual(x.left, n1)
@@ -861,7 +1045,7 @@ def test_dump_incomplete(self):
 
     def test_copy_location(self):
         src = ast.parse('1 + 1', mode='eval')
-        src.body.right = ast.copy_location(ast.Num(2), src.body.right)
+        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, '
@@ -878,7 +1062,7 @@ def test_copy_location(self):
     def test_fix_missing_locations(self):
         src = ast.parse('write("spam")')
         src.body.append(ast.Expr(ast.Call(ast.Name('spam', ast.Load()),
-                                          [ast.Str('eggs')], [])))
+                                          [ast.Constant('eggs')], [])))
         self.assertEqual(src, ast.fix_missing_locations(src))
         self.maxDiff = None
         self.assertEqual(ast.dump(src, include_attributes=True),
@@ -1171,9 +1355,9 @@ def arguments(args=None, posonlyargs=None, vararg=None,
         check(arguments(args=args), "must have Load context")
         check(arguments(posonlyargs=args), "must have Load context")
         check(arguments(kwonlyargs=args), "must have Load context")
-        check(arguments(defaults=[ast.Num(3)]),
+        check(arguments(defaults=[ast.Constant(3)]),
                        "more positional defaults than args")
-        check(arguments(kw_defaults=[ast.Num(4)]),
+        check(arguments(kw_defaults=[ast.Constant(4)]),
                        "length of kwonlyargs is not the same as kw_defaults")
         args = [ast.arg("x", ast.Name("x", ast.Load()))]
         check(arguments(args=args, defaults=[ast.Name("x", ast.Store())]),
@@ -1226,9 +1410,9 @@ def test_delete(self):
                   "must have Del context")
 
     def test_assign(self):
-        self.stmt(ast.Assign([], ast.Num(3)), "empty targets on Assign")
-        self.stmt(ast.Assign([None], ast.Num(3)), "None disallowed")
-        self.stmt(ast.Assign([ast.Name("x", ast.Load())], ast.Num(3)),
+        self.stmt(ast.Assign([], ast.Constant(3)), "empty targets on Assign")
+        self.stmt(ast.Assign([None], ast.Constant(3)), "None disallowed")
+        self.stmt(ast.Assign([ast.Name("x", ast.Load())], ast.Constant(3)),
                   "must have Store context")
         self.stmt(ast.Assign([ast.Name("x", ast.Store())],
                                 ast.Name("y", ast.Store())),
@@ -1256,39 +1440,39 @@ def test_for(self):
         self.stmt(ast.For(x, y, [p], [e]), "must have Load context")
 
     def test_while(self):
-        self.stmt(ast.While(ast.Num(3), [], []), "empty body on While")
+        self.stmt(ast.While(ast.Constant(3), [], []), "empty body on While")
         self.stmt(ast.While(ast.Name("x", ast.Store()), [ast.Pass()], []),
                   "must have Load context")
-        self.stmt(ast.While(ast.Num(3), [ast.Pass()],
+        self.stmt(ast.While(ast.Constant(3), [ast.Pass()],
                              [ast.Expr(ast.Name("x", ast.Store()))]),
                              "must have Load context")
 
     def test_if(self):
-        self.stmt(ast.If(ast.Num(3), [], []), "empty body on If")
+        self.stmt(ast.If(ast.Constant(3), [], []), "empty body on If")
         i = ast.If(ast.Name("x", ast.Store()), [ast.Pass()], [])
         self.stmt(i, "must have Load context")
-        i = ast.If(ast.Num(3), [ast.Expr(ast.Name("x", ast.Store()))], [])
+        i = ast.If(ast.Constant(3), [ast.Expr(ast.Name("x", ast.Store()))], [])
         self.stmt(i, "must have Load context")
-        i = ast.If(ast.Num(3), [ast.Pass()],
+        i = ast.If(ast.Constant(3), [ast.Pass()],
                    [ast.Expr(ast.Name("x", ast.Store()))])
         self.stmt(i, "must have Load context")
 
     def test_with(self):
         p = ast.Pass()
         self.stmt(ast.With([], [p]), "empty items on With")
-        i = ast.withitem(ast.Num(3), None)
+        i = ast.withitem(ast.Constant(3), None)
         self.stmt(ast.With([i], []), "empty body on With")
         i = ast.withitem(ast.Name("x", ast.Store()), None)
         self.stmt(ast.With([i], [p]), "must have Load context")
-        i = ast.withitem(ast.Num(3), ast.Name("x", ast.Load()))
+        i = ast.withitem(ast.Constant(3), ast.Name("x", ast.Load()))
         self.stmt(ast.With([i], [p]), "must have Store context")
 
     def test_raise(self):
-        r = ast.Raise(None, ast.Num(3))
+        r = ast.Raise(None, ast.Constant(3))
         self.stmt(r, "Raise with cause but no exception")
         r = ast.Raise(ast.Name("x", ast.Store()), None)
         self.stmt(r, "must have Load context")
-        r = ast.Raise(ast.Num(4), ast.Name("x", ast.Store()))
+        r = ast.Raise(ast.Constant(4), ast.Name("x", ast.Store()))
         self.stmt(r, "must have Load context")
 
     def test_try(self):
@@ -1359,11 +1543,11 @@ def test_expr(self):
     def test_boolop(self):
         b = ast.BoolOp(ast.And(), [])
         self.expr(b, "less than 2 values")
-        b = ast.BoolOp(ast.And(), [ast.Num(3)])
+        b = ast.BoolOp(ast.And(), [ast.Constant(3)])
         self.expr(b, "less than 2 values")
-        b = ast.BoolOp(ast.And(), [ast.Num(4), None])
+        b = ast.BoolOp(ast.And(), [ast.Constant(4), None])
         self.expr(b, "None disallowed")
-        b = ast.BoolOp(ast.And(), [ast.Num(4), ast.Name("x", ast.Store())])
+        b = ast.BoolOp(ast.And(), [ast.Constant(4), ast.Name("x", ast.Store())])
         self.expr(b, "must have Load context")
 
     def test_unaryop(self):
@@ -1451,11 +1635,11 @@ def test_compare(self):
         left = ast.Name("x", ast.Load())
         comp = ast.Compare(left, [ast.In()], [])
         self.expr(comp, "no comparators")
-        comp = ast.Compare(left, [ast.In()], [ast.Num(4), ast.Num(5)])
+        comp = ast.Compare(left, [ast.In()], [ast.Constant(4), ast.Constant(5)])
         self.expr(comp, "different number of comparators and operands")
-        comp = ast.Compare(ast.Num("blah"), [ast.In()], [left])
+        comp = ast.Compare(ast.Constant("blah"), [ast.In()], [left])
         self.expr(comp)
-        comp = ast.Compare(left, [ast.In()], [ast.Num("blah")])
+        comp = ast.Compare(left, [ast.In()], [ast.Constant("blah")])
         self.expr(comp)
 
     def test_call(self):
@@ -1471,23 +1655,37 @@ def test_call(self):
         self.expr(call, "must have Load context")
 
     def test_num(self):
-        class subint(int):
-            pass
-        class subfloat(float):
-            pass
-        class subcomplex(complex):
-            pass
-        for obj in "0", "hello":
-            self.expr(ast.Num(obj))
-        for obj in subint(), subfloat(), subcomplex():
-            self.expr(ast.Num(obj), "invalid type", exc=TypeError)
+        with warnings.catch_warnings(record=True) as wlog:
+            warnings.filterwarnings('ignore', '', DeprecationWarning)
+            from ast import Num
+
+        with warnings.catch_warnings(record=True) as wlog:
+            warnings.filterwarnings('always', '', DeprecationWarning)
+            class subint(int):
+                pass
+            class subfloat(float):
+                pass
+            class subcomplex(complex):
+                pass
+            for obj in "0", "hello":
+                self.expr(ast.Num(obj))
+            for obj in subint(), subfloat(), subcomplex():
+                self.expr(ast.Num(obj), "invalid type", exc=TypeError)
+
+        self.assertEqual([str(w.message) for w in wlog], [
+            'ast.Num is deprecated; use ast.Constant instead',
+            'ast.Num is deprecated; use ast.Constant instead',
+            'ast.Num is deprecated; use ast.Constant instead',
+            'ast.Num is deprecated; use ast.Constant instead',
+            'ast.Num is deprecated; use ast.Constant instead',
+        ])
 
     def test_attribute(self):
         attr = ast.Attribute(ast.Name("x", ast.Store()), "y", ast.Load())
         self.expr(attr, "must have Load context")
 
     def test_subscript(self):
-        sub = ast.Subscript(ast.Name("x", ast.Store()), ast.Num(3),
+        sub = ast.Subscript(ast.Name("x", ast.Store()), ast.Constant(3),
                             ast.Load())
         self.expr(sub, "must have Load context")
         x = ast.Name("x", ast.Load())
@@ -1507,7 +1705,7 @@ def test_subscript(self):
     def test_starred(self):
         left = ast.List([ast.Starred(ast.Name("x", ast.Load()), ast.Store())],
                         ast.Store())
-        assign = ast.Assign([left], ast.Num(4))
+        assign = ast.Assign([left], ast.Constant(4))
         self.stmt(assign, "must have Store context")
 
     def _sequence(self, fac):
@@ -1522,7 +1720,17 @@ def test_tuple(self):
         self._sequence(ast.Tuple)
 
     def test_nameconstant(self):
-        self.expr(ast.NameConstant(4))
+        with warnings.catch_warnings(record=True) as wlog:
+            warnings.filterwarnings('ignore', '', DeprecationWarning)
+            from ast import NameConstant
+
+        with warnings.catch_warnings(record=True) as wlog:
+            warnings.filterwarnings('always', '', DeprecationWarning)
+            self.expr(ast.NameConstant(4))
+
+        self.assertEqual([str(w.message) for w in wlog], [
+            'ast.NameConstant is deprecated; use ast.Constant instead',
+        ])
 
     def test_stdlib_validates(self):
         stdlib = os.path.dirname(ast.__file__)
@@ -2199,10 +2407,15 @@ def visit_Ellipsis(self, node):
         ])
         self.assertEqual([str(w.message) for w in wlog], [
             'visit_Num is deprecated; add visit_Constant',
+            'Attribute n is deprecated; use value instead',
             'visit_Num is deprecated; add visit_Constant',
+            'Attribute n is deprecated; use value instead',
             'visit_Num is deprecated; add visit_Constant',
+            'Attribute n is deprecated; use value instead',
             'visit_Str is deprecated; add visit_Constant',
+            'Attribute s is deprecated; use value instead',
             'visit_Bytes is deprecated; add visit_Constant',
+            'Attribute s is deprecated; use value instead',
             'visit_NameConstant is deprecated; add visit_Constant',
             'visit_NameConstant is deprecated; add visit_Constant',
             'visit_Ellipsis is deprecated; add visit_Constant',
diff --git a/Misc/NEWS.d/next/Library/2022-02-19-14-19-34.bpo-46797.6BXZX4.rst b/Misc/NEWS.d/next/Library/2022-02-19-14-19-34.bpo-46797.6BXZX4.rst
new file mode 100644
index 00000000000000..48675dfd477ed8
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2022-02-19-14-19-34.bpo-46797.6BXZX4.rst
@@ -0,0 +1,2 @@
+Deprecation warnings are now emitted for deprecated features in the
+:mod:`ast` module which were deprecated in documentation only.

From fdefe87709bd774b9d02ac584cd52884a5da9ecb Mon Sep 17 00:00:00 2001
From: Alex Waygood <Alex.Waygood@Gmail.com>
Date: Thu, 4 May 2023 13:12:59 +0100
Subject: [PATCH 2/2] fix patchcheck

Co-authored-by: Hugo van Kemenade <hugovk@users.noreply.github.com>
---
 Lib/test/test_ast.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py
index 9fb0fa1e83d053..03f7f5c5e32270 100644
--- a/Lib/test/test_ast.py
+++ b/Lib/test/test_ast.py
@@ -468,8 +468,8 @@ def test_field_attr_existence_deprecated(self):
                     # The argument is required.
                     continue
                 with self.subTest(item):
-                 with self.assertWarns(DeprecationWarning):
-                    x = item()
+                    with self.assertWarns(DeprecationWarning):
+                        x = item()
                 if isinstance(x, ast.AST):
                     self.assertEqual(type(x._fields), tuple)