diff options
-rw-r--r-- | Lib/test/test_ast.py | 41 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Core and Builtins/2024-03-26-17-22-38.gh-issue-117266.Kwh79O.rst | 2 | ||||
-rwxr-xr-x | Parser/asdl_c.py | 15 | ||||
-rw-r--r-- | Python/Python-ast.c | 15 |
4 files changed, 69 insertions, 4 deletions
diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index 7cecf31..3929e4e 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -2916,6 +2916,47 @@ class ASTConstructorTests(unittest.TestCase): self.assertEqual(node.name, 'foo') self.assertEqual(node.decorator_list, []) + def test_custom_subclass(self): + class NoInit(ast.AST): + pass + + obj = NoInit() + self.assertIsInstance(obj, NoInit) + self.assertEqual(obj.__dict__, {}) + + class Fields(ast.AST): + _fields = ('a',) + + with self.assertWarnsRegex(DeprecationWarning, + r"Fields provides _fields but not _field_types."): + obj = Fields() + with self.assertRaises(AttributeError): + obj.a + obj = Fields(a=1) + self.assertEqual(obj.a, 1) + + class FieldsAndTypes(ast.AST): + _fields = ('a',) + _field_types = {'a': int | None} + a: int | None = None + + obj = FieldsAndTypes() + self.assertIs(obj.a, None) + obj = FieldsAndTypes(a=1) + self.assertEqual(obj.a, 1) + + class FieldsAndTypesNoDefault(ast.AST): + _fields = ('a',) + _field_types = {'a': int} + + with self.assertWarnsRegex(DeprecationWarning, + r"FieldsAndTypesNoDefault\.__init__ missing 1 required positional argument: 'a'\."): + obj = FieldsAndTypesNoDefault() + with self.assertRaises(AttributeError): + obj.a + obj = FieldsAndTypesNoDefault(a=1) + self.assertEqual(obj.a, 1) + @support.cpython_only class ModuleStateTests(unittest.TestCase): diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-03-26-17-22-38.gh-issue-117266.Kwh79O.rst b/Misc/NEWS.d/next/Core and Builtins/2024-03-26-17-22-38.gh-issue-117266.Kwh79O.rst new file mode 100644 index 0000000..5055954 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-03-26-17-22-38.gh-issue-117266.Kwh79O.rst @@ -0,0 +1,2 @@ +Fix crashes for certain user-created subclasses of :class:`ast.AST`. Such +classes are now expected to set the ``_field_types`` attribute. diff --git a/Parser/asdl_c.py b/Parser/asdl_c.py index 59cc391..c4df2c5 100755 --- a/Parser/asdl_c.py +++ b/Parser/asdl_c.py @@ -973,11 +973,22 @@ ast_type_init(PyObject *self, PyObject *args, PyObject *kw) Py_ssize_t size = PySet_Size(remaining_fields); PyObject *field_types = NULL, *remaining_list = NULL; if (size > 0) { - if (!PyObject_GetOptionalAttr((PyObject*)Py_TYPE(self), &_Py_ID(_field_types), - &field_types)) { + if (PyObject_GetOptionalAttr((PyObject*)Py_TYPE(self), &_Py_ID(_field_types), + &field_types) < 0) { res = -1; goto cleanup; } + if (field_types == NULL) { + if (PyErr_WarnFormat( + PyExc_DeprecationWarning, 1, + "%.400s provides _fields but not _field_types. " + "This will become an error in Python 3.15.", + Py_TYPE(self)->tp_name + ) < 0) { + res = -1; + } + goto cleanup; + } remaining_list = PySequence_List(remaining_fields); if (!remaining_list) { goto set_remaining_cleanup; diff --git a/Python/Python-ast.c b/Python/Python-ast.c index 7b591dd..60b4626 100644 --- a/Python/Python-ast.c +++ b/Python/Python-ast.c @@ -5119,11 +5119,22 @@ ast_type_init(PyObject *self, PyObject *args, PyObject *kw) Py_ssize_t size = PySet_Size(remaining_fields); PyObject *field_types = NULL, *remaining_list = NULL; if (size > 0) { - if (!PyObject_GetOptionalAttr((PyObject*)Py_TYPE(self), &_Py_ID(_field_types), - &field_types)) { + if (PyObject_GetOptionalAttr((PyObject*)Py_TYPE(self), &_Py_ID(_field_types), + &field_types) < 0) { res = -1; goto cleanup; } + if (field_types == NULL) { + if (PyErr_WarnFormat( + PyExc_DeprecationWarning, 1, + "%.400s provides _fields but not _field_types. " + "This will become an error in Python 3.15.", + Py_TYPE(self)->tp_name + ) < 0) { + res = -1; + } + goto cleanup; + } remaining_list = PySequence_List(remaining_fields); if (!remaining_list) { goto set_remaining_cleanup; |