Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions Doc/library/ast.rst
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,9 @@ Node classes

.. attribute:: _field_types

The :attr:`!_field_types` attribute on each concrete class is a dictionary
mapping field names (as also listed in :attr:`_fields`) to their types.
The :attr:`!_field_types` attribute on each class is a dictionary mapping
field names (as also listed in :attr:`_fields`) to their types. Classes
without fields, such as the abstract classes, have an empty dictionary.

.. doctest::

Expand All @@ -85,6 +86,10 @@ Node classes

.. versionadded:: 3.13

.. versionchanged:: 3.16
The attribute is now also available on abstract classes and on other
classes without fields.

.. attribute:: lineno
col_offset
end_lineno
Expand Down
12 changes: 12 additions & 0 deletions Lib/test/test_ast/test_ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,18 @@ def test_field_attr_existence(self):
if isinstance(x, ast.AST):
self.assertIs(type(x._fields), tuple)

def test_field_types_attr_existence(self):
# gh-135797: every AST class has a _field_types dictionary whose keys
# are the names listed in _fields. Abstract classes have no fields
# and thus an empty dictionary, just like their empty _fields tuple.
for name, cls in ast.__dict__.items():
if isinstance(cls, type) and issubclass(cls, ast.AST):
with self.subTest(cls=name):
self.assertIsInstance(cls._field_types, dict)
self.assertEqual(set(cls._field_types), set(cls._fields))
self.assertEqual(ast.AST._field_types, {})
self.assertEqual(ast.mod._field_types, {})

def test_dynamic_attr(self):
for name, item in ast.__dict__.items():
# constructor has a different signature
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Add an empty ``_field_types`` dictionary to :class:`ast.AST` and to the
abstract :mod:`ast` classes that have no fields, such as ``ast.mod`` and
``ast.stmt``. The attribute was previously only set on classes with fields.
45 changes: 38 additions & 7 deletions Parser/asdl_c.py
Original file line number Diff line number Diff line change
Expand Up @@ -1134,10 +1134,14 @@ def visitModule(self, mod):
res = -1;
goto cleanup;
}
if (field_types == NULL) {
// Probably a user-defined subclass of AST that lacks _field_types.
if (field_types == NULL ||
(PyDict_Check(field_types) && PyDict_GET_SIZE(field_types) == 0))
{
// Probably a user-defined subclass of AST that lacks _field_types,
// or that inherits the empty dictionary of an abstract AST class.
// This will continue to work as it did before 3.13; i.e., attributes
// that are not passed in simply do not exist on the instance.
Py_XDECREF(field_types);
goto cleanup;
}
remaining_list = PySequence_List(remaining_fields);
Expand Down Expand Up @@ -1690,7 +1694,7 @@ def visitModule(self, mod):
make_type(struct ast_state *state, const char *type, PyObject* base,
const char* const* fields, int num_fields, const char *doc)
{
PyObject *fnames, *result;
PyObject *fnames, *field_types, *result;
int i;
fnames = PyTuple_New(num_fields);
if (!fnames) return NULL;
Expand All @@ -1710,6 +1714,23 @@ def visitModule(self, mod):
state->ast,
state->__doc__, doc);
Py_DECREF(fnames);
if (result == NULL) {
return NULL;
}
// Every AST class owns a _field_types dictionary. Classes without fields,
// such as the abstract sum types, simply have an empty one. The
// dictionaries of concrete classes are filled in by add_ast_annotations().
field_types = PyDict_New();
if (field_types == NULL) {
Py_DECREF(result);
return NULL;
}
if (PyObject_SetAttrString(result, "_field_types", field_types) < 0) {
Py_DECREF(field_types);
Py_DECREF(result);
return NULL;
}
Py_DECREF(field_types);
return result;
}

Expand Down Expand Up @@ -1839,16 +1860,26 @@ def visitModule(self, mod):

static int add_ast_fields(struct ast_state *state)
{
PyObject *empty_tuple;
PyObject *empty_tuple, *empty_dict;
empty_tuple = PyTuple_New(0);
if (!empty_tuple ||
PyObject_SetAttrString(state->AST_type, "_fields", empty_tuple) < 0 ||
if (!empty_tuple) {
return -1;
}
empty_dict = PyDict_New();
if (empty_dict == NULL) {
Py_DECREF(empty_tuple);
return -1;
}
if (PyObject_SetAttrString(state->AST_type, "_fields", empty_tuple) < 0 ||
PyObject_SetAttrString(state->AST_type, "_field_types", empty_dict) < 0 ||
PyObject_SetAttrString(state->AST_type, "__match_args__", empty_tuple) < 0 ||
PyObject_SetAttrString(state->AST_type, "_attributes", empty_tuple) < 0) {
Py_XDECREF(empty_tuple);
Py_DECREF(empty_tuple);
Py_DECREF(empty_dict);
return -1;
}
Py_DECREF(empty_tuple);
Py_DECREF(empty_dict);
return 0;
}

Expand Down
45 changes: 38 additions & 7 deletions Python/Python-ast.c

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading