Skip to content

Commit 38c4028

Browse files
[3.13] gh-121332: Make AST node constructor check _attributes instead of hardcoding attributes (GH-121334) (#121625)
(cherry picked from commit 58e8cf2)
1 parent 3b5f8d2 commit 38c4028

File tree

4 files changed

+66
-28
lines changed

4 files changed

+66
-28
lines changed

Lib/test/test_ast.py

+12
Original file line numberDiff line numberDiff line change
@@ -3148,6 +3148,18 @@ class FieldsAndTypes(ast.AST):
31483148
obj = FieldsAndTypes(a=1)
31493149
self.assertEqual(obj.a, 1)
31503150

3151+
def test_custom_attributes(self):
3152+
class MyAttrs(ast.AST):
3153+
_attributes = ("a", "b")
3154+
3155+
obj = MyAttrs(a=1, b=2)
3156+
self.assertEqual(obj.a, 1)
3157+
self.assertEqual(obj.b, 2)
3158+
3159+
with self.assertWarnsRegex(DeprecationWarning,
3160+
r"MyAttrs.__init__ got an unexpected keyword argument 'c'."):
3161+
obj = MyAttrs(c=3)
3162+
31513163
def test_fields_and_types_no_default(self):
31523164
class FieldsAndTypesNoDefault(ast.AST):
31533165
_fields = ('a',)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix constructor of :mod:`ast` nodes with custom ``_attributes``. Previously,
2+
passing custom attributes would raise a :py:exc:`DeprecationWarning`. Passing
3+
arguments to the constructor that are not in ``_fields`` or ``_attributes``
4+
remains deprecated. Patch by Jelle Zijlstra.

Parser/asdl_c.py

+25-14
Original file line numberDiff line numberDiff line change
@@ -880,7 +880,7 @@ def visitModule(self, mod):
880880
881881
Py_ssize_t i, numfields = 0;
882882
int res = -1;
883-
PyObject *key, *value, *fields, *remaining_fields = NULL;
883+
PyObject *key, *value, *fields, *attributes = NULL, *remaining_fields = NULL;
884884
if (PyObject_GetOptionalAttr((PyObject*)Py_TYPE(self), state->_fields, &fields) < 0) {
885885
goto cleanup;
886886
}
@@ -947,22 +947,32 @@ def visitModule(self, mod):
947947
goto cleanup;
948948
}
949949
}
950-
else if (
951-
PyUnicode_CompareWithASCIIString(key, "lineno") != 0 &&
952-
PyUnicode_CompareWithASCIIString(key, "col_offset") != 0 &&
953-
PyUnicode_CompareWithASCIIString(key, "end_lineno") != 0 &&
954-
PyUnicode_CompareWithASCIIString(key, "end_col_offset") != 0
955-
) {
956-
if (PyErr_WarnFormat(
957-
PyExc_DeprecationWarning, 1,
958-
"%.400s.__init__ got an unexpected keyword argument '%U'. "
959-
"Support for arbitrary keyword arguments is deprecated "
960-
"and will be removed in Python 3.15.",
961-
Py_TYPE(self)->tp_name, key
962-
) < 0) {
950+
else {
951+
// Lazily initialize "attributes"
952+
if (attributes == NULL) {
953+
attributes = PyObject_GetAttr((PyObject*)Py_TYPE(self), state->_attributes);
954+
if (attributes == NULL) {
955+
res = -1;
956+
goto cleanup;
957+
}
958+
}
959+
int contains = PySequence_Contains(attributes, key);
960+
if (contains == -1) {
963961
res = -1;
964962
goto cleanup;
965963
}
964+
else if (contains == 0) {
965+
if (PyErr_WarnFormat(
966+
PyExc_DeprecationWarning, 1,
967+
"%.400s.__init__ got an unexpected keyword argument '%U'. "
968+
"Support for arbitrary keyword arguments is deprecated "
969+
"and will be removed in Python 3.15.",
970+
Py_TYPE(self)->tp_name, key
971+
) < 0) {
972+
res = -1;
973+
goto cleanup;
974+
}
975+
}
966976
}
967977
res = PyObject_SetAttr(self, key, value);
968978
if (res < 0) {
@@ -1045,6 +1055,7 @@ def visitModule(self, mod):
10451055
Py_DECREF(field_types);
10461056
}
10471057
cleanup:
1058+
Py_XDECREF(attributes);
10481059
Py_XDECREF(fields);
10491060
Py_XDECREF(remaining_fields);
10501061
return res;

Python/Python-ast.c

+25-14
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)